Rename java proejct to jvm

This commit is contained in:
2025-03-14 20:59:28 -04:00
parent be3ae8ffab
commit c2e281334d
60 changed files with 3 additions and 3 deletions

110
src/jvm/pom.xml Normal file
View File

@@ -0,0 +1,110 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>solutions.bitbadger.documents</groupId>
<artifactId>java</artifactId>
<version>4.0.0-alpha1-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>solutions.bitbadger</groupId>
<artifactId>documents</artifactId>
<version>4.0.0-alpha1-SNAPSHOT</version>
</parent>
<name>${project.groupId}:${project.artifactId}</name>
<description>Expose a document store interface for PostgreSQL and SQLite (Java Library)</description>
<url>https://bitbadger.solutions/open-source/relational-documents/jvm/</url>
<scm>
<connection>scm:git:https://git.bitbadger.solutions/bit-badger/solutions.bitbadger.documents.git</connection>
<developerConnection>scm:git:https://git.bitbadger.solutions/bit-badger/solutions.bitbadger.documents.git</developerConnection>
<url>https://git.bitbadger.solutions/bit-badger/solutions.bitbadger.documents</url>
</scm>
<properties>
<jackson.version>2.18.2</jackson.version>
</properties>
<dependencies>
<dependency>
<groupId>solutions.bitbadger.documents</groupId>
<artifactId>common</artifactId>
<version>4.0.0-alpha1-SNAPSHOT</version>
<scope>system</scope>
<systemPath>${project.basedir}/../common/target/common-4.0.0-alpha1-SNAPSHOT.jar</systemPath>
<type>jar</type>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>process-sources</phase>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<sourceDirs>
<sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
</sourceDirs>
</configuration>
</execution>
<execution>
<id>test-compile</id>
<phase>process-test-sources</phase>
<goals>
<goal>test-compile</goal>
</goals>
<configuration>
<sourceDirs>
<sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
<sourceDir>${project.basedir}/src/test/java</sourceDir>
</sourceDirs>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.22.2</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,520 @@
@file:JvmName("ConnExt")
package solutions.bitbadger.documents.java
import solutions.bitbadger.documents.common.*
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 clazz The class of the document to be returned
* @param mapFunc The mapping function between the document and the domain item
* @return A list of results for the given query
*/
fun <TDoc> Connection.customList(
query: String,
parameters: Collection<Parameter<*>> = listOf(),
clazz: Class<TDoc>,
mapFunc: (ResultSet, Class<TDoc>) -> TDoc
) = Custom.list(query, parameters, clazz, 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 clazz The class of the document to be returned
* @param mapFunc The mapping function between the document and the domain item
* @return The document if one matches the query, `null` otherwise
*/
fun <TDoc> Connection.customSingle(
query: String,
parameters: Collection<Parameter<*>> = listOf(),
clazz: Class<TDoc>,
mapFunc: (ResultSet, Class<TDoc>) -> TDoc
) = Custom.single(query, parameters, clazz, 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<Parameter<*>> = 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 clazz The class of the document to be returned
* @param mapFunc The mapping function between the document and the domain item
* @return The scalar value from the query
*/
fun <T : Any> Connection.customScalar(
query: String,
parameters: Collection<Parameter<*>> = listOf(),
clazz: Class<T>,
mapFunc: (ResultSet, Class<T>) -> T
) = Custom.scalar(query, parameters, clazz, 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) =
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<String>) =
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
*/
@Throws(DocumentException::class)
fun Connection.ensureDocumentIndex(tableName: String, indexType: DocumentIndex) =
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
*/
fun <TDoc> 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
*/
fun <TDoc> 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
*/
fun <TKey, TDoc> 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<Field<*>>, 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
*/
@Throws(DocumentException::class)
fun <TContains> 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
*/
@Throws(DocumentException::class)
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 <TKey> 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<Field<*>>, 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
*/
@Throws(DocumentException::class)
fun <TContains> 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
*/
@Throws(DocumentException::class)
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 clazz The class of the document to be returned
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return A list of documents from the given table
*/
@JvmOverloads
fun <TDoc> Connection.findAll(tableName: String, clazz: Class<TDoc>, orderBy: Collection<Field<*>>? = null) =
Find.all(tableName, clazz, 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
* @param clazz The class of the document to be returned
* @return The document if it is found, `null` otherwise
*/
fun <TKey, TDoc> Connection.findById(tableName: String, docId: TKey, clazz: Class<TDoc>) =
Find.byId(tableName, docId, clazz, 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 clazz The class of the document to be returned
* @param howMatched How the fields should be matched
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return A list of documents matching the field comparison
*/
@JvmOverloads
fun <TDoc> Connection.findByFields(
tableName: String,
fields: Collection<Field<*>>,
clazz: Class<TDoc>,
howMatched: FieldMatch? = null,
orderBy: Collection<Field<*>>? = null
) =
Find.byFields(tableName, fields, clazz, 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 clazz The class of the document to be returned
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return A list of documents matching the JSON containment query
* @throws DocumentException If called on a SQLite connection
*/
@Throws(DocumentException::class)
@JvmOverloads
fun <TDoc, TContains> Connection.findByContains(
tableName: String,
criteria: TContains,
clazz: Class<TDoc>,
orderBy: Collection<Field<*>>? = null
) =
Find.byContains(tableName, criteria, clazz, 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 clazz The class of the document to be returned
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return A list of documents matching the JSON Path match query
* @throws DocumentException If called on a SQLite connection
*/
@Throws(DocumentException::class)
@JvmOverloads
fun <TDoc> Connection.findByJsonPath(
tableName: String,
path: String,
clazz: Class<TDoc>,
orderBy: Collection<Field<*>>? = null
) =
Find.byJsonPath(tableName, path, clazz, 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 clazz The class of the document to be returned
* @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`)
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return The first document matching the field comparison, or `null` if no matches are found
*/
@Throws(DocumentException::class)
@JvmOverloads
fun <TDoc> Connection.findFirstByFields(
tableName: String,
fields: Collection<Field<*>>,
clazz: Class<TDoc>,
howMatched: FieldMatch? = null,
orderBy: Collection<Field<*>>? = null
) =
Find.firstByFields(tableName, fields, clazz, 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 clazz The class of the document to be returned
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return The first document matching the JSON containment query, or `null` if no matches are found
* @throws DocumentException If called on a SQLite connection
*/
@Throws(DocumentException::class)
@JvmOverloads
fun <TDoc, TContains> Connection.findFirstByContains(
tableName: String,
criteria: TContains,
clazz: Class<TDoc>,
orderBy: Collection<Field<*>>? = null
) =
Find.firstByContains(tableName, criteria, clazz, 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 clazz The class of the document to be returned
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return The first document matching the JSON Path match query, or `null` if no matches are found
* @throws DocumentException If called on a SQLite connection
*/
@Throws(DocumentException::class)
@JvmOverloads
fun <TDoc> Connection.findFirstByJsonPath(
tableName: String,
path: String,
clazz: Class<TDoc>,
orderBy: Collection<Field<*>>? = null
) =
Find.firstByJsonPath(tableName, path, clazz, 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
*/
fun <TKey, TPatch> 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
*/
fun <TPatch> Connection.patchByFields(
tableName: String,
fields: Collection<Field<*>>,
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
*/
@Throws(DocumentException::class)
fun <TContains, TPatch> 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
*/
@Throws(DocumentException::class)
fun <TPatch> 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 <TKey> Connection.removeFieldsById(tableName: String, docId: TKey, toRemove: Collection<String>) =
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<Field<*>>,
toRemove: Collection<String>,
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
*/
@Throws(DocumentException::class)
fun <TContains> Connection.removeFieldsByContains(
tableName: String,
criteria: TContains,
toRemove: Collection<String>
) =
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
*/
@Throws(DocumentException::class)
fun Connection.removeFieldsByJsonPath(tableName: String, path: String, toRemove: Collection<String>) =
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 <TKey> 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<Field<*>>, 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
*/
@Throws(DocumentException::class)
fun <TContains> 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)

View File

@@ -0,0 +1,131 @@
package solutions.bitbadger.documents.java
import solutions.bitbadger.documents.common.*
import solutions.bitbadger.documents.common.query.Count
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(Count.all(tableName), listOf(), Long::class.java, 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<Field<*>>,
howMatched: FieldMatch? = null,
conn: Connection
): Long {
val named = Parameters.nameFields(fields)
return conn.customScalar(
Count.byFields(tableName, named, howMatched),
Parameters.addFields(named),
Long::class.java,
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<Field<*>>, 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
fun <TContains> byContains(tableName: String, criteria: TContains, conn: Connection) =
conn.customScalar(
Count.byContains(tableName),
listOf(Parameters.json(":criteria", criteria)),
Long::class.java,
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
fun <TContains> 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)),
Long::class.java,
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) }
}

View File

@@ -0,0 +1,142 @@
package solutions.bitbadger.documents.java
import solutions.bitbadger.documents.common.Configuration
import solutions.bitbadger.documents.common.Parameter
import java.sql.Connection
import java.sql.ResultSet
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
*/
@JvmStatic
fun <TDoc> list(
query: String,
parameters: Collection<Parameter<*>> = listOf(),
clazz: Class<TDoc>,
conn: Connection,
mapFunc: (ResultSet, Class<TDoc>) -> TDoc
) = Parameters.apply(conn, query, parameters).use { Results.toCustomList(it, clazz, mapFunc) }
/**
* Execute a query that returns a list of results (creates connection)
*
* @param query The query to retrieve the results
* @param parameters Parameters to use for the query
* @param clazz The class of the document to be returned
* @param mapFunc The mapping function between the document and the domain item
* @return A list of results for the given query
*/
@JvmStatic
fun <TDoc> list(
query: String,
parameters: Collection<Parameter<*>> = listOf(),
clazz: Class<TDoc>,
mapFunc: (ResultSet, Class<TDoc>) -> TDoc
) = Configuration.dbConn().use { list(query, parameters, clazz, it, mapFunc) }
/**
* Execute a query that returns one or no results
*
* @param query The query to retrieve the results
* @param parameters Parameters to use for the query
* @param clazz The class of the document to be returned
* @param conn The connection over which the query should be executed
* @param mapFunc The mapping function between the document and the domain item
* @return The document if one matches the query, `null` otherwise
*/
@JvmStatic
fun <TDoc> single(
query: String,
parameters: Collection<Parameter<*>> = listOf(),
clazz: Class<TDoc>,
conn: Connection,
mapFunc: (ResultSet, Class<TDoc>) -> TDoc
) = list("$query LIMIT 1", parameters, clazz, conn, mapFunc).singleOrNull()
/**
* Execute a query that returns one or no results
*
* @param query The query to retrieve the results
* @param parameters Parameters to use for the query
* @param clazz The class of the document to be returned
* @param mapFunc The mapping function between the document and the domain item
* @return The document if one matches the query, `null` otherwise
*/
@JvmStatic
fun <TDoc> single(
query: String,
parameters: Collection<Parameter<*>> = listOf(),
clazz: Class<TDoc>,
mapFunc: (ResultSet, Class<TDoc>) -> TDoc
) = Configuration.dbConn().use { single(query, parameters, clazz, 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
*/
@JvmStatic
fun nonQuery(query: String, parameters: Collection<Parameter<*>> = listOf(), conn: Connection) {
Parameters.apply(conn, query, parameters).use { it.executeUpdate() }
}
/**
* Execute a query that returns no results
*
* @param query The query to retrieve the results
* @param parameters Parameters to use for the query
*/
@JvmStatic
@JvmOverloads
fun nonQuery(query: String, parameters: Collection<Parameter<*>> = 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
*/
@JvmStatic
fun <T : Any> scalar(
query: String,
parameters: Collection<Parameter<*>> = listOf(),
clazz: Class<T>,
conn: Connection,
mapFunc: (ResultSet, Class<T>) -> T
) = Parameters.apply(conn, query, parameters).use { stmt ->
stmt.executeQuery().use { rs ->
rs.next()
mapFunc(rs, clazz)
}
}
/**
* 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
*/
fun <T : Any> scalar(
query: String,
parameters: Collection<Parameter<*>> = listOf(),
clazz: Class<T>,
mapFunc: (ResultSet, Class<T>) -> T
) = Configuration.dbConn().use { scalar(query, parameters, clazz, it, mapFunc) }
}

View File

@@ -0,0 +1,83 @@
package solutions.bitbadger.documents.java
import solutions.bitbadger.documents.common.Configuration
import solutions.bitbadger.documents.common.DocumentException
import solutions.bitbadger.documents.common.DocumentIndex
import solutions.bitbadger.documents.common.query.Definition
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
*/
@JvmStatic
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)
*/
@JvmStatic
fun ensureTable(tableName: String) =
Configuration.dbConn().use { ensureTable(tableName, it) }
/**
* Create an index on field(s) within documents in the specified table if necessary
*
* @param tableName The table to be indexed (may include schema)
* @param indexName The name of the index to create
* @param fields One or more fields to be indexed
* @param conn The connection on which the query should be executed
*/
@JvmStatic
fun ensureFieldIndex(tableName: String, indexName: String, fields: Collection<String>, 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
*/
@JvmStatic
fun ensureFieldIndex(tableName: String, indexName: String, fields: Collection<String>) =
Configuration.dbConn().use { ensureFieldIndex(tableName, indexName, fields, it) }
/**
* Create a document index on a table (PostgreSQL only)
*
* @param tableName The table to be indexed (may include schema)
* @param indexType The type of index to ensure
* @param conn The connection on which the query should be executed
* @throws DocumentException If called on a SQLite connection
*/
@Throws(DocumentException::class)
@JvmStatic
fun ensureDocumentIndex(tableName: String, indexType: DocumentIndex, conn: Connection) =
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
*/
@Throws(DocumentException::class)
@JvmStatic
fun ensureDocumentIndex(tableName: String, indexType: DocumentIndex) =
Configuration.dbConn().use { ensureDocumentIndex(tableName, indexType, it) }
}

View File

@@ -0,0 +1,112 @@
package solutions.bitbadger.documents.java
import solutions.bitbadger.documents.common.*
import solutions.bitbadger.documents.common.query.Delete
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
*/
@JvmStatic
fun <TKey> byId(tableName: String, docId: TKey, conn: Connection) =
conn.customNonQuery(
Delete.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
*/
@JvmStatic
fun <TKey> 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
*/
@JvmStatic
@JvmOverloads
fun byFields(tableName: String, fields: Collection<Field<*>>, howMatched: FieldMatch? = null, conn: Connection) {
val named = Parameters.nameFields(fields)
conn.customNonQuery(Delete.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
*/
@JvmStatic
@JvmOverloads
fun byFields(tableName: String, fields: Collection<Field<*>>, howMatched: FieldMatch? = null) =
Configuration.dbConn().use { byFields(tableName, fields, howMatched, it) }
/**
* Delete documents using a JSON containment query (PostgreSQL only)
*
* @param tableName The name of the table from which documents should be deleted
* @param criteria The object for which JSON containment should be checked
* @param conn The connection on which the deletion should be executed
* @throws DocumentException If called on a SQLite connection
*/
@Throws(DocumentException::class)
@JvmStatic
fun <TContains> 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
*/
@Throws(DocumentException::class)
@JvmStatic
fun <TContains> byContains(tableName: String, criteria: TContains) =
Configuration.dbConn().use { byContains(tableName, criteria, it) }
/**
* Delete documents using a JSON Path match query (PostgreSQL only)
*
* @param tableName The name of the table from which documents should be deleted
* @param path The JSON path comparison to match
* @param conn The connection on which the deletion should be executed
* @throws DocumentException If called on a SQLite connection
*/
@Throws(DocumentException::class)
@JvmStatic
fun byJsonPath(tableName: String, path: String, conn: Connection) =
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
*/
@Throws(DocumentException::class)
@JvmStatic
fun byJsonPath(tableName: String, path: String) =
Configuration.dbConn().use { byJsonPath(tableName, path, it) }
}

View File

@@ -0,0 +1,119 @@
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
import solutions.bitbadger.documents.common.query.Document
import solutions.bitbadger.documents.common.query.Where
import solutions.bitbadger.documents.common.query.statementWhere
/**
* Functions for manipulating documents
*/
object Document {
/**
* Insert a new document
*
* @param tableName The table into which the document should be inserted (may include schema)
* @param document The document to be inserted
* @param conn The connection on which the query should be executed
*/
@JvmStatic
fun <TDoc> insert(tableName: String, document: TDoc, conn: Connection) {
val strategy = Configuration.autoIdStrategy
val query = if (strategy == AutoId.DISABLED) {
Document.insert(tableName)
} else {
val idField = Configuration.idField
val dialect = Configuration.dialect("Create auto-ID insert query")
val dataParam = if (AutoId.needsAutoId(strategy, document, idField)) {
when (dialect) {
Dialect.POSTGRESQL ->
when (strategy) {
AutoId.NUMBER -> "' || (SELECT coalesce(max(data->>'$idField')::numeric, 0) + 1 " +
"FROM $tableName) || '"
AutoId.UUID -> "\"${AutoId.generateUUID()}\""
AutoId.RANDOM_STRING -> "\"${AutoId.generateRandomString()}\""
else -> "\"' || (:data)->>'$idField' || '\""
}.let { ":data::jsonb || ('{\"$idField\":$it}')::jsonb" }
Dialect.SQLITE ->
when (strategy) {
AutoId.NUMBER -> "(SELECT coalesce(max(data->>'$idField'), 0) + 1 FROM $tableName)"
AutoId.UUID -> "'${AutoId.generateUUID()}'"
AutoId.RANDOM_STRING -> "'${AutoId.generateRandomString()}'"
else -> "(:data)->>'$idField'"
}.let { "json_set(:data, '$.$idField', $it)" }
}
} else {
":data"
}
Document.insert(tableName).replace(":data", dataParam)
}
conn.customNonQuery(query, listOf(Parameters.json(":data", document)))
}
/**
* Insert a new document
*
* @param tableName The table into which the document should be inserted (may include schema)
* @param document The document to be inserted
*/
@JvmStatic
fun <TDoc> insert(tableName: String, document: TDoc) =
Configuration.dbConn().use { insert(tableName, document, it) }
/**
* Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
*
* @param tableName The table in which the document should be saved (may include schema)
* @param document The document to be saved
* @param conn The connection on which the query should be executed
*/
@JvmStatic
fun <TDoc> save(tableName: String, document: TDoc, conn: Connection) =
conn.customNonQuery(Document.save(tableName), listOf(Parameters.json(":data", document)))
/**
* Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
*
* @param tableName The table in which the document should be saved (may include schema)
* @param document The document to be saved
*/
@JvmStatic
fun <TDoc> save(tableName: String, document: TDoc) =
Configuration.dbConn().use { save(tableName, document, it) }
/**
* Update (replace) a document by its ID
*
* @param tableName The table in which the document should be replaced (may include schema)
* @param docId The ID of the document to be replaced
* @param document The document to be replaced
* @param conn The connection on which the query should be executed
*/
@JvmStatic
fun <TKey, TDoc> update(tableName: String, docId: TKey, document: TDoc, conn: Connection) =
conn.customNonQuery(
statementWhere(Document.update(tableName), Where.byId(":id", docId)),
Parameters.addFields(
listOf(Field.equal(Configuration.idField, docId, ":id")),
mutableListOf(Parameters.json(":data", document))
)
)
/**
* Update (replace) a document by its ID
*
* @param tableName The table in which the document should be replaced (may include schema)
* @param docId The ID of the document to be replaced
* @param document The document to be replaced
*/
@JvmStatic
fun <TKey, TDoc> update(tableName: String, docId: TKey, document: TDoc) =
Configuration.dbConn().use { update(tableName, docId, document, it) }
}

View File

@@ -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()
}

View File

@@ -0,0 +1,142 @@
package solutions.bitbadger.documents.java
import solutions.bitbadger.documents.common.*
import solutions.bitbadger.documents.common.query.Exists
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
*/
@JvmStatic
fun <TKey> byId(tableName: String, docId: TKey, conn: Connection) =
conn.customScalar(
Exists.byId(tableName, docId),
Parameters.addFields(listOf(Field.equal(Configuration.idField, docId, ":id"))),
Boolean::class.java,
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
*/
@JvmStatic
fun <TKey> 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
*/
@JvmStatic
@JvmOverloads
fun byFields(
tableName: String,
fields: Collection<Field<*>>,
howMatched: FieldMatch? = null,
conn: Connection
): Boolean {
val named = Parameters.nameFields(fields)
return conn.customScalar(
Exists.byFields(tableName, named, howMatched),
Parameters.addFields(named),
Boolean::class.java,
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
*/
@JvmStatic
@JvmOverloads
fun byFields(tableName: String, fields: Collection<Field<*>>, howMatched: FieldMatch? = null) =
Configuration.dbConn().use { byFields(tableName, fields, howMatched, it) }
/**
* Determine document existence using a JSON containment query (PostgreSQL only)
*
* @param tableName The name of the table in which document existence should be checked
* @param criteria The object for which JSON containment should be checked
* @param conn The connection on which the existence check should be executed
* @return True if any matching documents exist, false if not
* @throws DocumentException If called on a SQLite connection
*/
@Throws(DocumentException::class)
@JvmStatic
fun <TContains> byContains(tableName: String, criteria: TContains, conn: Connection) =
conn.customScalar(
Exists.byContains(tableName),
listOf(Parameters.json(":criteria", criteria)),
Boolean::class.java,
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
*/
@Throws(DocumentException::class)
@JvmStatic
fun <TContains> byContains(tableName: String, criteria: TContains) =
Configuration.dbConn().use { byContains(tableName, criteria, it) }
/**
* Determine document existence using a JSON Path match query (PostgreSQL only)
*
* @param tableName The name of the table in which document existence should be checked
* @param path The JSON path comparison to match
* @param conn The connection on which the existence check should be executed
* @return True if any matching documents exist, false if not
* @throws DocumentException If called on a SQLite connection
*/
@Throws(DocumentException::class)
@JvmStatic
fun byJsonPath(tableName: String, path: String, conn: Connection) =
conn.customScalar(
Exists.byJsonPath(tableName),
listOf(Parameter(":path", ParameterType.STRING, path)),
Boolean::class.java,
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
*/
@Throws(DocumentException::class)
@JvmStatic
fun byJsonPath(tableName: String, path: String) =
Configuration.dbConn().use { byJsonPath(tableName, path, it) }
}

View File

@@ -0,0 +1,471 @@
package solutions.bitbadger.documents.java
import solutions.bitbadger.documents.common.*
import solutions.bitbadger.documents.common.query.Find
import solutions.bitbadger.documents.common.query.orderBy
import java.sql.Connection
/**
* Functions to find and retrieve documents
*/
object Find {
/**
* Retrieve all documents in the given table, ordering results by the optional given fields
*
* @param tableName The table from which documents should be retrieved
* @param clazz The class of the document to be returned
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @param conn The connection over which documents should be retrieved
* @return A list of documents from the given table
*/
@JvmStatic
fun <TDoc> all(tableName: String, clazz: Class<TDoc>, orderBy: Collection<Field<*>>? = null, conn: Connection) =
conn.customList(Find.all(tableName) + (orderBy?.let(::orderBy) ?: ""), listOf(), clazz, Results::fromData)
/**
* Retrieve all documents in the given table
*
* @param tableName The table from which documents should be retrieved
* @param clazz The class of the document to be returned
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return A list of documents from the given table
*/
@JvmStatic
@JvmOverloads
fun <TDoc> all(tableName: String, clazz: Class<TDoc>, orderBy: Collection<Field<*>>? = null) =
Configuration.dbConn().use { all(tableName, clazz, orderBy, it) }
/**
* Retrieve all documents in the given table
*
* @param tableName The table from which documents should be retrieved
* @param clazz The class of the document to be returned
* @param conn The connection over which documents should be retrieved
* @return A list of documents from the given table
*/
@JvmStatic
fun <TDoc> all(tableName: String, clazz: Class<TDoc>, conn: Connection) =
all(tableName, clazz, null, conn)
/**
* Retrieve a document by its ID
*
* @param tableName The table from which the document should be retrieved
* @param docId The ID of the document to retrieve
* @param clazz The class of the document to be returned
* @param conn The connection over which documents should be retrieved
* @return The document if it is found, `null` otherwise
*/
@JvmStatic
fun <TKey, TDoc> byId(tableName: String, docId: TKey, clazz: Class<TDoc>, conn: Connection) =
conn.customSingle(
Find.byId(tableName, docId),
Parameters.addFields(listOf(Field.equal(Configuration.idField, docId, ":id"))),
clazz,
Results::fromData
)
/**
* Retrieve a document by its ID
*
* @param tableName The table from which the document should be retrieved
* @param docId The ID of the document to retrieve
* @param clazz The class of the document to be returned
* @return The document if it is found, `null` otherwise
*/
@JvmStatic
fun <TKey, TDoc> byId(tableName: String, docId: TKey, clazz: Class<TDoc>) =
Configuration.dbConn().use { byId(tableName, docId, clazz, it) }
/**
* Retrieve documents using a field comparison, ordering results by the given fields
*
* @param tableName The table from which documents should be retrieved
* @param fields The fields which should be compared
* @param clazz The class of the document to be returned
* @param howMatched How the fields should be matched
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @param conn The connection over which documents should be retrieved
* @return A list of documents matching the field comparison
*/
@JvmStatic
fun <TDoc> byFields(
tableName: String,
fields: Collection<Field<*>>,
clazz: Class<TDoc>,
howMatched: FieldMatch? = null,
orderBy: Collection<Field<*>>? = null,
conn: Connection
): List<TDoc> {
val named = Parameters.nameFields(fields)
return conn.customList(
Find.byFields(tableName, named, howMatched) + (orderBy?.let(::orderBy) ?: ""),
Parameters.addFields(named),
clazz,
Results::fromData
)
}
/**
* Retrieve documents using a field comparison, ordering results by the given fields
*
* @param tableName The table from which documents should be retrieved
* @param fields The fields which should be compared
* @param clazz The class of the document to be returned
* @param howMatched How the fields should be matched
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return A list of documents matching the field comparison
*/
@JvmStatic
@JvmOverloads
fun <TDoc> byFields(
tableName: String,
fields: Collection<Field<*>>,
clazz: Class<TDoc>,
howMatched: FieldMatch? = null,
orderBy: Collection<Field<*>>? = null
) =
Configuration.dbConn().use { byFields(tableName, fields, clazz, howMatched, orderBy, it) }
/**
* Retrieve documents using a field comparison
*
* @param tableName The table from which documents should be retrieved
* @param fields The fields which should be compared
* @param clazz The class of the document to be returned
* @param howMatched How the fields should be matched
* @param conn The connection over which documents should be retrieved
* @return A list of documents matching the field comparison
*/
@JvmStatic
fun <TDoc> byFields(
tableName: String,
fields: Collection<Field<*>>,
clazz: Class<TDoc>,
howMatched: FieldMatch? = null,
conn: Connection
) =
byFields(tableName, fields, clazz, howMatched, null, conn)
/**
* Retrieve documents using a JSON containment query, ordering results by the given fields (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved
* @param criteria The object for which JSON containment should be checked
* @param clazz The class of the document to be returned
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @param conn The connection over which documents should be retrieved
* @return A list of documents matching the JSON containment query
* @throws DocumentException If called on a SQLite connection
*/
@Throws(DocumentException::class)
@JvmStatic
fun <TDoc, TContains> byContains(
tableName: String,
criteria: TContains,
clazz: Class<TDoc>,
orderBy: Collection<Field<*>>? = null,
conn: Connection
) =
conn.customList(
Find.byContains(tableName) + (orderBy?.let(::orderBy) ?: ""),
listOf(Parameters.json(":criteria", criteria)),
clazz,
Results::fromData
)
/**
* Retrieve documents using a JSON containment query, ordering results by the given fields (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved
* @param criteria The object for which JSON containment should be checked
* @param clazz The class of the document to be returned
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return A list of documents matching the JSON containment query
* @throws DocumentException If called on a SQLite connection
*/
@Throws(DocumentException::class)
@JvmStatic
@JvmOverloads
fun <TDoc, TContains> byContains(
tableName: String,
criteria: TContains,
clazz: Class<TDoc>,
orderBy: Collection<Field<*>>? = null
) =
Configuration.dbConn().use { byContains(tableName, criteria, clazz, orderBy, it) }
/**
* Retrieve documents using a JSON containment query (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved
* @param criteria The object for which JSON containment should be checked
* @param clazz The class of the document to be returned
* @param conn The connection over which documents should be retrieved
* @return A list of documents matching the JSON containment query
* @throws DocumentException If called on a SQLite connection
*/
@Throws(DocumentException::class)
@JvmStatic
fun <TDoc, TContains> byContains(tableName: String, criteria: TContains, clazz: Class<TDoc>, conn: Connection) =
byContains(tableName, criteria, clazz, null, conn)
/**
* Retrieve documents using a JSON Path match query, ordering results by the given fields (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved
* @param path The JSON path comparison to match
* @param clazz The class of the document to be returned
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @param conn The connection over which documents should be retrieved
* @return A list of documents matching the JSON Path match query
* @throws DocumentException If called on a SQLite connection
*/
@Throws(DocumentException::class)
@JvmStatic
fun <TDoc> byJsonPath(
tableName: String,
path: String,
clazz: Class<TDoc>,
orderBy: Collection<Field<*>>? = null,
conn: Connection
) =
conn.customList(
Find.byJsonPath(tableName) + (orderBy?.let(::orderBy) ?: ""),
listOf(Parameter(":path", ParameterType.STRING, path)),
clazz,
Results::fromData
)
/**
* Retrieve documents using a JSON Path match query, ordering results by the given fields (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved
* @param path The JSON path comparison to match
* @param clazz The class of the document to be returned
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return A list of documents matching the JSON Path match query
* @throws DocumentException If called on a SQLite connection
*/
@Throws(DocumentException::class)
@JvmStatic
@JvmOverloads
fun <TDoc> byJsonPath(tableName: String, path: String, clazz: Class<TDoc>, orderBy: Collection<Field<*>>? = null) =
Configuration.dbConn().use { byJsonPath(tableName, path, clazz, orderBy, it) }
/**
* Retrieve documents using a JSON Path match query (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved
* @param path The JSON path comparison to match
* @param clazz The class of the document to be returned
* @param conn The connection over which documents should be retrieved
* @return A list of documents matching the JSON Path match query
* @throws DocumentException If called on a SQLite connection
*/
@Throws(DocumentException::class)
@JvmStatic
fun <TDoc> byJsonPath(tableName: String, path: String, clazz: Class<TDoc>, conn: Connection) =
byJsonPath(tableName, path, clazz, null, conn)
/**
* Retrieve the first document using a field comparison and optional ordering fields
*
* @param tableName The table from which documents should be retrieved
* @param fields The fields which should be compared
* @param clazz The class of the document to be returned
* @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`)
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @param conn The connection over which documents should be retrieved
* @return The first document matching the field comparison, or `null` if no matches are found
*/
@JvmStatic
fun <TDoc> firstByFields(
tableName: String,
fields: Collection<Field<*>>,
clazz: Class<TDoc>,
howMatched: FieldMatch? = null,
orderBy: Collection<Field<*>>? = null,
conn: Connection
): TDoc? {
val named = Parameters.nameFields(fields)
return conn.customSingle(
Find.byFields(tableName, named, howMatched) + (orderBy?.let(::orderBy) ?: ""),
Parameters.addFields(named),
clazz,
Results::fromData
)
}
/**
* Retrieve the first document using a field comparison and optional ordering fields
*
* @param tableName The table from which documents should be retrieved
* @param fields The fields which should be compared
* @param clazz The class of the document to be returned
* @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`)
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return The first document matching the field comparison, or `null` if no matches are found
*/
@JvmStatic
@JvmOverloads
fun <TDoc> firstByFields(
tableName: String,
fields: Collection<Field<*>>,
clazz: Class<TDoc>,
howMatched: FieldMatch? = null,
orderBy: Collection<Field<*>>? = null
) =
Configuration.dbConn().use { firstByFields(tableName, fields, clazz, howMatched, orderBy, it) }
/**
* Retrieve the first document using a field comparison
*
* @param tableName The table from which documents should be retrieved
* @param fields The fields which should be compared
* @param clazz The class of the document to be returned
* @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`)
* @param conn The connection over which documents should be retrieved
* @return The first document matching the field comparison, or `null` if no matches are found
*/
@JvmStatic
fun <TDoc> firstByFields(
tableName: String,
fields: Collection<Field<*>>,
clazz: Class<TDoc>,
howMatched: FieldMatch? = null,
conn: Connection
) =
firstByFields(tableName, fields, clazz, howMatched, null, conn)
/**
* Retrieve the first document using a JSON containment query and optional ordering fields (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved
* @param criteria The object for which JSON containment should be checked
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @param conn The connection over which documents should be retrieved
* @return The first document matching the JSON containment query, or `null` if no matches are found
* @throws DocumentException If called on a SQLite connection
*/
@Throws(DocumentException::class)
@JvmStatic
fun <TDoc, TContains> firstByContains(
tableName: String,
criteria: TContains,
clazz: Class<TDoc>,
orderBy: Collection<Field<*>>? = null,
conn: Connection
) =
conn.customSingle(
Find.byContains(tableName) + (orderBy?.let(::orderBy) ?: ""),
listOf(Parameters.json(":criteria", criteria)),
clazz,
Results::fromData
)
/**
* Retrieve the first document using a JSON containment query (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved
* @param criteria The object for which JSON containment should be checked
* @param clazz The class of the document to be returned
* @param conn The connection over which documents should be retrieved
* @return The first document matching the JSON containment query, or `null` if no matches are found
* @throws DocumentException If called on a SQLite connection
*/
@Throws(DocumentException::class)
@JvmStatic
fun <TDoc, TContains> firstByContains(
tableName: String,
criteria: TContains,
clazz: Class<TDoc>,
conn: Connection
) =
firstByContains(tableName, criteria, clazz, null, conn)
/**
* Retrieve the first document using a JSON containment query and optional ordering fields (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved
* @param criteria The object for which JSON containment should be checked
* @param clazz The class of the document to be returned
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return The first document matching the JSON containment query, or `null` if no matches are found
* @throws DocumentException If called on a SQLite connection
*/
@Throws(DocumentException::class)
@JvmStatic
@JvmOverloads
fun <TDoc, TContains> firstByContains(
tableName: String,
criteria: TContains,
clazz: Class<TDoc>,
orderBy: Collection<Field<*>>? = null
) =
Configuration.dbConn().use { firstByContains(tableName, criteria, clazz, orderBy, it) }
/**
* Retrieve the first document using a JSON Path match query and optional ordering fields (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved
* @param path The JSON path comparison to match
* @param clazz The class of the document to be returned
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @param conn The connection over which documents should be retrieved
* @return The first document matching the JSON Path match query, or `null` if no matches are found
* @throws DocumentException If called on a SQLite connection
*/
@Throws(DocumentException::class)
@JvmStatic
fun <TDoc> firstByJsonPath(
tableName: String,
path: String,
clazz: Class<TDoc>,
orderBy: Collection<Field<*>>? = null,
conn: Connection
) =
conn.customSingle(
Find.byJsonPath(tableName) + (orderBy?.let(::orderBy) ?: ""),
listOf(Parameter(":path", ParameterType.STRING, path)),
clazz,
Results::fromData
)
/**
* Retrieve the first document using a JSON Path match query (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved
* @param path The JSON path comparison to match
* @param clazz The class of the document to be returned
* @param conn The connection over which documents should be retrieved
* @return The first document matching the JSON Path match query, or `null` if no matches are found
* @throws DocumentException If called on a SQLite connection
*/
@Throws(DocumentException::class)
@JvmStatic
fun <TDoc> firstByJsonPath(tableName: String, path: String, clazz: Class<TDoc>, conn: Connection) =
firstByJsonPath(tableName, path, clazz, null, conn)
/**
* Retrieve the first document using a JSON Path match query and optional ordering fields (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved
* @param path The JSON path comparison to match
* @param clazz The class of the document to be returned
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return The first document matching the JSON Path match query, or `null` if no matches are found
* @throws DocumentException If called on a SQLite connection
*/
@Throws(DocumentException::class)
@JvmStatic
@JvmOverloads
fun <TDoc> firstByJsonPath(
tableName: String,
path: String,
clazz: Class<TDoc>,
orderBy: Collection<Field<*>>? = null
) =
Configuration.dbConn().use { firstByJsonPath(tableName, path, clazz, orderBy, it) }
}

View File

@@ -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 <TDoc> serialize(document: TDoc): String {
TODO("Replace this serializer in DocumentConfig.serializer")
}
override fun <TDoc> deserialize(json: String, clazz: Class<TDoc>): TDoc {
TODO("Replace this serializer in DocumentConfig.serializer")
}
}

View File

@@ -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 <daniel@bitbadger.solutions>
*/
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<Field<*>>): Collection<Field<*>> {
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 <T> 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<Field<*>>, existing: MutableCollection<Parameter<*>> = 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<Parameter<*>>) =
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<Parameter<*>>): 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<Pair<Int, Parameter<*>>>()
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<String>, parameterName: String = ":name"): MutableCollection<Parameter<*>> =
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()
}
}

