From 79436d6fd018d057e972d7aba773928500f7f4c0 Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Sat, 29 Mar 2025 19:49:35 -0400 Subject: [PATCH] Add Json support to kotlinx --- src/core/src/main/kotlin/java/Json.kt | 8 +- src/kotlinx/src/main/kotlin/Json.kt | 348 ++++++++++++++++ .../src/main/kotlin/extensions/Connection.kt | 115 +++++- src/kotlinx/src/test/kotlin/Types.kt | 15 + .../test/kotlin/integration/JsonFunctions.kt | 379 +++++++++++++++++- .../kotlin/integration/PostgreSQLJsonIT.kt | 156 +++++++ .../test/kotlin/integration/SQLiteJsonIT.kt | 112 ++++++ 7 files changed, 1125 insertions(+), 8 deletions(-) create mode 100644 src/kotlinx/src/main/kotlin/Json.kt create mode 100644 src/kotlinx/src/test/kotlin/integration/PostgreSQLJsonIT.kt create mode 100644 src/kotlinx/src/test/kotlin/integration/SQLiteJsonIT.kt diff --git a/src/core/src/main/kotlin/java/Json.kt b/src/core/src/main/kotlin/java/Json.kt index a5b4c1e..abeda6f 100644 --- a/src/core/src/main/kotlin/java/Json.kt +++ b/src/core/src/main/kotlin/java/Json.kt @@ -145,12 +145,8 @@ object Json { */ @Throws(DocumentException::class) @JvmStatic - fun byFields( - tableName: String, - fields: Collection>, - howMatched: FieldMatch? = null, - conn: Connection - ) = byFields(tableName, fields, howMatched, null, conn) + fun byFields(tableName: String, fields: Collection>, howMatched: FieldMatch? = null, conn: Connection) = + byFields(tableName, fields, howMatched, null, conn) /** * Retrieve documents using a JSON containment query, ordering results by the given fields (PostgreSQL only) diff --git a/src/kotlinx/src/main/kotlin/Json.kt b/src/kotlinx/src/main/kotlin/Json.kt new file mode 100644 index 0000000..a8d14bd --- /dev/null +++ b/src/kotlinx/src/main/kotlin/Json.kt @@ -0,0 +1,348 @@ +package solutions.bitbadger.documents.kotlinx + +import solutions.bitbadger.documents.* +import solutions.bitbadger.documents.query.FindQuery +import solutions.bitbadger.documents.query.orderBy +import solutions.bitbadger.documents.java.Json as CoreJson +import java.sql.Connection + +/** + * Functions to find and retrieve documents, returning them as JSON strings + */ +object Json { + + /** + * Retrieve all documents in the given table, ordering results by the optional given fields + * + * @param tableName The table from which documents should be retrieved + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @param conn The connection over which documents should be retrieved + * @return A JSON array of documents from the given table + * @throws DocumentException If query execution fails + */ + fun all(tableName: String, orderBy: Collection>? = null, conn: Connection) = + CoreJson.all(tableName, orderBy, conn) + + /** + * Retrieve all documents in the given table (creates connection) + * + * @param tableName The table from which documents should be retrieved + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @return A JSON array of documents from the given table + * @throws DocumentException If no connection string has been set, or if query execution fails + */ + fun all(tableName: String, orderBy: Collection>? = null) = + CoreJson.all(tableName, orderBy) + + /** + * Retrieve all documents in the given table + * + * @param tableName The table from which documents should be retrieved + * @param conn The connection over which documents should be retrieved + * @return A JSON array of documents from the given table + * @throws DocumentException If query execution fails + */ + fun all(tableName: String, conn: Connection) = + CoreJson.all(tableName, conn) + + /** + * Retrieve a document by its ID + * + * @param tableName The table from which the document should be retrieved + * @param docId The ID of the document to retrieve + * @param conn The connection over which documents should be retrieved + * @return A JSON document if found, an empty JSON object if not found + * @throws DocumentException If no dialect has been configured + */ + fun byId(tableName: String, docId: TKey, conn: Connection) = + CoreJson.byId(tableName, docId, conn) + + /** + * Retrieve a document by its ID (creates connection) + * + * @param tableName The table from which the document should be retrieved + * @param docId The ID of the document to retrieve + * @return A JSON document if found, an empty JSON object if not found + * @throws DocumentException If no connection string has been set + */ + fun byId(tableName: String, docId: TKey) = + CoreJson.byId(tableName, docId) + + /** + * Retrieve documents using a field comparison, ordering results by the given fields + * + * @param tableName The table from which documents should be retrieved + * @param fields The fields which should be compared + * @param howMatched How the fields should be matched + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @param conn The connection over which documents should be retrieved + * @return A JSON array of documents matching the field comparison + * @throws DocumentException If no dialect has been configured, or if parameters are invalid + */ + fun byFields( + tableName: String, + fields: Collection>, + howMatched: FieldMatch? = null, + orderBy: Collection>? = null, + conn: Connection + ) = CoreJson.byFields(tableName, fields, howMatched, orderBy, conn) + + /** + * Retrieve documents using a field comparison, ordering results by the given fields (creates connection) + * + * @param tableName The table from which documents should be retrieved + * @param fields The fields which should be compared + * @param howMatched How the fields should be matched + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @return A JSON array of documents matching the field comparison + * @throws DocumentException If no connection string has been set, or if parameters are invalid + */ + fun byFields( + tableName: String, + fields: Collection>, + howMatched: FieldMatch? = null, + orderBy: Collection>? = null + ) = CoreJson.byFields(tableName, fields, howMatched, orderBy) + + /** + * Retrieve documents using a field comparison + * + * @param tableName The table from which documents should be retrieved + * @param fields The fields which should be compared + * @param howMatched How the fields should be matched + * @param conn The connection over which documents should be retrieved + * @return A JSON array of documents matching the field comparison + * @throws DocumentException If no dialect has been configured, or if parameters are invalid + */ + fun byFields(tableName: String, fields: Collection>, howMatched: FieldMatch? = null, conn: Connection) = + CoreJson.byFields(tableName, fields, howMatched, conn) + + /** + * Retrieve documents using a JSON containment query, ordering results by the given fields (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param criteria The object for which JSON containment should be checked + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @param conn The connection over which documents should be retrieved + * @return A JSON array of documents matching the JSON containment query + * @throws DocumentException If called on a SQLite connection + */ + inline fun byContains( + tableName: String, + criteria: TContains, + orderBy: Collection>? = null, + conn: Connection + ) = Custom.jsonArray( + FindQuery.byContains(tableName) + (orderBy?.let(::orderBy) ?: ""), + listOf(Parameters.json(":criteria", criteria)), + conn, + Results::jsonFromData + ) + + /** + * Retrieve documents using a JSON containment query, ordering results by the given fields (PostgreSQL only; creates + * connection) + * + * @param tableName The table from which documents should be retrieved + * @param criteria The object for which JSON containment should be checked + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @return A JSON array of documents matching the JSON containment query + * @throws DocumentException If no connection string has been set, or if called on a SQLite connection + */ + inline fun byContains( + tableName: String, + criteria: TContains, + orderBy: Collection>? = null + ) = Configuration.dbConn().use { byContains(tableName, criteria, orderBy, it) } + + /** + * Retrieve documents using a JSON containment query (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param criteria The object for which JSON containment should be checked + * @param conn The connection over which documents should be retrieved + * @return A JSON array of documents matching the JSON containment query + * @throws DocumentException If called on a SQLite connection + */ + inline fun byContains(tableName: String, criteria: TContains, conn: Connection) = + byContains(tableName, criteria, null, conn) + + /** + * Retrieve documents using a JSON Path match query, ordering results by the given fields (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param path The JSON path comparison to match + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @param conn The connection over which documents should be retrieved + * @return A JSON array of documents matching the JSON Path match query + * @throws DocumentException If called on a SQLite connection + */ + fun byJsonPath(tableName: String, path: String, orderBy: Collection>? = null, conn: Connection) = + CoreJson.byJsonPath(tableName, path, orderBy, conn) + + /** + * Retrieve documents using a JSON Path match query, ordering results by the given fields (PostgreSQL only; creates + * connection) + * + * @param tableName The table from which documents should be retrieved + * @param path The JSON path comparison to match + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @return A JSON array of documents matching the JSON Path match query + * @throws DocumentException If no connection string has been set, or if called on a SQLite connection + */ + fun byJsonPath(tableName: String, path: String, orderBy: Collection>? = null) = + CoreJson.byJsonPath(tableName, path, orderBy) + + /** + * Retrieve documents using a JSON Path match query (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param path The JSON path comparison to match + * @param conn The connection over which documents should be retrieved + * @return A JSON array of documents matching the JSON Path match query + * @throws DocumentException If called on a SQLite connection + */ + fun byJsonPath(tableName: String, path: String, conn: Connection) = + CoreJson.byJsonPath(tableName, path, conn) + + /** + * Retrieve the first document using a field comparison and optional ordering fields + * + * @param tableName The table from which documents should be retrieved + * @param fields The fields which should be compared + * @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`) + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @param conn The connection over which documents should be retrieved + * @return The first JSON document matching the field comparison if found, an empty JSON object otherwise + * @throws DocumentException If no dialect has been configured, or if parameters are invalid + */ + fun firstByFields( + tableName: String, + fields: Collection>, + howMatched: FieldMatch? = null, + orderBy: Collection>? = null, + conn: Connection + ) = CoreJson.firstByFields(tableName, fields, howMatched, orderBy, conn) + + /** + * Retrieve the first document using a field comparison and optional ordering fields (creates connection) + * + * @param tableName The table from which documents should be retrieved + * @param fields The fields which should be compared + * @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`) + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @return The first JSON document matching the field comparison if found, an empty JSON object otherwise + * @throws DocumentException If no connection string has been set, or if parameters are invalid + */ + fun firstByFields( + tableName: String, + fields: Collection>, + howMatched: FieldMatch? = null, + orderBy: Collection>? = null + ) = CoreJson.firstByFields(tableName, fields, howMatched, orderBy) + + /** + * Retrieve the first document using a field comparison + * + * @param tableName The table from which documents should be retrieved + * @param fields The fields which should be compared + * @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`) + * @param conn The connection over which documents should be retrieved + * @return The first JSON document matching the field comparison if found, an empty JSON object otherwise + * @throws DocumentException If no dialect has been configured, or if parameters are invalid + */ + fun firstByFields( + tableName: String, + fields: Collection>, + howMatched: FieldMatch? = null, + conn: Connection + ) = CoreJson.firstByFields(tableName, fields, howMatched, conn) + + /** + * Retrieve the first document using a JSON containment query and optional ordering fields (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param criteria The object for which JSON containment should be checked + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @param conn The connection over which documents should be retrieved + * @return The first JSON document matching the JSON containment query if found, an empty JSON object otherwise + * @throws DocumentException If called on a SQLite connection + */ + inline fun firstByContains( + tableName: String, + criteria: TContains, + orderBy: Collection>? = null, + conn: Connection + ) = Custom.jsonSingle( + FindQuery.byContains(tableName) + (orderBy?.let(::orderBy) ?: ""), + listOf(Parameters.json(":criteria", criteria)), + conn, + Results::jsonFromData + ) + + /** + * Retrieve the first document using a JSON containment query (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param criteria The object for which JSON containment should be checked + * @param conn The connection over which documents should be retrieved + * @return The first JSON document matching the JSON containment query if found, an empty JSON object otherwise + * @throws DocumentException If called on a SQLite connection + */ + inline fun firstByContains(tableName: String, criteria: TContains, conn: Connection) = + firstByContains(tableName, criteria, null, conn) + + /** + * Retrieve the first document using a JSON containment query and optional ordering fields (PostgreSQL only; creates + * connection) + * + * @param tableName The table from which documents should be retrieved + * @param criteria The object for which JSON containment should be checked + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @return The first JSON document matching the JSON containment query if found, an empty JSON object otherwise + * @throws DocumentException If no connection string has been set, or if called on a SQLite connection + */ + inline fun firstByContains( + tableName: String, + criteria: TContains, + orderBy: Collection>? = null + ) = Configuration.dbConn().use { firstByContains(tableName, criteria, orderBy, it) } + + /** + * Retrieve the first document using a JSON Path match query and optional ordering fields (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param path The JSON path comparison to match + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @param conn The connection over which documents should be retrieved + * @return The first JSON document matching the JSON Path match query if found, an empty JSON object otherwise + * @throws DocumentException If called on a SQLite connection + */ + fun firstByJsonPath(tableName: String, path: String, orderBy: Collection>? = null, conn: Connection) = + CoreJson.firstByJsonPath(tableName, path, orderBy, conn) + + /** + * Retrieve the first document using a JSON Path match query (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param path The JSON path comparison to match + * @param conn The connection over which documents should be retrieved + * @return The first JSON document matching the JSON Path match query if found, an empty JSON object otherwise + * @throws DocumentException If called on a SQLite connection + */ + fun firstByJsonPath(tableName: String, path: String, conn: Connection) = + CoreJson.firstByJsonPath(tableName, path, conn) + + /** + * Retrieve the first document using a JSON Path match query and optional ordering fields (PostgreSQL only; creates + * connection) + * + * @param tableName The table from which documents should be retrieved + * @param path The JSON path comparison to match + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @return The first JSON document matching the JSON Path match query if found, an empty JSON object otherwise + * @throws DocumentException If no connection string has been set, or if called on a SQLite connection + */ + fun firstByJsonPath(tableName: String, path: String, orderBy: Collection>? = null) = + CoreJson.firstByJsonPath(tableName, path, orderBy) +} diff --git a/src/kotlinx/src/main/kotlin/extensions/Connection.kt b/src/kotlinx/src/main/kotlin/extensions/Connection.kt index 2b15a1b..090a0c1 100644 --- a/src/kotlinx/src/main/kotlin/extensions/Connection.kt +++ b/src/kotlinx/src/main/kotlin/extensions/Connection.kt @@ -233,7 +233,7 @@ inline fun Connection.existsByContains(tableName: String, cr fun Connection.existsByJsonPath(tableName: String, path: String) = Exists.byJsonPath(tableName, path, this) -// ~~~ DOCUMENT RETRIEVAL QUERIES ~~~ +// ~~~ DOCUMENT RETRIEVAL QUERIES (Domain Objects) ~~~ /** * Retrieve all documents in the given table, ordering results by the optional given fields @@ -347,6 +347,119 @@ inline fun Connection.findFirstByJsonPath( orderBy: Collection>? = null ) = Find.firstByJsonPath(tableName, path, orderBy, this) +// ~~~ DOCUMENT RETRIEVAL QUERIES (Raw JSON) ~~~ + +/** + * Retrieve all documents in the given table + * + * @param tableName The table from which documents should be retrieved + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @return A JSON array of documents from the given table + * @throws DocumentException If no connection string has been set, or if query execution fails + */ +fun Connection.jsonAll(tableName: String, orderBy: Collection>? = null) = + Json.all(tableName, orderBy, this) + +/** + * Retrieve a document by its ID + * + * @param tableName The table from which the document should be retrieved + * @param docId The ID of the document to retrieve + * @return A JSON document if found, an empty JSON object if not found + * @throws DocumentException If no connection string has been set + */ +fun Connection.jsonById(tableName: String, docId: TKey) = + Json.byId(tableName, docId, this) + +/** + * Retrieve documents using a field comparison, ordering results by the given fields + * + * @param tableName The table from which documents should be retrieved + * @param fields The fields which should be compared + * @param howMatched How the fields should be matched + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @return A JSON array of documents matching the field comparison + * @throws DocumentException If no connection string has been set, or if parameters are invalid + */ +fun Connection.jsonByFields( + tableName: String, + fields: Collection>, + howMatched: FieldMatch? = null, + orderBy: Collection>? = null +) = Json.byFields(tableName, fields, howMatched, orderBy, this) + +/** + * Retrieve documents using a JSON containment query, ordering results by the given fields (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param criteria The object for which JSON containment should be checked + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @return A JSON array of documents matching the JSON containment query + * @throws DocumentException If no connection string has been set, or if called on a SQLite connection + */ +inline fun Connection.jsonByContains( + tableName: String, + criteria: TContains, + orderBy: Collection>? = null +) = Json.byContains(tableName, criteria, orderBy, this) + +/** + * Retrieve documents using a JSON Path match query, ordering results by the given fields (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param path The JSON path comparison to match + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @return A JSON array of documents matching the JSON Path match query + * @throws DocumentException If no connection string has been set, or if called on a SQLite connection + */ +fun Connection.jsonByJsonPath(tableName: String, path: String, orderBy: Collection>? = null) = + Json.byJsonPath(tableName, path, orderBy, this) + +/** + * Retrieve the first document using a field comparison and optional ordering fields + * + * @param tableName The table from which documents should be retrieved + * @param fields The fields which should be compared + * @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`) + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @return The first JSON document matching the field comparison if found, an empty JSON object otherwise + * @throws DocumentException If no connection string has been set, or if parameters are invalid + */ +fun Connection.jsonFirstByFields( + tableName: String, + fields: Collection>, + howMatched: FieldMatch? = null, + orderBy: Collection>? = null +) = Json.firstByFields(tableName, fields, howMatched, orderBy, this) + +/** + * Retrieve the first document using a JSON containment query and optional ordering fields (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param criteria The object for which JSON containment should be checked + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @return The first JSON document matching the JSON containment query if found, an empty JSON object otherwise + * @throws DocumentException If no connection string has been set, or if called on a SQLite connection + */ +inline fun Connection.jsonFirstByContains( + tableName: String, + criteria: TContains, + orderBy: Collection>? = null +) = Json.firstByContains(tableName, criteria, orderBy, this) + +/** + * Retrieve the first document using a JSON Path match query and optional ordering fields (PostgreSQL only; creates + * connection) + * + * @param tableName The table from which documents should be retrieved + * @param path The JSON path comparison to match + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @return The first JSON document matching the JSON Path match query if found, an empty JSON object otherwise + * @throws DocumentException If no connection string has been set, or if called on a SQLite connection + */ +fun Connection.jsonFirstByJsonPath(tableName: String, path: String, orderBy: Collection>? = null) = + Json.firstByJsonPath(tableName, path, orderBy, this) + // ~~~ DOCUMENT PATCH (PARTIAL UPDATE) QUERIES ~~~ /** diff --git a/src/kotlinx/src/test/kotlin/Types.kt b/src/kotlinx/src/test/kotlin/Types.kt index ab3754f..426de88 100644 --- a/src/kotlinx/src/test/kotlin/Types.kt +++ b/src/kotlinx/src/test/kotlin/Types.kt @@ -49,5 +49,20 @@ data class JsonDocument(val id: String, val value: String = "", val numValue: In fun load(db: ThrowawayDatabase, tableName: String = TEST_TABLE) = testDocuments.forEach { db.conn.insert(tableName, it) } + + /** Document ID `one` as a JSON string */ + val one = """{"id":"one","value":"FIRST!","numValue":0}""" + + /** Document ID `two` as a JSON string */ + val two = """{"id":"two","value":"another","numValue":10,"sub":{"foo":"green","bar":"blue"}}""" + + /** Document ID `three` as a JSON string */ + val three = """{"id":"three","value":"","numValue":4}""" + + /** Document ID `four` as a JSON string */ + val four = """{"id":"four","value":"purple","numValue":17,"sub":{"foo":"green","bar":"red"}}""" + + /** Document ID `five` as a JSON string */ + val five = """{"id":"five","value":"purple","numValue":18}""" } } diff --git a/src/kotlinx/src/test/kotlin/integration/JsonFunctions.kt b/src/kotlinx/src/test/kotlin/integration/JsonFunctions.kt index f877497..be8e339 100644 --- a/src/kotlinx/src/test/kotlin/integration/JsonFunctions.kt +++ b/src/kotlinx/src/test/kotlin/integration/JsonFunctions.kt @@ -2,7 +2,22 @@ package solutions.bitbadger.documents.kotlinx.tests.integration import solutions.bitbadger.documents.Configuration import solutions.bitbadger.documents.Dialect +import solutions.bitbadger.documents.Field +import solutions.bitbadger.documents.FieldMatch +import solutions.bitbadger.documents.kotlinx.extensions.* +import solutions.bitbadger.documents.kotlinx.tests.* +import kotlin.test.assertEquals +import kotlin.test.assertTrue +/** + * Tests for the JSON-returning functions + * + * NOTE: PostgreSQL JSONB columns do not preserve the original JSON with which a document was stored. These tests are + * the most complex within the library, as they have split testing based on the backing data store. The PostgreSQL tests + * check IDs (and, in the case of ordered queries, which ones occur before which others) vs. the entire JSON string. + * Meanwhile, SQLite stores JSON as text, and will return exactly the JSON it was given when it was originally written. + * These tests can ensure the expected round-trip of the entire JSON string. + */ object JsonFunctions { /** @@ -15,6 +30,368 @@ object JsonFunctions { fun maybeJsonB(json: String) = when (Configuration.dialect()) { Dialect.SQLITE -> json - Dialect.POSTGRESQL -> json.replace("\":\"", "\": \"").replace("\",\"", "\", \"").replace("\":[", "\": [") + Dialect.POSTGRESQL -> json.replace("\":", "\": ").replace(",\"", ", \"") } + + /** + * Create a snippet of JSON to find a document ID + * + * @param id The ID of the document + * @return A connection-aware ID to check for presence and positioning + */ + private fun docId(id: String) = + maybeJsonB("{\"id\":\"$id\"") + + fun allDefault(db: ThrowawayDatabase) { + JsonDocument.load(db) + val json = db.conn.jsonAll(TEST_TABLE) + assertTrue(json.startsWith("["), "JSON should start with '[' ($json)") + when (Configuration.dialect()) { + Dialect.SQLITE -> { + assertTrue(json.contains(JsonDocument.one), "Document 'one' not found in JSON ($json)") + assertTrue(json.contains(JsonDocument.two), "Document 'two' not found in JSON ($json)") + assertTrue(json.contains(JsonDocument.three), "Document 'three' not found in JSON ($json)") + assertTrue(json.contains(JsonDocument.four), "Document 'four' not found in JSON ($json)") + assertTrue(json.contains(JsonDocument.five), "Document 'five' not found in JSON ($json)") + } + Dialect.POSTGRESQL -> { + assertTrue(json.contains(docId("one")), "Document 'one' not found in JSON ($json)") + assertTrue(json.contains(docId("two")), "Document 'two' not found in JSON ($json)") + assertTrue(json.contains(docId("three")), "Document 'three' not found in JSON ($json)") + assertTrue(json.contains(docId("four")), "Document 'four' not found in JSON ($json)") + assertTrue(json.contains(docId("five")), "Document 'five' not found in JSON ($json)") + } + } + assertTrue(json.endsWith("]"), "JSON should end with ']' ($json)") + } + + fun allEmpty(db: ThrowawayDatabase) = + assertEquals("[]", db.conn.jsonAll(TEST_TABLE), "There should have been no documents returned") + + fun byIdString(db: ThrowawayDatabase) { + JsonDocument.load(db) + val json = db.conn.jsonById(TEST_TABLE, "two") + when (Configuration.dialect()) { + Dialect.SQLITE -> assertEquals(JsonDocument.two, json, "An incorrect document was returned") + Dialect.POSTGRESQL -> assertTrue(json.contains(docId("two")), "An incorrect document was returned ($json)") + } + } + + fun byIdNumber(db: ThrowawayDatabase) { + Configuration.idField = "key" + try { + db.conn.insert(TEST_TABLE, NumIdDocument(18, "howdy")) + assertEquals( + maybeJsonB("{\"key\":18,\"text\":\"howdy\"}"), db.conn.jsonById(TEST_TABLE, 18), + "The document should have been found by numeric ID" + ) + } finally { + Configuration.idField = "id" + } + } + + fun byIdNotFound(db: ThrowawayDatabase) { + JsonDocument.load(db) + assertEquals("{}", db.conn.jsonById(TEST_TABLE, "x"), "There should have been no document returned") + } + + fun byFieldsMatch(db: ThrowawayDatabase) { + JsonDocument.load(db) + val json = db.conn.jsonByFields( + TEST_TABLE, listOf(Field.any("value", listOf("blue", "purple")), Field.exists("sub")), FieldMatch.ALL + ) + when (Configuration.dialect()) { + Dialect.SQLITE -> assertEquals("[${JsonDocument.four}]", json, "The incorrect document was returned") + Dialect.POSTGRESQL -> { + assertTrue(json.startsWith("["), "JSON should start with '[' ($json)") + assertTrue(json.contains(docId("four")),"The incorrect document was returned ($json)") + assertTrue(json.endsWith("]"), "JSON should end with ']' ($json)") + } + } + } + + fun byFieldsMatchOrdered(db: ThrowawayDatabase) { + JsonDocument.load(db) + val json = db.conn.jsonByFields( + TEST_TABLE, listOf(Field.equal("value", "purple")), orderBy = listOf(Field.named("id")) + ) + when (Configuration.dialect()) { + Dialect.SQLITE -> assertEquals( + "[${JsonDocument.five},${JsonDocument.four}]", json, "The documents were not ordered correctly" + ) + + Dialect.POSTGRESQL -> { + val fiveIdx = json.indexOf(docId("five")) + val fourIdx = json.indexOf(docId("four")) + assertTrue(json.startsWith("["), "JSON should start with '[' ($json)") + assertTrue(fiveIdx >= 0, "Document 'five' not found ($json)") + assertTrue(fourIdx >= 0, "Document 'four' not found ($json)") + assertTrue(fiveIdx < fourIdx, "Document 'five' should have been before 'four' ($json)") + assertTrue(json.endsWith("]"), "JSON should end with ']' ($json)") + } + } + } + + fun byFieldsMatchNumIn(db: ThrowawayDatabase) { + JsonDocument.load(db) + val json = db.conn.jsonByFields(TEST_TABLE, listOf(Field.any("numValue", listOf(2, 4, 6, 8)))) + when (Configuration.dialect()) { + Dialect.SQLITE -> assertEquals("[${JsonDocument.three}]", json, "The incorrect document was returned") + Dialect.POSTGRESQL -> { + assertTrue(json.startsWith("["), "JSON should start with '[' ($json)") + assertTrue(json.contains(docId("three")), "The incorrect document was returned ($json)") + assertTrue(json.endsWith("]"), "JSON should end with ']' ($json)") + } + } + } + + fun byFieldsNoMatch(db: ThrowawayDatabase) { + JsonDocument.load(db) + assertEquals( + "[]", db.conn.jsonByFields(TEST_TABLE, listOf(Field.greater("numValue", 100))), + "There should have been no documents returned" + ) + } + + fun byFieldsMatchInArray(db: ThrowawayDatabase) { + ArrayDocument.testDocuments.forEach { db.conn.insert(TEST_TABLE, it) } + val json = db.conn.jsonByFields(TEST_TABLE, listOf(Field.inArray("values", TEST_TABLE, listOf("c")))) + assertTrue(json.startsWith("["), "JSON should start with '[' ($json)") + assertTrue(json.contains(docId("first")), "The 'first' document was not found ($json)") + assertTrue(json.contains(docId("second")), "The 'second' document was not found ($json)") + assertTrue(json.endsWith("]"), "JSON should end with ']' ($json)") + } + + fun byFieldsNoMatchInArray(db: ThrowawayDatabase) { + ArrayDocument.testDocuments.forEach { db.conn.insert(TEST_TABLE, it) } + assertEquals( + "[]", db.conn.jsonByFields( + TEST_TABLE, listOf(Field.inArray("values", TEST_TABLE, listOf("j"))) + ), "There should have been no documents returned" + ) + } + + fun byContainsMatch(db: ThrowawayDatabase) { + JsonDocument.load(db) + val json = db.conn.jsonByContains>(TEST_TABLE, mapOf("value" to "purple")) + assertTrue(json.startsWith("["), "JSON should start with '[' ($json)") + when (Configuration.dialect()) { + Dialect.SQLITE -> { + assertTrue(json.contains(JsonDocument.four), "Document 'four' not found ($json)") + assertTrue(json.contains(JsonDocument.five), "Document 'five' not found ($json)") + } + Dialect.POSTGRESQL -> { + assertTrue(json.contains(docId("four")), "Document 'four' not found ($json)") + assertTrue(json.contains(docId("five")), "Document 'five' not found ($json)") + } + } + assertTrue(json.endsWith("]"), "JSON should end with ']' ($json)") + } + + fun byContainsMatchOrdered(db: ThrowawayDatabase) { + JsonDocument.load(db) + val json = db.conn.jsonByContains>>( + TEST_TABLE, mapOf("sub" to mapOf("foo" to "green")), listOf(Field.named("value")) + ) + when (Configuration.dialect()) { + Dialect.SQLITE -> assertEquals( + "[${JsonDocument.two},${JsonDocument.four}]", json, "The documents were not ordered correctly" + ) + Dialect.POSTGRESQL -> { + val twoIdx = json.indexOf(docId("two")) + val fourIdx = json.indexOf(docId("four")) + assertTrue(json.startsWith("["), "JSON should start with '[' ($json)") + assertTrue(twoIdx >= 0, "Document 'two' not found ($json)") + assertTrue(fourIdx >= 0, "Document 'four' not found ($json)") + assertTrue(twoIdx < fourIdx, "Document 'two' should have been before 'four' ($json)") + assertTrue(json.endsWith("]"), "JSON should end with ']' ($json)") + } + } + } + + fun byContainsNoMatch(db: ThrowawayDatabase) { + JsonDocument.load(db) + assertEquals( + "[]", + db.conn.jsonByContains>(TEST_TABLE, mapOf("value" to "indigo")), + "There should have been no documents returned" + ) + } + + fun byJsonPathMatch(db: ThrowawayDatabase) { + JsonDocument.load(db) + val json = db.conn.jsonByJsonPath(TEST_TABLE, "$.numValue ? (@ > 10)") + assertTrue(json.startsWith("["), "JSON should start with '[' ($json)") + when (Configuration.dialect()) { + Dialect.SQLITE -> { + assertTrue(json.contains(JsonDocument.four), "Document 'four' not found ($json)") + assertTrue(json.contains(JsonDocument.five), "Document 'five' not found ($json)") + } + Dialect.POSTGRESQL -> { + assertTrue(json.contains(docId("four")), "Document 'four' not found ($json)") + assertTrue(json.contains(docId("five")), "Document 'five' not found ($json)") + } + } + assertTrue(json.endsWith("]"), "JSON should end with ']' ($json)") + } + + fun byJsonPathMatchOrdered(db: ThrowawayDatabase) { + JsonDocument.load(db) + val json = db.conn.jsonByJsonPath(TEST_TABLE, "$.numValue ? (@ > 10)", listOf(Field.named("id"))) + when (Configuration.dialect()) { + Dialect.SQLITE -> assertEquals( + "[${JsonDocument.five},${JsonDocument.four}]", json, "The documents were not ordered correctly" + ) + + Dialect.POSTGRESQL -> { + val fiveIdx = json.indexOf(docId("five")) + val fourIdx = json.indexOf(docId("four")) + assertTrue(json.startsWith("["), "JSON should start with '[' ($json)") + assertTrue(fiveIdx >= 0, "Document 'five' not found ($json)") + assertTrue(fourIdx >= 0, "Document 'four' not found ($json)") + assertTrue(fiveIdx < fourIdx, "Document 'five' should have been before 'four' ($json)") + assertTrue(json.endsWith("]"), "JSON should end with ']' ($json)") + } + } + } + + fun byJsonPathNoMatch(db: ThrowawayDatabase) { + JsonDocument.load(db) + assertEquals( + "[]", + db.conn.jsonByJsonPath(TEST_TABLE, "$.numValue ? (@ > 100)"), + "There should have been no documents returned" + ) + } + + fun firstByFieldsMatchOne(db: ThrowawayDatabase) { + JsonDocument.load(db) + val json = db.conn.jsonFirstByFields(TEST_TABLE, listOf(Field.equal("value", "another"))) + when (Configuration.dialect()) { + Dialect.SQLITE -> assertEquals(JsonDocument.two, json, "The incorrect document was returned") + Dialect.POSTGRESQL -> assertTrue(json.contains(docId("two")), "The incorrect document was returned ($json)") + } + } + + fun firstByFieldsMatchMany(db: ThrowawayDatabase) { + JsonDocument.load(db) + val json = db.conn.jsonFirstByFields(TEST_TABLE, listOf(Field.equal("sub.foo", "green"))) + when (Configuration.dialect()) { + Dialect.SQLITE -> assertTrue( + json.contains(JsonDocument.two) || json.contains(JsonDocument.four), + "Expected document 'two' or 'four' ($json)" + ) + Dialect.POSTGRESQL -> assertTrue( + json.contains(docId("two")) || json.contains(docId("four")), + "Expected document 'two' or 'four' ($json)" + ) + } + } + + fun firstByFieldsMatchOrdered(db: ThrowawayDatabase) { + JsonDocument.load(db) + val json = db.conn.jsonFirstByFields( + TEST_TABLE, listOf(Field.equal("sub.foo", "green")), orderBy = listOf(Field.named("n:numValue DESC")) + ) + when (Configuration.dialect()) { + Dialect.SQLITE -> assertEquals(JsonDocument.four, json, "An incorrect document was returned") + Dialect.POSTGRESQL -> assertTrue(json.contains(docId("four")), "An incorrect document was returned ($json)") + } + } + + fun firstByFieldsNoMatch(db: ThrowawayDatabase) { + JsonDocument.load(db) + assertEquals( + "{}", + db.conn.jsonFirstByFields(TEST_TABLE, listOf(Field.equal("value", "absent"))), + "There should have been no document returned" + ) + } + + fun firstByContainsMatchOne(db: ThrowawayDatabase) { + JsonDocument.load(db) + val json = db.conn.jsonFirstByContains>(TEST_TABLE, mapOf("value" to "FIRST!")) + when (Configuration.dialect()) { + Dialect.SQLITE -> assertEquals(JsonDocument.one, json, "An incorrect document was returned") + Dialect.POSTGRESQL -> assertTrue(json.contains(docId("one")), "An incorrect document was returned ($json)") + } + } + + fun firstByContainsMatchMany(db: ThrowawayDatabase) { + JsonDocument.load(db) + val json = db.conn.jsonFirstByContains>(TEST_TABLE, mapOf("value" to "purple")) + when (Configuration.dialect()) { + Dialect.SQLITE -> assertTrue( + json.contains(JsonDocument.four) || json.contains(JsonDocument.five), + "Expected document 'four' or 'five' ($json)" + ) + Dialect.POSTGRESQL -> assertTrue( + json.contains(docId("four")) || json.contains(docId("five")), + "Expected document 'four' or 'five' ($json)" + ) + } + } + + fun firstByContainsMatchOrdered(db: ThrowawayDatabase) { + JsonDocument.load(db) + val json = db.conn.jsonFirstByContains>( + TEST_TABLE, mapOf("value" to "purple"), listOf(Field.named("sub.bar NULLS FIRST")) + ) + when (Configuration.dialect()) { + Dialect.SQLITE -> assertEquals(JsonDocument.five, json, "An incorrect document was returned") + Dialect.POSTGRESQL -> assertTrue(json.contains(docId("five")), "An incorrect document was returned ($json)") + } + } + + fun firstByContainsNoMatch(db: ThrowawayDatabase) { + JsonDocument.load(db) + assertEquals( + "{}", + db.conn.jsonFirstByContains>(TEST_TABLE, mapOf("value" to "indigo")), + "There should have been no document returned" + ) + } + + fun firstByJsonPathMatchOne(db: ThrowawayDatabase) { + JsonDocument.load(db) + val json = db.conn.jsonFirstByJsonPath(TEST_TABLE, "$.numValue ? (@ == 10)") + when (Configuration.dialect()) { + Dialect.SQLITE -> assertEquals(JsonDocument.two, json, "An incorrect document was returned") + Dialect.POSTGRESQL -> assertTrue(json.contains(docId("two")), "An incorrect document was returned ($json)") + } + } + + fun firstByJsonPathMatchMany(db: ThrowawayDatabase) { + JsonDocument.load(db) + val json = db.conn.jsonFirstByJsonPath(TEST_TABLE, "$.numValue ? (@ > 10)") + when (Configuration.dialect()) { + Dialect.SQLITE -> assertTrue( + json.contains(JsonDocument.four) || json.contains(JsonDocument.five), + "Expected document 'four' or 'five' ($json)" + ) + Dialect.POSTGRESQL -> assertTrue( + json.contains(docId("four")) || json.contains(docId("five")), + "Expected document 'four' or 'five' ($json)" + ) + } + } + + fun firstByJsonPathMatchOrdered(db: ThrowawayDatabase) { + JsonDocument.load(db) + val json = db.conn.jsonFirstByJsonPath(TEST_TABLE, "$.numValue ? (@ > 10)", listOf(Field.named("id DESC"))) + when (Configuration.dialect()) { + Dialect.SQLITE -> assertEquals(JsonDocument.four, json, "An incorrect document was returned") + Dialect.POSTGRESQL -> assertTrue(json.contains(docId("four")), "An incorrect document was returned ($json)") + } + } + + fun firstByJsonPathNoMatch(db: ThrowawayDatabase) { + JsonDocument.load(db) + assertEquals( + "{}", + db.conn.jsonFirstByJsonPath(TEST_TABLE, "$.numValue ? (@ > 100)"), + "There should have been no document returned" + ) + } + } \ No newline at end of file diff --git a/src/kotlinx/src/test/kotlin/integration/PostgreSQLJsonIT.kt b/src/kotlinx/src/test/kotlin/integration/PostgreSQLJsonIT.kt new file mode 100644 index 0000000..c17b2f4 --- /dev/null +++ b/src/kotlinx/src/test/kotlin/integration/PostgreSQLJsonIT.kt @@ -0,0 +1,156 @@ +package solutions.bitbadger.documents.kotlinx.tests.integration + +import org.junit.jupiter.api.DisplayName +import kotlin.test.Test + +/** + * PostgreSQL integration tests for the `Json` object / `json*` connection extension functions + */ +@DisplayName("KotlinX | PostgreSQL: Json") +class PostgreSQLJsonIT { + + @Test + @DisplayName("all retrieves all documents") + fun allDefault() = + PgDB().use(JsonFunctions::allDefault) + + @Test + @DisplayName("all succeeds with an empty table") + fun allEmpty() = + PgDB().use(JsonFunctions::allEmpty) + + @Test + @DisplayName("byId retrieves a document via a string ID") + fun byIdString() = + PgDB().use(JsonFunctions::byIdString) + + @Test + @DisplayName("byId retrieves a document via a numeric ID") + fun byIdNumber() = + PgDB().use(JsonFunctions::byIdNumber) + + @Test + @DisplayName("byId returns null when a matching ID is not found") + fun byIdNotFound() = + PgDB().use(JsonFunctions::byIdNotFound) + + @Test + @DisplayName("byFields retrieves matching documents") + fun byFieldsMatch() = + PgDB().use(JsonFunctions::byFieldsMatch) + + @Test + @DisplayName("byFields retrieves ordered matching documents") + fun byFieldsMatchOrdered() = + PgDB().use(JsonFunctions::byFieldsMatchOrdered) + + @Test + @DisplayName("byFields retrieves matching documents with a numeric IN clause") + fun byFieldsMatchNumIn() = + PgDB().use(JsonFunctions::byFieldsMatchNumIn) + + @Test + @DisplayName("byFields succeeds when no documents match") + fun byFieldsNoMatch() = + PgDB().use(JsonFunctions::byFieldsNoMatch) + + @Test + @DisplayName("byFields retrieves matching documents with an IN_ARRAY comparison") + fun byFieldsMatchInArray() = + PgDB().use(JsonFunctions::byFieldsMatchInArray) + + @Test + @DisplayName("byFields succeeds when no documents match an IN_ARRAY comparison") + fun byFieldsNoMatchInArray() = + PgDB().use(JsonFunctions::byFieldsNoMatchInArray) + + @Test + @DisplayName("byContains retrieves matching documents") + fun byContainsMatch() = + PgDB().use(JsonFunctions::byContainsMatch) + + @Test + @DisplayName("byContains retrieves ordered matching documents") + fun byContainsMatchOrdered() = + PgDB().use(JsonFunctions::byContainsMatchOrdered) + + @Test + @DisplayName("byContains succeeds when no documents match") + fun byContainsNoMatch() = + PgDB().use(JsonFunctions::byContainsNoMatch) + + @Test + @DisplayName("byJsonPath retrieves matching documents") + fun byJsonPathMatch() = + PgDB().use(JsonFunctions::byJsonPathMatch) + + @Test + @DisplayName("byJsonPath retrieves ordered matching documents") + fun byJsonPathMatchOrdered() = + PgDB().use(JsonFunctions::byJsonPathMatchOrdered) + + @Test + @DisplayName("byJsonPath succeeds when no documents match") + fun byJsonPathNoMatch() = + PgDB().use(JsonFunctions::byJsonPathNoMatch) + + @Test + @DisplayName("firstByFields retrieves a matching document") + fun firstByFieldsMatchOne() = + PgDB().use(JsonFunctions::firstByFieldsMatchOne) + + @Test + @DisplayName("firstByFields retrieves a matching document among many") + fun firstByFieldsMatchMany() = + PgDB().use(JsonFunctions::firstByFieldsMatchMany) + + @Test + @DisplayName("firstByFields retrieves a matching document among many (ordered)") + fun firstByFieldsMatchOrdered() = + PgDB().use(JsonFunctions::firstByFieldsMatchOrdered) + + @Test + @DisplayName("firstByFields returns null when no document matches") + fun firstByFieldsNoMatch() = + PgDB().use(JsonFunctions::firstByFieldsNoMatch) + + @Test + @DisplayName("firstByContains retrieves a matching document") + fun firstByContainsMatchOne() = + PgDB().use(JsonFunctions::firstByContainsMatchOne) + + @Test + @DisplayName("firstByContains retrieves a matching document among many") + fun firstByContainsMatchMany() = + PgDB().use(JsonFunctions::firstByContainsMatchMany) + + @Test + @DisplayName("firstByContains retrieves a matching document among many (ordered)") + fun firstByContainsMatchOrdered() = + PgDB().use(JsonFunctions::firstByContainsMatchOrdered) + + @Test + @DisplayName("firstByContains returns null when no document matches") + fun firstByContainsNoMatch() = + PgDB().use(JsonFunctions::firstByContainsNoMatch) + + @Test + @DisplayName("firstByJsonPath retrieves a matching document") + fun firstByJsonPathMatchOne() = + PgDB().use(JsonFunctions::firstByJsonPathMatchOne) + + @Test + @DisplayName("firstByJsonPath retrieves a matching document among many") + fun firstByJsonPathMatchMany() = + PgDB().use(JsonFunctions::firstByJsonPathMatchMany) + + @Test + @DisplayName("firstByJsonPath retrieves a matching document among many (ordered)") + fun firstByJsonPathMatchOrdered() = + PgDB().use(JsonFunctions::firstByJsonPathMatchOrdered) + + @Test + @DisplayName("firstByJsonPath returns null when no document matches") + fun firstByJsonPathNoMatch() = + PgDB().use(JsonFunctions::firstByJsonPathNoMatch) +} \ No newline at end of file diff --git a/src/kotlinx/src/test/kotlin/integration/SQLiteJsonIT.kt b/src/kotlinx/src/test/kotlin/integration/SQLiteJsonIT.kt new file mode 100644 index 0000000..91bcb67 --- /dev/null +++ b/src/kotlinx/src/test/kotlin/integration/SQLiteJsonIT.kt @@ -0,0 +1,112 @@ +package solutions.bitbadger.documents.kotlinx.tests.integration + +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.assertThrows +import solutions.bitbadger.documents.DocumentException +import kotlin.test.Test + +/** + * SQLite integration tests for the `Json` object / `json*` connection extension functions + */ +@DisplayName("KotlinX | SQLite: Json") +class SQLiteJsonIT { + + @Test + @DisplayName("all retrieves all documents") + fun allDefault() = + SQLiteDB().use(JsonFunctions::allDefault) + + @Test + @DisplayName("all succeeds with an empty table") + fun allEmpty() = + SQLiteDB().use(JsonFunctions::allEmpty) + + @Test + @DisplayName("byId retrieves a document via a string ID") + fun byIdString() = + SQLiteDB().use(JsonFunctions::byIdString) + + @Test + @DisplayName("byId retrieves a document via a numeric ID") + fun byIdNumber() = + SQLiteDB().use(JsonFunctions::byIdNumber) + + @Test + @DisplayName("byId returns null when a matching ID is not found") + fun byIdNotFound() = + SQLiteDB().use(JsonFunctions::byIdNotFound) + + @Test + @DisplayName("byFields retrieves matching documents") + fun byFieldsMatch() = + SQLiteDB().use(JsonFunctions::byFieldsMatch) + + @Test + @DisplayName("byFields retrieves ordered matching documents") + fun byFieldsMatchOrdered() = + SQLiteDB().use(JsonFunctions::byFieldsMatchOrdered) + + @Test + @DisplayName("byFields retrieves matching documents with a numeric IN clause") + fun byFieldsMatchNumIn() = + SQLiteDB().use(JsonFunctions::byFieldsMatchNumIn) + + @Test + @DisplayName("byFields succeeds when no documents match") + fun byFieldsNoMatch() = + SQLiteDB().use(JsonFunctions::byFieldsNoMatch) + + @Test + @DisplayName("byFields retrieves matching documents with an IN_ARRAY comparison") + fun byFieldsMatchInArray() = + SQLiteDB().use(JsonFunctions::byFieldsMatchInArray) + + @Test + @DisplayName("byFields succeeds when no documents match an IN_ARRAY comparison") + fun byFieldsNoMatchInArray() = + SQLiteDB().use(JsonFunctions::byFieldsNoMatchInArray) + + @Test + @DisplayName("byContains fails") + fun byContainsFails() { + assertThrows { SQLiteDB().use(JsonFunctions::byContainsMatch) } + } + + @Test + @DisplayName("byJsonPath fails") + fun byJsonPathFails() { + assertThrows { SQLiteDB().use(JsonFunctions::byJsonPathMatch) } + } + + @Test + @DisplayName("firstByFields retrieves a matching document") + fun firstByFieldsMatchOne() = + SQLiteDB().use(JsonFunctions::firstByFieldsMatchOne) + + @Test + @DisplayName("firstByFields retrieves a matching document among many") + fun firstByFieldsMatchMany() = + SQLiteDB().use(JsonFunctions::firstByFieldsMatchMany) + + @Test + @DisplayName("firstByFields retrieves a matching document among many (ordered)") + fun firstByFieldsMatchOrdered() = + SQLiteDB().use(JsonFunctions::firstByFieldsMatchOrdered) + + @Test + @DisplayName("firstByFields returns null when no document matches") + fun firstByFieldsNoMatch() = + SQLiteDB().use(JsonFunctions::firstByFieldsNoMatch) + + @Test + @DisplayName("firstByContains fails") + fun firstByContainsFails() { + assertThrows { SQLiteDB().use(JsonFunctions::firstByContainsMatchOne) } + } + + @Test + @DisplayName("firstByJsonPath fails") + fun firstByJsonPathFails() { + assertThrows { SQLiteDB().use(JsonFunctions::firstByJsonPathMatchOne) } + } +} \ No newline at end of file