From 5497238fb19e9e162515410ced6e158d34fc178a Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Fri, 14 Mar 2025 23:28:30 -0400 Subject: [PATCH] Kotlin project builds --- src/jvm/pom.xml | 4 +- src/kotlin/pom.xml | 23 +--- src/kotlin/src/main/kotlin/Count.kt | 17 +-- src/kotlin/src/main/kotlin/Custom.kt | 1 - src/kotlin/src/main/kotlin/Definition.kt | 19 +-- src/kotlin/src/main/kotlin/Delete.kt | 29 ++--- src/kotlin/src/main/kotlin/Document.kt | 114 ++++++++++++++++++ src/kotlin/src/main/kotlin/Exists.kt | 43 ++----- src/kotlin/src/main/kotlin/Find.kt | 76 +++++++----- src/kotlin/src/main/kotlin/Parameters.kt | 81 ++----------- src/kotlin/src/main/kotlin/Patch.kt | 9 +- src/kotlin/src/main/kotlin/RemoveFields.kt | 57 +++------ src/kotlin/src/main/kotlin/Results.kt | 17 ++- .../Connection.kt} | 35 +++--- src/pom.xml | 1 + 15 files changed, 260 insertions(+), 266 deletions(-) create mode 100644 src/kotlin/src/main/kotlin/Document.kt rename src/kotlin/src/main/kotlin/{ConnectionExtensions.kt => extensions/Connection.kt} (94%) diff --git a/src/jvm/pom.xml b/src/jvm/pom.xml index 2da78a7..33c4d65 100644 --- a/src/jvm/pom.xml +++ b/src/jvm/pom.xml @@ -5,7 +5,7 @@ 4.0.0 solutions.bitbadger.documents - java + jvm 4.0.0-alpha1-SNAPSHOT jar @@ -16,7 +16,7 @@ ${project.groupId}:${project.artifactId} - Expose a document store interface for PostgreSQL and SQLite (Java Library) + Expose a document store interface for PostgreSQL and SQLite (Standard JVM Library) https://bitbadger.solutions/open-source/relational-documents/jvm/ diff --git a/src/kotlin/pom.xml b/src/kotlin/pom.xml index 88bafc0..9889629 100644 --- a/src/kotlin/pom.xml +++ b/src/kotlin/pom.xml @@ -28,16 +28,10 @@ solutions.bitbadger.documents - common + jvm + 4.0.0-alpha1-SNAPSHOT system - ${project.basedir}/../common/target/common-4.0.0-alpha1-SNAPSHOT.jar - jar - - - solutions.bitbadger.documents - java - system - ${project.basedir}/../java/target/java-4.0.0-alpha1-SNAPSHOT.jar + ${project.basedir}/../jvm/target/jvm-4.0.0-alpha1-SNAPSHOT.jar jar @@ -57,11 +51,6 @@ compile - - - ${project.basedir}/src/main/kotlin - - test-compile @@ -69,12 +58,6 @@ test-compile - - - ${project.basedir}/src/test/java - ${project.basedir}/src/test/kotlin - - diff --git a/src/kotlin/src/main/kotlin/Count.kt b/src/kotlin/src/main/kotlin/Count.kt index 4b2f3f5..d8fbf7b 100644 --- a/src/kotlin/src/main/kotlin/Count.kt +++ b/src/kotlin/src/main/kotlin/Count.kt @@ -1,10 +1,7 @@ package solutions.bitbadger.documents.kotlin -import solutions.bitbadger.documents.Configuration -import solutions.bitbadger.documents.Field -import solutions.bitbadger.documents.FieldMatch -import solutions.bitbadger.documents.Parameter -import solutions.bitbadger.documents.ParameterType +import solutions.bitbadger.documents.* +import solutions.bitbadger.documents.kotlin.extensions.* import solutions.bitbadger.documents.query.Count import java.sql.Connection @@ -21,7 +18,6 @@ object Count { * @param conn The connection over which documents should be counted * @return A count of the documents in the table */ - @JvmStatic fun all(tableName: String, conn: Connection) = conn.customScalar(Count.all(tableName), mapFunc = Results::toCount) @@ -31,7 +27,6 @@ object Count { * @param tableName The name of the table in which documents should be counted * @return A count of the documents in the table */ - @JvmStatic fun all(tableName: String) = Configuration.dbConn().use { all(tableName, it) } @@ -44,8 +39,6 @@ object Count { * @param conn The connection on which the deletion should be executed * @return A count of the matching documents in the table */ - @JvmStatic - @JvmOverloads fun byFields( tableName: String, fields: Collection>, @@ -68,8 +61,6 @@ object Count { * @param howMatched How the fields should be matched * @return A count of the matching documents in the table */ - @JvmStatic - @JvmOverloads fun byFields(tableName: String, fields: Collection>, howMatched: FieldMatch? = null) = Configuration.dbConn().use { byFields(tableName, fields, howMatched, it) } @@ -82,7 +73,6 @@ object Count { * @return A count of the matching documents in the table * @throws DocumentException If called on a SQLite connection */ - @JvmStatic inline fun byContains(tableName: String, criteria: TContains, conn: Connection) = conn.customScalar(Count.byContains(tableName), listOf(Parameters.json(":criteria", criteria)), Results::toCount) @@ -94,7 +84,6 @@ object Count { * @return A count of the matching documents in the table * @throws DocumentException If called on a SQLite connection */ - @JvmStatic inline fun byContains(tableName: String, criteria: TContains) = Configuration.dbConn().use { byContains(tableName, criteria, it) } @@ -107,7 +96,6 @@ object Count { * @return A count of the matching documents in the table * @throws DocumentException If called on a SQLite connection */ - @JvmStatic fun byJsonPath(tableName: String, path: String, conn: Connection) = conn.customScalar( Count.byJsonPath(tableName), @@ -123,7 +111,6 @@ object Count { * @return A count of the matching documents in the table * @throws DocumentException If called on a SQLite connection */ - @JvmStatic fun byJsonPath(tableName: String, path: String) = Configuration.dbConn().use { byJsonPath(tableName, path, it) } } diff --git a/src/kotlin/src/main/kotlin/Custom.kt b/src/kotlin/src/main/kotlin/Custom.kt index 265abd6..4d77ee5 100644 --- a/src/kotlin/src/main/kotlin/Custom.kt +++ b/src/kotlin/src/main/kotlin/Custom.kt @@ -2,7 +2,6 @@ package solutions.bitbadger.documents.kotlin import solutions.bitbadger.documents.* import solutions.bitbadger.documents.Configuration -import solutions.bitbadger.documents.jvm.Parameters import solutions.bitbadger.documents.jvm.Custom as JvmCustom import java.sql.Connection import java.sql.ResultSet diff --git a/src/kotlin/src/main/kotlin/Definition.kt b/src/kotlin/src/main/kotlin/Definition.kt index 8c2b2d9..23fa87e 100644 --- a/src/kotlin/src/main/kotlin/Definition.kt +++ b/src/kotlin/src/main/kotlin/Definition.kt @@ -1,4 +1,8 @@ +package solutions.bitbadger.documents.kotlin + +import solutions.bitbadger.documents.DocumentException import solutions.bitbadger.documents.DocumentIndex +import solutions.bitbadger.documents.jvm.Definition as JvmDefinition import java.sql.Connection /** @@ -13,10 +17,7 @@ object Definition { * @param conn The connection on which the query should be executed */ fun ensureTable(tableName: String, conn: Connection) = - Configuration.dialect("ensure $tableName exists").let { - conn.customNonQuery(Definition.ensureTable(tableName, it)) - conn.customNonQuery(Definition.ensureKey(tableName, it)) - } + JvmDefinition.ensureTable(tableName, conn) /** * Create a document table if necessary @@ -24,7 +25,7 @@ object Definition { * @param tableName The table whose existence should be ensured (may include schema) */ fun ensureTable(tableName: String) = - Configuration.dbConn().use { ensureTable(tableName, it) } + JvmDefinition.ensureTable(tableName) /** * Create an index on field(s) within documents in the specified table if necessary @@ -35,7 +36,7 @@ object Definition { * @param conn The connection on which the query should be executed */ fun ensureFieldIndex(tableName: String, indexName: String, fields: Collection, conn: Connection) = - conn.customNonQuery(Definition.ensureIndexOn(tableName, indexName, fields)) + JvmDefinition.ensureFieldIndex(tableName, indexName, fields, conn) /** * Create an index on field(s) within documents in the specified table if necessary @@ -45,7 +46,7 @@ object Definition { * @param fields One or more fields to be indexed< */ fun ensureFieldIndex(tableName: String, indexName: String, fields: Collection) = - Configuration.dbConn().use { ensureFieldIndex(tableName, indexName, fields, it) } + JvmDefinition.ensureFieldIndex(tableName, indexName, fields) /** * Create a document index on a table (PostgreSQL only) @@ -56,7 +57,7 @@ object Definition { * @throws DocumentException If called on a SQLite connection */ fun ensureDocumentIndex(tableName: String, indexType: DocumentIndex, conn: Connection) = - conn.customNonQuery(Definition.ensureDocumentIndexOn(tableName, indexType)) + JvmDefinition.ensureDocumentIndex(tableName, indexType, conn) /** * Create a document index on a table (PostgreSQL only) @@ -66,5 +67,5 @@ object Definition { * @throws DocumentException If called on a SQLite connection */ fun ensureDocumentIndex(tableName: String, indexType: DocumentIndex) = - Configuration.dbConn().use { ensureDocumentIndex(tableName, indexType, it) } + JvmDefinition.ensureDocumentIndex(tableName, indexType) } diff --git a/src/kotlin/src/main/kotlin/Delete.kt b/src/kotlin/src/main/kotlin/Delete.kt index e092b05..e670d48 100644 --- a/src/kotlin/src/main/kotlin/Delete.kt +++ b/src/kotlin/src/main/kotlin/Delete.kt @@ -1,7 +1,9 @@ -import solutions.bitbadger.documents.Field -import solutions.bitbadger.documents.FieldMatch -import solutions.bitbadger.documents.Parameter -import solutions.bitbadger.documents.ParameterType +package solutions.bitbadger.documents.kotlin + +import solutions.bitbadger.documents.* +import solutions.bitbadger.documents.jvm.Delete as JvmDelete +import solutions.bitbadger.documents.kotlin.extensions.* +import solutions.bitbadger.documents.query.Delete import java.sql.Connection /** @@ -17,10 +19,7 @@ object Delete { * @param conn The connection on which the deletion should be executed */ fun byId(tableName: String, docId: TKey, conn: Connection) = - conn.customNonQuery( - Delete.byId(tableName, docId), - Parameters.addFields(listOf(Field.equal(Configuration.idField, docId, ":id"))) - ) + JvmDelete.byId(tableName, docId, conn) /** * Delete a document by its ID @@ -29,7 +28,7 @@ object Delete { * @param docId The ID of the document to be deleted */ fun byId(tableName: String, docId: TKey) = - Configuration.dbConn().use { byId(tableName, docId, it) } + JvmDelete.byId(tableName, docId) /** * Delete documents using a field comparison @@ -39,10 +38,8 @@ object Delete { * @param howMatched How the fields should be matched * @param conn The connection on which the deletion should be executed */ - fun byFields(tableName: String, fields: Collection>, howMatched: FieldMatch? = null, conn: Connection) { - val named = Parameters.nameFields(fields) - conn.customNonQuery(Delete.byFields(tableName, named, howMatched), Parameters.addFields(named)) - } + fun byFields(tableName: String, fields: Collection>, howMatched: FieldMatch? = null, conn: Connection) = + JvmDelete.byFields(tableName, fields, howMatched, conn) /** * Delete documents using a field comparison @@ -52,7 +49,7 @@ object Delete { * @param howMatched How the fields should be matched */ fun byFields(tableName: String, fields: Collection>, howMatched: FieldMatch? = null) = - Configuration.dbConn().use { byFields(tableName, fields, howMatched, it) } + JvmDelete.byFields(tableName, fields, howMatched) /** * Delete documents using a JSON containment query (PostgreSQL only) @@ -84,7 +81,7 @@ object Delete { * @throws DocumentException If called on a SQLite connection */ fun byJsonPath(tableName: String, path: String, conn: Connection) = - conn.customNonQuery(Delete.byJsonPath(tableName), listOf(Parameter(":path", ParameterType.STRING, path))) + JvmDelete.byJsonPath(tableName, path, conn) /** * Delete documents using a JSON Path match query (PostgreSQL only) @@ -94,5 +91,5 @@ object Delete { * @throws DocumentException If called on a SQLite connection */ fun byJsonPath(tableName: String, path: String) = - Configuration.dbConn().use { byJsonPath(tableName, path, it) } + JvmDelete.byJsonPath(tableName, path) } diff --git a/src/kotlin/src/main/kotlin/Document.kt b/src/kotlin/src/main/kotlin/Document.kt new file mode 100644 index 0000000..2c2547e --- /dev/null +++ b/src/kotlin/src/main/kotlin/Document.kt @@ -0,0 +1,114 @@ +package solutions.bitbadger.documents.kotlin + +import solutions.bitbadger.documents.AutoId +import solutions.bitbadger.documents.Configuration +import solutions.bitbadger.documents.Dialect +import solutions.bitbadger.documents.Field +import solutions.bitbadger.documents.extensions.customNonQuery +import solutions.bitbadger.documents.query.Document +import solutions.bitbadger.documents.query.Where +import solutions.bitbadger.documents.query.statementWhere +import java.sql.Connection + +/** + * Functions for manipulating documents + */ +object Document { + + /** + * Insert a new document + * + * @param tableName The table into which the document should be inserted (may include schema) + * @param document The document to be inserted + * @param conn The connection on which the query should be executed + */ + inline fun insert(tableName: String, document: TDoc, conn: Connection) { + val strategy = Configuration.autoIdStrategy + val query = if (strategy == AutoId.DISABLED) { + Document.insert(tableName) + } else { + val idField = Configuration.idField + val dialect = Configuration.dialect("Create auto-ID insert query") + val dataParam = if (AutoId.needsAutoId(strategy, document, idField)) { + when (dialect) { + Dialect.POSTGRESQL -> + when (strategy) { + AutoId.NUMBER -> "' || (SELECT coalesce(max(data->>'$idField')::numeric, 0) + 1 " + + "FROM $tableName) || '" + AutoId.UUID -> "\"${AutoId.generateUUID()}\"" + AutoId.RANDOM_STRING -> "\"${AutoId.generateRandomString()}\"" + else -> "\"' || (:data)->>'$idField' || '\"" + }.let { ":data::jsonb || ('{\"$idField\":$it}')::jsonb" } + + Dialect.SQLITE -> + when (strategy) { + AutoId.NUMBER -> "(SELECT coalesce(max(data->>'$idField'), 0) + 1 FROM $tableName)" + AutoId.UUID -> "'${AutoId.generateUUID()}'" + AutoId.RANDOM_STRING -> "'${AutoId.generateRandomString()}'" + else -> "(:data)->>'$idField'" + }.let { "json_set(:data, '$.$idField', $it)" } + } + } else { + ":data" + } + + Document.insert(tableName).replace(":data", dataParam) + } + conn.customNonQuery(query, listOf(Parameters.json(":data", document))) + } + + /** + * Insert a new document + * + * @param tableName The table into which the document should be inserted (may include schema) + * @param document The document to be inserted + */ + inline fun insert(tableName: String, document: TDoc) = + Configuration.dbConn().use { insert(tableName, document, it) } + + /** + * Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert") + * + * @param tableName The table in which the document should be saved (may include schema) + * @param document The document to be saved + * @param conn The connection on which the query should be executed + */ + inline fun save(tableName: String, document: TDoc, conn: Connection) = + conn.customNonQuery(Document.save(tableName), listOf(Parameters.json(":data", document))) + + /** + * Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert") + * + * @param tableName The table in which the document should be saved (may include schema) + * @param document The document to be saved + */ + inline fun save(tableName: String, document: TDoc) = + Configuration.dbConn().use { save(tableName, document, it) } + + /** + * Update (replace) a document by its ID + * + * @param tableName The table in which the document should be replaced (may include schema) + * @param docId The ID of the document to be replaced + * @param document The document to be replaced + * @param conn The connection on which the query should be executed + */ + inline fun update(tableName: String, docId: TKey, document: TDoc, conn: Connection) = + conn.customNonQuery( + statementWhere(Document.update(tableName), Where.byId(":id", docId)), + Parameters.addFields( + listOf(Field.equal(Configuration.idField, docId, ":id")), + mutableListOf(Parameters.json(":data", document)) + ) + ) + + /** + * Update (replace) a document by its ID + * + * @param tableName The table in which the document should be replaced (may include schema) + * @param docId The ID of the document to be replaced + * @param document The document to be replaced + */ + inline fun update(tableName: String, docId: TKey, document: TDoc) = + Configuration.dbConn().use { update(tableName, docId, document, it) } +} diff --git a/src/kotlin/src/main/kotlin/Exists.kt b/src/kotlin/src/main/kotlin/Exists.kt index 5146ba7..e253bbb 100644 --- a/src/kotlin/src/main/kotlin/Exists.kt +++ b/src/kotlin/src/main/kotlin/Exists.kt @@ -1,7 +1,9 @@ -import solutions.bitbadger.documents.Field -import solutions.bitbadger.documents.FieldMatch -import solutions.bitbadger.documents.Parameter -import solutions.bitbadger.documents.ParameterType +package solutions.bitbadger.documents.kotlin + +import solutions.bitbadger.documents.* +import solutions.bitbadger.documents.jvm.Exists as JvmExists +import solutions.bitbadger.documents.kotlin.extensions.* +import solutions.bitbadger.documents.query.Exists import java.sql.Connection /** @@ -18,11 +20,7 @@ object Exists { * @return True if the document exists, false if not */ fun byId(tableName: String, docId: TKey, conn: Connection) = - conn.customScalar( - Exists.byId(tableName, docId), - Parameters.addFields(listOf(Field.equal(Configuration.idField, docId, ":id"))), - Results::toExists - ) + JvmExists.byId(tableName, docId, conn) /** * Determine a document's existence by its ID @@ -32,7 +30,7 @@ object Exists { * @return True if the document exists, false if not */ fun byId(tableName: String, docId: TKey) = - Configuration.dbConn().use { byId(tableName, docId, it) } + JvmExists.byId(tableName, docId) /** * Determine document existence using a field comparison @@ -43,19 +41,8 @@ object Exists { * @param conn The connection on which the existence check should be executed * @return True if any matching documents exist, false if not */ - fun byFields( - tableName: String, - fields: Collection>, - howMatched: FieldMatch? = null, - conn: Connection - ): Boolean { - val named = Parameters.nameFields(fields) - return conn.customScalar( - Exists.byFields(tableName, named, howMatched), - Parameters.addFields(named), - Results::toExists - ) - } + fun byFields(tableName: String, fields: Collection>, howMatched: FieldMatch? = null, conn: Connection) = + JvmExists.byFields(tableName, fields, howMatched, conn) /** * Determine document existence using a field comparison @@ -66,7 +53,7 @@ object Exists { * @return True if any matching documents exist, false if not */ fun byFields(tableName: String, fields: Collection>, howMatched: FieldMatch? = null) = - Configuration.dbConn().use { byFields(tableName, fields, howMatched, it) } + JvmExists.byFields(tableName, fields, howMatched) /** * Determine document existence using a JSON containment query (PostgreSQL only) @@ -105,11 +92,7 @@ object Exists { * @throws DocumentException If called on a SQLite connection */ fun byJsonPath(tableName: String, path: String, conn: Connection) = - conn.customScalar( - Exists.byJsonPath(tableName), - listOf(Parameter(":path", ParameterType.STRING, path)), - Results::toExists - ) + JvmExists.byJsonPath(tableName, path, conn) /** * Determine document existence using a JSON Path match query (PostgreSQL only) @@ -120,5 +103,5 @@ object Exists { * @throws DocumentException If called on a SQLite connection */ fun byJsonPath(tableName: String, path: String) = - Configuration.dbConn().use { byJsonPath(tableName, path, it) } + JvmExists.byJsonPath(tableName, path) } diff --git a/src/kotlin/src/main/kotlin/Find.kt b/src/kotlin/src/main/kotlin/Find.kt index d698790..a9ecf18 100644 --- a/src/kotlin/src/main/kotlin/Find.kt +++ b/src/kotlin/src/main/kotlin/Find.kt @@ -1,7 +1,9 @@ -import solutions.bitbadger.documents.Field -import solutions.bitbadger.documents.FieldMatch -import solutions.bitbadger.documents.Parameter -import solutions.bitbadger.documents.ParameterType +package solutions.bitbadger.documents.kotlin + +import solutions.bitbadger.documents.* +import solutions.bitbadger.documents.kotlin.extensions.* +import solutions.bitbadger.documents.query.Find +import solutions.bitbadger.documents.query.orderBy import java.sql.Connection /** @@ -17,7 +19,7 @@ object Find { * @param conn The connection over which documents should be retrieved * @return A list of documents from the given table */ - inline fun all(tableName: String, orderBy: Collection>? = null, conn: Connection) = + inline fun all(tableName: String, orderBy: Collection>? = null, conn: Connection) = conn.customList(Find.all(tableName) + (orderBy?.let(::orderBy) ?: ""), mapFunc = Results::fromData) /** @@ -27,7 +29,7 @@ object Find { * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) * @return A list of documents from the given table */ - inline fun all(tableName: String, orderBy: Collection>? = null) = + inline fun all(tableName: String, orderBy: Collection>? = null) = Configuration.dbConn().use { all(tableName, orderBy, it) } /** @@ -37,7 +39,7 @@ object Find { * @param conn The connection over which documents should be retrieved * @return A list of documents from the given table */ - inline fun all(tableName: String, conn: Connection) = + inline fun all(tableName: String, conn: Connection) = all(tableName, null, conn) /** @@ -48,7 +50,7 @@ object Find { * @param conn The connection over which documents should be retrieved * @return The document if it is found, `null` otherwise */ - inline fun byId(tableName: String, docId: TKey, conn: Connection) = + inline fun byId(tableName: String, docId: TKey, conn: Connection) = conn.customSingle( Find.byId(tableName, docId), Parameters.addFields(listOf(Field.equal(Configuration.idField, docId, ":id"))), @@ -62,7 +64,7 @@ object Find { * @param docId The ID of the document to retrieve * @return The document if it is found, `null` otherwise */ - inline fun byId(tableName: String, docId: TKey) = + inline fun byId(tableName: String, docId: TKey) = Configuration.dbConn().use { byId(tableName, docId, it) } /** @@ -75,7 +77,7 @@ object Find { * @param conn The connection over which documents should be retrieved * @return A list of documents matching the field comparison */ - inline fun byFields( + inline fun byFields( tableName: String, fields: Collection>, howMatched: FieldMatch? = null, @@ -99,7 +101,7 @@ object Find { * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) * @return A list of documents matching the field comparison */ - inline fun byFields( + inline fun byFields( tableName: String, fields: Collection>, howMatched: FieldMatch? = null, @@ -116,7 +118,7 @@ object Find { * @param conn The connection over which documents should be retrieved * @return A list of documents matching the field comparison */ - inline fun byFields( + inline fun byFields( tableName: String, fields: Collection>, howMatched: FieldMatch? = null, @@ -132,7 +134,7 @@ object Find { * @param howMatched How the fields should be matched * @return A list of documents matching the field comparison */ - inline fun byFields( + inline fun byFields( tableName: String, fields: Collection>, howMatched: FieldMatch? = null @@ -149,7 +151,7 @@ object Find { * @return A list of documents matching the JSON containment query * @throws DocumentException If called on a SQLite connection */ - inline fun byContains( + inline fun byContains( tableName: String, criteria: TContains, orderBy: Collection>? = null, @@ -170,7 +172,7 @@ object Find { * @return A list of documents matching the JSON containment query * @throws DocumentException If called on a SQLite connection */ - inline fun byContains( + inline fun byContains( tableName: String, criteria: TContains, orderBy: Collection>? = null @@ -186,7 +188,11 @@ object Find { * @return A list of documents matching the JSON containment query * @throws DocumentException If called on a SQLite connection */ - inline fun byContains(tableName: String, criteria: TContains, conn: Connection) = + inline fun byContains( + tableName: String, + criteria: TContains, + conn: Connection + ) = byContains(tableName, criteria, null, conn) /** @@ -197,7 +203,7 @@ object Find { * @return A list of documents matching the JSON containment query * @throws DocumentException If called on a SQLite connection */ - inline fun byContains(tableName: String, criteria: TContains) = + inline fun byContains(tableName: String, criteria: TContains) = Configuration.dbConn().use { byContains(tableName, criteria, it) } /** @@ -210,7 +216,7 @@ object Find { * @return A list of documents matching the JSON Path match query * @throws DocumentException If called on a SQLite connection */ - inline fun byJsonPath( + inline fun byJsonPath( tableName: String, path: String, orderBy: Collection>? = null, @@ -231,7 +237,7 @@ object Find { * @return A list of documents matching the JSON Path match query * @throws DocumentException If called on a SQLite connection */ - inline fun byJsonPath(tableName: String, path: String, orderBy: Collection>? = null) = + inline fun byJsonPath(tableName: String, path: String, orderBy: Collection>? = null) = Configuration.dbConn().use { byJsonPath(tableName, path, orderBy, it) } /** @@ -243,7 +249,7 @@ object Find { * @return A list of documents matching the JSON Path match query * @throws DocumentException If called on a SQLite connection */ - inline fun byJsonPath(tableName: String, path: String, conn: Connection) = + inline fun byJsonPath(tableName: String, path: String, conn: Connection) = byJsonPath(tableName, path, null, conn) /** @@ -256,7 +262,7 @@ object Find { * @param conn The connection over which documents should be retrieved * @return The first document matching the field comparison, or `null` if no matches are found */ - inline fun firstByFields( + inline fun firstByFields( tableName: String, fields: Collection>, howMatched: FieldMatch? = null, @@ -280,7 +286,7 @@ object Find { * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) * @return The first document matching the field comparison, or `null` if no matches are found */ - inline fun firstByFields( + inline fun firstByFields( tableName: String, fields: Collection>, howMatched: FieldMatch? = null, @@ -297,7 +303,7 @@ object Find { * @param conn The connection over which documents should be retrieved * @return The first document matching the field comparison, or `null` if no matches are found */ - inline fun firstByFields( + inline fun firstByFields( tableName: String, fields: Collection>, howMatched: FieldMatch? = null, @@ -315,7 +321,7 @@ object Find { * @return The first document matching the JSON containment query, or `null` if no matches are found * @throws DocumentException If called on a SQLite connection */ - inline fun firstByContains( + inline fun firstByContains( tableName: String, criteria: TContains, orderBy: Collection>? = null, @@ -336,7 +342,11 @@ object Find { * @return The first document matching the JSON containment query, or `null` if no matches are found * @throws DocumentException If called on a SQLite connection */ - inline fun firstByContains(tableName: String, criteria: TContains, conn: Connection) = + inline fun firstByContains( + tableName: String, + criteria: TContains, + conn: Connection + ) = firstByContains(tableName, criteria, null, conn) /** @@ -348,7 +358,11 @@ object Find { * @return The first document matching the JSON containment query, or `null` if no matches are found * @throws DocumentException If called on a SQLite connection */ - inline fun firstByContains(tableName: String, criteria: TContains, orderBy: Collection>? = null) = + inline fun firstByContains( + tableName: String, + criteria: TContains, + orderBy: Collection>? = null + ) = Configuration.dbConn().use { firstByContains(tableName, criteria, orderBy, it) } /** @@ -361,7 +375,7 @@ object Find { * @return The first document matching the JSON Path match query, or `null` if no matches are found * @throws DocumentException If called on a SQLite connection */ - inline fun firstByJsonPath( + inline fun firstByJsonPath( tableName: String, path: String, orderBy: Collection>? = null, @@ -382,7 +396,7 @@ object Find { * @return The first document matching the JSON Path match query, or `null` if no matches are found * @throws DocumentException If called on a SQLite connection */ - inline fun firstByJsonPath(tableName: String, path: String, conn: Connection) = + inline fun firstByJsonPath(tableName: String, path: String, conn: Connection) = firstByJsonPath(tableName, path, null, conn) /** @@ -394,6 +408,10 @@ object Find { * @return The first document matching the JSON Path match query, or `null` if no matches are found * @throws DocumentException If called on a SQLite connection */ - inline fun firstByJsonPath(tableName: String, path: String, orderBy: Collection>? = null) = + inline fun firstByJsonPath( + tableName: String, + path: String, + orderBy: Collection>? = null + ) = Configuration.dbConn().use { firstByJsonPath(tableName, path, orderBy, it) } } diff --git a/src/kotlin/src/main/kotlin/Parameters.kt b/src/kotlin/src/main/kotlin/Parameters.kt index 7dae9e9..74bf62c 100644 --- a/src/kotlin/src/main/kotlin/Parameters.kt +++ b/src/kotlin/src/main/kotlin/Parameters.kt @@ -1,10 +1,8 @@ +package solutions.bitbadger.documents.kotlin + import solutions.bitbadger.documents.* -import solutions.bitbadger.documents.ParameterName -import solutions.bitbadger.documents.common.* +import solutions.bitbadger.documents.jvm.Parameters as JvmParameters import java.sql.Connection -import java.sql.PreparedStatement -import java.sql.SQLException -import kotlin.jvm.Throws /** * Functions to assist with the creation and implementation of parameters for SQL queries @@ -19,17 +17,8 @@ object Parameters { * @param fields The collection of fields to be named * @return The collection of fields with parameter names assigned */ - @JvmStatic - fun nameFields(fields: Collection>): Collection> { - val name = ParameterName() - return fields.map { - if (it.parameterName.isNullOrEmpty() && !listOf(Op.EXISTS, Op.NOT_EXISTS).contains(it.comparison.op)) { - it.withParameterName(name.derive(null)) - } else { - it - } - } - } + fun nameFields(fields: Collection>): Collection> = + JvmParameters.nameFields(fields) /** * Create a parameter by encoding a JSON object @@ -38,9 +27,8 @@ object Parameters { * @param value The object to be encoded as JSON * @return A parameter with the value encoded */ - @JvmStatic - fun json(name: String, value: T) = - Parameter(name, ParameterType.JSON, Configuration.serializer.serialize(value)) + inline fun json(name: String, value: T) = + Parameter(name, ParameterType.JSON, DocumentConfig.serialize(value)) /** * Add field parameters to the given set of parameters @@ -49,9 +37,8 @@ object Parameters { * @param existing Any existing parameters for the query (optional, defaults to empty collection) * @return A collection of parameters for the query */ - @JvmStatic fun addFields(fields: Collection>, existing: MutableCollection> = mutableListOf()) = - fields.fold(existing) { acc, field -> field.appendParameter(acc) } + JvmParameters.addFields(fields, existing) /** * Replace the parameter names in the query with question marks @@ -60,9 +47,8 @@ object Parameters { * @param parameters The parameters for the query * @return The query, with name parameters changed to `?`s */ - @JvmStatic fun replaceNamesInQuery(query: String, parameters: Collection>) = - parameters.sortedByDescending { it.name.length }.fold(query) { acc, param -> acc.replace(param.name, "?") } + JvmParameters.replaceNamesInQuery(query, parameters) /** * Apply the given parameters to the given query, returning a prepared statement @@ -73,38 +59,8 @@ object Parameters { * @return A `PreparedStatement` with the parameter names replaced with `?` and parameter values bound * @throws DocumentException If parameter names are invalid or number value types are invalid */ - @Throws(DocumentException::class) - @JvmStatic - fun apply(conn: Connection, query: String, parameters: Collection>): PreparedStatement { - - if (parameters.isEmpty()) return try { - conn.prepareStatement(query) - } catch (ex: SQLException) { - throw DocumentException("Error preparing no-parameter query: ${ex.message}", ex) - } - - val replacements = mutableListOf>>() - parameters.sortedByDescending { it.name.length }.forEach { - var startPos = query.indexOf(it.name) - while (startPos > -1) { - replacements.add(Pair(startPos, it)) - startPos = query.indexOf(it.name, startPos + it.name.length + 1) - } - } - - return try { - replaceNamesInQuery(query, parameters) - //.also(::println) - .let { conn.prepareStatement(it) } - .also { stmt -> - replacements.sortedBy { it.first } - .map { it.second } - .forEachIndexed { index, param -> param.bind(stmt, index + 1) } - } - } catch (ex: SQLException) { - throw DocumentException("Error creating query / binding parameters: ${ex.message}", ex) - } - } + fun apply(conn: Connection, query: String, parameters: Collection>) = + JvmParameters.apply(conn, query, parameters) /** * Create parameters for field names to be removed from a document @@ -114,17 +70,6 @@ object Parameters { * @return A list of parameters to use for building the query * @throws DocumentException If the dialect has not been set */ - @Throws(DocumentException::class) - @JvmStatic - @JvmOverloads - fun fieldNames(names: Collection, parameterName: String = ":name"): MutableCollection> = - when (Configuration.dialect("generate field name parameters")) { - Dialect.POSTGRESQL -> mutableListOf( - Parameter(parameterName, ParameterType.STRING, names.joinToString(",").let { "{$it}" }) - ) - - Dialect.SQLITE -> names.mapIndexed { index, name -> - Parameter("$parameterName$index", ParameterType.STRING, name) - }.toMutableList() - } + fun fieldNames(names: Collection, parameterName: String = ":name") = + JvmParameters.fieldNames(names, parameterName) } diff --git a/src/kotlin/src/main/kotlin/Patch.kt b/src/kotlin/src/main/kotlin/Patch.kt index ea8ebc3..097eb2d 100644 --- a/src/kotlin/src/main/kotlin/Patch.kt +++ b/src/kotlin/src/main/kotlin/Patch.kt @@ -1,7 +1,8 @@ -import solutions.bitbadger.documents.Field -import solutions.bitbadger.documents.FieldMatch -import solutions.bitbadger.documents.Parameter -import solutions.bitbadger.documents.ParameterType +package solutions.bitbadger.documents.kotlin + +import solutions.bitbadger.documents.* +import solutions.bitbadger.documents.kotlin.extensions.* +import solutions.bitbadger.documents.query.Patch import java.sql.Connection /** diff --git a/src/kotlin/src/main/kotlin/RemoveFields.kt b/src/kotlin/src/main/kotlin/RemoveFields.kt index 9d40864..f1c7675 100644 --- a/src/kotlin/src/main/kotlin/RemoveFields.kt +++ b/src/kotlin/src/main/kotlin/RemoveFields.kt @@ -1,5 +1,9 @@ +package solutions.bitbadger.documents.kotlin + import solutions.bitbadger.documents.* -import solutions.bitbadger.documents.common.* +import solutions.bitbadger.documents.jvm.RemoveFields as JvmRemoveFields +import solutions.bitbadger.documents.kotlin.extensions.* +import solutions.bitbadger.documents.query.RemoveFields import java.sql.Connection /** @@ -7,20 +11,6 @@ import java.sql.Connection */ object RemoveFields { - /** - * Translate field paths to JSON paths for SQLite queries - * - * @param parameters The parameters for the specified fields - * @return The parameters for the specified fields, translated if used for SQLite - */ - private fun translatePath(parameters: MutableCollection>): MutableCollection> { - val dialect = Configuration.dialect("remove fields") - return when (dialect) { - Dialect.POSTGRESQL -> parameters - Dialect.SQLITE -> parameters.map { Parameter(it.name, it.type, "$.${it.value}") }.toMutableList() - } - } - /** * Remove fields from a document by its ID * @@ -29,16 +19,8 @@ object RemoveFields { * @param toRemove The names of the fields to be removed * @param conn The connection on which the update should be executed */ - fun byId(tableName: String, docId: TKey, toRemove: Collection, conn: Connection) { - val nameParams = Parameters.fieldNames(toRemove) - conn.customNonQuery( - RemoveFields.byId(tableName, nameParams, docId), - Parameters.addFields( - listOf(Field.equal(Configuration.idField, docId, ":id")), - translatePath(nameParams) - ) - ) - } + fun byId(tableName: String, docId: TKey, toRemove: Collection, conn: Connection) = + JvmRemoveFields.byId(tableName, docId, toRemove, conn) /** * Remove fields from a document by its ID @@ -48,7 +30,7 @@ object RemoveFields { * @param toRemove The names of the fields to be removed */ fun byId(tableName: String, docId: TKey, toRemove: Collection) = - Configuration.dbConn().use { byId(tableName, docId, toRemove, it) } + JvmRemoveFields.byId(tableName, docId, toRemove) /** * Remove fields from documents using a field comparison @@ -65,14 +47,8 @@ object RemoveFields { toRemove: Collection, howMatched: FieldMatch? = null, conn: Connection - ) { - val named = Parameters.nameFields(fields) - val nameParams = Parameters.fieldNames(toRemove) - conn.customNonQuery( - RemoveFields.byFields(tableName, nameParams, named, howMatched), - Parameters.addFields(named, translatePath(nameParams)) - ) - } + ) = + JvmRemoveFields.byFields(tableName, fields, toRemove, howMatched, conn) /** * Remove fields from documents using a field comparison @@ -88,7 +64,7 @@ object RemoveFields { toRemove: Collection, howMatched: FieldMatch? = null ) = - Configuration.dbConn().use { byFields(tableName, fields, toRemove, howMatched, it) } + JvmRemoveFields.byFields(tableName, fields, toRemove, howMatched) /** * Remove fields from documents using a JSON containment query (PostgreSQL only) @@ -132,13 +108,8 @@ object RemoveFields { * @param conn The connection on which the update should be executed * @throws DocumentException If called on a SQLite connection */ - fun byJsonPath(tableName: String, path: String, toRemove: Collection, conn: Connection) { - val nameParams = Parameters.fieldNames(toRemove) - conn.customNonQuery( - RemoveFields.byJsonPath(tableName, nameParams), - listOf(Parameter(":path", ParameterType.STRING, path), *nameParams.toTypedArray()) - ) - } + fun byJsonPath(tableName: String, path: String, toRemove: Collection, conn: Connection) = + JvmRemoveFields.byJsonPath(tableName, path, toRemove, conn) /** * Remove fields from documents using a JSON Path match query (PostgreSQL only) @@ -149,5 +120,5 @@ object RemoveFields { * @throws DocumentException If called on a SQLite connection */ fun byJsonPath(tableName: String, path: String, toRemove: Collection) = - Configuration.dbConn().use { byJsonPath(tableName, path, toRemove, it) } + JvmRemoveFields.byJsonPath(tableName, path, toRemove) } diff --git a/src/kotlin/src/main/kotlin/Results.kt b/src/kotlin/src/main/kotlin/Results.kt index 131dd66..791fb60 100644 --- a/src/kotlin/src/main/kotlin/Results.kt +++ b/src/kotlin/src/main/kotlin/Results.kt @@ -16,21 +16,18 @@ object Results { * Create a domain item from a document, specifying the field in which the document is found * * @param field The field name containing the JSON document - * @param rs A `ResultSet` set to the row with the document to be constructed - * @return The constructed domain item + * @return A function to create the constructed domain item */ - inline fun fromDocument(field: String): (ResultSet, Class) -> TDoc = - { rs, _ -> Results.fromDocument(field, rs, TDoc::class.java) } + inline fun fromDocument(field: String): (ResultSet) -> TDoc = + { rs -> DocumentConfig.deserialize(rs.getString(field)) } /** * Create a domain item from a document * - * @param rs A `ResultSet` set to the row with the document to be constructed< - * @param clazz The class of the document to be returned * @return The constructed domain item */ - inline fun fromData(rs: ResultSet, clazz: Class = TDoc::class.java) = - Results.fromDocument("data", rs, TDoc::class.java) + inline fun fromData(rs: ResultSet) = + fromDocument("data")(rs) /** * Create a list of items for the results of the given command, using the specified mapping function @@ -59,7 +56,7 @@ object Results { * @param rs A `ResultSet` set to the row with the count to retrieve * @return The count from the row */ - fun toCount(rs: ResultSet, clazz: Class = Long::class.java) = + fun toCount(rs: ResultSet) = when (Configuration.dialect()) { Dialect.POSTGRESQL -> rs.getInt("it").toLong() Dialect.SQLITE -> rs.getLong("it") @@ -71,7 +68,7 @@ object Results { * @param rs A `ResultSet` set to the row with the true/false value to retrieve * @return The true/false value from the row */ - fun toExists(rs: ResultSet, clazz: Class = Boolean::class.java) = + fun toExists(rs: ResultSet) = when (Configuration.dialect()) { Dialect.POSTGRESQL -> rs.getBoolean("it") Dialect.SQLITE -> toCount(rs) > 0L diff --git a/src/kotlin/src/main/kotlin/ConnectionExtensions.kt b/src/kotlin/src/main/kotlin/extensions/Connection.kt similarity index 94% rename from src/kotlin/src/main/kotlin/ConnectionExtensions.kt rename to src/kotlin/src/main/kotlin/extensions/Connection.kt index 599cb22..d02e85b 100644 --- a/src/kotlin/src/main/kotlin/ConnectionExtensions.kt +++ b/src/kotlin/src/main/kotlin/extensions/Connection.kt @@ -1,10 +1,7 @@ -package solutions.bitbadger.documents.kotlin +package solutions.bitbadger.documents.kotlin.extensions -import solutions.bitbadger.documents.DocumentIndex -import solutions.bitbadger.documents.Field -import solutions.bitbadger.documents.FieldMatch -import solutions.bitbadger.documents.Parameter -import solutions.bitbadger.documents.java.jvm.Document +import solutions.bitbadger.documents.* +import solutions.bitbadger.documents.kotlin.* import java.sql.Connection import java.sql.ResultSet @@ -18,8 +15,8 @@ import java.sql.ResultSet * @param mapFunc The mapping function between the document and the domain item * @return A list of results for the given query */ -inline fun Connection.customList( - query: String, parameters: Collection> = listOf(), noinline mapFunc: (ResultSet, Class) -> TDoc +inline fun Connection.customList( + query: String, parameters: Collection> = listOf(), mapFunc: (ResultSet) -> TDoc ) = Custom.list(query, parameters, this, mapFunc) /** @@ -30,8 +27,8 @@ inline fun Connection.customList( * @param mapFunc The mapping function between the document and the domain item * @return The document if one matches the query, `null` otherwise */ -inline fun Connection.customSingle( - query: String, parameters: Collection> = listOf(), noinline mapFunc: (ResultSet, Class) -> TDoc +inline fun Connection.customSingle( + query: String, parameters: Collection> = listOf(), mapFunc: (ResultSet) -> TDoc ) = Custom.single(query, parameters, this, mapFunc) /** @@ -54,7 +51,7 @@ fun Connection.customNonQuery(query: String, parameters: Collection inline fun Connection.customScalar( query: String, parameters: Collection> = listOf(), - noinline mapFunc: (ResultSet, Class) -> T + mapFunc: (ResultSet) -> T ) = Custom.scalar(query, parameters, this, mapFunc) // ~~~ DEFINITION QUERIES ~~~ @@ -215,7 +212,7 @@ fun Connection.existsByJsonPath(tableName: String, path: String) = * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) * @return A list of documents from the given table */ -inline fun Connection.findAll(tableName: String, orderBy: Collection>? = null) = +inline fun Connection.findAll(tableName: String, orderBy: Collection>? = null) = Find.all(tableName, orderBy, this) /** @@ -225,7 +222,7 @@ inline fun Connection.findAll(tableName: String, orderBy: Collect * @param docId The ID of the document to retrieve * @return The document if it is found, `null` otherwise */ -inline fun Connection.findById(tableName: String, docId: TKey) = +inline fun Connection.findById(tableName: String, docId: TKey) = Find.byId(tableName, docId, this) /** @@ -237,7 +234,7 @@ inline fun Connection.findById(tableName: String, docId: TK * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) * @return A list of documents matching the field comparison */ -inline fun Connection.findByFields( +inline fun Connection.findByFields( tableName: String, fields: Collection>, howMatched: FieldMatch? = null, @@ -254,7 +251,7 @@ inline fun Connection.findByFields( * @return A list of documents matching the JSON containment query * @throws DocumentException If called on a SQLite connection */ -inline fun Connection.findByContains( +inline fun Connection.findByContains( tableName: String, criteria: TContains, orderBy: Collection>? = null @@ -270,7 +267,7 @@ inline fun Connection.findByContains( * @return A list of documents matching the JSON Path match query * @throws DocumentException If called on a SQLite connection */ -inline fun Connection.findByJsonPath( +inline fun Connection.findByJsonPath( tableName: String, path: String, orderBy: Collection>? = null @@ -286,7 +283,7 @@ inline fun Connection.findByJsonPath( * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering) * @return The first document matching the field comparison, or `null` if no matches are found */ -inline fun Connection.findFirstByFields( +inline fun Connection.findFirstByFields( tableName: String, fields: Collection>, howMatched: FieldMatch? = null, @@ -303,7 +300,7 @@ inline fun Connection.findFirstByFields( * @return The first document matching the JSON containment query, or `null` if no matches are found * @throws DocumentException If called on a SQLite connection */ -inline fun Connection.findFirstByContains( +inline fun Connection.findFirstByContains( tableName: String, criteria: TContains, orderBy: Collection>? = null @@ -319,7 +316,7 @@ inline fun Connection.findFirstByContains( * @return The first document matching the JSON Path match query, or `null` if no matches are found * @throws DocumentException If called on a SQLite connection */ -inline fun Connection.findFirstByJsonPath( +inline fun Connection.findFirstByJsonPath( tableName: String, path: String, orderBy: Collection>? = null diff --git a/src/pom.xml b/src/pom.xml index 96aa6fc..27748e6 100644 --- a/src/pom.xml +++ b/src/pom.xml @@ -46,6 +46,7 @@ jvm + kotlin