diff --git a/.gitignore b/.gitignore
index 0296a22..7fea862 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,3 +26,9 @@ replay_pid*
# Kotlin Gradle plugin data, see https://kotlinlang.org/docs/whatsnew20.html#new-directory-for-kotlin-data-in-gradle-projects
.kotlin/
+
+# Temporary output directories
+**/target
+
+# Maven Central Repo settings
+settings.xml
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..919ce1f
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..a55e7a1
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..8788ef7
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000..66a447d
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
new file mode 100644
index 0000000..b301a31
--- /dev/null
+++ b/.idea/jarRepositories.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
new file mode 100644
index 0000000..d1e0db8
--- /dev/null
+++ b/.idea/kotlinc.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/KotlinJavaRuntime.xml b/.idea/libraries/KotlinJavaRuntime.xml
new file mode 100644
index 0000000..cf8a559
--- /dev/null
+++ b/.idea/libraries/KotlinJavaRuntime.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/Maven__scala_sdk_3_0_0.xml b/.idea/libraries/Maven__scala_sdk_3_0_0.xml
new file mode 100644
index 0000000..d649b7c
--- /dev/null
+++ b/.idea/libraries/Maven__scala_sdk_3_0_0.xml
@@ -0,0 +1,26 @@
+
+
+
+ Scala_3_0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ file://$MAVEN_REPOSITORY$/org/scala-lang/scala3-sbt-bridge/3.0.0/scala3-sbt-bridge-3.0.0.jar
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/Maven__scala_sdk_3_1_3.xml b/.idea/libraries/Maven__scala_sdk_3_1_3.xml
new file mode 100644
index 0000000..17f32de
--- /dev/null
+++ b/.idea/libraries/Maven__scala_sdk_3_1_3.xml
@@ -0,0 +1,26 @@
+
+
+
+ Scala_3_1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ file://$MAVEN_REPOSITORY$/org/scala-lang/scala3-sbt-bridge/3.1.3/scala3-sbt-bridge-3.1.3.jar
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/Maven__scala_sdk_3_3_3.xml b/.idea/libraries/Maven__scala_sdk_3_3_3.xml
new file mode 100644
index 0000000..a753719
--- /dev/null
+++ b/.idea/libraries/Maven__scala_sdk_3_3_3.xml
@@ -0,0 +1,25 @@
+
+
+
+ Scala_3_3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ file://$MAVEN_REPOSITORY$/org/scala-lang/scala3-sbt-bridge/3.3.3/scala3-sbt-bridge-3.3.3.jar
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/Maven__scala_sdk_3_5_2.xml b/.idea/libraries/Maven__scala_sdk_3_5_2.xml
new file mode 100644
index 0000000..edaffc7
--- /dev/null
+++ b/.idea/libraries/Maven__scala_sdk_3_5_2.xml
@@ -0,0 +1,26 @@
+
+
+
+ Scala_3_5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ file://$MAVEN_REPOSITORY$/org/scala-lang/scala3-sbt-bridge/3.5.2/scala3-sbt-bridge-3.5.2.jar
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..85ecaa5
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/scala_compiler.xml b/.idea/scala_compiler.xml
new file mode 100644
index 0000000..96c87c5
--- /dev/null
+++ b/.idea/scala_compiler.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/scala_settings.xml b/.idea/scala_settings.xml
new file mode 100644
index 0000000..4608fe0
--- /dev/null
+++ b/.idea/scala_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 78c5f33..dac2e29 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,53 @@
# solutions.bitbadger.documents
-Treat PostgreSQL and SQLite as document stores from Java and Kotlin
\ No newline at end of file
+Treat PostgreSQL and SQLite as document stores from Java, Kotlin, Scala, and Groovy
+
+## Examples
+
+```java
+// Retrieve (find) all orders (Java)
+public List findOrders(Connection conn) {
+ Find.all(/*table name*/ "order", /*type*/ Order.class, conn);
+}
+```
+
+```kotlin
+// Mark an order as fulfilled (Kotlin)
+fun markFulfilled(orderId: Long, conn: Connection) =
+ conn.patchById(
+ /*table name*/ "order",
+ /*document ID*/ orderId,
+ /*patch object*/ mapOf("fulfilled" to true)
+ )
+```
+
+```scala
+// Delete orders marked as obsolete (Scala)
+def deleteObsolete(Connection conn):
+ conn.deleteByFields(/*table name*/ "order",
+ /*field criteria*/ Field.equal("obsolete", true) :: Nil)
+```
+
+```groovy
+// Remove the pending status from multiple orders (Groovy)
+void clearPending(List orderIds, Connection conn) {
+ conn.removeFieldsByFields(/*table name*/ "order",
+ /*field criteria*/ List.of(Field.any("id", orderIds)),
+ /*fields to remove*/ List.of("pending"))
+}
+```
+
+## Packages / Modules
+
+* The `core` module provides the base implementation and can be used from any JVM language.
+ * The `solutions.bitbadger.documents` package contains support types like `Configuration` and `Field`.
+ * The `solutions.bitbadger.documents.java` package contains document access functions and serialization config.
+ * The `solutions.bitbadger.documents.java.extensions` package contains extensions on the JDBC `Connection` object, callable as extension methods from Kotlin or as static functions from other languages.
+
+* The `groovy` module packages the extension methods so that Groovy can access them. No other packages will need to be imported; they will show up on any `Connection` instance.
+
+* The `kotlinx` module utilizes the kotlinx-serialization project for its JSON serialization, which requires a different serializer and different function/method signatures (`inline fun ...` vs. `fun ...`).
+ * `solutions.bitbadger.documents.kotlinx` and `solutions.bitbadger.documents.kotlinx.extensions` packages expose a similar API to their `java` counterparts, but one designed to be consumed from Kotlin. Generally, document retrieval functions will require a generic parameter instead of a `Class` parameter.
+
+* The `scala` module extends `core` by utilizing Scala's implicit `ClassTag`s to remove the `Class[T]` parameter.
+ * `solutions.bitbadger.documents.scala` and `solutions.bitbadger.documents.scala.extensions` packages expose the same API as their `java` counterparts, utilizing Scala collections and `Option`s instead of Java collections and `Optional`s.
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..eb24808
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,117 @@
+
+
+ 4.0.0
+
+ solutions.bitbadger
+ documents
+ 1.0.0-RC1
+ pom
+
+ ${project.groupId}:${project.artifactId}
+ Expose a document store interface for PostgreSQL and SQLite
+ https://relationaldocs.bitbadger.solutions/jvm/
+
+
+
+ MIT License
+ https://www.opensource.org/licenses/mit-license.php
+
+
+
+
+
+ Daniel J. Summers
+ daniel@bitbadger.solutions
+ Bit Badger Solutions
+ https://bitbadger.solutions
+
+
+
+
+ scm:git:https://git.bitbadger.solutions/bit-badger/solutions.bitbadger.documents.git
+ scm:git:https://git.bitbadger.solutions/bit-badger/solutions.bitbadger.documents.git
+ https://git.bitbadger.solutions/bit-badger/solutions.bitbadger.documents
+
+
+
+ UTF-8
+ official
+ 17
+ ${java.version}
+ true
+ 2.1.20
+ 1.8.0
+ 3.5.2
+ 4.0.26
+ 3.5.2
+ 3.5.2
+ 2.18.2
+ 3.46.1.2
+ 42.7.5
+ 3.6.0
+ 3.3.1
+ 3.11.2
+
+
+
+ ./src/core
+ ./src/groovy
+ ./src/kotlinx
+ ./src/scala
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+ 3.2.6
+
+
+ sign-artifacts
+ verify
+
+ sign
+
+
+
+
+
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ 5.10.0
+ test
+
+
+ org.jetbrains.kotlin
+ kotlin-test-junit5
+ ${kotlin.version}
+ test
+
+
+ org.xerial
+ sqlite-jdbc
+ ${sqlite.version}
+ test
+
+
+ org.slf4j
+ slf4j-simple
+ 2.0.16
+ test
+
+
+ org.postgresql
+ postgresql
+ ${postgresql.version}
+ test
+
+
+
+
\ No newline at end of file
diff --git a/solutions.bitbadger.documents.iml b/solutions.bitbadger.documents.iml
new file mode 100644
index 0000000..9a5cfce
--- /dev/null
+++ b/solutions.bitbadger.documents.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/core/core.iml b/src/core/core.iml
new file mode 100644
index 0000000..2feb230
--- /dev/null
+++ b/src/core/core.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/core/pom.xml b/src/core/pom.xml
new file mode 100644
index 0000000..63f0086
--- /dev/null
+++ b/src/core/pom.xml
@@ -0,0 +1,182 @@
+
+
+ 4.0.0
+
+ solutions.bitbadger
+ documents
+ 1.0.0-RC1
+ ../../pom.xml
+
+
+ solutions.bitbadger.documents
+ core
+
+ ${project.groupId}:${project.artifactId}
+ Expose a document store interface for PostgreSQL and SQLite (Core Library)
+ https://relationaldocs.bitbadger.solutions/jvm/
+
+
+
+ MIT License
+ https://www.opensource.org/licenses/mit-license.php
+
+
+
+
+
+ Daniel J. Summers
+ daniel@bitbadger.solutions
+ Bit Badger Solutions
+ https://bitbadger.solutions
+
+
+
+
+ scm:git:https://git.bitbadger.solutions/bit-badger/solutions.bitbadger.documents.git
+ scm:git:https://git.bitbadger.solutions/bit-badger/solutions.bitbadger.documents.git
+ https://git.bitbadger.solutions/bit-badger/solutions.bitbadger.documents
+
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+ ${kotlin.version}
+
+
+ compile
+ process-sources
+
+ compile
+
+
+
+ ${project.basedir}/src/main/java
+ ${project.basedir}/src/main/kotlin
+
+
+
+
+ test-compile
+ process-test-sources
+
+ test-compile
+
+
+
+ ${project.basedir}/src/test/java
+ ${project.basedir}/src/test/kotlin
+
+
+
+
+
+
+ maven-surefire-plugin
+ ${surefire.version}
+
+
+ maven-failsafe-plugin
+ ${failsafe.version}
+
+
+
+ integration-test
+ verify
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.13.0
+
+ ${java.version}
+ ${java.version}
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+ ${buildHelperPlugin.version}
+
+
+ generate-sources
+
+ add-source
+
+
+
+ src/main/kotlin
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ ${sourcePlugin.version}
+
+
+ attach-sources
+
+ jar-no-fork
+
+
+
+
+
+ org.jetbrains.dokka
+ dokka-maven-plugin
+ 2.0.0
+
+ true
+ ${project.basedir}/src/main/module-info.md
+
+
+
+ package
+
+ javadocJar
+
+
+
+
+
+ org.sonatype.central
+ central-publishing-maven-plugin
+ 0.7.0
+ true
+
+ Deployment-core-${project.version}
+ central
+
+
+
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib
+ ${kotlin.version}
+
+
+ org.jetbrains.kotlin
+ kotlin-reflect
+ ${kotlin.version}
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${jackson.version}
+ test
+
+
+
+
\ No newline at end of file
diff --git a/src/core/src/main/java/module-info.java b/src/core/src/main/java/module-info.java
new file mode 100644
index 0000000..63cd2b9
--- /dev/null
+++ b/src/core/src/main/java/module-info.java
@@ -0,0 +1,9 @@
+module solutions.bitbadger.documents.core {
+ requires kotlin.stdlib;
+ requires kotlin.reflect;
+ requires java.sql;
+ exports solutions.bitbadger.documents;
+ exports solutions.bitbadger.documents.java;
+ exports solutions.bitbadger.documents.java.extensions;
+ exports solutions.bitbadger.documents.query;
+}
diff --git a/src/core/src/main/kotlin/AutoId.kt b/src/core/src/main/kotlin/AutoId.kt
new file mode 100644
index 0000000..f94d7da
--- /dev/null
+++ b/src/core/src/main/kotlin/AutoId.kt
@@ -0,0 +1,85 @@
+package solutions.bitbadger.documents
+
+import kotlin.jvm.Throws
+import kotlin.reflect.full.*
+import kotlin.reflect.jvm.isAccessible
+
+/**
+ * Strategies for automatic document IDs
+ */
+enum class AutoId {
+ /** No automatic IDs will be generated */
+ DISABLED,
+
+ /** Generate a `MAX`-plus-1 numeric ID */
+ NUMBER,
+
+ /** Generate a `UUID` string ID */
+ UUID,
+
+ /** Generate a random hex character string ID */
+ RANDOM_STRING;
+
+ companion object {
+
+ /**
+ * Generate a `UUID` string
+ *
+ * @return A `UUID` string
+ */
+ @JvmStatic
+ fun generateUUID(): String =
+ java.util.UUID.randomUUID().toString().replace("-", "")
+
+ /**
+ * Generate a string of random hex characters
+ *
+ * @param length The length of the string (optional; defaults to configured length)
+ * @return A string of random hex characters of the requested length
+ */
+ @JvmStatic
+ fun generateRandomString(length: Int? = null): String =
+ (length ?: Configuration.idStringLength).let { len ->
+ kotlin.random.Random.nextBytes((len + 2) / 2)
+ .joinToString("") { String.format("%02x", it) }
+ .substring(0, len)
+ }
+
+ /**
+ * Determine if a document needs an automatic ID applied
+ *
+ * @param strategy The auto ID strategy for which the document is evaluated
+ * @param document The document whose need of an automatic ID should be determined
+ * @param idProp The name of the document property containing the ID
+ * @return `true` if the document needs an automatic ID, `false` if not
+ * @throws DocumentException If bad input prevents the determination
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun needsAutoId(strategy: AutoId, document: T, idProp: String): Boolean {
+ if (document == null) throw DocumentException("document cannot be null")
+
+ if (strategy == DISABLED) return false
+
+ val id = document!!::class.memberProperties.find { it.name == idProp }?.apply { isAccessible = true }
+ if (id == null) throw DocumentException("$idProp not found in document")
+
+ if (strategy == NUMBER) {
+ return when (id.returnType) {
+ Byte::class.createType() -> id.call(document) == 0.toByte()
+ Short::class.createType() -> id.call(document) == 0.toShort()
+ Int::class.createType() -> id.call(document) == 0
+ Long::class.createType() -> id.call(document) == 0.toLong()
+ else -> throw DocumentException("$idProp was not a number; cannot auto-generate number ID")
+ }
+ }
+
+ val typ = id.returnType.toString()
+ if (typ.endsWith("String") || typ.endsWith("String!")) {
+ return id.call(document) == ""
+ }
+
+ throw DocumentException("$idProp was not a string ($typ); cannot auto-generate UUID or random string")
+ }
+ }
+}
diff --git a/src/core/src/main/kotlin/Comparison.kt b/src/core/src/main/kotlin/Comparison.kt
new file mode 100644
index 0000000..f187882
--- /dev/null
+++ b/src/core/src/main/kotlin/Comparison.kt
@@ -0,0 +1,68 @@
+package solutions.bitbadger.documents
+
+/**
+ * Information required to generate a JSON field comparison
+ */
+interface Comparison {
+
+ /** The operation for the field comparison */
+ val op: Op
+
+ /** The value against which the comparison will be made */
+ val value: T
+
+ /** Whether the value should be considered numeric */
+ val isNumeric: Boolean
+}
+
+/**
+ * Function to determine if a value is numeric
+ *
+ * @param it The value in question
+ * @return True if it is a numeric type, false if not
+ */
+private fun isNumeric(it: T) =
+ it is Byte || it is Short || it is Int || it is Long
+
+/**
+ * A single-value comparison against a field in a JSON document
+ */
+class ComparisonSingle(override val op: Op, override val value: T) : Comparison {
+
+ init {
+ when (op) {
+ Op.BETWEEN, Op.IN, Op.IN_ARRAY ->
+ throw DocumentException("Cannot use single comparison for multiple values")
+ else -> { }
+ }
+ }
+
+ override val isNumeric = isNumeric(value)
+
+ override fun toString() =
+ "$op $value"
+}
+
+/**
+ * A range comparison against a field in a JSON document
+ */
+class ComparisonBetween(override val value: Pair) : Comparison> {
+ override val op = Op.BETWEEN
+ override val isNumeric = isNumeric(value.first)
+}
+
+/**
+ * A check within a collection of values
+ */
+class ComparisonIn(override val value: Collection) : Comparison> {
+ override val op = Op.IN
+ override val isNumeric = !value.isEmpty() && isNumeric(value.elementAt(0))
+}
+
+/**
+ * A check within a collection of values against an array in a document
+ */
+class ComparisonInArray(override val value: Pair>) : Comparison>> {
+ override val op = Op.IN_ARRAY
+ override val isNumeric = false
+}
diff --git a/src/core/src/main/kotlin/Configuration.kt b/src/core/src/main/kotlin/Configuration.kt
new file mode 100644
index 0000000..2aa3228
--- /dev/null
+++ b/src/core/src/main/kotlin/Configuration.kt
@@ -0,0 +1,67 @@
+package solutions.bitbadger.documents
+
+import java.sql.Connection
+import java.sql.DriverManager
+
+/**
+ * Configuration for the document library
+ */
+object Configuration {
+
+ /** The field in which a document's ID is stored */
+ @JvmField
+ var idField = "id"
+
+ /** The automatic ID strategy to use */
+ @JvmField
+ var autoIdStrategy = AutoId.DISABLED
+
+ /** The length of automatic random hex character string */
+ @JvmField
+ var idStringLength = 16
+
+ /** The derived dialect value from the connection string */
+ private var dialectValue: Dialect? = null
+
+ /** The connection string for the JDBC connection */
+ @JvmStatic
+ var connectionString: String? = null
+ /**
+ * Set a value for the connection string
+ * @param value The connection string to set
+ */
+ set(value) {
+ field = value
+ dialectValue = if (value.isNullOrBlank()) null else Dialect.deriveFromConnectionString(value)
+ }
+
+ /**
+ * Retrieve a new connection to the configured database
+ *
+ * @return A new connection to the configured database
+ * @throws DocumentException If the connection string is not set before calling this
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun dbConn(): Connection {
+ if (connectionString == null) {
+ throw DocumentException("Please provide a connection string before attempting data access")
+ }
+ return DriverManager.getConnection(connectionString)
+ }
+
+ /**
+ * The dialect in use
+ *
+ * @param process The process being attempted
+ * @return The dialect for the current connection
+ * @throws DocumentException If the dialect has not been set
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ @JvmOverloads
+ fun dialect(process: String? = null): Dialect =
+ dialectValue ?: throw DocumentException(
+ "Database mode not set" + if (process == null) "" else "; cannot $process"
+ )
+}
diff --git a/src/core/src/main/kotlin/Dialect.kt b/src/core/src/main/kotlin/Dialect.kt
new file mode 100644
index 0000000..1d0c32b
--- /dev/null
+++ b/src/core/src/main/kotlin/Dialect.kt
@@ -0,0 +1,33 @@
+package solutions.bitbadger.documents
+
+import kotlin.jvm.Throws
+
+/**
+ * The SQL dialect to use when building queries
+ */
+enum class Dialect {
+ /** PostgreSQL */
+ POSTGRESQL,
+
+ /** SQLite */
+ SQLITE;
+
+ companion object {
+
+ /**
+ * Derive the dialect from the given connection string
+ *
+ * @param connectionString The connection string from which the dialect will be derived
+ * @return The dialect for the connection string
+ * @throws DocumentException If the dialect cannot be determined
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun deriveFromConnectionString(connectionString: String): Dialect =
+ when {
+ connectionString.contains(":sqlite:") -> SQLITE
+ connectionString.contains(":postgresql:") -> POSTGRESQL
+ else -> throw DocumentException("Cannot determine dialect from [$connectionString]")
+ }
+ }
+}
diff --git a/src/core/src/main/kotlin/DocumentException.kt b/src/core/src/main/kotlin/DocumentException.kt
new file mode 100644
index 0000000..d415801
--- /dev/null
+++ b/src/core/src/main/kotlin/DocumentException.kt
@@ -0,0 +1,9 @@
+package solutions.bitbadger.documents
+
+/**
+ * An exception caused by invalid operations in the document library
+ *
+ * @param message The message for the exception
+ * @param cause The underlying exception (optional)
+ */
+class DocumentException @JvmOverloads constructor(message: String, cause: Throwable? = null) : Exception(message, cause)
diff --git a/src/core/src/main/kotlin/DocumentIndex.kt b/src/core/src/main/kotlin/DocumentIndex.kt
new file mode 100644
index 0000000..3ce8743
--- /dev/null
+++ b/src/core/src/main/kotlin/DocumentIndex.kt
@@ -0,0 +1,13 @@
+package solutions.bitbadger.documents
+
+/**
+ * The type of index to generate for the document
+ */
+enum class DocumentIndex(val sql: String) {
+
+ /** A GIN index with standard operations (all operators supported) */
+ FULL(""),
+
+ /** A GIN index with JSONPath operations (optimized for @>, @?, @@ operators) */
+ OPTIMIZED(" jsonb_path_ops")
+}
diff --git a/src/core/src/main/kotlin/DocumentSerializer.kt b/src/core/src/main/kotlin/DocumentSerializer.kt
new file mode 100644
index 0000000..bbdee39
--- /dev/null
+++ b/src/core/src/main/kotlin/DocumentSerializer.kt
@@ -0,0 +1,24 @@
+package solutions.bitbadger.documents
+
+/**
+ * The interface for a document serializer/deserializer
+ */
+interface DocumentSerializer {
+
+ /**
+ * Serialize a document to its JSON representation
+ *
+ * @param document The document to be serialized
+ * @return The JSON representation of the document
+ */
+ fun serialize(document: TDoc): String
+
+ /**
+ * Deserialize a document from its JSON representation
+ *
+ * @param json The JSON representation of the document
+ * @param clazz The class of the document to be deserialized
+ * @return The document instance represented by the given JSON string
+ */
+ fun deserialize(json: String, clazz: Class): TDoc
+}
diff --git a/src/core/src/main/kotlin/Field.kt b/src/core/src/main/kotlin/Field.kt
new file mode 100644
index 0000000..c4d017a
--- /dev/null
+++ b/src/core/src/main/kotlin/Field.kt
@@ -0,0 +1,320 @@
+package solutions.bitbadger.documents
+
+import kotlin.jvm.Throws
+
+/**
+ * A field and its comparison
+ *
+ * @property name The name of the field in the JSON document
+ * @property comparison The comparison to apply against the field
+ * @property parameterName The name of the parameter to use in the query (optional, generated if missing)
+ * @property qualifier A table qualifier to use to address the `data` field (useful for multi-table queries)
+ */
+class Field private constructor(
+ val name: String,
+ val comparison: Comparison,
+ val parameterName: String? = null,
+ val qualifier: String? = null) {
+
+ init {
+ if (parameterName != null && !parameterName.startsWith(':') && !parameterName.startsWith('@'))
+ throw DocumentException("Parameter Name must start with : or @ ($name)")
+ }
+
+ /**
+ * Specify the parameter name for the field
+ *
+ * @param paramName The parameter name to use for this field
+ * @return A new `Field` with the parameter name specified
+ */
+ fun withParameterName(paramName: String) =
+ Field(name, comparison, paramName, qualifier)
+
+ /**
+ * Specify a qualifier (alias) for the document table
+ *
+ * @param alias The table alias for this field
+ * @return A new `Field` with the table qualifier specified
+ */
+ fun withQualifier(alias: String) =
+ Field(name, comparison, parameterName, alias)
+
+ /**
+ * Get the path for this field
+ *
+ * @param dialect The SQL dialect to use for the path to the JSON field
+ * @param format Whether the value should be retrieved as JSON or SQL (optional, default SQL)
+ * @return The path for the field
+ */
+ @JvmOverloads
+ fun path(dialect: Dialect, format: FieldFormat = FieldFormat.SQL): String =
+ (if (qualifier == null) "" else "${qualifier}.") + nameToPath(name, dialect, format)
+
+ /** Parameters to bind each value of `IN` and `IN_ARRAY` operations */
+ private val inParameterNames: String
+ get() {
+ val values = if (comparison.op == Op.IN) {
+ comparison.value as Collection<*>
+ } else {
+ val parts = comparison.value as Pair<*, *>
+ parts.second as Collection<*>
+ }
+ return List(values.size) { idx -> "${parameterName}_$idx" }.joinToString(", ")
+ }
+
+ /**
+ * Create a `WHERE` clause fragment for this field
+ *
+ * @return The `WHERE` clause for this field
+ * @throws DocumentException If the field has no parameter name or the database dialect has not been set
+ */
+ @Throws(DocumentException::class)
+ fun toWhere(): String {
+ if (parameterName == null && !listOf(Op.EXISTS, Op.NOT_EXISTS).contains(comparison.op))
+ throw DocumentException("Parameter for $name must be specified")
+
+ val dialect = Configuration.dialect("make field WHERE clause")
+ val fieldName = path(dialect, if (comparison.op == Op.IN_ARRAY) FieldFormat.JSON else FieldFormat.SQL)
+ val fieldPath = when (dialect) {
+ Dialect.POSTGRESQL -> if (comparison.isNumeric) "($fieldName)::numeric" else fieldName
+ Dialect.SQLITE -> fieldName
+ }
+ val criteria = when (comparison.op) {
+ in listOf(Op.EXISTS, Op.NOT_EXISTS) -> ""
+ Op.BETWEEN -> " ${parameterName}min AND ${parameterName}max"
+ Op.IN -> " ($inParameterNames)"
+ Op.IN_ARRAY -> if (dialect == Dialect.POSTGRESQL) " ARRAY[$inParameterNames]" else ""
+ else -> " $parameterName"
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ return if (dialect == Dialect.SQLITE && comparison.op == Op.IN_ARRAY) {
+ val (table, _) = comparison.value as? Pair ?: throw DocumentException("InArray field invalid")
+ "EXISTS (SELECT 1 FROM json_each($table.data, '$.$name') WHERE value IN ($inParameterNames))"
+ } else {
+ "$fieldPath ${comparison.op.sql}$criteria"
+ }
+ }
+
+ /**
+ * Append the parameters required for this field
+ *
+ * @param existing The existing parameters
+ * @return The collection with the necessary parameters appended
+ */
+ fun appendParameter(existing: MutableCollection>): MutableCollection> {
+ val typ = if (comparison.isNumeric) ParameterType.NUMBER else ParameterType.STRING
+ when (comparison) {
+ is ComparisonBetween<*> -> {
+ existing.add(Parameter("${parameterName}min", typ, comparison.value.first))
+ existing.add(Parameter("${parameterName}max", typ, comparison.value.second))
+ }
+
+ is ComparisonIn<*> -> {
+ comparison.value.forEachIndexed { index, item ->
+ existing.add(Parameter("${parameterName}_$index", typ, item))
+ }
+ }
+
+ is ComparisonInArray<*> -> {
+ val mkString = Configuration.dialect("append parameters for InArray") == Dialect.POSTGRESQL
+ comparison.value.second.forEachIndexed { index, item ->
+ if (mkString) {
+ existing.add(Parameter("${parameterName}_$index", ParameterType.STRING, "$item"))
+ } else {
+ existing.add(Parameter("${parameterName}_$index", typ, item))
+ }
+ }
+ }
+
+ else -> {
+ if (comparison.op != Op.EXISTS && comparison.op != Op.NOT_EXISTS) {
+ existing.add(Parameter(parameterName!!, typ, comparison.value))
+ }
+ }
+ }
+ return existing
+ }
+
+ override fun toString() =
+ "Field ${parameterName ?: ""} $comparison${qualifier?.let { " (qualifier $it)" } ?: ""}"
+
+ companion object {
+
+ /**
+ * Create a field equality comparison
+ *
+ * @param name The name of the field to be compared
+ * @param value The value for the comparison
+ * @param paramName The parameter name for the field (optional, defaults to auto-generated)
+ * @return A `Field` with the given comparison
+ */
+ @JvmStatic
+ @JvmOverloads
+ fun equal(name: String, value: T, paramName: String? = null) =
+ Field(name, ComparisonSingle(Op.EQUAL, value), paramName)
+
+ /**
+ * Create a field greater-than comparison
+ *
+ * @param name The name of the field to be compared
+ * @param value The value for the comparison
+ * @param paramName The parameter name for the field (optional, defaults to auto-generated)
+ * @return A `Field` with the given comparison
+ */
+ @JvmStatic
+ @JvmOverloads
+ fun greater(name: String, value: T, paramName: String? = null) =
+ Field(name, ComparisonSingle(Op.GREATER, value), paramName)
+
+ /**
+ * Create a field greater-than-or-equal-to comparison
+ *
+ * @param name The name of the field to be compared
+ * @param value The value for the comparison
+ * @param paramName The parameter name for the field (optional, defaults to auto-generated)
+ * @return A `Field` with the given comparison
+ */
+ @JvmStatic
+ @JvmOverloads
+ fun greaterOrEqual(name: String, value: T, paramName: String? = null) =
+ Field(name, ComparisonSingle(Op.GREATER_OR_EQUAL, value), paramName)
+
+ /**
+ * Create a field less-than comparison
+ *
+ * @param name The name of the field to be compared
+ * @param value The value for the comparison
+ * @param paramName The parameter name for the field (optional, defaults to auto-generated)
+ * @return A `Field` with the given comparison
+ */
+ @JvmStatic
+ @JvmOverloads
+ fun less(name: String, value: T, paramName: String? = null) =
+ Field(name, ComparisonSingle(Op.LESS, value), paramName)
+
+ /**
+ * Create a field less-than-or-equal-to comparison
+ *
+ * @param name The name of the field to be compared
+ * @param value The value for the comparison
+ * @param paramName The parameter name for the field (optional, defaults to auto-generated)
+ * @return A `Field` with the given comparison
+ */
+ @JvmStatic
+ @JvmOverloads
+ fun lessOrEqual(name: String, value: T, paramName: String? = null) =
+ Field(name, ComparisonSingle(Op.LESS_OR_EQUAL, value), paramName)
+
+ /**
+ * Create a field inequality comparison
+ *
+ * @param name The name of the field to be compared
+ * @param value The value for the comparison
+ * @param paramName The parameter name for the field (optional, defaults to auto-generated)
+ * @return A `Field` with the given comparison
+ */
+ @JvmStatic
+ @JvmOverloads
+ fun notEqual(name: String, value: T, paramName: String? = null) =
+ Field(name, ComparisonSingle(Op.NOT_EQUAL, value), paramName)
+
+ /**
+ * Create a field range comparison
+ *
+ * @param name The name of the field to be compared
+ * @param minValue The lower value for the comparison
+ * @param maxValue The upper value for the comparison
+ * @param paramName The parameter name for the field (optional, defaults to auto-generated)
+ * @return A `Field` with the given comparison
+ */
+ @JvmStatic
+ @JvmOverloads
+ fun between(name: String, minValue: T, maxValue: T, paramName: String? = null) =
+ Field(name, ComparisonBetween(Pair(minValue, maxValue)), paramName)
+
+ /**
+ * Create a field where any values match (SQL `IN`)
+ *
+ * @param name The name of the field to be compared
+ * @param values The values for the comparison
+ * @param paramName The parameter name for the field (optional, defaults to auto-generated)
+ * @return A `Field` with the given comparison
+ */
+ @JvmStatic
+ @JvmOverloads
+ fun any(name: String, values: Collection, paramName: String? = null) =
+ Field(name, ComparisonIn(values), paramName)
+
+ /**
+ * Create a field where values should exist in a document's array
+ *
+ * @param name The name of the field to be compared
+ * @param tableName The name of the document table
+ * @param values The values for the comparison
+ * @param paramName The parameter name for the field (optional, defaults to auto-generated)
+ * @return A `Field` with the given comparison
+ */
+ @JvmStatic
+ @JvmOverloads
+ fun inArray(name: String, tableName: String, values: Collection, paramName: String? = null) =
+ Field(name, ComparisonInArray(Pair(tableName, values)), paramName)
+
+ /**
+ * Create a field where a document field should exist
+ *
+ * @param name The name of the field whose existence should be checked
+ * @return A `Field` with the given comparison
+ */
+ @JvmStatic
+ fun exists(name: String) =
+ Field(name, ComparisonSingle(Op.EXISTS, ""))
+
+ /**
+ * Create a field where a document field should not exist
+ *
+ * @param name The name of the field whose existence should be checked
+ * @return A `Field` with the given comparison
+ */
+ @JvmStatic
+ fun notExists(name: String) =
+ Field(name, ComparisonSingle(Op.NOT_EXISTS, ""))
+
+ /**
+ * Create a field with a given named comparison (useful for ordering fields)
+ *
+ * @param name The name of the field
+ * @return A `Field` with the given name (comparison equal to an empty string)
+ */
+ @JvmStatic
+ fun named(name: String) =
+ Field(name, ComparisonSingle(Op.EQUAL, ""))
+
+ /**
+ * Convert a name to the SQL path for the given dialect
+ *
+ * @param name The field name to be translated
+ * @param dialect The database for which the path should be created
+ * @param format Whether the field should be retrieved as a JSON value or a SQL value
+ * @return The path to the JSON field
+ */
+ @JvmStatic
+ fun nameToPath(name: String, dialect: Dialect, format: FieldFormat): String {
+ val path = StringBuilder("data")
+ val extra = if (format == FieldFormat.SQL) ">" else ""
+ if (name.indexOf('.') > -1) {
+ if (dialect == Dialect.POSTGRESQL) {
+ path.append("#>", extra, "'{", name.replace('.', ','), "}'")
+ } else {
+ val names = name.split('.').toMutableList()
+ val last = names.removeLast()
+ names.forEach { path.append("->'", it, "'") }
+ path.append("->", extra, "'", last, "'")
+ }
+ } else {
+ path.append("->", extra, "'", name, "'")
+ }
+ return path.toString()
+ }
+ }
+}
diff --git a/src/core/src/main/kotlin/FieldFormat.kt b/src/core/src/main/kotlin/FieldFormat.kt
new file mode 100644
index 0000000..d323696
--- /dev/null
+++ b/src/core/src/main/kotlin/FieldFormat.kt
@@ -0,0 +1,12 @@
+package solutions.bitbadger.documents
+
+/**
+ * The data format for a document field retrieval
+ */
+enum class FieldFormat {
+ /** Retrieve the field as a SQL value (string in PostgreSQL, best guess in SQLite */
+ SQL,
+
+ /** Retrieve the field as a JSON value */
+ JSON
+}
diff --git a/src/core/src/main/kotlin/FieldMatch.kt b/src/core/src/main/kotlin/FieldMatch.kt
new file mode 100644
index 0000000..3af7230
--- /dev/null
+++ b/src/core/src/main/kotlin/FieldMatch.kt
@@ -0,0 +1,12 @@
+package solutions.bitbadger.documents
+
+/**
+ * How fields should be matched in by-field queries
+ */
+enum class FieldMatch(val sql: String) {
+ /** Match any of the field criteria (`OR`) */
+ ANY("OR"),
+
+ /** Match all the field criteria (`AND`) */
+ ALL("AND"),
+}
diff --git a/src/core/src/main/kotlin/Op.kt b/src/core/src/main/kotlin/Op.kt
new file mode 100644
index 0000000..45004f4
--- /dev/null
+++ b/src/core/src/main/kotlin/Op.kt
@@ -0,0 +1,39 @@
+package solutions.bitbadger.documents
+
+/**
+ * A comparison operator used for fields
+ */
+enum class Op(val sql: String) {
+ /** Compare using equality */
+ EQUAL("="),
+
+ /** Compare using greater-than */
+ GREATER(">"),
+
+ /** Compare using greater-than-or-equal-to */
+ GREATER_OR_EQUAL(">="),
+
+ /** Compare using less-than */
+ LESS("<"),
+
+ /** Compare using less-than-or-equal-to */
+ LESS_OR_EQUAL("<="),
+
+ /** Compare using inequality */
+ NOT_EQUAL("<>"),
+
+ /** Compare between two values */
+ BETWEEN("BETWEEN"),
+
+ /** Compare existence in a list of values */
+ IN("IN"),
+
+ /** Compare overlap between an array and a list of values */
+ IN_ARRAY("??|"),
+
+ /** Compare existence */
+ EXISTS("IS NOT NULL"),
+
+ /** Compare nonexistence */
+ NOT_EXISTS("IS NULL")
+}
diff --git a/src/core/src/main/kotlin/Parameter.kt b/src/core/src/main/kotlin/Parameter.kt
new file mode 100644
index 0000000..3e9c9a7
--- /dev/null
+++ b/src/core/src/main/kotlin/Parameter.kt
@@ -0,0 +1,58 @@
+package solutions.bitbadger.documents
+
+import java.sql.PreparedStatement
+import java.sql.Types
+import kotlin.jvm.Throws
+
+/**
+ * A parameter to use for a query
+ *
+ * @property name The name of the parameter (prefixed with a colon)
+ * @property type The type of this parameter
+ * @property value The value of the parameter
+ */
+class Parameter(val name: String, val type: ParameterType, val value: T) {
+
+ init {
+ if (!name.startsWith(':') && !name.startsWith('@'))
+ throw DocumentException("Name must start with : or @ ($name)")
+ }
+
+ /**
+ * Bind this parameter to a prepared statement at the given index
+ *
+ * @param stmt The prepared statement to which this parameter should be bound
+ * @param index The index (1-based) to which the parameter should be bound
+ * @throws DocumentException If a number parameter is given a non-numeric value
+ */
+ @Throws(DocumentException::class)
+ fun bind(stmt: PreparedStatement, index: Int) {
+ when (type) {
+ ParameterType.NUMBER -> {
+ when (value) {
+ null -> stmt.setNull(index, Types.NULL)
+ is Byte -> stmt.setByte(index, value)
+ is Short -> stmt.setShort(index, value)
+ is Int -> stmt.setInt(index, value)
+ is Long -> stmt.setLong(index, value)
+ else -> throw DocumentException(
+ "Number parameter must be Byte, Short, Int, or Long (${value::class.simpleName})"
+ )
+ }
+ }
+
+ ParameterType.STRING -> {
+ when (value) {
+ null -> stmt.setNull(index, Types.NULL)
+ is String -> stmt.setString(index, value)
+ else -> stmt.setString(index, value.toString())
+ }
+ }
+
+ ParameterType.JSON -> stmt.setObject(index, value as String, Types.OTHER)
+ }
+ }
+
+ override fun toString() =
+ "$type[$name] = $value"
+}
diff --git a/src/core/src/main/kotlin/ParameterName.kt b/src/core/src/main/kotlin/ParameterName.kt
new file mode 100644
index 0000000..a090db0
--- /dev/null
+++ b/src/core/src/main/kotlin/ParameterName.kt
@@ -0,0 +1,18 @@
+package solutions.bitbadger.documents
+
+/**
+ * Derive parameter names; each instance wraps a counter to provide names for anonymous fields
+ */
+class ParameterName {
+
+ private var currentIdx = 0
+
+ /**
+ * Derive the parameter name from the current possibly-null string
+ *
+ * @param paramName The name of the parameter as specified by the field
+ * @return The name from the field, if present, or a derived name if missing
+ */
+ fun derive(paramName: String?): String =
+ paramName ?: ":field${currentIdx++}"
+}
diff --git a/src/core/src/main/kotlin/ParameterType.kt b/src/core/src/main/kotlin/ParameterType.kt
new file mode 100644
index 0000000..edf44b7
--- /dev/null
+++ b/src/core/src/main/kotlin/ParameterType.kt
@@ -0,0 +1,15 @@
+package solutions.bitbadger.documents
+
+/**
+ * The types of parameters supported by the document library
+ */
+enum class ParameterType {
+ /** The parameter value is some sort of number (`Byte`, `Short`, `Int`, or `Long`) */
+ NUMBER,
+
+ /** The parameter value is a string */
+ STRING,
+
+ /** The parameter should be JSON-encoded */
+ JSON,
+}
diff --git a/src/core/src/main/kotlin/java/Count.kt b/src/core/src/main/kotlin/java/Count.kt
new file mode 100644
index 0000000..bd599e9
--- /dev/null
+++ b/src/core/src/main/kotlin/java/Count.kt
@@ -0,0 +1,147 @@
+package solutions.bitbadger.documents.java
+
+import solutions.bitbadger.documents.*
+import solutions.bitbadger.documents.query.CountQuery
+import java.sql.Connection
+import kotlin.jvm.Throws
+
+/**
+ * Functions to count documents
+ */
+object Count {
+
+ /**
+ * Count all documents in the table
+ *
+ * @param tableName The name of the table in which documents should be counted
+ * @param conn The connection over which documents should be counted
+ * @return A count of the documents in the table
+ * @throws DocumentException If any dependent process does
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun all(tableName: String, conn: Connection) =
+ Custom.scalar(CountQuery.all(tableName), listOf(), Long::class.java, conn, Results::toCount)
+
+ /**
+ * Count all documents in the table
+ *
+ * @param tableName The name of the table in which documents should be counted
+ * @return A count of the documents in the table
+ * @throws DocumentException If no connection string has been set
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun all(tableName: String) =
+ Configuration.dbConn().use { all(tableName, it) }
+
+ /**
+ * Count documents using a field comparison
+ *
+ * @param tableName The name of the table in which documents should be counted
+ * @param fields The fields which should be compared
+ * @param howMatched How the fields should be matched
+ * @param conn The connection on which the deletion should be executed
+ * @return A count of the matching documents in the table
+ * @throws DocumentException If no dialect has been configured
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ @JvmOverloads
+ fun byFields(
+ tableName: String,
+ fields: Collection>,
+ howMatched: FieldMatch? = null,
+ conn: Connection
+ ): Long {
+ val named = Parameters.nameFields(fields)
+ return Custom.scalar(
+ CountQuery.byFields(tableName, named, howMatched),
+ Parameters.addFields(named),
+ Long::class.java,
+ conn,
+ Results::toCount
+ )
+ }
+
+ /**
+ * Count documents using a field comparison
+ *
+ * @param tableName The name of the table in which documents should be counted
+ * @param fields The fields which should be compared
+ * @param howMatched How the fields should be matched
+ * @return A count of the matching documents in the table
+ * @throws DocumentException If no connection string has been set
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ @JvmOverloads
+ fun byFields(tableName: String, fields: Collection>, howMatched: FieldMatch? = null) =
+ Configuration.dbConn().use { byFields(tableName, fields, howMatched, it) }
+
+ /**
+ * Count documents using a JSON containment query (PostgreSQL only)
+ *
+ * @param tableName The name of the table in which documents should be counted
+ * @param criteria The object for which JSON containment should be checked
+ * @param conn The connection on which the count should be executed
+ * @return A count of the matching documents in the table
+ * @throws DocumentException If called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byContains(tableName: String, criteria: TContains, conn: Connection) =
+ Custom.scalar(
+ CountQuery.byContains(tableName),
+ listOf(Parameters.json(":criteria", criteria)),
+ Long::class.java,
+ conn,
+ Results::toCount
+ )
+
+ /**
+ * Count documents using a JSON containment query (PostgreSQL only)
+ *
+ * @param tableName The name of the table in which documents should be counted
+ * @param criteria The object for which JSON containment should be checked
+ * @return A count of the matching documents in the table
+ * @throws DocumentException If no connection string has been set, or if called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byContains(tableName: String, criteria: TContains) =
+ Configuration.dbConn().use { byContains(tableName, criteria, it) }
+
+ /**
+ * Count documents using a JSON Path match query (PostgreSQL only)
+ *
+ * @param tableName The name of the table in which documents should be counted
+ * @param path The JSON path comparison to match
+ * @param conn The connection on which the count should be executed
+ * @return A count of the matching documents in the table
+ * @throws DocumentException If called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byJsonPath(tableName: String, path: String, conn: Connection) =
+ Custom.scalar(
+ CountQuery.byJsonPath(tableName),
+ listOf(Parameter(":path", ParameterType.STRING, path)),
+ Long::class.java,
+ conn,
+ Results::toCount
+ )
+
+ /**
+ * Count documents using a JSON Path match query (PostgreSQL only)
+ *
+ * @param tableName The name of the table in which documents should be counted
+ * @param path The JSON path comparison to match
+ * @return A count of the matching documents in the table
+ * @throws DocumentException If no connection string has been set, or if called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byJsonPath(tableName: String, path: String) =
+ Configuration.dbConn().use { byJsonPath(tableName, path, it) }
+}
diff --git a/src/core/src/main/kotlin/java/Custom.kt b/src/core/src/main/kotlin/java/Custom.kt
new file mode 100644
index 0000000..2220ece
--- /dev/null
+++ b/src/core/src/main/kotlin/java/Custom.kt
@@ -0,0 +1,281 @@
+package solutions.bitbadger.documents.java
+
+import solutions.bitbadger.documents.Configuration
+import solutions.bitbadger.documents.DocumentException
+import solutions.bitbadger.documents.Parameter
+import java.io.PrintWriter
+import java.sql.Connection
+import java.sql.ResultSet
+import java.sql.SQLException
+import java.util.*
+import kotlin.jvm.Throws
+
+/**
+ * Functions to run custom queries
+ */
+object Custom {
+
+ /**
+ * Execute a query that returns a list of results
+ *
+ * @param query The query to retrieve the results
+ * @param parameters Parameters to use for the query
+ * @param clazz The class of the document to be returned
+ * @param conn The connection over which the query should be executed
+ * @param mapFunc The mapping function between the document and the domain item
+ * @return A list of results for the given query
+ * @throws DocumentException If parameters are invalid
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun list(
+ query: String,
+ parameters: Collection> = listOf(),
+ clazz: Class,
+ conn: Connection,
+ mapFunc: (ResultSet, Class) -> TDoc
+ ) = Parameters.apply(conn, query, parameters).use { Results.toCustomList(it, clazz, mapFunc) }
+
+ /**
+ * Execute a query that returns a list of results (creates connection)
+ *
+ * @param query The query to retrieve the results
+ * @param parameters Parameters to use for the query
+ * @param clazz The class of the document to be returned
+ * @param mapFunc The mapping function between the document and the domain item
+ * @return A list of results for the given query
+ * @throws DocumentException If no connection string has been set, or if parameters are invalid
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun list(
+ query: String,
+ parameters: Collection> = listOf(),
+ clazz: Class,
+ mapFunc: (ResultSet, Class) -> TDoc
+ ) = Configuration.dbConn().use { list(query, parameters, clazz, it, mapFunc) }
+
+ /**
+ * Execute a query that returns a JSON array of results
+ *
+ * @param query The query to retrieve the results
+ * @param parameters Parameters to use for the query
+ * @param conn The connection over which the query should be executed
+ * @param mapFunc The mapping function to extract the JSON from the query
+ * @return A JSON array of results for the given query
+ * @throws DocumentException If parameters are invalid
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun jsonArray(
+ query: String,
+ parameters: Collection> = listOf(),
+ conn: Connection,
+ mapFunc: (ResultSet) -> String
+ ) = Parameters.apply(conn, query, parameters).use { Results.toJsonArray(it, mapFunc) }
+
+ /**
+ * Execute a query that returns a JSON array of results (creates connection)
+ *
+ * @param query The query to retrieve the results
+ * @param parameters Parameters to use for the query
+ * @param mapFunc The mapping function to extract the JSON from the query
+ * @return A JSON array of results for the given query
+ * @throws DocumentException If parameters are invalid
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun jsonArray(query: String, parameters: Collection> = listOf(), mapFunc: (ResultSet) -> String) =
+ Configuration.dbConn().use { jsonArray(query, parameters, it, mapFunc) }
+
+ /**
+ * Execute a query, writing its JSON array of results to the given `PrintWriter`
+ *
+ * @param query The query to retrieve the results
+ * @param parameters Parameters to use for the query
+ * @param writer The writer to which the results should be written
+ * @param conn The connection over which the query should be executed
+ * @param mapFunc The mapping function to extract the JSON from the query
+ * @throws DocumentException If parameters are invalid
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun writeJsonArray(
+ query: String,
+ parameters: Collection> = listOf(),
+ writer: PrintWriter,
+ conn: Connection,
+ mapFunc: (ResultSet) -> String
+ ) = Parameters.apply(conn, query, parameters).use { Results.writeJsonArray(writer, it, mapFunc) }
+
+ /**
+ * Execute a query, writing its JSON array of results to the given `PrintWriter` (creates connection)
+ *
+ * @param query The query to retrieve the results
+ * @param parameters Parameters to use for the query
+ * @param writer The writer to which the results should be written
+ * @param mapFunc The mapping function to extract the JSON from the query
+ * @throws DocumentException If parameters are invalid
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun writeJsonArray(
+ query: String,
+ parameters: Collection> = listOf(),
+ writer: PrintWriter,
+ mapFunc: (ResultSet) -> String
+ ) = Configuration.dbConn().use { writeJsonArray(query, parameters, writer, it, mapFunc) }
+
+ /**
+ * Execute a query that returns one or no results
+ *
+ * @param query The query to retrieve the results
+ * @param parameters Parameters to use for the query
+ * @param clazz The class of the document to be returned
+ * @param conn The connection over which the query should be executed
+ * @param mapFunc The mapping function between the document and the domain item
+ * @return An `Optional` value, with the document if one matches the query
+ * @throws DocumentException If parameters are invalid
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun single(
+ query: String,
+ parameters: Collection> = listOf(),
+ clazz: Class,
+ conn: Connection,
+ mapFunc: (ResultSet, Class) -> TDoc
+ ) = Optional.ofNullable(list("$query LIMIT 1", parameters, clazz, conn, mapFunc).singleOrNull())
+
+ /**
+ * Execute a query that returns one or no results
+ *
+ * @param query The query to retrieve the results
+ * @param parameters Parameters to use for the query
+ * @param clazz The class of the document to be returned
+ * @param mapFunc The mapping function between the document and the domain item
+ * @return The document if one matches the query, `null` otherwise
+ * @throws DocumentException If no connection string has been set, or if parameters are invalid
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun single(
+ query: String,
+ parameters: Collection> = listOf(),
+ clazz: Class,
+ mapFunc: (ResultSet, Class) -> TDoc
+ ) = Configuration.dbConn().use { single(query, parameters, clazz, it, mapFunc) }
+
+ /**
+ * Execute a query that returns JSON for one or no documents
+ *
+ * @param query The query to retrieve the results
+ * @param parameters Parameters to use for the query
+ * @param conn The connection over which the query should be executed
+ * @param mapFunc The mapping function between the document and the domain item
+ * @return The JSON for the document if found, an empty object (`{}`) if not
+ * @throws DocumentException If parameters are invalid
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun jsonSingle(
+ query: String,
+ parameters: Collection> = listOf(),
+ conn: Connection,
+ mapFunc: (ResultSet) -> String
+ ) = jsonArray("$query LIMIT 1", parameters, conn, mapFunc).let {
+ if (it == "[]") "{}" else it.substring(1, it.length - 1)
+ }
+
+ /**
+ * Execute a query that returns JSON for one or no documents (creates connection)
+ *
+ * @param query The query to retrieve the results
+ * @param parameters Parameters to use for the query
+ * @param mapFunc The mapping function between the document and the domain item
+ * @return The JSON for the document if found, an empty object (`{}`) if not
+ * @throws DocumentException If parameters are invalid
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun jsonSingle(query: String, parameters: Collection> = listOf(), mapFunc: (ResultSet) -> String) =
+ Configuration.dbConn().use { jsonSingle(query, parameters, it, mapFunc) }
+
+ /**
+ * Execute a query that returns no results
+ *
+ * @param query The query to retrieve the results
+ * @param conn The connection over which the query should be executed
+ * @param parameters Parameters to use for the query
+ * @throws DocumentException If parameters are invalid or if the query fails
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun nonQuery(query: String, parameters: Collection> = listOf(), conn: Connection) {
+ try {
+ Parameters.apply(conn, query, parameters).use { it.executeUpdate() }
+ } catch (ex: SQLException) {
+ throw DocumentException("Unable to execute non-query: ${ex.message}", ex)
+ }
+ }
+
+ /**
+ * Execute a query that returns no results
+ *
+ * @param query The query to retrieve the results
+ * @param parameters Parameters to use for the query
+ * @throws DocumentException If no connection string has been set, if parameters are invalid, or if the query fails
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ @JvmOverloads
+ fun nonQuery(query: String, parameters: Collection> = listOf()) =
+ Configuration.dbConn().use { nonQuery(query, parameters, it) }
+
+ /**
+ * Execute a query that returns a scalar result
+ *
+ * @param query The query to retrieve the result
+ * @param parameters Parameters to use for the query
+ * @param conn The connection over which the query should be executed
+ * @param mapFunc The mapping function between the document and the domain item
+ * @return The scalar value from the query
+ * @throws DocumentException If parameters are invalid or if the query fails
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun scalar(
+ query: String,
+ parameters: Collection> = listOf(),
+ clazz: Class,
+ conn: Connection,
+ mapFunc: (ResultSet, Class) -> T
+ ) = Parameters.apply(conn, query, parameters).use { stmt ->
+ try {
+ stmt.executeQuery().use { rs ->
+ rs.next()
+ mapFunc(rs, clazz)
+ }
+ } catch (ex: SQLException) {
+ throw DocumentException("Unable to retrieve scalar value: ${ex.message}", ex)
+ }
+ }
+
+ /**
+ * Execute a query that returns a scalar result
+ *
+ * @param query The query to retrieve the result
+ * @param parameters Parameters to use for the query
+ * @param mapFunc The mapping function between the document and the domain item
+ * @return The scalar value from the query
+ * @throws DocumentException If no connection string has been set, or if parameters are invalid
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun scalar(
+ query: String,
+ parameters: Collection> = listOf(),
+ clazz: Class,
+ mapFunc: (ResultSet, Class) -> T
+ ) = Configuration.dbConn().use { scalar(query, parameters, clazz, it, mapFunc) }
+}
diff --git a/src/core/src/main/kotlin/java/Definition.kt b/src/core/src/main/kotlin/java/Definition.kt
new file mode 100644
index 0000000..baf7b9b
--- /dev/null
+++ b/src/core/src/main/kotlin/java/Definition.kt
@@ -0,0 +1,92 @@
+package solutions.bitbadger.documents.java
+
+import solutions.bitbadger.documents.Configuration
+import solutions.bitbadger.documents.DocumentException
+import solutions.bitbadger.documents.DocumentIndex
+import solutions.bitbadger.documents.query.DefinitionQuery
+import java.sql.Connection
+import kotlin.jvm.Throws
+
+/**
+ * Functions to define tables and indexes
+ */
+object Definition {
+
+ /**
+ * Create a document table if necessary
+ *
+ * @param tableName The table whose existence should be ensured (may include schema)
+ * @param conn The connection on which the query should be executed
+ * @throws DocumentException If the dialect is not configured
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun ensureTable(tableName: String, conn: Connection) =
+ Configuration.dialect("ensure $tableName exists").let {
+ Custom.nonQuery(DefinitionQuery.ensureTable(tableName, it), conn = conn)
+ Custom.nonQuery(DefinitionQuery.ensureKey(tableName, it), conn = conn)
+ }
+
+ /**
+ * Create a document table if necessary
+ *
+ * @param tableName The table whose existence should be ensured (may include schema)
+ * @throws DocumentException If no connection string has been set
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun ensureTable(tableName: String) =
+ Configuration.dbConn().use { ensureTable(tableName, it) }
+
+ /**
+ * Create an index on field(s) within documents in the specified table if necessary
+ *
+ * @param tableName The table to be indexed (may include schema)
+ * @param indexName The name of the index to create
+ * @param fields One or more fields to be indexed
+ * @param conn The connection on which the query should be executed
+ * @throws DocumentException If any dependent process does
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun ensureFieldIndex(tableName: String, indexName: String, fields: Collection, conn: Connection) =
+ Custom.nonQuery(DefinitionQuery.ensureIndexOn(tableName, indexName, fields), conn = conn)
+
+ /**
+ * Create an index on field(s) within documents in the specified table if necessary
+ *
+ * @param tableName The table to be indexed (may include schema)
+ * @param indexName The name of the index to create
+ * @param fields One or more fields to be indexed
+ * @throws DocumentException If no connection string has been set, or if any dependent process does
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun ensureFieldIndex(tableName: String, indexName: String, fields: Collection) =
+ Configuration.dbConn().use { ensureFieldIndex(tableName, indexName, fields, it) }
+
+ /**
+ * Create a document index on a table (PostgreSQL only)
+ *
+ * @param tableName The table to be indexed (may include schema)
+ * @param indexType The type of index to ensure
+ * @param conn The connection on which the query should be executed
+ * @throws DocumentException If called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun ensureDocumentIndex(tableName: String, indexType: DocumentIndex, conn: Connection) =
+ Custom.nonQuery(DefinitionQuery.ensureDocumentIndexOn(tableName, indexType), conn = conn)
+
+ /**
+ * Create a document index on a table (PostgreSQL only)
+ *
+ * @param tableName The table to be indexed (may include schema)
+ * @param indexType The type of index to ensure
+ * @throws DocumentException If no connection string has been set, or if called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun ensureDocumentIndex(tableName: String, indexType: DocumentIndex) =
+ Configuration.dbConn().use { ensureDocumentIndex(tableName, indexType, it) }
+}
diff --git a/src/core/src/main/kotlin/java/Delete.kt b/src/core/src/main/kotlin/java/Delete.kt
new file mode 100644
index 0000000..969ac5e
--- /dev/null
+++ b/src/core/src/main/kotlin/java/Delete.kt
@@ -0,0 +1,122 @@
+package solutions.bitbadger.documents.java
+
+import solutions.bitbadger.documents.*
+import solutions.bitbadger.documents.query.DeleteQuery
+import java.sql.Connection
+import kotlin.jvm.Throws
+
+/**
+ * Functions to delete documents
+ */
+object Delete {
+
+ /**
+ * Delete a document by its ID
+ *
+ * @param tableName The name of the table from which documents should be deleted
+ * @param docId The ID of the document to be deleted
+ * @param conn The connection on which the deletion should be executed
+ * @throws DocumentException If no dialect has been configured
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byId(tableName: String, docId: TKey, conn: Connection) =
+ Custom.nonQuery(
+ DeleteQuery.byId(tableName, docId),
+ Parameters.addFields(listOf(Field.equal(Configuration.idField, docId, ":id"))),
+ conn
+ )
+
+ /**
+ * Delete a document by its ID
+ *
+ * @param tableName The name of the table from which documents should be deleted
+ * @param docId The ID of the document to be deleted
+ * @throws DocumentException If no connection string has been set
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byId(tableName: String, docId: TKey) =
+ Configuration.dbConn().use { byId(tableName, docId, it) }
+
+ /**
+ * Delete documents using a field comparison
+ *
+ * @param tableName The name of the table from which documents should be deleted
+ * @param fields The fields which should be compared
+ * @param howMatched How the fields should be matched
+ * @param conn The connection on which the deletion should be executed
+ * @throws DocumentException If no dialect has been configured, or if parameters are invalid
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ @JvmOverloads
+ fun byFields(tableName: String, fields: Collection>, howMatched: FieldMatch? = null, conn: Connection) {
+ val named = Parameters.nameFields(fields)
+ Custom.nonQuery(DeleteQuery.byFields(tableName, named, howMatched), Parameters.addFields(named), conn)
+ }
+
+ /**
+ * Delete documents using a field comparison
+ *
+ * @param tableName The name of the table from which documents should be deleted
+ * @param fields The fields which should be compared
+ * @param howMatched How the fields should be matched
+ * @throws DocumentException If no connection string has been set, or if parameters are invalid
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ @JvmOverloads
+ fun byFields(tableName: String, fields: Collection>, howMatched: FieldMatch? = null) =
+ Configuration.dbConn().use { byFields(tableName, fields, howMatched, it) }
+
+ /**
+ * Delete documents using a JSON containment query (PostgreSQL only)
+ *
+ * @param tableName The name of the table from which documents should be deleted
+ * @param criteria The object for which JSON containment should be checked
+ * @param conn The connection on which the deletion should be executed
+ * @throws DocumentException If called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byContains(tableName: String, criteria: TContains, conn: Connection) =
+ Custom.nonQuery(DeleteQuery.byContains(tableName), listOf(Parameters.json(":criteria", criteria)), conn)
+
+ /**
+ * Delete documents using a JSON containment query (PostgreSQL only)
+ *
+ * @param tableName The name of the table from which documents should be deleted
+ * @param criteria The object for which JSON containment should be checked
+ * @throws DocumentException If no connection string has been set, or if called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byContains(tableName: String, criteria: TContains) =
+ Configuration.dbConn().use { byContains(tableName, criteria, it) }
+
+ /**
+ * Delete documents using a JSON Path match query (PostgreSQL only)
+ *
+ * @param tableName The name of the table from which documents should be deleted
+ * @param path The JSON path comparison to match
+ * @param conn The connection on which the deletion should be executed
+ * @throws DocumentException If called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byJsonPath(tableName: String, path: String, conn: Connection) =
+ Custom.nonQuery(DeleteQuery.byJsonPath(tableName), listOf(Parameter(":path", ParameterType.STRING, path)), conn)
+
+ /**
+ * Delete documents using a JSON Path match query (PostgreSQL only)
+ *
+ * @param tableName The name of the table from which documents should be deleted
+ * @param path The JSON path comparison to match
+ * @throws DocumentException If no connection string has been set, or if called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byJsonPath(tableName: String, path: String) =
+ Configuration.dbConn().use { byJsonPath(tableName, path, it) }
+}
diff --git a/src/core/src/main/kotlin/java/Document.kt b/src/core/src/main/kotlin/java/Document.kt
new file mode 100644
index 0000000..018b49c
--- /dev/null
+++ b/src/core/src/main/kotlin/java/Document.kt
@@ -0,0 +1,109 @@
+package solutions.bitbadger.documents.java
+
+import solutions.bitbadger.documents.AutoId
+import solutions.bitbadger.documents.Configuration
+import solutions.bitbadger.documents.DocumentException
+import solutions.bitbadger.documents.Field
+import solutions.bitbadger.documents.query.DocumentQuery
+import solutions.bitbadger.documents.query.Where
+import solutions.bitbadger.documents.query.statementWhere
+import java.sql.Connection
+import kotlin.jvm.Throws
+
+/**
+ * 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
+ * @throws DocumentException If IDs are misconfigured, or if the database command fails
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun insert(tableName: String, document: TDoc, conn: Connection) {
+ val strategy = Configuration.autoIdStrategy
+ val query = if (strategy == AutoId.DISABLED || !AutoId.needsAutoId(strategy, document, Configuration.idField)) {
+ DocumentQuery.insert(tableName)
+ } else {
+ DocumentQuery.insert(tableName, strategy)
+ }
+ Custom.nonQuery(query, listOf(Parameters.json(":data", document)), conn)
+ }
+
+ /**
+ * 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
+ * @throws DocumentException If no connection string has been set; if IDs are misconfigured; or if the database
+ * command fails
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ 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
+ * @throws DocumentException If the database command fails
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun save(tableName: String, document: TDoc, conn: Connection) =
+ Custom.nonQuery(DocumentQuery.save(tableName), listOf(Parameters.json(":data", document)), conn)
+
+ /**
+ * 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
+ * @throws DocumentException If no connection string has been set, or if the database command fails
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ 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
+ * @throws DocumentException If no dialect has been configured, or if the database command fails
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun update(tableName: String, docId: TKey, document: TDoc, conn: Connection) =
+ Custom.nonQuery(
+ statementWhere(DocumentQuery.update(tableName), Where.byId(":id", docId)),
+ Parameters.addFields(
+ listOf(Field.equal(Configuration.idField, docId, ":id")),
+ mutableListOf(Parameters.json(":data", document))
+ ),
+ conn
+ )
+
+ /**
+ * 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
+ * @throws DocumentException If no connection string has been set, or if the database command fails
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun update(tableName: String, docId: TKey, document: TDoc) =
+ Configuration.dbConn().use { update(tableName, docId, document, it) }
+}
diff --git a/src/core/src/main/kotlin/java/DocumentConfig.kt b/src/core/src/main/kotlin/java/DocumentConfig.kt
new file mode 100644
index 0000000..f817b4b
--- /dev/null
+++ b/src/core/src/main/kotlin/java/DocumentConfig.kt
@@ -0,0 +1,15 @@
+package solutions.bitbadger.documents.java
+
+import solutions.bitbadger.documents.DocumentSerializer
+
+/**
+ * Configuration for document serialization
+ */
+object DocumentConfig {
+
+ /**
+ * The serializer to use for documents
+ */
+ @JvmStatic
+ var serializer: DocumentSerializer = NullDocumentSerializer()
+}
diff --git a/src/core/src/main/kotlin/java/Exists.kt b/src/core/src/main/kotlin/java/Exists.kt
new file mode 100644
index 0000000..bbfa151
--- /dev/null
+++ b/src/core/src/main/kotlin/java/Exists.kt
@@ -0,0 +1,155 @@
+package solutions.bitbadger.documents.java
+
+import solutions.bitbadger.documents.*
+import solutions.bitbadger.documents.query.ExistsQuery
+import java.sql.Connection
+import kotlin.jvm.Throws
+
+/**
+ * Functions to determine whether documents exist
+ */
+object Exists {
+
+ /**
+ * Determine a document's existence by its ID
+ *
+ * @param tableName The name of the table in which document existence should be checked
+ * @param docId The ID of the document to be checked
+ * @param conn The connection on which the existence check should be executed
+ * @return True if the document exists, false if not
+ * @throws DocumentException If no dialect has been configured
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byId(tableName: String, docId: TKey, conn: Connection) =
+ Custom.scalar(
+ ExistsQuery.byId(tableName, docId),
+ Parameters.addFields(listOf(Field.equal(Configuration.idField, docId, ":id"))),
+ Boolean::class.java,
+ conn,
+ Results::toExists
+ )
+
+ /**
+ * Determine a document's existence by its ID
+ *
+ * @param tableName The name of the table in which document existence should be checked
+ * @param docId The ID of the document to be checked
+ * @return True if the document exists, false if not
+ * @throws DocumentException If no connection string has been set
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byId(tableName: String, docId: TKey) =
+ Configuration.dbConn().use { byId(tableName, docId, it) }
+
+ /**
+ * Determine document existence using a field comparison
+ *
+ * @param tableName The name of the table in which document existence should be checked
+ * @param fields The fields which should be compared
+ * @param howMatched How the fields should be matched
+ * @param conn The connection on which the existence check should be executed
+ * @return True if any matching documents exist, false if not
+ * @throws DocumentException If no dialect has been configured, or if parameters are invalid
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ @JvmOverloads
+ fun byFields(
+ tableName: String,
+ fields: Collection>,
+ howMatched: FieldMatch? = null,
+ conn: Connection
+ ): Boolean {
+ val named = Parameters.nameFields(fields)
+ return Custom.scalar(
+ ExistsQuery.byFields(tableName, named, howMatched),
+ Parameters.addFields(named),
+ Boolean::class.java,
+ conn,
+ Results::toExists
+ )
+ }
+
+ /**
+ * Determine document existence using a field comparison
+ *
+ * @param tableName The name of the table in which document existence should be checked
+ * @param fields The fields which should be compared
+ * @param howMatched How the fields should be matched
+ * @return True if any matching documents exist, false if not
+ * @throws DocumentException If no connection string has been set, or if parameters are invalid
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ @JvmOverloads
+ fun byFields(tableName: String, fields: Collection>, howMatched: FieldMatch? = null) =
+ Configuration.dbConn().use { byFields(tableName, fields, howMatched, it) }
+
+ /**
+ * Determine document existence using a JSON containment query (PostgreSQL only)
+ *
+ * @param tableName The name of the table in which document existence should be checked
+ * @param criteria The object for which JSON containment should be checked
+ * @param conn The connection on which the existence check should be executed
+ * @return True if any matching documents exist, false if not
+ * @throws DocumentException If called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byContains(tableName: String, criteria: TContains, conn: Connection) =
+ Custom.scalar(
+ ExistsQuery.byContains(tableName),
+ listOf(Parameters.json(":criteria", criteria)),
+ Boolean::class.java,
+ conn,
+ Results::toExists
+ )
+
+ /**
+ * Determine document existence using a JSON containment query (PostgreSQL only)
+ *
+ * @param tableName The name of the table in which document existence should be checked
+ * @param criteria The object for which JSON containment should be checked
+ * @return True if any matching documents exist, false if not
+ * @throws DocumentException If no connection string has been set, or if called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byContains(tableName: String, criteria: TContains) =
+ Configuration.dbConn().use { byContains(tableName, criteria, it) }
+
+ /**
+ * Determine document existence using a JSON Path match query (PostgreSQL only)
+ *
+ * @param tableName The name of the table in which document existence should be checked
+ * @param path The JSON path comparison to match
+ * @param conn The connection on which the existence check should be executed
+ * @return True if any matching documents exist, false if not
+ * @throws DocumentException If called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byJsonPath(tableName: String, path: String, conn: Connection) =
+ Custom.scalar(
+ ExistsQuery.byJsonPath(tableName),
+ listOf(Parameter(":path", ParameterType.STRING, path)),
+ Boolean::class.java,
+ conn,
+ Results::toExists
+ )
+
+ /**
+ * Determine document existence using a JSON Path match query (PostgreSQL only)
+ *
+ * @param tableName The name of the table in which document existence should be checked
+ * @param path The JSON path comparison to match
+ * @return True if any matching documents exist, false if not
+ * @throws DocumentException If no connection string has been set, or if called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byJsonPath(tableName: String, path: String) =
+ Configuration.dbConn().use { byJsonPath(tableName, path, it) }
+}
diff --git a/src/core/src/main/kotlin/java/Find.kt b/src/core/src/main/kotlin/java/Find.kt
new file mode 100644
index 0000000..09d8bef
--- /dev/null
+++ b/src/core/src/main/kotlin/java/Find.kt
@@ -0,0 +1,502 @@
+package solutions.bitbadger.documents.java
+
+import solutions.bitbadger.documents.*
+import solutions.bitbadger.documents.query.FindQuery
+import solutions.bitbadger.documents.query.orderBy
+import java.sql.Connection
+import java.util.Optional
+import kotlin.jvm.Throws
+
+/**
+ * Functions to find and retrieve documents
+ */
+object Find {
+
+ /**
+ * Retrieve all documents in the given table, ordering results by the optional given fields
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param clazz The class of the document to be returned
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @param conn The connection over which documents should be retrieved
+ * @return A list of documents from the given table
+ * @throws DocumentException If query execution fails
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun all(tableName: String, clazz: Class, orderBy: Collection>? = null, conn: Connection) =
+ Custom.list(FindQuery.all(tableName) + (orderBy?.let(::orderBy) ?: ""), listOf(), clazz, conn, Results::fromData)
+
+ /**
+ * Retrieve all documents in the given table
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param clazz The class of the document to be returned
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @return A list of documents from the given table
+ * @throws DocumentException If no connection string has been set, or if query execution fails
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ @JvmOverloads
+ fun all(tableName: String, clazz: Class, orderBy: Collection>? = null) =
+ Configuration.dbConn().use { all(tableName, clazz, orderBy, it) }
+
+ /**
+ * Retrieve all documents in the given table
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param clazz The class of the document to be returned
+ * @param conn The connection over which documents should be retrieved
+ * @return A list of documents from the given table
+ * @throws DocumentException If query execution fails
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun all(tableName: String, clazz: Class, conn: Connection) =
+ all(tableName, clazz, null, conn)
+
+ /**
+ * Retrieve a document by its ID
+ *
+ * @param tableName The table from which the document should be retrieved
+ * @param docId The ID of the document to retrieve
+ * @param clazz The class of the document to be returned
+ * @param conn The connection over which documents should be retrieved
+ * @return An `Optional` item with the document if it is found
+ * @throws DocumentException If no dialect has been configured
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byId(tableName: String, docId: TKey, clazz: Class, conn: Connection) =
+ Custom.single(
+ FindQuery.byId(tableName, docId),
+ Parameters.addFields(listOf(Field.equal(Configuration.idField, docId, ":id"))),
+ clazz,
+ conn,
+ Results::fromData
+ )
+
+ /**
+ * Retrieve a document by its ID
+ *
+ * @param tableName The table from which the document should be retrieved
+ * @param docId The ID of the document to retrieve
+ * @param clazz The class of the document to be returned
+ * @return An `Optional` item with the document if it is found
+ * @throws DocumentException If no connection string has been set
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byId(tableName: String, docId: TKey, clazz: Class) =
+ Configuration.dbConn().use { byId(tableName, docId, clazz, it) }
+
+ /**
+ * Retrieve documents using a field comparison, ordering results by the given fields
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param fields The fields which should be compared
+ * @param clazz The class of the document to be returned
+ * @param howMatched How the fields should be matched
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @param conn The connection over which documents should be retrieved
+ * @return A list of documents matching the field comparison
+ * @throws DocumentException If no dialect has been configured, or if parameters are invalid
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byFields(
+ tableName: String,
+ fields: Collection>,
+ clazz: Class,
+ howMatched: FieldMatch? = null,
+ orderBy: Collection>? = null,
+ conn: Connection
+ ): List {
+ val named = Parameters.nameFields(fields)
+ return Custom.list(
+ FindQuery.byFields(tableName, named, howMatched) + (orderBy?.let(::orderBy) ?: ""),
+ Parameters.addFields(named),
+ clazz,
+ conn,
+ Results::fromData
+ )
+ }
+
+ /**
+ * Retrieve documents using a field comparison, ordering results by the given fields
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param fields The fields which should be compared
+ * @param clazz The class of the document to be returned
+ * @param howMatched How the fields should be matched
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @return A list of documents matching the field comparison
+ * @throws DocumentException If no connection string has been set, or if parameters are invalid
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ @JvmOverloads
+ fun byFields(
+ tableName: String,
+ fields: Collection>,
+ clazz: Class,
+ howMatched: FieldMatch? = null,
+ orderBy: Collection>? = null
+ ) =
+ Configuration.dbConn().use { byFields(tableName, fields, clazz, howMatched, orderBy, it) }
+
+ /**
+ * Retrieve documents using a field comparison
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param fields The fields which should be compared
+ * @param clazz The class of the document to be returned
+ * @param howMatched How the fields should be matched
+ * @param conn The connection over which documents should be retrieved
+ * @return A list of documents matching the field comparison
+ * @throws DocumentException If no dialect has been configured, or if parameters are invalid
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byFields(
+ tableName: String,
+ fields: Collection>,
+ clazz: Class,
+ howMatched: FieldMatch? = null,
+ conn: Connection
+ ) =
+ byFields(tableName, fields, clazz, howMatched, null, conn)
+
+ /**
+ * Retrieve documents using a JSON containment query, ordering results by the given fields (PostgreSQL only)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param criteria The object for which JSON containment should be checked
+ * @param clazz The class of the document to be returned
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @param conn The connection over which documents should be retrieved
+ * @return A list of documents matching the JSON containment query
+ * @throws DocumentException If called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byContains(
+ tableName: String,
+ criteria: TContains,
+ clazz: Class,
+ orderBy: Collection>? = null,
+ conn: Connection
+ ) =
+ Custom.list(
+ FindQuery.byContains(tableName) + (orderBy?.let(::orderBy) ?: ""),
+ listOf(Parameters.json(":criteria", criteria)),
+ clazz,
+ conn,
+ Results::fromData
+ )
+
+ /**
+ * Retrieve documents using a JSON containment query, ordering results by the given fields (PostgreSQL only)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param criteria The object for which JSON containment should be checked
+ * @param clazz The class of the document to be returned
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @return A list of documents matching the JSON containment query
+ * @throws DocumentException If no connection string has been set, or if called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ @JvmOverloads
+ fun byContains(
+ tableName: String,
+ criteria: TContains,
+ clazz: Class,
+ orderBy: Collection>? = null
+ ) =
+ Configuration.dbConn().use { byContains(tableName, criteria, clazz, orderBy, it) }
+
+ /**
+ * Retrieve documents using a JSON containment query (PostgreSQL only)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param criteria The object for which JSON containment should be checked
+ * @param clazz The class of the document to be returned
+ * @param conn The connection over which documents should be retrieved
+ * @return A list of documents matching the JSON containment query
+ * @throws DocumentException If called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byContains(tableName: String, criteria: TContains, clazz: Class, conn: Connection) =
+ byContains(tableName, criteria, clazz, null, conn)
+
+ /**
+ * Retrieve documents using a JSON Path match query, ordering results by the given fields (PostgreSQL only)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param path The JSON path comparison to match
+ * @param clazz The class of the document to be returned
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @param conn The connection over which documents should be retrieved
+ * @return A list of documents matching the JSON Path match query
+ * @throws DocumentException If called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byJsonPath(
+ tableName: String,
+ path: String,
+ clazz: Class,
+ orderBy: Collection>? = null,
+ conn: Connection
+ ) =
+ Custom.list(
+ FindQuery.byJsonPath(tableName) + (orderBy?.let(::orderBy) ?: ""),
+ listOf(Parameter(":path", ParameterType.STRING, path)),
+ clazz,
+ conn,
+ Results::fromData
+ )
+
+ /**
+ * Retrieve documents using a JSON Path match query, ordering results by the given fields (PostgreSQL only)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param path The JSON path comparison to match
+ * @param clazz The class of the document to be returned
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @return A list of documents matching the JSON Path match query
+ * @throws DocumentException If no connection string has been set, or if called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ @JvmOverloads
+ fun byJsonPath(tableName: String, path: String, clazz: Class, orderBy: Collection>? = null) =
+ Configuration.dbConn().use { byJsonPath(tableName, path, clazz, orderBy, it) }
+
+ /**
+ * Retrieve documents using a JSON Path match query (PostgreSQL only)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param path The JSON path comparison to match
+ * @param clazz The class of the document to be returned
+ * @param conn The connection over which documents should be retrieved
+ * @return A list of documents matching the JSON Path match query
+ * @throws DocumentException If called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byJsonPath(tableName: String, path: String, clazz: Class, conn: Connection) =
+ byJsonPath(tableName, path, clazz, null, conn)
+
+ /**
+ * Retrieve the first document using a field comparison and optional ordering fields
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param fields The fields which should be compared
+ * @param clazz The class of the document to be returned
+ * @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`)
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @param conn The connection over which documents should be retrieved
+ * @return An `Optional` item, with the first document matching the field comparison if found
+ * @throws DocumentException If no dialect has been configured, or if parameters are invalid
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun firstByFields(
+ tableName: String,
+ fields: Collection>,
+ clazz: Class,
+ howMatched: FieldMatch? = null,
+ orderBy: Collection>? = null,
+ conn: Connection
+ ): Optional {
+ val named = Parameters.nameFields(fields)
+ return Custom.single(
+ FindQuery.byFields(tableName, named, howMatched) + (orderBy?.let(::orderBy) ?: ""),
+ Parameters.addFields(named),
+ clazz,
+ conn,
+ Results::fromData
+ )
+ }
+
+ /**
+ * Retrieve the first document using a field comparison and optional ordering fields
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param fields The fields which should be compared
+ * @param clazz The class of the document to be returned
+ * @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`)
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @return An `Optional` item, with the first document matching the field comparison if found
+ * @throws DocumentException If no connection string has been set, or if parameters are invalid
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ @JvmOverloads
+ fun firstByFields(
+ tableName: String,
+ fields: Collection>,
+ clazz: Class,
+ howMatched: FieldMatch? = null,
+ orderBy: Collection>? = null
+ ) =
+ Configuration.dbConn().use { firstByFields(tableName, fields, clazz, howMatched, orderBy, it) }
+
+ /**
+ * Retrieve the first document using a field comparison
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param fields The fields which should be compared
+ * @param clazz The class of the document to be returned
+ * @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`)
+ * @param conn The connection over which documents should be retrieved
+ * @return An `Optional` item, with the first document matching the field comparison if found
+ * @throws DocumentException If no dialect has been configured, or if parameters are invalid
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun firstByFields(
+ tableName: String,
+ fields: Collection>,
+ clazz: Class,
+ howMatched: FieldMatch? = null,
+ conn: Connection
+ ) =
+ firstByFields(tableName, fields, clazz, howMatched, null, conn)
+
+ /**
+ * Retrieve the first document using a JSON containment query and optional ordering fields (PostgreSQL only)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param criteria The object for which JSON containment should be checked
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @param conn The connection over which documents should be retrieved
+ * @return An `Optional` item, with the first document matching the JSON containment query if found
+ * @throws DocumentException If called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun firstByContains(
+ tableName: String,
+ criteria: TContains,
+ clazz: Class,
+ orderBy: Collection>? = null,
+ conn: Connection
+ ) =
+ Custom.single(
+ FindQuery.byContains(tableName) + (orderBy?.let(::orderBy) ?: ""),
+ listOf(Parameters.json(":criteria", criteria)),
+ clazz,
+ conn,
+ Results::fromData
+ )
+
+ /**
+ * Retrieve the first document using a JSON containment query (PostgreSQL only)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param criteria The object for which JSON containment should be checked
+ * @param clazz The class of the document to be returned
+ * @param conn The connection over which documents should be retrieved
+ * @return An `Optional` item, with the first document matching the JSON containment query if found
+ * @throws DocumentException If called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun firstByContains(
+ tableName: String,
+ criteria: TContains,
+ clazz: Class,
+ conn: Connection
+ ) =
+ firstByContains(tableName, criteria, clazz, null, conn)
+
+ /**
+ * Retrieve the first document using a JSON containment query and optional ordering fields (PostgreSQL only)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param criteria The object for which JSON containment should be checked
+ * @param clazz The class of the document to be returned
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @return An `Optional` item, with the first document matching the JSON containment query if found
+ * @throws DocumentException If no connection string has been set, or if called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ @JvmOverloads
+ fun firstByContains(
+ tableName: String,
+ criteria: TContains,
+ clazz: Class,
+ orderBy: Collection>? = null
+ ) =
+ Configuration.dbConn().use { firstByContains(tableName, criteria, clazz, orderBy, it) }
+
+ /**
+ * Retrieve the first document using a JSON Path match query and optional ordering fields (PostgreSQL only)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param path The JSON path comparison to match
+ * @param clazz The class of the document to be returned
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @param conn The connection over which documents should be retrieved
+ * @return An `Optional` item, with the first document matching the JSON Path match query if found
+ * @throws DocumentException If called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun firstByJsonPath(
+ tableName: String,
+ path: String,
+ clazz: Class,
+ orderBy: Collection>? = null,
+ conn: Connection
+ ) =
+ Custom.single(
+ FindQuery.byJsonPath(tableName) + (orderBy?.let(::orderBy) ?: ""),
+ listOf(Parameter(":path", ParameterType.STRING, path)),
+ clazz,
+ conn,
+ Results::fromData
+ )
+
+ /**
+ * Retrieve the first document using a JSON Path match query (PostgreSQL only)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param path The JSON path comparison to match
+ * @param clazz The class of the document to be returned
+ * @param conn The connection over which documents should be retrieved
+ * @return An `Optional` item, with the first document matching the JSON Path match query if found
+ * @throws DocumentException If called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun firstByJsonPath(tableName: String, path: String, clazz: Class, conn: Connection) =
+ firstByJsonPath(tableName, path, clazz, null, conn)
+
+ /**
+ * Retrieve the first document using a JSON Path match query and optional ordering fields (PostgreSQL only)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param path The JSON path comparison to match
+ * @param clazz The class of the document to be returned
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @return An `Optional` item, with the first document matching the JSON Path match query if found
+ * @throws DocumentException If no connection string has been set, or if called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ @JvmOverloads
+ fun firstByJsonPath(
+ tableName: String,
+ path: String,
+ clazz: Class,
+ orderBy: Collection>? = null
+ ) =
+ Configuration.dbConn().use { firstByJsonPath(tableName, path, clazz, orderBy, it) }
+}
diff --git a/src/core/src/main/kotlin/java/Json.kt b/src/core/src/main/kotlin/java/Json.kt
new file mode 100644
index 0000000..d41aa51
--- /dev/null
+++ b/src/core/src/main/kotlin/java/Json.kt
@@ -0,0 +1,877 @@
+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
+
+/**
+ * Functions to find and retrieve documents, returning them as JSON strings
+ */
+object Json {
+
+ /**
+ * Retrieve all documents in the given table, ordering results by the optional given fields
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @param conn The connection over which documents should be retrieved
+ * @return A JSON array of documents from the given table
+ * @throws DocumentException If query execution fails
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun all(tableName: String, orderBy: Collection>? = null, conn: Connection) =
+ Custom.jsonArray(
+ FindQuery.all(tableName) + (orderBy?.let(::orderBy) ?: ""),
+ listOf(),
+ conn,
+ Results::jsonFromData
+ )
+
+ /**
+ * Retrieve all documents in the given table (creates connection)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @return A JSON array of documents from the given table
+ * @throws DocumentException If no connection string has been set, or if query execution fails
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ @JvmOverloads
+ fun all(tableName: String, orderBy: Collection>? = null) =
+ Configuration.dbConn().use { all(tableName, orderBy, it) }
+
+ /**
+ * Retrieve all documents in the given table
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param conn The connection over which documents should be retrieved
+ * @return A JSON array of documents from the given table
+ * @throws DocumentException If query execution fails
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun all(tableName: String, conn: Connection) =
+ all(tableName, null, conn)
+
+ /**
+ * 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
+ *
+ * @param tableName The table from which the document should be retrieved
+ * @param docId The ID of the document to retrieve
+ * @param conn The connection over which documents should be retrieved
+ * @return A JSON document if found, an empty JSON object if not found
+ * @throws DocumentException If no dialect has been configured
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byId(tableName: String, docId: TKey, conn: Connection) =
+ Custom.jsonSingle(
+ FindQuery.byId(tableName, docId),
+ Parameters.addFields(listOf(Field.equal(Configuration.idField, docId, ":id"))),
+ conn,
+ Results::jsonFromData
+ )
+
+ /**
+ * Retrieve a document by its ID (creates connection)
+ *
+ * @param tableName The table from which the document should be retrieved
+ * @param docId The ID of the document to retrieve
+ * @return A JSON document if found, an empty JSON object if not found
+ * @throws DocumentException If no connection string has been set
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ 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
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param fields The fields which should be compared
+ * @param howMatched How the fields should be matched
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @param conn The connection over which documents should be retrieved
+ * @return A JSON array of documents matching the field comparison
+ * @throws DocumentException If no dialect has been configured, or if parameters are invalid
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byFields(
+ tableName: String,
+ fields: Collection>,
+ howMatched: FieldMatch? = null,
+ orderBy: Collection>? = null,
+ conn: Connection
+ ): String {
+ val named = Parameters.nameFields(fields)
+ return Custom.jsonArray(
+ FindQuery.byFields(tableName, named, howMatched) + (orderBy?.let(::orderBy) ?: ""),
+ Parameters.addFields(named),
+ conn,
+ Results::jsonFromData
+ )
+ }
+
+ /**
+ * Retrieve documents using a field comparison, ordering results by the given fields (creates connection)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param fields The fields which should be compared
+ * @param howMatched How the fields should be matched
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @return A JSON array of documents matching the field comparison
+ * @throws DocumentException If no connection string has been set, or if parameters are invalid
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ @JvmOverloads
+ fun byFields(
+ tableName: String,
+ fields: Collection>,
+ howMatched: FieldMatch? = null,
+ orderBy: Collection>? = null
+ ) = Configuration.dbConn().use { byFields(tableName, fields, howMatched, orderBy, it) }
+
+ /**
+ * Retrieve documents using a field comparison
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param fields The fields which should be compared
+ * @param howMatched How the fields should be matched
+ * @param conn The connection over which documents should be retrieved
+ * @return A JSON array of documents matching the field comparison
+ * @throws DocumentException If no dialect has been configured, or if parameters are invalid
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byFields(tableName: String, fields: Collection>, howMatched: FieldMatch? = null, conn: Connection) =
+ byFields(tableName, fields, howMatched, null, conn)
+
+ /**
+ * 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)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param criteria The object for which JSON containment should be checked
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @param conn The connection over which documents should be retrieved
+ * @return A JSON array of documents matching the JSON containment query
+ * @throws DocumentException If called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byContains(
+ tableName: String,
+ criteria: TContains,
+ orderBy: Collection>? = null,
+ conn: Connection
+ ) = Custom.jsonArray(
+ FindQuery.byContains(tableName) + (orderBy?.let(::orderBy) ?: ""),
+ listOf(Parameters.json(":criteria", criteria)),
+ conn,
+ Results::jsonFromData
+ )
+
+ /**
+ * Retrieve documents using a JSON containment query, ordering results by the given fields (PostgreSQL only; creates
+ * connection)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param criteria The object for which JSON containment should be checked
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @return A JSON array of documents matching the JSON containment query
+ * @throws DocumentException If no connection string has been set, or if called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ @JvmOverloads
+ fun byContains(tableName: String, criteria: TContains, orderBy: Collection>? = null) =
+ Configuration.dbConn().use { byContains(tableName, criteria, orderBy, it) }
+
+ /**
+ * Retrieve documents using a JSON containment query (PostgreSQL only)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param criteria The object for which JSON containment should be checked
+ * @param conn The connection over which documents should be retrieved
+ * @return A JSON array of documents matching the JSON containment query
+ * @throws DocumentException If called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byContains(tableName: String, criteria: TContains, conn: Connection) =
+ byContains(tableName, criteria, null, conn)
+
+ /**
+ * 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)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param path The JSON path comparison to match
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @param conn The connection over which documents should be retrieved
+ * @return A JSON array of documents matching the JSON Path match query
+ * @throws DocumentException If called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byJsonPath(tableName: String, path: String, orderBy: Collection>? = null, conn: Connection) =
+ Custom.jsonArray(
+ FindQuery.byJsonPath(tableName) + (orderBy?.let(::orderBy) ?: ""),
+ listOf(Parameter(":path", ParameterType.STRING, path)),
+ conn,
+ Results::jsonFromData
+ )
+
+ /**
+ * Retrieve documents using a JSON Path match query, ordering results by the given fields (PostgreSQL only; creates
+ * connection)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param path The JSON path comparison to match
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @return A JSON array of documents matching the JSON Path match query
+ * @throws DocumentException If no connection string has been set, or if called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ @JvmOverloads
+ fun byJsonPath(tableName: String, path: String, orderBy: Collection>? = null) =
+ Configuration.dbConn().use { byJsonPath(tableName, path, orderBy, it) }
+
+ /**
+ * Retrieve documents using a JSON Path match query (PostgreSQL only)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param path The JSON path comparison to match
+ * @param conn The connection over which documents should be retrieved
+ * @return A JSON array of documents matching the JSON Path match query
+ * @throws DocumentException If called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byJsonPath(tableName: String, path: String, conn: Connection) =
+ byJsonPath(tableName, path, null, conn)
+
+ /**
+ * 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
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param fields The fields which should be compared
+ * @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`)
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @param conn The connection over which documents should be retrieved
+ * @return The first JSON document matching the field comparison if found, an empty JSON object otherwise
+ * @throws DocumentException If no dialect has been configured, or if parameters are invalid
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun firstByFields(
+ tableName: String,
+ fields: Collection>,
+ howMatched: FieldMatch? = null,
+ orderBy: Collection>? = null,
+ conn: Connection
+ ): String {
+ val named = Parameters.nameFields(fields)
+ return Custom.jsonSingle(
+ FindQuery.byFields(tableName, named, howMatched) + (orderBy?.let(::orderBy) ?: ""),
+ Parameters.addFields(named),
+ conn,
+ Results::jsonFromData
+ )
+ }
+
+ /**
+ * Retrieve the first document using a field comparison and optional ordering fields (creates connection)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param fields The fields which should be compared
+ * @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`)
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @return The first JSON document matching the field comparison if found, an empty JSON object otherwise
+ * @throws DocumentException If no connection string has been set, or if parameters are invalid
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ @JvmOverloads
+ fun firstByFields(
+ tableName: String,
+ fields: Collection>,
+ howMatched: FieldMatch? = null,
+ orderBy: Collection>? = null
+ ) = Configuration.dbConn().use { firstByFields(tableName, fields, howMatched, orderBy, it) }
+
+ /**
+ * Retrieve the first document using a field comparison
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param fields The fields which should be compared
+ * @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`)
+ * @param conn The connection over which documents should be retrieved
+ * @return The first JSON document matching the field comparison if found, an empty JSON object otherwise
+ * @throws DocumentException If no dialect has been configured, or if parameters are invalid
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun firstByFields(
+ tableName: String,
+ fields: Collection>,
+ howMatched: FieldMatch? = null,
+ conn: Connection
+ ) = firstByFields(tableName, fields, howMatched, null, conn)
+
+ /**
+ * 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)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param criteria The object for which JSON containment should be checked
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @param conn The connection over which documents should be retrieved
+ * @return The first JSON document matching the JSON containment query if found, an empty JSON object otherwise
+ * @throws DocumentException If called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun firstByContains(
+ tableName: String,
+ criteria: TContains,
+ orderBy: Collection>? = null,
+ conn: Connection
+ ) = Custom.jsonSingle(
+ FindQuery.byContains(tableName) + (orderBy?.let(::orderBy) ?: ""),
+ listOf(Parameters.json(":criteria", criteria)),
+ conn,
+ Results::jsonFromData
+ )
+
+ /**
+ * Retrieve the first document using a JSON containment query (PostgreSQL only)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param criteria The object for which JSON containment should be checked
+ * @param conn The connection over which documents should be retrieved
+ * @return The first JSON document matching the JSON containment query if found, an empty JSON object otherwise
+ * @throws DocumentException If called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun firstByContains(tableName: String, criteria: TContains, conn: Connection) =
+ firstByContains(tableName, criteria, null, conn)
+
+ /**
+ * Retrieve the first document using a JSON containment query and optional ordering fields (PostgreSQL only; creates
+ * connection)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param criteria The object for which JSON containment should be checked
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @return The first JSON document matching the JSON containment query if found, an empty JSON object otherwise
+ * @throws DocumentException If no connection string has been set, or if called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ @JvmOverloads
+ 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)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param path The JSON path comparison to match
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @param conn The connection over which documents should be retrieved
+ * @return The first JSON document matching the JSON Path match query if found, an empty JSON object otherwise
+ * @throws DocumentException If called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun firstByJsonPath(tableName: String, path: String, orderBy: Collection>? = null, conn: Connection) =
+ Custom.jsonSingle(
+ FindQuery.byJsonPath(tableName) + (orderBy?.let(::orderBy) ?: ""),
+ listOf(Parameter(":path", ParameterType.STRING, path)),
+ conn,
+ Results::jsonFromData
+ )
+
+ /**
+ * Retrieve the first document using a JSON Path match query (PostgreSQL only)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param path The JSON path comparison to match
+ * @param conn The connection over which documents should be retrieved
+ * @return The first JSON document matching the JSON Path match query if found, an empty JSON object otherwise
+ * @throws DocumentException If called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun firstByJsonPath(tableName: String, path: String, conn: Connection) =
+ firstByJsonPath(tableName, path, null, conn)
+
+ /**
+ * Retrieve the first document using a JSON Path match query and optional ordering fields (PostgreSQL only; creates
+ * connection)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param path The JSON path comparison to match
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @return The first JSON document matching the JSON Path match query if found, an empty JSON object otherwise
+ * @throws DocumentException If no connection string has been set, or if called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ @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/NullDocumentSerializer.kt b/src/core/src/main/kotlin/java/NullDocumentSerializer.kt
new file mode 100644
index 0000000..233a6ce
--- /dev/null
+++ b/src/core/src/main/kotlin/java/NullDocumentSerializer.kt
@@ -0,0 +1,21 @@
+package solutions.bitbadger.documents.java
+
+import solutions.bitbadger.documents.DocumentSerializer
+
+/**
+ * A serializer that tells the user to implement another one
+ *
+ * This is the default serializer, so the library itself does not have any firm dependency on any JSON serialization
+ * library. The tests for this library (will) have an example Jackson-based serializer.
+ */
+class NullDocumentSerializer : DocumentSerializer {
+
+ override fun serialize(document: TDoc): String {
+ TODO("Replace this serializer in DocumentConfig.serializer")
+ }
+
+ override fun deserialize(json: String, clazz: Class): TDoc {
+ TODO("Replace this serializer in DocumentConfig.serializer")
+ }
+
+}
diff --git a/src/core/src/main/kotlin/java/Parameters.kt b/src/core/src/main/kotlin/java/Parameters.kt
new file mode 100644
index 0000000..b45b64a
--- /dev/null
+++ b/src/core/src/main/kotlin/java/Parameters.kt
@@ -0,0 +1,131 @@
+package solutions.bitbadger.documents.java
+
+import solutions.bitbadger.documents.*
+import solutions.bitbadger.documents.ParameterName
+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
+ *
+ * @author Daniel J. Summers
+ */
+object Parameters {
+
+ /**
+ * Assign parameter names to any fields that do not have them assigned
+ *
+ * @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
+ }
+ }
+ }
+
+ /**
+ * Create a parameter by encoding a JSON object
+ *
+ * @param name The parameter name
+ * @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, DocumentConfig.serializer.serialize(value))
+
+ /**
+ * Add field parameters to the given set of parameters
+ *
+ * @param fields The fields being compared in the query
+ * @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) }
+
+ /**
+ * Replace the parameter names in the query with question marks
+ *
+ * @param query The query with named placeholders
+ * @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, "?") }
+
+ /**
+ * Apply the given parameters to the given query, returning a prepared statement
+ *
+ * @param conn The active JDBC connection
+ * @param query The query
+ * @param parameters The parameters for the query
+ * @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)
+ }
+ }
+
+ /**
+ * Create parameters for field names to be removed from a document
+ *
+ * @param names The names of the fields to be removed
+ * @param parameterName The parameter name to use for the query
+ * @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()
+ }
+}
diff --git a/src/core/src/main/kotlin/java/Patch.kt b/src/core/src/main/kotlin/java/Patch.kt
new file mode 100644
index 0000000..1a03d00
--- /dev/null
+++ b/src/core/src/main/kotlin/java/Patch.kt
@@ -0,0 +1,155 @@
+package solutions.bitbadger.documents.java
+
+import solutions.bitbadger.documents.*
+import solutions.bitbadger.documents.query.PatchQuery
+import java.sql.Connection
+import kotlin.jvm.Throws
+
+/**
+ * Functions to patch (partially update) documents
+ */
+object Patch {
+
+ /**
+ * Patch a document by its ID
+ *
+ * @param tableName The name of the table in which a document should be patched
+ * @param docId The ID of the document to be patched
+ * @param patch The object whose properties should be replaced in the document
+ * @param conn The connection on which the update should be executed
+ * @throws DocumentException If no dialect has been configured
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byId(tableName: String, docId: TKey, patch: TPatch, conn: Connection) =
+ Custom.nonQuery(
+ PatchQuery.byId(tableName, docId),
+ Parameters.addFields(
+ listOf(Field.equal(Configuration.idField, docId, ":id")),
+ mutableListOf(Parameters.json(":data", patch))
+ ),
+ conn
+ )
+
+ /**
+ * Patch a document by its ID
+ *
+ * @param tableName The name of the table in which a document should be patched
+ * @param docId The ID of the document to be patched
+ * @param patch The object whose properties should be replaced in the document
+ * @throws DocumentException If no connection string has been set
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byId(tableName: String, docId: TKey, patch: TPatch) =
+ Configuration.dbConn().use { byId(tableName, docId, patch, it) }
+
+ /**
+ * Patch documents using a field comparison
+ *
+ * @param tableName The name of the table in which documents should be patched
+ * @param fields The fields which should be compared
+ * @param patch The object whose properties should be replaced in the document
+ * @param howMatched How the fields should be matched
+ * @param conn The connection on which the update should be executed
+ * @throws DocumentException If no dialect has been configured, or if parameters are invalid
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byFields(
+ tableName: String,
+ fields: Collection>,
+ patch: TPatch,
+ howMatched: FieldMatch? = null,
+ conn: Connection
+ ) {
+ val named = Parameters.nameFields(fields)
+ Custom.nonQuery(
+ PatchQuery.byFields(tableName, named, howMatched),
+ Parameters.addFields(named, mutableListOf(Parameters.json(":data", patch))),
+ conn
+ )
+ }
+
+ /**
+ * Patch documents using a field comparison
+ *
+ * @param tableName The name of the table in which documents should be patched
+ * @param fields The fields which should be compared
+ * @param patch The object whose properties should be replaced in the document
+ * @param howMatched How the fields should be matched
+ * @throws DocumentException If no connection string has been set, or if parameters are invalid
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ @JvmOverloads
+ fun byFields(
+ tableName: String,
+ fields: Collection>,
+ patch: TPatch,
+ howMatched: FieldMatch? = null
+ ) =
+ Configuration.dbConn().use { byFields(tableName, fields, patch, howMatched, it) }
+
+ /**
+ * Patch documents using a JSON containment query (PostgreSQL only)
+ *
+ * @param tableName The name of the table in which documents should be patched
+ * @param criteria The object against which JSON containment should be checked
+ * @param patch The object whose properties should be replaced in the document
+ * @param conn The connection on which the update should be executed
+ * @throws DocumentException If called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byContains(tableName: String, criteria: TContains, patch: TPatch, conn: Connection) =
+ Custom.nonQuery(
+ PatchQuery.byContains(tableName),
+ listOf(Parameters.json(":criteria", criteria), Parameters.json(":data", patch)),
+ conn
+ )
+
+ /**
+ * Patch documents using a JSON containment query (PostgreSQL only)
+ *
+ * @param tableName The name of the table in which documents should be patched
+ * @param criteria The object against which JSON containment should be checked
+ * @param patch The object whose properties should be replaced in the document
+ * @throws DocumentException If no connection string has been set, or if called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byContains(tableName: String, criteria: TContains, patch: TPatch) =
+ Configuration.dbConn().use { byContains(tableName, criteria, patch, it) }
+
+ /**
+ * Patch documents using a JSON Path match query (PostgreSQL only)
+ *
+ * @param tableName The name of the table in which documents should be patched
+ * @param path The JSON path comparison to match
+ * @param patch The object whose properties should be replaced in the document
+ * @param conn The connection on which the update should be executed
+ * @throws DocumentException If called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byJsonPath(tableName: String, path: String, patch: TPatch, conn: Connection) =
+ Custom.nonQuery(
+ PatchQuery.byJsonPath(tableName),
+ listOf(Parameter(":path", ParameterType.STRING, path), Parameters.json(":data", patch)),
+ conn
+ )
+
+ /**
+ * Patch documents using a JSON Path match query (PostgreSQL only)
+ *
+ * @param tableName The name of the table in which documents should be patched
+ * @param path The JSON path comparison to match
+ * @param patch The object whose properties should be replaced in the document
+ * @throws DocumentException If no connection string has been set, or if called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byJsonPath(tableName: String, path: String, patch: TPatch) =
+ Configuration.dbConn().use { byJsonPath(tableName, path, patch, it) }
+}
diff --git a/src/core/src/main/kotlin/java/RemoveFields.kt b/src/core/src/main/kotlin/java/RemoveFields.kt
new file mode 100644
index 0000000..540d504
--- /dev/null
+++ b/src/core/src/main/kotlin/java/RemoveFields.kt
@@ -0,0 +1,178 @@
+package solutions.bitbadger.documents.java
+
+import solutions.bitbadger.documents.*
+import solutions.bitbadger.documents.query.RemoveFieldsQuery
+import java.sql.Connection
+import kotlin.jvm.Throws
+
+/**
+ * Functions to remove fields from documents
+ */
+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
+ *
+ * @param tableName The name of the table in which the document's fields should be removed
+ * @param docId The ID of the document to have fields removed
+ * @param toRemove The names of the fields to be removed
+ * @param conn The connection on which the update should be executed
+ * @throws DocumentException If no dialect has been configured
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byId(tableName: String, docId: TKey, toRemove: Collection, conn: Connection) {
+ val nameParams = Parameters.fieldNames(toRemove)
+ Custom.nonQuery(
+ RemoveFieldsQuery.byId(tableName, nameParams, docId),
+ Parameters.addFields(listOf(Field.equal(Configuration.idField, docId, ":id")), translatePath(nameParams)),
+ conn
+ )
+ }
+
+ /**
+ * Remove fields from a document by its ID
+ *
+ * @param tableName The name of the table in which the document's fields should be removed
+ * @param docId The ID of the document to have fields removed
+ * @param toRemove The names of the fields to be removed
+ * @throws DocumentException If no connection string has been set
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byId(tableName: String, docId: TKey, toRemove: Collection) =
+ Configuration.dbConn().use { byId(tableName, docId, toRemove, it) }
+
+ /**
+ * Remove fields from documents using a field comparison
+ *
+ * @param tableName The name of the table in which document fields should be removed
+ * @param fields The fields which should be compared
+ * @param toRemove The names of the fields to be removed
+ * @param howMatched How the fields should be matched
+ * @param conn The connection on which the update should be executed
+ * @throws DocumentException If no dialect has been configured, or if parameters are invalid
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun byFields(
+ tableName: String,
+ fields: Collection>,
+ toRemove: Collection,
+ howMatched: FieldMatch? = null,
+ conn: Connection
+ ) {
+ val named = Parameters.nameFields(fields)
+ val nameParams = Parameters.fieldNames(toRemove)
+ Custom.nonQuery(
+ RemoveFieldsQuery.byFields(tableName, nameParams, named, howMatched),
+ Parameters.addFields(named, translatePath(nameParams)),
+ conn
+ )
+ }
+
+ /**
+ * Remove fields from documents using a field comparison
+ *
+ * @param tableName The name of the table in which document fields should be removed
+ * @param fields The fields which should be compared
+ * @param toRemove The names of the fields to be removed
+ * @param howMatched How the fields should be matched
+ * @throws DocumentException If no connection string has been set, or if parameters are invalid
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ @JvmOverloads
+ fun byFields(
+ tableName: String,
+ fields: Collection>,
+ toRemove: Collection,
+ howMatched: FieldMatch? = null
+ ) =
+ Configuration.dbConn().use { byFields(tableName, fields, toRemove, howMatched, it) }
+
+ /**
+ * Remove fields from documents using a JSON containment query (PostgreSQL only)
+ *
+ * @param tableName The name of the table in which document fields should be removed
+ * @param criteria The object against which JSON containment should be checked
+ * @param toRemove The names of the fields to be removed
+ * @param conn The connection on which the update should be executed
+ * @throws DocumentException If called on a SQLite connection
+ */
+ @Throws(DocumentException::class)
+ @JvmStatic
+ fun