From 7bba852f982399dcbcfa73b6cc8381ee71727a7c Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Thu, 27 Mar 2025 23:10:18 -0400 Subject: [PATCH] WIP on core Json tests --- src/core/src/main/kotlin/java/Json.kt | 426 ++++++++++++++++++ src/core/src/main/kotlin/java/Results.kt | 2 +- .../main/kotlin/java/extensions/Connection.kt | 155 ++++++- src/core/src/test/kotlin/Types.kt | 15 + .../test/kotlin/integration/JsonFunctions.kt | 298 +++++++++++- .../kotlin/integration/PostgreSQLJsonIT.kt | 156 +++++++ .../test/kotlin/integration/SQLiteJsonIT.kt | 112 +++++ 7 files changed, 1144 insertions(+), 20 deletions(-) create mode 100644 src/core/src/main/kotlin/java/Json.kt create mode 100644 src/core/src/test/kotlin/integration/PostgreSQLJsonIT.kt create mode 100644 src/core/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 new file mode 100644 index 0000000..cebc83e --- /dev/null +++ b/src/core/src/main/kotlin/java/Json.kt @@ -0,0 +1,426 @@ +package solutions.bitbadger.documents.java + +import solutions.bitbadger.documents.* +import solutions.bitbadger.documents.query.FindQuery +import solutions.bitbadger.documents.query.orderBy +import java.sql.Connection +import kotlin.jvm.Throws + +/** + * 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 + */ + @Throws(DocumentException::class) + @JvmStatic + fun all(tableName: String, orderBy: Collection>? = null, conn: Connection) = + Custom.jsonArray(FindQuery.all(tableName) + (orderBy?.let(::orderBy) ?: ""), listOf(), conn, Results::jsonFromData) + + /** + * 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 + */ + @Throws(DocumentException::class) + @JvmStatic + @JvmOverloads + fun all(tableName: String, orderBy: Collection>? = null) = + Configuration.dbConn().use { all(tableName, orderBy, it) } + + /** + * 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 + */ + @Throws(DocumentException::class) + @JvmStatic + fun all(tableName: String, conn: Connection) = + all(tableName, null, 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 + */ + @Throws(DocumentException::class) + @JvmStatic + fun byId(tableName: String, docId: TKey, conn: Connection) = + Custom.jsonSingle( + FindQuery.byId(tableName, docId), + Parameters.addFields(listOf(Field.equal(Configuration.idField, docId, ":id"))), + conn, + Results::jsonFromData + ) + + /** + * 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 + */ + @Throws(DocumentException::class) + @JvmStatic + fun byId(tableName: String, docId: TKey) = + Configuration.dbConn().use { byId(tableName, docId, it) } + + /** + * 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 + */ + @Throws(DocumentException::class) + @JvmStatic + fun byFields( + tableName: String, + fields: Collection>, + howMatched: FieldMatch? = null, + orderBy: Collection>? = null, + conn: Connection + ): String { + val named = Parameters.nameFields(fields) + return Custom.jsonArray( + FindQuery.byFields(tableName, named, howMatched) + (orderBy?.let(::orderBy) ?: ""), + Parameters.addFields(named), + conn, + Results::jsonFromData + ) + } + + /** + * 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 + */ + @Throws(DocumentException::class) + @JvmStatic + @JvmOverloads + fun byFields( + tableName: String, + fields: Collection>, + howMatched: FieldMatch? = null, + orderBy: Collection>? = null + ) = Configuration.dbConn().use { byFields(tableName, fields, howMatched, orderBy, it) } + + /** + * 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 + */ + @Throws(DocumentException::class) + @JvmStatic + 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) + * + * @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 + */ + @Throws(DocumentException::class) + @JvmStatic + 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) + * + * @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 + */ + @Throws(DocumentException::class) + @JvmStatic + @JvmOverloads + 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 + */ + @Throws(DocumentException::class) + @JvmStatic + 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 + */ + @Throws(DocumentException::class) + @JvmStatic + fun byJsonPath(tableName: String, path: String, orderBy: Collection>? = null, conn: Connection) = + Custom.jsonArray( + FindQuery.byJsonPath(tableName) + (orderBy?.let(::orderBy) ?: ""), + listOf(Parameter(":path", ParameterType.STRING, path)), + conn, + Results::jsonFromData + ) + + /** + * 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 + */ + @Throws(DocumentException::class) + @JvmStatic + @JvmOverloads + fun byJsonPath(tableName: String, path: String, orderBy: Collection>? = null) = + Configuration.dbConn().use { byJsonPath(tableName, path, orderBy, it) } + + /** + * 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 + */ + @Throws(DocumentException::class) + @JvmStatic + fun byJsonPath(tableName: String, path: String, conn: Connection) = + byJsonPath(tableName, path, null, 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 + */ + @Throws(DocumentException::class) + @JvmStatic + fun firstByFields( + tableName: String, + fields: Collection>, + howMatched: FieldMatch? = null, + orderBy: Collection>? = null, + conn: Connection + ): String { + val named = Parameters.nameFields(fields) + return Custom.jsonSingle( + FindQuery.byFields(tableName, named, howMatched) + (orderBy?.let(::orderBy) ?: ""), + Parameters.addFields(named), + conn, + Results::jsonFromData + ) + } + + /** + * 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 + */ + @Throws(DocumentException::class) + @JvmStatic + @JvmOverloads + fun firstByFields( + tableName: String, + fields: Collection>, + howMatched: FieldMatch? = null, + orderBy: Collection>? = null + ) = Configuration.dbConn().use { firstByFields(tableName, fields, howMatched, orderBy, it) } + + /** + * 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 + */ + @Throws(DocumentException::class) + @JvmStatic + fun firstByFields( + tableName: String, + fields: Collection>, + howMatched: FieldMatch? = null, + conn: Connection + ) = firstByFields(tableName, fields, howMatched, null, 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 + */ + @Throws(DocumentException::class) + @JvmStatic + 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 + */ + @Throws(DocumentException::class) + @JvmStatic + 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) + * + * @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 + */ + @Throws(DocumentException::class) + @JvmStatic + @JvmOverloads + 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 + */ + @Throws(DocumentException::class) + @JvmStatic + fun firstByJsonPath(tableName: String, path: String, orderBy: Collection>? = null, conn: Connection) = + Custom.jsonSingle( + FindQuery.byJsonPath(tableName) + (orderBy?.let(::orderBy) ?: ""), + listOf(Parameter(":path", ParameterType.STRING, path)), + conn, + Results::jsonFromData + ) + + /** + * 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 + */ + @Throws(DocumentException::class) + @JvmStatic + fun firstByJsonPath(tableName: String, path: String, conn: Connection) = + firstByJsonPath(tableName, path, null, conn) + + /** + * 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) + * @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 + */ + @Throws(DocumentException::class) + @JvmStatic + @JvmOverloads + fun firstByJsonPath(tableName: String, path: String, orderBy: Collection>? = null) = + Configuration.dbConn().use { firstByJsonPath(tableName, path, orderBy, it) } +} diff --git a/src/core/src/main/kotlin/java/Results.kt b/src/core/src/main/kotlin/java/Results.kt index 66fc15c..c71557c 100644 --- a/src/core/src/main/kotlin/java/Results.kt +++ b/src/core/src/main/kotlin/java/Results.kt @@ -120,7 +120,7 @@ object Results { * @throws DocumentException If there is a problem executing the query (unchecked) */ @JvmStatic - fun toJsonArray(stmt: PreparedStatement, mapFunc: (ResultSet) -> String) = + fun toJsonArray(stmt: PreparedStatement, mapFunc: (ResultSet) -> String): String = try { val results = StringBuilder("[") stmt.executeQuery().use { diff --git a/src/core/src/main/kotlin/java/extensions/Connection.kt b/src/core/src/main/kotlin/java/extensions/Connection.kt index b23ef02..f5c9ac1 100644 --- a/src/core/src/main/kotlin/java/extensions/Connection.kt +++ b/src/core/src/main/kotlin/java/extensions/Connection.kt @@ -287,7 +287,7 @@ fun Connection.existsByContains(tableName: String, criteria: TContai 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 @@ -309,7 +309,7 @@ fun Connection.findAll(tableName: String, clazz: Class, orderBy: Co * @param tableName The table from which the document should be retrieved * @param docId The ID of the document to retrieve * @param clazz The class of the document to be returned - * @return The document if it is found, `null` otherwise + * @return An `Optional` item with the document if it is found * @throws DocumentException If no dialect has been configured */ @Throws(DocumentException::class) @@ -335,8 +335,7 @@ fun Connection.findByFields( clazz: Class, howMatched: FieldMatch? = null, orderBy: Collection>? = null -) = - Find.byFields(tableName, fields, clazz, howMatched, orderBy, this) +) = Find.byFields(tableName, fields, clazz, howMatched, orderBy, this) /** * Retrieve documents using a JSON containment query, ordering results by the optional given fields (PostgreSQL only) @@ -355,8 +354,7 @@ fun Connection.findByContains( criteria: TContains, clazz: Class, orderBy: Collection>? = null -) = - Find.byContains(tableName, criteria, clazz, orderBy, this) +) = Find.byContains(tableName, criteria, clazz, orderBy, this) /** * Retrieve documents using a JSON Path match query, ordering results by the optional given fields (PostgreSQL only) @@ -375,8 +373,7 @@ fun Connection.findByJsonPath( path: String, clazz: Class, orderBy: Collection>? = null -) = - Find.byJsonPath(tableName, path, clazz, orderBy, this) +) = Find.byJsonPath(tableName, path, clazz, orderBy, this) /** * Retrieve the first document using a field comparison and optional ordering fields @@ -386,7 +383,7 @@ fun Connection.findByJsonPath( * @param clazz The class of the document to be returned * @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 document matching the field comparison, or `null` if no matches are found + * @return An `Optional` item, with the first document matching the field comparison if found * @throws DocumentException If no dialect has been configured, or if parameters are invalid */ @Throws(DocumentException::class) @@ -397,8 +394,7 @@ fun Connection.findFirstByFields( clazz: Class, howMatched: FieldMatch? = null, orderBy: Collection>? = null -) = - Find.firstByFields(tableName, fields, clazz, howMatched, orderBy, this) +) = Find.firstByFields(tableName, fields, clazz, howMatched, orderBy, this) /** * Retrieve the first document using a JSON containment query and optional ordering fields (PostgreSQL only) @@ -407,7 +403,7 @@ fun Connection.findFirstByFields( * @param criteria The object for which JSON containment should be checked * @param clazz The class of the document to be returned * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) - * @return The first document matching the JSON containment query, or `null` if no matches are found + * @return An `Optional` item, with the first document matching the JSON containment query if found * @throws DocumentException If called on a SQLite connection */ @Throws(DocumentException::class) @@ -417,8 +413,7 @@ fun Connection.findFirstByContains( criteria: TContains, clazz: Class, orderBy: Collection>? = null -) = - Find.firstByContains(tableName, criteria, clazz, orderBy, this) +) = Find.firstByContains(tableName, criteria, clazz, orderBy, this) /** * Retrieve the first document using a JSON Path match query and optional ordering fields (PostgreSQL only) @@ -427,7 +422,7 @@ fun Connection.findFirstByContains( * @param path The JSON path comparison to match * @param clazz The class of the document to be returned * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) - * @return The first document matching the JSON Path match query, or `null` if no matches are found + * @return An `Optional` item, with the first document matching the JSON Path match query if found * @throws DocumentException If called on a SQLite connection */ @Throws(DocumentException::class) @@ -437,8 +432,134 @@ fun Connection.findFirstByJsonPath( path: String, clazz: Class, orderBy: Collection>? = null -) = - Find.firstByJsonPath(tableName, path, clazz, orderBy, this) +) = Find.firstByJsonPath(tableName, path, clazz, orderBy, this) + +// ~~~ DOCUMENT RETRIEVAL QUERIES (Raw 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) + * @return A JSON array of documents from the given table + * @throws DocumentException If query execution fails + */ +@Throws(DocumentException::class) +@JvmOverloads +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 dialect has been configured + */ +@Throws(DocumentException::class) +fun Connection.jsonById(tableName: String, docId: TKey) = + Json.byId(tableName, docId, this) + +/** + * Retrieve documents using a field comparison, ordering results by the optional given fields + * + * @param tableName The table from which the document 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 dialect has been configured, or if parameters are invalid + */ +@Throws(DocumentException::class) +@JvmOverloads +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 optional given fields (PostgreSQL only) + * + * @param tableName The name of the table in which document existence should be checked + * @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 called on a SQLite connection + */ +@Throws(DocumentException::class) +@JvmOverloads +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 optional 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 called on a SQLite connection + */ +@Throws(DocumentException::class) +@JvmOverloads +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 dialect has been configured, or if parameters are invalid + */ +@Throws(DocumentException::class) +@JvmOverloads +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 called on a SQLite connection + */ +@Throws(DocumentException::class) +@JvmOverloads +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) + * + * @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 called on a SQLite connection + */ +@Throws(DocumentException::class) +@JvmOverloads +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/core/src/test/kotlin/Types.kt b/src/core/src/test/kotlin/Types.kt index cc222fa..e074eaf 100644 --- a/src/core/src/test/kotlin/Types.kt +++ b/src/core/src/test/kotlin/Types.kt @@ -44,5 +44,20 @@ data class JsonDocument(val id: String, val value: String = "", val numValue: In fun load(db: ThrowawayDatabase, tableName: String = TEST_TABLE) = testDocuments.forEach { Document.insert(tableName, it, db.conn) } + + /** Document ID `one` as a JSON string */ + val one = """{"id":"one","value":"FIRST!","numValue":0,"sub":null}""" + + /** 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,"sub":null}""" + + /** 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,"sub":null}""" } } diff --git a/src/core/src/test/kotlin/integration/JsonFunctions.kt b/src/core/src/test/kotlin/integration/JsonFunctions.kt index 4fe6bea..8b27176 100644 --- a/src/core/src/test/kotlin/integration/JsonFunctions.kt +++ b/src/core/src/test/kotlin/integration/JsonFunctions.kt @@ -2,7 +2,25 @@ package solutions.bitbadger.documents.core.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.core.tests.ArrayDocument +import solutions.bitbadger.documents.core.tests.JsonDocument +import solutions.bitbadger.documents.core.tests.NumIdDocument +import solutions.bitbadger.documents.core.tests.TEST_TABLE +import solutions.bitbadger.documents.java.extensions.* +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,7 +33,283 @@ object JsonFunctions { fun maybeJsonB(json: String) = when (Configuration.dialect()) { Dialect.SQLITE -> json - Dialect.POSTGRESQL -> json.replace("\":\"", "\": \"").replace("\",\"", "\", \"").replace("\":[", "\": [") + Dialect.POSTGRESQL -> json.replace("\":", "\": ").replace(",\"", ", \"") } -} \ No newline at end of file + /** + * 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) + 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.indexOf(docId("one")) >= 0, "Document 'one' not found in JSON ($json)") + assertTrue(json.indexOf(docId("two")) >= 0, "Document 'two' not found in JSON ($json)") + assertTrue(json.indexOf(docId("three")) >= 0, "Document 'three' not found in JSON ($json)") + assertTrue(json.indexOf(docId("four")) >= 0, "Document 'four' not found in JSON ($json)") + assertTrue(json.indexOf(docId("five")) >= 0, "Document 'five' not found in JSON ($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.indexOf(docId("two")) >= 0, + "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.contains(docId("four")), + "The incorrect document was returned ($json)" + ) + } + } + + // TODO: stopped here with Postgres/SQLite split + + fun byFieldsMatchOrdered(db: ThrowawayDatabase) { + JsonDocument.load(db) + assertEquals( + maybeJsonB("[${JsonDocument.five},${JsonDocument.four}]"), db.conn.jsonByFields( + TEST_TABLE, listOf(Field.equal("value", "purple")), orderBy = listOf(Field.named("id")) + ), "The documents were not ordered correctly" + ) + } + + fun byFieldsMatchNumIn(db: ThrowawayDatabase) { + JsonDocument.load(db) + assertEquals( + maybeJsonB("[${JsonDocument.three}]"), db.conn.jsonByFields( + TEST_TABLE, listOf(Field.any("numValue", listOf(2, 4, 6, 8))) + ), "The incorrect document was returned" + ) + } + + 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.contains(maybeJsonB("{\"id\":\"first\"")), "The 'first' document was not found ($json)") + assertTrue(json.contains(maybeJsonB("{\"id\":\"second\"")), "The 'second' document was not found ($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.contains(maybeJsonB(JsonDocument.four)), "Document 'four' not found ($json)") + assertTrue(json.contains(maybeJsonB(JsonDocument.five)), "Document 'five' not found ($json)") + } + + fun byContainsMatchOrdered(db: ThrowawayDatabase) { + JsonDocument.load(db) + assertEquals( + maybeJsonB("[${JsonDocument.two},${JsonDocument.four}]"), db.conn.jsonByContains( + TEST_TABLE, mapOf("sub" to mapOf("foo" to "green")), listOf(Field.named("value")) + ), "The documents were not ordered correctly" + ) + } + + 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.contains(maybeJsonB(JsonDocument.four)), "Document 'four' not found ($json)") + assertTrue(json.contains(maybeJsonB(JsonDocument.five)), "Document 'five' not found ($json)") + } + + fun byJsonPathMatchOrdered(db: ThrowawayDatabase) { + JsonDocument.load(db) + val json = db.conn.jsonByJsonPath(TEST_TABLE, "$.numValue ? (@ > 10)", listOf(Field.named("id"))) + val fiveIdx = json.indexOf(docId("five")) + val fourIdx = json.indexOf(docId("four")) + 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)") + } + + 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"))) + assertTrue(json.contains(docId("two")), "The incorrect document was returned") + } + + fun firstByFieldsMatchMany(db: ThrowawayDatabase) { + JsonDocument.load(db) + val json = db.conn.jsonFirstByFields(TEST_TABLE, listOf(Field.equal("sub.foo", "green"))) + assertTrue( + json.contains(maybeJsonB(JsonDocument.two)) || json.contains(maybeJsonB(JsonDocument.four)), + "Expected document 'two' or 'four' ($json)" + ) + } + + fun firstByFieldsMatchOrdered(db: ThrowawayDatabase) { + JsonDocument.load(db) + assertEquals( + maybeJsonB(JsonDocument.four), db.conn.jsonFirstByFields( + TEST_TABLE, listOf(Field.equal("sub.foo", "green")), orderBy = listOf(Field.named("n:numValue DESC")) + ), "An incorrect document was returned" + ) + } + + 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) + assertEquals( + maybeJsonB(JsonDocument.one), + db.conn.jsonFirstByContains(TEST_TABLE, mapOf("value" to "FIRST!")), + "An incorrect document was returned" + ) + } + + fun firstByContainsMatchMany(db: ThrowawayDatabase) { + JsonDocument.load(db) + val json = db.conn.jsonFirstByContains(TEST_TABLE, mapOf("value" to "purple")) + assertTrue( + json.contains(maybeJsonB(JsonDocument.four)) || json.contains(maybeJsonB(JsonDocument.five)), + "Expected document 'four' or 'five' ($json)" + ) + } + + fun firstByContainsMatchOrdered(db: ThrowawayDatabase) { + JsonDocument.load(db) + assertEquals( + maybeJsonB(JsonDocument.five), db.conn.jsonFirstByContains( + TEST_TABLE, mapOf("value" to "purple"), listOf(Field.named("sub.bar NULLS FIRST")) + ), "An incorrect document was returned" + ) + } + + 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) + assertEquals( + maybeJsonB(JsonDocument.two), + db.conn.jsonFirstByJsonPath(TEST_TABLE, "$.numValue ? (@ == 10)"), + "An incorrect document was returned" + ) + } + + fun firstByJsonPathMatchMany(db: ThrowawayDatabase) { + JsonDocument.load(db) + val json = db.conn.jsonFirstByJsonPath(TEST_TABLE, "$.numValue ? (@ > 10)") + assertTrue( + json.contains(maybeJsonB(JsonDocument.four)) || json.contains(maybeJsonB(JsonDocument.five)), + "Expected document 'four' or 'five' ($json)" + ) + } + + fun firstByJsonPathMatchOrdered(db: ThrowawayDatabase) { + JsonDocument.load(db) + assertEquals( + maybeJsonB(JsonDocument.four), + db.conn.jsonFirstByJsonPath(TEST_TABLE, "$.numValue ? (@ > 10)", listOf(Field.named("id DESC"))), + "An incorrect document was returned" + ) + } + + fun firstByJsonPathNoMatch(db: ThrowawayDatabase) { + JsonDocument.load(db) + assertEquals( + "{}", + db.conn.jsonFirstByJsonPath(TEST_TABLE, "$.numValue ? (@ > 100)"), + "There should have been no document returned" + ) + } +} diff --git a/src/core/src/test/kotlin/integration/PostgreSQLJsonIT.kt b/src/core/src/test/kotlin/integration/PostgreSQLJsonIT.kt new file mode 100644 index 0000000..4e98051 --- /dev/null +++ b/src/core/src/test/kotlin/integration/PostgreSQLJsonIT.kt @@ -0,0 +1,156 @@ +package solutions.bitbadger.documents.core.tests.integration + +import org.junit.jupiter.api.DisplayName +import kotlin.test.Test + +/** + * PostgreSQL integration tests for the `Json` object / `json*` connection extension functions + */ +@DisplayName("Core | Kotlin | 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) +} diff --git a/src/core/src/test/kotlin/integration/SQLiteJsonIT.kt b/src/core/src/test/kotlin/integration/SQLiteJsonIT.kt new file mode 100644 index 0000000..6f20601 --- /dev/null +++ b/src/core/src/test/kotlin/integration/SQLiteJsonIT.kt @@ -0,0 +1,112 @@ +package solutions.bitbadger.documents.core.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("Core | Kotlin | 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) } + } +}