View File

@@ -0,0 +1,144 @@
package solutions.bitbadger.documents.java
import solutions.bitbadger.documents.common.*
import solutions.bitbadger.documents.common.query.Patch
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
*/
@JvmStatic
fun <TKey, TPatch> 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
*/
@JvmStatic
fun <TKey, TPatch> 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
*/
@JvmStatic
fun <TPatch> byFields(
tableName: String,
fields: Collection<Field<*>>,
patch: TPatch,
howMatched: FieldMatch? = null,
conn: Connection
) {
val named = Parameters.nameFields(fields)
conn.customNonQuery(
Patch.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
*/
@JvmStatic
@JvmOverloads
fun <TPatch> byFields(
tableName: String,
fields: Collection<Field<*>>,
patch: TPatch,
howMatched: FieldMatch? = null
) =
Configuration.dbConn().use { byFields(tableName, fields, patch, howMatched, it) }
/**
* Patch documents using a JSON containment query (PostgreSQL only)
*
* @param tableName The name of the table in which documents should be patched
* @param criteria The object against which JSON containment should be checked
* @param patch The object whose properties should be replaced in the document
* @param conn The connection on which the update should be executed
* @throws DocumentException If called on a SQLite connection
*/
@Throws(DocumentException::class)
@JvmStatic
fun <TContains, TPatch> 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
*/
@Throws(DocumentException::class)
@JvmStatic
fun <TContains, TPatch> byContains(tableName: String, criteria: TContains, patch: TPatch) =
Configuration.dbConn().use { byContains(tableName, criteria, patch, it) }
/**
* Patch documents using a JSON Path match query (PostgreSQL only)
*
* @param tableName The name of the table in which documents should be patched
* @param path The JSON path comparison to match
* @param patch The object whose properties should be replaced in the document
* @param conn The connection on which the update should be executed
* @throws DocumentException If called on a SQLite connection
*/
@Throws(DocumentException::class)
@JvmStatic
fun <TPatch> 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
*/
@Throws(DocumentException::class)
@JvmStatic
fun <TPatch> byJsonPath(tableName: String, path: String, patch: TPatch) =
Configuration.dbConn().use { byJsonPath(tableName, path, patch, it) }
}

View File

@@ -0,0 +1,168 @@
package solutions.bitbadger.documents.java
import solutions.bitbadger.documents.common.*
import solutions.bitbadger.documents.common.query.RemoveFields
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<Parameter<*>>): MutableCollection<Parameter<*>> {
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
*/
@JvmStatic
fun <TKey> byId(tableName: String, docId: TKey, toRemove: Collection<String>, conn: Connection) {
val nameParams = Parameters.fieldNames(toRemove)
conn.customNonQuery(
RemoveFields.byId(tableName, nameParams, docId),
Parameters.addFields(
listOf(Field.equal(Configuration.idField, docId, ":id")),
translatePath(nameParams)
)
)
}
/**
* 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
*/
@JvmStatic
fun <TKey> byId(tableName: String, docId: TKey, toRemove: Collection<String>) =
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
*/
@JvmStatic
fun byFields(
tableName: String,
fields: Collection<Field<*>>,
toRemove: Collection<String>,
howMatched: FieldMatch? = null,
conn: Connection
) {
val named = Parameters.nameFields(fields)
val nameParams = Parameters.fieldNames(toRemove)
conn.customNonQuery(
RemoveFields.byFields(tableName, nameParams, named, howMatched),
Parameters.addFields(named, translatePath(nameParams))
)
}
/**
* 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
*/
@JvmStatic
@JvmOverloads
fun byFields(
tableName: String,
fields: Collection<Field<*>>,
toRemove: Collection<String>,
howMatched: FieldMatch? = null
) =
Configuration.dbConn().use { byFields(tableName, fields, toRemove, howMatched, it) }
/**
* Remove fields from documents using a JSON containment query (PostgreSQL only)
*
* @param tableName The name of the table in which document fields should be removed
* @param criteria The object against which JSON containment should be checked
* @param toRemove The names of the fields to be removed
* @param conn The connection on which the update should be executed
* @throws DocumentException If called on a SQLite connection
*/
@Throws(DocumentException::class)
@JvmStatic
fun <TContains> byContains(
tableName: String,
criteria: TContains,
toRemove: Collection<String>,
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
*/
@Throws(DocumentException::class)
@JvmStatic
fun <TContains> byContains(tableName: String, criteria: TContains, toRemove: Collection<String>) =
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
*/
@Throws(DocumentException::class)
@JvmStatic
fun byJsonPath(tableName: String, path: String, toRemove: Collection<String>, 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
*/
@Throws(DocumentException::class)
@JvmStatic
fun byJsonPath(tableName: String, path: String, toRemove: Collection<String>) =
Configuration.dbConn().use { byJsonPath(tableName, path, toRemove, it) }
}

View File

@@ -0,0 +1,93 @@
package solutions.bitbadger.documents.java
import solutions.bitbadger.documents.common.Configuration
import solutions.bitbadger.documents.common.Dialect
import solutions.bitbadger.documents.common.DocumentException
import java.sql.PreparedStatement
import java.sql.ResultSet
import java.sql.SQLException
import kotlin.jvm.Throws
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
* @param clazz The class of the document to be returned
* @return The constructed domain item
*/
@JvmStatic
fun <TDoc> fromDocument(field: String, rs: ResultSet, clazz: Class<TDoc>) =
DocumentConfig.serializer.deserialize(rs.getString(field), clazz)
/**
* 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
*/
@JvmStatic
fun <TDoc> fromData(rs: ResultSet, clazz: Class<TDoc>) =
fromDocument("data", rs, clazz)
/**
* 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
* @param clazz The class of the document to be returned
* @return A list of items from the query's result
* @throws DocumentException If there is a problem executing the query
*/
@Throws(DocumentException::class)
@JvmStatic
fun <TDoc> toCustomList(
stmt: PreparedStatement, clazz: Class<TDoc>, mapFunc: (ResultSet, Class<TDoc>) -> TDoc
) =
try {
stmt.executeQuery().use {
val results = mutableListOf<TDoc>()
while (it.next()) {
results.add(mapFunc(it, clazz))
}
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
* @param clazz The type parameter (ignored; this always returns `Long`)
* @return The count from the row
* @throws DocumentException If the dialect has not been set
*/
@Throws(DocumentException::class)
@JvmStatic
fun toCount(rs: ResultSet, clazz: Class<*>) =
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
* @param clazz The type parameter (ignored; this always returns `Boolean`)
* @return The true/false value from the row
* @throws DocumentException If the dialect has not been set
*/
@Throws(DocumentException::class)
@JvmStatic
fun toExists(rs: ResultSet, clazz: Class<*>) =
when (Configuration.dialect()) {
Dialect.POSTGRESQL -> rs.getBoolean("it")
Dialect.SQLITE -> toCount(rs, Long::class.java) > 0L
}
}

View File

@@ -0,0 +1,121 @@
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.common.*;
import solutions.bitbadger.documents.java.Parameters;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
/**
* Unit tests for the `Parameters` object
*/
@DisplayName("Java | Java | Parameters")
final public class ParametersTest {
/**
* Reset the dialect
*/
@AfterEach
public void cleanUp() {
Configuration.setConnectionString(null);
}
@Test
@DisplayName("nameFields works with no changes")
public void nameFieldsNoChange() {
List<Field<?>> fields = List.of(Field.equal("a", "", ":test"), Field.exists("q"), Field.equal("b", "", ":me"));
Field<?>[] named = Parameters.nameFields(fields).toArray(new Field<?>[] { });
assertEquals(fields.size(), named.length, "There should have been 3 fields in the list");
assertSame(fields.get(0), named[0], "The first field should be the same");
assertSame(fields.get(1), named[1], "The second field should be the same");
assertSame(fields.get(2), named[2], "The third field should be the same");
}
@Test
@DisplayName("nameFields works when changing fields")
public void nameFieldsChange() {
List<Field<?>> fields = List.of(
Field.equal("a", ""), Field.equal("e", "", ":hi"), Field.equal("b", ""), Field.notExists("z"));
Field<?>[] named = Parameters.nameFields(fields).toArray(new Field<?>[] { });
assertEquals(fields.size(), named.length, "There should have been 4 fields in the list");
assertNotSame(fields.get(0), named[0], "The first field should not be the same");
assertEquals(":field0", named[0].getParameterName(), "First parameter name incorrect");
assertSame(fields.get(1), named[1], "The second field should be the same");
assertNotSame(fields.get(2), named[2], "The third field should not be the same");
assertEquals(":field1", named[2].getParameterName(), "Third parameter name incorrect");
assertSame(fields.get(3), named[3], "The fourth field should be the same");
}
@Test
@DisplayName("replaceNamesInQuery replaces successfully")
public void replaceNamesInQuery() {
List<Parameter<?>> parameters = List.of(new Parameter<>(":data", ParameterType.JSON, "{}"),
new Parameter<>(":data_ext", ParameterType.STRING, ""));
String query =
"SELECT data, data_ext FROM tbl WHERE data = :data AND data_ext = :data_ext AND more_data = :data";
assertEquals("SELECT data, data_ext FROM tbl WHERE data = ? AND data_ext = ? AND more_data = ?",
Parameters.replaceNamesInQuery(query, parameters), "Parameters not replaced correctly");
}
@Test
@DisplayName("fieldNames generates a single parameter (PostgreSQL)")
public void fieldNamesSinglePostgres() throws DocumentException {
Configuration.setConnectionString(":postgresql:");
Parameter<?>[] nameParams = Parameters.fieldNames(List.of("test")).toArray(new Parameter<?>[]{});
assertEquals(1, nameParams.length, "There should be one name parameter");
assertEquals(":name", nameParams[0].getName(), "The parameter name is incorrect");
assertEquals(ParameterType.STRING, nameParams[0].getType(), "The parameter type is incorrect");
assertEquals("{test}", nameParams[0].getValue(), "The parameter value is incorrect");
}
@Test
@DisplayName("fieldNames generates multiple parameters (PostgreSQL)")
public void fieldNamesMultiplePostgres() throws DocumentException {
Configuration.setConnectionString(":postgresql:");
Parameter<?>[] nameParams = Parameters.fieldNames(List.of("test", "this", "today"))
.toArray(new Parameter<?>[]{});
assertEquals(1, nameParams.length, "There should be one name parameter");
assertEquals(":name", nameParams[0].getName(), "The parameter name is incorrect");
assertEquals(ParameterType.STRING, nameParams[0].getType(), "The parameter type is incorrect");
assertEquals("{test,this,today}", nameParams[0].getValue(), "The parameter value is incorrect");
}
@Test
@DisplayName("fieldNames generates a single parameter (SQLite)")
public void fieldNamesSingleSQLite() throws DocumentException {
Configuration.setConnectionString(":sqlite:");
Parameter<?>[] nameParams = Parameters.fieldNames(List.of("test")).toArray(new Parameter<?>[]{});
assertEquals(1, nameParams.length, "There should be one name parameter");
assertEquals(":name0", nameParams[0].getName(), "The parameter name is incorrect");
assertEquals(ParameterType.STRING, nameParams[0].getType(), "The parameter type is incorrect");
assertEquals("test", nameParams[0].getValue(), "The parameter value is incorrect");
}
@Test
@DisplayName("fieldNames generates multiple parameters (SQLite)")
public void fieldNamesMultipleSQLite() throws DocumentException {
Configuration.setConnectionString(":sqlite:");
Parameter<?>[] nameParams = Parameters.fieldNames(List.of("test", "this", "today"))
.toArray(new Parameter<?>[]{});
assertEquals(3, nameParams.length, "There should be one name parameter");
assertEquals(":name0", nameParams[0].getName(), "The first parameter name is incorrect");
assertEquals(ParameterType.STRING, nameParams[0].getType(), "The first parameter type is incorrect");
assertEquals("test", nameParams[0].getValue(), "The first parameter value is incorrect");
assertEquals(":name1", nameParams[1].getName(), "The second parameter name is incorrect");
assertEquals(ParameterType.STRING, nameParams[1].getType(), "The second parameter type is incorrect");
assertEquals("this", nameParams[1].getValue(), "The second parameter value is incorrect");
assertEquals(":name2", nameParams[2].getName(), "The third parameter name is incorrect");
assertEquals(ParameterType.STRING, nameParams[2].getType(), "The third parameter type is incorrect");
assertEquals("today", nameParams[2].getValue(), "The third parameter value is incorrect");
}
@Test
@DisplayName("fieldNames fails if dialect not set")
public void fieldNamesFails() {
assertThrows(DocumentException.class, () -> Parameters.fieldNames(List.of()));
}
}

View File

@@ -0,0 +1,62 @@
package solutions.bitbadger.documents.java.java.integration.common;
import solutions.bitbadger.documents.common.Field;
import solutions.bitbadger.documents.java.Count;
import solutions.bitbadger.documents.java.integration.ThrowawayDatabase;
import solutions.bitbadger.documents.java.java.testDocs.JsonDocument;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static solutions.bitbadger.documents.java.integration.TypesKt.TEST_TABLE;
/**
* Integration tests for the `Count` object
*/
final public class CountFunctions {
public static void all(ThrowawayDatabase db) {
JsonDocument.load(db);
assertEquals(5L, Count.all(TEST_TABLE, db.getConn()), "There should have been 5 documents in the table");
}
public static void byFieldsNumeric(ThrowawayDatabase db) {
JsonDocument.load(db);
assertEquals(3L, Count.byFields(TEST_TABLE, List.of(Field.between("numValue", 10, 20)), db.getConn()),
"There should have been 3 matching documents");
}
public static void byFieldsAlpha(ThrowawayDatabase db) {
JsonDocument.load(db);
assertEquals(1L, Count.byFields(TEST_TABLE, List.of(Field.between("value", "aardvark", "apple")), db.getConn()),
"There should have been 1 matching document");
}
public static void byContainsMatch(ThrowawayDatabase db) {
JsonDocument.load(db);
assertEquals(2L, Count.byContains(TEST_TABLE, Map.of("value", "purple"), db.getConn()),
"There should have been 2 matching documents");
}
public static void byContainsNoMatch(ThrowawayDatabase db) {
JsonDocument.load(db);
assertEquals(0L, Count.byContains(TEST_TABLE, Map.of("value", "magenta"), db.getConn()),
"There should have been no matching documents");
}
public static void byJsonPathMatch(ThrowawayDatabase db) {
JsonDocument.load(db);
assertEquals(2L, Count.byJsonPath(TEST_TABLE, "$.numValue ? (@ < 5)", db.getConn()),
"There should have been 2 matching documents");
}
public static void byJsonPathNoMatch(ThrowawayDatabase db) {
JsonDocument.load(db);
assertEquals(0L, Count.byJsonPath(TEST_TABLE, "$.numValue ? (@ > 100)", db.getConn()),
"There should have been no matching documents");
}
private CountFunctions() {
}
}

View File

@@ -0,0 +1,69 @@
package solutions.bitbadger.documents.java.java.integration.postgresql;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import solutions.bitbadger.documents.java.integration.postgresql.PgDB;
import solutions.bitbadger.documents.java.java.integration.common.CountFunctions;
/**
* PostgreSQL integration tests for the `Count` object / `count*` connection extension functions
*/
@DisplayName("Java | Java | PostgreSQL: Count")
public class CountIT {
@Test
@DisplayName("all counts all documents")
public void all() {
try (PgDB db = new PgDB()) {
CountFunctions.all(db);
}
}
@Test
@DisplayName("byFields counts documents by a numeric value")
public void byFieldsNumeric() {
try (PgDB db = new PgDB()) {
CountFunctions.byFieldsNumeric(db);
}
}
@Test
@DisplayName("byFields counts documents by a alphanumeric value")
public void byFieldsAlpha() {
try (PgDB db = new PgDB()) {
CountFunctions.byFieldsAlpha(db);
}
}
@Test
@DisplayName("byContains counts documents when matches are found")
public void byContainsMatch() {
try (PgDB db = new PgDB()) {
CountFunctions.byContainsMatch(db);
}
}
@Test
@DisplayName("byContains counts documents when no matches are found")
public void byContainsNoMatch() {
try (PgDB db = new PgDB()) {
CountFunctions.byContainsNoMatch(db);
}
}
@Test
@DisplayName("byJsonPath counts documents when matches are found")
public void byJsonPathMatch() {
try (PgDB db = new PgDB()) {
CountFunctions.byJsonPathMatch(db);
}
}
@Test
@DisplayName("byJsonPath counts documents when no matches are found")
public void byJsonPathNoMatch() {
try (PgDB db = new PgDB()) {
CountFunctions.byJsonPathNoMatch(db);
}
}
}

View File

@@ -0,0 +1,56 @@
package solutions.bitbadger.documents.java.java.integration.sqlite;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import solutions.bitbadger.documents.common.DocumentException;
import solutions.bitbadger.documents.java.integration.sqlite.SQLiteDB;
import solutions.bitbadger.documents.java.java.integration.common.CountFunctions;
import static org.junit.jupiter.api.Assertions.assertThrows;
/**
* SQLite integration tests for the `Count` object / `count*` connection extension functions
*/
@DisplayName("Java | Java | SQLite: Count")
public class CountIT {
@Test
@DisplayName("all counts all documents")
public void all() {
try (SQLiteDB db = new SQLiteDB()) {
CountFunctions.all(db);
}
}
@Test
@DisplayName("byFields counts documents by a numeric value")
public void byFieldsNumeric() {
try (SQLiteDB db = new SQLiteDB()) {
CountFunctions.byFieldsNumeric(db);
}
}
@Test
@DisplayName("byFields counts documents by a alphanumeric value")
public void byFieldsAlpha() {
try (SQLiteDB db = new SQLiteDB()) {
CountFunctions.byFieldsAlpha(db);
}
}
@Test
@DisplayName("byContains fails")
public void byContainsMatch() {
try (SQLiteDB db = new SQLiteDB()) {
assertThrows(DocumentException.class, () -> CountFunctions.byContainsMatch(db));
}
}
@Test
@DisplayName("byJsonPath fails")
public void byJsonPathMatch() {
try (SQLiteDB db = new SQLiteDB()) {
assertThrows(DocumentException.class, () -> CountFunctions.byJsonPathMatch(db));
}
}
}

View File

@@ -0,0 +1,18 @@
package solutions.bitbadger.documents.java.java.testDocs;
public class ByteIdClass {
private byte id;
public byte getId() {
return id;
}
public void setId(byte id) {
this.id = id;
}
public ByteIdClass(byte id) {
this.id = id;
}
}

View File

@@ -0,0 +1,18 @@
package solutions.bitbadger.documents.java.java.testDocs;
public class IntIdClass {
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public IntIdClass(int id) {
this.id = id;
}
}

View File

@@ -0,0 +1,82 @@
package solutions.bitbadger.documents.java.java.testDocs;
import kotlinx.serialization.Serializable;
import solutions.bitbadger.documents.java.Document;
import solutions.bitbadger.documents.java.integration.ThrowawayDatabase;
import java.util.List;
import static solutions.bitbadger.documents.java.integration.TypesKt.TEST_TABLE;
@Serializable
public class JsonDocument {
private String id;
private String value;
private int numValue;
private SubDocument sub;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public int getNumValue() {
return numValue;
}
public void setNumValue(int numValue) {
this.numValue = numValue;
}
public SubDocument getSub() {
return sub;
}
public void setSub(SubDocument sub) {
this.sub = sub;
}
public JsonDocument(String id, String value, int numValue, SubDocument sub) {
this.id = id;
this.value = value;
this.numValue = numValue;
this.sub = sub;
}
public JsonDocument(String id, String value, int numValue) {
this(id, value, numValue, null);
}
public JsonDocument(String id) {
this(id, "", 0, null);
}
private static final List<JsonDocument> testDocuments = List.of(
new JsonDocument("one", "FIRST!", 0),
new JsonDocument("two", "another", 10, new SubDocument("green", "blue")),
new JsonDocument("three", "", 4),
new JsonDocument("four", "purple", 17, new SubDocument("green", "red")),
new JsonDocument("five", "purple", 18));
public static void load(ThrowawayDatabase db, String tableName) {
for (JsonDocument doc : testDocuments) {
Document.insert(tableName, doc, db.getConn());
}
}
public static void load(ThrowawayDatabase db) {
load(db, TEST_TABLE);
}
}

View File

@@ -0,0 +1,18 @@
package solutions.bitbadger.documents.java.java.testDocs;
public class LongIdClass {
private long id;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public LongIdClass(long id) {
this.id = id;
}
}

View File

@@ -0,0 +1,18 @@
package solutions.bitbadger.documents.java.java.testDocs;
public class ShortIdClass {
private short id;
public short getId() {
return id;
}
public void setId(short id) {
this.id = id;
}
public ShortIdClass(short id) {
this.id = id;
}
}

View File

@@ -0,0 +1,18 @@
package solutions.bitbadger.documents.java.java.testDocs;
public class StringIdClass {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public StringIdClass(String id) {
this.id = id;
}
}

View File

@@ -0,0 +1,32 @@
package solutions.bitbadger.documents.java.java.testDocs;
public class SubDocument {
private String foo;
private String bar;
public String getFoo() {
return foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
public String getBar() {
return bar;
}
public void setBar(String bar) {
this.bar = bar;
}
public SubDocument(String foo, String bar) {
this.foo = foo;
this.bar = bar;
}
public SubDocument() {
this("", "");
}
}

View File

@@ -0,0 +1,18 @@
package solutions.bitbadger.documents.java
import solutions.bitbadger.documents.common.DocumentSerializer
import com.fasterxml.jackson.databind.ObjectMapper
/**
* A JSON serializer using Jackson's default options
*/
class JacksonDocumentSerializer : DocumentSerializer {
val mapper = ObjectMapper()
override fun <TDoc> serialize(document: TDoc) =
mapper.writeValueAsString(document)
override fun <TDoc> deserialize(json: String, clazz: Class<TDoc>) =
mapper.readValue(json, clazz)
}

View File

@@ -0,0 +1,119 @@
package solutions.bitbadger.documents.java
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.assertThrows
import solutions.bitbadger.documents.common.*
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotSame
import kotlin.test.assertSame
/**
* Unit tests for the `Parameters` object
*/
@DisplayName("Kotlin | Java | Parameters")
class ParametersTest {
/**
* Reset the dialect
*/
@AfterEach
fun cleanUp() {
Configuration.connectionString = null
}
@Test
@DisplayName("nameFields works with no changes")
fun nameFieldsNoChange() {
val fields = listOf(Field.equal("a", "", ":test"), Field.exists("q"), Field.equal("b", "", ":me"))
val named = Parameters.nameFields(fields)
assertEquals(fields.size, named.size, "There should have been 3 fields in the list")
assertSame(fields.elementAt(0), named.elementAt(0), "The first field should be the same")
assertSame(fields.elementAt(1), named.elementAt(1), "The second field should be the same")
assertSame(fields.elementAt(2), named.elementAt(2), "The third field should be the same")
}
@Test
@DisplayName("nameFields works when changing fields")
fun nameFieldsChange() {
val fields = listOf(
Field.equal("a", ""), Field.equal("e", "", ":hi"), Field.equal("b", ""), Field.notExists("z"))
val named = Parameters.nameFields(fields)
assertEquals(fields.size, named.size, "There should have been 4 fields in the list")
assertNotSame(fields.elementAt(0), named.elementAt(0), "The first field should not be the same")
assertEquals(":field0", named.elementAt(0).parameterName, "First parameter name incorrect")
assertSame(fields.elementAt(1), named.elementAt(1), "The second field should be the same")
assertNotSame(fields.elementAt(2), named.elementAt(2), "The third field should not be the same")
assertEquals(":field1", named.elementAt(2).parameterName, "Third parameter name incorrect")
assertSame(fields.elementAt(3), named.elementAt(3), "The fourth field should be the same")
}
@Test
@DisplayName("replaceNamesInQuery replaces successfully")
fun replaceNamesInQuery() {
val parameters = listOf(
Parameter(":data", ParameterType.JSON, "{}"),
Parameter(":data_ext", ParameterType.STRING, "")
)
val query = "SELECT data, data_ext FROM tbl WHERE data = :data AND data_ext = :data_ext AND more_data = :data"
assertEquals("SELECT data, data_ext FROM tbl WHERE data = ? AND data_ext = ? AND more_data = ?",
Parameters.replaceNamesInQuery(query, parameters), "Parameters not replaced correctly")
}
@Test
@DisplayName("fieldNames generates a single parameter (PostgreSQL)")
fun fieldNamesSinglePostgres() {
Configuration.connectionString = ":postgresql:"
val nameParams = Parameters.fieldNames(listOf("test")).toList()
assertEquals(1, nameParams.size, "There should be one name parameter")
assertEquals(":name", nameParams[0].name, "The parameter name is incorrect")
assertEquals(ParameterType.STRING, nameParams[0].type, "The parameter type is incorrect")
assertEquals("{test}", nameParams[0].value, "The parameter value is incorrect")
}
@Test
@DisplayName("fieldNames generates multiple parameters (PostgreSQL)")
fun fieldNamesMultiplePostgres() {
Configuration.connectionString = ":postgresql:"
val nameParams = Parameters.fieldNames(listOf("test", "this", "today")).toList()
assertEquals(1, nameParams.size, "There should be one name parameter")
assertEquals(":name", nameParams[0].name, "The parameter name is incorrect")
assertEquals(ParameterType.STRING, nameParams[0].type, "The parameter type is incorrect")
assertEquals("{test,this,today}", nameParams[0].value, "The parameter value is incorrect")
}
@Test
@DisplayName("fieldNames generates a single parameter (SQLite)")
fun fieldNamesSingleSQLite() {
Configuration.connectionString = ":sqlite:"
val nameParams = Parameters.fieldNames(listOf("test")).toList()
assertEquals(1, nameParams.size, "There should be one name parameter")
assertEquals(":name0", nameParams[0].name, "The parameter name is incorrect")
assertEquals(ParameterType.STRING, nameParams[0].type, "The parameter type is incorrect")
assertEquals("test", nameParams[0].value, "The parameter value is incorrect")
}
@Test
@DisplayName("fieldNames generates multiple parameters (SQLite)")
fun fieldNamesMultipleSQLite() {
Configuration.connectionString = ":sqlite:"
val nameParams = Parameters.fieldNames(listOf("test", "this", "today")).toList()
assertEquals(3, nameParams.size, "There should be one name parameter")
assertEquals(":name0", nameParams[0].name, "The first parameter name is incorrect")
assertEquals(ParameterType.STRING, nameParams[0].type, "The first parameter type is incorrect")
assertEquals("test", nameParams[0].value, "The first parameter value is incorrect")
assertEquals(":name1", nameParams[1].name, "The second parameter name is incorrect")
assertEquals(ParameterType.STRING, nameParams[1].type, "The second parameter type is incorrect")
assertEquals("this", nameParams[1].value, "The second parameter value is incorrect")
assertEquals(":name2", nameParams[2].name, "The third parameter name is incorrect")
assertEquals(ParameterType.STRING, nameParams[2].type, "The third parameter type is incorrect")
assertEquals("today", nameParams[2].value, "The third parameter value is incorrect")
}
@Test
@DisplayName("fieldNames fails if dialect not set")
fun fieldNamesFails() {
assertThrows<DocumentException> { Parameters.fieldNames(listOf()) }
}
}

View File

@@ -0,0 +1,20 @@
package solutions.bitbadger.documents.java.integration
import java.sql.Connection
/**
* Common interface for PostgreSQL and SQLite throwaway databases
*/
interface ThrowawayDatabase : AutoCloseable {
/** The database connection for the throwaway database */
val conn: Connection
/**
* Determine if a database object exists
*
* @param name The name of the object whose existence should be checked
* @return True if the object exists, false if not
*/
fun dbObjectExists(name: String): Boolean
}

View File

@@ -0,0 +1,52 @@
package solutions.bitbadger.documents.java.integration
import kotlinx.serialization.Serializable
import solutions.bitbadger.documents.java.insert
/** The test table name to use for integration tests */
const val TEST_TABLE = "test_table"
@Serializable
data class NumIdDocument(val key: Int, val text: String) {
constructor() : this(0, "")
}
@Serializable
data class SubDocument(val foo: String, val bar: String) {
constructor() : this("", "")
}
@Serializable
data class ArrayDocument(val id: String, val values: List<String>) {
constructor() : this("", listOf())
companion object {
/** A set of documents used for integration tests */
val testDocuments = listOf(
ArrayDocument("first", listOf("a", "b", "c")),
ArrayDocument("second", listOf("c", "d", "e")),
ArrayDocument("third", listOf("x", "y", "z"))
)
}
}
@Serializable
data class JsonDocument(val id: String, val value: String = "", val numValue: Int = 0, val sub: SubDocument? = null) {
constructor() : this("")
companion object {
/** Documents to use for testing */
private val testDocuments = listOf(
JsonDocument("one", "FIRST!", 0, null),
JsonDocument("two", "another", 10, SubDocument("green", "blue")),
JsonDocument("three", "", 4, null),
JsonDocument("four", "purple", 17, SubDocument("green", "red")),
JsonDocument("five", "purple", 18, null)
)
fun load(db: ThrowawayDatabase, tableName: String = TEST_TABLE) =
testDocuments.forEach { db.conn.insert(tableName, it) }
}
}

View File

@@ -0,0 +1,76 @@
package solutions.bitbadger.documents.java.integration.common
import solutions.bitbadger.documents.common.Field
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
/**
* Integration tests for the `Count` object
*/
object Count {
fun all(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertEquals(5L, db.conn.countAll(TEST_TABLE), "There should have been 5 documents in the table")
}
fun byFieldsNumeric(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertEquals(
3L,
db.conn.countByFields(TEST_TABLE, listOf(Field.between("numValue", 10, 20))),
"There should have been 3 matching documents"
)
}
fun byFieldsAlpha(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertEquals(
1L,
db.conn.countByFields(TEST_TABLE, listOf(Field.between("value", "aardvark", "apple"))),
"There should have been 1 matching document"
)
}
fun byContainsMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertEquals(
2L,
db.conn.countByContains(TEST_TABLE, mapOf("value" to "purple")),
"There should have been 2 matching documents"
)
}
fun byContainsNoMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertEquals(
0L,
db.conn.countByContains(TEST_TABLE, mapOf("value" to "magenta")),
"There should have been no matching documents"
)
}
fun byJsonPathMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertEquals(
2L,
db.conn.countByJsonPath(TEST_TABLE, "$.numValue ? (@ < 5)"),
"There should have been 2 matching documents"
)
}
fun byJsonPathNoMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertEquals(
0L,
db.conn.countByJsonPath(TEST_TABLE, "$.numValue ? (@ > 100)"),
"There should have been no matching documents"
)
}
}

