Reorg modules; complete impls/tests

This commit is contained in:
2025-03-25 07:20:30 -04:00
parent 815f506339
commit 50188d939e
363 changed files with 11626 additions and 1574 deletions

113
src/kotlinx/pom.xml Normal file
View File

@@ -0,0 +1,113 @@
<?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>
<parent>
<groupId>solutions.bitbadger</groupId>
<artifactId>documents</artifactId>
<version>4.0.0-alpha1-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<groupId>solutions.bitbadger.documents</groupId>
<artifactId>kotlinx</artifactId>
<name>${project.groupId}:${project.artifactId}</name>
<description>Expose a document store interface for PostgreSQL and SQLite (KotlinX Serialization 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>
<dependencies>
<dependency>
<groupId>solutions.bitbadger.documents</groupId>
<artifactId>core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-serialization-json-jvm</artifactId>
<version>${serialization.version}</version>
</dependency>
</dependencies>
<build>
<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/java</sourceDir>
<sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
</sourceDirs>
</configuration>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
<configuration>
<sourceDirs>
<sourceDir>${project.basedir}/src/test/java</sourceDir>
<sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
</sourceDirs>
</configuration>
</execution>
</executions>
<configuration>
<compilerPlugins>
<plugin>kotlinx-serialization</plugin>
</compilerPlugins>
</configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-serialization</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire.version}</version>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${failsafe.version}</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>
<version>3.13.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,10 @@
module solutions.bitbadger.documents.kotlinx {
requires solutions.bitbadger.documents.core;
requires kotlin.stdlib;
requires kotlin.reflect;
requires kotlinx.serialization.json;
requires java.sql;
exports solutions.bitbadger.documents.kotlinx;
exports solutions.bitbadger.documents.kotlinx.extensions;
}

View File

@@ -0,0 +1,120 @@
package solutions.bitbadger.documents.kotlinx
import solutions.bitbadger.documents.*
import solutions.bitbadger.documents.kotlinx.extensions.*
import solutions.bitbadger.documents.query.CountQuery
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
*/
fun all(tableName: String, conn: Connection) =
conn.customScalar(CountQuery.all(tableName), mapFunc = Results::toCount)
/**
* Count all documents in the table
*
* @param tableName The name of the table in which documents should be counted
* @return A count of the documents in the table
*/
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
*/
fun byFields(
tableName: String,
fields: Collection<Field<*>>,
howMatched: FieldMatch? = null,
conn: Connection
): Long {
val named = Parameters.nameFields(fields)
return conn.customScalar(
CountQuery.byFields(tableName, named, howMatched),
Parameters.addFields(named),
Results::toCount
)
}
/**
* Count documents using a field comparison
*
* @param tableName The name of the table in which documents should be counted
* @param fields The fields which should be compared
* @param howMatched How the fields should be matched
* @return A count of the matching documents in the table
*/
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
*/
inline fun <reified TContains> byContains(tableName: String, criteria: TContains, conn: Connection) =
conn.customScalar<Long>(
CountQuery.byContains(tableName),
listOf(Parameters.json<TContains>(":criteria", criteria)),
Results::toCount
)
/**
* Count documents using a JSON containment query (PostgreSQL only)
*
* @param tableName The name of the table in which documents should be counted
* @param criteria The object for which JSON containment should be checked
* @return A count of the matching documents in the table
* @throws DocumentException If called on a SQLite connection
*/
inline fun <reified 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
*/
fun byJsonPath(tableName: String, path: String, conn: Connection) =
conn.customScalar(
CountQuery.byJsonPath(tableName),
listOf(Parameter(":path", ParameterType.STRING, path)),
Results::toCount
)
/**
* Count documents using a JSON Path match query (PostgreSQL only)
*
* @param tableName The name of the table in which documents should be counted
* @param path The JSON path comparison to match
* @return A count of the matching documents in the table
* @throws DocumentException If called on a SQLite connection
*/
fun byJsonPath(tableName: String, path: String) =
Configuration.dbConn().use { byJsonPath(tableName, path, it) }
}

View File

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

View File

@@ -0,0 +1,71 @@
package solutions.bitbadger.documents.kotlinx
import solutions.bitbadger.documents.DocumentException
import solutions.bitbadger.documents.DocumentIndex
import solutions.bitbadger.documents.java.Definition as JvmDefinition
import java.sql.Connection
/**
* Functions to define tables and indexes
*/
object Definition {
/**
* Create a document table if necessary
*
* @param tableName The table whose existence should be ensured (may include schema)
* @param conn The connection on which the query should be executed
*/
fun ensureTable(tableName: String, conn: Connection) =
JvmDefinition.ensureTable(tableName, conn)
/**
* Create a document table if necessary
*
* @param tableName The table whose existence should be ensured (may include schema)
*/
fun ensureTable(tableName: String) =
JvmDefinition.ensureTable(tableName)
/**
* Create an index on field(s) within documents in the specified table if necessary
*
* @param tableName The table to be indexed (may include schema)
* @param indexName The name of the index to create
* @param fields One or more fields to be indexed<
* @param conn The connection on which the query should be executed
*/
fun ensureFieldIndex(tableName: String, indexName: String, fields: Collection<String>, conn: Connection) =
JvmDefinition.ensureFieldIndex(tableName, indexName, fields, conn)
/**
* Create an index on field(s) within documents in the specified table if necessary
*
* @param tableName The table to be indexed (may include schema)
* @param indexName The name of the index to create
* @param fields One or more fields to be indexed<
*/
fun ensureFieldIndex(tableName: String, indexName: String, fields: Collection<String>) =
JvmDefinition.ensureFieldIndex(tableName, indexName, fields)
/**
* Create a document index on a table (PostgreSQL only)
*
* @param tableName The table to be indexed (may include schema)
* @param indexType The type of index to ensure
* @param conn The connection on which the query should be executed
* @throws DocumentException If called on a SQLite connection
*/
fun ensureDocumentIndex(tableName: String, indexType: DocumentIndex, conn: Connection) =
JvmDefinition.ensureDocumentIndex(tableName, indexType, conn)
/**
* Create a document index on a table (PostgreSQL only)
*
* @param tableName The table to be indexed (may include schema)
* @param indexType The type of index to ensure
* @throws DocumentException If called on a SQLite connection
*/
fun ensureDocumentIndex(tableName: String, indexType: DocumentIndex) =
JvmDefinition.ensureDocumentIndex(tableName, indexType)
}

View File

