From f2dd740933b9b495696e3ba043b4f96a3a2a520e Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Wed, 2 Apr 2025 23:23:34 -0400 Subject: [PATCH] WIP on KotlinX Json tests --- src/core/src/main/kotlin/java/Results.kt | 2 +- src/kotlinx/src/main/kotlin/Json.kt | 383 +++++++++++++ .../src/main/kotlin/extensions/Connection.kt | 164 +++++- .../test/kotlin/integration/JsonFunctions.kt | 515 ++++++++++++++---- .../kotlin/integration/PostgreSQLJsonIT.kt | 147 ++++- .../test/kotlin/integration/SQLiteJsonIT.kt | 101 +++- 6 files changed, 1194 insertions(+), 118 deletions(-) diff --git a/src/core/src/main/kotlin/java/Results.kt b/src/core/src/main/kotlin/java/Results.kt index 0e98fc3..3f49229 100644 --- a/src/core/src/main/kotlin/java/Results.kt +++ b/src/core/src/main/kotlin/java/Results.kt @@ -100,7 +100,7 @@ object Results { */ @JvmStatic fun jsonFromDocument(field: String, rs: ResultSet) = - rs.getString(field) + rs.getString(field) ?: "{}" /** * Retrieve the JSON text of a document, specifying the field in which the document is found diff --git a/src/kotlinx/src/main/kotlin/Json.kt b/src/kotlinx/src/main/kotlin/Json.kt index a8d14bd..75a6de7 100644 --- a/src/kotlinx/src/main/kotlin/Json.kt +++ b/src/kotlinx/src/main/kotlin/Json.kt @@ -3,6 +3,7 @@ package solutions.bitbadger.documents.kotlinx import solutions.bitbadger.documents.* import solutions.bitbadger.documents.query.FindQuery import solutions.bitbadger.documents.query.orderBy +import java.io.PrintWriter import solutions.bitbadger.documents.java.Json as CoreJson import java.sql.Connection @@ -45,6 +46,41 @@ object Json { fun all(tableName: String, conn: Connection) = CoreJson.all(tableName, conn) + /** + * Write all documents in the given table to the given `PrintWriter`, ordering results by the optional given fields + * + * @param tableName The table from which documents should be retrieved + * @param writer The `PrintWriter` to which the results should be written + * @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 + * @throws DocumentException If query execution fails + */ + fun writeAll(tableName: String, writer: PrintWriter, orderBy: Collection>? = null, conn: Connection) = + CoreJson.writeAll(tableName, writer, orderBy, conn) + + /** + * Write all documents in the given table to the given `PrintWriter`, ordering results by the optional given fields + * (creates connection) + * + * @param tableName The table from which documents should be retrieved + * @param writer The `PrintWriter` to which the results should be written + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @throws DocumentException If query execution fails + */ + fun writeAll(tableName: String, writer: PrintWriter, orderBy: Collection>? = null) = + CoreJson.writeAll(tableName, writer, orderBy) + + /** + * Write all documents in the given table to the given `PrintWriter` + * + * @param tableName The table from which documents should be retrieved + * @param writer The `PrintWriter` to which the results should be written + * @param conn The connection over which documents should be retrieved + * @throws DocumentException If query execution fails + */ + fun writeAll(tableName: String, writer: PrintWriter, conn: Connection) = + CoreJson.writeAll(tableName, writer, conn) + /** * Retrieve a document by its ID * @@ -68,6 +104,29 @@ object Json { fun byId(tableName: String, docId: TKey) = CoreJson.byId(tableName, docId) + /** + * Write a document to the given `PrintWriter` by its ID + * + * @param tableName The table from which the document should be retrieved + * @param writer The `PrintWriter` to which the results should be written + * @param docId The ID of the document to retrieve + * @param conn The connection over which documents should be retrieved + * @throws DocumentException If no dialect has been configured + */ + fun writeById(tableName: String, writer: PrintWriter, docId: TKey, conn: Connection) = + CoreJson.writeById(tableName, writer, docId, conn) + + /** + * Write a document to the given `PrintWriter` by its ID (creates connection) + * + * @param tableName The table from which the document should be retrieved + * @param writer The `PrintWriter` to which the results should be written + * @param docId The ID of the document to retrieve + * @throws DocumentException If no dialect has been configured + */ + fun writeById(tableName: String, writer: PrintWriter, docId: TKey) = + CoreJson.writeById(tableName, writer, docId) + /** * Retrieve documents using a field comparison, ordering results by the given fields * @@ -117,6 +176,63 @@ object Json { fun byFields(tableName: String, fields: Collection>, howMatched: FieldMatch? = null, conn: Connection) = CoreJson.byFields(tableName, fields, howMatched, conn) + /** + * Write documents to the given `PrintWriter` using a field comparison, ordering results by the given fields + * + * @param tableName The table from which documents should be retrieved + * @param writer The `PrintWriter` to which the results should be written + * @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 + * @throws DocumentException If no dialect has been configured, or if parameters are invalid + */ + fun writeByFields( + tableName: String, + writer: PrintWriter, + fields: Collection>, + howMatched: FieldMatch? = null, + orderBy: Collection>? = null, + conn: Connection + ) = CoreJson.writeByFields(tableName, writer, fields, howMatched, orderBy, conn) + + /** + * Write documents to the given `PrintWriter` using a field comparison, ordering results by the given fields + * (creates connection) + * + * @param tableName The table from which documents should be retrieved + * @param writer The `PrintWriter` to which the results should be written + * @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) + * @throws DocumentException If no dialect has been configured, or if parameters are invalid + */ + fun writeByFields( + tableName: String, + writer: PrintWriter, + fields: Collection>, + howMatched: FieldMatch? = null, + orderBy: Collection>? = null + ) = CoreJson.writeByFields(tableName, writer, fields, howMatched, orderBy) + + /** + * Write documents to the given `PrintWriter` using a field comparison + * + * @param tableName The table from which documents should be retrieved + * @param writer The `PrintWriter` to which the results should be written + * @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 + * @throws DocumentException If no dialect has been configured, or if parameters are invalid + */ + fun writeByFields( + tableName: String, + writer: PrintWriter, + fields: Collection>, + howMatched: FieldMatch? = null, + conn: Connection + ) = CoreJson.writeByFields(tableName, writer, fields, howMatched, conn) + /** * Retrieve documents using a JSON containment query, ordering results by the given fields (PostgreSQL only) * @@ -167,6 +283,65 @@ object Json { inline fun byContains(tableName: String, criteria: TContains, conn: Connection) = byContains(tableName, criteria, null, conn) + /** + * Write documents to the given `PrintWriter` using a JSON containment query, ordering results by the given fields + * (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param writer The `PrintWriter` to which the results should be written + * @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 + * @throws DocumentException If called on a SQLite connection + */ + inline fun writeByContains( + tableName: String, + writer: PrintWriter, + criteria: TContains, + orderBy: Collection>? = null, + conn: Connection + ) = Custom.writeJsonArray( + FindQuery.byContains(tableName) + (orderBy?.let(::orderBy) ?: ""), + listOf(Parameters.json(":criteria", criteria)), + writer, + conn, + Results::jsonFromData + ) + + /** + * Write documents to the given `PrintWriter` 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 writer The `PrintWriter` to which the results should be written + * @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) + * @throws DocumentException If called on a SQLite connection + */ + inline fun writeByContains( + tableName: String, + writer: PrintWriter, + criteria: TContains, + orderBy: Collection>? = null + ) = Configuration.dbConn().use { writeByContains(tableName, writer, criteria, orderBy, it) } + + /** + * Write documents to the given `PrintWriter` using a JSON containment query, ordering results by the given fields + * (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param writer The `PrintWriter` to which the results should be written + * @param criteria The object for which JSON containment should be checked + * @param conn The connection over which documents should be retrieved + * @throws DocumentException If called on a SQLite connection + */ + inline fun writeByContains( + tableName: String, + writer: PrintWriter, + criteria: TContains, + conn: Connection + ) = writeByContains(tableName, writer, criteria, null, conn) + /** * Retrieve documents using a JSON Path match query, ordering results by the given fields (PostgreSQL only) * @@ -205,6 +380,50 @@ object Json { fun byJsonPath(tableName: String, path: String, conn: Connection) = CoreJson.byJsonPath(tableName, path, conn) + /** + * Write documents to the given `PrintWriter` 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 writer The `PrintWriter` to which the results should be written + * @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 + * @throws DocumentException If called on a SQLite connection + */ + fun writeByJsonPath( + tableName: String, + writer: PrintWriter, + path: String, + orderBy: Collection>? = null, + conn: Connection + ) = CoreJson.writeByJsonPath(tableName, writer, path, orderBy, conn) + + /** + * Write documents to the given `PrintWriter` 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 writer The `PrintWriter` to which the results should be written + * @param path The JSON path comparison to match + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @throws DocumentException If called on a SQLite connection + */ + fun writeByJsonPath(tableName: String, writer: PrintWriter, path: String, orderBy: Collection>? = null) = + CoreJson.writeByJsonPath(tableName, writer, path, orderBy) + + /** + * Write documents to the given `PrintWriter` using a JSON Path match query (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param writer The `PrintWriter` to which the results should be written + * @param path The JSON path comparison to match + * @param conn The connection over which documents should be retrieved + * @throws DocumentException If called on a SQLite connection + */ + fun writeByJsonPath(tableName: String, writer: PrintWriter, path: String, conn: Connection) = + CoreJson.writeByJsonPath(tableName, writer, path, conn) + /** * Retrieve the first document using a field comparison and optional ordering fields * @@ -258,6 +477,63 @@ object Json { conn: Connection ) = CoreJson.firstByFields(tableName, fields, howMatched, conn) + /** + * Write the first document to the given `PrintWriter` using a field comparison and optional ordering fields + * + * @param tableName The table from which documents should be retrieved + * @param writer The `PrintWriter` to which the results should be written + * @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 + * @throws DocumentException If no dialect has been configured, or if parameters are invalid + */ + fun writeFirstByFields( + tableName: String, + writer: PrintWriter, + fields: Collection>, + howMatched: FieldMatch? = null, + orderBy: Collection>? = null, + conn: Connection + ) = CoreJson.writeFirstByFields(tableName, writer, fields, howMatched, orderBy, conn) + + /** + * Write the first document to the given `PrintWriter` using a field comparison and optional ordering fields + * (creates connection) + * + * @param tableName The table from which documents should be retrieved + * @param writer The `PrintWriter` to which the results should be written + * @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) + * @throws DocumentException If no dialect has been configured, or if parameters are invalid + */ + fun writeFirstByFields( + tableName: String, + writer: PrintWriter, + fields: Collection>, + howMatched: FieldMatch? = null, + orderBy: Collection>? = null + ) = CoreJson.writeFirstByFields(tableName, writer, fields, howMatched, orderBy) + + /** + * Write the first document to the given `PrintWriter` using a field comparison + * + * @param tableName The table from which documents should be retrieved + * @param writer The `PrintWriter` to which the results should be written + * @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 + * @throws DocumentException If no dialect has been configured, or if parameters are invalid + */ + fun writeFirstByFields( + tableName: String, + writer: PrintWriter, + fields: Collection>, + howMatched: FieldMatch? = null, + conn: Connection + ) = CoreJson.writeFirstByFields(tableName, writer, fields, howMatched, conn) + /** * Retrieve the first document using a JSON containment query and optional ordering fields (PostgreSQL only) * @@ -308,6 +584,65 @@ object Json { orderBy: Collection>? = null ) = Configuration.dbConn().use { firstByContains(tableName, criteria, orderBy, it) } + /** + * Write the first document to the given `PrintWriter` using a JSON containment query and optional ordering fields + * (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param writer The `PrintWriter` to which the results should be written + * @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 + * @throws DocumentException If called on a SQLite connection + */ + inline fun writeFirstByContains( + tableName: String, + writer: PrintWriter, + criteria: TContains, + orderBy: Collection>? = null, + conn: Connection + ) = writer.write( + Custom.jsonSingle( + FindQuery.byContains(tableName) + (orderBy?.let(::orderBy) ?: ""), + listOf(Parameters.json(":criteria", criteria)), + conn, + Results::jsonFromData + ) + ) + + /** + * Write the first document to the given `PrintWriter` using a JSON containment query and optional ordering fields + * (PostgreSQL only; creates connection) + * + * @param tableName The table from which documents should be retrieved + * @param writer The `PrintWriter` to which the results should be written + * @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) + * @throws DocumentException If called on a SQLite connection + */ + inline fun writeFirstByContains( + tableName: String, + writer: PrintWriter, + criteria: TContains, + orderBy: Collection>? = null + ) = Configuration.dbConn().use { writeFirstByContains(tableName, writer, criteria, orderBy, it) } + + /** + * Write the first document to the given `PrintWriter` using a JSON containment query (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param writer The `PrintWriter` to which the results should be written + * @param criteria The object for which JSON containment should be checked + * @param conn The connection over which documents should be retrieved + * @throws DocumentException If called on a SQLite connection + */ + inline fun writeFirstByContains( + tableName: String, + writer: PrintWriter, + criteria: TContains, + conn: Connection + ) = writeFirstByContains(tableName, writer, criteria, null, conn) + /** * Retrieve the first document using a JSON Path match query and optional ordering fields (PostgreSQL only) * @@ -345,4 +680,52 @@ object Json { */ fun firstByJsonPath(tableName: String, path: String, orderBy: Collection>? = null) = CoreJson.firstByJsonPath(tableName, path, orderBy) + + /** + * Write the first document to the given `PrintWriter` using a JSON Path match query and optional ordering fields + * (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param writer The `PrintWriter` to which the results should be written + * @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 + * @throws DocumentException If called on a SQLite connection + */ + fun writeFirstByJsonPath( + tableName: String, + writer: PrintWriter, + path: String, + orderBy: Collection>? = null, + conn: Connection + ) = CoreJson.writeFirstByJsonPath(tableName, writer, path, orderBy, conn) + + /** + * Write the first document to the given `PrintWriter` 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 writer The `PrintWriter` to which the results should be written + * @param path The JSON path comparison to match + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @throws DocumentException If called on a SQLite connection + */ + fun writeFirstByJsonPath( + tableName: String, + writer: PrintWriter, + path: String, + orderBy: Collection>? = null + ) = CoreJson.writeFirstByJsonPath(tableName, writer, path, orderBy) + + /** + * Write the first document to the given `PrintWriter` using a JSON Path match query (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param writer The `PrintWriter` to which the results should be written + * @param path The JSON path comparison to match + * @param conn The connection over which documents should be retrieved + * @throws DocumentException If called on a SQLite connection + */ + fun writeFirstByJsonPath(tableName: String, writer: PrintWriter, path: String, conn: Connection) = + CoreJson.writeFirstByJsonPath(tableName, writer, path, conn) } diff --git a/src/kotlinx/src/main/kotlin/extensions/Connection.kt b/src/kotlinx/src/main/kotlin/extensions/Connection.kt index 090a0c1..e437e21 100644 --- a/src/kotlinx/src/main/kotlin/extensions/Connection.kt +++ b/src/kotlinx/src/main/kotlin/extensions/Connection.kt @@ -2,6 +2,7 @@ package solutions.bitbadger.documents.kotlinx.extensions import solutions.bitbadger.documents.* import solutions.bitbadger.documents.kotlinx.* +import java.io.PrintWriter import java.sql.Connection import java.sql.ResultSet @@ -32,7 +33,23 @@ fun Connection.customJsonArray( query: String, parameters: Collection> = listOf(), mapFunc: (ResultSet) -> String -) = Custom.jsonArray(query, parameters, mapFunc) +) = Custom.jsonArray(query, parameters, this, mapFunc) + +/** + * Execute a query, writing its JSON array of results to the given `PrintWriter` (creates connection) + * + * @param query The query to retrieve the results + * @param parameters Parameters to use for the query + * @param writer The writer to which the results should be written + * @param mapFunc The mapping function to extract the JSON from the query + * @throws DocumentException If parameters are invalid + */ +fun Connection.writeCustomJsonArray( + query: String, + parameters: Collection> = listOf(), + writer: PrintWriter, + mapFunc: (ResultSet) -> String +) = Custom.writeJsonArray(query, parameters, writer, this, mapFunc) /** * Execute a query that returns one or no results @@ -59,7 +76,7 @@ fun Connection.customJsonSingle( query: String, parameters: Collection> = listOf(), mapFunc: (ResultSet) -> String -) = Custom.jsonSingle(query, parameters, mapFunc) +) = Custom.jsonSingle(query, parameters, this, mapFunc) /** * Execute a query that returns no results @@ -448,8 +465,7 @@ inline fun Connection.jsonFirstByContains( ) = Json.firstByContains(tableName, criteria, orderBy, this) /** - * Retrieve the first document using a JSON Path match query and optional ordering fields (PostgreSQL only; creates - * connection) + * 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 @@ -460,6 +476,134 @@ inline fun Connection.jsonFirstByContains( fun Connection.jsonFirstByJsonPath(tableName: String, path: String, orderBy: Collection>? = null) = Json.firstByJsonPath(tableName, path, orderBy, this) +// ~~~ DOCUMENT RETRIEVAL QUERIES (Write raw JSON to PrintWriter) ~~~ + +/** + * Write all documents in the given table to the given `PrintWriter`, ordering results by the optional given fields + * + * @param tableName The table from which documents should be retrieved + * @param writer The `PrintWriter` to which the results should be written + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @throws DocumentException If no connection string has been set, or if query execution fails + */ +fun Connection.writeJsonAll(tableName: String, writer: PrintWriter, orderBy: Collection>? = null) = + Json.writeAll(tableName, writer, orderBy, this) + +/** + * Write a document to the given `PrintWriter` by its ID + * + * @param tableName The table from which the document should be retrieved + * @param writer The `PrintWriter` to which the results should be written + * @param docId The ID of the document to retrieve + * @throws DocumentException If no connection string has been set + */ +fun Connection.writeJsonById(tableName: String, writer: PrintWriter, docId: TKey) = + Json.writeById(tableName, writer, docId, this) + +/** + * Write documents to the given `PrintWriter` using a field comparison, ordering results by the given fields + * + * @param tableName The table from which documents should be retrieved + * @param writer The `PrintWriter` to which the results should be written + * @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) + * @throws DocumentException If no connection string has been set, or if parameters are invalid + */ +fun Connection.writeJsonByFields( + tableName: String, + writer: PrintWriter, + fields: Collection>, + howMatched: FieldMatch? = null, + orderBy: Collection>? = null +) = Json.writeByFields(tableName, writer, fields, howMatched, orderBy, this) + +/** + * Write documents to the given `PrintWriter` using a JSON containment query, ordering results by the given fields + * (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param writer The `PrintWriter` to which the results should be written + * @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) + * @throws DocumentException If no connection string has been set, or if called on a SQLite connection + */ +inline fun Connection.writeJsonByContains( + tableName: String, + writer: PrintWriter, + criteria: TContains, + orderBy: Collection>? = null +) = Json.writeByContains(tableName, writer, criteria, orderBy, this) + +/** + * Write documents to the given `PrintWriter` 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 writer The `PrintWriter` to which the results should be written + * @param path The JSON path comparison to match + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @throws DocumentException If no connection string has been set, or if called on a SQLite connection + */ +fun Connection.writeJsonByJsonPath( + tableName: String, + writer: PrintWriter, + path: String, + orderBy: Collection>? = null +) = Json.writeByJsonPath(tableName, writer, path, orderBy, this) + +/** + * Write the first document to the given `PrintWriter` using a field comparison and optional ordering fields + * + * @param tableName The table from which documents should be retrieved + * @param writer The `PrintWriter` to which the results should be written + * @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) + * @throws DocumentException If no connection string has been set, or if parameters are invalid + */ +fun Connection.writeJsonFirstByFields( + tableName: String, + writer: PrintWriter, + fields: Collection>, + howMatched: FieldMatch? = null, + orderBy: Collection>? = null +) = Json.writeFirstByFields(tableName, writer, fields, howMatched, orderBy, this) + +/** + * Write the first document to the given `PrintWriter` using a JSON containment query and optional ordering fields + * (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param writer The `PrintWriter` to which the results should be written + * @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) + * @throws DocumentException If no connection string has been set, or if called on a SQLite connection + */ +inline fun Connection.writeJsonFirstByContains( + tableName: String, + writer: PrintWriter, + criteria: TContains, + orderBy: Collection>? = null +) = Json.writeFirstByContains(tableName, writer, criteria, orderBy, this) + +/** + * Write the first document to the given `PrintWriter` using a JSON Path match query and optional ordering fields + * (PostgreSQL only) + * + * @param tableName The table from which documents should be retrieved + * @param writer The `PrintWriter` to which the results should be written + * @param path The JSON path comparison to match + * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) + * @throws DocumentException If no connection string has been set, or if called on a SQLite connection + */ +fun Connection.writeJsonFirstByJsonPath( + tableName: String, + writer: PrintWriter, + path: String, + orderBy: Collection>? = null +) = Json.writeFirstByJsonPath(tableName, writer, path, orderBy, this) + // ~~~ DOCUMENT PATCH (PARTIAL UPDATE) QUERIES ~~~ /** @@ -485,8 +629,7 @@ inline fun Connection.patchByFields( fields: Collection>, patch: TPatch, howMatched: FieldMatch? = null -) = - Patch.byFields(tableName, fields, patch, howMatched, this) +) = Patch.byFields(tableName, fields, patch, howMatched, this) /** * Patch documents using a JSON containment query (PostgreSQL only) @@ -500,8 +643,7 @@ inline fun Connection.patchByContains( tableName: String, criteria: TContains, patch: TPatch -) = - Patch.byContains(tableName, criteria, patch, this) +) = Patch.byContains(tableName, criteria, patch, this) /** * Patch documents using a JSON Path match query (PostgreSQL only) @@ -539,8 +681,7 @@ fun Connection.removeFieldsByFields( fields: Collection>, toRemove: Collection, howMatched: FieldMatch? = null -) = - RemoveFields.byFields(tableName, fields, toRemove, howMatched, this) +) = RemoveFields.byFields(tableName, fields, toRemove, howMatched, this) /** * Remove fields from documents using a JSON containment query (PostgreSQL only) @@ -554,8 +695,7 @@ inline fun Connection.removeFieldsByContains( tableName: String, criteria: TContains, toRemove: Collection -) = - RemoveFields.byContains(tableName, criteria, toRemove, this) +) = RemoveFields.byContains(tableName, criteria, toRemove, this) /** * Remove fields from documents using a JSON Path match query (PostgreSQL only) diff --git a/src/kotlinx/src/test/kotlin/integration/JsonFunctions.kt b/src/kotlinx/src/test/kotlin/integration/JsonFunctions.kt index 34d6779..9aaecf4 100644 --- a/src/kotlinx/src/test/kotlin/integration/JsonFunctions.kt +++ b/src/kotlinx/src/test/kotlin/integration/JsonFunctions.kt @@ -70,49 +70,93 @@ object JsonFunctions { checkAllDefault(db.conn.jsonAll(TEST_TABLE)) } -// fun writeAllDefault(db: ThrowawayDatabase) { -// JsonDocument.load(db) -// val output = StringWriter() -// val writer = PrintWriter(output) -// db.conn.writeJsonAll(TEST_TABLE, writer) -// checkAllDefault(output.toString()) -// } + fun writeAllDefault(db: ThrowawayDatabase) { + JsonDocument.load(db) + val output = StringWriter() + val writer = PrintWriter(output) + db.conn.writeJsonAll(TEST_TABLE, writer) + checkAllDefault(output.toString()) + } + + private fun checkAllEmpty(json: String) = + assertEquals("[]", json, "There should have been no documents returned") fun allEmpty(db: ThrowawayDatabase) = - assertEquals("[]", db.conn.jsonAll(TEST_TABLE), "There should have been no documents returned") + checkAllEmpty(db.conn.jsonAll(TEST_TABLE)) - fun byIdString(db: ThrowawayDatabase) { - JsonDocument.load(db) - val json = db.conn.jsonById(TEST_TABLE, "two") + fun writeAllEmpty(db: ThrowawayDatabase) { + val output = StringWriter() + val writer = PrintWriter(output) + db.conn.writeJsonAll(TEST_TABLE, writer) + checkAllEmpty(output.toString()) + } + + private fun checkByIdString(json: String) = 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 byIdString(db: ThrowawayDatabase) { + JsonDocument.load(db) + checkByIdString(db.conn.jsonById(TEST_TABLE, "two")) } + fun writeByIdString(db: ThrowawayDatabase) { + JsonDocument.load(db) + val output = StringWriter() + val writer = PrintWriter(output) + db.conn.writeJsonById(TEST_TABLE, writer, "two") + checkByIdString(output.toString()) + } + + private fun checkByIdNumber(json: String) = + assertEquals( + maybeJsonB("""{"key":18,"text":"howdy"}"""), + json, + "The document should have been found by numeric ID" + ) + 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" - ) + checkByIdString(db.conn.jsonById(TEST_TABLE, 18)) } 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 writeByIdNumber(db: ThrowawayDatabase) { + Configuration.idField = "key" + try { + db.conn.insert(TEST_TABLE, NumIdDocument(18, "howdy")) + val output = StringWriter() + val writer = PrintWriter(output) + db.conn.writeJsonById(TEST_TABLE, writer, 18) + checkByIdNumber(output.toString()) + } finally { + Configuration.idField = "id" + } } - fun byFieldsMatch(db: ThrowawayDatabase) { + private fun checkByIdNotFound(json: String) = + assertEquals("{}", json, "There should have been no document returned") + + fun byIdNotFound(db: ThrowawayDatabase) { JsonDocument.load(db) - val json = db.conn.jsonByFields( - TEST_TABLE, listOf(Field.any("value", listOf("blue", "purple")), Field.exists("sub")), FieldMatch.ALL - ) + checkByIdNotFound(db.conn.jsonById(TEST_TABLE, "x")) + } + + fun writeByIdNotFound(db: ThrowawayDatabase) { + JsonDocument.load(db) + val output = StringWriter() + val writer = PrintWriter(output) + db.conn.writeJsonById(TEST_TABLE, writer, "x") + checkByIdNotFound(output.toString()) + } + + private fun checkByFieldsMatch(json: String) = when (Configuration.dialect()) { Dialect.SQLITE -> assertEquals("[${JsonDocument.four}]", json, "The incorrect document was returned") Dialect.POSTGRESQL -> { @@ -121,13 +165,30 @@ object JsonFunctions { assertTrue(json.endsWith("]"), "JSON should end with ']' ($json)") } } + + fun byFieldsMatch(db: ThrowawayDatabase) { + JsonDocument.load(db) + checkByFieldsMatch( + db.conn.jsonByFields( + TEST_TABLE, listOf(Field.any("value", listOf("blue", "purple")), Field.exists("sub")), FieldMatch.ALL + ) + ) } - fun byFieldsMatchOrdered(db: ThrowawayDatabase) { + fun writeByFieldsMatch(db: ThrowawayDatabase) { JsonDocument.load(db) - val json = db.conn.jsonByFields( - TEST_TABLE, listOf(Field.equal("value", "purple")), orderBy = listOf(Field.named("id")) + val output = StringWriter() + val writer = PrintWriter(output) + db.conn.writeJsonByFields( + TEST_TABLE, + writer, + listOf(Field.any("value", listOf("blue", "purple")), Field.exists("sub")), + FieldMatch.ALL ) + checkByFieldsMatch(output.toString()) + } + + private fun checkByFieldsMatchOrdered(json: String) = when (Configuration.dialect()) { Dialect.SQLITE -> assertEquals( "[${JsonDocument.five},${JsonDocument.four}]", json, "The documents were not ordered correctly" @@ -143,11 +204,27 @@ object JsonFunctions { assertTrue(json.endsWith("]"), "JSON should end with ']' ($json)") } } + + fun byFieldsMatchOrdered(db: ThrowawayDatabase) { + JsonDocument.load(db) + checkByFieldsMatchOrdered( + db.conn.jsonByFields( + TEST_TABLE, listOf(Field.equal("value", "purple")), orderBy = listOf(Field.named("id")) + ) + ) } - fun byFieldsMatchNumIn(db: ThrowawayDatabase) { + fun writeByFieldsMatchOrdered(db: ThrowawayDatabase) { JsonDocument.load(db) - val json = db.conn.jsonByFields(TEST_TABLE, listOf(Field.any("numValue", listOf(2, 4, 6, 8)))) + val output = StringWriter() + val writer = PrintWriter(output) + db.conn.writeJsonByFields( + TEST_TABLE, writer, listOf(Field.equal("value", "purple")), orderBy = listOf(Field.named("id")) + ) + checkByFieldsMatchOrdered(output.toString()) + } + + private fun checkByFieldsMatchNumIn(json: String) = when (Configuration.dialect()) { Dialect.SQLITE -> assertEquals("[${JsonDocument.three}]", json, "The incorrect document was returned") Dialect.POSTGRESQL -> { @@ -156,37 +233,77 @@ object JsonFunctions { assertTrue(json.endsWith("]"), "JSON should end with ']' ($json)") } } + + fun byFieldsMatchNumIn(db: ThrowawayDatabase) { + JsonDocument.load(db) + checkByFieldsMatchNumIn(db.conn.jsonByFields(TEST_TABLE, listOf(Field.any("numValue", listOf(2, 4, 6, 8))))) } + fun writeByFieldsMatchNumIn(db: ThrowawayDatabase) { + JsonDocument.load(db) + val output = StringWriter() + val writer = PrintWriter(output) + db.conn.writeJsonByFields(TEST_TABLE, writer, listOf(Field.any("numValue", listOf(2, 4, 6, 8)))) + checkByFieldsMatchNumIn(output.toString()) + } + + private fun checkByFieldsNoMatch(json: String) = + assertEquals("[]", json, "There should have been no documents 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" - ) + checkByFieldsNoMatch(db.conn.jsonByFields(TEST_TABLE, listOf(Field.greater("numValue", 100)))) } - 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")))) + fun writeByFieldsNoMatch(db: ThrowawayDatabase) { + JsonDocument.load(db) + val output = StringWriter() + val writer = PrintWriter(output) + db.conn.writeJsonByFields(TEST_TABLE, writer, listOf(Field.greater("numValue", 100))) + checkByFieldsNoMatch(output.toString()) + } + + private fun checkByFieldsMatchInArray(json: String) { 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) { + fun byFieldsMatchInArray(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" + checkByFieldsMatchInArray( + db.conn.jsonByFields(TEST_TABLE, listOf(Field.inArray("values", TEST_TABLE, listOf("c")))) ) } - fun byContainsMatch(db: ThrowawayDatabase) { - JsonDocument.load(db) - val json = db.conn.jsonByContains>(TEST_TABLE, mapOf("value" to "purple")) + fun writeByFieldsMatchInArray(db: ThrowawayDatabase) { + ArrayDocument.testDocuments.forEach { db.conn.insert(TEST_TABLE, it) } + val output = StringWriter() + val writer = PrintWriter(output) + db.conn.writeJsonByFields(TEST_TABLE, writer, listOf(Field.inArray("values", TEST_TABLE, listOf("c")))) + checkByFieldsMatchInArray(output.toString()) + } + + private fun checkByFieldsNoMatchInArray(json: String) = + assertEquals("[]", json, "There should have been no documents returned") + + fun byFieldsNoMatchInArray(db: ThrowawayDatabase) { + ArrayDocument.testDocuments.forEach { db.conn.insert(TEST_TABLE, it) } + checkByFieldsNoMatchInArray( + db.conn.jsonByFields(TEST_TABLE, listOf(Field.inArray("values", TEST_TABLE, listOf("j")))) + ) + } + + fun writeByFieldsNoMatchInArray(db: ThrowawayDatabase) { + ArrayDocument.testDocuments.forEach { db.conn.insert(TEST_TABLE, it) } + val output = StringWriter() + val writer = PrintWriter(output) + db.conn.writeJsonByFields(TEST_TABLE, writer, listOf(Field.inArray("values", TEST_TABLE, listOf("j")))) + checkByFieldsNoMatchInArray(output.toString()) + } + + private fun checkByContainsMatch(json: String) { assertTrue(json.startsWith("["), "JSON should start with '[' ($json)") when (Configuration.dialect()) { Dialect.SQLITE -> { @@ -201,11 +318,20 @@ object JsonFunctions { assertTrue(json.endsWith("]"), "JSON should end with ']' ($json)") } - fun byContainsMatchOrdered(db: ThrowawayDatabase) { + fun byContainsMatch(db: ThrowawayDatabase) { JsonDocument.load(db) - val json = db.conn.jsonByContains>>( - TEST_TABLE, mapOf("sub" to mapOf("foo" to "green")), listOf(Field.named("value")) - ) + checkByContainsMatch(db.conn.jsonByContains>(TEST_TABLE, mapOf("value" to "purple"))) + } + + fun writeByContainsMatch(db: ThrowawayDatabase) { + JsonDocument.load(db) + val output = StringWriter() + val writer = PrintWriter(output) + db.conn.writeJsonByContains>(TEST_TABLE, writer, mapOf("value" to "purple")) + checkByContainsMatch(output.toString()) + } + + private fun checkByContainsMatchOrdered(json: String) = when (Configuration.dialect()) { Dialect.SQLITE -> assertEquals( "[${JsonDocument.two},${JsonDocument.four}]", json, "The documents were not ordered correctly" @@ -220,20 +346,43 @@ object JsonFunctions { assertTrue(json.endsWith("]"), "JSON should end with ']' ($json)") } } - } - fun byContainsNoMatch(db: ThrowawayDatabase) { + fun byContainsMatchOrdered(db: ThrowawayDatabase) { JsonDocument.load(db) - assertEquals( - "[]", - db.conn.jsonByContains>(TEST_TABLE, mapOf("value" to "indigo")), - "There should have been no documents returned" + checkByContainsMatchOrdered( + db.conn.jsonByContains>>( + TEST_TABLE, mapOf("sub" to mapOf("foo" to "green")), listOf(Field.named("value")) + ) ) } - fun byJsonPathMatch(db: ThrowawayDatabase) { + fun writeByContainsMatchOrdered(db: ThrowawayDatabase) { JsonDocument.load(db) - val json = db.conn.jsonByJsonPath(TEST_TABLE, "$.numValue ? (@ > 10)") + val output = StringWriter() + val writer = PrintWriter(output) + db.conn.writeJsonByContains>>( + TEST_TABLE, writer, mapOf("sub" to mapOf("foo" to "green")), listOf(Field.named("value")) + ) + checkByContainsMatchOrdered(output.toString()) + } + + private fun checkByContainsNoMatch(json: String) = + assertEquals("[]", json, "There should have been no documents returned") + + fun byContainsNoMatch(db: ThrowawayDatabase) { + JsonDocument.load(db) + checkByContainsNoMatch(db.conn.jsonByContains>(TEST_TABLE, mapOf("value" to "indigo"))) + } + + fun writeByContainsNoMatch(db: ThrowawayDatabase) { + JsonDocument.load(db) + val output = StringWriter() + val writer = PrintWriter(output) + db.conn.writeJsonByContains>(TEST_TABLE, writer, mapOf("value" to "indigo")) + checkByContainsNoMatch(output.toString()) + } + + private fun checkByJsonPathMatch(json: String) { assertTrue(json.startsWith("["), "JSON should start with '[' ($json)") when (Configuration.dialect()) { Dialect.SQLITE -> { @@ -248,9 +397,20 @@ object JsonFunctions { assertTrue(json.endsWith("]"), "JSON should end with ']' ($json)") } - fun byJsonPathMatchOrdered(db: ThrowawayDatabase) { + fun byJsonPathMatch(db: ThrowawayDatabase) { JsonDocument.load(db) - val json = db.conn.jsonByJsonPath(TEST_TABLE, "$.numValue ? (@ > 10)", listOf(Field.named("id"))) + checkByJsonPathMatch(db.conn.jsonByJsonPath(TEST_TABLE, "$.numValue ? (@ > 10)")) + } + + fun writeByJsonPathMatch(db: ThrowawayDatabase) { + JsonDocument.load(db) + val output = StringWriter() + val writer = PrintWriter(output) + db.conn.writeJsonByJsonPath(TEST_TABLE, writer, "$.numValue ? (@ > 10)") + checkByJsonPathMatch(output.toString()) + } + + private fun checkByJsonPathMatchOrdered(json: String) = when (Configuration.dialect()) { Dialect.SQLITE -> assertEquals( "[${JsonDocument.five},${JsonDocument.four}]", json, "The documents were not ordered correctly" @@ -266,29 +426,58 @@ object JsonFunctions { assertTrue(json.endsWith("]"), "JSON should end with ']' ($json)") } } - } - fun byJsonPathNoMatch(db: ThrowawayDatabase) { + fun byJsonPathMatchOrdered(db: ThrowawayDatabase) { JsonDocument.load(db) - assertEquals( - "[]", - db.conn.jsonByJsonPath(TEST_TABLE, "$.numValue ? (@ > 100)"), - "There should have been no documents returned" + checkByJsonPathMatchOrdered( + db.conn.jsonByJsonPath(TEST_TABLE, "$.numValue ? (@ > 10)", listOf(Field.named("id"))) ) } - fun firstByFieldsMatchOne(db: ThrowawayDatabase) { + fun writeByJsonPathMatchOrdered(db: ThrowawayDatabase) { JsonDocument.load(db) - val json = db.conn.jsonFirstByFields(TEST_TABLE, listOf(Field.equal("value", "another"))) + val output = StringWriter() + val writer = PrintWriter(output) + db.conn.writeJsonByJsonPath(TEST_TABLE, writer, "$.numValue ? (@ > 10)", listOf(Field.named("id"))) + checkByJsonPathMatchOrdered(output.toString()) + } + + private fun checkByJsonPathNoMatch(json: String) = + assertEquals("[]", json, "There should have been no documents returned") + + fun byJsonPathNoMatch(db: ThrowawayDatabase) { + JsonDocument.load(db) + checkByJsonPathNoMatch(db.conn.jsonByJsonPath(TEST_TABLE, "$.numValue ? (@ > 100)")) + } + + fun writeByJsonPathNoMatch(db: ThrowawayDatabase) { + JsonDocument.load(db) + val output = StringWriter() + val writer = PrintWriter(output) + db.conn.writeJsonByJsonPath(TEST_TABLE, writer, "$.numValue ? (@ > 100)") + checkByJsonPathNoMatch(output.toString()) + } + + private fun checkFirstByFieldsMatchOne(json: String) = 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 firstByFieldsMatchOne(db: ThrowawayDatabase) { + JsonDocument.load(db) + checkFirstByFieldsMatchOne(db.conn.jsonFirstByFields(TEST_TABLE, listOf(Field.equal("value", "another")))) } - fun firstByFieldsMatchMany(db: ThrowawayDatabase) { + fun writeFirstByFieldsMatchOne(db: ThrowawayDatabase) { JsonDocument.load(db) - val json = db.conn.jsonFirstByFields(TEST_TABLE, listOf(Field.equal("sub.foo", "green"))) + val output = StringWriter() + val writer = PrintWriter(output) + db.conn.writeJsonFirstByFields(TEST_TABLE, writer, listOf(Field.equal("value", "another"))) + checkFirstByFieldsMatchOne(output.toString()) + } + + private fun checkFirstByFieldsMatchMany(json: String) = when (Configuration.dialect()) { Dialect.SQLITE -> assertTrue( json.contains(JsonDocument.two) || json.contains(JsonDocument.four), @@ -299,40 +488,85 @@ object JsonFunctions { "Expected document 'two' or 'four' ($json)" ) } + + fun firstByFieldsMatchMany(db: ThrowawayDatabase) { + JsonDocument.load(db) + checkFirstByFieldsMatchMany(db.conn.jsonFirstByFields(TEST_TABLE, listOf(Field.equal("sub.foo", "green")))) } - fun firstByFieldsMatchOrdered(db: ThrowawayDatabase) { + fun writeFirstByFieldsMatchMany(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")) - ) + val output = StringWriter() + val writer = PrintWriter(output) + db.conn.writeJsonFirstByFields(TEST_TABLE, writer, listOf(Field.equal("sub.foo", "green"))) + checkFirstByFieldsMatchMany(output.toString()) + } + + private fun checkFirstByFieldsMatchOrdered(json: String) = 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) { + fun firstByFieldsMatchOrdered(db: ThrowawayDatabase) { JsonDocument.load(db) - assertEquals( - "{}", - db.conn.jsonFirstByFields(TEST_TABLE, listOf(Field.equal("value", "absent"))), - "There should have been no document returned" + checkFirstByFieldsMatchOrdered( + db.conn.jsonFirstByFields( + TEST_TABLE, listOf(Field.equal("sub.foo", "green")), orderBy = listOf(Field.named("n:numValue DESC")) + ) ) } - fun firstByContainsMatchOne(db: ThrowawayDatabase) { + fun writeFirstByFieldsMatchOrdered(db: ThrowawayDatabase) { JsonDocument.load(db) - val json = db.conn.jsonFirstByContains>(TEST_TABLE, mapOf("value" to "FIRST!")) + val output = StringWriter() + val writer = PrintWriter(output) + db.conn.writeJsonFirstByFields( + TEST_TABLE, + writer, + listOf(Field.equal("sub.foo", "green")), + orderBy = listOf(Field.named("n:numValue DESC")) + ) + checkFirstByFieldsMatchOrdered(output.toString()) + } + + private fun checkFirstByFieldsNoMatch(json: String) = + assertEquals("{}", json, "There should have been no document returned") + + fun firstByFieldsNoMatch(db: ThrowawayDatabase) { + JsonDocument.load(db) + checkFirstByFieldsNoMatch(db.conn.jsonFirstByFields(TEST_TABLE, listOf(Field.equal("value", "absent")))) + } + + fun writeFirstByFieldsNoMatch(db: ThrowawayDatabase) { + JsonDocument.load(db) + val output = StringWriter() + val writer = PrintWriter(output) + db.conn.writeJsonFirstByFields(TEST_TABLE, writer, listOf(Field.equal("value", "absent"))) + checkFirstByFieldsNoMatch(output.toString()) + } + + private fun checkFirstByContainsMatchOne(json: String) = 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 firstByContainsMatchOne(db: ThrowawayDatabase) { + JsonDocument.load(db) + checkFirstByContainsMatchOne( + db.conn.jsonFirstByContains>(TEST_TABLE, mapOf("value" to "FIRST!")) + ) } - fun firstByContainsMatchMany(db: ThrowawayDatabase) { + fun writeFirstByContainsMatchOne(db: ThrowawayDatabase) { JsonDocument.load(db) - val json = db.conn.jsonFirstByContains>(TEST_TABLE, mapOf("value" to "purple")) + val output = StringWriter() + val writer = PrintWriter(output) + db.conn.writeJsonFirstByContains>(TEST_TABLE, writer, mapOf("value" to "FIRST!")) + } + + private fun checkFirstByContainsMatchMany(json: String) = when (Configuration.dialect()) { Dialect.SQLITE -> assertTrue( json.contains(JsonDocument.four) || json.contains(JsonDocument.five), @@ -343,40 +577,85 @@ object JsonFunctions { "Expected document 'four' or 'five' ($json)" ) } + + fun firstByContainsMatchMany(db: ThrowawayDatabase) { + JsonDocument.load(db) + checkFirstByContainsMatchMany( + db.conn.jsonFirstByContains>(TEST_TABLE, mapOf("value" to "purple")) + ) } - fun firstByContainsMatchOrdered(db: ThrowawayDatabase) { + fun writeFirstByContainsMatchMany(db: ThrowawayDatabase) { JsonDocument.load(db) - val json = db.conn.jsonFirstByContains>( - TEST_TABLE, mapOf("value" to "purple"), listOf(Field.named("sub.bar NULLS FIRST")) - ) + val output = StringWriter() + val writer = PrintWriter(output) + db.conn.writeJsonFirstByContains>(TEST_TABLE, writer, mapOf("value" to "purple")) + checkFirstByContainsMatchMany(output.toString()) + } + + private fun checkFirstByContainsMatchOrdered(json: String) = 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) { + fun firstByContainsMatchOrdered(db: ThrowawayDatabase) { JsonDocument.load(db) - assertEquals( - "{}", - db.conn.jsonFirstByContains>(TEST_TABLE, mapOf("value" to "indigo")), - "There should have been no document returned" + checkFirstByContainsMatchOrdered( + db.conn.jsonFirstByContains>( + TEST_TABLE, mapOf("value" to "purple"), listOf(Field.named("sub.bar NULLS FIRST")) + ) ) } - fun firstByJsonPathMatchOne(db: ThrowawayDatabase) { + fun writeFirstByContainsMatchOrdered(db: ThrowawayDatabase) { JsonDocument.load(db) - val json = db.conn.jsonFirstByJsonPath(TEST_TABLE, "$.numValue ? (@ == 10)") + val output = StringWriter() + val writer = PrintWriter(output) + db.conn.writeJsonFirstByContains>( + TEST_TABLE, writer, mapOf("value" to "purple"), listOf(Field.named("sub.bar NULLS FIRST")) + ) + checkFirstByContainsMatchOrdered(output.toString()) + } + + private fun checkFirstByContainsNoMatch(json: String) = + assertEquals("{}", json, "There should have been no document returned") + + fun firstByContainsNoMatch(db: ThrowawayDatabase) { + JsonDocument.load(db) + checkFirstByContainsNoMatch( + db.conn.jsonFirstByContains>(TEST_TABLE, mapOf("value" to "indigo")) + ) + } + + fun writeFirstByContainsNoMatch(db: ThrowawayDatabase) { + JsonDocument.load(db) + val output = StringWriter() + val writer = PrintWriter(output) + db.conn.writeJsonFirstByContains>(TEST_TABLE, writer, mapOf("value" to "indigo")) + checkFirstByContainsNoMatch(output.toString()) + } + + private fun checkFirstByJsonPathMatchOne(json: String) = 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 firstByJsonPathMatchOne(db: ThrowawayDatabase) { + JsonDocument.load(db) + checkFirstByJsonPathMatchOne(db.conn.jsonFirstByJsonPath(TEST_TABLE, "$.numValue ? (@ == 10)")) } - fun firstByJsonPathMatchMany(db: ThrowawayDatabase) { + fun writeFirstByJsonPathMatchOne(db: ThrowawayDatabase) { JsonDocument.load(db) - val json = db.conn.jsonFirstByJsonPath(TEST_TABLE, "$.numValue ? (@ > 10)") + val output = StringWriter() + val writer = PrintWriter(output) + db.conn.writeJsonFirstByJsonPath(TEST_TABLE, writer, "$.numValue ? (@ == 10)") + checkFirstByJsonPathMatchOne(output.toString()) + } + + private fun checkFirstByJsonPathMatchMany(json: String) = when (Configuration.dialect()) { Dialect.SQLITE -> assertTrue( json.contains(JsonDocument.four) || json.contains(JsonDocument.five), @@ -387,24 +666,54 @@ object JsonFunctions { "Expected document 'four' or 'five' ($json)" ) } + + fun firstByJsonPathMatchMany(db: ThrowawayDatabase) { + JsonDocument.load(db) + checkFirstByJsonPathMatchMany(db.conn.jsonFirstByJsonPath(TEST_TABLE, "$.numValue ? (@ > 10)")) } - fun firstByJsonPathMatchOrdered(db: ThrowawayDatabase) { + fun writeFirstByJsonPathMatchMany(db: ThrowawayDatabase) { JsonDocument.load(db) - val json = db.conn.jsonFirstByJsonPath(TEST_TABLE, "$.numValue ? (@ > 10)", listOf(Field.named("id DESC"))) + val output = StringWriter() + val writer = PrintWriter(output) + db.conn.writeJsonFirstByJsonPath(TEST_TABLE, writer, "$.numValue ? (@ > 10)") + checkFirstByJsonPathMatchMany(output.toString()) + } + + private fun checkFirstByJsonPathMatchOrdered(json: String) = 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) { + fun firstByJsonPathMatchOrdered(db: ThrowawayDatabase) { JsonDocument.load(db) - assertEquals( - "{}", - db.conn.jsonFirstByJsonPath(TEST_TABLE, "$.numValue ? (@ > 100)"), - "There should have been no document returned" + checkFirstByJsonPathMatchOrdered( + db.conn.jsonFirstByJsonPath(TEST_TABLE, "$.numValue ? (@ > 10)", listOf(Field.named("id DESC"))) ) } -} \ No newline at end of file + fun writeFirstByJsonPathMatchOrdered(db: ThrowawayDatabase) { + JsonDocument.load(db) + val output = StringWriter() + val writer = PrintWriter(output) + db.conn.writeJsonFirstByJsonPath(TEST_TABLE, writer, "$.numValue ? (@ > 10)", listOf(Field.named("id DESC"))) + checkFirstByJsonPathMatchOrdered(output.toString()) + } + + private fun checkFirstByJsonPathNoMatch(json: String) = + assertEquals("{}", json, "There should have been no document returned") + + fun firstByJsonPathNoMatch(db: ThrowawayDatabase) { + JsonDocument.load(db) + checkFirstByJsonPathNoMatch(db.conn.jsonFirstByJsonPath(TEST_TABLE, "$.numValue ? (@ > 100)")) + } + + fun writeFirstByJsonPathNoMatch(db: ThrowawayDatabase) { + JsonDocument.load(db) + val output = StringWriter() + val writer = PrintWriter(output) + db.conn.writeJsonFirstByJsonPath(TEST_TABLE, writer, "$.numValue ? (@ > 100)") + checkFirstByJsonPathNoMatch(output.toString()) + } +} diff --git a/src/kotlinx/src/test/kotlin/integration/PostgreSQLJsonIT.kt b/src/kotlinx/src/test/kotlin/integration/PostgreSQLJsonIT.kt index c17b2f4..b41be7c 100644 --- a/src/kotlinx/src/test/kotlin/integration/PostgreSQLJsonIT.kt +++ b/src/kotlinx/src/test/kotlin/integration/PostgreSQLJsonIT.kt @@ -153,4 +153,149 @@ class PostgreSQLJsonIT { @DisplayName("firstByJsonPath returns null when no document matches") fun firstByJsonPathNoMatch() = PgDB().use(JsonFunctions::firstByJsonPathNoMatch) -} \ No newline at end of file + + @Test + @DisplayName("writeAll retrieves all documents") + fun writeAllDefault() = + PgDB().use(JsonFunctions::writeAllDefault) + + @Test + @DisplayName("writeAll succeeds with an empty table") + fun writeAllEmpty() = + PgDB().use(JsonFunctions::writeAllEmpty) + + @Test + @DisplayName("writeById retrieves a document via a string ID") + fun writeByIdString() = + PgDB().use(JsonFunctions::writeByIdString) + + @Test + @DisplayName("writeById retrieves a document via a numeric ID") + fun writeByIdNumber() = + PgDB().use(JsonFunctions::writeByIdNumber) + + @Test + @DisplayName("writeById returns null when a matching ID is not found") + fun writeByIdNotFound() = + PgDB().use(JsonFunctions::writeByIdNotFound) + + @Test + @DisplayName("writeByFields retrieves matching documents") + fun writeByFieldsMatch() = + PgDB().use(JsonFunctions::writeByFieldsMatch) + + @Test + @DisplayName("writeByFields retrieves ordered matching documents") + fun writeByFieldsMatchOrdered() = + PgDB().use(JsonFunctions::writeByFieldsMatchOrdered) + + @Test + @DisplayName("writeByFields retrieves matching documents with a numeric IN clause") + fun writeByFieldsMatchNumIn() = + PgDB().use(JsonFunctions::writeByFieldsMatchNumIn) + + @Test + @DisplayName("writeByFields succeeds when no documents match") + fun writeByFieldsNoMatch() = + PgDB().use(JsonFunctions::writeByFieldsNoMatch) + + @Test + @DisplayName("writeByFields retrieves matching documents with an IN_ARRAY comparison") + fun writeByFieldsMatchInArray() = + PgDB().use(JsonFunctions::writeByFieldsMatchInArray) + + @Test + @DisplayName("writeByFields succeeds when no documents match an IN_ARRAY comparison") + fun writeByFieldsNoMatchInArray() = + PgDB().use(JsonFunctions::writeByFieldsNoMatchInArray) + + @Test + @DisplayName("writeByContains retrieves matching documents") + fun writeByContainsMatch() = + PgDB().use(JsonFunctions::writeByContainsMatch) + + @Test + @DisplayName("writeByContains retrieves ordered matching documents") + fun writeByContainsMatchOrdered() = + PgDB().use(JsonFunctions::writeByContainsMatchOrdered) + + @Test + @DisplayName("writeByContains succeeds when no documents match") + fun writeByContainsNoMatch() = + PgDB().use(JsonFunctions::writeByContainsNoMatch) + + @Test + @DisplayName("writeByJsonPath retrieves matching documents") + fun writeByJsonPathMatch() = + PgDB().use(JsonFunctions::writeByJsonPathMatch) + + @Test + @DisplayName("writeByJsonPath retrieves ordered matching documents") + fun writeByJsonPathMatchOrdered() = + PgDB().use(JsonFunctions::writeByJsonPathMatchOrdered) + + @Test + @DisplayName("writeByJsonPath succeeds when no documents match") + fun writeByJsonPathNoMatch() = + PgDB().use(JsonFunctions::writeByJsonPathNoMatch) + + @Test + @DisplayName("writeFirstByFields retrieves a matching document") + fun writeFirstByFieldsMatchOne() = + PgDB().use(JsonFunctions::writeFirstByFieldsMatchOne) + + @Test + @DisplayName("writeFirstByFields retrieves a matching document among many") + fun writeFirstByFieldsMatchMany() = + PgDB().use(JsonFunctions::writeFirstByFieldsMatchMany) + + @Test + @DisplayName("writeFirstByFields retrieves a matching document among many (ordered)") + fun writeFirstByFieldsMatchOrdered() = + PgDB().use(JsonFunctions::writeFirstByFieldsMatchOrdered) + + @Test + @DisplayName("writeFirstByFields returns null when no document matches") + fun writeFirstByFieldsNoMatch() = + PgDB().use(JsonFunctions::writeFirstByFieldsNoMatch) + + @Test + @DisplayName("writeFirstByContains retrieves a matching document") + fun writeFirstByContainsMatchOne() = + PgDB().use(JsonFunctions::writeFirstByContainsMatchOne) + + @Test + @DisplayName("writeFirstByContains retrieves a matching document among many") + fun writeFirstByContainsMatchMany() = + PgDB().use(JsonFunctions::writeFirstByContainsMatchMany) + + @Test + @DisplayName("writeFirstByContains retrieves a matching document among many (ordered)") + fun writeFirstByContainsMatchOrdered() = + PgDB().use(JsonFunctions::writeFirstByContainsMatchOrdered) + + @Test + @DisplayName("writeFirstByContains returns null when no document matches") + fun writeFirstByContainsNoMatch() = + PgDB().use(JsonFunctions::writeFirstByContainsNoMatch) + + @Test + @DisplayName("writeFirstByJsonPath retrieves a matching document") + fun writeFirstByJsonPathMatchOne() = + PgDB().use(JsonFunctions::writeFirstByJsonPathMatchOne) + + @Test + @DisplayName("writeFirstByJsonPath retrieves a matching document among many") + fun writeFirstByJsonPathMatchMany() = + PgDB().use(JsonFunctions::writeFirstByJsonPathMatchMany) + + @Test + @DisplayName("writeFirstByJsonPath retrieves a matching document among many (ordered)") + fun writeFirstByJsonPathMatchOrdered() = + PgDB().use(JsonFunctions::writeFirstByJsonPathMatchOrdered) + + @Test + @DisplayName("writeFirstByJsonPath returns null when no document matches") + fun writeFirstByJsonPathNoMatch() = + PgDB().use(JsonFunctions::writeFirstByJsonPathNoMatch) +} diff --git a/src/kotlinx/src/test/kotlin/integration/SQLiteJsonIT.kt b/src/kotlinx/src/test/kotlin/integration/SQLiteJsonIT.kt index 91bcb67..b6e8b04 100644 --- a/src/kotlinx/src/test/kotlin/integration/SQLiteJsonIT.kt +++ b/src/kotlinx/src/test/kotlin/integration/SQLiteJsonIT.kt @@ -109,4 +109,103 @@ class SQLiteJsonIT { fun firstByJsonPathFails() { assertThrows { SQLiteDB().use(JsonFunctions::firstByJsonPathMatchOne) } } -} \ No newline at end of file + + @Test + @DisplayName("writeAll retrieves all documents") + fun writeAllDefault() = + SQLiteDB().use(JsonFunctions::writeAllDefault) + + @Test + @DisplayName("writeAll succeeds with an empty table") + fun writeAllEmpty() = + SQLiteDB().use(JsonFunctions::writeAllEmpty) + + @Test + @DisplayName("writeById retrieves a document via a string ID") + fun writeByIdString() = + SQLiteDB().use(JsonFunctions::writeByIdString) + + @Test + @DisplayName("writeById retrieves a document via a numeric ID") + fun writeByIdNumber() = + SQLiteDB().use(JsonFunctions::writeByIdNumber) + + @Test + @DisplayName("writeById returns null when a matching ID is not found") + fun writeByIdNotFound() = + SQLiteDB().use(JsonFunctions::writeByIdNotFound) + + @Test + @DisplayName("writeByFields retrieves matching documents") + fun writeByFieldsMatch() = + SQLiteDB().use(JsonFunctions::writeByFieldsMatch) + + @Test + @DisplayName("writeByFields retrieves ordered matching documents") + fun writeByFieldsMatchOrdered() = + SQLiteDB().use(JsonFunctions::writeByFieldsMatchOrdered) + + @Test + @DisplayName("writeByFields retrieves matching documents with a numeric IN clause") + fun writeByFieldsMatchNumIn() = + SQLiteDB().use(JsonFunctions::writeByFieldsMatchNumIn) + + @Test + @DisplayName("writeByFields succeeds when no documents match") + fun writeByFieldsNoMatch() = + SQLiteDB().use(JsonFunctions::writeByFieldsNoMatch) + + @Test + @DisplayName("writeByFields retrieves matching documents with an IN_ARRAY comparison") + fun writeByFieldsMatchInArray() = + SQLiteDB().use(JsonFunctions::writeByFieldsMatchInArray) + + @Test + @DisplayName("writeByFields succeeds when no documents match an IN_ARRAY comparison") + fun writeByFieldsNoMatchInArray() = + SQLiteDB().use(JsonFunctions::writeByFieldsNoMatchInArray) + + @Test + @DisplayName("writeByContains fails") + fun writeByContainsFails() { + assertThrows { SQLiteDB().use(JsonFunctions::writeByContainsMatch) } + } + + @Test + @DisplayName("writeByJsonPath fails") + fun writeByJsonPathFails() { + assertThrows { SQLiteDB().use(JsonFunctions::writeByJsonPathMatch) } + } + + @Test + @DisplayName("writeFirstByFields retrieves a matching document") + fun writeFirstByFieldsMatchOne() = + SQLiteDB().use(JsonFunctions::writeFirstByFieldsMatchOne) + + @Test + @DisplayName("writeFirstByFields retrieves a matching document among many") + fun writeFirstByFieldsMatchMany() = + SQLiteDB().use(JsonFunctions::writeFirstByFieldsMatchMany) + + @Test + @DisplayName("writeFirstByFields retrieves a matching document among many (ordered)") + fun writeFirstByFieldsMatchOrdered() = + SQLiteDB().use(JsonFunctions::writeFirstByFieldsMatchOrdered) + + @Test + @DisplayName("writeFirstByFields returns null when no document matches") + fun writeFirstByFieldsNoMatch() = + SQLiteDB().use(JsonFunctions::writeFirstByFieldsNoMatch) + + @Test + @DisplayName("writeFirstByContains fails") + fun writeFirstByContainsFails() { + assertThrows { SQLiteDB().use(JsonFunctions::writeFirstByContainsMatchOne) } + } + + @Test + @DisplayName("writeFirstByJsonPath fails") + fun writeFirstByJsonPathFails() { + assertThrows { SQLiteDB().use(JsonFunctions::writeFirstByJsonPathMatchOne) } + } +}