diff --git a/documents.iml b/documents.iml
index ab059ae..f0b3e9b 100644
--- a/documents.iml
+++ b/documents.iml
@@ -2,10 +2,14 @@
-
-
-
-
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/common/src/main/kotlin/DocumentSerializer.kt b/src/common/src/main/kotlin/DocumentSerializer.kt
new file mode 100644
index 0000000..09cb1c9
--- /dev/null
+++ b/src/common/src/main/kotlin/DocumentSerializer.kt
@@ -0,0 +1,24 @@
+package solutions.bitbadger.documents.common
+
+/**
+ * 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/common/src/test/java/solutions/bitbadger/documents/common/java/AutoIdTest.java b/src/common/src/test/java/solutions/bitbadger/documents/common/java/AutoIdTest.java
index 1708332..9321974 100644
--- a/src/common/src/test/java/solutions/bitbadger/documents/common/java/AutoIdTest.java
+++ b/src/common/src/test/java/solutions/bitbadger/documents/common/java/AutoIdTest.java
@@ -4,7 +4,7 @@ import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import solutions.bitbadger.documents.common.AutoId;
import solutions.bitbadger.documents.common.DocumentException;
-import solutions.bitbadger.documents.java.testDocs.*;
+import solutions.bitbadger.documents.java.java.testDocs.*;
import static org.junit.jupiter.api.Assertions.*;
diff --git a/src/common/src/test/kotlin/ComparisonTest.kt b/src/common/src/test/kotlin/ComparisonTest.kt
index 3cd54ad..b40a89e 100644
--- a/src/common/src/test/kotlin/ComparisonTest.kt
+++ b/src/common/src/test/kotlin/ComparisonTest.kt
@@ -2,7 +2,6 @@ package solutions.bitbadger.documents.common
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
-import solutions.bitbadger.documents.integration.TEST_TABLE
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@@ -101,7 +100,7 @@ class ComparisonInArrayTest {
fun op() =
assertEquals(
Op.IN_ARRAY,
- ComparisonInArray(Pair(TEST_TABLE, listOf())).op,
+ ComparisonInArray(Pair("tbl", listOf())).op,
"InArray comparison should have IN_ARRAY op"
)
diff --git a/src/common/src/test/kotlin/query/RemoveFieldsTest.kt b/src/common/src/test/kotlin/query/RemoveFieldsTest.kt
index 498a435..1710e0b 100644
--- a/src/common/src/test/kotlin/query/RemoveFieldsTest.kt
+++ b/src/common/src/test/kotlin/query/RemoveFieldsTest.kt
@@ -4,11 +4,7 @@ import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
-import solutions.bitbadger.documents.*
-import solutions.bitbadger.documents.common.Configuration
-import solutions.bitbadger.documents.common.Dialect
-import solutions.bitbadger.documents.common.DocumentException
-import solutions.bitbadger.documents.common.Field
+import solutions.bitbadger.documents.common.*
import kotlin.test.assertEquals
/**
@@ -34,7 +30,7 @@ class RemoveFieldsTest {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals(
"UPDATE $tbl SET data = data - :name::text[] WHERE data->>'id' = :id",
- RemoveFields.byId(tbl, Parameters.fieldNames(listOf("a", "z"))),
+ RemoveFields.byId(tbl, listOf(Parameter(":name", ParameterType.STRING, "{a,z}"))),
"Remove Fields query not constructed correctly"
)
}
@@ -45,7 +41,13 @@ class RemoveFieldsTest {
Configuration.dialectValue = Dialect.SQLITE
assertEquals(
"UPDATE $tbl SET data = json_remove(data, :name0, :name1) WHERE data->>'id' = :id",
- RemoveFields.byId(tbl, Parameters.fieldNames(listOf("a", "z"))),
+ RemoveFields.byId(
+ tbl,
+ listOf(
+ Parameter(":name0", ParameterType.STRING, "a"),
+ Parameter(":name1", ParameterType.STRING, "z")
+ )
+ ),
"Remove Field query not constructed correctly"
)
}
@@ -56,7 +58,11 @@ class RemoveFieldsTest {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals(
"UPDATE $tbl SET data = data - :name::text[] WHERE data->>'f' > :g",
- RemoveFields.byFields(tbl, Parameters.fieldNames(listOf("b", "c")), listOf(Field.greater("f", "", ":g"))),
+ RemoveFields.byFields(
+ tbl,
+ listOf(Parameter(":name", ParameterType.STRING, "{b,c}")),
+ listOf(Field.greater("f", "", ":g"))
+ ),
"Remove Field query not constructed correctly"
)
}
@@ -67,7 +73,11 @@ class RemoveFieldsTest {
Configuration.dialectValue = Dialect.SQLITE
assertEquals(
"UPDATE $tbl SET data = json_remove(data, :name0, :name1) WHERE data->>'f' > :g",
- RemoveFields.byFields(tbl, Parameters.fieldNames(listOf("b", "c")), listOf(Field.greater("f", "", ":g"))),
+ RemoveFields.byFields(
+ tbl,
+ listOf(Parameter(":name0", ParameterType.STRING, "b"), Parameter(":name1", ParameterType.STRING, "c")),
+ listOf(Field.greater("f", "", ":g"))
+ ),
"Remove Field query not constructed correctly"
)
}
@@ -78,7 +88,7 @@ class RemoveFieldsTest {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals(
"UPDATE $tbl SET data = data - :name::text[] WHERE data @> :criteria",
- RemoveFields.byContains(tbl, Parameters.fieldNames(listOf("m", "n"))),
+ RemoveFields.byContains(tbl, listOf(Parameter(":name", ParameterType.STRING, "{m,n}"))),
"Remove Field query not constructed correctly"
)
}
@@ -96,7 +106,7 @@ class RemoveFieldsTest {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals(
"UPDATE $tbl SET data = data - :name::text[] WHERE jsonb_path_exists(data, :path::jsonpath)",
- RemoveFields.byJsonPath(tbl, Parameters.fieldNames(listOf("o", "p"))),
+ RemoveFields.byJsonPath(tbl, listOf(Parameter(":name", ParameterType.STRING, "{o,p}"))),
"Remove Field query not constructed correctly"
)
}
diff --git a/src/java/pom.xml b/src/java/pom.xml
new file mode 100644
index 0000000..0b272c0
--- /dev/null
+++ b/src/java/pom.xml
@@ -0,0 +1,113 @@
+
+
+ 4.0.0
+
+ solutions.bitbadger.documents
+ java
+ 4.0.0-alpha1-SNAPSHOT
+ jar
+
+
+ solutions.bitbadger
+ documents
+ 4.0.0-alpha1-SNAPSHOT
+
+
+ ${project.groupId}:${project.artifactId}
+ Expose a document store interface for PostgreSQL and SQLite (Java Library)
+ https://bitbadger.solutions/open-source/relational-documents/jvm/
+
+
+ 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
+
+
+
+
+ solutions.bitbadger.documents
+ common
+ 4.0.0-alpha1-SNAPSHOT
+ system
+ ${project.basedir}/../common/target/common-4.0.0-alpha1-SNAPSHOT.jar
+ jar
+
+
+
+
+ src/main/kotlin
+ src/test/kotlin
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+ ${kotlin.version}
+
+
+ compile
+ process-sources
+
+ compile
+
+
+
+ ${project.basedir}/src/main/kotlin
+
+
+
+
+ test-compile
+ test-compile
+
+ test-compile
+
+
+
+ ${project.basedir}/src/test/java
+ ${project.basedir}/src/test/kotlin
+
+
+
+
+
+
+ kotlinx-serialization
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-serialization
+ ${kotlin.version}
+
+
+
+
+ maven-surefire-plugin
+ 2.22.2
+
+
+ maven-failsafe-plugin
+ 2.22.2
+
+
+
+ integration-test
+ verify
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ ${java.version}
+ ${java.version}
+
+
+
+
+
diff --git a/src/java/src/main/kotlin/ConnectionExtensions.kt b/src/java/src/main/kotlin/ConnectionExtensions.kt
new file mode 100644
index 0000000..a5230c2
--- /dev/null
+++ b/src/java/src/main/kotlin/ConnectionExtensions.kt
@@ -0,0 +1,477 @@
+package solutions.bitbadger.documents.java
+
+import solutions.bitbadger.documents.Custom
+import solutions.bitbadger.documents.common.DocumentIndex
+import solutions.bitbadger.documents.common.Field
+import solutions.bitbadger.documents.common.FieldMatch
+import solutions.bitbadger.documents.common.Parameter
+import solutions.bitbadger.documents.java.*
+import java.sql.Connection
+import java.sql.ResultSet
+
+// ~~~ CUSTOM QUERIES ~~~
+
+/**
+ * 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 mapFunc The mapping function between the document and the domain item
+ * @return A list of results for the given query
+ */
+inline fun Connection.customList(
+ query: String, parameters: Collection> = listOf(), noinline mapFunc: (ResultSet, Class) -> TDoc
+) = Custom.list(query, parameters, this, 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 mapFunc The mapping function between the document and the domain item
+ * @return The document if one matches the query, `null` otherwise
+ */
+inline fun Connection.customSingle(
+ query: String, parameters: Collection> = listOf(), noinline mapFunc: (ResultSet, Class) -> TDoc
+) = Custom.single(query, parameters, this, mapFunc)
+
+/**
+ * Execute a query that returns no results
+ *
+ * @param query The query to retrieve the results
+ * @param parameters Parameters to use for the query
+ */
+fun Connection.customNonQuery(query: String, parameters: Collection> = listOf()) =
+ Custom.nonQuery(query, parameters, this)
+
+/**
+ * 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
+ */
+inline fun Connection.customScalar(
+ query: String,
+ parameters: Collection> = listOf(),
+ noinline mapFunc: (ResultSet, Class) -> T
+) = Custom.scalar(query, parameters, this, mapFunc)
+
+// ~~~ DEFINITION QUERIES ~~~
+
+/**
+ * Create a document table if necessary
+ *
+ * @param tableName The table whose existence should be ensured (may include schema)
+ */
+fun Connection.ensureTable(tableName: String) =
+ solutions.bitbadger.documents.java.Definition.ensureTable(tableName, this)
+
+/**
+ * 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<
+ */
+fun Connection.ensureFieldIndex(tableName: String, indexName: String, fields: Collection) =
+ solutions.bitbadger.documents.java.Definition.ensureFieldIndex(tableName, indexName, fields, this)
+
+/**
+ * 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 called on a SQLite connection
+ */
+fun Connection.ensureDocumentIndex(tableName: String, indexType: DocumentIndex) =
+ solutions.bitbadger.documents.java.Definition.ensureDocumentIndex(tableName, indexType, this)
+
+// ~~~ DOCUMENT MANIPULATION QUERIES ~~~
+
+/**
+ * Insert a new document
+ *
+ * @param tableName The table into which the document should be inserted (may include schema)
+ * @param document The document to be inserted
+ */
+inline fun Connection.insert(tableName: String, document: TDoc) =
+ Document.insert(tableName, document, this)
+
+/**
+ * Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
+ *
+ * @param tableName The table in which the document should be saved (may include schema)
+ * @param document The document to be saved
+ */
+inline fun Connection.save(tableName: String, document: TDoc) =
+ Document.save(tableName, document, this)
+
+/**
+ * Update (replace) a document by its ID
+ *
+ * @param tableName The table in which the document should be replaced (may include schema)
+ * @param docId The ID of the document to be replaced
+ * @param document The document to be replaced
+ */
+inline fun Connection.update(tableName: String, docId: TKey, document: TDoc) =
+ Document.update(tableName, docId, document, this)
+
+// ~~~ DOCUMENT COUNT QUERIES ~~~
+
+/**
+ * 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
+ */
+fun Connection.countAll(tableName: String) =
+ Count.all(tableName, this)
+
+/**
+ * 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
+ */
+fun Connection.countByFields(tableName: String, fields: Collection>, howMatched: FieldMatch? = null) =
+ Count.byFields(tableName, fields, howMatched, this)
+
+/**
+ * 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 called on a SQLite connection
+ */
+inline fun Connection.countByContains(tableName: String, criteria: TContains) =
+ Count.byContains(tableName, criteria, this)
+
+/**
+ * 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 called on a SQLite connection
+ */
+fun Connection.countByJsonPath(tableName: String, path: String) =
+ Count.byJsonPath(tableName, path, this)
+
+// ~~~ DOCUMENT EXISTENCE QUERIES ~~~
+
+/**
+ * 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
+ */
+fun Connection.existsById(tableName: String, docId: TKey) =
+ Exists.byId(tableName, docId, this)
+
+/**
+ * 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
+ */
+fun Connection.existsByFields(tableName: String, fields: Collection>, howMatched: FieldMatch? = null) =
+ Exists.byFields(tableName, fields, howMatched, this)
+
+/**
+ * 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 called on a SQLite connection
+ */
+inline fun Connection.existsByContains(tableName: String, criteria: TContains) =
+ Exists.byContains(tableName, criteria, this)
+
+/**
+ * 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 called on a SQLite connection
+ */
+fun Connection.existsByJsonPath(tableName: String, path: String) =
+ Exists.byJsonPath(tableName, path, this)
+
+// ~~~ DOCUMENT RETRIEVAL QUERIES ~~~
+
+/**
+ * Retrieve all documents in the given table, ordering results by the optional given fields
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @return A list of documents from the given table
+ */
+inline fun Connection.findAll(tableName: String, orderBy: Collection>? = null) =
+ solutions.bitbadger.documents.java.Find.all(tableName, orderBy, this)
+
+/**
+ * Retrieve a document by its ID
+ *
+ * @param tableName The table from which the document should be retrieved
+ * @param docId The ID of the document to retrieve
+ * @return The document if it is found, `null` otherwise
+ */
+inline fun Connection.findById(tableName: String, docId: TKey) =
+ solutions.bitbadger.documents.java.Find.byId(tableName, docId, this)
+
+/**
+ * Retrieve documents using a field comparison, ordering results by the optional given fields
+ *
+ * @param tableName The table from which the document should be retrieved
+ * @param fields The fields which should be compared
+ * @param howMatched How the fields should be matched
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @return A list of documents matching the field comparison
+ */
+inline fun Connection.findByFields(
+ tableName: String,
+ fields: Collection>,
+ howMatched: FieldMatch? = null,
+ orderBy: Collection>? = null
+) =
+ solutions.bitbadger.documents.java.Find.byFields(tableName, fields, howMatched, orderBy, this)
+
+/**
+ * Retrieve documents using a JSON containment query, ordering results by the optional given fields (PostgreSQL only)
+ *
+ * @param tableName The name of the table in which document existence should be checked
+ * @param criteria The object for which JSON containment should be checked
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @return A list of documents matching the JSON containment query
+ * @throws DocumentException If called on a SQLite connection
+ */
+inline fun Connection.findByContains(
+ tableName: String,
+ criteria: TContains,
+ orderBy: Collection>? = null
+) =
+ solutions.bitbadger.documents.java.Find.byContains(tableName, criteria, orderBy, this)
+
+/**
+ * Retrieve documents using a JSON Path match query, ordering results by the optional given fields (PostgreSQL only)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param path The JSON path comparison to match
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @return A list of documents matching the JSON Path match query
+ * @throws DocumentException If called on a SQLite connection
+ */
+inline fun Connection.findByJsonPath(
+ tableName: String,
+ path: String,
+ orderBy: Collection>? = null
+) =
+ solutions.bitbadger.documents.java.Find.byJsonPath(tableName, path, orderBy, this)
+
+/**
+ * Retrieve the first document using a field comparison and optional ordering fields
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param fields The fields which should be compared
+ * @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`)
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @return The first document matching the field comparison, or `null` if no matches are found
+ */
+inline fun Connection.findFirstByFields(
+ tableName: String,
+ fields: Collection>,
+ howMatched: FieldMatch? = null,
+ orderBy: Collection>? = null
+) =
+ solutions.bitbadger.documents.java.Find.firstByFields(tableName, fields, howMatched, orderBy, this)
+
+/**
+ * Retrieve the first document using a JSON containment query and optional ordering fields (PostgreSQL only)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param criteria The object for which JSON containment should be checked
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @return The first document matching the JSON containment query, or `null` if no matches are found
+ * @throws DocumentException If called on a SQLite connection
+ */
+inline fun Connection.findFirstByContains(
+ tableName: String,
+ criteria: TContains,
+ orderBy: Collection>? = null
+) =
+ solutions.bitbadger.documents.java.Find.firstByContains(tableName, criteria, orderBy, this)
+
+/**
+ * Retrieve the first document using a JSON Path match query and optional ordering fields (PostgreSQL only)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param path The JSON path comparison to match
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @return The first document matching the JSON Path match query, or `null` if no matches are found
+ * @throws DocumentException If called on a SQLite connection
+ */
+inline fun Connection.findFirstByJsonPath(
+ tableName: String,
+ path: String,
+ orderBy: Collection>? = null
+) =
+ solutions.bitbadger.documents.java.Find.firstByJsonPath(tableName, path, orderBy, this)
+
+// ~~~ DOCUMENT PATCH (PARTIAL UPDATE) QUERIES ~~~
+
+/**
+ * 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
+ */
+inline fun Connection.patchById(tableName: String, docId: TKey, patch: TPatch) =
+ Patch.byId(tableName, docId, patch, this)
+
+/**
+ * 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
+ */
+inline fun Connection.patchByFields(
+ tableName: String,
+ fields: Collection>,
+ patch: TPatch,
+ howMatched: FieldMatch? = null
+) =
+ Patch.byFields(tableName, fields, patch, howMatched, this)
+
+/**
+ * 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 called on a SQLite connection
+ */
+inline fun Connection.patchByContains(
+ tableName: String,
+ criteria: TContains,
+ patch: TPatch
+) =
+ Patch.byContains(tableName, criteria, patch, this)
+
+/**
+ * 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 called on a SQLite connection
+ */
+inline fun Connection.patchByJsonPath(tableName: String, path: String, patch: TPatch) =
+ Patch.byJsonPath(tableName, path, patch, this)
+
+// ~~~ DOCUMENT FIELD REMOVAL QUERIES ~~~
+
+/**
+ * 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
+ */
+fun Connection.removeFieldsById(tableName: String, docId: TKey, toRemove: Collection) =
+ RemoveFields.byId(tableName, docId, toRemove, this)
+
+/**
+ * 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
+ */
+fun Connection.removeFieldsByFields(
+ tableName: String,
+ fields: Collection>,
+ toRemove: Collection,
+ howMatched: FieldMatch? = null
+) =
+ RemoveFields.byFields(tableName, fields, toRemove, howMatched, this)
+
+/**
+ * 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
+ * @throws DocumentException If called on a SQLite connection
+ */
+inline fun Connection.removeFieldsByContains(
+ tableName: String,
+ criteria: TContains,
+ toRemove: Collection
+) =
+ RemoveFields.byContains(tableName, criteria, toRemove, this)
+
+/**
+ * Remove fields from documents using a JSON Path match query (PostgreSQL only)
+ *
+ * @param tableName The name of the table in which document fields should be removed
+ * @param path The JSON path comparison to match
+ * @param toRemove The names of the fields to be removed
+ * @throws DocumentException If called on a SQLite connection
+ */
+fun Connection.removeFieldsByJsonPath(tableName: String, path: String, toRemove: Collection) =
+ RemoveFields.byJsonPath(tableName, path, toRemove, this)
+
+// ~~~ DOCUMENT DELETION QUERIES ~~~
+
+/**
+ * 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
+ */
+fun Connection.deleteById(tableName: String, docId: TKey) =
+ Delete.byId(tableName, docId, this)
+
+/**
+ * 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
+ */
+fun Connection.deleteByFields(tableName: String, fields: Collection>, howMatched: FieldMatch? = null) =
+ Delete.byFields(tableName, fields, howMatched, this)
+
+/**
+ * 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 called on a SQLite connection
+ */
+inline fun Connection.deleteByContains(tableName: String, criteria: TContains) =
+ Delete.byContains(tableName, criteria, this)
+
+/**
+ * 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 called on a SQLite connection
+ */
+fun Connection.deleteByJsonPath(tableName: String, path: String) =
+ Delete.byJsonPath(tableName, path, this)
diff --git a/src/java/src/main/kotlin/Count.kt b/src/java/src/main/kotlin/Count.kt
new file mode 100644
index 0000000..5d42b38
--- /dev/null
+++ b/src/java/src/main/kotlin/Count.kt
@@ -0,0 +1,127 @@
+package solutions.bitbadger.documents.java
+
+import solutions.bitbadger.documents.Results
+import solutions.bitbadger.documents.common.Field
+import solutions.bitbadger.documents.common.FieldMatch
+import solutions.bitbadger.documents.common.Parameter
+import solutions.bitbadger.documents.common.ParameterType
+import java.sql.Connection
+
+/**
+ * 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
+ */
+ @JvmStatic
+ fun all(tableName: String, conn: Connection) =
+ conn.customScalar(all(tableName), mapFunc = 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
+ */
+ @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
+ */
+ @JvmStatic
+ @JvmOverloads
+ fun byFields(
+ tableName: String,
+ fields: Collection>,
+ howMatched: FieldMatch? = null,
+ conn: Connection
+ ): Long {
+ val named = Parameters.nameFields(fields)
+ return conn.customScalar(
+ byFields(tableName, named, howMatched),
+ Parameters.addFields(named),
+ 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
+ */
+ @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
+ */
+ @JvmStatic
+ inline fun byContains(tableName: String, criteria: TContains, conn: Connection) =
+ conn.customScalar(Count.byContains(tableName), listOf(Parameters.json(":criteria", criteria)), 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 called on a SQLite connection
+ */
+ @JvmStatic
+ inline 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
+ */
+ @JvmStatic
+ fun byJsonPath(tableName: String, path: String, conn: Connection) =
+ conn.customScalar(
+ Count.byJsonPath(tableName),
+ listOf(Parameter(":path", ParameterType.STRING, path)),
+ 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 called on a SQLite connection
+ */
+ @JvmStatic
+ fun byJsonPath(tableName: String, path: String) =
+ Configuration.dbConn().use { byJsonPath(tableName, path, it) }
+}
diff --git a/src/main/kotlin/java/Custom.kt b/src/java/src/main/kotlin/Custom.kt
similarity index 99%
rename from src/main/kotlin/java/Custom.kt
rename to src/java/src/main/kotlin/Custom.kt
index 7d5ee67..5503022 100644
--- a/src/main/kotlin/java/Custom.kt
+++ b/src/java/src/main/kotlin/Custom.kt
@@ -2,7 +2,6 @@ package solutions.bitbadger.documents.java
import solutions.bitbadger.documents.common.Configuration
import solutions.bitbadger.documents.common.Parameter
-import solutions.bitbadger.documents.Parameters
import java.sql.Connection
import java.sql.ResultSet
diff --git a/src/main/kotlin/Definition.kt b/src/java/src/main/kotlin/Definition.kt
similarity index 96%
rename from src/main/kotlin/Definition.kt
rename to src/java/src/main/kotlin/Definition.kt
index 424d742..7cd749b 100644
--- a/src/main/kotlin/Definition.kt
+++ b/src/java/src/main/kotlin/Definition.kt
@@ -1,8 +1,7 @@
-package solutions.bitbadger.documents
+package solutions.bitbadger.documents.java
import solutions.bitbadger.documents.common.DocumentIndex
import java.sql.Connection
-import solutions.bitbadger.documents.common.query.Definition
/**
* Functions to define tables and indexes
diff --git a/src/java/src/main/kotlin/Delete.kt b/src/java/src/main/kotlin/Delete.kt
new file mode 100644
index 0000000..6f9bd52
--- /dev/null
+++ b/src/java/src/main/kotlin/Delete.kt
@@ -0,0 +1,100 @@
+package solutions.bitbadger.documents.java
+
+import solutions.bitbadger.documents.common.Field
+import solutions.bitbadger.documents.common.FieldMatch
+import solutions.bitbadger.documents.common.Parameter
+import solutions.bitbadger.documents.common.ParameterType
+import java.sql.Connection
+
+/**
+ * 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
+ */
+ fun byId(tableName: String, docId: TKey, conn: Connection) =
+ conn.customNonQuery(
+ byId(tableName, docId),
+ Parameters.addFields(listOf(Field.equal(Configuration.idField, docId, ":id")))
+ )
+
+ /**
+ * 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
+ */
+ 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
+ */
+ fun byFields(tableName: String, fields: Collection>, howMatched: FieldMatch? = null, conn: Connection) {
+ val named = Parameters.nameFields(fields)
+ conn.customNonQuery(byFields(tableName, named, howMatched), Parameters.addFields(named))
+ }
+
+ /**
+ * 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
+ */
+ 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
+ */
+ inline fun byContains(tableName: String, criteria: TContains, conn: Connection) =
+ conn.customNonQuery(Delete.byContains(tableName), listOf(Parameters.json(":criteria", criteria)))
+
+ /**
+ * 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 called on a SQLite connection
+ */
+ inline 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
+ */
+ fun byJsonPath(tableName: String, path: String, conn: Connection) =
+ conn.customNonQuery(Delete.byJsonPath(tableName), listOf(Parameter(":path", ParameterType.STRING, path)))
+
+ /**
+ * 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 called on a SQLite connection
+ */
+ fun byJsonPath(tableName: String, path: String) =
+ Configuration.dbConn().use { byJsonPath(tableName, path, it) }
+}
diff --git a/src/main/kotlin/Document.kt b/src/java/src/main/kotlin/Document.kt
similarity index 98%
rename from src/main/kotlin/Document.kt
rename to src/java/src/main/kotlin/Document.kt
index 6984e5d..6a29a7c 100644
--- a/src/main/kotlin/Document.kt
+++ b/src/java/src/main/kotlin/Document.kt
@@ -1,6 +1,7 @@
-package solutions.bitbadger.documents
+package solutions.bitbadger.documents.java
import solutions.bitbadger.documents.common.AutoId
+import solutions.bitbadger.documents.common.Configuration
import solutions.bitbadger.documents.common.Dialect
import solutions.bitbadger.documents.common.Field
import java.sql.Connection
diff --git a/src/java/src/main/kotlin/DocumentConfig.kt b/src/java/src/main/kotlin/DocumentConfig.kt
new file mode 100644
index 0000000..81431d7
--- /dev/null
+++ b/src/java/src/main/kotlin/DocumentConfig.kt
@@ -0,0 +1,15 @@
+package solutions.bitbadger.documents.java
+
+import solutions.bitbadger.documents.common.DocumentSerializer
+
+/**
+ * Configuration for document serialization
+ */
+object DocumentConfig {
+
+ /**
+ * The serializer to use for documents
+ */
+ @JvmStatic
+ var serializer: DocumentSerializer = NullDocumentSerializer()
+}
diff --git a/src/java/src/main/kotlin/Exists.kt b/src/java/src/main/kotlin/Exists.kt
new file mode 100644
index 0000000..5f9deda
--- /dev/null
+++ b/src/java/src/main/kotlin/Exists.kt
@@ -0,0 +1,127 @@
+package solutions.bitbadger.documents.java
+
+import solutions.bitbadger.documents.Results
+import solutions.bitbadger.documents.common.Field
+import solutions.bitbadger.documents.common.FieldMatch
+import solutions.bitbadger.documents.common.Parameter
+import solutions.bitbadger.documents.common.ParameterType
+import java.sql.Connection
+
+/**
+ * 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
+ */
+ fun byId(tableName: String, docId: TKey, conn: Connection) =
+ conn.customScalar(
+ byId(tableName, docId),
+ Parameters.addFields(listOf(Field.equal(Configuration.idField, docId, ":id"))),
+ 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
+ */
+ 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
+ */
+ fun byFields(
+ tableName: String,
+ fields: Collection>,
+ howMatched: FieldMatch? = null,
+ conn: Connection
+ ): Boolean {
+ val named = Parameters.nameFields(fields)
+ return conn.customScalar(
+ byFields(tableName, named, howMatched),
+ Parameters.addFields(named),
+ 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
+ */
+ 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
+ */
+ inline fun byContains(tableName: String, criteria: TContains, conn: Connection) =
+ conn.customScalar(
+ Exists.byContains(tableName),
+ listOf(Parameters.json(":criteria", criteria)),
+ 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 called on a SQLite connection
+ */
+ inline 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
+ */
+ fun byJsonPath(tableName: String, path: String, conn: Connection) =
+ conn.customScalar(
+ Exists.byJsonPath(tableName),
+ listOf(Parameter(":path", ParameterType.STRING, path)),
+ 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 called on a SQLite connection
+ */
+ fun byJsonPath(tableName: String, path: String) =
+ Configuration.dbConn().use { byJsonPath(tableName, path, it) }
+}
diff --git a/src/java/src/main/kotlin/Find.kt b/src/java/src/main/kotlin/Find.kt
new file mode 100644
index 0000000..87f74a5
--- /dev/null
+++ b/src/java/src/main/kotlin/Find.kt
@@ -0,0 +1,403 @@
+package solutions.bitbadger.documents.java
+
+import solutions.bitbadger.documents.Results
+import solutions.bitbadger.documents.common.Field
+import solutions.bitbadger.documents.common.FieldMatch
+import solutions.bitbadger.documents.common.Parameter
+import solutions.bitbadger.documents.common.ParameterType
+import java.sql.Connection
+import solutions.bitbadger.documents.common.query.orderBy
+
+/**
+ * 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 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
+ */
+ inline fun all(tableName: String, orderBy: Collection>? = null, conn: Connection) =
+ conn.customList(all(tableName) + (orderBy?.let(::orderBy) ?: ""), mapFunc = Results::fromData)
+
+ /**
+ * Retrieve all documents in the given table
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @return A list of documents from the given table
+ */
+ inline 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 list of documents from the given table
+ */
+ inline fun all(tableName: String, conn: Connection) =
+ all(tableName, null, conn)
+
+ /**
+ * Retrieve a document by its ID
+ *
+ * @param tableName The table from which the document should be retrieved
+ * @param docId The ID of the document to retrieve
+ * @param conn The connection over which documents should be retrieved
+ * @return The document if it is found, `null` otherwise
+ */
+ inline fun byId(tableName: String, docId: TKey, conn: Connection) =
+ conn.customSingle(
+ byId(tableName, docId),
+ Parameters.addFields(listOf(Field.equal(Configuration.idField, docId, ":id"))),
+ 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
+ * @return The document if it is found, `null` otherwise
+ */
+ inline fun byId(tableName: String, docId: TKey) =
+ Configuration.dbConn().use { byId(tableName, docId, it) }
+
+ /**
+ * Retrieve documents using a field comparison, ordering results by the given fields
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param fields The fields which should be compared
+ * @param howMatched How the fields should be matched
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @param conn The connection over which documents should be retrieved
+ * @return A list of documents matching the field comparison
+ */
+ inline fun byFields(
+ tableName: String,
+ fields: Collection>,
+ howMatched: FieldMatch? = null,
+ orderBy: Collection>? = null,
+ conn: Connection
+ ): List {
+ val named = Parameters.nameFields(fields)
+ return conn.customList(
+ byFields(tableName, named, howMatched) + (orderBy?.let(::orderBy) ?: ""),
+ Parameters.addFields(named),
+ 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 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
+ */
+ inline 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 list of documents matching the field comparison
+ */
+ inline fun byFields(
+ tableName: String,
+ fields: Collection>,
+ howMatched: FieldMatch? = null,
+ conn: Connection
+ ) =
+ byFields(tableName, fields, howMatched, null, conn)
+
+ /**
+ * 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
+ * @return A list of documents matching the field comparison
+ */
+ inline fun byFields(
+ tableName: String,
+ fields: Collection>,
+ howMatched: FieldMatch? = null
+ ) =
+ Configuration.dbConn().use { byFields(tableName, fields, howMatched, null, it) }
+
+ /**
+ * 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 list of documents matching the JSON containment query
+ * @throws DocumentException If called on a SQLite connection
+ */
+ inline fun byContains(
+ tableName: String,
+ criteria: TContains,
+ orderBy: Collection>? = null,
+ conn: Connection
+ ) =
+ conn.customList(
+ Find.byContains(tableName) + (orderBy?.let(::orderBy) ?: ""),
+ listOf(Parameters.json(":criteria", criteria)),
+ 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 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 called on a SQLite connection
+ */
+ inline 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 list of documents matching the JSON containment query
+ * @throws DocumentException If called on a SQLite connection
+ */
+ inline fun byContains(tableName: String, criteria: TContains, conn: Connection) =
+ byContains(tableName, criteria, null, conn)
+
+ /**
+ * 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
+ * @return A list of documents matching the JSON containment query
+ * @throws DocumentException If called on a SQLite connection
+ */
+ inline fun byContains(tableName: String, criteria: TContains) =
+ Configuration.dbConn().use { byContains(tableName, criteria, it) }
+
+ /**
+ * 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 list of documents matching the JSON Path match query
+ * @throws DocumentException If called on a SQLite connection
+ */
+ inline fun byJsonPath(
+ tableName: String,
+ path: String,
+ orderBy: Collection>? = null,
+ conn: Connection
+ ) =
+ conn.customList(
+ byJsonPath(tableName) + (orderBy?.let(::orderBy) ?: ""),
+ listOf(Parameter(":path", ParameterType.STRING, path)),
+ 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 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 called on a SQLite connection
+ */
+ inline 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 list of documents matching the JSON Path match query
+ * @throws DocumentException If called on a SQLite connection
+ */
+ inline fun byJsonPath(tableName: String, path: String, conn: Connection) =
+ byJsonPath(tableName, path, null, conn)
+
+ /**
+ * Retrieve the first document using a field comparison and optional ordering fields
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param fields The fields which should be compared
+ * @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`)
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @param conn The connection over which documents should be retrieved
+ * @return The first document matching the field comparison, or `null` if no matches are found
+ */
+ inline fun firstByFields(
+ tableName: String,
+ fields: Collection>,
+ howMatched: FieldMatch? = null,
+ orderBy: Collection>? = null,
+ conn: Connection
+ ): TDoc? {
+ val named = Parameters.nameFields(fields)
+ return conn.customSingle(
+ byFields(tableName, named, howMatched) + (orderBy?.let(::orderBy) ?: ""),
+ Parameters.addFields(named),
+ 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 howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`)
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @return The first document matching the field comparison, or `null` if no matches are found
+ */
+ inline 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 document matching the field comparison, or `null` if no matches are found
+ */
+ inline fun firstByFields(
+ tableName: String,
+ fields: Collection>,
+ howMatched: FieldMatch? = null,
+ conn: Connection
+ ) =
+ firstByFields(tableName, fields, howMatched, null, conn)
+
+ /**
+ * Retrieve the first document using a JSON containment query and optional ordering fields (PostgreSQL only)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param criteria The object for which JSON containment should be checked
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @param conn The connection over which documents should be retrieved
+ * @return The first document matching the JSON containment query, or `null` if no matches are found
+ * @throws DocumentException If called on a SQLite connection
+ */
+ inline fun firstByContains(
+ tableName: String,
+ criteria: TContains,
+ orderBy: Collection>? = null,
+ conn: Connection
+ ) =
+ conn.customSingle(
+ Find.byContains(tableName) + (orderBy?.let(::orderBy) ?: ""),
+ listOf(Parameters.json(":criteria", criteria)),
+ 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 conn The connection over which documents should be retrieved
+ * @return The first document matching the JSON containment query, or `null` if no matches are found
+ * @throws DocumentException If called on a SQLite connection
+ */
+ inline fun firstByContains(tableName: String, criteria: TContains, conn: Connection) =
+ firstByContains(tableName, criteria, null, conn)
+
+ /**
+ * Retrieve the first document using a JSON containment query and optional ordering fields (PostgreSQL only)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param criteria The object for which JSON containment should be checked
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @return The first document matching the JSON containment query, or `null` if no matches are found
+ * @throws DocumentException If called on a SQLite connection
+ */
+ inline fun firstByContains(tableName: String, criteria: TContains, orderBy: Collection>? = null) =
+ Configuration.dbConn().use { firstByContains(tableName, criteria, orderBy, it) }
+
+ /**
+ * Retrieve the first document using a JSON Path match query and optional ordering fields (PostgreSQL only)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param path The JSON path comparison to match
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @param conn The connection over which documents should be retrieved
+ * @return The first document matching the JSON Path match query, or `null` if no matches are found
+ * @throws DocumentException If called on a SQLite connection
+ */
+ inline fun firstByJsonPath(
+ tableName: String,
+ path: String,
+ orderBy: Collection>? = null,
+ conn: Connection
+ ) =
+ conn.customSingle(
+ byJsonPath(tableName) + (orderBy?.let(::orderBy) ?: ""),
+ listOf(Parameter(":path", ParameterType.STRING, path)),
+ 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 conn The connection over which documents should be retrieved
+ * @return The first document matching the JSON Path match query, or `null` if no matches are found
+ * @throws DocumentException If called on a SQLite connection
+ */
+ inline fun firstByJsonPath(tableName: String, path: String, conn: Connection) =
+ firstByJsonPath(tableName, path, null, conn)
+
+ /**
+ * Retrieve the first document using a JSON Path match query and optional ordering fields (PostgreSQL only)
+ *
+ * @param tableName The table from which documents should be retrieved
+ * @param path The JSON path comparison to match
+ * @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
+ * @return The first document matching the JSON Path match query, or `null` if no matches are found
+ * @throws DocumentException If called on a SQLite connection
+ */
+ inline fun firstByJsonPath(tableName: String, path: String, orderBy: Collection>? = null) =
+ Configuration.dbConn().use { firstByJsonPath(tableName, path, orderBy, it) }
+}
diff --git a/src/java/src/main/kotlin/NullDocumentSerializer.kt b/src/java/src/main/kotlin/NullDocumentSerializer.kt
new file mode 100644
index 0000000..0fd5ea4
--- /dev/null
+++ b/src/java/src/main/kotlin/NullDocumentSerializer.kt
@@ -0,0 +1,21 @@
+package solutions.bitbadger.documents.java
+
+import solutions.bitbadger.documents.common.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/java/src/main/kotlin/Parameters.kt b/src/java/src/main/kotlin/Parameters.kt
new file mode 100644
index 0000000..e8b94b6
--- /dev/null
+++ b/src/java/src/main/kotlin/Parameters.kt
@@ -0,0 +1,130 @@
+package solutions.bitbadger.documents.java
+
+import solutions.bitbadger.documents.common.*
+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/java/src/main/kotlin/Patch.kt b/src/java/src/main/kotlin/Patch.kt
new file mode 100644
index 0000000..7b48698
--- /dev/null
+++ b/src/java/src/main/kotlin/Patch.kt
@@ -0,0 +1,138 @@
+package solutions.bitbadger.documents.java
+
+import solutions.bitbadger.documents.common.Field
+import solutions.bitbadger.documents.common.FieldMatch
+import solutions.bitbadger.documents.common.Parameter
+import solutions.bitbadger.documents.common.ParameterType
+import java.sql.Connection
+
+/**
+ * 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
+ */
+ inline fun byId(tableName: String, docId: TKey, patch: TPatch, conn: Connection) =
+ conn.customNonQuery(
+ Patch.byId(tableName, docId),
+ Parameters.addFields(
+ listOf(Field.equal(Configuration.idField, docId, ":id")),
+ mutableListOf(Parameters.json(":data", 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
+ */
+ inline 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
+ */
+ inline fun byFields(
+ tableName: String,
+ fields: Collection>,
+ patch: TPatch,
+ howMatched: FieldMatch? = null,
+ conn: Connection
+ ) {
+ val named = Parameters.nameFields(fields)
+ conn.customNonQuery(
+ byFields(tableName, named, howMatched), Parameters.addFields(
+ named,
+ mutableListOf(Parameters.json(":data", patch))
+ )
+ )
+ }
+
+ /**
+ * 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
+ */
+ inline 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
+ */
+ inline fun byContains(
+ tableName: String,
+ criteria: TContains,
+ patch: TPatch,
+ conn: Connection
+ ) =
+ conn.customNonQuery(
+ Patch.byContains(tableName),
+ listOf(Parameters.json(":criteria", criteria), Parameters.json(":data", patch))
+ )
+
+ /**
+ * 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 called on a SQLite connection
+ */
+ inline 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
+ */
+ inline fun byJsonPath(tableName: String, path: String, patch: TPatch, conn: Connection) =
+ conn.customNonQuery(
+ Patch.byJsonPath(tableName),
+ listOf(Parameter(":path", ParameterType.STRING, path), Parameters.json(":data", patch))
+ )
+
+ /**
+ * 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 called on a SQLite connection
+ */
+ inline fun byJsonPath(tableName: String, path: String, patch: TPatch) =
+ Configuration.dbConn().use { byJsonPath(tableName, path, patch, it) }
+}
diff --git a/src/java/src/main/kotlin/RemoveFields.kt b/src/java/src/main/kotlin/RemoveFields.kt
new file mode 100644
index 0000000..0bb10f0
--- /dev/null
+++ b/src/java/src/main/kotlin/RemoveFields.kt
@@ -0,0 +1,154 @@
+package solutions.bitbadger.documents.java
+
+import solutions.bitbadger.documents.common.*
+import java.sql.Connection
+
+/**
+ * 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
+ */
+ fun byId(tableName: String, docId: TKey, toRemove: Collection, conn: Connection) {
+ val nameParams = Parameters.fieldNames(toRemove)
+ conn.customNonQuery(
+ byId(tableName, nameParams, docId),
+ Parameters.addFields(
+ listOf(Field.equal(Configuration.idField, docId, ":id")),
+ translatePath(nameParams)
+ )
+ )
+ }
+
+ /**
+ * 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
+ */
+ 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
+ */
+ fun byFields(
+ tableName: String,
+ fields: Collection>,
+ toRemove: Collection,
+ howMatched: FieldMatch? = null,
+ conn: Connection
+ ) {
+ val named = Parameters.nameFields(fields)
+ val nameParams = Parameters.fieldNames(toRemove)
+ conn.customNonQuery(
+ byFields(tableName, nameParams, named, howMatched),
+ Parameters.addFields(named, translatePath(nameParams))
+ )
+ }
+
+ /**
+ * 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
+ */
+ 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
+ */
+ inline fun byContains(
+ tableName: String,
+ criteria: TContains,
+ toRemove: Collection,
+ conn: Connection
+ ) {
+ val nameParams = Parameters.fieldNames(toRemove)
+ conn.customNonQuery(
+ RemoveFields.byContains(tableName, nameParams),
+ listOf(Parameters.json(":criteria", criteria), *nameParams.toTypedArray())
+ )
+ }
+
+ /**
+ * 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
+ * @throws DocumentException If called on a SQLite connection
+ */
+ inline fun byContains(tableName: String, criteria: TContains, toRemove: Collection) =
+ Configuration.dbConn().use { byContains(tableName, criteria, toRemove, it) }
+
+ /**
+ * Remove fields from documents using a JSON Path match query (PostgreSQL only)
+ *
+ * @param tableName The name of the table in which document fields should be removed
+ * @param path The JSON path comparison to match
+ * @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
+ */
+ fun byJsonPath(tableName: String, path: String, toRemove: Collection, conn: Connection) {
+ val nameParams = Parameters.fieldNames(toRemove)
+ conn.customNonQuery(
+ RemoveFields.byJsonPath(tableName, nameParams),
+ listOf(Parameter(":path", ParameterType.STRING, path), *nameParams.toTypedArray())
+ )
+ }
+
+ /**
+ * Remove fields from documents using a JSON Path match query (PostgreSQL only)
+ *
+ * @param tableName The name of the table in which document fields should be removed
+ * @param path The JSON path comparison to match
+ * @param toRemove The names of the fields to be removed
+ * @throws DocumentException If called on a SQLite connection
+ */
+ fun byJsonPath(tableName: String, path: String, toRemove: Collection) =
+ Configuration.dbConn().use { byJsonPath(tableName, path, toRemove, it) }
+}
diff --git a/src/main/kotlin/java/Results.kt b/src/java/src/main/kotlin/Results.kt
similarity index 96%
rename from src/main/kotlin/java/Results.kt
rename to src/java/src/main/kotlin/Results.kt
index 9b6f9eb..6438eaa 100644
--- a/src/main/kotlin/java/Results.kt
+++ b/src/java/src/main/kotlin/Results.kt
@@ -1,6 +1,6 @@
package solutions.bitbadger.documents.java
-import solutions.bitbadger.documents.Configuration
+import solutions.bitbadger.documents.common.Configuration
import solutions.bitbadger.documents.common.Dialect
import solutions.bitbadger.documents.common.DocumentException
import java.sql.PreparedStatement
@@ -20,7 +20,7 @@ object Results {
*/
@JvmStatic
fun fromDocument(field: String, rs: ResultSet, clazz: Class) =
- Configuration.serializer.deserialize(rs.getString(field), clazz)
+ DocumentConfig.serializer.deserialize(rs.getString(field), clazz)
/**
* Create a domain item from a document
diff --git a/src/test/java/solutions/bitbadger/documents/java/ParametersTest.java b/src/java/src/test/java/solutions/bitbadger/documents/java/java/ParametersTest.java
similarity index 94%
rename from src/test/java/solutions/bitbadger/documents/java/ParametersTest.java
rename to src/java/src/test/java/solutions/bitbadger/documents/java/java/ParametersTest.java
index c3b98c8..d2f2ee4 100644
--- a/src/test/java/solutions/bitbadger/documents/java/ParametersTest.java
+++ b/src/java/src/test/java/solutions/bitbadger/documents/java/java/ParametersTest.java
@@ -1,13 +1,10 @@
-package solutions.bitbadger.documents.java;
+package solutions.bitbadger.documents.java.java;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
-import solutions.bitbadger.documents.*;
-import solutions.bitbadger.documents.common.DocumentException;
-import solutions.bitbadger.documents.common.Field;
-import solutions.bitbadger.documents.common.Parameter;
-import solutions.bitbadger.documents.common.ParameterType;
+import solutions.bitbadger.documents.common.*;
+import solutions.bitbadger.documents.java.Parameters;
import java.util.List;
@@ -16,7 +13,7 @@ import static org.junit.jupiter.api.Assertions.*;
/**
* Unit tests for the `Parameters` object
*/
-@DisplayName("Java | Parameters")
+@DisplayName("Java | Java | Parameters")
final public class ParametersTest {
/**
diff --git a/src/java/src/test/java/solutions/bitbadger/documents/java/java/integration/common/Count.java b/src/java/src/test/java/solutions/bitbadger/documents/java/java/integration/common/Count.java
new file mode 100644
index 0000000..4ffc4ce
--- /dev/null
+++ b/src/java/src/test/java/solutions/bitbadger/documents/java/java/integration/common/Count.java
@@ -0,0 +1,20 @@
+package solutions.bitbadger.documents.java.java.integration.common;
+
+import solutions.bitbadger.documents.java.integration.ThrowawayDatabase;
+import solutions.bitbadger.documents.java.java.testDocs.JsonDocument;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static solutions.bitbadger.documents.java.integration.TypesKt.TEST_TABLE;
+
+final public class Count {
+
+ public static void all(ThrowawayDatabase db) {
+ JsonDocument.load(db);
+ assertEquals(5L, solutions.bitbadger.documents.java.Count.all(TEST_TABLE, db.getConn()),
+ "There should have been 5 documents in the table");
+ }
+
+
+ private Count() {
+ }
+}
diff --git a/src/test/java/solutions/bitbadger/documents/java/integration/postgresql/CountIT.java b/src/java/src/test/java/solutions/bitbadger/documents/java/java/integration/postgresql/CountIT.java
similarity index 65%
rename from src/test/java/solutions/bitbadger/documents/java/integration/postgresql/CountIT.java
rename to src/java/src/test/java/solutions/bitbadger/documents/java/java/integration/postgresql/CountIT.java
index 32fb88d..525aee5 100644
--- a/src/test/java/solutions/bitbadger/documents/java/integration/postgresql/CountIT.java
+++ b/src/java/src/test/java/solutions/bitbadger/documents/java/java/integration/postgresql/CountIT.java
@@ -1,9 +1,9 @@
-package solutions.bitbadger.documents.java.integration.postgresql;
+package solutions.bitbadger.documents.java.java.integration.postgresql;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
-import solutions.bitbadger.documents.integration.postgresql.PgDB;
-import solutions.bitbadger.documents.java.integration.common.Count;
+import solutions.bitbadger.documents.java.integration.postgresql.PgDB;
+import solutions.bitbadger.documents.java.java.integration.common.Count;
/**
* PostgreSQL integration tests for the `Count` object / `count*` connection extension functions
diff --git a/src/test/java/solutions/bitbadger/documents/java/testDocs/ByteIdClass.java b/src/java/src/test/java/solutions/bitbadger/documents/java/java/testDocs/ByteIdClass.java
similarity index 79%
rename from src/test/java/solutions/bitbadger/documents/java/testDocs/ByteIdClass.java
rename to src/java/src/test/java/solutions/bitbadger/documents/java/java/testDocs/ByteIdClass.java
index c0d41e7..4ffdf15 100644
--- a/src/test/java/solutions/bitbadger/documents/java/testDocs/ByteIdClass.java
+++ b/src/java/src/test/java/solutions/bitbadger/documents/java/java/testDocs/ByteIdClass.java
@@ -1,4 +1,4 @@
-package solutions.bitbadger.documents.java.testDocs;
+package solutions.bitbadger.documents.java.java.testDocs;
public class ByteIdClass {
diff --git a/src/test/java/solutions/bitbadger/documents/java/testDocs/IntIdClass.java b/src/java/src/test/java/solutions/bitbadger/documents/java/java/testDocs/IntIdClass.java
similarity index 79%
rename from src/test/java/solutions/bitbadger/documents/java/testDocs/IntIdClass.java
rename to src/java/src/test/java/solutions/bitbadger/documents/java/java/testDocs/IntIdClass.java
index 1d7a0cc..25a5613 100644
--- a/src/test/java/solutions/bitbadger/documents/java/testDocs/IntIdClass.java
+++ b/src/java/src/test/java/solutions/bitbadger/documents/java/java/testDocs/IntIdClass.java
@@ -1,4 +1,4 @@
-package solutions.bitbadger.documents.java.testDocs;
+package solutions.bitbadger.documents.java.java.testDocs;
public class IntIdClass {
diff --git a/src/test/java/solutions/bitbadger/documents/java/testDocs/JsonDocument.java b/src/java/src/test/java/solutions/bitbadger/documents/java/java/testDocs/JsonDocument.java
similarity index 87%
rename from src/test/java/solutions/bitbadger/documents/java/testDocs/JsonDocument.java
rename to src/java/src/test/java/solutions/bitbadger/documents/java/java/testDocs/JsonDocument.java
index 7bd9906..d1d02c6 100644
--- a/src/test/java/solutions/bitbadger/documents/java/testDocs/JsonDocument.java
+++ b/src/java/src/test/java/solutions/bitbadger/documents/java/java/testDocs/JsonDocument.java
@@ -1,12 +1,12 @@
-package solutions.bitbadger.documents.java.testDocs;
+package solutions.bitbadger.documents.java.java.testDocs;
import kotlinx.serialization.Serializable;
-import solutions.bitbadger.documents.Document;
-import solutions.bitbadger.documents.integration.ThrowawayDatabase;
+import solutions.bitbadger.documents.java.Document;
+import solutions.bitbadger.documents.java.integration.ThrowawayDatabase;
import java.util.List;
-import static solutions.bitbadger.documents.integration.TypesKt.TEST_TABLE;
+import static solutions.bitbadger.documents.java.integration.TypesKt.TEST_TABLE;
@Serializable
public class JsonDocument {
diff --git a/src/test/java/solutions/bitbadger/documents/java/testDocs/LongIdClass.java b/src/java/src/test/java/solutions/bitbadger/documents/java/java/testDocs/LongIdClass.java
similarity index 79%
rename from src/test/java/solutions/bitbadger/documents/java/testDocs/LongIdClass.java
rename to src/java/src/test/java/solutions/bitbadger/documents/java/java/testDocs/LongIdClass.java
index 1ee8bc0..9a56846 100644
--- a/src/test/java/solutions/bitbadger/documents/java/testDocs/LongIdClass.java
+++ b/src/java/src/test/java/solutions/bitbadger/documents/java/java/testDocs/LongIdClass.java
@@ -1,4 +1,4 @@
-package solutions.bitbadger.documents.java.testDocs;
+package solutions.bitbadger.documents.java.java.testDocs;
public class LongIdClass {
diff --git a/src/test/java/solutions/bitbadger/documents/java/testDocs/ShortIdClass.java b/src/java/src/test/java/solutions/bitbadger/documents/java/java/testDocs/ShortIdClass.java
similarity index 80%
rename from src/test/java/solutions/bitbadger/documents/java/testDocs/ShortIdClass.java
rename to src/java/src/test/java/solutions/bitbadger/documents/java/java/testDocs/ShortIdClass.java
index 83a2f3c..c5913cc 100644
--- a/src/test/java/solutions/bitbadger/documents/java/testDocs/ShortIdClass.java
+++ b/src/java/src/test/java/solutions/bitbadger/documents/java/java/testDocs/ShortIdClass.java
@@ -1,4 +1,4 @@
-package solutions.bitbadger.documents.java.testDocs;
+package solutions.bitbadger.documents.java.java.testDocs;
public class ShortIdClass {
diff --git a/src/test/java/solutions/bitbadger/documents/java/testDocs/StringIdClass.java b/src/java/src/test/java/solutions/bitbadger/documents/java/java/testDocs/StringIdClass.java
similarity index 80%
rename from src/test/java/solutions/bitbadger/documents/java/testDocs/StringIdClass.java
rename to src/java/src/test/java/solutions/bitbadger/documents/java/java/testDocs/StringIdClass.java
index 3013885..35e4945 100644
--- a/src/test/java/solutions/bitbadger/documents/java/testDocs/StringIdClass.java
+++ b/src/java/src/test/java/solutions/bitbadger/documents/java/java/testDocs/StringIdClass.java
@@ -1,4 +1,4 @@
-package solutions.bitbadger.documents.java.testDocs;
+package solutions.bitbadger.documents.java.java.testDocs;
public class StringIdClass {
diff --git a/src/test/java/solutions/bitbadger/documents/java/testDocs/SubDocument.java b/src/java/src/test/java/solutions/bitbadger/documents/java/java/testDocs/SubDocument.java
similarity index 89%
rename from src/test/java/solutions/bitbadger/documents/java/testDocs/SubDocument.java
rename to src/java/src/test/java/solutions/bitbadger/documents/java/java/testDocs/SubDocument.java
index f0b46ec..fa4c730 100644
--- a/src/test/java/solutions/bitbadger/documents/java/testDocs/SubDocument.java
+++ b/src/java/src/test/java/solutions/bitbadger/documents/java/java/testDocs/SubDocument.java
@@ -1,4 +1,4 @@
-package solutions.bitbadger.documents.java.testDocs;
+package solutions.bitbadger.documents.java.java.testDocs;
public class SubDocument {
diff --git a/src/test/kotlin/ParametersTest.kt b/src/java/src/test/kotlin/ParametersTest.kt
similarity index 98%
rename from src/test/kotlin/ParametersTest.kt
rename to src/java/src/test/kotlin/ParametersTest.kt
index 566c267..ac91104 100644
--- a/src/test/kotlin/ParametersTest.kt
+++ b/src/java/src/test/kotlin/ParametersTest.kt
@@ -1,4 +1,4 @@
-package solutions.bitbadger.documents
+package solutions.bitbadger.documents.java
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.DisplayName
@@ -12,7 +12,7 @@ import kotlin.test.assertSame
/**
* Unit tests for the `Parameters` object
*/
-@DisplayName("Kotlin | Parameters")
+@DisplayName("Kotlin | Java | Parameters")
class ParametersTest {
/**
diff --git a/src/test/kotlin/integration/ThrowawayDatabase.kt b/src/java/src/test/kotlin/integration/ThrowawayDatabase.kt
similarity index 89%
rename from src/test/kotlin/integration/ThrowawayDatabase.kt
rename to src/java/src/test/kotlin/integration/ThrowawayDatabase.kt
index 5138ba4..1e46251 100644
--- a/src/test/kotlin/integration/ThrowawayDatabase.kt
+++ b/src/java/src/test/kotlin/integration/ThrowawayDatabase.kt
@@ -1,4 +1,4 @@
-package solutions.bitbadger.documents.integration
+package solutions.bitbadger.documents.java.integration
import java.sql.Connection
diff --git a/src/test/kotlin/integration/Types.kt b/src/java/src/test/kotlin/integration/Types.kt
similarity index 93%
rename from src/test/kotlin/integration/Types.kt
rename to src/java/src/test/kotlin/integration/Types.kt
index 54adfb4..730caa0 100644
--- a/src/test/kotlin/integration/Types.kt
+++ b/src/java/src/test/kotlin/integration/Types.kt
@@ -1,7 +1,7 @@
-package solutions.bitbadger.documents.integration
+package solutions.bitbadger.documents.java.integration
import kotlinx.serialization.Serializable
-import solutions.bitbadger.documents.insert
+import solutions.bitbadger.documents.java.insert
/** The test table name to use for integration tests */
const val TEST_TABLE = "test_table"
diff --git a/src/test/kotlin/integration/common/Count.kt b/src/java/src/test/kotlin/integration/common/Count.kt
similarity index 80%
rename from src/test/kotlin/integration/common/Count.kt
rename to src/java/src/test/kotlin/integration/common/Count.kt
index e360977..e8b3258 100644
--- a/src/test/kotlin/integration/common/Count.kt
+++ b/src/java/src/test/kotlin/integration/common/Count.kt
@@ -1,10 +1,13 @@
-package solutions.bitbadger.documents.integration.common
+package solutions.bitbadger.documents.java.integration.common
-import solutions.bitbadger.documents.*
import solutions.bitbadger.documents.common.Field
-import solutions.bitbadger.documents.integration.JsonDocument
-import solutions.bitbadger.documents.integration.TEST_TABLE
-import solutions.bitbadger.documents.integration.ThrowawayDatabase
+import solutions.bitbadger.documents.java.countAll
+import solutions.bitbadger.documents.java.countByContains
+import solutions.bitbadger.documents.java.countByFields
+import solutions.bitbadger.documents.java.countByJsonPath
+import solutions.bitbadger.documents.java.integration.JsonDocument
+import solutions.bitbadger.documents.java.integration.TEST_TABLE
+import solutions.bitbadger.documents.java.integration.ThrowawayDatabase
import kotlin.test.assertEquals
/**
diff --git a/src/test/kotlin/integration/common/Custom.kt b/src/java/src/test/kotlin/integration/common/Custom.kt
similarity index 81%
rename from src/test/kotlin/integration/common/Custom.kt
rename to src/java/src/test/kotlin/integration/common/Custom.kt
index f8361c6..05073d3 100644
--- a/src/test/kotlin/integration/common/Custom.kt
+++ b/src/java/src/test/kotlin/integration/common/Custom.kt
@@ -1,15 +1,10 @@
-package solutions.bitbadger.documents.integration.common
+package solutions.bitbadger.documents.java.integration.common
-import solutions.bitbadger.documents.*
-import solutions.bitbadger.documents.common.Field
-import solutions.bitbadger.documents.common.Parameter
-import solutions.bitbadger.documents.common.ParameterType
-import solutions.bitbadger.documents.integration.JsonDocument
-import solutions.bitbadger.documents.integration.TEST_TABLE
-import solutions.bitbadger.documents.integration.ThrowawayDatabase
-import solutions.bitbadger.documents.common.query.Count
-import solutions.bitbadger.documents.common.query.Delete
-import solutions.bitbadger.documents.common.query.Find
+import solutions.bitbadger.documents.common.*
+import solutions.bitbadger.documents.java.integration.JsonDocument
+import solutions.bitbadger.documents.java.integration.TEST_TABLE
+import solutions.bitbadger.documents.java.Results
+import solutions.bitbadger.documents.java.integration.ThrowawayDatabase
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
diff --git a/src/test/kotlin/integration/common/Definition.kt b/src/java/src/test/kotlin/integration/common/Definition.kt
similarity index 85%
rename from src/test/kotlin/integration/common/Definition.kt
rename to src/java/src/test/kotlin/integration/common/Definition.kt
index e667ce8..6f36226 100644
--- a/src/test/kotlin/integration/common/Definition.kt
+++ b/src/java/src/test/kotlin/integration/common/Definition.kt
@@ -1,9 +1,11 @@
-package solutions.bitbadger.documents.integration.common
+package solutions.bitbadger.documents.java.integration.common
-import solutions.bitbadger.documents.*
import solutions.bitbadger.documents.common.DocumentIndex
-import solutions.bitbadger.documents.integration.TEST_TABLE
-import solutions.bitbadger.documents.integration.ThrowawayDatabase
+import solutions.bitbadger.documents.java.ensureDocumentIndex
+import solutions.bitbadger.documents.java.ensureFieldIndex
+import solutions.bitbadger.documents.java.ensureTable
+import solutions.bitbadger.documents.java.integration.TEST_TABLE
+import solutions.bitbadger.documents.java.integration.ThrowawayDatabase
import kotlin.test.assertFalse
import kotlin.test.assertTrue
diff --git a/src/test/kotlin/integration/common/Delete.kt b/src/java/src/test/kotlin/integration/common/Delete.kt
similarity index 90%
rename from src/test/kotlin/integration/common/Delete.kt
rename to src/java/src/test/kotlin/integration/common/Delete.kt
index 1e69426..2ea4338 100644
--- a/src/test/kotlin/integration/common/Delete.kt
+++ b/src/java/src/test/kotlin/integration/common/Delete.kt
@@ -1,10 +1,10 @@
-package solutions.bitbadger.documents.integration.common
+package solutions.bitbadger.documents.java.integration.common
-import solutions.bitbadger.documents.*
import solutions.bitbadger.documents.common.Field
-import solutions.bitbadger.documents.integration.JsonDocument
-import solutions.bitbadger.documents.integration.TEST_TABLE
-import solutions.bitbadger.documents.integration.ThrowawayDatabase
+import solutions.bitbadger.documents.java.*
+import solutions.bitbadger.documents.java.integration.JsonDocument
+import solutions.bitbadger.documents.java.integration.TEST_TABLE
+import solutions.bitbadger.documents.java.integration.ThrowawayDatabase
import kotlin.test.assertEquals
/**
diff --git a/src/test/kotlin/integration/common/Document.kt b/src/java/src/test/kotlin/integration/common/Document.kt
similarity index 97%
rename from src/test/kotlin/integration/common/Document.kt
rename to src/java/src/test/kotlin/integration/common/Document.kt
index bb3c41b..e698525 100644
--- a/src/test/kotlin/integration/common/Document.kt
+++ b/src/java/src/test/kotlin/integration/common/Document.kt
@@ -1,8 +1,10 @@
-package solutions.bitbadger.documents.integration.common
+package solutions.bitbadger.documents.java.integration.common
import solutions.bitbadger.documents.*
import solutions.bitbadger.documents.common.Field
import solutions.bitbadger.documents.integration.*
+import solutions.bitbadger.documents.java.*
+import solutions.bitbadger.documents.java.integration.*
import kotlin.test.*
/**
diff --git a/src/test/kotlin/integration/common/Exists.kt b/src/java/src/test/kotlin/integration/common/Exists.kt
similarity index 80%
rename from src/test/kotlin/integration/common/Exists.kt
rename to src/java/src/test/kotlin/integration/common/Exists.kt
index 2dc51b8..3d5513e 100644
--- a/src/test/kotlin/integration/common/Exists.kt
+++ b/src/java/src/test/kotlin/integration/common/Exists.kt
@@ -1,10 +1,13 @@
-package solutions.bitbadger.documents.integration.common
+package solutions.bitbadger.documents.java.integration.common
-import solutions.bitbadger.documents.*
import solutions.bitbadger.documents.common.Field
-import solutions.bitbadger.documents.integration.JsonDocument
-import solutions.bitbadger.documents.integration.TEST_TABLE
-import solutions.bitbadger.documents.integration.ThrowawayDatabase
+import solutions.bitbadger.documents.java.existsByContains
+import solutions.bitbadger.documents.java.existsByFields
+import solutions.bitbadger.documents.java.existsById
+import solutions.bitbadger.documents.java.existsByJsonPath
+import solutions.bitbadger.documents.java.integration.JsonDocument
+import solutions.bitbadger.documents.java.integration.TEST_TABLE
+import solutions.bitbadger.documents.java.integration.ThrowawayDatabase
import kotlin.test.assertFalse
import kotlin.test.assertTrue
diff --git a/src/test/kotlin/integration/common/Find.kt b/src/java/src/test/kotlin/integration/common/Find.kt
similarity index 97%
rename from src/test/kotlin/integration/common/Find.kt
rename to src/java/src/test/kotlin/integration/common/Find.kt
index 839991d..d7f124e 100644
--- a/src/test/kotlin/integration/common/Find.kt
+++ b/src/java/src/test/kotlin/integration/common/Find.kt
@@ -1,9 +1,11 @@
-package solutions.bitbadger.documents.integration.common
+package solutions.bitbadger.documents.java.integration.common
import solutions.bitbadger.documents.*
import solutions.bitbadger.documents.common.Field
import solutions.bitbadger.documents.common.FieldMatch
import solutions.bitbadger.documents.integration.*
+import solutions.bitbadger.documents.java.*
+import solutions.bitbadger.documents.java.integration.*
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
@@ -211,7 +213,8 @@ object Find {
fun firstByFieldsMatchOrdered(db: ThrowawayDatabase) {
JsonDocument.load(db)
- val doc = db.conn.findFirstByFields(TEST_TABLE, listOf(Field.equal("sub.foo", "green")), orderBy = listOf(
+ val doc = db.conn.findFirstByFields(
+ TEST_TABLE, listOf(Field.equal("sub.foo", "green")), orderBy = listOf(
Field.named("n:numValue DESC")))
assertNotNull(doc, "There should have been a document returned")
assertEquals("four", doc.id, "An incorrect document was returned")
diff --git a/src/test/kotlin/integration/common/Patch.kt b/src/java/src/test/kotlin/integration/common/Patch.kt
similarity index 92%
rename from src/test/kotlin/integration/common/Patch.kt
rename to src/java/src/test/kotlin/integration/common/Patch.kt
index 37d7315..9a4144c 100644
--- a/src/test/kotlin/integration/common/Patch.kt
+++ b/src/java/src/test/kotlin/integration/common/Patch.kt
@@ -1,10 +1,11 @@
-package solutions.bitbadger.documents.integration.common
+package solutions.bitbadger.documents.java.integration.common
import solutions.bitbadger.documents.*
import solutions.bitbadger.documents.common.Field
-import solutions.bitbadger.documents.integration.JsonDocument
-import solutions.bitbadger.documents.integration.TEST_TABLE
-import solutions.bitbadger.documents.integration.ThrowawayDatabase
+import solutions.bitbadger.documents.java.*
+import solutions.bitbadger.documents.java.integration.JsonDocument
+import solutions.bitbadger.documents.java.integration.TEST_TABLE
+import solutions.bitbadger.documents.java.integration.ThrowawayDatabase
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
diff --git a/src/test/kotlin/integration/common/RemoveFields.kt b/src/java/src/test/kotlin/integration/common/RemoveFields.kt
similarity index 93%
rename from src/test/kotlin/integration/common/RemoveFields.kt
rename to src/java/src/test/kotlin/integration/common/RemoveFields.kt
index e8381a1..022c484 100644
--- a/src/test/kotlin/integration/common/RemoveFields.kt
+++ b/src/java/src/test/kotlin/integration/common/RemoveFields.kt
@@ -1,10 +1,11 @@
-package solutions.bitbadger.documents.integration.common
+package solutions.bitbadger.documents.java.integration.common
import solutions.bitbadger.documents.*
import solutions.bitbadger.documents.common.Field
-import solutions.bitbadger.documents.integration.JsonDocument
-import solutions.bitbadger.documents.integration.TEST_TABLE
-import solutions.bitbadger.documents.integration.ThrowawayDatabase
+import solutions.bitbadger.documents.java.*
+import solutions.bitbadger.documents.java.integration.JsonDocument
+import solutions.bitbadger.documents.java.integration.TEST_TABLE
+import solutions.bitbadger.documents.java.integration.ThrowawayDatabase
import kotlin.test.*
diff --git a/src/test/kotlin/integration/postgresql/CountIT.kt b/src/java/src/test/kotlin/integration/postgresql/CountIT.kt
similarity index 90%
rename from src/test/kotlin/integration/postgresql/CountIT.kt
rename to src/java/src/test/kotlin/integration/postgresql/CountIT.kt
index 9b4dc33..bb6e416 100644
--- a/src/test/kotlin/integration/postgresql/CountIT.kt
+++ b/src/java/src/test/kotlin/integration/postgresql/CountIT.kt
@@ -1,7 +1,7 @@
-package solutions.bitbadger.documents.integration.postgresql
+package solutions.bitbadger.documents.java.integration.postgresql
import org.junit.jupiter.api.DisplayName
-import solutions.bitbadger.documents.integration.common.Count
+import solutions.bitbadger.documents.java.integration.common.Count
import kotlin.test.Test
/**
diff --git a/src/test/kotlin/integration/postgresql/CustomIT.kt b/src/java/src/test/kotlin/integration/postgresql/CustomIT.kt
similarity index 89%
rename from src/test/kotlin/integration/postgresql/CustomIT.kt
rename to src/java/src/test/kotlin/integration/postgresql/CustomIT.kt
index 5729df9..803bfb1 100644
--- a/src/test/kotlin/integration/postgresql/CustomIT.kt
+++ b/src/java/src/test/kotlin/integration/postgresql/CustomIT.kt
@@ -1,7 +1,7 @@
-package solutions.bitbadger.documents.integration.postgresql
+package solutions.bitbadger.documents.java.integration.postgresql
import org.junit.jupiter.api.DisplayName
-import solutions.bitbadger.documents.integration.common.Custom
+import solutions.bitbadger.documents.java.integration.common.Custom
import kotlin.test.Test
diff --git a/src/test/kotlin/integration/postgresql/DefinitionIT.kt b/src/java/src/test/kotlin/integration/postgresql/DefinitionIT.kt
similarity index 86%
rename from src/test/kotlin/integration/postgresql/DefinitionIT.kt
rename to src/java/src/test/kotlin/integration/postgresql/DefinitionIT.kt
index 363c92d..9c94abc 100644
--- a/src/test/kotlin/integration/postgresql/DefinitionIT.kt
+++ b/src/java/src/test/kotlin/integration/postgresql/DefinitionIT.kt
@@ -1,7 +1,7 @@
-package solutions.bitbadger.documents.integration.postgresql
+package solutions.bitbadger.documents.java.integration.postgresql
import org.junit.jupiter.api.DisplayName
-import solutions.bitbadger.documents.integration.common.Definition
+import solutions.bitbadger.documents.java.integration.common.Definition
import kotlin.test.Test
/**
diff --git a/src/test/kotlin/integration/postgresql/DeleteIT.kt b/src/java/src/test/kotlin/integration/postgresql/DeleteIT.kt
similarity index 90%
rename from src/test/kotlin/integration/postgresql/DeleteIT.kt
rename to src/java/src/test/kotlin/integration/postgresql/DeleteIT.kt
index 707be57..803400c 100644
--- a/src/test/kotlin/integration/postgresql/DeleteIT.kt
+++ b/src/java/src/test/kotlin/integration/postgresql/DeleteIT.kt
@@ -1,7 +1,7 @@
-package solutions.bitbadger.documents.integration.postgresql
+package solutions.bitbadger.documents.java.integration.postgresql
import org.junit.jupiter.api.DisplayName
-import solutions.bitbadger.documents.integration.common.Delete
+import solutions.bitbadger.documents.java.integration.common.Delete
import kotlin.test.Test
/**
diff --git a/src/test/kotlin/integration/postgresql/DocumentIT.kt b/src/java/src/test/kotlin/integration/postgresql/DocumentIT.kt
similarity index 91%
rename from src/test/kotlin/integration/postgresql/DocumentIT.kt
rename to src/java/src/test/kotlin/integration/postgresql/DocumentIT.kt
index 36d15af..382d64b 100644
--- a/src/test/kotlin/integration/postgresql/DocumentIT.kt
+++ b/src/java/src/test/kotlin/integration/postgresql/DocumentIT.kt
@@ -1,7 +1,7 @@
-package solutions.bitbadger.documents.integration.postgresql
+package solutions.bitbadger.documents.java.integration.postgresql
import org.junit.jupiter.api.DisplayName
-import solutions.bitbadger.documents.integration.common.Document
+import solutions.bitbadger.documents.java.integration.common.Document
import kotlin.test.Test
/**
diff --git a/src/test/kotlin/integration/postgresql/ExistsIT.kt b/src/java/src/test/kotlin/integration/postgresql/ExistsIT.kt
similarity index 91%
rename from src/test/kotlin/integration/postgresql/ExistsIT.kt
rename to src/java/src/test/kotlin/integration/postgresql/ExistsIT.kt
index be02740..757ec75 100644
--- a/src/test/kotlin/integration/postgresql/ExistsIT.kt
+++ b/src/java/src/test/kotlin/integration/postgresql/ExistsIT.kt
@@ -1,7 +1,7 @@
-package solutions.bitbadger.documents.integration.postgresql
+package solutions.bitbadger.documents.java.integration.postgresql
import org.junit.jupiter.api.DisplayName
-import solutions.bitbadger.documents.integration.common.Exists
+import solutions.bitbadger.documents.java.integration.common.Exists
import kotlin.test.Test
/**
diff --git a/src/test/kotlin/integration/postgresql/FindIT.kt b/src/java/src/test/kotlin/integration/postgresql/FindIT.kt
similarity index 97%
rename from src/test/kotlin/integration/postgresql/FindIT.kt
rename to src/java/src/test/kotlin/integration/postgresql/FindIT.kt
index 6b00cc3..c6a95fe 100644
--- a/src/test/kotlin/integration/postgresql/FindIT.kt
+++ b/src/java/src/test/kotlin/integration/postgresql/FindIT.kt
@@ -1,7 +1,7 @@
-package solutions.bitbadger.documents.integration.postgresql
+package solutions.bitbadger.documents.java.integration.postgresql
import org.junit.jupiter.api.DisplayName
-import solutions.bitbadger.documents.integration.common.Find
+import solutions.bitbadger.documents.java.integration.common.Find
import kotlin.test.Test
/**
diff --git a/src/test/kotlin/integration/postgresql/PatchIT.kt b/src/java/src/test/kotlin/integration/postgresql/PatchIT.kt
similarity index 90%
rename from src/test/kotlin/integration/postgresql/PatchIT.kt
rename to src/java/src/test/kotlin/integration/postgresql/PatchIT.kt
index df1c79d..d650355 100644
--- a/src/test/kotlin/integration/postgresql/PatchIT.kt
+++ b/src/java/src/test/kotlin/integration/postgresql/PatchIT.kt
@@ -1,7 +1,7 @@
-package solutions.bitbadger.documents.integration.postgresql
+package solutions.bitbadger.documents.java.integration.postgresql
import org.junit.jupiter.api.DisplayName
-import solutions.bitbadger.documents.integration.common.Patch
+import solutions.bitbadger.documents.java.integration.common.Patch
import kotlin.test.Test
/**
diff --git a/src/test/kotlin/integration/postgresql/PgDB.kt b/src/java/src/test/kotlin/integration/postgresql/PgDB.kt
similarity index 88%
rename from src/test/kotlin/integration/postgresql/PgDB.kt
rename to src/java/src/test/kotlin/integration/postgresql/PgDB.kt
index c72af32..7fecb54 100644
--- a/src/test/kotlin/integration/postgresql/PgDB.kt
+++ b/src/java/src/test/kotlin/integration/postgresql/PgDB.kt
@@ -1,10 +1,10 @@
-package solutions.bitbadger.documents.integration.postgresql
+package solutions.bitbadger.documents.java.integration.postgresql
import solutions.bitbadger.documents.*
import solutions.bitbadger.documents.common.Parameter
import solutions.bitbadger.documents.common.ParameterType
-import solutions.bitbadger.documents.integration.TEST_TABLE
-import solutions.bitbadger.documents.integration.ThrowawayDatabase
+import solutions.bitbadger.documents.java.integration.TEST_TABLE
+import solutions.bitbadger.documents.java.integration.ThrowawayDatabase
/**
* A wrapper for a throwaway PostgreSQL database
diff --git a/src/test/kotlin/integration/postgresql/RemoveFieldsIT.kt b/src/java/src/test/kotlin/integration/postgresql/RemoveFieldsIT.kt
similarity index 94%
rename from src/test/kotlin/integration/postgresql/RemoveFieldsIT.kt
rename to src/java/src/test/kotlin/integration/postgresql/RemoveFieldsIT.kt
index 9719447..3a10709 100644
--- a/src/test/kotlin/integration/postgresql/RemoveFieldsIT.kt
+++ b/src/java/src/test/kotlin/integration/postgresql/RemoveFieldsIT.kt
@@ -1,7 +1,7 @@
-package solutions.bitbadger.documents.integration.postgresql
+package solutions.bitbadger.documents.java.integration.postgresql
import org.junit.jupiter.api.DisplayName
-import solutions.bitbadger.documents.integration.common.RemoveFields
+import solutions.bitbadger.documents.java.integration.common.RemoveFields
import kotlin.test.Test
/**
diff --git a/src/test/kotlin/integration/sqlite/CountIT.kt b/src/java/src/test/kotlin/integration/sqlite/CountIT.kt
similarity index 89%
rename from src/test/kotlin/integration/sqlite/CountIT.kt
rename to src/java/src/test/kotlin/integration/sqlite/CountIT.kt
index 7b08ddf..62fdb63 100644
--- a/src/test/kotlin/integration/sqlite/CountIT.kt
+++ b/src/java/src/test/kotlin/integration/sqlite/CountIT.kt
@@ -1,9 +1,9 @@
-package solutions.bitbadger.documents.integration.sqlite
+package solutions.bitbadger.documents.java.integration.sqlite
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.assertThrows
import solutions.bitbadger.documents.common.DocumentException
-import solutions.bitbadger.documents.integration.common.Count
+import solutions.bitbadger.documents.java.integration.common.Count
import kotlin.test.Test
/**
diff --git a/src/test/kotlin/integration/sqlite/CustomIT.kt b/src/java/src/test/kotlin/integration/sqlite/CustomIT.kt
similarity index 89%
rename from src/test/kotlin/integration/sqlite/CustomIT.kt
rename to src/java/src/test/kotlin/integration/sqlite/CustomIT.kt
index ab37b5f..21adab3 100644
--- a/src/test/kotlin/integration/sqlite/CustomIT.kt
+++ b/src/java/src/test/kotlin/integration/sqlite/CustomIT.kt
@@ -1,7 +1,7 @@
-package solutions.bitbadger.documents.integration.sqlite
+package solutions.bitbadger.documents.java.integration.sqlite
import org.junit.jupiter.api.DisplayName
-import solutions.bitbadger.documents.integration.common.Custom
+import solutions.bitbadger.documents.java.integration.common.Custom
import kotlin.test.Test
/**
diff --git a/src/test/kotlin/integration/sqlite/DefinitionIT.kt b/src/java/src/test/kotlin/integration/sqlite/DefinitionIT.kt
similarity index 88%
rename from src/test/kotlin/integration/sqlite/DefinitionIT.kt
rename to src/java/src/test/kotlin/integration/sqlite/DefinitionIT.kt
index 0cd15fa..3e553ed 100644
--- a/src/test/kotlin/integration/sqlite/DefinitionIT.kt
+++ b/src/java/src/test/kotlin/integration/sqlite/DefinitionIT.kt
@@ -1,9 +1,9 @@
-package solutions.bitbadger.documents.integration.sqlite
+package solutions.bitbadger.documents.java.integration.sqlite
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.assertThrows
import solutions.bitbadger.documents.common.DocumentException
-import solutions.bitbadger.documents.integration.common.Definition
+import solutions.bitbadger.documents.java.integration.common.Definition
import kotlin.test.Test
/**
diff --git a/src/test/kotlin/integration/sqlite/DeleteIT.kt b/src/java/src/test/kotlin/integration/sqlite/DeleteIT.kt
similarity index 90%
rename from src/test/kotlin/integration/sqlite/DeleteIT.kt
rename to src/java/src/test/kotlin/integration/sqlite/DeleteIT.kt
index 40c100e..b3d056a 100644
--- a/src/test/kotlin/integration/sqlite/DeleteIT.kt
+++ b/src/java/src/test/kotlin/integration/sqlite/DeleteIT.kt
@@ -1,9 +1,9 @@
-package solutions.bitbadger.documents.integration.sqlite
+package solutions.bitbadger.documents.java.integration.sqlite
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.assertThrows
import solutions.bitbadger.documents.common.DocumentException
-import solutions.bitbadger.documents.integration.common.Delete
+import solutions.bitbadger.documents.java.integration.common.Delete
import kotlin.test.Test
/**
diff --git a/src/test/kotlin/integration/sqlite/DocumentIT.kt b/src/java/src/test/kotlin/integration/sqlite/DocumentIT.kt
similarity index 91%
rename from src/test/kotlin/integration/sqlite/DocumentIT.kt
rename to src/java/src/test/kotlin/integration/sqlite/DocumentIT.kt
index 402cd6e..1745237 100644
--- a/src/test/kotlin/integration/sqlite/DocumentIT.kt
+++ b/src/java/src/test/kotlin/integration/sqlite/DocumentIT.kt
@@ -1,7 +1,7 @@
-package solutions.bitbadger.documents.integration.sqlite
+package solutions.bitbadger.documents.java.integration.sqlite
import org.junit.jupiter.api.DisplayName
-import solutions.bitbadger.documents.integration.common.Document
+import solutions.bitbadger.documents.java.integration.common.Document
import kotlin.test.Test
/**
diff --git a/src/test/kotlin/integration/sqlite/ExistsIT.kt b/src/java/src/test/kotlin/integration/sqlite/ExistsIT.kt
similarity index 90%
rename from src/test/kotlin/integration/sqlite/ExistsIT.kt
rename to src/java/src/test/kotlin/integration/sqlite/ExistsIT.kt
index 42bf633..55933ac 100644
--- a/src/test/kotlin/integration/sqlite/ExistsIT.kt
+++ b/src/java/src/test/kotlin/integration/sqlite/ExistsIT.kt
@@ -1,9 +1,9 @@
-package solutions.bitbadger.documents.integration.sqlite
+package solutions.bitbadger.documents.java.integration.sqlite
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.assertThrows
import solutions.bitbadger.documents.common.DocumentException
-import solutions.bitbadger.documents.integration.common.Exists
+import solutions.bitbadger.documents.java.integration.common.Exists
import kotlin.test.Test
/**
diff --git a/src/test/kotlin/integration/sqlite/FindIT.kt b/src/java/src/test/kotlin/integration/sqlite/FindIT.kt
similarity index 96%
rename from src/test/kotlin/integration/sqlite/FindIT.kt
rename to src/java/src/test/kotlin/integration/sqlite/FindIT.kt
index 6750c03..aef0003 100644
--- a/src/test/kotlin/integration/sqlite/FindIT.kt
+++ b/src/java/src/test/kotlin/integration/sqlite/FindIT.kt
@@ -1,9 +1,9 @@
-package solutions.bitbadger.documents.integration.sqlite
+package solutions.bitbadger.documents.java.integration.sqlite
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.assertThrows
import solutions.bitbadger.documents.common.DocumentException
-import solutions.bitbadger.documents.integration.common.Find
+import solutions.bitbadger.documents.java.integration.common.Find
import kotlin.test.Test
/**
diff --git a/src/test/kotlin/integration/sqlite/PatchIT.kt b/src/java/src/test/kotlin/integration/sqlite/PatchIT.kt
similarity index 90%
rename from src/test/kotlin/integration/sqlite/PatchIT.kt
rename to src/java/src/test/kotlin/integration/sqlite/PatchIT.kt
index 76e9c27..ea1b147 100644
--- a/src/test/kotlin/integration/sqlite/PatchIT.kt
+++ b/src/java/src/test/kotlin/integration/sqlite/PatchIT.kt
@@ -1,9 +1,9 @@
-package solutions.bitbadger.documents.integration.sqlite
+package solutions.bitbadger.documents.java.integration.sqlite
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.assertThrows
import solutions.bitbadger.documents.common.DocumentException
-import solutions.bitbadger.documents.integration.common.Patch
+import solutions.bitbadger.documents.java.integration.common.Patch
import kotlin.test.Test
/**
diff --git a/src/test/kotlin/integration/sqlite/RemoveFieldsIT.kt b/src/java/src/test/kotlin/integration/sqlite/RemoveFieldsIT.kt
similarity index 92%
rename from src/test/kotlin/integration/sqlite/RemoveFieldsIT.kt
rename to src/java/src/test/kotlin/integration/sqlite/RemoveFieldsIT.kt
index 096d1fb..3fbad3c 100644
--- a/src/test/kotlin/integration/sqlite/RemoveFieldsIT.kt
+++ b/src/java/src/test/kotlin/integration/sqlite/RemoveFieldsIT.kt
@@ -1,9 +1,9 @@
-package solutions.bitbadger.documents.integration.sqlite
+package solutions.bitbadger.documents.java.integration.sqlite
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.assertThrows
import solutions.bitbadger.documents.common.DocumentException
-import solutions.bitbadger.documents.integration.common.RemoveFields
+import solutions.bitbadger.documents.java.integration.common.RemoveFields
import kotlin.test.Test
/**
diff --git a/src/test/kotlin/integration/sqlite/SQLiteDB.kt b/src/java/src/test/kotlin/integration/sqlite/SQLiteDB.kt
similarity index 81%
rename from src/test/kotlin/integration/sqlite/SQLiteDB.kt
rename to src/java/src/test/kotlin/integration/sqlite/SQLiteDB.kt
index e6be940..34d977b 100644
--- a/src/test/kotlin/integration/sqlite/SQLiteDB.kt
+++ b/src/java/src/test/kotlin/integration/sqlite/SQLiteDB.kt
@@ -1,10 +1,10 @@
-package solutions.bitbadger.documents.integration.sqlite
+package solutions.bitbadger.documents.java.integration.sqlite
import solutions.bitbadger.documents.*
import solutions.bitbadger.documents.common.Parameter
import solutions.bitbadger.documents.common.ParameterType
-import solutions.bitbadger.documents.integration.TEST_TABLE
-import solutions.bitbadger.documents.integration.ThrowawayDatabase
+import solutions.bitbadger.documents.java.integration.TEST_TABLE
+import solutions.bitbadger.documents.java.integration.ThrowawayDatabase
import java.io.File
/**
diff --git a/src/kotlin/pom.xml b/src/kotlin/pom.xml
new file mode 100644
index 0000000..88bafc0
--- /dev/null
+++ b/src/kotlin/pom.xml
@@ -0,0 +1,119 @@
+
+
+ 4.0.0
+
+ solutions.bitbadger.documents
+ kotlin
+ 4.0.0-alpha1-SNAPSHOT
+ jar
+
+
+ solutions.bitbadger
+ documents
+ 4.0.0-alpha1-SNAPSHOT
+
+
+ ${project.groupId}:${project.artifactId}
+ Expose a document store interface for PostgreSQL and SQLite (Kotlin Library)
+ https://bitbadger.solutions/open-source/relational-documents/jvm/
+
+
+ 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
+
+
+
+
+ solutions.bitbadger.documents
+ common
+ system
+ ${project.basedir}/../common/target/common-4.0.0-alpha1-SNAPSHOT.jar
+ jar
+
+
+ solutions.bitbadger.documents
+ java
+ system
+ ${project.basedir}/../java/target/java-4.0.0-alpha1-SNAPSHOT.jar
+ jar
+
+
+
+
+ src/main/kotlin
+ src/test/kotlin
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+ ${kotlin.version}
+
+
+ compile
+ process-sources
+
+ compile
+
+
+
+ ${project.basedir}/src/main/kotlin
+
+
+
+
+ test-compile
+ test-compile
+
+ test-compile
+
+
+
+ ${project.basedir}/src/test/java
+ ${project.basedir}/src/test/kotlin
+
+
+
+
+
+
+ kotlinx-serialization
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-serialization
+ ${kotlin.version}
+
+
+
+
+ maven-surefire-plugin
+ 2.22.2
+
+
+ maven-failsafe-plugin
+ 2.22.2
+
+
+
+ integration-test
+ verify
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ ${java.version}
+ ${java.version}
+
+
+
+
+
diff --git a/src/main/kotlin/Configuration.kt b/src/kotlin/src/main/kotlin/Configuration.kt
similarity index 68%
rename from src/main/kotlin/Configuration.kt
rename to src/kotlin/src/main/kotlin/Configuration.kt
index 705a1d9..f8becb9 100644
--- a/src/main/kotlin/Configuration.kt
+++ b/src/kotlin/src/main/kotlin/Configuration.kt
@@ -1,12 +1,6 @@
-package solutions.bitbadger.documents
+package solutions.bitbadger.documents.kotlin
import kotlinx.serialization.json.Json
-import solutions.bitbadger.documents.common.AutoId
-import solutions.bitbadger.documents.common.Dialect
-import solutions.bitbadger.documents.common.DocumentException
-import java.sql.Connection
-import java.sql.DriverManager
-import kotlin.jvm.Throws
object Configuration {
diff --git a/src/main/kotlin/ConnectionExtensions.kt b/src/kotlin/src/main/kotlin/ConnectionExtensions.kt
similarity index 99%
rename from src/main/kotlin/ConnectionExtensions.kt
rename to src/kotlin/src/main/kotlin/ConnectionExtensions.kt
index c63406b..28e951b 100644
--- a/src/main/kotlin/ConnectionExtensions.kt
+++ b/src/kotlin/src/main/kotlin/ConnectionExtensions.kt
@@ -1,9 +1,10 @@
-package solutions.bitbadger.documents
+package solutions.bitbadger.documents.kotlin
import solutions.bitbadger.documents.common.DocumentIndex
import solutions.bitbadger.documents.common.Field
import solutions.bitbadger.documents.common.FieldMatch
import solutions.bitbadger.documents.common.Parameter
+import solutions.bitbadger.documents.java.Document
import java.sql.Connection
import java.sql.ResultSet
diff --git a/src/main/kotlin/Count.kt b/src/kotlin/src/main/kotlin/Count.kt
similarity index 97%
rename from src/main/kotlin/Count.kt
rename to src/kotlin/src/main/kotlin/Count.kt
index b865af0..e2ac2d5 100644
--- a/src/main/kotlin/Count.kt
+++ b/src/kotlin/src/main/kotlin/Count.kt
@@ -1,10 +1,12 @@
-package solutions.bitbadger.documents
+package solutions.bitbadger.documents.kotlin
+import solutions.bitbadger.documents.common.Configuration
import solutions.bitbadger.documents.common.Field
import solutions.bitbadger.documents.common.FieldMatch
import solutions.bitbadger.documents.common.Parameter
import solutions.bitbadger.documents.common.ParameterType
import solutions.bitbadger.documents.common.query.Count
+
import java.sql.Connection
/**
diff --git a/src/kotlin/src/main/kotlin/Custom.kt b/src/kotlin/src/main/kotlin/Custom.kt
new file mode 100644
index 0000000..b3f75fd
--- /dev/null
+++ b/src/kotlin/src/main/kotlin/Custom.kt
@@ -0,0 +1,121 @@
+package solutions.bitbadger.documents.kotlin
+
+import solutions.bitbadger.documents.common.Configuration
+import solutions.bitbadger.documents.common.Parameter
+import solutions.bitbadger.documents.java.Custom
+import java.sql.Connection
+import java.sql.ResultSet
+
+/**
+ * Custom query execution functions
+ */
+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
+ */
+ inline fun list(
+ query: String,
+ parameters: Collection> = listOf(),
+ conn: Connection,
+ noinline mapFunc: (ResultSet, Class) -> TDoc
+ ) = Custom.list(query, parameters, TDoc::class.java, conn, 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 mapFunc The mapping function between the document and the domain item
+ * @return A list of results for the given query
+ */
+ inline fun list(
+ query: String,
+ parameters: Collection> = listOf(),
+ noinline mapFunc: (ResultSet, Class) -> TDoc
+ ) = Configuration.dbConn().use { list(query, parameters, 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 conn The connection over which the query should be executed
+ * @param mapFunc The mapping function between the document and the domain item
+ * @return The document if one matches the query, `null` otherwise
+ */
+ inline fun single(
+ query: String,
+ parameters: Collection> = listOf(),
+ conn: Connection,
+ noinline mapFunc: (ResultSet, Class) -> TDoc
+ ) = Custom.single(query, parameters, TDoc::class.java, conn, 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 mapFunc The mapping function between the document and the domain item
+ * @return The document if one matches the query, `null` otherwise
+ */
+ inline fun single(
+ query: String,
+ parameters: Collection> = listOf(),
+ noinline mapFunc: (ResultSet, Class) -> TDoc
+ ) = Configuration.dbConn().use { single(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
+ */
+ fun nonQuery(query: String, parameters: Collection> = listOf(), conn: Connection) =
+ Custom.nonQuery(query, parameters, conn)
+
+ /**
+ * Execute a query that returns no results
+ *
+ * @param query The query to retrieve the results
+ * @param parameters Parameters to use for the query
+ */
+ 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
+ */
+ inline fun scalar(
+ query: String,
+ parameters: Collection> = listOf(),
+ conn: Connection,
+ noinline mapFunc: (ResultSet, Class) -> T
+ ) = Custom.scalar(query, parameters, T::class.java, conn, mapFunc)
+
+ /**
+ * 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
+ */
+ inline fun scalar(
+ query: String, parameters: Collection> = listOf(), noinline mapFunc: (ResultSet, Class) -> T
+ ) = Configuration.dbConn().use { scalar(query, parameters, it, mapFunc) }
+}
diff --git a/src/kotlin/src/main/kotlin/Definition.kt b/src/kotlin/src/main/kotlin/Definition.kt
new file mode 100644
index 0000000..07af62a
--- /dev/null
+++ b/src/kotlin/src/main/kotlin/Definition.kt
@@ -0,0 +1,70 @@
+import solutions.bitbadger.documents.common.DocumentIndex
+import java.sql.Connection
+
+/**
+ * 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
+ */
+ fun ensureTable(tableName: String, conn: Connection) =
+ Configuration.dialect("ensure $tableName exists").let {
+ conn.customNonQuery(Definition.ensureTable(tableName, it))
+ conn.customNonQuery(Definition.ensureKey(tableName, it))
+ }
+
+ /**
+ * Create a document table if necessary
+ *
+ * @param tableName The table whose existence should be ensured (may include schema)
+ */
+ 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
+ */
+ fun ensureFieldIndex(tableName: String, indexName: String, fields: Collection, conn: Connection) =
+ conn.customNonQuery(Definition.ensureIndexOn(tableName, indexName, fields))
+
+ /**
+ * 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<
+ */
+ 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
+ */
+ fun ensureDocumentIndex(tableName: String, indexType: DocumentIndex, conn: Connection) =
+ conn.customNonQuery(Definition.ensureDocumentIndexOn(tableName, indexType))
+
+ /**
+ * 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 called on a SQLite connection
+ */
+ fun ensureDocumentIndex(tableName: String, indexType: DocumentIndex) =
+ Configuration.dbConn().use { ensureDocumentIndex(tableName, indexType, it) }
+}
diff --git a/src/main/kotlin/Delete.kt b/src/kotlin/src/main/kotlin/Delete.kt
similarity index 97%
rename from src/main/kotlin/Delete.kt
rename to src/kotlin/src/main/kotlin/Delete.kt
index eca0352..07288a2 100644
--- a/src/main/kotlin/Delete.kt
+++ b/src/kotlin/src/main/kotlin/Delete.kt
@@ -1,10 +1,7 @@
-package solutions.bitbadger.documents
-
import solutions.bitbadger.documents.common.Field
import solutions.bitbadger.documents.common.FieldMatch
import solutions.bitbadger.documents.common.Parameter
import solutions.bitbadger.documents.common.ParameterType
-import solutions.bitbadger.documents.common.query.Delete
import java.sql.Connection
/**
diff --git a/src/main/kotlin/Exists.kt b/src/kotlin/src/main/kotlin/Exists.kt
similarity index 98%
rename from src/main/kotlin/Exists.kt
rename to src/kotlin/src/main/kotlin/Exists.kt
index 4b0072a..dfb1eb2 100644
--- a/src/main/kotlin/Exists.kt
+++ b/src/kotlin/src/main/kotlin/Exists.kt
@@ -1,10 +1,7 @@
-package solutions.bitbadger.documents
-
import solutions.bitbadger.documents.common.Field
import solutions.bitbadger.documents.common.FieldMatch
import solutions.bitbadger.documents.common.Parameter
import solutions.bitbadger.documents.common.ParameterType
-import solutions.bitbadger.documents.common.query.Exists
import java.sql.Connection
/**
diff --git a/src/main/kotlin/Find.kt b/src/kotlin/src/main/kotlin/Find.kt
similarity index 99%
rename from src/main/kotlin/Find.kt
rename to src/kotlin/src/main/kotlin/Find.kt
index 6135145..3e58e71 100644
--- a/src/main/kotlin/Find.kt
+++ b/src/kotlin/src/main/kotlin/Find.kt
@@ -1,11 +1,8 @@
-package solutions.bitbadger.documents
-
import solutions.bitbadger.documents.common.Field
import solutions.bitbadger.documents.common.FieldMatch
import solutions.bitbadger.documents.common.Parameter
import solutions.bitbadger.documents.common.ParameterType
import java.sql.Connection
-import solutions.bitbadger.documents.common.query.Find
import solutions.bitbadger.documents.common.query.orderBy
/**
diff --git a/src/main/kotlin/Parameters.kt b/src/kotlin/src/main/kotlin/Parameters.kt
similarity index 98%
rename from src/main/kotlin/Parameters.kt
rename to src/kotlin/src/main/kotlin/Parameters.kt
index f31a5db..46cd843 100644
--- a/src/main/kotlin/Parameters.kt
+++ b/src/kotlin/src/main/kotlin/Parameters.kt
@@ -1,7 +1,4 @@
-package solutions.bitbadger.documents
-
import solutions.bitbadger.documents.common.*
-import solutions.bitbadger.documents.common.ParameterName
import java.sql.Connection
import java.sql.PreparedStatement
import java.sql.SQLException
diff --git a/src/main/kotlin/Patch.kt b/src/kotlin/src/main/kotlin/Patch.kt
similarity index 98%
rename from src/main/kotlin/Patch.kt
rename to src/kotlin/src/main/kotlin/Patch.kt
index 4b23b78..5217b4a 100644
--- a/src/main/kotlin/Patch.kt
+++ b/src/kotlin/src/main/kotlin/Patch.kt
@@ -1,10 +1,7 @@
-package solutions.bitbadger.documents
-
import solutions.bitbadger.documents.common.Field
import solutions.bitbadger.documents.common.FieldMatch
import solutions.bitbadger.documents.common.Parameter
import solutions.bitbadger.documents.common.ParameterType
-import solutions.bitbadger.documents.common.query.Patch
import java.sql.Connection
/**
diff --git a/src/main/kotlin/RemoveFields.kt b/src/kotlin/src/main/kotlin/RemoveFields.kt
similarity index 98%
rename from src/main/kotlin/RemoveFields.kt
rename to src/kotlin/src/main/kotlin/RemoveFields.kt
index 486351a..d75c63b 100644
--- a/src/main/kotlin/RemoveFields.kt
+++ b/src/kotlin/src/main/kotlin/RemoveFields.kt
@@ -1,7 +1,4 @@
-package solutions.bitbadger.documents
-
import solutions.bitbadger.documents.common.*
-import solutions.bitbadger.documents.common.query.RemoveFields
import java.sql.Connection
/**
diff --git a/src/kotlin/src/main/kotlin/Results.kt b/src/kotlin/src/main/kotlin/Results.kt
new file mode 100644
index 0000000..f961b00
--- /dev/null
+++ b/src/kotlin/src/main/kotlin/Results.kt
@@ -0,0 +1,80 @@
+package solutions.bitbadger.documents.kotlin
+
+import solutions.bitbadger.documents.common.Configuration
+import solutions.bitbadger.documents.common.Dialect
+import solutions.bitbadger.documents.common.DocumentException
+import solutions.bitbadger.documents.java.Results
+import java.sql.PreparedStatement
+import java.sql.ResultSet
+import java.sql.SQLException
+
+/**
+ * Helper functions for handling results
+ */
+object Results {
+
+ /**
+ * Create a domain item from a document, specifying the field in which the document is found
+ *
+ * @param field The field name containing the JSON document
+ * @param rs A `ResultSet` set to the row with the document to be constructed
+ * @return The constructed domain item
+ */
+ inline fun fromDocument(field: String): (ResultSet, Class) -> TDoc =
+ { rs, _ -> Results.fromDocument(field, rs, TDoc::class.java) }
+
+ /**
+ * Create a domain item from a document
+ *
+ * @param rs A `ResultSet` set to the row with the document to be constructed<
+ * @param clazz The class of the document to be returned
+ * @return The constructed domain item
+ */
+ inline fun fromData(rs: ResultSet, clazz: Class = TDoc::class.java) =
+ Results.fromDocument("data", rs, TDoc::class.java)
+
+ /**
+ * Create a list of items for the results of the given command, using the specified mapping function
+ *
+ * @param stmt The prepared statement to execute
+ * @param mapFunc The mapping function from data reader to domain class instance
+ * @return A list of items from the query's result
+ * @throws DocumentException If there is a problem executing the query
+ */
+ inline fun toCustomList(stmt: PreparedStatement, mapFunc: (ResultSet) -> TDoc) =
+ try {
+ stmt.executeQuery().use {
+ val results = mutableListOf()
+ while (it.next()) {
+ results.add(mapFunc(it))
+ }
+ results.toList()
+ }
+ } catch (ex: SQLException) {
+ throw DocumentException("Error retrieving documents from query: ${ex.message}", ex)
+ }
+
+ /**
+ * Extract a count from the first column
+ *
+ * @param rs A `ResultSet` set to the row with the count to retrieve
+ * @return The count from the row
+ */
+ fun toCount(rs: ResultSet, clazz: Class = Long::class.java) =
+ when (Configuration.dialect()) {
+ Dialect.POSTGRESQL -> rs.getInt("it").toLong()
+ Dialect.SQLITE -> rs.getLong("it")
+ }
+
+ /**
+ * Extract a true/false value from the first column
+ *
+ * @param rs A `ResultSet` set to the row with the true/false value to retrieve
+ * @return The true/false value from the row
+ */
+ fun toExists(rs: ResultSet, clazz: Class = Boolean::class.java) =
+ when (Configuration.dialect()) {
+ Dialect.POSTGRESQL -> rs.getBoolean("it")
+ Dialect.SQLITE -> toCount(rs) > 0L
+ }
+}
diff --git a/src/pom.xml b/src/pom.xml
index 60ce46a..544ca00 100644
--- a/src/pom.xml
+++ b/src/pom.xml
@@ -46,6 +46,7 @@
common
+ java
diff --git a/src/test/java/solutions/bitbadger/documents/java/ConfigurationTest.java b/src/test/java/solutions/bitbadger/documents/java/ConfigurationTest.java
index c24b49c..fe67b44 100644
--- a/src/test/java/solutions/bitbadger/documents/java/ConfigurationTest.java
+++ b/src/test/java/solutions/bitbadger/documents/java/ConfigurationTest.java
@@ -2,7 +2,6 @@ package solutions.bitbadger.documents.java;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
-import solutions.bitbadger.documents.*;
import solutions.bitbadger.documents.common.Dialect;
import solutions.bitbadger.documents.common.DocumentException;
diff --git a/src/test/java/solutions/bitbadger/documents/java/integration/common/Count.java b/src/test/java/solutions/bitbadger/documents/java/integration/common/Count.java
deleted file mode 100644
index 8258386..0000000
--- a/src/test/java/solutions/bitbadger/documents/java/integration/common/Count.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package solutions.bitbadger.documents.java.integration.common;
-
-import solutions.bitbadger.documents.integration.ThrowawayDatabase;
-import solutions.bitbadger.documents.java.testDocs.JsonDocument;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static solutions.bitbadger.documents.integration.TypesKt.TEST_TABLE;
-
-final public class Count {
-
- public static void all(ThrowawayDatabase db) {
- JsonDocument.load(db);
- assertEquals(5L, solutions.bitbadger.documents.Count.all(TEST_TABLE, db.getConn()),
- "There should have been 5 documents in the table");
- }
-
-
- private Count() {
- }
-}