Add JSON string custom functions
This commit is contained in:
		
							parent
							
								
									3990b40c38
								
							
						
					
					
						commit
						18ba1be191
					
				@ -54,6 +54,39 @@ object Custom {
 | 
			
		||||
        mapFunc: (ResultSet, Class<TDoc>) -> TDoc
 | 
			
		||||
    ) = Configuration.dbConn().use { list(query, parameters, clazz, it, mapFunc) }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Execute a query that returns a JSON array of results
 | 
			
		||||
     *
 | 
			
		||||
     * @param query The query to retrieve the results
 | 
			
		||||
     * @param parameters Parameters to use for the query
 | 
			
		||||
     * @param conn The connection over which the query should be executed
 | 
			
		||||
     * @param mapFunc The mapping function to extract the JSON from the query
 | 
			
		||||
     * @return A JSON array of results for the given query
 | 
			
		||||
     * @throws DocumentException If parameters are invalid
 | 
			
		||||
     */
 | 
			
		||||
    @Throws(DocumentException::class)
 | 
			
		||||
    @JvmStatic
 | 
			
		||||
    fun jsonArray(
 | 
			
		||||
        query: String,
 | 
			
		||||
        parameters: Collection<Parameter<*>> = listOf(),
 | 
			
		||||
        conn: Connection,
 | 
			
		||||
        mapFunc: (ResultSet) -> String
 | 
			
		||||
    ) = Parameters.apply(conn, query, parameters).use { Results.toJsonArray(it, mapFunc) }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Execute a query that returns a JSON array of results (creates connection)
 | 
			
		||||
     *
 | 
			
		||||
     * @param query The query to retrieve the results
 | 
			
		||||
     * @param parameters Parameters to use for the query
 | 
			
		||||
     * @param mapFunc The mapping function to extract the JSON from the query
 | 
			
		||||
     * @return A JSON array of results for the given query
 | 
			
		||||
     * @throws DocumentException If parameters are invalid
 | 
			
		||||
     */
 | 
			
		||||
    @Throws(DocumentException::class)
 | 
			
		||||
    @JvmStatic
 | 
			
		||||
    fun jsonArray(query: String, parameters: Collection<Parameter<*>> = listOf(), mapFunc: (ResultSet) -> String) =
 | 
			
		||||
        Configuration.dbConn().use { jsonArray(query, parameters, it, mapFunc) }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Execute a query that returns one or no results
 | 
			
		||||
     *
 | 
			
		||||