View File

@@ -0,0 +1,89 @@
package solutions.bitbadger.documents.java.integration.common
import solutions.bitbadger.documents.common.*
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.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.assertNotNull
import kotlin.test.assertNull
/**
* Integration tests for the `Custom` object
*/
object Custom {
fun listEmpty(db: ThrowawayDatabase) {
JsonDocument.load(db)
db.conn.deleteByFields(TEST_TABLE, listOf(Field.exists(Configuration.idField)))
val result = db.conn.customList(Find.all(TEST_TABLE), listOf(), JsonDocument::class.java, Results::fromData)
assertEquals(0, result.size, "There should have been no results")
}
fun listAll(db: ThrowawayDatabase) {
JsonDocument.load(db)
val result = db.conn.customList(Find.all(TEST_TABLE), listOf(), JsonDocument::class.java, Results::fromData)
assertEquals(5, result.size, "There should have been 5 results")
}
fun singleNone(db: ThrowawayDatabase) =
assertNull(
db.conn.customSingle(Find.all(TEST_TABLE), listOf(), JsonDocument::class.java, Results::fromData),
"There should not have been a document returned"
)
fun singleOne(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertNotNull(
db.conn.customSingle(Find.all(TEST_TABLE), listOf(), JsonDocument::class.java, Results::fromData),
"There should not have been a document returned"
)
}
fun nonQueryChanges(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertEquals(
5L, db.conn.customScalar(Count.all(TEST_TABLE), listOf(), Long::class.java, Results::toCount),
"There should have been 5 documents in the table"
)
db.conn.customNonQuery("DELETE FROM $TEST_TABLE")
assertEquals(
0L, db.conn.customScalar(Count.all(TEST_TABLE), listOf(), Long::class.java, Results::toCount),
"There should have been no documents in the table"
)
}
fun nonQueryNoChanges(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertEquals(
5L, db.conn.customScalar(Count.all(TEST_TABLE), listOf(), Long::class.java, Results::toCount),
"There should have been 5 documents in the table"
)
db.conn.customNonQuery(
Delete.byId(TEST_TABLE, "eighty-two"),
listOf(Parameter(":id", ParameterType.STRING, "eighty-two"))
)
assertEquals(
5L, db.conn.customScalar(Count.all(TEST_TABLE), listOf(), Long::class.java, Results::toCount),
"There should still have been 5 documents in the table"
)
}
fun scalar(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertEquals(
3L,
db.conn.customScalar(
"SELECT 3 AS it FROM $TEST_TABLE LIMIT 1",
listOf(),
Long::class.java,
Results::toCount
),
"The number 3 should have been returned"
)
}
}