@@ -0,0 +1,95 @@
package solutions.bitbadger.documents.kotlinx
import solutions.bitbadger.documents.*
import solutions.bitbadger.documents.java.Delete as JvmDelete
import solutions.bitbadger.documents.kotlinx.extensions.*
import solutions.bitbadger.documents.query.DeleteQuery
import java.sql.Connection
/**
* Functions to delete documents
*/
object Delete {
/**
* Delete a document by its ID
*
* @param tableName The name of the table from which documents should be deleted
* @param docId The ID of the document to be deleted
* @param conn The connection on which the deletion should be executed
*/
fun <TKey> byId(tableName: String, docId: TKey, conn: Connection) =
JvmDelete.byId(tableName, docId, conn)
/**
* Delete a document by its ID
*
* @param tableName The name of the table from which documents should be deleted
* @param docId The ID of the document to be deleted
*/
fun <TKey> byId(tableName: String, docId: TKey) =
JvmDelete.byId(tableName, docId)
/**
* Delete documents using a field comparison
*
* @param tableName The name of the table from which documents should be deleted
* @param fields The fields which should be compared
* @param howMatched How the fields should be matched
* @param conn The connection on which the deletion should be executed
*/
fun byFields(tableName: String, fields: Collection<Field<*>>, howMatched: FieldMatch? = null, conn: Connection) =
JvmDelete.byFields(tableName, fields, howMatched, conn)
/**
* Delete documents using a field comparison
*
* @param tableName The name of the table from which documents should be deleted
* @param fields The fields which should be compared
* @param howMatched How the fields should be matched
*/
fun byFields(tableName: String, fields: Collection<Field<*>>, howMatched: FieldMatch? = null) =
JvmDelete.byFields(tableName, fields, howMatched)
/**
* Delete documents using a JSON containment query (PostgreSQL only)
*
* @param tableName The name of the table from which documents should be deleted
* @param criteria The object for which JSON containment should be checked
* @param conn The connection on which the deletion should be executed
* @throws DocumentException If called on a SQLite connection
*/
inline fun <reified TContains> byContains(tableName: String, criteria: TContains, conn: Connection) =
conn.customNonQuery(DeleteQuery.byContains(tableName), listOf(Parameters.json<TContains>(":criteria", criteria)))
/**
* Delete documents using a JSON containment query (PostgreSQL only)
*
* @param tableName The name of the table from which documents should be deleted
* @param criteria The object for which JSON containment should be checked
* @throws DocumentException If called on a SQLite connection
*/
inline fun <reified 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
*/
fun byJsonPath(tableName: String, path: String, conn: Connection) =
JvmDelete.byJsonPath(tableName, path, conn)
/**
* Delete documents using a JSON Path match query (PostgreSQL only)
*
* @param tableName The name of the table from which documents should be deleted
* @param path The JSON path comparison to match
* @throws DocumentException If called on a SQLite connection
*/
fun byJsonPath(tableName: String, path: String) =
JvmDelete.byJsonPath(tableName, path)
}

View File

@@ -0,0 +1,114 @@
package solutions.bitbadger.documents.kotlinx
import solutions.bitbadger.documents.AutoId
import solutions.bitbadger.documents.Configuration
import solutions.bitbadger.documents.Dialect
import solutions.bitbadger.documents.Field
import solutions.bitbadger.documents.kotlinx.extensions.customNonQuery
import solutions.bitbadger.documents.query.DocumentQuery
import solutions.bitbadger.documents.query.Where
import solutions.bitbadger.documents.query.statementWhere
import java.sql.Connection
/**
* Functions for manipulating documents
*/
object Document {
/**
* Insert a new document
*
* @param tableName The table into which the document should be inserted (may include schema)
* @param document The document to be inserted
* @param conn The connection on which the query should be executed
*/
inline fun <reified TDoc> insert(tableName: String, document: TDoc, conn: Connection) {
val strategy = Configuration.autoIdStrategy
val query = if (strategy == AutoId.DISABLED) {
DocumentQuery.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"
}
DocumentQuery.insert(tableName).replace(":data", dataParam)
}
conn.customNonQuery(query, listOf(Parameters.json(":data", document)))
}
/**
* Insert a new document
*
* @param tableName The table into which the document should be inserted (may include schema)
* @param document The document to be inserted
*/
inline fun <reified 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
*/
inline fun <reified TDoc> save(tableName: String, document: TDoc, conn: Connection) =
conn.customNonQuery(DocumentQuery.save(tableName), listOf(Parameters.json(":data", document)))
/**
* Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
*
* @param tableName The table in which the document should be saved (may include schema)
* @param document The document to be saved
*/
inline fun <reified 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
*/
inline fun <TKey, reified TDoc> update(tableName: String, docId: TKey, document: TDoc, conn: Connection) =
conn.customNonQuery(
statementWhere(DocumentQuery.update(tableName), Where.byId(":id", docId)),
Parameters.addFields(
listOf(Field.equal(Configuration.idField, docId, ":id")),
mutableListOf(Parameters.json(":data", document))
)
)
/**
* Update (replace) a document by its ID
*
* @param tableName The table in which the document should be replaced (may include schema)
* @param docId The ID of the document to be replaced
* @param document The document to be replaced
*/
inline fun <TKey, reified TDoc> update(tableName: String, docId: TKey, document: TDoc) =
Configuration.dbConn().use { update(tableName, docId, document, it) }
}

View File

@@ -0,0 +1,33 @@
package solutions.bitbadger.documents.kotlinx
import kotlinx.serialization.json.Json
/**
* Configuration for document serialization
*/
object DocumentConfig {
val options = Json {
coerceInputValues = true
encodeDefaults = true
explicitNulls = false
}
/**
* Serialize a document to JSON
*
* @param document The document to be serialized
* @return The JSON string with the serialized document
*/
inline fun <reified TDoc> serialize(document: TDoc) =
options.encodeToString(document)
/**
* Deserialize a document from JSON
*
* @param json The JSON string with the serialized document
* @return The document created from the given JSON
*/
inline fun <reified TDoc> deserialize(json: String) =
options.decodeFromString<TDoc>(json)
}

View File

@@ -0,0 +1,107 @@
package solutions.bitbadger.documents.kotlinx
import solutions.bitbadger.documents.*
import solutions.bitbadger.documents.java.Exists as JvmExists
import solutions.bitbadger.documents.kotlinx.extensions.*
import solutions.bitbadger.documents.query.ExistsQuery
import java.sql.Connection
/**
* Functions to determine whether documents exist
*/
object Exists {
/**
* Determine a document's existence by its ID
*
* @param tableName The name of the table in which document existence should be checked
* @param docId The ID of the document to be checked
* @param conn The connection on which the existence check should be executed
* @return True if the document exists, false if not
*/
fun <TKey> byId(tableName: String, docId: TKey, conn: Connection) =
JvmExists.byId(tableName, docId, conn)
/**
* 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> byId(tableName: String, docId: TKey) =
JvmExists.byId(tableName, docId)
/**
* Determine document existence using a field comparison
*
* @param tableName The name of the table in which document existence should be checked
* @param fields The fields which should be compared
* @param howMatched How the fields should be matched
* @param conn The connection on which the existence check should be executed
* @return True if any matching documents exist, false if not
*/
fun byFields(tableName: String, fields: Collection<Field<*>>, howMatched: FieldMatch? = null, conn: Connection) =
JvmExists.byFields(tableName, fields, howMatched, conn)
/**
* Determine document existence using a field comparison
*
* @param tableName The name of the table in which document existence should be checked
* @param fields The fields which should be compared
* @param howMatched How the fields should be matched
* @return True if any matching documents exist, false if not
*/
fun byFields(tableName: String, fields: Collection<Field<*>>, howMatched: FieldMatch? = null) =
JvmExists.byFields(tableName, fields, howMatched)
/**
* Determine document existence using a JSON containment query (PostgreSQL only)
*
* @param tableName The name of the table in which document existence should be checked
* @param criteria The object for which JSON containment should be checked
* @param conn The connection on which the existence check should be executed
* @return True if any matching documents exist, false if not
* @throws DocumentException If called on a SQLite connection
*/
inline fun <reified TContains> byContains(tableName: String, criteria: TContains, conn: Connection) =
conn.customScalar(
ExistsQuery.byContains(tableName),
listOf(Parameters.json(":criteria", criteria)),
Results::toExists
)
/**
* Determine document existence using a JSON containment query (PostgreSQL only)
*
* @param tableName The name of the table in which document existence should be checked
* @param criteria The object for which JSON containment should be checked
* @return True if any matching documents exist, false if not
* @throws DocumentException If called on a SQLite connection
*/
inline fun <reified 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
*/
fun byJsonPath(tableName: String, path: String, conn: Connection) =
JvmExists.byJsonPath(tableName, path, conn)
/**
* Determine document existence using a JSON Path match query (PostgreSQL only)
*
* @param tableName The name of the table in which document existence should be checked
* @param path The JSON path comparison to match
* @return True if any matching documents exist, false if not
* @throws DocumentException If called on a SQLite connection
*/
fun byJsonPath(tableName: String, path: String) =
JvmExists.byJsonPath(tableName, path)
}

