From e51728d16a2ac8437f6cabfafe89da28c724e8a1 Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Sun, 30 Mar 2025 20:34:19 -0400 Subject: [PATCH] Add write methods for Json output --- src/core/src/main/kotlin/java/Json.kt | 473 +++++++++++++++- .../main/kotlin/java/extensions/Connection.kt | 145 ++++- .../test/kotlin/integration/JsonFunctions.kt | 512 ++++++++++++++---- .../kotlin/integration/PostgreSQLJsonIT.kt | 153 +++++- .../test/kotlin/integration/SQLiteJsonIT.kt | 99 ++++ 5 files changed, 1268 insertions(+), 114 deletions(-) diff --git a/src/core/src/main/kotlin/java/Json.kt b/src/core/src/main/kotlin/java/Json.kt index abeda6f..d41aa51 100644 --- a/src/core/src/main/kotlin/java/Json.kt +++ b/src/core/src/main/kotlin/java/Json.kt @@ -3,6 +3,7 @@ package solutions.bitbadger.documents.java import solutions.bitbadger.documents.* import solutions.bitbadger.documents.query.FindQuery import solutions.bitbadger.documents.query.orderBy +import java.io.PrintWriter import java.sql.Connection import kotlin.jvm.Throws @@ -23,7 +24,12 @@ object Json { @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) + Custom.jsonArray( + FindQuery.all(tableName) + (orderBy?.let(::orderBy) ?: ""), + listOf(), + conn, + Results::jsonFromData + ) /** * Retrieve all documents in the given table (creates connection) @@ -52,6 +58,54 @@ object Json { fun all(tableName: String, conn: Connection) = all(tableName, null, 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 + */ + @Throws(DocumentException::class) + @JvmStatic + fun writeAll(tableName: String, writer: PrintWriter, orderBy: Collection>? = null, conn: Connection) = + Custom.writeJsonArray( + FindQuery.all(tableName) + (orderBy?.let(::orderBy) ?: ""), + listOf(), + writer, + conn, + Results::jsonFromData + ) + + /** + * 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 + */ + @Throws(DocumentException::class) + @JvmStatic + @JvmOverloads + fun writeAll(tableName: String, writer: PrintWriter, orderBy: Collection>? = null) = + Configuration.dbConn().use { writeAll(tableName, writer, orderBy, it) } + + /** + * 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 + */ + @Throws(DocumentException::class) + @JvmStatic + fun writeAll(tableName: String, writer: PrintWriter, conn: Connection) = + writeAll(tableName, writer, null, conn) + /** * Retrieve a document by its ID * @@ -84,6 +138,33 @@ object Json { fun byId(tableName: String, docId: TKey) = Configuration.dbConn().use { byId(tableName, docId, it) } + /** + * Write a document to the given `PrintWriter` by its ID (writes empty object if not found) + * + * @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 + */ + @Throws(DocumentException::class) + @JvmStatic + fun writeById(tableName: String, writer: PrintWriter, docId: TKey, conn: Connection) = + writer.write(byId(tableName, docId, conn)) + + /** + * Write a document to the given `PrintWriter` by its ID (writes empty object if not found; 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 + */ + @Throws(DocumentException::class) + @JvmStatic + fun writeById(tableName: String, writer: PrintWriter, docId: TKey) = + Configuration.dbConn().use { writeById(tableName, writer, docId, it) } + /** * Retrieve documents using a field comparison, ordering results by the given fields * @@ -148,6 +229,79 @@ object Json { fun byFields(tableName: String, fields: Collection>, howMatched: FieldMatch? = null, conn: Connection) = byFields(tableName, fields, howMatched, null, 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 + */ + @Throws(DocumentException::class) + @JvmStatic + fun writeByFields( + tableName: String, + writer: PrintWriter, + fields: Collection>, + howMatched: FieldMatch? = null, + orderBy: Collection>? = null, + conn: Connection + ) { + val named = Parameters.nameFields(fields) + Custom.writeJsonArray( + FindQuery.byFields(tableName, named, howMatched) + (orderBy?.let(::orderBy) ?: ""), + Parameters.addFields(named), + writer, + conn, + Results::jsonFromData + ) + } + + /** + * 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 connection string has been set, or if parameters are invalid + */ + @Throws(DocumentException::class) + @JvmStatic + @JvmOverloads + fun writeByFields( + tableName: String, + writer: PrintWriter, + fields: Collection>, + howMatched: FieldMatch? = null, + orderBy: Collection>? = null + ) = Configuration.dbConn().use { writeByFields(tableName, writer, fields, howMatched, orderBy, it) } + + /** + * 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 + */ + @Throws(DocumentException::class) + @JvmStatic + fun writeByFields( + tableName: String, + writer: PrintWriter, + fields: Collection>, + howMatched: FieldMatch? = null, + conn: Connection + ) = writeByFields(tableName, writer, fields, howMatched, null, conn) + /** * Retrieve documents using a JSON containment query, ordering results by the given fields (PostgreSQL only) * @@ -166,11 +320,11 @@ object Json { orderBy: Collection>? = null, conn: Connection ) = Custom.jsonArray( - FindQuery.byContains(tableName) + (orderBy?.let(::orderBy) ?: ""), - listOf(Parameters.json(":criteria", criteria)), - conn, - Results::jsonFromData - ) + FindQuery.byContains(tableName) + (orderBy?.let(::orderBy) ?: ""), + listOf(Parameters.json(":criteria", criteria)), + conn, + Results::jsonFromData + ) /** * Retrieve documents using a JSON containment query, ordering results by the given fields (PostgreSQL only; creates @@ -202,6 +356,67 @@ object Json { 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 + */ + @Throws(DocumentException::class) + @JvmStatic + 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 no connection string has been set, or if called on a SQLite connection + */ + @Throws(DocumentException::class) + @JvmStatic + @JvmOverloads + 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 (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 + */ + @Throws(DocumentException::class) + @JvmStatic + 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) * @@ -252,6 +467,63 @@ object Json { fun byJsonPath(tableName: String, path: String, conn: Connection) = byJsonPath(tableName, path, null, 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 + */ + @Throws(DocumentException::class) + @JvmStatic + fun writeByJsonPath( + tableName: String, + writer: PrintWriter, + path: String, + orderBy: Collection>? = null, + conn: Connection + ) = Custom.writeJsonArray( + FindQuery.byJsonPath(tableName) + (orderBy?.let(::orderBy) ?: ""), + listOf(Parameter(":path", ParameterType.STRING, path)), + writer, + conn, + Results::jsonFromData + ) + + /** + * 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 no connection string has been set, or if called on a SQLite connection + */ + @Throws(DocumentException::class) + @JvmStatic + @JvmOverloads + fun writeByJsonPath(tableName: String, writer: PrintWriter, path: String, orderBy: Collection>? = null) = + Configuration.dbConn().use { writeByJsonPath(tableName, writer, path, orderBy, it) } + + /** + * 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 + */ + @Throws(DocumentException::class) + @JvmStatic + fun writeByJsonPath(tableName: String, writer: PrintWriter, path: String, conn: Connection) = + writeByJsonPath(tableName, writer, path, null, conn) + /** * Retrieve the first document using a field comparison and optional ordering fields * @@ -320,6 +592,71 @@ object Json { conn: Connection ) = firstByFields(tableName, fields, howMatched, null, 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 + */ + @Throws(DocumentException::class) + @JvmStatic + fun writeFirstByFields( + tableName: String, + writer: PrintWriter, + fields: Collection>, + howMatched: FieldMatch? = null, + orderBy: Collection>? = null, + conn: Connection + ) = writer.write(firstByFields(tableName, 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 connection string has been set, or if parameters are invalid + */ + @Throws(DocumentException::class) + @JvmStatic + @JvmOverloads + fun writeFirstByFields( + tableName: String, + writer: PrintWriter, + fields: Collection>, + howMatched: FieldMatch? = null, + orderBy: Collection>? = null + ) = Configuration.dbConn().use { writeFirstByFields(tableName, writer, fields, howMatched, orderBy, it) } + + /** + * 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 + */ + @Throws(DocumentException::class) + @JvmStatic + fun writeFirstByFields( + tableName: String, + writer: PrintWriter, + fields: Collection>, + howMatched: FieldMatch? = null, + conn: Connection + ) = writeFirstByFields(tableName, writer, fields, howMatched, null, conn) + + /** * Retrieve the first document using a JSON containment query and optional ordering fields (PostgreSQL only) * @@ -338,11 +675,11 @@ object Json { orderBy: Collection>? = null, conn: Connection ) = Custom.jsonSingle( - FindQuery.byContains(tableName) + (orderBy?.let(::orderBy) ?: ""), - listOf(Parameters.json(":criteria", criteria)), - conn, - Results::jsonFromData - ) + 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) @@ -374,6 +711,65 @@ object Json { fun firstByContains(tableName: String, criteria: TContains, 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 + */ + @Throws(DocumentException::class) + @JvmStatic + fun writeFirstByContains( + tableName: String, + writer: PrintWriter, + criteria: TContains, + orderBy: Collection>? = null, + conn: Connection + ) = writer.write(firstByContains(tableName, criteria, orderBy, conn)) + + /** + * 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 + */ + @Throws(DocumentException::class) + @JvmStatic + fun writeFirstByContains( + tableName: String, + writer: PrintWriter, + criteria: TContains, + conn: Connection + ) = writeFirstByContains(tableName, writer, criteria, null, conn) + + /** + * 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 no connection string has been set, or if called on a SQLite connection + */ + @Throws(DocumentException::class) + @JvmStatic + @JvmOverloads + fun writeFirstByContains( + tableName: String, + writer: PrintWriter, + criteria: TContains, + orderBy: Collection>? = null + ) = Configuration.dbConn().use { writeFirstByContains(tableName, writer, criteria, orderBy, it) } + /** * Retrieve the first document using a JSON Path match query and optional ordering fields (PostgreSQL only) * @@ -423,4 +819,59 @@ object Json { @JvmOverloads fun firstByJsonPath(tableName: String, path: String, orderBy: Collection>? = null) = Configuration.dbConn().use { firstByJsonPath(tableName, path, orderBy, it) } + + /** + * 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 + */ + @Throws(DocumentException::class) + @JvmStatic + fun writeFirstByJsonPath( + tableName: String, + writer: PrintWriter, + path: String, + orderBy: Collection>? = null, + conn: Connection + ) = writer.write(firstByJsonPath(tableName, path, orderBy, conn)) + + /** + * 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 + */ + @Throws(DocumentException::class) + @JvmStatic + fun writeFirstByJsonPath(tableName: String, writer: PrintWriter, path: String, conn: Connection) = + writeFirstByJsonPath(tableName, writer, path, null, 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 no connection string has been set, or if called on a SQLite connection + */ + @Throws(DocumentException::class) + @JvmStatic + @JvmOverloads + fun writeFirstByJsonPath( + tableName: String, + writer: PrintWriter, + path: String, + orderBy: Collection>? = null + ) = Configuration.dbConn().use { writeFirstByJsonPath(tableName, writer, path, orderBy, it) } } diff --git a/src/core/src/main/kotlin/java/extensions/Connection.kt b/src/core/src/main/kotlin/java/extensions/Connection.kt index cb163b3..9b13fee 100644 --- a/src/core/src/main/kotlin/java/extensions/Connection.kt +++ b/src/core/src/main/kotlin/java/extensions/Connection.kt @@ -95,7 +95,7 @@ fun Connection.customJsonSingle( query: String, parameters: Collection> = listOf(), mapFunc: (ResultSet) -> String -) = Configuration.dbConn().use { Custom.jsonSingle(query, parameters, it, mapFunc) } +) = Custom.jsonSingle(query, parameters, this, mapFunc) /** * Execute a query that returns no results @@ -575,6 +575,149 @@ 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 output) ~~~ + +/** + * 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 query execution fails + */ +@Throws(DocumentException::class) +@JvmOverloads +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 dialect has been configured + */ +@Throws(DocumentException::class) +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 optional given fields + * + * @param tableName The table from which the document 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 + */ +@Throws(DocumentException::class) +@JvmOverloads +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 optional given + * fields (PostgreSQL only) + * + * @param tableName The name of the table in which document existence should be checked + * @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 + */ +@Throws(DocumentException::class) +@JvmOverloads +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 optional 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 called on a SQLite connection + */ +@Throws(DocumentException::class) +@JvmOverloads +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 dialect has been configured, or if parameters are invalid + */ +@Throws(DocumentException::class) +@JvmOverloads +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 called on a SQLite connection + */ +@Throws(DocumentException::class) +@JvmOverloads +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 called on a SQLite connection + */ +@Throws(DocumentException::class) +@JvmOverloads +fun Connection.writeJsonFirstByJsonPath( + tableName: String, + writer: PrintWriter, + path: String, + orderBy: Collection>? = null +) = Json.writeFirstByJsonPath(tableName, writer, path, orderBy, this) + // ~~~ DOCUMENT PATCH (PARTIAL UPDATE) QUERIES ~~~ /** diff --git a/src/core/src/test/kotlin/integration/JsonFunctions.kt b/src/core/src/test/kotlin/integration/JsonFunctions.kt index 94b23d4..2ef24f1 100644 --- a/src/core/src/test/kotlin/integration/JsonFunctions.kt +++ b/src/core/src/test/kotlin/integration/JsonFunctions.kt @@ -9,6 +9,8 @@ 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 java.io.PrintWriter +import java.io.StringWriter import kotlin.test.assertEquals import kotlin.test.assertTrue @@ -45,9 +47,7 @@ object JsonFunctions { private fun docId(id: String) = maybeJsonB("{\"id\":\"$id\"") - fun allDefault(db: ThrowawayDatabase) { - JsonDocument.load(db) - val json = db.conn.jsonAll(TEST_TABLE) + private fun checkAllDefault(json: String) { assertTrue(json.startsWith("["), "JSON should start with '[' ($json)") when (Configuration.dialect()) { Dialect.SQLITE -> { @@ -68,41 +68,98 @@ object JsonFunctions { assertTrue(json.endsWith("]"), "JSON should end with ']' ($json)") } - fun allEmpty(db: ThrowawayDatabase) = - assertEquals("[]", db.conn.jsonAll(TEST_TABLE), "There should have been no documents returned") - - fun byIdString(db: ThrowawayDatabase) { + fun allDefault(db: ThrowawayDatabase) { JsonDocument.load(db) - val json = db.conn.jsonById(TEST_TABLE, "two") + 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()) + } + + private fun checkAllEmpty(json: String) = + assertEquals("[]", json, "There should have been no documents returned") + + fun allEmpty(db: ThrowawayDatabase) = + checkAllEmpty(db.conn.jsonAll(TEST_TABLE)) + + 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" - ) + checkByIdNumber(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 -> { @@ -111,13 +168,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" @@ -133,11 +207,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 -> { @@ -146,37 +236,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 -> { @@ -191,11 +321,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" @@ -210,20 +349,41 @@ 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 -> { @@ -238,9 +398,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" @@ -256,29 +427,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), @@ -289,40 +489,84 @@ 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!")) + checkFirstByContainsMatchOne(output.toString()) + } + + private fun checkFirstByContainsMatchMany(json: String) = when (Configuration.dialect()) { Dialect.SQLITE -> assertTrue( json.contains(JsonDocument.four) || json.contains(JsonDocument.five), @@ -333,40 +577,81 @@ 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), @@ -377,23 +662,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 firstByJsonPathMatchOrdered(db: ThrowawayDatabase) { + JsonDocument.load(db) + checkFirstByJsonPathMatchOrdered( + db.conn.jsonFirstByJsonPath(TEST_TABLE, "$.numValue ? (@ > 10)", listOf(Field.named("id DESC"))) + ) } + 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) - assertEquals( - "{}", - db.conn.jsonFirstByJsonPath(TEST_TABLE, "$.numValue ? (@ > 100)"), - "There should have been no document returned" - ) + 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/core/src/test/kotlin/integration/PostgreSQLJsonIT.kt b/src/core/src/test/kotlin/integration/PostgreSQLJsonIT.kt index 4e98051..703078d 100644 --- a/src/core/src/test/kotlin/integration/PostgreSQLJsonIT.kt +++ b/src/core/src/test/kotlin/integration/PostgreSQLJsonIT.kt @@ -30,7 +30,7 @@ class PostgreSQLJsonIT { PgDB().use(JsonFunctions::byIdNumber) @Test - @DisplayName("byId returns null when a matching ID is not found") + @DisplayName("byId returns an empty document when a matching ID is not found") fun byIdNotFound() = PgDB().use(JsonFunctions::byIdNotFound) @@ -110,7 +110,7 @@ class PostgreSQLJsonIT { PgDB().use(JsonFunctions::firstByFieldsMatchOrdered) @Test - @DisplayName("firstByFields returns null when no document matches") + @DisplayName("firstByFields returns an empty document when no document matches") fun firstByFieldsNoMatch() = PgDB().use(JsonFunctions::firstByFieldsNoMatch) @@ -130,7 +130,7 @@ class PostgreSQLJsonIT { PgDB().use(JsonFunctions::firstByContainsMatchOrdered) @Test - @DisplayName("firstByContains returns null when no document matches") + @DisplayName("firstByContains returns an empty document when no document matches") fun firstByContainsNoMatch() = PgDB().use(JsonFunctions::firstByContainsNoMatch) @@ -150,7 +150,152 @@ class PostgreSQLJsonIT { PgDB().use(JsonFunctions::firstByJsonPathMatchOrdered) @Test - @DisplayName("firstByJsonPath returns null when no document matches") + @DisplayName("firstByJsonPath returns an empty document when no document matches") fun firstByJsonPathNoMatch() = PgDB().use(JsonFunctions::firstByJsonPathNoMatch) + + @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 writes an empty document 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 writes an empty document 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 writes an empty document 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 writes an empty document when no document matches") + fun writeFirstByJsonPathNoMatch() = + PgDB().use(JsonFunctions::writeFirstByJsonPathNoMatch) } diff --git a/src/core/src/test/kotlin/integration/SQLiteJsonIT.kt b/src/core/src/test/kotlin/integration/SQLiteJsonIT.kt index 6f20601..f416849 100644 --- a/src/core/src/test/kotlin/integration/SQLiteJsonIT.kt +++ b/src/core/src/test/kotlin/integration/SQLiteJsonIT.kt @@ -109,4 +109,103 @@ class SQLiteJsonIT { fun firstByJsonPathFails() { assertThrows { SQLiteDB().use(JsonFunctions::firstByJsonPathMatchOne) } } + + @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) } + } }