View File

@@ -0,0 +1,48 @@
package solutions.bitbadger.documents.java.integration.common
import solutions.bitbadger.documents.common.DocumentIndex
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
/**
* Integration tests for the `Definition` object / `ensure*` connection extension functions
*/
object Definition {
fun ensureTable(db: ThrowawayDatabase) {
assertFalse(db.dbObjectExists("ensured"), "The 'ensured' table should not exist")
assertFalse(db.dbObjectExists("idx_ensured_key"), "The PK index for the 'ensured' table should not exist")
db.conn.ensureTable("ensured")
assertTrue(db.dbObjectExists("ensured"), "The 'ensured' table should exist")
assertTrue(db.dbObjectExists("idx_ensured_key"), "The PK index for the 'ensured' table should now exist")
}
fun ensureFieldIndex(db: ThrowawayDatabase) {
assertFalse(db.dbObjectExists("idx_${TEST_TABLE}_test"), "The test index should not exist")
db.conn.ensureFieldIndex(TEST_TABLE, "test", listOf("id", "category"))
assertTrue(db.dbObjectExists("idx_${TEST_TABLE}_test"), "The test index should now exist")
}
fun ensureDocumentIndexFull(db: ThrowawayDatabase) {
assertFalse(db.dbObjectExists("doc_table"), "The 'doc_table' table should not exist")
db.conn.ensureTable("doc_table")
assertTrue(db.dbObjectExists("doc_table"), "The 'doc_table' table should exist")
assertFalse(db.dbObjectExists("idx_doc_table_document"), "The document index should not exist")
db.conn.ensureDocumentIndex("doc_table", DocumentIndex.FULL)
assertTrue(db.dbObjectExists("idx_doc_table_document"), "The document index should exist")
}
fun ensureDocumentIndexOptimized(db: ThrowawayDatabase) {
assertFalse(db.dbObjectExists("doc_table"), "The 'doc_table' table should not exist")
db.conn.ensureTable("doc_table")
assertTrue(db.dbObjectExists("doc_table"), "The 'doc_table' table should exist")
assertFalse(db.dbObjectExists("idx_doc_table_document"), "The document index should not exist")
db.conn.ensureDocumentIndex("doc_table", DocumentIndex.OPTIMIZED)
assertTrue(db.dbObjectExists("idx_doc_table_document"), "The document index should exist")
}
}