@ -94,6 +127,41 @@ object Custom {
 | 
			
		||||
        mapFunc: (ResultSet, Class<TDoc>) -> TDoc
 | 
			
		||||
    ) = Configuration.dbConn().use { single(query, parameters, clazz, it, mapFunc) }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Execute a query that returns JSON for one or no documents
 | 
			
		||||
     *
 | 
			
		||||
     * @param query The query to retrieve the results
 | 
			
		||||
     * @param parameters Parameters to use for the query
 | 
			
		||||
     * @param conn The connection over which the query should be executed
 | 
			
		||||
     * @param mapFunc The mapping function between the document and the domain item
 | 
			
		||||
     * @return The JSON for the document if found, an empty object (`{}`) if not
 | 
			
		||||
     * @throws DocumentException If parameters are invalid
 | 
			
		||||
     */
 | 
			
		||||
    @Throws(DocumentException::class)
 | 
			
		||||
    @JvmStatic
 | 
			
		||||
    fun jsonSingle(
 | 
			
		||||
        query: String,
 | 
			
		||||
        parameters: Collection<Parameter<*>> = listOf(),
 | 
			
		||||
        conn: Connection,
 | 
			
		||||
        mapFunc: (ResultSet) -> String
 | 
			
		||||
    ) = jsonArray("$query LIMIT 1", parameters, conn, mapFunc).let {
 | 
			
		||||
            if (it == "[]") "{}" else it.substring(1, it.length - 1)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Execute a query that returns JSON for one or no documents (creates connection)
 | 
			
		||||
     *
 | 
			
		||||
     * @param query The query to retrieve the results
 | 
			
		||||
     * @param parameters Parameters to use for the query
 | 
			
		||||
     * @param mapFunc The mapping function between the document and the domain item
 | 
			
		||||
     * @return The JSON for the document if found, an empty object (`{}`) if not
 | 
			
		||||
     * @throws DocumentException If parameters are invalid
 | 
			
		||||
     */
 | 
			
		||||
    @Throws(DocumentException::class)
 | 
			
		||||
    @JvmStatic
 | 
			
		||||
    fun jsonSingle(query: String, parameters: Collection<Parameter<*>> = listOf(), mapFunc: (ResultSet) -> String) =
 | 
			
		||||
        Configuration.dbConn().use { jsonSingle(query, parameters, it, mapFunc) }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Execute a query that returns no results
 | 
			
		||||
     *
 | 
			
		||||
 | 
			
		||||
@ -89,4 +89,48 @@ object Results {
 | 
			
		||||
            Dialect.POSTGRESQL -> rs.getBoolean("it")
 | 
			
		||||
            Dialect.SQLITE -> toCount(rs, Long::class.java) > 0L
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Retrieve the JSON text of a document, specifying the field in which the document is found
 | 
			
		||||
     *
 | 
			
		||||
     * @param field The field name containing the JSON document
 | 
			
		||||
     * @param rs A `ResultSet` set to the row with the document to be constructed
 | 
			
		||||
     * @return The JSON text of the document
 | 
			
		||||
     */
 | 
			
		||||
    @JvmStatic
 | 
			
		||||
    fun jsonFromDocument(field: String, rs: ResultSet) =
 | 
			
		||||
        rs.getString(field)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Retrieve the JSON text of a document, specifying the field in which the document is found
 | 
			
		||||
     *
 | 
			
		||||
     * @param rs A `ResultSet` set to the row with the document to be constructed
 | 
			
		||||
     * @return The JSON text of the document
 | 
			
		||||
     */
 | 
			
		||||
    @JvmStatic
 | 
			
		||||
    fun jsonFromData(rs: ResultSet) =
 | 
			
		||||
        jsonFromDocument("data", rs)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a JSON array of items for the results of the given command, using the specified mapping function
 | 
			
		||||
     *
 | 
			
		||||
     * @param stmt The prepared statement to execute
 | 
			
		||||
     * @param mapFunc The mapping function from data reader to JSON text
 | 
			
		||||
     * @return A string with a JSON array of documents from the query's result
 | 
			
		||||
     * @throws DocumentException If there is a problem executing the query (unchecked)
 | 
			
		||||
     */
 | 
			
		||||
    @JvmStatic
 | 
			
		||||
    fun toJsonArray(stmt: PreparedStatement, mapFunc: (ResultSet) -> String) =
 | 
			
		||||
        try {
 | 
			
		||||
            val results = StringBuilder("[")
 | 
			
		||||
            stmt.executeQuery().use {
 | 
			
		||||
                while (it.next()) {
 | 
			
		||||
                    if (results.length > 2) results.append(",")
 | 
			
		||||
                    results.append(mapFunc(it))
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            results.append("]").toString()
 | 
			
		||||
        } catch (ex: SQLException) {
 | 
			
		||||
            throw DocumentException("Error retrieving documents from query: ${ex.message}", ex)
 | 
			
		||||
        }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -29,6 +29,23 @@ fun <TDoc> Connection.customList(
 | 
			
		||||
) =
 | 
			
		||||
    Custom.list(query, parameters, clazz, this, mapFunc)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Execute a query that returns a JSON array of results
 | 
			
		||||
 *
 | 
			
		||||
 * @param query The query to retrieve the results
 | 
			
		||||
 * @param parameters Parameters to use for the query
 | 
			
		||||
 * @param mapFunc The mapping function to extract the JSON from the query
 | 
			
		||||
 * @return A JSON array of results for the given query
 | 
			
		||||
 * @throws DocumentException If parameters are invalid
 | 
			
		||||
 */
 | 
			
		||||
@Throws(DocumentException::class)
 | 
			
		||||
fun Connection.customJsonArray(
 | 
			
		||||
    query: String,
 | 
			
		||||
    parameters: Collection<Parameter<*>> = listOf(),
 | 
			
		||||
    mapFunc: (ResultSet) -> String
 | 
			
		||||
) =
 | 
			
		||||
    Custom.jsonArray(query, parameters, this, mapFunc)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Execute a query that returns one or no results
 | 
			
		||||
 *
 | 
			
		||||
@ -48,6 +65,23 @@ fun <TDoc> Connection.customSingle(
 | 
			
		||||
) =
 | 
			
		||||
    Custom.single(query, parameters, clazz, this, mapFunc)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Execute a query that returns JSON for one or no documents
 | 
			
		||||
 *
 | 
			
		||||
 * @param query The query to retrieve the results
 | 
			
		||||
 * @param parameters Parameters to use for the query
 | 
			
		||||
 * @param mapFunc The mapping function between the document and the domain item
 | 
			
		||||
 * @return The JSON for the document if found, an empty object (`{}`) if not
 | 
			
		||||
 * @throws DocumentException If parameters are invalid
 | 
			
		||||
 */
 | 
			
		||||
@Throws(DocumentException::class)
 | 
			
		||||
fun Connection.customJsonSingle(
 | 
			
		||||
    query: String,
 | 
			
		||||
    parameters: Collection<Parameter<*>> = listOf(),
 | 
			
		||||
    mapFunc: (ResultSet) -> String
 | 
			
		||||
) =
 | 
			
		||||
    Configuration.dbConn().use { Custom.jsonSingle(query, parameters, it, mapFunc) }
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Execute a query that returns no results
 | 
			
		||||
 *
 | 
			
		||||
 | 
			
		||||
@ -13,6 +13,7 @@ import java.util.List;
 | 
			
		||||
import static org.junit.jupiter.api.Assertions.*;
 | 
			
		||||
import static solutions.bitbadger.documents.core.tests.TypesKt.TEST_TABLE;
 | 
			
		||||
import static solutions.bitbadger.documents.java.extensions.ConnExt.*;
 | 
			
		||||
import static solutions.bitbadger.documents.query.QueryUtils.orderBy;
 | 
			
		||||
 | 
			
		||||
final public class CustomFunctions {
 | 
			
		||||
 | 
			
		||||
@ -31,6 +32,29 @@ final public class CustomFunctions {
 | 
			
		||||
        assertEquals(5, result.size(), "There should have been 5 results");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void jsonArrayEmpty(ThrowawayDatabase db) throws DocumentException {
 | 
			
		||||
        assertEquals(0L, countAll(db.getConn(), TEST_TABLE), "The test table should be empty");
 | 
			
		||||
        assertEquals("[]", customJsonArray(db.getConn(), FindQuery.all(TEST_TABLE), List.of(), Results::jsonFromData),
 | 
			
		||||
                "An empty list was not represented correctly");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void jsonArraySingle(ThrowawayDatabase db) throws DocumentException {
 | 
			
		||||
        insert(db.getConn(), TEST_TABLE, new ArrayDocument("one", List.of("2", "3")));
 | 
			
		||||
        assertEquals(JsonFunctions.maybeJsonB("[{\"id\":\"one\",\"values\":[\"2\",\"3\"]}]"),
 | 
			
		||||
                customJsonArray(db.getConn(), FindQuery.all(TEST_TABLE), List.of(), Results::jsonFromData),
 | 
			
		||||
                "A single document list was not represented correctly");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void jsonArrayMany(ThrowawayDatabase db) throws DocumentException {
 | 
			
		||||
        for (ArrayDocument doc : ArrayDocument.testDocuments) { insert(db.getConn(), TEST_TABLE, doc); }
 | 
			
		||||
        assertEquals(JsonFunctions.maybeJsonB("[{\"id\":\"first\",\"values\":[\"a\",\"b\",\"c\"]},"
 | 
			
		||||
                        + "{\"id\":\"second\",\"values\":[\"c\",\"d\",\"e\"]},"
 | 
			
		||||
                        + "{\"id\":\"third\",\"values\":[\"x\",\"y\",\"z\"]}]"),
 | 
			
		||||
                customJsonArray(db.getConn(), FindQuery.all(TEST_TABLE) + orderBy(List.of(Field.named("id"))),
 | 
			
		||||
                        List.of(), Results::jsonFromData),
 | 
			
		||||
                "A multiple document list was not represented correctly");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void singleNone(ThrowawayDatabase db) throws DocumentException {
 | 
			
		||||
        assertFalse(
 | 
			
		||||
                customSingle(db.getConn(), FindQuery.all(TEST_TABLE), List.of(), JsonDocument.class, Results::fromData)
 | 
			
		||||
@ -46,6 +70,18 @@ final public class CustomFunctions {
 | 
			
		||||
                "There should have been a document returned");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void jsonSingleNone(ThrowawayDatabase db) throws DocumentException {
 | 
			
		||||
        assertEquals("{}", customJsonSingle(db.getConn(), FindQuery.all(TEST_TABLE), List.of(), Results::jsonFromData),
 | 
			
		||||
                "An empty document was not represented correctly");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void jsonSingleOne(ThrowawayDatabase db) throws DocumentException {
 | 
			
		||||
        insert(db.getConn(), TEST_TABLE, new ArrayDocument("me", List.of("myself", "i")));
 | 
			
		||||
        assertEquals(JsonFunctions.maybeJsonB("{\"id\":\"me\",\"values\":[\"myself\",\"i\"]}"),
 | 
			
		||||
                customJsonSingle(db.getConn(), FindQuery.all(TEST_TABLE), List.of(), Results::jsonFromData),
 | 
			
		||||
                "A single document was not represented correctly");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void nonQueryChanges(ThrowawayDatabase db) throws DocumentException {
 | 
			
		||||
        JsonDocument.load(db);
 | 
			
		||||
        assertEquals(5L,
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,22 @@
 | 
			
		||||
package solutions.bitbadger.documents.core.tests.java.integration;
 | 
			
		||||
 | 
			
		||||
import solutions.bitbadger.documents.Configuration;
 | 
			
		||||
import solutions.bitbadger.documents.Dialect;
 | 
			
		||||
import solutions.bitbadger.documents.DocumentException;
 | 
			
		||||
 | 
			
		||||
public class JsonFunctions {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * PostgreSQL, when returning JSONB as a string, has spaces after commas and colons delineating fields and values.
 | 
			
		||||
     * This function will do a crude string replacement to match the target string based on the dialect being tested.
 | 
			
		||||
     *
 | 
			
		||||
     * @param json The JSON which should be returned
 | 
			
		||||
     * @return The actual expected JSON based on the database being tested
 | 
			
		||||
     */
 | 
			
		||||
    public static String maybeJsonB(String json) throws DocumentException {
 | 
			
		||||
        if (Configuration.dialect() == Dialect.SQLITE) {
 | 
			
		||||
            return json;
 | 
			
		||||
        }
 | 
			
		||||
        return json.replace("\":\"", "\": \"").replace("\",\"", "\", \"").replace("\":[", "\": [");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -27,6 +27,30 @@ final public class PostgreSQLCustomIT {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("jsonArray succeeds with empty array")
 | 
			
		||||
    public void jsonArrayEmpty() throws DocumentException {
 | 
			
		||||
        try (PgDB db = new PgDB()) {
 | 
			
		||||
            CustomFunctions.jsonArrayEmpty(db);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("jsonArray succeeds with a single-item array")
 | 
			
		||||
    public void jsonArraySingle() throws DocumentException {
 | 
			
		||||
        try (PgDB db = new PgDB()) {
 | 
			
		||||
            CustomFunctions.jsonArraySingle(db);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("jsonArray succeeds with a multi-item array")
 | 
			
		||||
    public void jsonArrayMany() throws DocumentException {
 | 
			
		||||
        try (PgDB db = new PgDB()) {
 | 
			
		||||
            CustomFunctions.jsonArrayMany(db);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("single succeeds when document not found")
 | 
			
		||||
    public void singleNone() throws DocumentException {
 | 
			
		||||
@ -43,6 +67,22 @@ final public class PostgreSQLCustomIT {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("jsonSingle succeeds when document not found")
 | 
			
		||||
    public void jsonSingleNone() throws DocumentException {
 | 
			
		||||
        try (PgDB db = new PgDB()) {
 | 
			
		||||
            CustomFunctions.jsonSingleNone(db);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("jsonSingle succeeds when a document is found")
 | 
			
		||||
    public void jsonSingleOne() throws DocumentException {
 | 
			
		||||
        try (PgDB db = new PgDB()) {
 | 
			
		||||
            CustomFunctions.jsonSingleOne(db);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("nonQuery makes changes")
 | 
			
		||||
    public void nonQueryChanges() throws DocumentException {
 | 
			
		||||
 | 
			
		||||
@ -27,6 +27,30 @@ final public class SQLiteCustomIT {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("jsonArray succeeds with empty array")
 | 
			
		||||
    public void jsonArrayEmpty() throws DocumentException {
 | 
			
		||||
        try (SQLiteDB db = new SQLiteDB()) {
 | 
			
		||||
            CustomFunctions.jsonArrayEmpty(db);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("jsonArray succeeds with a single-item array")
 | 
			
		||||
    public void jsonArraySingle() throws DocumentException {
 | 
			
		||||
        try (SQLiteDB db = new SQLiteDB()) {
 | 
			
		||||
            CustomFunctions.jsonArraySingle(db);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("jsonArray succeeds with a multi-item array")
 | 
			
		||||
    public void jsonArrayMany() throws DocumentException {
 | 
			
		||||
        try (SQLiteDB db = new SQLiteDB()) {
 | 
			
		||||
            CustomFunctions.jsonArrayMany(db);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("single succeeds when document not found")
 | 
			
		||||
    public void singleNone() throws DocumentException {
 | 
			
		||||
@ -43,6 +67,22 @@ final public class SQLiteCustomIT {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("jsonSingle succeeds when document not found")
 | 
			
		||||
    public void jsonSingleNone() throws DocumentException {
 | 
			
		||||
        try (SQLiteDB db = new SQLiteDB()) {
 | 
			
		||||
            CustomFunctions.jsonSingleNone(db);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("jsonSingle succeeds when a document is found")
 | 
			
		||||
    public void jsonSingleOne() throws DocumentException {
 | 
			
		||||
        try (SQLiteDB db = new SQLiteDB()) {
 | 
			
		||||
            CustomFunctions.jsonSingleOne(db);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("nonQuery makes changes")
 | 
			
		||||
    public void nonQueryChanges() throws DocumentException {
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,7 @@ import solutions.bitbadger.documents.java.Results
 | 
			
		||||
import solutions.bitbadger.documents.query.CountQuery
 | 
			
		||||
import solutions.bitbadger.documents.query.DeleteQuery
 | 
			
		||||
import solutions.bitbadger.documents.query.FindQuery
 | 
			
		||||
import solutions.bitbadger.documents.query.orderBy
 | 
			
		||||
import kotlin.test.*
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@ -29,6 +30,36 @@ object CustomFunctions {
 | 
			
		||||
        assertEquals(5, result.size, "There should have been 5 results")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun jsonArrayEmpty(db: ThrowawayDatabase) {
 | 
			
		||||
        assertEquals(0L, db.conn.countAll(TEST_TABLE), "The test table should be empty")
 | 
			
		||||
        assertEquals(
 | 
			
		||||
            "[]",
 | 
			
		||||
            db.conn.customJsonArray(FindQuery.all(TEST_TABLE), listOf(), Results::jsonFromData),
 | 
			
		||||
            "An empty list was not represented correctly"
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun jsonArraySingle(db: ThrowawayDatabase) {
 | 
			
		||||
        db.conn.insert(TEST_TABLE, ArrayDocument("one", listOf("2", "3")))
 | 
			
		||||
        assertEquals(
 | 
			
		||||
            JsonFunctions.maybeJsonB("[{\"id\":\"one\",\"values\":[\"2\",\"3\"]}]"),
 | 
			
		||||
            db.conn.customJsonArray(FindQuery.all(TEST_TABLE), listOf(), Results::jsonFromData),
 | 
			
		||||
            "A single document list was not represented correctly"
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun jsonArrayMany(db: ThrowawayDatabase) {
 | 
			
		||||
        ArrayDocument.testDocuments.forEach { db.conn.insert(TEST_TABLE, it) }
 | 
			
		||||
        assertEquals(
 | 
			
		||||
            JsonFunctions.maybeJsonB("[{\"id\":\"first\",\"values\":[\"a\",\"b\",\"c\"]},"
 | 
			
		||||
                + "{\"id\":\"second\",\"values\":[\"c\",\"d\",\"e\"]},"
 | 
			
		||||
                + "{\"id\":\"third\",\"values\":[\"x\",\"y\",\"z\"]}]"),
 | 
			
		||||
            db.conn.customJsonArray(FindQuery.all(TEST_TABLE) + orderBy(listOf(Field.named("id"))), listOf(),
 | 
			
		||||
                Results::jsonFromData),
 | 
			
		||||
            "A multiple document list was not represented correctly"
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun singleNone(db: ThrowawayDatabase) =
 | 
			
		||||
        assertFalse(
 | 
			
		||||
            db.conn.customSingle(
 | 
			
		||||
@ -53,6 +84,19 @@ object CustomFunctions {
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun jsonSingleNone(db: ThrowawayDatabase) =
 | 
			
		||||
        assertEquals("{}", db.conn.customJsonSingle(FindQuery.all(TEST_TABLE), listOf(), Results::jsonFromData),
 | 
			
		||||
            "An empty document was not represented correctly")
 | 
			
		||||
 | 
			
		||||
    fun jsonSingleOne(db: ThrowawayDatabase) {
 | 
			
		||||
        db.conn.insert(TEST_TABLE, ArrayDocument("me", listOf("myself", "i")))
 | 
			
		||||
        assertEquals(
 | 
			
		||||
            JsonFunctions.maybeJsonB("{\"id\":\"me\",\"values\":[\"myself\",\"i\"]}"),
 | 
			
		||||
            db.conn.customJsonSingle(FindQuery.all(TEST_TABLE), listOf(), Results::jsonFromData),
 | 
			
		||||
            "A single document was not represented correctly"
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun nonQueryChanges(db: ThrowawayDatabase) {
 | 
			
		||||
        JsonDocument.load(db)
 | 
			
		||||
        assertEquals(
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										21
									
								
								src/core/src/test/kotlin/integration/JsonFunctions.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/core/src/test/kotlin/integration/JsonFunctions.kt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
package solutions.bitbadger.documents.core.tests.integration
 | 
			
		||||
 | 
			
		||||
import solutions.bitbadger.documents.Configuration
 | 
			
		||||
import solutions.bitbadger.documents.Dialect
 | 
			
		||||
 | 
			
		||||
object JsonFunctions {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * PostgreSQL, when returning JSONB as a string, has spaces after commas and colons delineating fields and values.
 | 
			
		||||
     * This function will do a crude string replacement to match the target string based on the dialect being tested.
 | 
			
		||||
     *
 | 
			
		||||
     * @param json The JSON which should be returned
 | 
			
		||||
     * @return The actual expected JSON based on the database being tested
 | 
			
		||||
     */
 | 
			
		||||
    fun maybeJsonB(json: String) =
 | 
			
		||||
        when (Configuration.dialect()) {
 | 
			
		||||
            Dialect.SQLITE -> json
 | 
			
		||||
            Dialect.POSTGRESQL -> json.replace("\":\"", "\": \"").replace("\",\"", "\", \"").replace("\":[", "\": [")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -20,6 +20,21 @@ class PostgreSQLCustomIT {
 | 
			
		||||
    fun listAll() =
 | 
			
		||||
        PgDB().use(CustomFunctions::listAll)
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("jsonArray succeeds with empty array")
 | 
			
		||||
    fun jsonArrayEmpty() =
 | 
			
		||||
        PgDB().use(CustomFunctions::jsonArrayEmpty)
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("jsonArray succeeds with a single-item list")
 | 
			
		||||
    fun jsonArraySingle() =
 | 
			
		||||
        PgDB().use(CustomFunctions::jsonArraySingle)
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("jsonArray succeeds with a multi-item list")
 | 
			
		||||
    fun jsonArrayMany() =
 | 
			
		||||
        PgDB().use(CustomFunctions::jsonArrayMany)
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("single succeeds when document not found")
 | 
			
		||||
    fun singleNone() =
 | 
			
		||||
@ -30,6 +45,16 @@ class PostgreSQLCustomIT {
 | 
			
		||||
    fun singleOne() =
 | 
			
		||||
        PgDB().use(CustomFunctions::singleOne)
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("jsonSingle succeeds when document not found")
 | 
			
		||||
    fun jsonSingleNone() =
 | 
			
		||||
        PgDB().use(CustomFunctions::jsonSingleNone)
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("jsonSingle succeeds when a document is found")
 | 
			
		||||
    fun jsonSingleOne() =
 | 
			
		||||
        PgDB().use(CustomFunctions::jsonSingleOne)
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("nonQuery makes changes")
 | 
			
		||||
    fun nonQueryChanges() =
 | 
			
		||||
 | 
			
		||||
@ -19,6 +19,21 @@ class SQLiteCustomIT {
 | 
			
		||||
    fun listAll() =
 | 
			
		||||
        SQLiteDB().use(CustomFunctions::listAll)
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("jsonArray succeeds with empty array")
 | 
			
		||||
    fun jsonArrayEmpty() =
 | 
			
		||||
        SQLiteDB().use(CustomFunctions::jsonArrayEmpty)
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("jsonArray succeeds with a single-item list")
 | 
			
		||||
    fun jsonArraySingle() =
 | 
			
		||||
        SQLiteDB().use(CustomFunctions::jsonArraySingle)
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("jsonArray succeeds with a multi-item list")
 | 
			
		||||
    fun jsonArrayMany() =
 | 
			
		||||
        SQLiteDB().use(CustomFunctions::jsonArrayMany)
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("single succeeds when document not found")
 | 
			
		||||
    fun singleNone() =
 | 
			
		||||
@ -29,6 +44,16 @@ class SQLiteCustomIT {
 | 
			
		||||
    fun singleOne() =
 | 
			
		||||
        SQLiteDB().use(CustomFunctions::singleOne)
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("jsonSingle succeeds when document not found")
 | 
			
		||||
    fun jsonSingleNone() =
 | 
			
		||||
        SQLiteDB().use(CustomFunctions::jsonSingleNone)
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("jsonSingle succeeds when a document is found")
 | 
			
		||||
    fun jsonSingleOne() =
 | 
			
		||||
        SQLiteDB().use(CustomFunctions::jsonSingleOne)
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("nonQuery makes changes")
 | 
			
		||||
    fun nonQueryChanges() =
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,7 @@ import solutions.bitbadger.documents.java.Results
 | 
			
		||||
import solutions.bitbadger.documents.query.CountQuery
 | 
			
		||||
import solutions.bitbadger.documents.query.DeleteQuery
 | 
			
		||||
import solutions.bitbadger.documents.query.FindQuery
 | 
			
		||||
import solutions.bitbadger.documents.query.QueryUtils
 | 
			
		||||
 | 
			
		||||
import static org.junit.jupiter.api.Assertions.*
 | 
			
		||||
import static solutions.bitbadger.documents.groovy.tests.Types.TEST_TABLE
 | 
			
		||||
@ -27,6 +28,28 @@ final class CustomFunctions {
 | 
			
		||||
        assertEquals 5, result.size(), 'There should have been 5 results'
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static void jsonArrayEmpty(ThrowawayDatabase db) {
 | 
			
		||||
        assertEquals(0L, db.conn.countAll(TEST_TABLE), 'The test table should be empty')
 | 
			
		||||
        assertEquals('[]', db.conn.customJsonArray(FindQuery.all(TEST_TABLE), List.of(), Results::jsonFromData),
 | 
			
		||||
                'An empty list was not represented correctly');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static void jsonArraySingle(ThrowawayDatabase db) {
 | 
			
		||||
        db.conn.insert(TEST_TABLE, new ArrayDocument("one", List.of("2", "3")))
 | 
			
		||||
        assertEquals(JsonFunctions.maybeJsonB('[{"id":"one","values":["2","3"]}]'),
 | 
			
		||||
                db.conn.customJsonArray(FindQuery.all(TEST_TABLE), List.of(), Results::jsonFromData),
 | 
			
		||||
                'A single document list was not represented correctly')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static void jsonArrayMany(ThrowawayDatabase db) {
 | 
			
		||||
        ArrayDocument.testDocuments.forEach { db.conn.insert(TEST_TABLE, it) }
 | 
			
		||||
        assertEquals(JsonFunctions.maybeJsonB('[{"id":"first","values":["a","b","c"]},'
 | 
			
		||||
                        + '{"id":"second","values":["c","d","e"]},{"id":"third","values":["x","y","z"]}]'),
 | 
			
		||||
                db.conn.customJsonArray(FindQuery.all(TEST_TABLE) + QueryUtils.orderBy(List.of(Field.named("id"))),
 | 
			
		||||
                        List.of(), Results::jsonFromData),
 | 
			
		||||
                'A multiple document list was not represented correctly')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static void singleNone(ThrowawayDatabase db) {
 | 
			
		||||
        assertFalse(db.conn.customSingle(FindQuery.all(TEST_TABLE), List.of(), JsonDocument, Results.&fromData)
 | 
			
		||||
                        .isPresent(),
 | 
			
		||||
@ -41,6 +64,18 @@ final class CustomFunctions {
 | 
			
		||||
                'There should not have been a document returned')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static void jsonSingleNone(ThrowawayDatabase db) {
 | 
			
		||||
        assertEquals('{}', db.conn.customJsonSingle(FindQuery.all(TEST_TABLE), List.of(), Results::jsonFromData),
 | 
			
		||||
                'An empty document was not represented correctly')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static void jsonSingleOne(ThrowawayDatabase db) {
 | 
			
		||||
        db.conn.insert(TEST_TABLE, new ArrayDocument("me", List.of("myself", "i")))
 | 
			
		||||
        assertEquals(JsonFunctions.maybeJsonB('{"id":"me","values":["myself","i"]}'),
 | 
			
		||||
                db.conn.customJsonSingle(FindQuery.all(TEST_TABLE), List.of(), Results::jsonFromData),
 | 
			
		||||
                'A single document was not represented correctly');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static void nonQueryChanges(ThrowawayDatabase db) {
 | 
			
		||||
        JsonDocument.load db
 | 
			
		||||
        assertEquals(5L, db.conn.customScalar(CountQuery.all(TEST_TABLE), List.of(), Long, Results.&toCount),
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,20 @@
 | 
			
		||||
package solutions.bitbadger.documents.groovy.tests.integration
 | 
			
		||||
 | 
			
		||||
import solutions.bitbadger.documents.Configuration
 | 
			
		||||
import solutions.bitbadger.documents.Dialect
 | 
			
		||||
 | 
			
		||||
final class JsonFunctions {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * PostgreSQL, when returning JSONB as a string, has spaces after commas and colons delineating fields and values.
 | 
			
		||||
     * This function will do a crude string replacement to match the target string based on the dialect being tested.
 | 
			
		||||
     *
 | 
			
		||||
     * @param json The JSON which should be returned
 | 
			
		||||
     * @return The actual expected JSON based on the database being tested
 | 
			
		||||
     */
 | 
			
		||||
    static String maybeJsonB(String json) {
 | 
			
		||||
        Configuration.dialect() == Dialect.SQLITE
 | 
			
		||||
                ? json
 | 
			
		||||
                : json.replace('":"', '": "').replace('","', '", "').replace('":[', '": [')
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -21,6 +21,24 @@ final class PostgreSQLCustomIT {
 | 
			
		||||
        new PgDB().withCloseable CustomFunctions.&listAll
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName('jsonArray succeeds with empty array')
 | 
			
		||||
    void jsonArrayEmpty() {
 | 
			
		||||
        new PgDB().withCloseable CustomFunctions.&jsonArrayEmpty
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName('jsonArray succeeds with a single-item array')
 | 
			
		||||
    void jsonArraySingle() {
 | 
			
		||||
        new PgDB().withCloseable CustomFunctions.&jsonArraySingle
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName('jsonArray succeeds with a multi-item array')
 | 
			
		||||
    void jsonArrayMany() {
 | 
			
		||||
        new PgDB().withCloseable CustomFunctions.&jsonArrayMany
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName('single succeeds when document not found')
 | 
			
		||||
    void singleNone() {
 | 
			
		||||
@ -33,6 +51,18 @@ final class PostgreSQLCustomIT {
 | 
			
		||||
        new PgDB().withCloseable CustomFunctions.&singleOne
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName('jsonSingle succeeds when document not found')
 | 
			
		||||
    void jsonSingleNone() {
 | 
			
		||||
        new PgDB().withCloseable CustomFunctions.&jsonSingleNone
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName('jsonSingle succeeds when a document is found')
 | 
			
		||||
    void jsonSingleOne() {
 | 
			
		||||
        new PgDB().withCloseable CustomFunctions.&jsonSingleOne
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName('nonQuery makes changes')
 | 
			
		||||
    void nonQueryChanges() {
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@ package solutions.bitbadger.documents.kotlinx
 | 
			
		||||
 | 
			
		||||
import solutions.bitbadger.documents.*
 | 
			
		||||
import solutions.bitbadger.documents.Configuration
 | 
			
		||||
import solutions.bitbadger.documents.java.Custom as JvmCustom
 | 
			
		||||
import solutions.bitbadger.documents.java.Custom as CoreCustom
 | 
			
		||||
import java.sql.Connection
 | 
			
		||||
import java.sql.ResultSet
 | 
			
		||||
 | 
			
		||||
@ -41,6 +41,35 @@ object Custom {
 | 
			
		||||
        mapFunc: (ResultSet) -> TDoc
 | 
			
		||||
    ) = Configuration.dbConn().use { list(query, parameters, it, mapFunc) }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Execute a query that returns a JSON array of results
 | 
			
		||||
     *
 | 
			
		||||
     * @param query The query to retrieve the results
 | 
			
		||||
     * @param parameters Parameters to use for the query
 | 
			
		||||
     * @param conn The connection over which the query should be executed
 | 
			
		||||
     * @param mapFunc The mapping function to extract the JSON from the query
 | 
			
		||||
     * @return A JSON array of results for the given query
 | 
			
		||||
     * @throws DocumentException If parameters are invalid
 | 
			
		||||
     */
 | 
			
		||||
    fun jsonArray(
 | 
			
		||||
        query: String,
 | 
			
		||||
        parameters: Collection<Parameter<*>> = listOf(),
 | 
			
		||||
        conn: Connection,
 | 
			
		||||
        mapFunc: (ResultSet) -> String
 | 
			
		||||
    ) = CoreCustom.jsonArray(query, parameters, conn, mapFunc)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Execute a query that returns a JSON array of results (creates connection)
 | 
			
		||||
     *
 | 
			
		||||
     * @param query The query to retrieve the results
 | 
			
		||||
     * @param parameters Parameters to use for the query
 | 
			
		||||
     * @param mapFunc The mapping function to extract the JSON from the query
 | 
			
		||||
     * @return A JSON array of results for the given query
 | 
			
		||||
     * @throws DocumentException If parameters are invalid
 | 
			
		||||
     */
 | 
			
		||||
    fun jsonArray(query: String, parameters: Collection<Parameter<*>> = listOf(), mapFunc: (ResultSet) -> String) =
 | 
			
		||||
        CoreCustom.jsonArray(query, parameters, mapFunc)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Execute a query that returns one or no results
 | 
			
		||||
     *
 | 
			
		||||
@ -71,6 +100,35 @@ object Custom {
 | 
			
		||||
        noinline mapFunc: (ResultSet) -> TDoc
 | 
			
		||||
    ) = Configuration.dbConn().use { single(query, parameters, it, mapFunc) }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Execute a query that returns JSON for one or no documents
 | 
			
		||||
     *
 | 
			
		||||
     * @param query The query to retrieve the results
 | 
			
		||||
     * @param parameters Parameters to use for the query
 | 
			
		||||
     * @param conn The connection over which the query should be executed
 | 
			
		||||
     * @param mapFunc The mapping function between the document and the domain item
 | 
			
		||||
     * @return The JSON for the document if found, an empty object (`{}`) if not
 | 
			
		||||
     * @throws DocumentException If parameters are invalid
 | 
			
		||||
     */
 | 
			
		||||
    fun jsonSingle(
 | 
			
		||||
        query: String,
 | 
			
		||||
        parameters: Collection<Parameter<*>> = listOf(),
 | 
			
		||||
        conn: Connection,
 | 
			
		||||
        mapFunc: (ResultSet) -> String
 | 
			
		||||
    ) = CoreCustom.jsonSingle(query, parameters, conn, mapFunc)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Execute a query that returns JSON for one or no documents (creates connection)
 | 
			
		||||
     *
 | 
			
		||||
     * @param query The query to retrieve the results
 | 
			
		||||
     * @param parameters Parameters to use for the query
 | 
			
		||||
     * @param mapFunc The mapping function between the document and the domain item
 | 
			
		||||
     * @return The JSON for the document if found, an empty object (`{}`) if not
 | 
			
		||||
     * @throws DocumentException If parameters are invalid
 | 
			
		||||
     */
 | 
			
		||||
    fun jsonSingle(query: String, parameters: Collection<Parameter<*>> = listOf(), mapFunc: (ResultSet) -> String) =
 | 
			
		||||
        CoreCustom.jsonSingle(query, parameters, mapFunc)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Execute a query that returns no results
 | 
			
		||||
     *
 | 
			
		||||
@ -79,7 +137,7 @@ object Custom {
 | 
			
		||||
     * @param parameters Parameters to use for the query
 | 
			
		||||
     */
 | 
			
		||||
    fun nonQuery(query: String, parameters: Collection<Parameter<*>> = listOf(), conn: Connection) =
 | 
			
		||||
        JvmCustom.nonQuery(query, parameters, conn)
 | 
			
		||||
        CoreCustom.nonQuery(query, parameters, conn)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Execute a query that returns no results
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,7 @@ package solutions.bitbadger.documents.kotlinx
 | 
			
		||||
import solutions.bitbadger.documents.Configuration
 | 
			
		||||
import solutions.bitbadger.documents.Dialect
 | 
			
		||||
import solutions.bitbadger.documents.DocumentException
 | 
			
		||||
import solutions.bitbadger.documents.java.Results as CoreResults
 | 
			
		||||
import java.sql.PreparedStatement
 | 
			
		||||
import java.sql.ResultSet
 | 
			
		||||
import java.sql.SQLException
 | 
			
		||||
@ -73,4 +74,34 @@ object Results {
 | 
			
		||||
            Dialect.POSTGRESQL -> rs.getBoolean("it")
 | 
			
		||||
            Dialect.SQLITE -> toCount(rs) > 0L
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Retrieve the JSON text of a document, specifying the field in which the document is found
 | 
			
		||||
     *
 | 
			
		||||
     * @param field The field name containing the JSON document
 | 
			
		||||
     * @param rs A `ResultSet` set to the row with the document to be constructed
 | 
			
		||||
     * @return The JSON text of the document
 | 
			
		||||
     */
 | 
			
		||||
    fun jsonFromDocument(field: String, rs: ResultSet) =
 | 
			
		||||
        CoreResults.jsonFromDocument(field, rs)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Retrieve the JSON text of a document, specifying the field in which the document is found
 | 
			
		||||
     *
 | 
			
		||||
     * @param rs A `ResultSet` set to the row with the document to be constructed
 | 
			
		||||
     * @return The JSON text of the document
 | 
			
		||||
     */
 | 
			
		||||
    fun jsonFromData(rs: ResultSet) =
 | 
			
		||||
        CoreResults.jsonFromData(rs)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a JSON array of items for the results of the given command, using the specified mapping function
 | 
			
		||||
     *
 | 
			
		||||
     * @param stmt The prepared statement to execute
 | 
			
		||||
     * @param mapFunc The mapping function from data reader to JSON text
 | 
			
		||||
     * @return A string with a JSON array of documents from the query's result
 | 
			
		||||
     * @throws DocumentException If there is a problem executing the query (unchecked)
 | 
			
		||||
     */
 | 
			
		||||
    fun toJsonArray(stmt: PreparedStatement, mapFunc: (ResultSet) -> String) =
 | 
			
		||||
        CoreResults.toJsonArray(stmt, mapFunc)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -19,6 +19,21 @@ inline fun <reified TDoc : Any> Connection.customList(
 | 
			
		||||
    query: String, parameters: Collection<Parameter<*>> = listOf(), mapFunc: (ResultSet) -> TDoc
 | 
			
		||||
) = Custom.list(query, parameters, this, mapFunc)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Execute a query that returns a JSON array of results
 | 
			
		||||
 *
 | 
			
		||||
 * @param query The query to retrieve the results
 | 
			
		||||
 * @param parameters Parameters to use for the query
 | 
			
		||||
 * @param mapFunc The mapping function to extract the JSON from the query
 | 
			
		||||
 * @return A JSON array of results for the given query
 | 
			
		||||
 * @throws DocumentException If parameters are invalid
 | 
			
		||||
 */
 | 
			
		||||
fun Connection.customJsonArray(
 | 
			
		||||
    query: String,
 | 
			
		||||
    parameters: Collection<Parameter<*>> = listOf(),
 | 
			
		||||
    mapFunc: (ResultSet) -> String
 | 
			
		||||
) = Custom.jsonArray(query, parameters, mapFunc)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Execute a query that returns one or no results
 | 
			
		||||
 *
 | 
			
		||||
@ -31,6 +46,21 @@ inline fun <reified TDoc : Any> Connection.customSingle(
 | 
			
		||||
    query: String, parameters: Collection<Parameter<*>> = listOf(), mapFunc: (ResultSet) -> TDoc
 | 
			
		||||
) = Custom.single(query, parameters, this, mapFunc)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Execute a query that returns JSON for one or no documents (creates connection)
 | 
			
		||||
 *
 | 
			
		||||
 * @param query The query to retrieve the results
 | 
			
		||||
 * @param parameters Parameters to use for the query
 | 
			
		||||
 * @param mapFunc The mapping function between the document and the domain item
 | 
			
		||||
 * @return The JSON for the document if found, an empty object (`{}`) if not
 | 
			
		||||
 * @throws DocumentException If parameters are invalid
 | 
			
		||||
 */
 | 
			
		||||
fun Connection.customJsonSingle(
 | 
			
		||||
    query: String,
 | 
			
		||||
    parameters: Collection<Parameter<*>> = listOf(),
 | 
			
		||||
    mapFunc: (ResultSet) -> String
 | 
			
		||||
) = Custom.jsonSingle(query, parameters, mapFunc)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Execute a query that returns no results
 | 
			
		||||
 *
 | 
			
		||||
@ -239,8 +269,7 @@ inline fun <reified TDoc : Any> Connection.findByFields(
 | 
			
		||||
    fields: Collection<Field<*>>,
 | 
			
		||||
    howMatched: FieldMatch? = null,
 | 
			
		||||
    orderBy: Collection<Field<*>>? = null
 | 
			
		||||
) =
 | 
			
		||||
    Find.byFields<TDoc>(tableName, fields, howMatched, orderBy, this)
 | 
			
		||||
) = Find.byFields<TDoc>(tableName, fields, howMatched, orderBy, this)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Retrieve documents using a JSON containment query, ordering results by the optional given fields (PostgreSQL only)
 | 
			
		||||
@ -255,8 +284,7 @@ inline fun <reified TDoc : Any, reified TContains> Connection.findByContains(
 | 
			
		||||
    tableName: String,
 | 
			
		||||
    criteria: TContains,
 | 
			
		||||
    orderBy: Collection<Field<*>>? = null
 | 
			
		||||
) =
 | 
			
		||||
    Find.byContains<TDoc, TContains>(tableName, criteria, orderBy, this)
 | 
			
		||||
) = Find.byContains<TDoc, TContains>(tableName, criteria, orderBy, this)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Retrieve documents using a JSON Path match query, ordering results by the optional given fields (PostgreSQL only)
 | 
			
		||||
@ -271,8 +299,7 @@ inline fun <reified TDoc : Any> Connection.findByJsonPath(
 | 
			
		||||
    tableName: String,
 | 
			
		||||
    path: String,
 | 
			
		||||
    orderBy: Collection<Field<*>>? = null
 | 
			
		||||
) =
 | 
			
		||||
    Find.byJsonPath<TDoc>(tableName, path, orderBy, this)
 | 
			
		||||
) = Find.byJsonPath<TDoc>(tableName, path, orderBy, this)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Retrieve the first document using a field comparison and optional ordering fields
 | 
			
		||||
@ -288,8 +315,7 @@ inline fun <reified TDoc : Any> Connection.findFirstByFields(
 | 
			
		||||
    fields: Collection<Field<*>>,
 | 
			
		||||
    howMatched: FieldMatch? = null,
 | 
			
		||||
    orderBy: Collection<Field<*>>? = null
 | 
			
		||||
) =
 | 
			
		||||
    Find.firstByFields<TDoc>(tableName, fields, howMatched, orderBy, this)
 | 
			
		||||
) = Find.firstByFields<TDoc>(tableName, fields, howMatched, orderBy, this)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Retrieve the first document using a JSON containment query and optional ordering fields (PostgreSQL only)
 | 
			
		||||
@ -304,8 +330,7 @@ inline fun <reified TDoc : Any, reified TContains> Connection.findFirstByContain
 | 
			
		||||
    tableName: String,
 | 
			
		||||
    criteria: TContains,
 | 
			
		||||
    orderBy: Collection<Field<*>>? = null
 | 
			
		||||
) =
 | 
			
		||||
    Find.firstByContains<TDoc, TContains>(tableName, criteria, orderBy, this)
 | 
			
		||||
) = Find.firstByContains<TDoc, TContains>(tableName, criteria, orderBy, this)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Retrieve the first document using a JSON Path match query and optional ordering fields (PostgreSQL only)
 | 
			
		||||
@ -320,8 +345,7 @@ inline fun <reified TDoc : Any> Connection.findFirstByJsonPath(
 | 
			
		||||
    tableName: String,
 | 
			
		||||
    path: String,
 | 
			
		||||
    orderBy: Collection<Field<*>>? = null
 | 
			
		||||
) =
 | 
			
		||||
    Find.firstByJsonPath<TDoc>(tableName, path, orderBy, this)
 | 
			
		||||
) = Find.firstByJsonPath<TDoc>(tableName, path, orderBy, this)
 | 
			
		||||
 | 
			
		||||
// ~~~ DOCUMENT PATCH (PARTIAL UPDATE) QUERIES ~~~
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,7 @@ package solutions.bitbadger.documents.kotlinx.tests.integration
 | 
			
		||||
import solutions.bitbadger.documents.*
 | 
			
		||||
import solutions.bitbadger.documents.kotlinx.Results
 | 
			
		||||
import solutions.bitbadger.documents.kotlinx.extensions.*
 | 
			
		||||
import solutions.bitbadger.documents.kotlinx.tests.ArrayDocument
 | 
			
		||||
import solutions.bitbadger.documents.kotlinx.tests.JsonDocument
 | 
			
		||||
import solutions.bitbadger.documents.kotlinx.tests.TEST_TABLE
 | 
			
		||||
import solutions.bitbadger.documents.query.*
 | 
			
		||||
@ -28,6 +29,36 @@ object CustomFunctions {
 | 
			
		||||
        assertEquals(5, result.size, "There should have been 5 results")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun jsonArrayEmpty(db: ThrowawayDatabase) {
 | 
			
		||||
        assertEquals(0L, db.conn.countAll(TEST_TABLE), "The test table should be empty")
 | 
			
		||||
        assertEquals(
 | 
			
		||||
            "[]",
 | 
			
		||||
            db.conn.customJsonArray(FindQuery.all(TEST_TABLE), listOf(), Results::jsonFromData),
 | 
			
		||||
            "An empty list was not represented correctly"
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun jsonArraySingle(db: ThrowawayDatabase) {
 | 
			
		||||
        db.conn.insert(TEST_TABLE, ArrayDocument("one", listOf("2", "3")))
 | 
			
		||||
        assertEquals(
 | 
			
		||||
            JsonFunctions.maybeJsonB("[{\"id\":\"one\",\"values\":[\"2\",\"3\"]}]"),
 | 
			
		||||
            db.conn.customJsonArray(FindQuery.all(TEST_TABLE), listOf(), Results::jsonFromData),
 | 
			
		||||
            "A single document list was not represented correctly"
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun jsonArrayMany(db: ThrowawayDatabase) {
 | 
			
		||||
        ArrayDocument.testDocuments.forEach { db.conn.insert(TEST_TABLE, it) }
 | 
			
		||||
        assertEquals(
 | 
			
		||||
            JsonFunctions.maybeJsonB("[{\"id\":\"first\",\"values\":[\"a\",\"b\",\"c\"]},"
 | 
			
		||||
                    + "{\"id\":\"second\",\"values\":[\"c\",\"d\",\"e\"]},"
 | 
			
		||||
                    + "{\"id\":\"third\",\"values\":[\"x\",\"y\",\"z\"]}]"),
 | 
			
		||||
            db.conn.customJsonArray(FindQuery.all(TEST_TABLE) + orderBy(listOf(Field.named("id"))), listOf(),
 | 
			
		||||
                Results::jsonFromData),
 | 
			
		||||
            "A multiple document list was not represented correctly"
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun singleNone(db: ThrowawayDatabase) =
 | 
			
		||||
        assertNull(
 | 
			
		||||
            db.conn.customSingle(FindQuery.all(TEST_TABLE), mapFunc = Results::fromData),
 | 
			
		||||
@ -42,6 +73,19 @@ object CustomFunctions {
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun jsonSingleNone(db: ThrowawayDatabase) =
 | 
			
		||||
        assertEquals("{}", db.conn.customJsonSingle(FindQuery.all(TEST_TABLE), listOf(), Results::jsonFromData),
 | 
			
		||||
            "An empty document was not represented correctly")
 | 
			
		||||
 | 
			
		||||
    fun jsonSingleOne(db: ThrowawayDatabase) {
 | 
			
		||||
        db.conn.insert(TEST_TABLE, ArrayDocument("me", listOf("myself", "i")))
 | 
			
		||||
        assertEquals(
 | 
			
		||||
            JsonFunctions.maybeJsonB("{\"id\":\"me\",\"values\":[\"myself\",\"i\"]}"),
 | 
			
		||||
            db.conn.customJsonSingle(FindQuery.all(TEST_TABLE), listOf(), Results::jsonFromData),
 | 
			
		||||
            "A single document was not represented correctly"
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun nonQueryChanges(db: ThrowawayDatabase) {
 | 
			
		||||
        JsonDocument.load(db)
 | 
			
		||||
        assertEquals(
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										20
									
								
								src/kotlinx/src/test/kotlin/integration/JsonFunctions.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/kotlinx/src/test/kotlin/integration/JsonFunctions.kt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
			
		||||
package solutions.bitbadger.documents.kotlinx.tests.integration
 | 
			
		||||
 | 
			
		||||
import solutions.bitbadger.documents.Configuration
 | 
			
		||||
import solutions.bitbadger.documents.Dialect
 | 
			
		||||
 | 
			
		||||
object JsonFunctions {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * PostgreSQL, when returning JSONB as a string, has spaces after commas and colons delineating fields and values.
 | 
			
		||||
     * This function will do a crude string replacement to match the target string based on the dialect being tested.
 | 
			
		||||
     *
 | 
			
		||||
     * @param json The JSON which should be returned
 | 
			
		||||
     * @return The actual expected JSON based on the database being tested
 | 
			
		||||
     */
 | 
			
		||||
    fun maybeJsonB(json: String) =
 | 
			
		||||
        when (Configuration.dialect()) {
 | 
			
		||||
            Dialect.SQLITE -> json
 | 
			
		||||
            Dialect.POSTGRESQL -> json.replace("\":\"", "\": \"").replace("\",\"", "\", \"").replace("\":[", "\": [")
 | 
			
		||||
        }
 | 
			
		||||
}
 | 
			
		||||
@ -20,6 +20,21 @@ class PostgreSQLCustomIT {
 | 
			
		||||
    fun listAll() =
 | 
			
		||||
        PgDB().use(CustomFunctions::listAll)
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("jsonArray succeeds with empty array")
 | 
			
		||||
    fun jsonArrayEmpty() =
 | 
			
		||||
        PgDB().use(CustomFunctions::jsonArrayEmpty)
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("jsonArray succeeds with a single-item array")
 | 
			
		||||
    fun jsonArraySingle() =
 | 
			
		||||
        PgDB().use(CustomFunctions::jsonArraySingle)
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("jsonArray succeeds with a multi-item array")
 | 
			
		||||
    fun jsonArrayMany() =
 | 
			
		||||
        PgDB().use(CustomFunctions::jsonArrayMany)
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("single succeeds when document not found")
 | 
			
		||||
    fun singleNone() =
 | 
			
		||||
@ -30,6 +45,16 @@ class PostgreSQLCustomIT {
 | 
			
		||||
    fun singleOne() =
 | 
			
		||||
        PgDB().use(CustomFunctions::singleOne)
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("jsonSingle succeeds when document not found")
 | 
			
		||||
    fun jsonSingleNone() =
 | 
			
		||||
        PgDB().use(CustomFunctions::jsonSingleNone)
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("jsonSingle succeeds when a document is found")
 | 
			
		||||
    fun jsonSingleOne() =
 | 
			
		||||
        PgDB().use(CustomFunctions::jsonSingleOne)
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("nonQuery makes changes")
 | 
			
		||||
    fun nonQueryChanges() =
 | 
			
		||||
 | 
			
		||||
@ -19,6 +19,21 @@ class SQLiteCustomIT {
 | 
			
		||||
    fun listAll() =
 | 
			
		||||
        SQLiteDB().use(CustomFunctions::listAll)
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("jsonArray succeeds with empty array")
 | 
			
		||||
    fun jsonArrayEmpty() =
 | 
			
		||||
        SQLiteDB().use(CustomFunctions::jsonArrayEmpty)
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("jsonArray succeeds with a single-item array")
 | 
			
		||||
    fun jsonArraySingle() =
 | 
			
		||||
        SQLiteDB().use(CustomFunctions::jsonArraySingle)
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("jsonArray succeeds with a multi-item array")
 | 
			
		||||
    fun jsonArrayMany() =
 | 
			
		||||
        SQLiteDB().use(CustomFunctions::jsonArrayMany)
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("single succeeds when document not found")
 | 
			
		||||
    fun singleNone() =
 | 
			
		||||
@ -29,6 +44,16 @@ class SQLiteCustomIT {
 | 
			
		||||
    fun singleOne() =
 | 
			
		||||
        SQLiteDB().use(CustomFunctions::singleOne)
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("jsonSingle succeeds when document not found")
 | 
			
		||||
    fun jsonSingleNone() =
 | 
			
		||||
        SQLiteDB().use(CustomFunctions::jsonSingleNone)
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("jsonSingle succeeds when a document is found")
 | 
			
		||||
    fun jsonSingleOne() =
 | 
			
		||||
        SQLiteDB().use(CustomFunctions::jsonSingleOne)
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    @DisplayName("nonQuery makes changes")
 | 
			
		||||
    fun nonQueryChanges() =
 | 
			
		||||
 | 
			
		||||
@ -33,7 +33,7 @@ object Custom:
 | 
			
		||||
   */
 | 
			
		||||
  def list[Doc](query: String, conn: Connection, mapFunc: (ResultSet, ClassTag[Doc]) => Doc)
 | 
			
		||||
               (using tag: ClassTag[Doc]): List[Doc] =
 | 
			
		||||
    list(query, List(), conn, mapFunc)
 | 
			
		||||
    list(query, Nil, conn, mapFunc)
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Execute a query that returns a list of results (creates connection)
 | 
			
		||||
@ -59,6 +59,54 @@ object Custom:
 | 
			
		||||
  def list[Doc](query: String, mapFunc: (ResultSet, ClassTag[Doc]) => Doc)(using tag: ClassTag[Doc]): List[Doc] =
 | 
			
		||||
    list(query, List(), mapFunc)
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Execute a query that returns a JSON array of results
 | 
			
		||||
   *
 | 
			
		||||
   * @param query      The query to retrieve the results
 | 
			
		||||
   * @param parameters Parameters to use for the query
 | 
			
		||||
   * @param conn       The connection over which the query should be executed
 | 
			
		||||
   * @param mapFunc    The mapping function to extract the JSON from the query
 | 
			
		||||
   * @return A JSON array of results for the given query
 | 
			
		||||
   * @throws DocumentException If parameters are invalid
 | 
			
		||||
   */
 | 
			
		||||
  def jsonArray(query: String, parameters: Seq[Parameter[?]], conn: Connection, mapFunc: ResultSet => String): String =
 | 
			
		||||
    Using(Parameters.apply(conn, query, parameters)) { stmt => Results.toJsonArray(stmt, mapFunc) }.get
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Execute a query that returns a JSON array of results
 | 
			
		||||
   *
 | 
			
		||||
   * @param query   The query to retrieve the results
 | 
			
		||||
   * @param conn    The connection over which the query should be executed
 | 
			
		||||
   * @param mapFunc The mapping function to extract the JSON from the query
 | 
			
		||||
   * @return A JSON array of results for the given query
 | 
			
		||||
   * @throws DocumentException If parameters are invalid
 | 
			
		||||
   */
 | 
			
		||||
  def jsonArray(query: String, conn: Connection, mapFunc: ResultSet => String): String =
 | 
			
		||||
    jsonArray(query, Nil, conn, mapFunc)
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Execute a query that returns a JSON array of results (creates connection)
 | 
			
		||||
   *
 | 
			
		||||
   * @param query      The query to retrieve the results
 | 
			
		||||
   * @param parameters Parameters to use for the query
 | 
			
		||||
   * @param mapFunc    The mapping function to extract the JSON from the query
 | 
			
		||||
   * @return A JSON array of results for the given query
 | 
			
		||||
   * @throws DocumentException If parameters are invalid
 | 
			
		||||
   */
 | 
			
		||||
  def jsonArray(query: String, parameters: Seq[Parameter[?]], mapFunc: ResultSet => String): String =
 | 
			
		||||
    Using(Configuration.dbConn()) { conn => jsonArray(query, parameters, conn, mapFunc) }.get
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Execute a query that returns a JSON array of results (creates connection)
 | 
			
		||||
   *
 | 
			
		||||
   * @param query   The query to retrieve the results
 | 
			
		||||
   * @param mapFunc The mapping function to extract the JSON from the query
 | 
			
		||||
   * @return A JSON array of results for the given query
 | 
			
		||||
   * @throws DocumentException If parameters are invalid
 | 
			
		||||
   */
 | 
			
		||||
  def jsonArray(query: String, mapFunc: ResultSet => String): String =
 | 
			
		||||
    jsonArray(query, Nil, mapFunc)
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Execute a query that returns one or no results
 | 
			
		||||
   *
 | 
			
		||||
@ -110,6 +158,57 @@ object Custom:
 | 
			
		||||
  def single[Doc](query: String, mapFunc: (ResultSet, ClassTag[Doc]) => Doc)(using tag: ClassTag[Doc]): Option[Doc] =
 | 
			
		||||
    single[Doc](query, List(), mapFunc)
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Execute a query that returns JSON for one or no documents
 | 
			
		||||
   *
 | 
			
		||||
   * @param query      The query to retrieve the results
 | 
			
		||||
   * @param parameters Parameters to use for the query
 | 
			
		||||
   * @param conn       The connection over which the query should be executed
 | 
			
		||||
   * @param mapFunc    The mapping function between the document and the domain item
 | 
			
		||||
   * @return The JSON for the document if found, an empty object (`{}`) if not
 | 
			
		||||
   * @throws DocumentException If parameters are invalid
 | 
			
		||||
   */
 | 
			
		||||
  def jsonSingle(query: String, parameters: Seq[Parameter[?]], conn: Connection, mapFunc: ResultSet => String): String =
 | 
			
		||||
    val result = jsonArray("$query LIMIT 1", parameters, conn, mapFunc)
 | 
			
		||||
    result match
 | 
			
		||||
      case "[]" => "{}"
 | 
			
		||||
      case _ => result.substring(1, result.length - 1)
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Execute a query that returns JSON for one or no documents
 | 
			
		||||
   *
 | 
			
		||||
   * @param query   The query to retrieve the results
 | 
			
		||||
   * @param conn    The connection over which the query should be executed
 | 
			
		||||
   * @param mapFunc The mapping function between the document and the domain item
 | 
			
		||||
   * @return The JSON for the document if found, an empty object (`{}`) if not
 | 
			
		||||
   * @throws DocumentException If parameters are invalid
 | 
			
		||||
   */
 | 
			
		||||
  def jsonSingle(query: String, conn: Connection, mapFunc: ResultSet => String): String =
 | 
			
		||||
    jsonSingle(query, Nil, conn, mapFunc)
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Execute a query that returns JSON for one or no documents (creates connection)
 | 
			
		||||
   *
 | 
			
		||||
   * @param query      The query to retrieve the results
 | 
			
		||||
   * @param parameters Parameters to use for the query
 | 
			
		||||
   * @param mapFunc    The mapping function between the document and the domain item
 | 
			
		||||
   * @return The JSON for the document if found, an empty object (`{}`) if not
 | 
			
		||||
   * @throws DocumentException If parameters are invalid
 | 
			
		||||
   */
 | 
			
		||||
  def jsonSingle(query: String, parameters: Seq[Parameter[?]], mapFunc: ResultSet => String): String =
 | 
			
		||||
    Using(Configuration.dbConn()) { conn => jsonSingle(query, parameters, conn, mapFunc) }.get
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Execute a query that returns JSON for one or no documents (creates connection)
 | 
			
		||||
   *
 | 
			
		||||
   * @param query   The query to retrieve the results
 | 
			
		||||
   * @param mapFunc The mapping function between the document and the domain item
 | 
			
		||||
   * @return The JSON for the document if found, an empty object (`{}`) if not
 | 
			
		||||
   * @throws DocumentException If parameters are invalid
 | 
			
		||||
   */
 | 
			
		||||
  def jsonSingle(query: String, mapFunc: ResultSet => String): String =
 | 
			
		||||
    jsonSingle(query, Nil, mapFunc)
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Execute a query that returns no results
 | 
			
		||||
   *
 | 
			
		||||
 | 
			
		||||
@ -75,3 +75,44 @@ object Results:
 | 
			
		||||
   */
 | 
			
		||||
  def toExists(rs: ResultSet, tag: ClassTag[Boolean] = ClassTag.Boolean): Boolean =
 | 
			
		||||
    CoreResults.toExists(rs, Boolean.getClass)
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Retrieve the JSON text of a document, specifying the field in which the document is found
 | 
			
		||||
   *
 | 
			
		||||
   * @param field The field name containing the JSON document
 | 
			
		||||
   * @param rs    A `ResultSet` set to the row with the document to be constructed
 | 
			
		||||
   * @return The JSON text of the document
 | 
			
		||||
   */
 | 
			
		||||
  def jsonFromDocument(field: String, rs: ResultSet): String =
 | 
			
		||||
    CoreResults.jsonFromDocument(field, rs)
 | 
			
		||||
  
 | 
			
		||||
  /**
 | 
			
		||||
   * Retrieve the JSON text of a document, specifying the field in which the document is found
 | 
			
		||||
   *
 | 
			
		||||
   * @param rs A `ResultSet` set to the row with the document to be constructed
 | 
			
		||||
   * @return The JSON text of the document
 | 
			
		||||
   */
 | 
			
		||||
  def jsonFromData(rs: ResultSet): String =
 | 
			
		||||
    CoreResults.jsonFromData(rs)
 | 
			
		||||
  
 | 
			
		||||
  /**
 | 
			
		||||
   * Create a JSON array of items for the results of the given command, using the specified mapping function
 | 
			
		||||
   *
 | 
			
		||||
   * @param stmt    The prepared statement to execute
 | 
			
		||||
   * @param mapFunc The mapping function from data reader to JSON text
 | 
			
		||||
   * @return A string with a JSON array of documents from the query's result
 | 
			
		||||
   * @throws DocumentException If there is a problem executing the query (unchecked)
 | 
			
		||||
   */
 | 
			
		||||
  def toJsonArray(stmt: PreparedStatement, mapFunc: ResultSet => String): String =
 | 
			
		||||
    try
 | 
			
		||||
      val results = StringBuilder("[")
 | 
			
		||||
      Using(stmt.executeQuery()) { rs =>
 | 
			
		||||
        while (rs.next()) {
 | 
			
		||||
          if (results.length > 2) results.append(",")
 | 
			
		||||
          results.append(mapFunc(rs))
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      results.append("]").toString()
 | 
			
		||||
    catch
 | 
			
		||||
      case ex: SQLException => 
 | 
			
		||||
        throw DocumentException("Error retrieving documents from query: ${ex.message}", ex)
 | 
			
		||||
 | 
			
		||||
@ -35,6 +35,29 @@ extension (conn: Connection)
 | 
			
		||||
                     (using tag: ClassTag[Doc]): List[Doc] =
 | 
			
		||||
    Custom.list[Doc](query, conn, mapFunc)
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Execute a query that returns a JSON array of results
 | 
			
		||||
   *
 | 
			
		||||
   * @param query      The query to retrieve the results
 | 
			
		||||
   * @param parameters Parameters to use for the query
 | 
			
		||||
   * @param mapFunc    The mapping function to extract the JSON from the query
 | 
			
		||||
   * @return A JSON array of results for the given query
 | 
			
		||||
   * @throws DocumentException If parameters are invalid
 | 
			
		||||
   */
 | 
			
		||||
  def customJsonArray(query: String, parameters: Seq[Parameter[?]], mapFunc: ResultSet => String): String =
 | 
			
		||||
    Custom.jsonArray(query, parameters, conn, mapFunc)
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Execute a query that returns a JSON array of results
 | 
			
		||||
   *
 | 
			
		||||
   * @param query   The query to retrieve the results
 | 
			
		||||
   * @param mapFunc The mapping function to extract the JSON from the query
 | 
			
		||||
   * @return A JSON array of results for the given query
 | 
			
		||||
   * @throws DocumentException If parameters are invalid
 | 
			
		||||
   */
 | 
			
		||||
  def customJsonArray(query: String, mapFunc: ResultSet => String): String =
 | 
			
		||||
    Custom.jsonArray(query, mapFunc)
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Execute a query that returns one or no results
 | 
			
		||||
   *
 | 
			
		||||
@ -60,6 +83,29 @@ extension (conn: Connection)
 | 
			
		||||
                       (using tag: ClassTag[Doc]): Option[Doc] =
 | 
			
		||||
    Custom.single[Doc](query, conn, mapFunc)
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Execute a query that returns JSON for one or no documents (creates connection)
 | 
			
		||||
   *
 | 
			
		||||
   * @param query      The query to retrieve the results
 | 
			
		||||
   * @param parameters Parameters to use for the query
 | 
			
		||||
   * @param mapFunc    The mapping function between the document and the domain item
 | 
			
		||||
   * @return The JSON for the document if found, an empty object (`{}`) if not
 | 
			
		||||
   * @throws DocumentException If parameters are invalid
 | 
			
		||||
   */
 | 
			
		||||
  def customJsonSingle(query: String, parameters: Seq[Parameter[?]], mapFunc: ResultSet => String): String =
 | 
			
		||||
    Custom.jsonSingle(query, parameters, conn, mapFunc)
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Execute a query that returns JSON for one or no documents (creates connection)
 | 
			
		||||
   *
 | 
			
		||||
   * @param query   The query to retrieve the results
 | 
			
		||||
   * @param mapFunc The mapping function between the document and the domain item
 | 
			
		||||
   * @return The JSON for the document if found, an empty object (`{}`) if not
 | 
			
		||||
   * @throws DocumentException If parameters are invalid
 | 
			
		||||
   */
 | 
			
		||||
  def customJsonSingle(query: String, mapFunc: ResultSet => String): String =
 | 
			
		||||
    Custom.jsonSingle(query, mapFunc)
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Execute a query that returns no results
 | 
			
		||||
   *
 | 
			
		||||
 | 
			
		||||
@ -1,12 +1,14 @@
 | 
			
		||||
package solutions.bitbadger.documents.scala.tests.integration
 | 
			
		||||
 | 
			
		||||
import org.junit.jupiter.api.Assertions.*
 | 
			
		||||
import solutions.bitbadger.documents.query.{CountQuery, DeleteQuery, FindQuery}
 | 
			
		||||
import solutions.bitbadger.documents.query.{CountQuery, DeleteQuery, FindQuery, QueryUtils}
 | 
			
		||||
import solutions.bitbadger.documents.scala.Results
 | 
			
		||||
import solutions.bitbadger.documents.scala.extensions.*
 | 
			
		||||
import solutions.bitbadger.documents.scala.tests.TEST_TABLE
 | 
			
		||||
import solutions.bitbadger.documents.{Configuration, Field, Parameter, ParameterType}
 | 
			
		||||
 | 
			
		||||
import scala.jdk.CollectionConverters.*
 | 
			
		||||
 | 
			
		||||
object CustomFunctions:
 | 
			
		||||
 | 
			
		||||
  def listEmpty(db: ThrowawayDatabase): Unit =
 | 
			
		||||
@ -20,6 +22,26 @@ object CustomFunctions:
 | 
			
		||||
    val result = db.conn.customList[JsonDocument](FindQuery.all(TEST_TABLE), Results.fromData)
 | 
			
		||||
    assertEquals(5, result.size, "There should have been 5 results")
 | 
			
		||||
 | 
			
		||||
  def jsonArrayEmpty(db: ThrowawayDatabase): Unit =
 | 
			
		||||
    assertEquals(0L, db.conn.countAll(TEST_TABLE), "The test table should be empty")
 | 
			
		||||
    assertEquals("[]", db.conn.customJsonArray(FindQuery.all(TEST_TABLE), Nil, Results.jsonFromData),
 | 
			
		||||
      "An empty list was not represented correctly")
 | 
			
		||||
 | 
			
		||||
  def jsonArraySingle(db: ThrowawayDatabase): Unit =
 | 
			
		||||
    db.conn.insert(TEST_TABLE, ArrayDocument("one", "2" :: "3" :: Nil))
 | 
			
		||||
    assertEquals(JsonFunctions.maybeJsonB("[{\"id\":\"one\",\"values\":[\"2\",\"3\"]}]"),
 | 
			
		||||
      db.conn.customJsonArray(FindQuery.all(TEST_TABLE), Nil, Results.jsonFromData),
 | 
			
		||||
      "A single document list was not represented correctly")
 | 
			
		||||
  
 | 
			
		||||
  def jsonArrayMany(db: ThrowawayDatabase): Unit =
 | 
			
		||||
    ArrayDocument.testDocuments.foreach { doc => db.conn.insert(TEST_TABLE, doc) }
 | 
			
		||||
    assertEquals(JsonFunctions.maybeJsonB("[{\"id\":\"first\",\"values\":[\"a\",\"b\",\"c\"]},"
 | 
			
		||||
        + "{\"id\":\"second\",\"values\":[\"c\",\"d\",\"e\"]},"
 | 
			
		||||
        + "{\"id\":\"third\",\"values\":[\"x\",\"y\",\"z\"]}]"),
 | 
			
		||||
      db.conn.customJsonArray(FindQuery.all(TEST_TABLE) + QueryUtils.orderBy((Field.named("id") :: Nil).asJava), Nil,
 | 
			
		||||
        Results.jsonFromData),
 | 
			
		||||
      "A multiple document list was not represented correctly")
 | 
			
		||||
 | 
			
		||||
  def singleNone(db: ThrowawayDatabase): Unit =
 | 
			
		||||
    assertTrue(db.conn.customSingle[JsonDocument](FindQuery.all(TEST_TABLE), Results.fromData).isEmpty,
 | 
			
		||||
      "There should not have been a document returned")
 | 
			
		||||
@ -29,6 +51,16 @@ object CustomFunctions:
 | 
			
		||||
    assertTrue(db.conn.customSingle[JsonDocument](FindQuery.all(TEST_TABLE), Results.fromData).isDefined,
 | 
			
		||||
      "There should have been a document returned")
 | 
			
		||||
 | 
			
		||||
  def jsonSingleNone(db: ThrowawayDatabase): Unit =
 | 
			
		||||
    assertEquals("{}", db.conn.customJsonSingle(FindQuery.all(TEST_TABLE), Nil, Results.jsonFromData),
 | 
			
		||||
      "An empty document was not represented correctly")
 | 
			
		||||
 | 
			
		||||
  def jsonSingleOne(db: ThrowawayDatabase): Unit =
 | 
			
		||||
    db.conn.insert(TEST_TABLE, ArrayDocument("me", "myself" :: "i" :: Nil))
 | 
			
		||||
    assertEquals(JsonFunctions.maybeJsonB("{\"id\":\"me\",\"values\":[\"myself\",\"i\"]}"),
 | 
			
		||||
      db.conn.customJsonSingle(FindQuery.all(TEST_TABLE), Nil, Results.jsonFromData),
 | 
			
		||||
      "A single document was not represented correctly")
 | 
			
		||||
 | 
			
		||||
  def nonQueryChanges(db: ThrowawayDatabase): Unit =
 | 
			
		||||
    JsonDocument.load(db)
 | 
			
		||||
    assertEquals(5L, db.conn.customScalar[Long](CountQuery.all(TEST_TABLE), Results.toCount),
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										17
									
								
								src/scala/src/test/scala/integration/JsonFunctions.scala
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/scala/src/test/scala/integration/JsonFunctions.scala
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
			
		||||
package solutions.bitbadger.documents.scala.tests.integration
 | 
			
		||||
 | 
			
		||||
import solutions.bitbadger.documents.{Configuration, Dialect}
 | 
			
		||||
 | 
			
		||||
object JsonFunctions:
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * PostgreSQL, when returning JSONB as a string, has spaces after commas and colons delineating fields and values.
 | 
			
		||||
   * This function will do a crude string replacement to match the target string based on the dialect being tested.
 | 
			
		||||
   *
 | 
			
		||||
   * @param json The JSON which should be returned
 | 
			
		||||
   * @return The actual expected JSON based on the database being tested
 | 
			
		||||
   */
 | 
			
		||||
  def maybeJsonB(json: String): String =
 | 
			
		||||
    Configuration.dialect() match
 | 
			
		||||
      case Dialect.SQLITE => json
 | 
			
		||||
      case Dialect.POSTGRESQL => json.replace("\":\"", "\": \"").replace("\",\"", "\", \"").replace("\":[", "\": [")
 | 
			
		||||
@ -17,6 +17,21 @@ class PostgreSQLCustomIT:
 | 
			
		||||
  def listAll(): Unit =
 | 
			
		||||
    Using(PgDB()) { db => CustomFunctions.listAll(db) }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  @DisplayName("jsonArray succeeds with empty array")
 | 
			
		||||
  def jsonArrayEmpty(): Unit =
 | 
			
		||||
    Using(PgDB()) { db => CustomFunctions.jsonArrayEmpty(db) }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  @DisplayName("jsonArray succeeds with a single-item array")
 | 
			
		||||
  def jsonArraySingle(): Unit =
 | 
			
		||||
    Using(PgDB()) { db => CustomFunctions.jsonArraySingle(db) }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  @DisplayName("jsonArray succeeds with a multi-item array")
 | 
			
		||||
  def jsonArrayMany(): Unit =
 | 
			
		||||
    Using(PgDB()) { db => CustomFunctions.jsonArrayMany(db) }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  @DisplayName("single succeeds when document not found")
 | 
			
		||||
  def singleNone(): Unit =
 | 
			
		||||
@ -27,6 +42,16 @@ class PostgreSQLCustomIT:
 | 
			
		||||
  def singleOne(): Unit =
 | 
			
		||||
    Using(PgDB()) { db => CustomFunctions.singleOne(db) }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  @DisplayName("jsonSingle succeeds when document not found")
 | 
			
		||||
  def jsonSingleNone(): Unit =
 | 
			
		||||
    Using(PgDB()) { db => CustomFunctions.jsonSingleNone(db) }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  @DisplayName("jsonSingle succeeds when a document is found")
 | 
			
		||||
  def jsonSingleOne(): Unit =
 | 
			
		||||
    Using(PgDB()) { db => CustomFunctions.jsonSingleOne(db) }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  @DisplayName("nonQuery makes changes")
 | 
			
		||||
  def nonQueryChanges(): Unit =
 | 
			
		||||
 | 
			
		||||
@ -17,6 +17,21 @@ class SQLiteCustomIT:
 | 
			
		||||
  def listAll(): Unit =
 | 
			
		||||
    Using(SQLiteDB()) { db => CustomFunctions.listAll(db) }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  @DisplayName("jsonArray succeeds with empty array")
 | 
			
		||||
  def jsonArrayEmpty(): Unit =
 | 
			
		||||
    Using(SQLiteDB()) { db => CustomFunctions.jsonArrayEmpty(db) }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  @DisplayName("jsonArray succeeds with a single-item array")
 | 
			
		||||
  def jsonArraySingle(): Unit =
 | 
			
		||||
    Using(SQLiteDB()) { db => CustomFunctions.jsonArraySingle(db) }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  @DisplayName("jsonArray succeeds with a multi-item array")
 | 
			
		||||
  def jsonArrayMany(): Unit =
 | 
			
		||||
    Using(SQLiteDB()) { db => CustomFunctions.jsonArrayMany(db) }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  @DisplayName("single succeeds when document not found")
 | 
			
		||||
  def singleNone(): Unit =
 | 
			
		||||
@ -27,6 +42,16 @@ class SQLiteCustomIT:
 | 
			
		||||
  def singleOne(): Unit =
 | 
			
		||||
    Using(SQLiteDB()) { db => CustomFunctions.singleOne(db) }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  @DisplayName("jsonSingle succeeds when document not found")
 | 
			
		||||
  def jsonSingleNone(): Unit =
 | 
			
		||||
    Using(SQLiteDB()) { db => CustomFunctions.jsonSingleNone(db) }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  @DisplayName("jsonSingle succeeds when a document is found")
 | 
			
		||||
  def jsonSingleOne(): Unit =
 | 
			
		||||
    Using(SQLiteDB()) { db => CustomFunctions.jsonSingleOne(db) }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  @DisplayName("nonQuery makes changes")
 | 
			
		||||
  def nonQueryChanges(): Unit =
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user