View File

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

View File

@@ -0,0 +1,77 @@
package solutions.bitbadger.documents.kotlinx
import solutions.bitbadger.documents.Field
import solutions.bitbadger.documents.Parameter
import solutions.bitbadger.documents.ParameterType
import solutions.bitbadger.documents.java.Parameters as JvmParameters
import java.sql.Connection
/**
* 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
*/
fun nameFields(fields: Collection<Field<*>>): Collection<Field<*>> =
JvmParameters.nameFields(fields)
/**
* 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
*/
inline fun <reified T> json(name: String, value: T) =
Parameter(name, ParameterType.JSON, DocumentConfig.serialize<T>(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
*/
fun addFields(fields: Collection<Field<*>>, existing: MutableCollection<Parameter<*>> = mutableListOf()) =
JvmParameters.addFields(fields, existing)
/**
* 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
*/
fun replaceNamesInQuery(query: String, parameters: Collection<Parameter<*>>) =
JvmParameters.replaceNamesInQuery(query, parameters)
/**
* 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
*/
fun apply(conn: Connection, query: String, parameters: Collection<Parameter<*>>) =
JvmParameters.apply(conn, query, parameters)
/**
* 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
*/
fun fieldNames(names: Collection<String>, parameterName: String = ":name") =
JvmParameters.fieldNames(names, parameterName)
}

View File

@@ -0,0 +1,137 @@
package solutions.bitbadger.documents.kotlinx
import solutions.bitbadger.documents.*
import solutions.bitbadger.documents.kotlinx.extensions.*
import solutions.bitbadger.documents.query.PatchQuery
import java.sql.Connection
/**
* Functions to patch (partially update) documents
*/
object Patch {
/**
* Patch a document by its ID
*
* @param tableName The name of the table in which a document should be patched
* @param docId The ID of the document to be patched
* @param patch The object whose properties should be replaced in the document
* @param conn The connection on which the update should be executed
*/
inline fun <TKey, reified TPatch> byId(tableName: String, docId: TKey, patch: TPatch, conn: Connection) =
conn.customNonQuery(
PatchQuery.byId(tableName, docId),
Parameters.addFields(
listOf(Field.equal(Configuration.idField, docId, ":id")),
mutableListOf(Parameters.json(":data", patch))
)
)
/**
* Patch a document by its ID
*
* @param tableName The name of the table in which a document should be patched
* @param docId The ID of the document to be patched
* @param patch The object whose properties should be replaced in the document
*/
inline fun <TKey, reified 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
*/
inline fun <reified TPatch> byFields(
tableName: String,
fields: Collection<Field<*>>,
patch: TPatch,
howMatched: FieldMatch? = null,
conn: Connection
) {
val named = Parameters.nameFields(fields)
conn.customNonQuery(
PatchQuery.byFields(tableName, named, howMatched), Parameters.addFields(
named,
mutableListOf(Parameters.json(":data", patch))
)
)
}
/**
* Patch documents using a field comparison
*
* @param tableName The name of the table in which documents should be patched
* @param fields The fields which should be compared
* @param patch The object whose properties should be replaced in the document
* @param howMatched How the fields should be matched
*/
inline fun <reified 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
*/
inline fun <reified TContains, reified TPatch> byContains(
tableName: String,
criteria: TContains,
patch: TPatch,
conn: Connection
) =
conn.customNonQuery(
PatchQuery.byContains(tableName),
listOf(Parameters.json(":criteria", criteria), Parameters.json(":data", patch))
)
/**
* Patch documents using a JSON containment query (PostgreSQL only)
*
* @param tableName The name of the table in which documents should be patched
* @param criteria The object against which JSON containment should be checked
* @param patch The object whose properties should be replaced in the document
* @throws DocumentException If called on a SQLite connection
*/
inline fun <reified TContains, reified 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
*/
inline fun <reified TPatch> byJsonPath(tableName: String, path: String, patch: TPatch, conn: Connection) =
conn.customNonQuery(
PatchQuery.byJsonPath(tableName),
listOf(Parameter(":path", ParameterType.STRING, path), Parameters.json(":data", patch))
)
/**
* Patch documents using a JSON Path match query (PostgreSQL only)
*
* @param tableName The name of the table in which documents should be patched
* @param path The JSON path comparison to match
* @param patch The object whose properties should be replaced in the document
* @throws DocumentException If called on a SQLite connection
*/
inline fun <reified TPatch> byJsonPath(tableName: String, path: String, patch: TPatch) =
Configuration.dbConn().use { byJsonPath(tableName, path, patch, it) }
}

View File

@@ -0,0 +1,124 @@
package solutions.bitbadger.documents.kotlinx
import solutions.bitbadger.documents.*
import solutions.bitbadger.documents.java.RemoveFields as JvmRemoveFields
import solutions.bitbadger.documents.kotlinx.extensions.*
import solutions.bitbadger.documents.query.RemoveFieldsQuery
import java.sql.Connection
/**
* Functions to remove fields from documents
*/
object RemoveFields {
/**
* Remove fields from a document by its ID
*
* @param tableName The name of the table in which the document's fields should be removed
* @param docId The ID of the document to have fields removed
* @param toRemove The names of the fields to be removed
* @param conn The connection on which the update should be executed
*/
fun <TKey> byId(tableName: String, docId: TKey, toRemove: Collection<String>, conn: Connection) =
JvmRemoveFields.byId(tableName, docId, toRemove, conn)
/**
* Remove fields from a document by its ID
*
* @param tableName The name of the table in which the document's fields should be removed
* @param docId The ID of the document to have fields removed
* @param toRemove The names of the fields to be removed
*/
fun <TKey> byId(tableName: String, docId: TKey, toRemove: Collection<String>) =
JvmRemoveFields.byId(tableName, docId, toRemove)
/**
* Remove fields from documents using a field comparison
*
* @param tableName The name of the table in which document fields should be removed
* @param fields The fields which should be compared
* @param toRemove The names of the fields to be removed
* @param howMatched How the fields should be matched
* @param conn The connection on which the update should be executed
*/
fun byFields(
tableName: String,
fields: Collection<Field<*>>,
toRemove: Collection<String>,
howMatched: FieldMatch? = null,
conn: Connection
) =
JvmRemoveFields.byFields(tableName, fields, toRemove, howMatched, conn)
/**
* Remove fields from documents using a field comparison
*
* @param tableName The name of the table in which document fields should be removed
* @param fields The fields which should be compared
* @param toRemove The names of the fields to be removed
* @param howMatched How the fields should be matched
*/
fun byFields(
tableName: String,
fields: Collection<Field<*>>,
toRemove: Collection<String>,
howMatched: FieldMatch? = null
) =
JvmRemoveFields.byFields(tableName, fields, toRemove, howMatched)
/**
* Remove fields from documents using a JSON containment query (PostgreSQL only)
*
* @param tableName The name of the table in which document fields should be removed
* @param criteria The object against which JSON containment should be checked
* @param toRemove The names of the fields to be removed
* @param conn The connection on which the update should be executed
* @throws DocumentException If called on a SQLite connection
*/
inline fun <reified TContains> byContains(
tableName: String,
criteria: TContains,
toRemove: Collection<String>,
conn: Connection
) {
val nameParams = Parameters.fieldNames(toRemove)
conn.customNonQuery(
RemoveFieldsQuery.byContains(tableName, nameParams),
listOf(Parameters.json(":criteria", criteria), *nameParams.toTypedArray())
)
}
/**
* Remove fields from documents using a JSON containment query (PostgreSQL only)
*
* @param tableName The name of the table in which document fields should be removed
* @param criteria The object against which JSON containment should be checked
* @param toRemove The names of the fields to be removed
* @throws DocumentException If called on a SQLite connection
*/
inline fun <reified 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
*/
fun byJsonPath(tableName: String, path: String, toRemove: Collection<String>, conn: Connection) =
JvmRemoveFields.byJsonPath(tableName, path, toRemove, conn)
/**
* Remove fields from documents using a JSON Path match query (PostgreSQL only)
*
* @param tableName The name of the table in which document fields should be removed
* @param path The JSON path comparison to match
* @param toRemove The names of the fields to be removed
* @throws DocumentException If called on a SQLite connection
*/
fun byJsonPath(tableName: String, path: String, toRemove: Collection<String>) =
JvmRemoveFields.byJsonPath(tableName, path, toRemove)
}

View File

@@ -0,0 +1,76 @@
package solutions.bitbadger.documents.kotlinx
import solutions.bitbadger.documents.Configuration
import solutions.bitbadger.documents.Dialect
import solutions.bitbadger.documents.DocumentException
import java.sql.PreparedStatement
import java.sql.ResultSet
import java.sql.SQLException
/**
* Helper functions for handling results
*/
object Results {
/**
* Create a domain item from a document, specifying the field in which the document is found
*
* @param field The field name containing the JSON document
* @return A function to create the constructed domain item
*/
inline fun <reified TDoc> fromDocument(field: String): (ResultSet) -> TDoc =
{ rs -> DocumentConfig.deserialize<TDoc>(rs.getString(field)) }
/**
* Create a domain item from a document
*
* @return The constructed domain item
*/
inline fun <reified TDoc> fromData(rs: ResultSet) =
fromDocument<TDoc>("data")(rs)
/**
* Create a list of items for the results of the given command, using the specified mapping function
*
* @param stmt The prepared statement to execute
* @param mapFunc The mapping function from data reader to domain class instance
* @return A list of items from the query's result
* @throws DocumentException If there is a problem executing the query
*/
inline fun <reified TDoc : Any> toCustomList(stmt: PreparedStatement, mapFunc: (ResultSet) -> TDoc) =
try {
stmt.executeQuery().use {
val results = mutableListOf<TDoc>()
while (it.next()) {
results.add(mapFunc(it))
}
results.toList()
}
} catch (ex: SQLException) {
throw DocumentException("Error retrieving documents from query: ${ex.message}", ex)
}
/**
* Extract a count from the first column
*
* @param rs A `ResultSet` set to the row with the count to retrieve
* @return The count from the row
*/
fun toCount(rs: ResultSet) =
when (Configuration.dialect()) {
Dialect.POSTGRESQL -> rs.getInt("it").toLong()
Dialect.SQLITE -> rs.getLong("it")
}
/**
* Extract a true/false value from the first column
*
* @param rs A `ResultSet` set to the row with the true/false value to retrieve
* @return The true/false value from the row
*/
fun toExists(rs: ResultSet) =
when (Configuration.dialect()) {
Dialect.POSTGRESQL -> rs.getBoolean("it")
Dialect.SQLITE -> toCount(rs) > 0L
}
}

View File

@@ -0,0 +1,473 @@
package solutions.bitbadger.documents.kotlinx.extensions
import solutions.bitbadger.documents.*
import solutions.bitbadger.documents.kotlinx.*
import java.sql.Connection
import java.sql.ResultSet
// ~~~ CUSTOM QUERIES ~~~
/**
* Execute a query that returns a list of results
*
* @param query The query to retrieve the results
* @param parameters Parameters to use for the query
* @param mapFunc The mapping function between the document and the domain item
* @return A list of results for the given query
*/
inline fun <reified TDoc : Any> Connection.customList(
query: String, parameters: Collection<Parameter<*>> = listOf(), mapFunc: (ResultSet) -> TDoc
) = Custom.list(query, parameters, this, mapFunc)
/**
* Execute a query that returns one or no results
*
* @param query The query to retrieve the results
* @param parameters Parameters to use for the query
* @param mapFunc The mapping function between the document and the domain item
* @return The document if one matches the query, `null` otherwise
*/
inline fun <reified TDoc : Any> Connection.customSingle(
query: String, parameters: Collection<Parameter<*>> = listOf(), mapFunc: (ResultSet) -> TDoc
) = Custom.single(query, parameters, this, mapFunc)
/**
* Execute a query that returns no results
*
* @param query The query to retrieve the results
* @param parameters Parameters to use for the query
*/
fun Connection.customNonQuery(query: String, parameters: Collection<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 mapFunc The mapping function between the document and the domain item
* @return The scalar value from the query
*/
inline fun <reified T : Any> Connection.customScalar(
query: String,
parameters: Collection<Parameter<*>> = listOf(),
mapFunc: (ResultSet) -> T
) = Custom.scalar(query, parameters, this, mapFunc)
// ~~~ DEFINITION QUERIES ~~~
/**
* Create a document table if necessary
*
* @param tableName The table whose existence should be ensured (may include schema)
*/
fun Connection.ensureTable(tableName: String) =
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
*/
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
*/
inline fun <reified 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
*/
inline fun <reified 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
*/
inline fun <TKey, reified 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
*/
inline fun <reified 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
*/
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
*/
inline fun <reified 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
*/
fun Connection.existsByJsonPath(tableName: String, path: String) =
Exists.byJsonPath(tableName, path, this)
// ~~~ DOCUMENT RETRIEVAL QUERIES ~~~
/**
* Retrieve all documents in the given table, ordering results by the optional given fields
*
* @param tableName The table from which documents should be retrieved
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return A list of documents from the given table
*/
inline fun <reified TDoc : Any> Connection.findAll(tableName: String, orderBy: Collection<Field<*>>? = null) =
Find.all<TDoc>(tableName, orderBy, this)
/**
* Retrieve a document by its ID
*
* @param tableName The table from which the document should be retrieved
* @param docId The ID of the document to retrieve
* @return The document if it is found, `null` otherwise
*/
inline fun <TKey, reified TDoc : Any> Connection.findById(tableName: String, docId: TKey) =
Find.byId<TKey, TDoc>(tableName, docId, this)
/**
* Retrieve documents using a field comparison, ordering results by the optional given fields
*
* @param tableName The table from which the document should be retrieved
* @param fields The fields which should be compared
* @param howMatched How the fields should be matched
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return A list of documents matching the field comparison
*/
inline fun <reified TDoc : Any> Connection.findByFields(
tableName: String,
fields: Collection<Field<*>>,
howMatched: FieldMatch? = null,
orderBy: Collection<Field<*>>? = null
) =
Find.byFields<TDoc>(tableName, fields, howMatched, orderBy, this)
/**
* Retrieve documents using a JSON containment query, ordering results by the optional given fields (PostgreSQL only)
*
* @param tableName The name of the table in which document existence should be checked
* @param criteria The object for which JSON containment should be checked
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return A list of documents matching the JSON containment query
* @throws DocumentException If called on a SQLite connection
*/
inline fun <reified TDoc : Any, reified TContains> Connection.findByContains(
tableName: String,
criteria: TContains,
orderBy: Collection<Field<*>>? = null
) =
Find.byContains<TDoc, TContains>(tableName, criteria, orderBy, this)
/**
* Retrieve documents using a JSON Path match query, ordering results by the optional given fields (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved
* @param path The JSON path comparison to match
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return A list of documents matching the JSON Path match query
* @throws DocumentException If called on a SQLite connection
*/
inline fun <reified TDoc : Any> Connection.findByJsonPath(
tableName: String,
path: String,
orderBy: Collection<Field<*>>? = null
) =
Find.byJsonPath<TDoc>(tableName, path, orderBy, this)
/**
* Retrieve the first document using a field comparison and optional ordering fields
*
* @param tableName The table from which documents should be retrieved
* @param fields The fields which should be compared
* @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`)
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return The first document matching the field comparison, or `null` if no matches are found
*/
inline fun <reified TDoc : Any> Connection.findFirstByFields(
tableName: String,
fields: Collection<Field<*>>,
howMatched: FieldMatch? = null,
orderBy: Collection<Field<*>>? = null
) =
Find.firstByFields<TDoc>(tableName, fields, howMatched, orderBy, this)
/**
* Retrieve the first document using a JSON containment query and optional ordering fields (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved
* @param criteria The object for which JSON containment should be checked
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return The first document matching the JSON containment query, or `null` if no matches are found
* @throws DocumentException If called on a SQLite connection
*/
inline fun <reified TDoc : Any, reified TContains> Connection.findFirstByContains(
tableName: String,
criteria: TContains,
orderBy: Collection<Field<*>>? = null
) =
Find.firstByContains<TDoc, TContains>(tableName, criteria, orderBy, this)
/**
* Retrieve the first document using a JSON Path match query and optional ordering fields (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved
* @param path The JSON path comparison to match
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return The first document matching the JSON Path match query, or `null` if no matches are found
* @throws DocumentException If called on a SQLite connection
*/
inline fun <reified TDoc : Any> Connection.findFirstByJsonPath(
tableName: String,
path: String,
orderBy: Collection<Field<*>>? = null
) =
Find.firstByJsonPath<TDoc>(tableName, path, orderBy, this)
// ~~~ DOCUMENT PATCH (PARTIAL UPDATE) QUERIES ~~~
/**
* Patch a document by its ID
*
* @param tableName The name of the table in which a document should be patched
* @param docId The ID of the document to be patched
* @param patch The object whose properties should be replaced in the document
*/
inline fun <TKey, reified 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
*/
inline fun <reified 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
*/
inline fun <reified TContains, reified 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
*/
inline fun <reified 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
*/
inline fun <reified 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
*/
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
*/
inline fun <reified 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,14 @@
module solutions.bitbadger.documents.kotlinx.tests {
requires solutions.bitbadger.documents.core;
requires solutions.bitbadger.documents.kotlinx;
requires java.sql;
requires kotlin.stdlib;
requires kotlinx.serialization.json;
requires kotlin.test.junit5;
exports solutions.bitbadger.documents.kotlinx.tests;
exports solutions.bitbadger.documents.kotlinx.tests.integration;
opens solutions.bitbadger.documents.kotlinx.tests;
opens solutions.bitbadger.documents.kotlinx.tests.integration;
}

View File

@@ -0,0 +1,22 @@
package solutions.bitbadger.documents.kotlinx.tests
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import solutions.bitbadger.documents.kotlinx.DocumentConfig
import kotlin.test.assertFalse
import kotlin.test.assertTrue
/**
* Unit tests for the `Configuration` object
*/
@DisplayName("KotlinX | DocumentConfig")
class DocumentConfigTest {
@Test
@DisplayName("Default JSON options are as expected")
fun defaultJsonOptions() {
assertTrue(DocumentConfig.options.configuration.encodeDefaults, "Encode Defaults should have been set")
assertFalse(DocumentConfig.options.configuration.explicitNulls, "Explicit Nulls should not have been set")
assertTrue(DocumentConfig.options.configuration.coerceInputValues, "Coerce Input Values should have been set")
}
}

View File

@@ -0,0 +1,53 @@
package solutions.bitbadger.documents.kotlinx.tests
import kotlinx.serialization.Serializable
import solutions.bitbadger.documents.kotlinx.extensions.insert
import solutions.bitbadger.documents.kotlinx.tests.integration.ThrowawayDatabase
/** 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,72 @@
package solutions.bitbadger.documents.kotlinx.tests.integration
import solutions.bitbadger.documents.*
import solutions.bitbadger.documents.kotlinx.extensions.*
import solutions.bitbadger.documents.kotlinx.tests.JsonDocument
import solutions.bitbadger.documents.kotlinx.tests.TEST_TABLE
import kotlin.test.assertEquals
/**
* Integration tests for the `Count` object
*/
object CountFunctions {
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,83 @@
package solutions.bitbadger.documents.kotlinx.tests.integration
import solutions.bitbadger.documents.*
import solutions.bitbadger.documents.kotlinx.Results
import solutions.bitbadger.documents.kotlinx.extensions.*
import solutions.bitbadger.documents.kotlinx.tests.JsonDocument
import solutions.bitbadger.documents.kotlinx.tests.TEST_TABLE
import solutions.bitbadger.documents.query.*
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
/**
* Integration tests for the `Custom` object
*/
object CustomFunctions {
fun listEmpty(db: ThrowawayDatabase) {
JsonDocument.load(db)
db.conn.deleteByFields(TEST_TABLE, listOf(Field.exists(Configuration.idField)))
val result = db.conn.customList<JsonDocument>(FindQuery.all(TEST_TABLE), mapFunc = Results::fromData)
assertEquals(0, result.size, "There should have been no results")
}
fun listAll(db: ThrowawayDatabase) {
JsonDocument.load(db)
val result = db.conn.customList<JsonDocument>(FindQuery.all(TEST_TABLE), mapFunc = Results::fromData)
assertEquals(5, result.size, "There should have been 5 results")
}
fun singleNone(db: ThrowawayDatabase) =
assertNull(
db.conn.customSingle(FindQuery.all(TEST_TABLE), mapFunc = Results::fromData),
"There should not have been a document returned"
)
fun singleOne(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertNotNull(
db.conn.customSingle<JsonDocument>(FindQuery.all(TEST_TABLE), mapFunc = Results::fromData),
"There should not have been a document returned"
)
}
fun nonQueryChanges(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertEquals(
5L, db.conn.customScalar(CountQuery.all(TEST_TABLE), mapFunc = Results::toCount),
"There should have been 5 documents in the table"
)
db.conn.customNonQuery("DELETE FROM $TEST_TABLE")
assertEquals(
0L, db.conn.customScalar(CountQuery.all(TEST_TABLE), mapFunc = Results::toCount),
"There should have been no documents in the table"
)
}
fun nonQueryNoChanges(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertEquals(
5L, db.conn.customScalar(CountQuery.all(TEST_TABLE), mapFunc = Results::toCount),
"There should have been 5 documents in the table"
)
db.conn.customNonQuery(
DeleteQuery.byId(TEST_TABLE, "eighty-two"),
listOf(Parameter(":id", ParameterType.STRING, "eighty-two"))
)
assertEquals(
5L, db.conn.customScalar(CountQuery.all(TEST_TABLE), mapFunc = 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", mapFunc = Results::toCount),
"The number 3 should have been returned"
)
}
}

View File

@@ -0,0 +1,45 @@
package solutions.bitbadger.documents.kotlinx.tests.integration
import solutions.bitbadger.documents.*
import solutions.bitbadger.documents.kotlinx.extensions.*
import solutions.bitbadger.documents.kotlinx.tests.TEST_TABLE
import kotlin.test.assertFalse
import kotlin.test.assertTrue
/**
* Integration tests for the `Definition` object / `ensure*` connection extension functions
*/
object DefinitionFunctions {
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,69 @@
package solutions.bitbadger.documents.kotlinx.tests.integration
import solutions.bitbadger.documents.*
import solutions.bitbadger.documents.kotlinx.extensions.*
import solutions.bitbadger.documents.kotlinx.tests.JsonDocument
import solutions.bitbadger.documents.kotlinx.tests.TEST_TABLE
import kotlin.test.assertEquals
/**
* Integration tests for the `Delete` object
*/
object DeleteFunctions {
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,130 @@
package solutions.bitbadger.documents.kotlinx.tests.integration
import org.junit.jupiter.api.assertThrows
import solutions.bitbadger.documents.*
import solutions.bitbadger.documents.kotlinx.extensions.*
import solutions.bitbadger.documents.kotlinx.tests.JsonDocument
import solutions.bitbadger.documents.kotlinx.tests.NumIdDocument
import solutions.bitbadger.documents.kotlinx.tests.SubDocument
import solutions.bitbadger.documents.kotlinx.tests.TEST_TABLE
import kotlin.test.*
/**
* Integration tests for the `Document` object / `insert`, `save`, `update` connection extension functions
*/
object DocumentFunctions {
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<JsonDocument>(TEST_TABLE)
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))
assertThrows<DocumentException>("Inserting a document with a duplicate key should have thrown an exception") {
db.conn.insert(TEST_TABLE, JsonDocument("a", "b", 22, null))
}
}
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<NumIdDocument>(TEST_TABLE, 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<JsonDocument>(TEST_TABLE)
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<JsonDocument>(TEST_TABLE)
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<String, JsonDocument>(TEST_TABLE, "two")
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<String, JsonDocument>(TEST_TABLE, "test"),
"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<String, JsonDocument>(TEST_TABLE, "one")
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,66 @@
package solutions.bitbadger.documents.kotlinx.tests.integration
import solutions.bitbadger.documents.*
import solutions.bitbadger.documents.kotlinx.extensions.*
import solutions.bitbadger.documents.kotlinx.tests.JsonDocument
import solutions.bitbadger.documents.kotlinx.tests.TEST_TABLE
import kotlin.test.assertFalse
import kotlin.test.assertTrue
/**
* Integration tests for the `Exists` object
*/
object ExistsFunctions {
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,300 @@
package solutions.bitbadger.documents.kotlinx.tests.integration
import solutions.bitbadger.documents.*
import solutions.bitbadger.documents.kotlinx.extensions.*
import solutions.bitbadger.documents.kotlinx.tests.ArrayDocument
import solutions.bitbadger.documents.kotlinx.tests.JsonDocument
import solutions.bitbadger.documents.kotlinx.tests.NumIdDocument
import solutions.bitbadger.documents.kotlinx.tests.TEST_TABLE
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue
/**
* Integration tests for the `Find` object
*/
object FindFunctions {
fun allDefault(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertEquals(5, db.conn.findAll<JsonDocument>(TEST_TABLE).size, "There should have been 5 documents returned")
}
fun allAscending(db: ThrowawayDatabase) {
JsonDocument.load(db)
val docs = db.conn.findAll<JsonDocument>(TEST_TABLE, 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<JsonDocument>(TEST_TABLE, 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<JsonDocument>(
TEST_TABLE,
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<JsonDocument>(TEST_TABLE).size, "There should have been no documents returned")
fun byIdString(db: ThrowawayDatabase) {
JsonDocument.load(db)
val doc = db.conn.findById<String, JsonDocument>(TEST_TABLE, "two")
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<Int, NumIdDocument>(TEST_TABLE, 18)
assertNotNull(doc, "The document should have been returned")
} finally {
Configuration.idField = "id"
}
}
fun byIdNotFound(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertNull(
db.conn.findById<String, JsonDocument>(TEST_TABLE, "x"),
"There should have been no document returned"
)
}
fun byFieldsMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
val docs = db.conn.findByFields<JsonDocument>(
TEST_TABLE,
listOf(Field.any("value", listOf("blue", "purple")), Field.exists("sub")),
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<JsonDocument>(
TEST_TABLE,
listOf(Field.equal("value", "purple")),
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<JsonDocument>(TEST_TABLE, listOf(Field.any("numValue", listOf(2, 4, 6, 8))))
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<JsonDocument>(TEST_TABLE, listOf(Field.greater("numValue", 100))).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<ArrayDocument>(TEST_TABLE, listOf(Field.inArray("values", TEST_TABLE, listOf("c"))))
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<ArrayDocument>(
TEST_TABLE,
listOf(Field.inArray("values", TEST_TABLE, listOf("j")))
).size,
"There should have been no documents returned"
)
}
fun byContainsMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
val docs = db.conn.findByContains<JsonDocument, Map<String, String>>(TEST_TABLE, mapOf("value" to "purple"))
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<JsonDocument, Map<String, Map<String, String>>>(
TEST_TABLE,
mapOf("sub" to mapOf("foo" to "green")),
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<JsonDocument, Map<String, String>>(TEST_TABLE, mapOf("value" to "indigo")).size,
"There should have been no documents returned"
)
}
fun byJsonPathMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
val docs = db.conn.findByJsonPath<JsonDocument>(TEST_TABLE, "$.numValue ? (@ > 10)")
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<JsonDocument>(TEST_TABLE, "$.numValue ? (@ > 10)", 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<JsonDocument>(TEST_TABLE, "$.numValue ? (@ > 100)").size,
"There should have been no documents returned"
)
}
fun firstByFieldsMatchOne(db: ThrowawayDatabase) {
JsonDocument.load(db)
val doc = db.conn.findFirstByFields<JsonDocument>(TEST_TABLE, listOf(Field.equal("value", "another")))
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<JsonDocument>(TEST_TABLE, listOf(Field.equal("sub.foo", "green")))
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<JsonDocument>(
TEST_TABLE,
listOf(Field.equal("sub.foo", "green")),
orderBy = listOf(Field.named("n:numValue DESC"))
)
assertNotNull(doc, "There should have been a document returned")
assertEquals("four", doc.id, "An incorrect document was returned")
}
fun firstByFieldsNoMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertNull(
db.conn.findFirstByFields(TEST_TABLE, listOf(Field.equal("value", "absent"))),
"There should have been no document returned"
)
}
fun firstByContainsMatchOne(db: ThrowawayDatabase) {
JsonDocument.load(db)
val doc = db.conn.findFirstByContains<JsonDocument, Map<String, String>>(TEST_TABLE, mapOf("value" to "FIRST!"))
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<JsonDocument, Map<String, String>>(TEST_TABLE, mapOf("value" to "purple"))
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<JsonDocument, Map<String, String>>(
TEST_TABLE,
mapOf("value" to "purple"),
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<JsonDocument, Map<String, String>>(
TEST_TABLE,
mapOf("value" to "indigo")
), "There should have been no document returned"
)
}
fun firstByJsonPathMatchOne(db: ThrowawayDatabase) {
JsonDocument.load(db)
val doc = db.conn.findFirstByJsonPath<JsonDocument>(TEST_TABLE, "$.numValue ? (@ == 10)")
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<JsonDocument>(TEST_TABLE, "$.numValue ? (@ > 10)")
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<JsonDocument>(
TEST_TABLE,
"$.numValue ? (@ > 10)",
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<JsonDocument>(TEST_TABLE, "$.numValue ? (@ > 100)"),
"There should have been no document returned"
)
}
}

View File

@@ -0,0 +1,90 @@
package solutions.bitbadger.documents.kotlinx.tests.integration
import solutions.bitbadger.documents.*
import solutions.bitbadger.documents.kotlinx.extensions.*
import solutions.bitbadger.documents.kotlinx.tests.JsonDocument
import solutions.bitbadger.documents.kotlinx.tests.TEST_TABLE
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
/**
* Integration tests for the `Patch` object
*/
object PatchFunctions {
fun byIdMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
db.conn.patchById(TEST_TABLE, "one", mapOf("numValue" to 44))
val doc = db.conn.findById<String, JsonDocument>(TEST_TABLE, "one")
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<JsonDocument, Map<String, String>>(TEST_TABLE, contains)
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<JsonDocument>(TEST_TABLE, path)
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,54 @@
package solutions.bitbadger.documents.kotlinx.tests.integration
import solutions.bitbadger.documents.*
import solutions.bitbadger.documents.kotlinx.Results
import solutions.bitbadger.documents.kotlinx.extensions.*
import solutions.bitbadger.documents.kotlinx.tests.TEST_TABLE
/**
* A wrapper for a throwaway PostgreSQL database
*/
class PgDB : ThrowawayDatabase {
private var dbName = ""
init {
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)), 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,46 @@
package solutions.bitbadger.documents.kotlinx.tests.integration
import org.junit.jupiter.api.DisplayName
import kotlin.test.Test
/**
* PostgreSQL integration tests for the `Count` object / `count*` connection extension functions
*/
@DisplayName("KotlinX | PostgreSQL: Count")
class PostgreSQLCountIT {
@Test
@DisplayName("all counts all documents")
fun all() =
PgDB().use(CountFunctions::all)
@Test
@DisplayName("byFields counts documents by a numeric value")
fun byFieldsNumeric() =
PgDB().use(CountFunctions::byFieldsNumeric)
@Test
@DisplayName("byFields counts documents by a alphanumeric value")
fun byFieldsAlpha() =
PgDB().use(CountFunctions::byFieldsAlpha)
@Test
@DisplayName("byContains counts documents when matches are found")
fun byContainsMatch() =
PgDB().use(CountFunctions::byContainsMatch)
@Test
@DisplayName("byContains counts documents when no matches are found")
fun byContainsNoMatch() =
PgDB().use(CountFunctions::byContainsNoMatch)
@Test
@DisplayName("byJsonPath counts documents when matches are found")
fun byJsonPathMatch() =
PgDB().use(CountFunctions::byJsonPathMatch)
@Test
@DisplayName("byJsonPath counts documents when no matches are found")
fun byJsonPathNoMatch() =
PgDB().use(CountFunctions::byJsonPathNoMatch)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,108 @@
package solutions.bitbadger.documents.kotlinx.tests.integration
import solutions.bitbadger.documents.*
import solutions.bitbadger.documents.kotlinx.extensions.*
import solutions.bitbadger.documents.kotlinx.tests.JsonDocument
import solutions.bitbadger.documents.kotlinx.tests.TEST_TABLE
import kotlin.test.*
/**
* Integration tests for the `RemoveFields` object
*/
object RemoveFieldsFunctions {
fun byIdMatchFields(db: ThrowawayDatabase) {
JsonDocument.load(db)
db.conn.removeFieldsById(TEST_TABLE, "two", listOf("sub", "value"))
val doc = db.conn.findById<String, JsonDocument>(TEST_TABLE, "two")
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<JsonDocument>(TEST_TABLE, fields)
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<JsonDocument, Map<String, Map<String, String>>>(TEST_TABLE, criteria)
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<JsonDocument>(TEST_TABLE, path)
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,40 @@
package solutions.bitbadger.documents.kotlinx.tests.integration
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.assertThrows
import solutions.bitbadger.documents.DocumentException
import kotlin.test.Test
/**
* SQLite integration tests for the `Count` object / `count*` connection extension functions
*/
@DisplayName("KotlinX | SQLite: Count")
class SQLiteCountIT {
@Test
@DisplayName("all counts all documents")
fun all() =
SQLiteDB().use(CountFunctions::all)
@Test
@DisplayName("byFields counts documents by a numeric value")
fun byFieldsNumeric() =
SQLiteDB().use(CountFunctions::byFieldsNumeric)
@Test
@DisplayName("byFields counts documents by a alphanumeric value")
fun byFieldsAlpha() =
SQLiteDB().use(CountFunctions::byFieldsAlpha)
@Test
@DisplayName("byContains fails")
fun byContainsMatch() {
assertThrows<DocumentException> { SQLiteDB().use(CountFunctions::byContainsMatch) }
}
@Test
@DisplayName("byJsonPath fails")
fun byJsonPathMatch() {
assertThrows<DocumentException> { SQLiteDB().use(CountFunctions::byJsonPathMatch) }
}
}

View File

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

View File

@@ -0,0 +1,35 @@
package solutions.bitbadger.documents.kotlinx.tests.integration
import solutions.bitbadger.documents.*
import solutions.bitbadger.documents.kotlinx.extensions.*
import solutions.bitbadger.documents.kotlinx.tests.TEST_TABLE
import solutions.bitbadger.documents.kotlinx.Results
import java.io.File
/**
* A wrapper for a throwaway SQLite database
*/
class SQLiteDB : ThrowawayDatabase {
private var dbName = ""
init {
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)), Results::toExists)
}

View File

@@ -0,0 +1,35 @@
package solutions.bitbadger.documents.kotlinx.tests.integration
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.assertThrows
import solutions.bitbadger.documents.DocumentException
import kotlin.test.Test
/**
* SQLite integration tests for the `Definition` object / `ensure*` connection extension functions
*/
@DisplayName("KotlinX | SQLite: Definition")
class SQLiteDefinitionIT {
@Test
@DisplayName("ensureTable creates table and index")
fun ensureTable() =
SQLiteDB().use(DefinitionFunctions::ensureTable)
@Test
@DisplayName("ensureFieldIndex creates an index")
fun ensureFieldIndex() =
SQLiteDB().use(DefinitionFunctions::ensureFieldIndex)
@Test
@DisplayName("ensureDocumentIndex fails for full index")
fun ensureDocumentIndexFull() {
assertThrows<DocumentException> { SQLiteDB().use(DefinitionFunctions::ensureDocumentIndexFull) }
}
@Test
@DisplayName("ensureDocumentIndex fails for optimized index")
fun ensureDocumentIndexOptimized() {
assertThrows<DocumentException> { SQLiteDB().use(DefinitionFunctions::ensureDocumentIndexOptimized) }
}
}

View File

@@ -0,0 +1,45 @@
package solutions.bitbadger.documents.kotlinx.tests.integration
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.assertThrows
import solutions.bitbadger.documents.DocumentException
import kotlin.test.Test
/**
* SQLite integration tests for the `Delete` object / `deleteBy*` connection extension functions
*/
@DisplayName("KotlinX | SQLite: Delete")
class SQLiteDeleteIT {
@Test
@DisplayName("byId deletes a matching ID")
fun byIdMatch() =
SQLiteDB().use(DeleteFunctions::byIdMatch)
@Test
@DisplayName("byId succeeds when no ID matches")
fun byIdNoMatch() =
SQLiteDB().use(DeleteFunctions::byIdNoMatch)
@Test
@DisplayName("byFields deletes matching documents")
fun byFieldsMatch() =
SQLiteDB().use(DeleteFunctions::byFieldsMatch)
@Test
@DisplayName("byFields succeeds when no documents match")
fun byFieldsNoMatch() =
SQLiteDB().use(DeleteFunctions::byFieldsNoMatch)
@Test
@DisplayName("byContains fails")
fun byContainsFails() {
assertThrows<DocumentException> { SQLiteDB().use(DeleteFunctions::byContainsMatch) }
}
@Test
@DisplayName("byJsonPath fails")
fun byJsonPathFails() {
assertThrows<DocumentException> { SQLiteDB().use(DeleteFunctions::byJsonPathMatch) }
}
}

View File

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

View File

@@ -0,0 +1,45 @@
package solutions.bitbadger.documents.kotlinx.tests.integration
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.assertThrows
import solutions.bitbadger.documents.DocumentException
import kotlin.test.Test
/**
* SQLite integration tests for the `Exists` object / `existsBy*` connection extension functions
*/
@DisplayName("KotlinX | SQLite: Exists")
class SQLiteExistsIT {
@Test
@DisplayName("byId returns true when a document matches the ID")
fun byIdMatch() =
SQLiteDB().use(ExistsFunctions::byIdMatch)
@Test
@DisplayName("byId returns false when no document matches the ID")
fun byIdNoMatch() =
SQLiteDB().use(ExistsFunctions::byIdNoMatch)
@Test
@DisplayName("byFields returns true when documents match")
fun byFieldsMatch() =
SQLiteDB().use(ExistsFunctions::byFieldsMatch)
@Test
@DisplayName("byFields returns false when no documents match")
fun byFieldsNoMatch() =
SQLiteDB().use(ExistsFunctions::byFieldsNoMatch)
@Test
@DisplayName("byContains fails")
fun byContainsFails() {
assertThrows<DocumentException> { SQLiteDB().use(ExistsFunctions::byContainsMatch) }
}
@Test
@DisplayName("byJsonPath fails")
fun byJsonPathFails() {
assertThrows<DocumentException> { SQLiteDB().use(ExistsFunctions::byJsonPathMatch) }
}
}

View File

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

View File

@@ -0,0 +1,45 @@
package solutions.bitbadger.documents.kotlinx.tests.integration
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.assertThrows
import solutions.bitbadger.documents.DocumentException
import kotlin.test.Test
/**
* SQLite integration tests for the `Patch` object / `patchBy*` connection extension functions
*/
@DisplayName("KotlinX | SQLite: Patch")
class SQLitePatchIT {
@Test
@DisplayName("byId patches an existing document")
fun byIdMatch() =
SQLiteDB().use(PatchFunctions::byIdMatch)
@Test
@DisplayName("byId succeeds for a non-existent document")
fun byIdNoMatch() =
SQLiteDB().use(PatchFunctions::byIdNoMatch)
@Test
@DisplayName("byFields patches matching document")
fun byFieldsMatch() =
SQLiteDB().use(PatchFunctions::byFieldsMatch)
@Test
@DisplayName("byFields succeeds when no documents match")
fun byFieldsNoMatch() =
SQLiteDB().use(PatchFunctions::byFieldsNoMatch)
@Test
@DisplayName("byContains fails")
fun byContainsFails() {
assertThrows<DocumentException> { SQLiteDB().use(PatchFunctions::byContainsMatch) }
}
@Test
@DisplayName("byJsonPath fails")
fun byJsonPathFails() {
assertThrows<DocumentException> { SQLiteDB().use(PatchFunctions::byJsonPathMatch) }
}
}

View File

@@ -0,0 +1,55 @@
package solutions.bitbadger.documents.kotlinx.tests.integration
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.assertThrows
import solutions.bitbadger.documents.DocumentException
import kotlin.test.Test
/**
* SQLite integration tests for the `RemoveFields` object / `removeFieldsBy*` connection extension functions
*/
@DisplayName("KotlinX | SQLite: RemoveFields")
class SQLiteRemoveFieldsIT {
@Test
@DisplayName("byId removes fields from an existing document")
fun byIdMatchFields() =
SQLiteDB().use(RemoveFieldsFunctions::byIdMatchFields)
@Test
@DisplayName("byId succeeds when fields do not exist on an existing document")
fun byIdMatchNoFields() =
SQLiteDB().use(RemoveFieldsFunctions::byIdMatchNoFields)
@Test
@DisplayName("byId succeeds when no document exists")
fun byIdNoMatch() =
SQLiteDB().use(RemoveFieldsFunctions::byIdNoMatch)
@Test
@DisplayName("byFields removes fields from matching documents")
fun byFieldsMatchFields() =
SQLiteDB().use(RemoveFieldsFunctions::byFieldsMatchFields)
@Test
@DisplayName("byFields succeeds when fields do not exist on matching documents")
fun byFieldsMatchNoFields() =
SQLiteDB().use(RemoveFieldsFunctions::byFieldsMatchNoFields)
@Test
@DisplayName("byFields succeeds when no matching documents exist")
fun byFieldsNoMatch() =
SQLiteDB().use(RemoveFieldsFunctions::byFieldsNoMatch)
@Test
@DisplayName("byContains fails")
fun byContainsFails() {
assertThrows<DocumentException> { SQLiteDB().use(RemoveFieldsFunctions::byContainsMatchFields) }
}
@Test
@DisplayName("byJsonPath fails")
fun byJsonPathFails() {
assertThrows<DocumentException> { SQLiteDB().use(RemoveFieldsFunctions::byJsonPathMatchFields) }
}
}

View File

@@ -0,0 +1,20 @@
package solutions.bitbadger.documents.kotlinx.tests.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
}