View File

@@ -0,0 +1,70 @@
package solutions.bitbadger.documents.java.integration.common
import solutions.bitbadger.documents.common.Field
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
/**
* Integration tests for the `Delete` object
*/
object Delete {
fun byIdMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertEquals(5, db.conn.countAll(TEST_TABLE), "There should be 5 documents in the table")
db.conn.deleteById(TEST_TABLE, "four")
assertEquals(4, db.conn.countAll(TEST_TABLE), "There should now be 4 documents in the table")
}
fun byIdNoMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertEquals(5, db.conn.countAll(TEST_TABLE), "There should be 5 documents in the table")
db.conn.deleteById(TEST_TABLE, "negative four")
assertEquals(5, db.conn.countAll(TEST_TABLE), "There should still be 5 documents in the table")
}
fun byFieldsMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertEquals(5, db.conn.countAll(TEST_TABLE), "There should be 5 documents in the table")
db.conn.deleteByFields(TEST_TABLE, listOf(Field.notEqual("value", "purple")))
assertEquals(2, db.conn.countAll(TEST_TABLE), "There should now be 2 documents in the table")
}
fun byFieldsNoMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertEquals(5, db.conn.countAll(TEST_TABLE), "There should be 5 documents in the table")
db.conn.deleteByFields(TEST_TABLE, listOf(Field.equal("value", "crimson")))
assertEquals(5, db.conn.countAll(TEST_TABLE), "There should still be 5 documents in the table")
}
fun byContainsMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertEquals(5, db.conn.countAll(TEST_TABLE), "There should be 5 documents in the table")
db.conn.deleteByContains(TEST_TABLE, mapOf("value" to "purple"))
assertEquals(3, db.conn.countAll(TEST_TABLE), "There should now be 3 documents in the table")
}
fun byContainsNoMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertEquals(5, db.conn.countAll(TEST_TABLE), "There should be 5 documents in the table")
db.conn.deleteByContains(TEST_TABLE, mapOf("target" to "acquired"))
assertEquals(5, db.conn.countAll(TEST_TABLE), "There should still be 5 documents in the table")
}
fun byJsonPathMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertEquals(5, db.conn.countAll(TEST_TABLE), "There should be 5 documents in the table")
db.conn.deleteByJsonPath(TEST_TABLE, "$.value ? (@ == \"purple\")")
assertEquals(3, db.conn.countAll(TEST_TABLE), "There should now be 3 documents in the table")
}
fun byJsonPathNoMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertEquals(5, db.conn.countAll(TEST_TABLE), "There should be 5 documents in the table")
db.conn.deleteByJsonPath(TEST_TABLE, "$.numValue ? (@ > 100)")
assertEquals(5, db.conn.countAll(TEST_TABLE), "There should still be 5 documents in the table")
}
}

View File

@@ -0,0 +1,129 @@
package solutions.bitbadger.documents.java.integration.common
import solutions.bitbadger.documents.common.*
import solutions.bitbadger.documents.java.*
import solutions.bitbadger.documents.java.integration.*
import kotlin.test.*
/**
* Integration tests for the `Document` object / `insert`, `save`, `update` connection extension functions
*/
object Document {
fun insertDefault(db: ThrowawayDatabase) {
assertEquals(0L, db.conn.countAll(TEST_TABLE), "There should be no documents in the table")
val doc = JsonDocument("turkey", "", 0, SubDocument("gobble", "gobble"))
db.conn.insert(TEST_TABLE, doc)
val after = db.conn.findAll(TEST_TABLE, JsonDocument::class.java)
assertEquals(1, after.size, "There should be one document in the table")
assertEquals(doc, after[0], "The document should be what was inserted")
}
fun insertDupe(db: ThrowawayDatabase) {
db.conn.insert(TEST_TABLE, JsonDocument("a", "", 0, null))
try {
db.conn.insert(TEST_TABLE, JsonDocument("a", "b", 22, null))
fail("Inserting a document with a duplicate key should have thrown an exception")
} catch (_: Exception) {
// yay
}
}
fun insertNumAutoId(db: ThrowawayDatabase) {
try {
Configuration.autoIdStrategy = AutoId.NUMBER
Configuration.idField = "key"
assertEquals(0L, db.conn.countAll(TEST_TABLE), "There should be no documents in the table")
db.conn.insert(TEST_TABLE, NumIdDocument(0, "one"))
db.conn.insert(TEST_TABLE, NumIdDocument(0, "two"))
db.conn.insert(TEST_TABLE, NumIdDocument(77, "three"))
db.conn.insert(TEST_TABLE, NumIdDocument(0, "four"))
val after = db.conn.findAll(TEST_TABLE, NumIdDocument::class.java, listOf(Field.named("key")))
assertEquals(4, after.size, "There should have been 4 documents returned")
assertEquals(
"1|2|77|78", after.joinToString("|") { it.key.toString() },
"The IDs were not generated correctly"
)
} finally {
Configuration.autoIdStrategy = AutoId.DISABLED
Configuration.idField = "id"
}
}
fun insertUUIDAutoId(db: ThrowawayDatabase) {
try {
Configuration.autoIdStrategy = AutoId.UUID
assertEquals(0L, db.conn.countAll(TEST_TABLE), "There should be no documents in the table")
db.conn.insert(TEST_TABLE, JsonDocument(""))
val after = db.conn.findAll(TEST_TABLE, JsonDocument::class.java)
assertEquals(1, after.size, "There should have been 1 document returned")
assertEquals(32, after[0].id.length, "The ID was not generated correctly")
} finally {
Configuration.autoIdStrategy = AutoId.DISABLED
}
}
fun insertStringAutoId(db: ThrowawayDatabase) {
try {
Configuration.autoIdStrategy = AutoId.RANDOM_STRING
assertEquals(0L, db.conn.countAll(TEST_TABLE), "There should be no documents in the table")
db.conn.insert(TEST_TABLE, JsonDocument(""))
Configuration.idStringLength = 21
db.conn.insert(TEST_TABLE, JsonDocument(""))
val after = db.conn.findAll(TEST_TABLE, JsonDocument::class.java)
assertEquals(2, after.size, "There should have been 2 documents returned")
assertEquals(16, after[0].id.length, "The first document's ID was not generated correctly")
assertEquals(21, after[1].id.length, "The second document's ID was not generated correctly")
} finally {
Configuration.autoIdStrategy = AutoId.DISABLED
Configuration.idStringLength = 16
}
}
fun saveMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
db.conn.save(TEST_TABLE, JsonDocument("two", numValue = 44))
val doc = db.conn.findById(TEST_TABLE, "two", JsonDocument::class.java)
assertNotNull(doc, "There should have been a document returned")
assertEquals("two", doc.id, "An incorrect document was returned")
assertEquals("", doc.value, "The \"value\" field was not updated")
assertEquals(44, doc.numValue, "The \"numValue\" field was not updated")
assertNull(doc.sub, "The \"sub\" field was not updated")
}
fun saveNoMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
db.conn.save(TEST_TABLE, JsonDocument("test", sub = SubDocument("a", "b")))
assertNotNull(
db.conn.findById(TEST_TABLE, "test", JsonDocument::class.java),
"The test document should have been saved"
)
}
fun updateMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
db.conn.update(TEST_TABLE, "one", JsonDocument("one", "howdy", 8, SubDocument("y", "z")))
val doc = db.conn.findById(TEST_TABLE, "one", JsonDocument::class.java)
assertNotNull(doc, "There should have been a document returned")
assertEquals("one", doc.id, "An incorrect document was returned")
assertEquals("howdy", doc.value, "The \"value\" field was not updated")
assertEquals(8, doc.numValue, "The \"numValue\" field was not updated")
assertNotNull(doc.sub, "The sub-document should not be null")
assertEquals("y", doc.sub.foo, "The sub-document \"foo\" field was not updated")
assertEquals("z", doc.sub.bar, "The sub-document \"bar\" field was not updated")
}
fun updateNoMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertFalse { db.conn.existsById(TEST_TABLE, "two-hundred") }
db.conn.update(TEST_TABLE, "two-hundred", JsonDocument("two-hundred", numValue = 200))
assertFalse { db.conn.existsById(TEST_TABLE, "two-hundred") }
}
}

View File

@@ -0,0 +1,70 @@
package solutions.bitbadger.documents.java.integration.common
import solutions.bitbadger.documents.common.Field
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
/**
* Integration tests for the `Exists` object
*/
object Exists {
fun byIdMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertTrue("The document with ID \"three\" should exist") { db.conn.existsById(TEST_TABLE, "three") }
}
fun byIdNoMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertFalse("The document with ID \"seven\" should not exist") { db.conn.existsById(TEST_TABLE, "seven") }
}
fun byFieldsMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertTrue("Matching documents should have been found") {
db.conn.existsByFields(TEST_TABLE, listOf(Field.equal("numValue", 10)))
}
}
fun byFieldsNoMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertFalse("No matching documents should have been found") {
db.conn.existsByFields(TEST_TABLE, listOf(Field.equal("nothing", "none")))
}
}
fun byContainsMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertTrue("Matching documents should have been found") {
db.conn.existsByContains(TEST_TABLE, mapOf("value" to "purple"))
}
}
fun byContainsNoMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertFalse("Matching documents should not have been found") {
db.conn.existsByContains(TEST_TABLE, mapOf("value" to "violet"))
}
}
fun byJsonPathMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertTrue("Matching documents should have been found") {
db.conn.existsByJsonPath(TEST_TABLE, "$.numValue ? (@ == 10)")
}
}
fun byJsonPathNoMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertFalse("Matching documents should not have been found") {
db.conn.existsByJsonPath(TEST_TABLE, "$.numValue ? (@ == 10.1)")
}
}
}

View File

@@ -0,0 +1,325 @@
package solutions.bitbadger.documents.java.integration.common
import solutions.bitbadger.documents.common.*
import solutions.bitbadger.documents.java.*
import solutions.bitbadger.documents.java.integration.*
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue
/**
* Integration tests for the `Find` object
*/
object Find {
fun allDefault(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertEquals(
5,
db.conn.findAll(TEST_TABLE, JsonDocument::class.java).size,
"There should have been 5 documents returned"
)
}
fun allAscending(db: ThrowawayDatabase) {
JsonDocument.load(db)
val docs = db.conn.findAll(TEST_TABLE, JsonDocument::class.java, listOf(Field.named("id")))
assertEquals(5, docs.size, "There should have been 5 documents returned")
assertEquals(
"five|four|one|three|two",
docs.joinToString("|") { it.id },
"The documents were not ordered correctly"
)
}
fun allDescending(db: ThrowawayDatabase) {
JsonDocument.load(db)
val docs = db.conn.findAll(TEST_TABLE, JsonDocument::class.java, listOf(Field.named("id DESC")))
assertEquals(5, docs.size, "There should have been 5 documents returned")
assertEquals(
"two|three|one|four|five",
docs.joinToString("|") { it.id },
"The documents were not ordered correctly"
)
}
fun allNumOrder(db: ThrowawayDatabase) {
JsonDocument.load(db)
val docs = db.conn.findAll(
TEST_TABLE,
JsonDocument::class.java,
listOf(Field.named("sub.foo NULLS LAST"), Field.named("n:numValue"))
)
assertEquals(5, docs.size, "There should have been 5 documents returned")
assertEquals(
"two|four|one|three|five",
docs.joinToString("|") { it.id },
"The documents were not ordered correctly"
)
}
fun allEmpty(db: ThrowawayDatabase) =
assertEquals(
0,
db.conn.findAll(TEST_TABLE, JsonDocument::class.java).size,
"There should have been no documents returned"
)
fun byIdString(db: ThrowawayDatabase) {
JsonDocument.load(db)
val doc = db.conn.findById(TEST_TABLE, "two", JsonDocument::class.java)
assertNotNull(doc, "The document should have been returned")
assertEquals("two", doc.id, "An incorrect document was returned")
}
fun byIdNumber(db: ThrowawayDatabase) {
Configuration.idField = "key"
try {
db.conn.insert(TEST_TABLE, NumIdDocument(18, "howdy"))
val doc = db.conn.findById(TEST_TABLE, 18, NumIdDocument::class.java)
assertNotNull(doc, "The document should have been returned")
} finally {
Configuration.idField = "id"
}
}
fun byIdNotFound(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertNull(
db.conn.findById(TEST_TABLE, "x", JsonDocument::class.java),
"There should have been no document returned"
)
}
fun byFieldsMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
val docs = db.conn.findByFields(
TEST_TABLE,
listOf(Field.any("value", listOf("blue", "purple")), Field.exists("sub")),
JsonDocument::class.java,
FieldMatch.ALL
)
assertEquals(1, docs.size, "There should have been a document returned")
assertEquals("four", docs[0].id, "The incorrect document was returned")
}
fun byFieldsMatchOrdered(db: ThrowawayDatabase) {
JsonDocument.load(db)
val docs = db.conn.findByFields(
TEST_TABLE,
listOf(Field.equal("value", "purple")),
JsonDocument::class.java,
orderBy = listOf(Field.named("id"))
)
assertEquals(2, docs.size, "There should have been 2 documents returned")
assertEquals("five|four", docs.joinToString("|") { it.id }, "The documents were not ordered correctly")
}
fun byFieldsMatchNumIn(db: ThrowawayDatabase) {
JsonDocument.load(db)
val docs = db.conn.findByFields(
TEST_TABLE,
listOf(Field.any("numValue", listOf(2, 4, 6, 8))),
JsonDocument::class.java
)
assertEquals(1, docs.size, "There should have been a document returned")
assertEquals("three", docs[0].id, "The incorrect document was returned")
}
fun byFieldsNoMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertEquals(
0,
db.conn.findByFields(TEST_TABLE, listOf(Field.greater("numValue", 100)), JsonDocument::class.java).size,
"There should have been no documents returned"
)
}
fun byFieldsMatchInArray(db: ThrowawayDatabase) {
ArrayDocument.testDocuments.forEach { db.conn.insert(TEST_TABLE, it) }
val docs =
db.conn.findByFields(
TEST_TABLE,
listOf(Field.inArray("values", TEST_TABLE, listOf("c"))),
ArrayDocument::class.java
)
assertEquals(2, docs.size, "There should have been two documents returned")
assertTrue(listOf("first", "second").contains(docs[0].id), "An incorrect document was returned (${docs[0].id}")
assertTrue(listOf("first", "second").contains(docs[1].id), "An incorrect document was returned (${docs[1].id}")
}
fun byFieldsNoMatchInArray(db: ThrowawayDatabase) {
ArrayDocument.testDocuments.forEach { db.conn.insert(TEST_TABLE, it) }
assertEquals(
0,
db.conn.findByFields(
TEST_TABLE,
listOf(Field.inArray("values", TEST_TABLE, listOf("j"))),
ArrayDocument::class.java
).size,
"There should have been no documents returned"
)
}
fun byContainsMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
val docs = db.conn.findByContains(TEST_TABLE, mapOf("value" to "purple"), JsonDocument::class.java)
assertEquals(2, docs.size, "There should have been 2 documents returned")
assertTrue(listOf("four", "five").contains(docs[0].id), "An incorrect document was returned (${docs[0].id}")
assertTrue(listOf("four", "five").contains(docs[1].id), "An incorrect document was returned (${docs[1].id}")
}
fun byContainsMatchOrdered(db: ThrowawayDatabase) {
JsonDocument.load(db)
val docs = db.conn.findByContains(
TEST_TABLE,
mapOf("sub" to mapOf("foo" to "green")),
JsonDocument::class.java,
listOf(Field.named("value"))
)
assertEquals(2, docs.size, "There should have been 2 documents returned")
assertEquals("two|four", docs.joinToString("|") { it.id }, "The documents were not ordered correctly")
}
fun byContainsNoMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertEquals(
0,
db.conn.findByContains(TEST_TABLE, mapOf("value" to "indigo"), JsonDocument::class.java).size,
"There should have been no documents returned"
)
}
fun byJsonPathMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
val docs = db.conn.findByJsonPath(TEST_TABLE, "$.numValue ? (@ > 10)", JsonDocument::class.java)
assertEquals(2, docs.size, "There should have been 2 documents returned")
assertTrue(listOf("four", "five").contains(docs[0].id), "An incorrect document was returned (${docs[0].id}")
assertTrue(listOf("four", "five").contains(docs[1].id), "An incorrect document was returned (${docs[1].id}")
}
fun byJsonPathMatchOrdered(db: ThrowawayDatabase) {
JsonDocument.load(db)
val docs = db.conn.findByJsonPath(
TEST_TABLE,
"$.numValue ? (@ > 10)",
JsonDocument::class.java,
listOf(Field.named("id"))
)
assertEquals(2, docs.size, "There should have been 2 documents returned")
assertEquals("five|four", docs.joinToString("|") { it.id }, "The documents were not ordered correctly")
}
fun byJsonPathNoMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertEquals(
0,
db.conn.findByJsonPath(TEST_TABLE, "$.numValue ? (@ > 100)", JsonDocument::class.java).size,
"There should have been no documents returned"
)
}
fun firstByFieldsMatchOne(db: ThrowawayDatabase) {
JsonDocument.load(db)
val doc =
db.conn.findFirstByFields(TEST_TABLE, listOf(Field.equal("value", "another")), JsonDocument::class.java)
assertNotNull(doc, "There should have been a document returned")
assertEquals("two", doc.id, "The incorrect document was returned")
}
fun firstByFieldsMatchMany(db: ThrowawayDatabase) {
JsonDocument.load(db)
val doc =
db.conn.findFirstByFields(TEST_TABLE, listOf(Field.equal("sub.foo", "green")), JsonDocument::class.java)
assertNotNull(doc, "There should have been a document returned")
assertTrue(listOf("two", "four").contains(doc.id), "An incorrect document was returned (${doc.id}")
}
fun firstByFieldsMatchOrdered(db: ThrowawayDatabase) {
JsonDocument.load(db)
val doc = db.conn.findFirstByFields(
TEST_TABLE, listOf(Field.equal("sub.foo", "green")), JsonDocument::class.java, 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")
}
fun firstByFieldsNoMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertNull(
db.conn.findFirstByFields(TEST_TABLE, listOf(Field.equal("value", "absent")), JsonDocument::class.java),
"There should have been no document returned"
)
}
fun firstByContainsMatchOne(db: ThrowawayDatabase) {
JsonDocument.load(db)
val doc = db.conn.findFirstByContains(TEST_TABLE, mapOf("value" to "FIRST!"), JsonDocument::class.java)
assertNotNull(doc, "There should have been a document returned")
assertEquals("one", doc.id, "An incorrect document was returned")
}
fun firstByContainsMatchMany(db: ThrowawayDatabase) {
JsonDocument.load(db)
val doc = db.conn.findFirstByContains(TEST_TABLE, mapOf("value" to "purple"), JsonDocument::class.java)
assertNotNull(doc, "There should have been a document returned")
assertTrue(listOf("four", "five").contains(doc.id), "An incorrect document was returned (${doc.id}")
}
fun firstByContainsMatchOrdered(db: ThrowawayDatabase) {
JsonDocument.load(db)
val doc = db.conn.findFirstByContains(
TEST_TABLE,
mapOf("value" to "purple"),
JsonDocument::class.java,
listOf(Field.named("sub.bar NULLS FIRST"))
)
assertNotNull(doc, "There should have been a document returned")
assertEquals("five", doc.id, "An incorrect document was returned")
}
fun firstByContainsNoMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertNull(
db.conn.findFirstByContains(TEST_TABLE, mapOf("value" to "indigo"), JsonDocument::class.java),
"There should have been no document returned"
)
}
fun firstByJsonPathMatchOne(db: ThrowawayDatabase) {
JsonDocument.load(db)
val doc = db.conn.findFirstByJsonPath(TEST_TABLE, "$.numValue ? (@ == 10)", JsonDocument::class.java)
assertNotNull(doc, "There should have been a document returned")
assertEquals("two", doc.id, "An incorrect document was returned")
}
fun firstByJsonPathMatchMany(db: ThrowawayDatabase) {
JsonDocument.load(db)
val doc = db.conn.findFirstByJsonPath(TEST_TABLE, "$.numValue ? (@ > 10)", JsonDocument::class.java)
assertNotNull(doc, "There should have been a document returned")
assertTrue(listOf("four", "five").contains(doc.id), "An incorrect document was returned (${doc.id}")
}
fun firstByJsonPathMatchOrdered(db: ThrowawayDatabase) {
JsonDocument.load(db)
val doc = db.conn.findFirstByJsonPath(
TEST_TABLE,
"$.numValue ? (@ > 10)",
JsonDocument::class.java,
listOf(Field.named("id DESC"))
)
assertNotNull(doc, "There should have been a document returned")
assertEquals("four", doc.id, "An incorrect document was returned")
}
fun firstByJsonPathNoMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertNull(
db.conn.findFirstByJsonPath(TEST_TABLE, "$.numValue ? (@ > 100)", JsonDocument::class.java),
"There should have been no document returned"
)
}
}

View File

@@ -0,0 +1,91 @@
package solutions.bitbadger.documents.java.integration.common
import solutions.bitbadger.documents.common.Field
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
import kotlin.test.assertTrue
/**
* Integration tests for the `Patch` object
*/
object Patch {
fun byIdMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
db.conn.patchById(TEST_TABLE, "one", mapOf("numValue" to 44))
val doc = db.conn.findById(TEST_TABLE, "one", JsonDocument::class.java)
assertNotNull(doc, "There should have been a document returned")
assertEquals("one", doc.id, "An incorrect document was returned")
assertEquals(44, doc.numValue, "The document was not patched")
}
fun byIdNoMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertFalse("Document with ID \"forty-seven\" should not exist") {
db.conn.existsById(TEST_TABLE, "forty-seven")
}
db.conn.patchById(TEST_TABLE, "forty-seven", mapOf("foo" to "green")) // no exception = pass
}
fun byFieldsMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
db.conn.patchByFields(TEST_TABLE, listOf(Field.equal("value", "purple")), mapOf("numValue" to 77))
assertEquals(
2,
db.conn.countByFields(TEST_TABLE, listOf(Field.equal("numValue", 77))),
"There should have been 2 documents with numeric value 77"
)
}
fun byFieldsNoMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
val fields = listOf(Field.equal("value", "burgundy"))
assertFalse("There should be no documents with value of \"burgundy\"") {
db.conn.existsByFields(TEST_TABLE, fields)
}
db.conn.patchByFields(TEST_TABLE, fields, mapOf("foo" to "green")) // no exception = pass
}
fun byContainsMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
val contains = mapOf("value" to "another")
db.conn.patchByContains(TEST_TABLE, contains, mapOf("numValue" to 12))
val doc = db.conn.findFirstByContains(TEST_TABLE, contains, JsonDocument::class.java)
assertNotNull(doc, "There should have been a document returned")
assertEquals("two", doc.id, "The incorrect document was returned")
assertEquals(12, doc.numValue, "The document was not updated")
}
fun byContainsNoMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
val contains = mapOf("value" to "updated")
assertFalse("There should be no matching documents") { db.conn.existsByContains(TEST_TABLE, contains) }
db.conn.patchByContains(TEST_TABLE, contains, mapOf("sub.foo" to "green")) // no exception = pass
}
fun byJsonPathMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
val path = "$.numValue ? (@ > 10)"
db.conn.patchByJsonPath(TEST_TABLE, path, mapOf("value" to "blue"))
val docs = db.conn.findByJsonPath(TEST_TABLE, path, JsonDocument::class.java)
assertEquals(2, docs.size, "There should have been two documents returned")
docs.forEach {
assertTrue(listOf("four", "five").contains(it.id), "An incorrect document was returned (${it.id})")
assertEquals("blue", it.value, "The value for ID ${it.id} was incorrect")
}
}
fun byJsonPathNoMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
val path = "$.numValue ? (@ > 100)"
assertFalse("There should be no documents with numeric values over 100") {
db.conn.existsByJsonPath(TEST_TABLE, path)
}
db.conn.patchByJsonPath(TEST_TABLE, path, mapOf("value" to "blue")) // no exception = pass
}
}

View File

@@ -0,0 +1,110 @@
package solutions.bitbadger.documents.java.integration.common
import solutions.bitbadger.documents.common.Field
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.*
/**
* Integration tests for the `RemoveFields` object
*/
object RemoveFields {
fun byIdMatchFields(db: ThrowawayDatabase) {
JsonDocument.load(db)
db.conn.removeFieldsById(TEST_TABLE, "two", listOf("sub", "value"))
val doc = db.conn.findById(TEST_TABLE, "two", JsonDocument::class.java)
assertNotNull(doc, "There should have been a document returned")
assertEquals("", doc.value, "The value should have been empty")
assertNull(doc.sub, "The sub-document should have been removed")
}
fun byIdMatchNoFields(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertFalse { db.conn.existsByFields(TEST_TABLE, listOf(Field.exists("a_field_that_does_not_exist"))) }
db.conn.removeFieldsById(TEST_TABLE, "one", listOf("a_field_that_does_not_exist")) // no exception = pass
}
fun byIdNoMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertFalse { db.conn.existsById(TEST_TABLE, "fifty") }
db.conn.removeFieldsById(TEST_TABLE, "fifty", listOf("sub")) // no exception = pass
}
fun byFieldsMatchFields(db: ThrowawayDatabase) {
JsonDocument.load(db)
val fields = listOf(Field.equal("numValue", 17))
db.conn.removeFieldsByFields(TEST_TABLE, fields, listOf("sub"))
val doc = db.conn.findFirstByFields(TEST_TABLE, fields, JsonDocument::class.java)
assertNotNull(doc, "The document should have been returned")
assertEquals("four", doc.id, "An incorrect document was returned")
assertNull(doc.sub, "The sub-document should have been removed")
}
fun byFieldsMatchNoFields(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertFalse { db.conn.existsByFields(TEST_TABLE, listOf(Field.exists("nada"))) }
db.conn.removeFieldsByFields(TEST_TABLE, listOf(Field.equal("numValue", 17)), listOf("nada")) // no exn = pass
}
fun byFieldsNoMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
val fields = listOf(Field.notEqual("missing", "nope"))
assertFalse { db.conn.existsByFields(TEST_TABLE, fields) }
db.conn.removeFieldsByFields(TEST_TABLE, fields, listOf("value")) // no exception = pass
}
fun byContainsMatchFields(db: ThrowawayDatabase) {
JsonDocument.load(db)
val criteria = mapOf("sub" to mapOf("foo" to "green"))
db.conn.removeFieldsByContains(TEST_TABLE, criteria, listOf("value"))
val docs = db.conn.findByContains(TEST_TABLE, criteria, JsonDocument::class.java)
assertEquals(2, docs.size, "There should have been 2 documents returned")
docs.forEach {
assertTrue(listOf("two", "four").contains(it.id), "An incorrect document was returned (${it.id})")
assertEquals("", it.value, "The value should have been empty")
}
}
fun byContainsMatchNoFields(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertFalse { db.conn.existsByFields(TEST_TABLE, listOf(Field.exists("invalid_field"))) }
db.conn.removeFieldsByContains(TEST_TABLE, mapOf("sub" to mapOf("foo" to "green")), listOf("invalid_field"))
// no exception = pass
}
fun byContainsNoMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
val contains = mapOf("value" to "substantial")
assertFalse { db.conn.existsByContains(TEST_TABLE, contains) }
db.conn.removeFieldsByContains(TEST_TABLE, contains, listOf("numValue"))
}
fun byJsonPathMatchFields(db: ThrowawayDatabase) {
JsonDocument.load(db)
val path = "$.value ? (@ == \"purple\")"
db.conn.removeFieldsByJsonPath(TEST_TABLE, path, listOf("sub"))
val docs = db.conn.findByJsonPath(TEST_TABLE, path, JsonDocument::class.java)
assertEquals(2, docs.size, "There should have been 2 documents returned")
docs.forEach {
assertTrue(listOf("four", "five").contains(it.id), "An incorrect document was returned (${it.id})")
assertNull(it.sub, "The sub-document should have been removed")
}
}
fun byJsonPathMatchNoFields(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertFalse { db.conn.existsByFields(TEST_TABLE, listOf(Field.exists("submarine"))) }
db.conn.removeFieldsByJsonPath(TEST_TABLE, "$.value ? (@ == \"purple\")", listOf("submarine")) // no exn = pass
}
fun byJsonPathNoMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
val path = "$.value ? (@ == \"mauve\")"
assertFalse { db.conn.existsByJsonPath(TEST_TABLE, path) }
db.conn.removeFieldsByJsonPath(TEST_TABLE, path, listOf("value")) // no exception = pass
}
}

View File

@@ -0,0 +1,47 @@
package solutions.bitbadger.documents.java.integration.postgresql
import org.junit.jupiter.api.DisplayName
import solutions.bitbadger.documents.java.integration.common.Count
import kotlin.test.Test
/**
* PostgreSQL integration tests for the `Count` object / `count*` connection extension functions
*/
@DisplayName("Java | Kotlin | PostgreSQL: Count")
class CountIT {
@Test
@DisplayName("all counts all documents")
fun all() =
PgDB().use(Count::all)
@Test
@DisplayName("byFields counts documents by a numeric value")
fun byFieldsNumeric() =
PgDB().use(Count::byFieldsNumeric)
@Test
@DisplayName("byFields counts documents by a alphanumeric value")
fun byFieldsAlpha() =
PgDB().use(Count::byFieldsAlpha)
@Test
@DisplayName("byContains counts documents when matches are found")
fun byContainsMatch() =
PgDB().use(Count::byContainsMatch)
@Test
@DisplayName("byContains counts documents when no matches are found")
fun byContainsNoMatch() =
PgDB().use(Count::byContainsNoMatch)
@Test
@DisplayName("byJsonPath counts documents when matches are found")
fun byJsonPathMatch() =
PgDB().use(Count::byJsonPathMatch)
@Test
@DisplayName("byJsonPath counts documents when no matches are found")
fun byJsonPathNoMatch() =
PgDB().use(Count::byJsonPathNoMatch)
}

View File

@@ -0,0 +1,48 @@
package solutions.bitbadger.documents.java.integration.postgresql
import org.junit.jupiter.api.DisplayName
import solutions.bitbadger.documents.java.integration.common.Custom
import kotlin.test.Test
/**
* PostgreSQL integration tests for the `Custom` object / `custom*` connection extension functions
*/
@DisplayName("Java | Kotlin | PostgreSQL: Custom")
class CustomIT {
@Test
@DisplayName("list succeeds with empty list")
fun listEmpty() =
PgDB().use(Custom::listEmpty)
@Test
@DisplayName("list succeeds with a non-empty list")
fun listAll() =
PgDB().use(Custom::listAll)
@Test
@DisplayName("single succeeds when document not found")
fun singleNone() =
PgDB().use(Custom::singleNone)
@Test
@DisplayName("single succeeds when a document is found")
fun singleOne() =
PgDB().use(Custom::singleOne)
@Test
@DisplayName("nonQuery makes changes")
fun nonQueryChanges() =
PgDB().use(Custom::nonQueryChanges)
@Test
@DisplayName("nonQuery makes no changes when where clause matches nothing")
fun nonQueryNoChanges() =
PgDB().use(Custom::nonQueryNoChanges)
@Test
@DisplayName("scalar succeeds")
fun scalar() =
PgDB().use(Custom::scalar)
}

View File

@@ -0,0 +1,32 @@
package solutions.bitbadger.documents.java.integration.postgresql
import org.junit.jupiter.api.DisplayName
import solutions.bitbadger.documents.java.integration.common.Definition
import kotlin.test.Test
/**
* PostgreSQL integration tests for the `Definition` object / `ensure*` connection extension functions
*/
@DisplayName("Java | Kotlin | PostgreSQL: Definition")
class DefinitionIT {
@Test
@DisplayName("ensureTable creates table and index")
fun ensureTable() =
PgDB().use(Definition::ensureTable)
@Test
@DisplayName("ensureFieldIndex creates an index")
fun ensureFieldIndex() =
PgDB().use(Definition::ensureFieldIndex)
@Test
@DisplayName("ensureDocumentIndex creates a full index")
fun ensureDocumentIndexFull() =
PgDB().use(Definition::ensureDocumentIndexFull)
@Test
@DisplayName("ensureDocumentIndex creates an optimized index")
fun ensureDocumentIndexOptimized() =
PgDB().use(Definition::ensureDocumentIndexOptimized)
}

View File

@@ -0,0 +1,52 @@
package solutions.bitbadger.documents.java.integration.postgresql
import org.junit.jupiter.api.DisplayName
import solutions.bitbadger.documents.java.integration.common.Delete
import kotlin.test.Test
/**
* PostgreSQL integration tests for the `Delete` object / `deleteBy*` connection extension functions
*/
@DisplayName("Java | Kotlin | PostgreSQL: Delete")
class DeleteIT {
@Test
@DisplayName("byId deletes a matching ID")
fun byIdMatch() =
PgDB().use(Delete::byIdMatch)
@Test
@DisplayName("byId succeeds when no ID matches")
fun byIdNoMatch() =
PgDB().use(Delete::byIdNoMatch)
@Test
@DisplayName("byFields deletes matching documents")
fun byFieldsMatch() =
PgDB().use(Delete::byFieldsMatch)
@Test
@DisplayName("byFields succeeds when no documents match")
fun byFieldsNoMatch() =
PgDB().use(Delete::byFieldsNoMatch)
@Test
@DisplayName("byContains deletes matching documents")
fun byContainsMatch() =
PgDB().use(Delete::byContainsMatch)
@Test
@DisplayName("byContains succeeds when no documents match")
fun byContainsNoMatch() =
PgDB().use(Delete::byContainsNoMatch)
@Test
@DisplayName("byJsonPath deletes matching documents")
fun byJsonPathMatch() =
PgDB().use(Delete::byJsonPathMatch)
@Test
@DisplayName("byJsonPath succeeds when no documents match")
fun byJsonPathNoMatch() =
PgDB().use(Delete::byJsonPathNoMatch)
}

View File

@@ -0,0 +1,57 @@
package solutions.bitbadger.documents.java.integration.postgresql
import org.junit.jupiter.api.DisplayName
import solutions.bitbadger.documents.java.integration.common.Document
import kotlin.test.Test
/**
* PostgreSQL integration tests for the `Document` object / `insert`, `save`, `update` connection extension functions
*/
@DisplayName("Java | Kotlin | PostgreSQL: Document")
class DocumentIT {
@Test
@DisplayName("insert works with default values")
fun insertDefault() =
PgDB().use(Document::insertDefault)
@Test
@DisplayName("insert fails with duplicate key")
fun insertDupe() =
PgDB().use(Document::insertDupe)
@Test
@DisplayName("insert succeeds with numeric auto IDs")
fun insertNumAutoId() =
PgDB().use(Document::insertNumAutoId)
@Test
@DisplayName("insert succeeds with UUID auto ID")
fun insertUUIDAutoId() =
PgDB().use(Document::insertUUIDAutoId)
@Test
@DisplayName("insert succeeds with random string auto ID")
fun insertStringAutoId() =
PgDB().use(Document::insertStringAutoId)
@Test
@DisplayName("save updates an existing document")
fun saveMatch() =
PgDB().use(Document::saveMatch)
@Test
@DisplayName("save inserts a new document")
fun saveNoMatch() =
PgDB().use(Document::saveNoMatch)
@Test
@DisplayName("update replaces an existing document")
fun updateMatch() =
PgDB().use(Document::updateMatch)
@Test
@DisplayName("update succeeds when no document exists")
fun updateNoMatch() =
PgDB().use(Document::updateNoMatch)
}

View File

@@ -0,0 +1,52 @@
package solutions.bitbadger.documents.java.integration.postgresql
import org.junit.jupiter.api.DisplayName
import solutions.bitbadger.documents.java.integration.common.Exists
import kotlin.test.Test
/**
* PostgreSQL integration tests for the `Exists` object / `existsBy*` connection extension functions
*/
@DisplayName("Java | Kotlin | PostgreSQL: Exists")
class ExistsIT {
@Test
@DisplayName("byId returns true when a document matches the ID")
fun byIdMatch() =
PgDB().use(Exists::byIdMatch)
@Test
@DisplayName("byId returns false when no document matches the ID")
fun byIdNoMatch() =
PgDB().use(Exists::byIdNoMatch)
@Test
@DisplayName("byFields returns true when documents match")
fun byFieldsMatch() =
PgDB().use(Exists::byFieldsMatch)
@Test
@DisplayName("byFields returns false when no documents match")
fun byFieldsNoMatch() =
PgDB().use(Exists::byFieldsNoMatch)
@Test
@DisplayName("byContains returns true when documents match")
fun byContainsMatch() =
PgDB().use(Exists::byContainsMatch)
@Test
@DisplayName("byContains returns false when no documents match")
fun byContainsNoMatch() =
PgDB().use(Exists::byContainsNoMatch)
@Test
@DisplayName("byJsonPath returns true when documents match")
fun byJsonPathMatch() =
PgDB().use(Exists::byJsonPathMatch)
@Test
@DisplayName("byJsonPath returns false when no documents match")
fun byJsonPathNoMatch() =
PgDB().use(Exists::byJsonPathNoMatch)
}

View File

@@ -0,0 +1,172 @@
package solutions.bitbadger.documents.java.integration.postgresql
import org.junit.jupiter.api.DisplayName
import solutions.bitbadger.documents.java.integration.common.Find
import kotlin.test.Test
/**
* PostgreSQL integration tests for the `Find` object / `find*` connection extension functions
*/
@DisplayName("Java | Kotlin | PostgreSQL: Find")
class FindIT {
@Test
@DisplayName("all retrieves all documents")
fun allDefault() =
PgDB().use(Find::allDefault)
@Test
@DisplayName("all sorts data ascending")
fun allAscending() =
PgDB().use(Find::allAscending)
@Test
@DisplayName("all sorts data descending")
fun allDescending() =
PgDB().use(Find::allDescending)
@Test
@DisplayName("all sorts data numerically")
fun allNumOrder() =
PgDB().use(Find::allNumOrder)
@Test
@DisplayName("all succeeds with an empty table")
fun allEmpty() =
PgDB().use(Find::allEmpty)
@Test
@DisplayName("byId retrieves a document via a string ID")
fun byIdString() =
PgDB().use(Find::byIdString)
@Test
@DisplayName("byId retrieves a document via a numeric ID")
fun byIdNumber() =
PgDB().use(Find::byIdNumber)
@Test
@DisplayName("byId returns null when a matching ID is not found")
fun byIdNotFound() =
PgDB().use(Find::byIdNotFound)
@Test
@DisplayName("byFields retrieves matching documents")
fun byFieldsMatch() =
PgDB().use(Find::byFieldsMatch)
@Test
@DisplayName("byFields retrieves ordered matching documents")
fun byFieldsMatchOrdered() =
PgDB().use(Find::byFieldsMatchOrdered)
@Test
@DisplayName("byFields retrieves matching documents with a numeric IN clause")
fun byFieldsMatchNumIn() =
PgDB().use(Find::byFieldsMatchNumIn)
@Test
@DisplayName("byFields succeeds when no documents match")
fun byFieldsNoMatch() =
PgDB().use(Find::byFieldsNoMatch)
@Test
@DisplayName("byFields retrieves matching documents with an IN_ARRAY comparison")
fun byFieldsMatchInArray() =
PgDB().use(Find::byFieldsMatchInArray)
@Test
@DisplayName("byFields succeeds when no documents match an IN_ARRAY comparison")
fun byFieldsNoMatchInArray() =
PgDB().use(Find::byFieldsNoMatchInArray)
@Test
@DisplayName("byContains retrieves matching documents")
fun byContainsMatch() =
PgDB().use(Find::byContainsMatch)
@Test
@DisplayName("byContains retrieves ordered matching documents")
fun byContainsMatchOrdered() =
PgDB().use(Find::byContainsMatchOrdered)
@Test
@DisplayName("byContains succeeds when no documents match")
fun byContainsNoMatch() =
PgDB().use(Find::byContainsNoMatch)
@Test
@DisplayName("byJsonPath retrieves matching documents")
fun byJsonPathMatch() =
PgDB().use(Find::byJsonPathMatch)
@Test
@DisplayName("byJsonPath retrieves ordered matching documents")
fun byJsonPathMatchOrdered() =
PgDB().use(Find::byJsonPathMatchOrdered)
@Test
@DisplayName("byJsonPath succeeds when no documents match")
fun byJsonPathNoMatch() =
PgDB().use(Find::byJsonPathNoMatch)
@Test
@DisplayName("firstByFields retrieves a matching document")
fun firstByFieldsMatchOne() =
PgDB().use(Find::firstByFieldsMatchOne)
@Test
@DisplayName("firstByFields retrieves a matching document among many")
fun firstByFieldsMatchMany() =
PgDB().use(Find::firstByFieldsMatchMany)
@Test
@DisplayName("firstByFields retrieves a matching document among many (ordered)")
fun firstByFieldsMatchOrdered() =
PgDB().use(Find::firstByFieldsMatchOrdered)
@Test
@DisplayName("firstByFields returns null when no document matches")
fun firstByFieldsNoMatch() =
PgDB().use(Find::firstByFieldsNoMatch)
@Test
@DisplayName("firstByContains retrieves a matching document")
fun firstByContainsMatchOne() =
PgDB().use(Find::firstByContainsMatchOne)
@Test
@DisplayName("firstByContains retrieves a matching document among many")
fun firstByContainsMatchMany() =
PgDB().use(Find::firstByContainsMatchMany)
@Test
@DisplayName("firstByContains retrieves a matching document among many (ordered)")
fun firstByContainsMatchOrdered() =
PgDB().use(Find::firstByContainsMatchOrdered)
@Test
@DisplayName("firstByContains returns null when no document matches")
fun firstByContainsNoMatch() =
PgDB().use(Find::firstByContainsNoMatch)
@Test
@DisplayName("firstByJsonPath retrieves a matching document")
fun firstByJsonPathMatchOne() =
PgDB().use(Find::firstByJsonPathMatchOne)
@Test
@DisplayName("firstByJsonPath retrieves a matching document among many")
fun firstByJsonPathMatchMany() =
PgDB().use(Find::firstByJsonPathMatchMany)
@Test
@DisplayName("firstByJsonPath retrieves a matching document among many (ordered)")
fun firstByJsonPathMatchOrdered() =
PgDB().use(Find::firstByJsonPathMatchOrdered)
@Test
@DisplayName("firstByJsonPath returns null when no document matches")
fun firstByJsonPathNoMatch() =
PgDB().use(Find::firstByJsonPathNoMatch)
}

View File

@@ -0,0 +1,52 @@
package solutions.bitbadger.documents.java.integration.postgresql
import org.junit.jupiter.api.DisplayName
import solutions.bitbadger.documents.java.integration.common.Patch
import kotlin.test.Test
/**
* PostgreSQL integration tests for the `Patch` object / `patchBy*` connection extension functions
*/
@DisplayName("Java | Kotlin | PostgreSQL: Patch")
class PatchIT {
@Test
@DisplayName("byId patches an existing document")
fun byIdMatch() =
PgDB().use(Patch::byIdMatch)
@Test
@DisplayName("byId succeeds for a non-existent document")
fun byIdNoMatch() =
PgDB().use(Patch::byIdNoMatch)
@Test
@DisplayName("byFields patches matching document")
fun byFieldsMatch() =
PgDB().use(Patch::byFieldsMatch)
@Test
@DisplayName("byFields succeeds when no documents match")
fun byFieldsNoMatch() =
PgDB().use(Patch::byFieldsNoMatch)
@Test
@DisplayName("byContains patches matching document")
fun byContainsMatch() =
PgDB().use(Patch::byContainsMatch)
@Test
@DisplayName("byContains succeeds when no documents match")
fun byContainsNoMatch() =
PgDB().use(Patch::byContainsNoMatch)
@Test
@DisplayName("byJsonPath patches matching document")
fun byJsonPathMatch() =
PgDB().use(Patch::byJsonPathMatch)
@Test
@DisplayName("byJsonPath succeeds when no documents match")
fun byJsonPathNoMatch() =
PgDB().use(Patch::byJsonPathNoMatch)
}

View File

@@ -0,0 +1,55 @@
package solutions.bitbadger.documents.java.integration.postgresql
import solutions.bitbadger.documents.common.*
import solutions.bitbadger.documents.java.*
import solutions.bitbadger.documents.java.integration.TEST_TABLE
import solutions.bitbadger.documents.java.integration.ThrowawayDatabase
/**
* A wrapper for a throwaway PostgreSQL database
*/
class PgDB : ThrowawayDatabase {
private var dbName = ""
init {
DocumentConfig.serializer = JacksonDocumentSerializer()
dbName = "throwaway_${AutoId.generateRandomString(8)}"
Configuration.connectionString = connString("postgres")
Configuration.dbConn().use {
it.customNonQuery("CREATE DATABASE $dbName")
}
Configuration.connectionString = connString(dbName)
}
override val conn = Configuration.dbConn()
init {
conn.ensureTable(TEST_TABLE)
}
override fun close() {
conn.close()
Configuration.connectionString = connString("postgres")
Configuration.dbConn().use {
it.customNonQuery("DROP DATABASE $dbName")
}
Configuration.connectionString = null
}
override fun dbObjectExists(name: String) =
conn.customScalar("SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = :name) AS it",
listOf(Parameter(":name", ParameterType.STRING, name)), Boolean::class.java, Results::toExists)
companion object {
/**
* Create a connection string for the given database
*
* @param database The database to which the library should connect
* @return The connection string for the database
*/
private fun connString(database: String) =
"jdbc:postgresql://localhost/$database?user=postgres&password=postgres"
}
}

View File

@@ -0,0 +1,72 @@
package solutions.bitbadger.documents.java.integration.postgresql
import org.junit.jupiter.api.DisplayName
import solutions.bitbadger.documents.java.integration.common.RemoveFields
import kotlin.test.Test
/**
* PostgreSQL integration tests for the `RemoveFields` object / `removeFieldsBy*` connection extension functions
*/
@DisplayName("Java | Kotlin | PostgreSQL: RemoveFields")
class RemoveFieldsIT {
@Test
@DisplayName("byId removes fields from an existing document")
fun byIdMatchFields() =
PgDB().use(RemoveFields::byIdMatchFields)
@Test
@DisplayName("byId succeeds when fields do not exist on an existing document")
fun byIdMatchNoFields() =
PgDB().use(RemoveFields::byIdMatchNoFields)
@Test
@DisplayName("byId succeeds when no document exists")
fun byIdNoMatch() =
PgDB().use(RemoveFields::byIdNoMatch)
@Test
@DisplayName("byFields removes fields from matching documents")
fun byFieldsMatchFields() =
PgDB().use(RemoveFields::byFieldsMatchFields)
@Test
@DisplayName("byFields succeeds when fields do not exist on matching documents")
fun byFieldsMatchNoFields() =
PgDB().use(RemoveFields::byFieldsMatchNoFields)
@Test
@DisplayName("byFields succeeds when no matching documents exist")
fun byFieldsNoMatch() =
PgDB().use(RemoveFields::byFieldsNoMatch)
@Test
@DisplayName("byContains removes fields from matching documents")
fun byContainsMatchFields() =
PgDB().use(RemoveFields::byContainsMatchFields)
@Test
@DisplayName("byContains succeeds when fields do not exist on matching documents")
fun byContainsMatchNoFields() =
PgDB().use(RemoveFields::byContainsMatchNoFields)
@Test
@DisplayName("byContains succeeds when no matching documents exist")
fun byContainsNoMatch() =
PgDB().use(RemoveFields::byContainsNoMatch)
@Test
@DisplayName("byJsonPath removes fields from matching documents")
fun byJsonPathMatchFields() =
PgDB().use(RemoveFields::byJsonPathMatchFields)
@Test
@DisplayName("byJsonPath succeeds when fields do not exist on matching documents")
fun byJsonPathMatchNoFields() =
PgDB().use(RemoveFields::byJsonPathMatchNoFields)
@Test
@DisplayName("byJsonPath succeeds when no matching documents exist")
fun byJsonPathNoMatch() =
PgDB().use(RemoveFields::byJsonPathNoMatch)
}

View File

@@ -0,0 +1,41 @@
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.java.integration.common.Count
import kotlin.test.Test
/**
* SQLite integration tests for the `Count` object / `count*` connection extension functions
*/
@DisplayName("Java | Kotlin | SQLite: Count")
class CountIT {
@Test
@DisplayName("all counts all documents")
fun all() =
SQLiteDB().use(Count::all)
@Test
@DisplayName("byFields counts documents by a numeric value")
fun byFieldsNumeric() =
SQLiteDB().use(Count::byFieldsNumeric)
@Test
@DisplayName("byFields counts documents by a alphanumeric value")
fun byFieldsAlpha() =
SQLiteDB().use(Count::byFieldsAlpha)
@Test
@DisplayName("byContains fails")
fun byContainsMatch() {
assertThrows<DocumentException> { SQLiteDB().use(Count::byContainsMatch) }
}
@Test
@DisplayName("byJsonPath fails")
fun byJsonPathMatch() {
assertThrows<DocumentException> { SQLiteDB().use(Count::byJsonPathMatch) }
}
}

View File

@@ -0,0 +1,47 @@
package solutions.bitbadger.documents.java.integration.sqlite
import org.junit.jupiter.api.DisplayName
import solutions.bitbadger.documents.java.integration.common.Custom
import kotlin.test.Test
/**
* SQLite integration tests for the `Custom` object / `custom*` connection extension functions
*/
@DisplayName("Java | Kotlin | SQLite: Custom")
class CustomIT {
@Test
@DisplayName("list succeeds with empty list")
fun listEmpty() =
SQLiteDB().use(Custom::listEmpty)
@Test
@DisplayName("list succeeds with a non-empty list")
fun listAll() =
SQLiteDB().use(Custom::listAll)
@Test
@DisplayName("single succeeds when document not found")
fun singleNone() =
SQLiteDB().use(Custom::singleNone)
@Test
@DisplayName("single succeeds when a document is found")
fun singleOne() =
SQLiteDB().use(Custom::singleOne)
@Test
@DisplayName("nonQuery makes changes")
fun nonQueryChanges() =
SQLiteDB().use(Custom::nonQueryChanges)
@Test
@DisplayName("nonQuery makes no changes when where clause matches nothing")
fun nonQueryNoChanges() =
SQLiteDB().use(Custom::nonQueryNoChanges)
@Test
@DisplayName("scalar succeeds")
fun scalar() =
SQLiteDB().use(Custom::scalar)
}

View File

@@ -0,0 +1,36 @@
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.java.integration.common.Definition
import kotlin.test.Test
/**
* SQLite integration tests for the `Definition` object / `ensure*` connection extension functions
*/
@DisplayName("Java | Kotlin | SQLite: Definition")
class DefinitionIT {
@Test
@DisplayName("ensureTable creates table and index")
fun ensureTable() =
SQLiteDB().use(Definition::ensureTable)
@Test
@DisplayName("ensureFieldIndex creates an index")
fun ensureFieldIndex() =
SQLiteDB().use(Definition::ensureFieldIndex)
@Test
@DisplayName("ensureDocumentIndex fails for full index")
fun ensureDocumentIndexFull() {
assertThrows<DocumentException> { SQLiteDB().use(Definition::ensureDocumentIndexFull) }
}
@Test
@DisplayName("ensureDocumentIndex fails for optimized index")
fun ensureDocumentIndexOptimized() {
assertThrows<DocumentException> { SQLiteDB().use(Definition::ensureDocumentIndexOptimized) }
}
}

View File

@@ -0,0 +1,46 @@
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.java.integration.common.Delete
import kotlin.test.Test
/**
* SQLite integration tests for the `Delete` object / `deleteBy*` connection extension functions
*/
@DisplayName("Java | Kotlin | SQLite: Delete")
class DeleteIT {
@Test
@DisplayName("byId deletes a matching ID")
fun byIdMatch() =
SQLiteDB().use(Delete::byIdMatch)
@Test
@DisplayName("byId succeeds when no ID matches")
fun byIdNoMatch() =
SQLiteDB().use(Delete::byIdNoMatch)
@Test
@DisplayName("byFields deletes matching documents")
fun byFieldsMatch() =
SQLiteDB().use(Delete::byFieldsMatch)
@Test
@DisplayName("byFields succeeds when no documents match")
fun byFieldsNoMatch() =
SQLiteDB().use(Delete::byFieldsNoMatch)
@Test
@DisplayName("byContains fails")
fun byContainsFails() {
assertThrows<DocumentException> { SQLiteDB().use(Delete::byContainsMatch) }
}
@Test
@DisplayName("byJsonPath fails")
fun byJsonPathFails() {
assertThrows<DocumentException> { SQLiteDB().use(Delete::byJsonPathMatch) }
}
}

View File

@@ -0,0 +1,57 @@
package solutions.bitbadger.documents.java.integration.sqlite
import org.junit.jupiter.api.DisplayName
import solutions.bitbadger.documents.java.integration.common.Document
import kotlin.test.Test
/**
* SQLite integration tests for the `Document` object / `insert`, `save`, `update` connection extension functions
*/
@DisplayName("Java | Kotlin | SQLite: Document")
class DocumentIT {
@Test
@DisplayName("insert works with default values")
fun insertDefault() =
SQLiteDB().use(Document::insertDefault)
@Test
@DisplayName("insert fails with duplicate key")
fun insertDupe() =
SQLiteDB().use(Document::insertDupe)
@Test
@DisplayName("insert succeeds with numeric auto IDs")
fun insertNumAutoId() =
SQLiteDB().use(Document::insertNumAutoId)
@Test
@DisplayName("insert succeeds with UUID auto ID")
fun insertUUIDAutoId() =
SQLiteDB().use(Document::insertUUIDAutoId)
@Test
@DisplayName("insert succeeds with random string auto ID")
fun insertStringAutoId() =
SQLiteDB().use(Document::insertStringAutoId)
@Test
@DisplayName("save updates an existing document")
fun saveMatch() =
SQLiteDB().use(Document::saveMatch)
@Test
@DisplayName("save inserts a new document")
fun saveNoMatch() =
SQLiteDB().use(Document::saveNoMatch)
@Test
@DisplayName("update replaces an existing document")
fun updateMatch() =
SQLiteDB().use(Document::updateMatch)
@Test
@DisplayName("update succeeds when no document exists")
fun updateNoMatch() =
SQLiteDB().use(Document::updateNoMatch)
}

View File

@@ -0,0 +1,46 @@
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.java.integration.common.Exists
import kotlin.test.Test
/**
* SQLite integration tests for the `Exists` object / `existsBy*` connection extension functions
*/
@DisplayName("Java | Kotlin | SQLite: Exists")
class ExistsIT {
@Test
@DisplayName("byId returns true when a document matches the ID")
fun byIdMatch() =
SQLiteDB().use(Exists::byIdMatch)
@Test
@DisplayName("byId returns false when no document matches the ID")
fun byIdNoMatch() =
SQLiteDB().use(Exists::byIdNoMatch)
@Test
@DisplayName("byFields returns true when documents match")
fun byFieldsMatch() =
SQLiteDB().use(Exists::byFieldsMatch)
@Test
@DisplayName("byFields returns false when no documents match")
fun byFieldsNoMatch() =
SQLiteDB().use(Exists::byFieldsNoMatch)
@Test
@DisplayName("byContains fails")
fun byContainsFails() {
assertThrows<DocumentException> { SQLiteDB().use(Exists::byContainsMatch) }
}
@Test
@DisplayName("byJsonPath fails")
fun byJsonPathFails() {
assertThrows<DocumentException> { SQLiteDB().use(Exists::byJsonPathMatch) }
}
}

View File

@@ -0,0 +1,128 @@
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.java.integration.common.Find
import kotlin.test.Test
/**
* SQLite integration tests for the `Find` object / `find*` connection extension functions
*/
@DisplayName("Java | Kotlin | SQLite: Find")
class FindIT {
@Test
@DisplayName("all retrieves all documents")
fun allDefault() =
SQLiteDB().use(Find::allDefault)
@Test
@DisplayName("all sorts data ascending")
fun allAscending() =
SQLiteDB().use(Find::allAscending)
@Test
@DisplayName("all sorts data descending")
fun allDescending() =
SQLiteDB().use(Find::allDescending)
@Test
@DisplayName("all sorts data numerically")
fun allNumOrder() =
SQLiteDB().use(Find::allNumOrder)
@Test
@DisplayName("all succeeds with an empty table")
fun allEmpty() =
SQLiteDB().use(Find::allEmpty)
@Test
@DisplayName("byId retrieves a document via a string ID")
fun byIdString() =
SQLiteDB().use(Find::byIdString)
@Test
@DisplayName("byId retrieves a document via a numeric ID")
fun byIdNumber() =
SQLiteDB().use(Find::byIdNumber)
@Test
@DisplayName("byId returns null when a matching ID is not found")
fun byIdNotFound() =
SQLiteDB().use(Find::byIdNotFound)
@Test
@DisplayName("byFields retrieves matching documents")
fun byFieldsMatch() =
SQLiteDB().use(Find::byFieldsMatch)
@Test
@DisplayName("byFields retrieves ordered matching documents")
fun byFieldsMatchOrdered() =
SQLiteDB().use(Find::byFieldsMatchOrdered)
@Test
@DisplayName("byFields retrieves matching documents with a numeric IN clause")
fun byFieldsMatchNumIn() =
SQLiteDB().use(Find::byFieldsMatchNumIn)
@Test
@DisplayName("byFields succeeds when no documents match")
fun byFieldsNoMatch() =
SQLiteDB().use(Find::byFieldsNoMatch)
@Test
@DisplayName("byFields retrieves matching documents with an IN_ARRAY comparison")
fun byFieldsMatchInArray() =
SQLiteDB().use(Find::byFieldsMatchInArray)
@Test
@DisplayName("byFields succeeds when no documents match an IN_ARRAY comparison")
fun byFieldsNoMatchInArray() =
SQLiteDB().use(Find::byFieldsNoMatchInArray)
@Test
@DisplayName("byContains fails")
fun byContainsFails() {
assertThrows<DocumentException> { SQLiteDB().use(Find::byContainsMatch) }
}
@Test
@DisplayName("byJsonPath fails")
fun byJsonPathFails() {
assertThrows<DocumentException> { SQLiteDB().use(Find::byJsonPathMatch) }
}
@Test
@DisplayName("firstByFields retrieves a matching document")
fun firstByFieldsMatchOne() =
SQLiteDB().use(Find::firstByFieldsMatchOne)
@Test
@DisplayName("firstByFields retrieves a matching document among many")
fun firstByFieldsMatchMany() =
SQLiteDB().use(Find::firstByFieldsMatchMany)
@Test
@DisplayName("firstByFields retrieves a matching document among many (ordered)")
fun firstByFieldsMatchOrdered() =
SQLiteDB().use(Find::firstByFieldsMatchOrdered)
@Test
@DisplayName("firstByFields returns null when no document matches")
fun firstByFieldsNoMatch() =
SQLiteDB().use(Find::firstByFieldsNoMatch)
@Test
@DisplayName("firstByContains fails")
fun firstByContainsFails() {
assertThrows<DocumentException> { SQLiteDB().use(Find::firstByContainsMatchOne) }
}
@Test
@DisplayName("firstByJsonPath fails")
fun firstByJsonPathFails() {
assertThrows<DocumentException> { SQLiteDB().use(Find::firstByJsonPathMatchOne) }
}
}

View File

@@ -0,0 +1,46 @@
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.java.integration.common.Patch
import kotlin.test.Test
/**
* SQLite integration tests for the `Patch` object / `patchBy*` connection extension functions
*/
@DisplayName("Java | Kotlin | SQLite: Patch")
class PatchIT {
@Test
@DisplayName("byId patches an existing document")
fun byIdMatch() =
SQLiteDB().use(Patch::byIdMatch)
@Test
@DisplayName("byId succeeds for a non-existent document")
fun byIdNoMatch() =
SQLiteDB().use(Patch::byIdNoMatch)
@Test
@DisplayName("byFields patches matching document")
fun byFieldsMatch() =
SQLiteDB().use(Patch::byFieldsMatch)
@Test
@DisplayName("byFields succeeds when no documents match")
fun byFieldsNoMatch() =
SQLiteDB().use(Patch::byFieldsNoMatch)
@Test
@DisplayName("byContains fails")
fun byContainsFails() {
assertThrows<DocumentException> { SQLiteDB().use(Patch::byContainsMatch) }
}
@Test
@DisplayName("byJsonPath fails")
fun byJsonPathFails() {
assertThrows<DocumentException> { SQLiteDB().use(Patch::byJsonPathMatch) }
}
}

View File

@@ -0,0 +1,56 @@
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.java.integration.common.RemoveFields
import kotlin.test.Test
/**
* SQLite integration tests for the `RemoveFields` object / `removeFieldsBy*` connection extension functions
*/
@DisplayName("Java | Kotlin | SQLite: RemoveFields")
class RemoveFieldsIT {
@Test
@DisplayName("byId removes fields from an existing document")
fun byIdMatchFields() =
SQLiteDB().use(RemoveFields::byIdMatchFields)
@Test
@DisplayName("byId succeeds when fields do not exist on an existing document")
fun byIdMatchNoFields() =
SQLiteDB().use(RemoveFields::byIdMatchNoFields)
@Test
@DisplayName("byId succeeds when no document exists")
fun byIdNoMatch() =
SQLiteDB().use(RemoveFields::byIdNoMatch)
@Test
@DisplayName("byFields removes fields from matching documents")
fun byFieldsMatchFields() =
SQLiteDB().use(RemoveFields::byFieldsMatchFields)
@Test
@DisplayName("byFields succeeds when fields do not exist on matching documents")
fun byFieldsMatchNoFields() =
SQLiteDB().use(RemoveFields::byFieldsMatchNoFields)
@Test
@DisplayName("byFields succeeds when no matching documents exist")
fun byFieldsNoMatch() =
SQLiteDB().use(RemoveFields::byFieldsNoMatch)
@Test
@DisplayName("byContains fails")
fun byContainsFails() {
assertThrows<DocumentException> { SQLiteDB().use(RemoveFields::byContainsMatchFields) }
}
@Test
@DisplayName("byJsonPath fails")
fun byJsonPathFails() {
assertThrows<DocumentException> { SQLiteDB().use(RemoveFields::byJsonPathMatchFields) }
}
}

View File

@@ -0,0 +1,36 @@
package solutions.bitbadger.documents.java.integration.sqlite
import solutions.bitbadger.documents.common.*
import solutions.bitbadger.documents.java.*
import solutions.bitbadger.documents.java.integration.TEST_TABLE
import solutions.bitbadger.documents.java.integration.ThrowawayDatabase
import java.io.File
/**
* A wrapper for a throwaway SQLite database
*/
class SQLiteDB : ThrowawayDatabase {
private var dbName = ""
init {
DocumentConfig.serializer = JacksonDocumentSerializer()
dbName = "test-db-${AutoId.generateRandomString(8)}.db"
Configuration.connectionString = "jdbc:sqlite:$dbName"
}
override val conn = Configuration.dbConn()
init {
conn.ensureTable(TEST_TABLE)
}
override fun close() {
conn.close()
File(dbName).delete()
}
override fun dbObjectExists(name: String) =
conn.customScalar("SELECT EXISTS (SELECT 1 FROM sqlite_master WHERE name = :name) AS it",
listOf(Parameter(":name", ParameterType.STRING, name)), Boolean::class.java, Results::toExists)
}