Initial Development #1

Merged
danieljsummers merged 88 commits from v1-rc into main 2025-04-16 01:29:20 +00:00
18 changed files with 348 additions and 100 deletions
Showing only changes of commit ca8c8e79de - Show all commits

View File

@ -21,7 +21,7 @@ object Configuration {
var idStringLength = 16 var idStringLength = 16
/** The derived dialect value from the connection string */ /** The derived dialect value from the connection string */
internal var dialectValue: Dialect? = null private var dialectValue: Dialect? = null
/** The connection string for the JDBC connection */ /** The connection string for the JDBC connection */
@JvmStatic @JvmStatic
@ -35,12 +35,13 @@ object Configuration {
* Retrieve a new connection to the configured database * Retrieve a new connection to the configured database
* *
* @return A new connection to the configured database * @return A new connection to the configured database
* @throws IllegalArgumentException If the connection string is not set before calling this * @throws DocumentException If the connection string is not set before calling this
*/ */
@Throws(DocumentException::class)
@JvmStatic @JvmStatic
fun dbConn(): Connection { fun dbConn(): Connection {
if (connectionString == null) { if (connectionString == null) {
throw IllegalArgumentException("Please provide a connection string before attempting data access") throw DocumentException("Please provide a connection string before attempting data access")
} }
return DriverManager.getConnection(connectionString) return DriverManager.getConnection(connectionString)
} }

View File

@ -1,11 +1,14 @@
package solutions.bitbadger.documents package solutions.bitbadger.documents
import kotlin.jvm.Throws
/** /**
* The SQL dialect to use when building queries * The SQL dialect to use when building queries
*/ */
enum class Dialect { enum class Dialect {
/** PostgreSQL */ /** PostgreSQL */
POSTGRESQL, POSTGRESQL,
/** SQLite */ /** SQLite */
SQLITE; SQLITE;
@ -18,9 +21,11 @@ enum class Dialect {
* @return The dialect for the connection string * @return The dialect for the connection string
* @throws DocumentException If the dialect cannot be determined * @throws DocumentException If the dialect cannot be determined
*/ */
@Throws(DocumentException::class)
@JvmStatic
fun deriveFromConnectionString(connectionString: String): Dialect = fun deriveFromConnectionString(connectionString: String): Dialect =
when { when {
connectionString.contains(":sqlite:") -> SQLITE connectionString.contains(":sqlite:") -> SQLITE
connectionString.contains(":postgresql:") -> POSTGRESQL connectionString.contains(":postgresql:") -> POSTGRESQL
else -> throw DocumentException("Cannot determine dialect from [$connectionString]") else -> throw DocumentException("Cannot determine dialect from [$connectionString]")
} }

View File

@ -6,4 +6,4 @@ package solutions.bitbadger.documents
* @param message The message for the exception * @param message The message for the exception
* @param cause The underlying exception (optional) * @param cause The underlying exception (optional)
*/ */
class DocumentException(message: String, cause: Throwable? = null) : Exception(message, cause) class DocumentException @JvmOverloads constructor(message: String, cause: Throwable? = null) : Exception(message, cause)

View File

@ -118,7 +118,6 @@ class Field<T> private constructor(
is ComparisonInArray<*> -> { is ComparisonInArray<*> -> {
val mkString = Configuration.dialect("append parameters for InArray") == Dialect.POSTGRESQL val mkString = Configuration.dialect("append parameters for InArray") == Dialect.POSTGRESQL
// TODO: I think this is actually Pair<String, Collection<*>>
comparison.value.second.forEachIndexed { index, item -> comparison.value.second.forEachIndexed { index, item ->
if (mkString) { if (mkString) {
existing.add(Parameter("${parameterName}_$index", ParameterType.STRING, "$item")) existing.add(Parameter("${parameterName}_$index", ParameterType.STRING, "$item"))

View File

@ -6,6 +6,7 @@ package solutions.bitbadger.documents
enum class FieldFormat { enum class FieldFormat {
/** Retrieve the field as a SQL value (string in PostgreSQL, best guess in SQLite */ /** Retrieve the field as a SQL value (string in PostgreSQL, best guess in SQLite */
SQL, SQL,
/** Retrieve the field as a JSON value */ /** Retrieve the field as a JSON value */
JSON JSON
} }

View File

@ -6,6 +6,7 @@ package solutions.bitbadger.documents
enum class FieldMatch(val sql: String) { enum class FieldMatch(val sql: String) {
/** Match any of the field criteria (`OR`) */ /** Match any of the field criteria (`OR`) */
ANY("OR"), ANY("OR"),
/** Match all the field criteria (`AND`) */ /** Match all the field criteria (`AND`) */
ALL("AND"), ALL("AND"),
} }

View File

@ -6,24 +6,34 @@ package solutions.bitbadger.documents
enum class Op(val sql: String) { enum class Op(val sql: String) {
/** Compare using equality */ /** Compare using equality */
EQUAL("="), EQUAL("="),
/** Compare using greater-than */ /** Compare using greater-than */
GREATER(">"), GREATER(">"),
/** Compare using greater-than-or-equal-to */ /** Compare using greater-than-or-equal-to */
GREATER_OR_EQUAL(">="), GREATER_OR_EQUAL(">="),
/** Compare using less-than */ /** Compare using less-than */
LESS("<"), LESS("<"),
/** Compare using less-than-or-equal-to */ /** Compare using less-than-or-equal-to */
LESS_OR_EQUAL("<="), LESS_OR_EQUAL("<="),
/** Compare using inequality */ /** Compare using inequality */
NOT_EQUAL("<>"), NOT_EQUAL("<>"),
/** Compare between two values */ /** Compare between two values */
BETWEEN("BETWEEN"), BETWEEN("BETWEEN"),
/** Compare existence in a list of values */ /** Compare existence in a list of values */
IN("IN"), IN("IN"),
/** Compare overlap between an array and a list of values */ /** Compare overlap between an array and a list of values */
IN_ARRAY("??|"), IN_ARRAY("??|"),
/** Compare existence */ /** Compare existence */
EXISTS("IS NOT NULL"), EXISTS("IS NOT NULL"),
/** Compare nonexistence */ /** Compare nonexistence */
NOT_EXISTS("IS NULL") NOT_EXISTS("IS NULL")
} }

View File

@ -2,6 +2,7 @@ package solutions.bitbadger.documents
import java.sql.PreparedStatement import java.sql.PreparedStatement
import java.sql.Types import java.sql.Types
import kotlin.jvm.Throws
/** /**
* A parameter to use for a query * A parameter to use for a query
@ -22,7 +23,9 @@ class Parameter<T>(val name: String, val type: ParameterType, val value: T) {
* *
* @param stmt The prepared statement to which this parameter should be bound * @param stmt The prepared statement to which this parameter should be bound
* @param index The index (1-based) to which the parameter should be bound * @param index The index (1-based) to which the parameter should be bound
* @throws DocumentException If a number parameter is given a non-numeric value
*/ */
@Throws(DocumentException::class)
fun bind(stmt: PreparedStatement, index: Int) { fun bind(stmt: PreparedStatement, index: Int) {
when (type) { when (type) {
ParameterType.NUMBER -> { ParameterType.NUMBER -> {
@ -33,7 +36,7 @@ class Parameter<T>(val name: String, val type: ParameterType, val value: T) {
is Int -> stmt.setInt(index, value) is Int -> stmt.setInt(index, value)
is Long -> stmt.setLong(index, value) is Long -> stmt.setLong(index, value)
else -> throw DocumentException( else -> throw DocumentException(
"Number parameter must be Byte, Short, Int, or Long (${value!!::class.simpleName})" "Number parameter must be Byte, Short, Int, or Long (${value::class.simpleName})"
) )
} }
} }

View File

@ -6,6 +6,7 @@ import solutions.bitbadger.documents.*
import solutions.bitbadger.documents.jvm.* import solutions.bitbadger.documents.jvm.*
import java.sql.Connection import java.sql.Connection
import java.sql.ResultSet import java.sql.ResultSet
import kotlin.jvm.Throws
// ~~~ CUSTOM QUERIES ~~~ // ~~~ CUSTOM QUERIES ~~~
@ -17,7 +18,9 @@ import java.sql.ResultSet
* @param clazz The class of the document to be returned * @param clazz The class of the document to be returned
* @param mapFunc The mapping function between the document and the domain item * @param mapFunc The mapping function between the document and the domain item
* @return A list of results for the given query * @return A list of results for the given query
* @throws DocumentException If parameters are invalid
*/ */
@Throws(DocumentException::class)
fun <TDoc> Connection.customList( fun <TDoc> Connection.customList(
query: String, query: String,
parameters: Collection<Parameter<*>> = listOf(), parameters: Collection<Parameter<*>> = listOf(),
@ -34,7 +37,9 @@ fun <TDoc> Connection.customList(
* @param clazz The class of the document to be returned * @param clazz The class of the document to be returned
* @param mapFunc The mapping function between the document and the domain item * @param mapFunc The mapping function between the document and the domain item
* @return The document if one matches the query, `null` otherwise * @return The document if one matches the query, `null` otherwise
* @throws DocumentException If parameters are invalid
*/ */
@Throws(DocumentException::class)
fun <TDoc> Connection.customSingle( fun <TDoc> Connection.customSingle(
query: String, query: String,
parameters: Collection<Parameter<*>> = listOf(), parameters: Collection<Parameter<*>> = listOf(),
@ -48,7 +53,9 @@ fun <TDoc> Connection.customSingle(
* *
* @param query The query to retrieve the results * @param query The query to retrieve the results
* @param parameters Parameters to use for the query * @param parameters Parameters to use for the query
* @throws DocumentException If parameters are invalid
*/ */
@Throws(DocumentException::class)
fun Connection.customNonQuery(query: String, parameters: Collection<Parameter<*>> = listOf()) = fun Connection.customNonQuery(query: String, parameters: Collection<Parameter<*>> = listOf()) =
Custom.nonQuery(query, parameters, this) Custom.nonQuery(query, parameters, this)
@ -60,7 +67,9 @@ fun Connection.customNonQuery(query: String, parameters: Collection<Parameter<*>
* @param clazz The class of the document to be returned * @param clazz The class of the document to be returned
* @param mapFunc The mapping function between the document and the domain item * @param mapFunc The mapping function between the document and the domain item
* @return The scalar value from the query * @return The scalar value from the query
* @throws DocumentException If parameters are invalid
*/ */
@Throws(DocumentException::class)
fun <T : Any> Connection.customScalar( fun <T : Any> Connection.customScalar(
query: String, query: String,
parameters: Collection<Parameter<*>> = listOf(), parameters: Collection<Parameter<*>> = listOf(),
@ -109,7 +118,7 @@ fun Connection.ensureDocumentIndex(tableName: String, indexType: DocumentIndex)
* @param document The document to be inserted * @param document The document to be inserted
*/ */
fun <TDoc> Connection.insert(tableName: String, document: TDoc) = fun <TDoc> Connection.insert(tableName: String, document: TDoc) =
Document.insert<TDoc>(tableName, document, this) Document.insert(tableName, document, this)
/** /**
* Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert") * Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
@ -137,7 +146,9 @@ fun <TKey, TDoc> Connection.update(tableName: String, docId: TKey, document: TDo
* *
* @param tableName The name of the table in which documents should be counted * @param tableName The name of the table in which documents should be counted
* @return A count of the documents in the table * @return A count of the documents in the table
* @throws DocumentException If any dependent process does
*/ */
@Throws(DocumentException::class)
fun Connection.countAll(tableName: String) = fun Connection.countAll(tableName: String) =
Count.all(tableName, this) Count.all(tableName, this)
@ -148,7 +159,9 @@ fun Connection.countAll(tableName: String) =
* @param fields The fields which should be compared * @param fields The fields which should be compared
* @param howMatched How the fields should be matched * @param howMatched How the fields should be matched
* @return A count of the matching documents in the table * @return A count of the matching documents in the table
* @throws DocumentException If the dialect has not been configured
*/ */
@Throws(DocumentException::class)
@JvmOverloads @JvmOverloads
fun Connection.countByFields(tableName: String, fields: Collection<Field<*>>, howMatched: FieldMatch? = null) = fun Connection.countByFields(tableName: String, fields: Collection<Field<*>>, howMatched: FieldMatch? = null) =
Count.byFields(tableName, fields, howMatched, this) Count.byFields(tableName, fields, howMatched, this)

View File

@ -4,6 +4,7 @@ import solutions.bitbadger.documents.*
import solutions.bitbadger.documents.query.CountQuery import solutions.bitbadger.documents.query.CountQuery
import solutions.bitbadger.documents.extensions.customScalar import solutions.bitbadger.documents.extensions.customScalar
import java.sql.Connection import java.sql.Connection
import kotlin.jvm.Throws
/** /**
* Functions to count documents * Functions to count documents
@ -16,7 +17,9 @@ object Count {
* @param tableName The name of the table in which documents should be counted * @param tableName The name of the table in which documents should be counted
* @param conn The connection over which documents should be counted * @param conn The connection over which documents should be counted
* @return A count of the documents in the table * @return A count of the documents in the table
* @throws DocumentException If any dependent process does
*/ */
@Throws(DocumentException::class)
@JvmStatic @JvmStatic
fun all(tableName: String, conn: Connection) = fun all(tableName: String, conn: Connection) =
conn.customScalar(CountQuery.all(tableName), listOf(), Long::class.java, Results::toCount) conn.customScalar(CountQuery.all(tableName), listOf(), Long::class.java, Results::toCount)
@ -26,7 +29,9 @@ object Count {
* *
* @param tableName The name of the table in which documents should be counted * @param tableName The name of the table in which documents should be counted
* @return A count of the documents in the table * @return A count of the documents in the table
* @throws DocumentException If no connection string has been set
*/ */
@Throws(DocumentException::class)
@JvmStatic @JvmStatic
fun all(tableName: String) = fun all(tableName: String) =
Configuration.dbConn().use { all(tableName, it) } Configuration.dbConn().use { all(tableName, it) }
@ -39,7 +44,9 @@ object Count {
* @param howMatched How the fields should be matched * @param howMatched How the fields should be matched
* @param conn The connection on which the deletion should be executed * @param conn The connection on which the deletion should be executed
* @return A count of the matching documents in the table * @return A count of the matching documents in the table
* @throws DocumentException If no dialect has been configured
*/ */
@Throws(DocumentException::class)
@JvmStatic @JvmStatic
@JvmOverloads @JvmOverloads
fun byFields( fun byFields(
@ -64,7 +71,9 @@ object Count {
* @param fields The fields which should be compared * @param fields The fields which should be compared
* @param howMatched How the fields should be matched * @param howMatched How the fields should be matched
* @return A count of the matching documents in the table * @return A count of the matching documents in the table
* @throws DocumentException If no connection string has been set
*/ */
@Throws(DocumentException::class)
@JvmStatic @JvmStatic
@JvmOverloads @JvmOverloads
fun byFields(tableName: String, fields: Collection<Field<*>>, howMatched: FieldMatch? = null) = fun byFields(tableName: String, fields: Collection<Field<*>>, howMatched: FieldMatch? = null) =
@ -79,6 +88,7 @@ object Count {
* @return A count of the matching documents in the table * @return A count of the matching documents in the table
* @throws DocumentException If called on a SQLite connection * @throws DocumentException If called on a SQLite connection
*/ */
@Throws(DocumentException::class)
@JvmStatic @JvmStatic
fun <TContains> byContains(tableName: String, criteria: TContains, conn: Connection) = fun <TContains> byContains(tableName: String, criteria: TContains, conn: Connection) =
conn.customScalar( conn.customScalar(
@ -94,8 +104,9 @@ object Count {
* @param tableName The name of the table in which documents should be counted * @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 criteria The object for which JSON containment should be checked
* @return A count of the matching documents in the table * @return A count of the matching documents in the table
* @throws DocumentException If called on a SQLite connection * @throws DocumentException If no connection string has been set, or if called on a SQLite connection
*/ */
@Throws(DocumentException::class)
@JvmStatic @JvmStatic
fun <TContains> byContains(tableName: String, criteria: TContains) = fun <TContains> byContains(tableName: String, criteria: TContains) =
Configuration.dbConn().use { byContains(tableName, criteria, it) } Configuration.dbConn().use { byContains(tableName, criteria, it) }
@ -109,6 +120,7 @@ object Count {
* @return A count of the matching documents in the table * @return A count of the matching documents in the table
* @throws DocumentException If called on a SQLite connection * @throws DocumentException If called on a SQLite connection
*/ */
@Throws(DocumentException::class)
@JvmStatic @JvmStatic
fun byJsonPath(tableName: String, path: String, conn: Connection) = fun byJsonPath(tableName: String, path: String, conn: Connection) =
conn.customScalar( conn.customScalar(
@ -124,8 +136,9 @@ object Count {
* @param tableName The name of the table in which documents should be counted * @param tableName The name of the table in which documents should be counted
* @param path The JSON path comparison to match * @param path The JSON path comparison to match
* @return A count of the matching documents in the table * @return A count of the matching documents in the table
* @throws DocumentException If called on a SQLite connection * @throws DocumentException If no connection string has been set, or if called on a SQLite connection
*/ */
@Throws(DocumentException::class)
@JvmStatic @JvmStatic
fun byJsonPath(tableName: String, path: String) = fun byJsonPath(tableName: String, path: String) =
Configuration.dbConn().use { byJsonPath(tableName, path, it) } Configuration.dbConn().use { byJsonPath(tableName, path, it) }

View File

@ -1,9 +1,11 @@
package solutions.bitbadger.documents.jvm package solutions.bitbadger.documents.jvm
import solutions.bitbadger.documents.Configuration import solutions.bitbadger.documents.Configuration
import solutions.bitbadger.documents.DocumentException
import solutions.bitbadger.documents.Parameter import solutions.bitbadger.documents.Parameter
import java.sql.Connection import java.sql.Connection
import java.sql.ResultSet import java.sql.ResultSet
import kotlin.jvm.Throws
object Custom { object Custom {
@ -16,7 +18,9 @@ object Custom {
* @param conn The connection over which the query should be executed * @param conn The connection over which the query should be executed
* @param mapFunc The mapping function between the document and the domain item * @param mapFunc The mapping function between the document and the domain item
* @return A list of results for the given query * @return A list of results for the given query
* @throws DocumentException If parameters are invalid
*/ */
@Throws(DocumentException::class)
@JvmStatic @JvmStatic
fun <TDoc> list( fun <TDoc> list(
query: String, query: String,
@ -34,7 +38,9 @@ object Custom {
* @param clazz The class of the document to be returned * @param clazz The class of the document to be returned
* @param mapFunc The mapping function between the document and the domain item * @param mapFunc The mapping function between the document and the domain item
* @return A list of results for the given query * @return A list of results for the given query
* @throws DocumentException If no connection string has been set, or if parameters are invalid
*/ */
@Throws(DocumentException::class)
@JvmStatic @JvmStatic
fun <TDoc> list( fun <TDoc> list(
query: String, query: String,
@ -52,7 +58,9 @@ object Custom {
* @param conn The connection over which the query should be executed * @param conn The connection over which the query should be executed
* @param mapFunc The mapping function between the document and the domain item * @param mapFunc The mapping function between the document and the domain item
* @return The document if one matches the query, `null` otherwise * @return The document if one matches the query, `null` otherwise
* @throws DocumentException If parameters are invalid
*/ */
@Throws(DocumentException::class)
@JvmStatic @JvmStatic
fun <TDoc> single( fun <TDoc> single(
query: String, query: String,
@ -70,7 +78,9 @@ object Custom {
* @param clazz The class of the document to be returned * @param clazz The class of the document to be returned
* @param mapFunc The mapping function between the document and the domain item * @param mapFunc The mapping function between the document and the domain item
* @return The document if one matches the query, `null` otherwise * @return The document if one matches the query, `null` otherwise
* @throws DocumentException If no connection string has been set, or if parameters are invalid
*/ */
@Throws(DocumentException::class)
@JvmStatic @JvmStatic
fun <TDoc> single( fun <TDoc> single(
query: String, query: String,
@ -85,7 +95,9 @@ object Custom {
* @param query The query to retrieve the results * @param query The query to retrieve the results
* @param conn The connection over which the query should be executed * @param conn The connection over which the query should be executed
* @param parameters Parameters to use for the query * @param parameters Parameters to use for the query
* @throws DocumentException If parameters are invalid
*/ */
@Throws(DocumentException::class)
@JvmStatic @JvmStatic
fun nonQuery(query: String, parameters: Collection<Parameter<*>> = listOf(), conn: Connection) { fun nonQuery(query: String, parameters: Collection<Parameter<*>> = listOf(), conn: Connection) {
Parameters.apply(conn, query, parameters).use { it.executeUpdate() } Parameters.apply(conn, query, parameters).use { it.executeUpdate() }
@ -96,7 +108,9 @@ object Custom {
* *
* @param query The query to retrieve the results * @param query The query to retrieve the results
* @param parameters Parameters to use for the query * @param parameters Parameters to use for the query
* @throws DocumentException If no connection string has been set, or if parameters are invalid
*/ */
@Throws(DocumentException::class)
@JvmStatic @JvmStatic
@JvmOverloads @JvmOverloads
fun nonQuery(query: String, parameters: Collection<Parameter<*>> = listOf()) = fun nonQuery(query: String, parameters: Collection<Parameter<*>> = listOf()) =
@ -110,7 +124,9 @@ object Custom {
* @param conn The connection over which the query should be executed * @param conn The connection over which the query should be executed
* @param mapFunc The mapping function between the document and the domain item * @param mapFunc The mapping function between the document and the domain item
* @return The scalar value from the query * @return The scalar value from the query
* @throws DocumentException If parameters are invalid
*/ */
@Throws(DocumentException::class)
@JvmStatic @JvmStatic
fun <T : Any> scalar( fun <T : Any> scalar(
query: String, query: String,
@ -132,7 +148,10 @@ object Custom {
* @param parameters Parameters to use for the query * @param parameters Parameters to use for the query
* @param mapFunc The mapping function between the document and the domain item * @param mapFunc The mapping function between the document and the domain item
* @return The scalar value from the query * @return The scalar value from the query
* @throws DocumentException If no connection string has been set, or if parameters are invalid
*/ */
@Throws(DocumentException::class)
@JvmStatic
fun <T : Any> scalar( fun <T : Any> scalar(
query: String, query: String,
parameters: Collection<Parameter<*>> = listOf(), parameters: Collection<Parameter<*>> = listOf(),

View File

@ -6,6 +6,7 @@ import solutions.bitbadger.documents.DocumentIndex
import solutions.bitbadger.documents.extensions.customNonQuery import solutions.bitbadger.documents.extensions.customNonQuery
import solutions.bitbadger.documents.query.DefinitionQuery import solutions.bitbadger.documents.query.DefinitionQuery
import java.sql.Connection import java.sql.Connection
import kotlin.jvm.Throws
/** /**
* Functions to define tables and indexes * Functions to define tables and indexes
@ -17,7 +18,9 @@ object Definition {
* *
* @param tableName The table whose existence should be ensured (may include schema) * @param tableName The table whose existence should be ensured (may include schema)
* @param conn The connection on which the query should be executed * @param conn The connection on which the query should be executed
* @throws DocumentException If the dialect is not configured
*/ */
@Throws(DocumentException::class)
@JvmStatic @JvmStatic
fun ensureTable(tableName: String, conn: Connection) = fun ensureTable(tableName: String, conn: Connection) =
Configuration.dialect("ensure $tableName exists").let { Configuration.dialect("ensure $tableName exists").let {
@ -29,7 +32,9 @@ object Definition {
* Create a document table if necessary * Create a document table if necessary
* *
* @param tableName The table whose existence should be ensured (may include schema) * @param tableName The table whose existence should be ensured (may include schema)
* @throws DocumentException If no connection string has been set
*/ */
@Throws(DocumentException::class)
@JvmStatic @JvmStatic
fun ensureTable(tableName: String) = fun ensureTable(tableName: String) =
Configuration.dbConn().use { ensureTable(tableName, it) } Configuration.dbConn().use { ensureTable(tableName, it) }
@ -41,7 +46,9 @@ object Definition {
* @param indexName The name of the index to create * @param indexName The name of the index to create
* @param fields One or more fields to be indexed * @param fields One or more fields to be indexed
* @param conn The connection on which the query should be executed * @param conn The connection on which the query should be executed
* @throws DocumentException If any dependent process does
*/ */
@Throws(DocumentException::class)
@JvmStatic @JvmStatic
fun ensureFieldIndex(tableName: String, indexName: String, fields: Collection<String>, conn: Connection) = fun ensureFieldIndex(tableName: String, indexName: String, fields: Collection<String>, conn: Connection) =
conn.customNonQuery(DefinitionQuery.ensureIndexOn(tableName, indexName, fields)) conn.customNonQuery(DefinitionQuery.ensureIndexOn(tableName, indexName, fields))
@ -52,7 +59,9 @@ object Definition {
* @param tableName The table to be indexed (may include schema) * @param tableName The table to be indexed (may include schema)
* @param indexName The name of the index to create * @param indexName The name of the index to create
* @param fields One or more fields to be indexed * @param fields One or more fields to be indexed
* @throws DocumentException If no connection string has been set, or if any dependent process does
*/ */
@Throws(DocumentException::class)
@JvmStatic @JvmStatic
fun ensureFieldIndex(tableName: String, indexName: String, fields: Collection<String>) = fun ensureFieldIndex(tableName: String, indexName: String, fields: Collection<String>) =
Configuration.dbConn().use { ensureFieldIndex(tableName, indexName, fields, it) } Configuration.dbConn().use { ensureFieldIndex(tableName, indexName, fields, it) }
@ -75,7 +84,7 @@ object Definition {
* *
* @param tableName The table to be indexed (may include schema) * @param tableName The table to be indexed (may include schema)
* @param indexType The type of index to ensure * @param indexType The type of index to ensure
* @throws DocumentException If called on a SQLite connection * @throws DocumentException If no connection string has been set, or if called on a SQLite connection
*/ */
@Throws(DocumentException::class) @Throws(DocumentException::class)
@JvmStatic @JvmStatic

View File

@ -17,18 +17,18 @@ import static solutions.bitbadger.documents.support.TypesKt.TEST_TABLE;
*/ */
final public class CountFunctions { final public class CountFunctions {
public static void all(ThrowawayDatabase db) { public static void all(ThrowawayDatabase db) throws DocumentException {
JsonDocument.load(db); JsonDocument.load(db);
assertEquals(5L, countAll(db.getConn(), TEST_TABLE), "There should have been 5 documents in the table"); assertEquals(5L, countAll(db.getConn(), TEST_TABLE), "There should have been 5 documents in the table");
} }
public static void byFieldsNumeric(ThrowawayDatabase db) { public static void byFieldsNumeric(ThrowawayDatabase db) throws DocumentException {
JsonDocument.load(db); JsonDocument.load(db);
assertEquals(3L, countByFields(db.getConn(), TEST_TABLE, List.of(Field.between("numValue", 10, 20))), assertEquals(3L, countByFields(db.getConn(), TEST_TABLE, List.of(Field.between("numValue", 10, 20))),
"There should have been 3 matching documents"); "There should have been 3 matching documents");
} }
public static void byFieldsAlpha(ThrowawayDatabase db) { public static void byFieldsAlpha(ThrowawayDatabase db) throws DocumentException {
JsonDocument.load(db); JsonDocument.load(db);
assertEquals(1L, countByFields(db.getConn(), TEST_TABLE, List.of(Field.between("value", "aardvark", "apple"))), assertEquals(1L, countByFields(db.getConn(), TEST_TABLE, List.of(Field.between("value", "aardvark", "apple"))),
"There should have been 1 matching document"); "There should have been 1 matching document");

View File

@ -14,7 +14,7 @@ public class CountIT {
@Test @Test
@DisplayName("all counts all documents") @DisplayName("all counts all documents")
public void all() { public void all() throws DocumentException {
try (PgDB db = new PgDB()) { try (PgDB db = new PgDB()) {
CountFunctions.all(db); CountFunctions.all(db);
} }
@ -22,7 +22,7 @@ public class CountIT {
@Test @Test
@DisplayName("byFields counts documents by a numeric value") @DisplayName("byFields counts documents by a numeric value")
public void byFieldsNumeric() { public void byFieldsNumeric() throws DocumentException {
try (PgDB db = new PgDB()) { try (PgDB db = new PgDB()) {
CountFunctions.byFieldsNumeric(db); CountFunctions.byFieldsNumeric(db);
} }
@ -30,7 +30,7 @@ public class CountIT {
@Test @Test
@DisplayName("byFields counts documents by a alphanumeric value") @DisplayName("byFields counts documents by a alphanumeric value")
public void byFieldsAlpha() { public void byFieldsAlpha() throws DocumentException {
try (PgDB db = new PgDB()) { try (PgDB db = new PgDB()) {
CountFunctions.byFieldsAlpha(db); CountFunctions.byFieldsAlpha(db);
} }

View File

@ -16,7 +16,7 @@ public class CountIT {
@Test @Test
@DisplayName("all counts all documents") @DisplayName("all counts all documents")
public void all() { public void all() throws DocumentException {
try (SQLiteDB db = new SQLiteDB()) { try (SQLiteDB db = new SQLiteDB()) {
CountFunctions.all(db); CountFunctions.all(db);
} }
@ -24,7 +24,7 @@ public class CountIT {
@Test @Test
@DisplayName("byFields counts documents by a numeric value") @DisplayName("byFields counts documents by a numeric value")
public void byFieldsNumeric() { public void byFieldsNumeric() throws DocumentException {
try (SQLiteDB db = new SQLiteDB()) { try (SQLiteDB db = new SQLiteDB()) {
CountFunctions.byFieldsNumeric(db); CountFunctions.byFieldsNumeric(db);
} }
@ -32,7 +32,7 @@ public class CountIT {
@Test @Test
@DisplayName("byFields counts documents by a alphanumeric value") @DisplayName("byFields counts documents by a alphanumeric value")
public void byFieldsAlpha() { public void byFieldsAlpha() throws DocumentException {
try (SQLiteDB db = new SQLiteDB()) { try (SQLiteDB db = new SQLiteDB()) {
CountFunctions.byFieldsAlpha(db); CountFunctions.byFieldsAlpha(db);
} }

View File

@ -0,0 +1,172 @@
package solutions.bitbadger.documents.java.query;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import solutions.bitbadger.documents.DocumentException;
import solutions.bitbadger.documents.Field;
import solutions.bitbadger.documents.FieldMatch;
import solutions.bitbadger.documents.query.Where;
import solutions.bitbadger.documents.support.ForceDialect;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
/**
* Unit tests for the `Where` object
*/
@DisplayName("JVM | Java | Query | Where")
final public class WhereTest {
/**
* Clear the connection string (resets Dialect)
*/
@AfterEach
public void cleanUp() {
ForceDialect.none();
}
@Test
@DisplayName("byFields is blank when given no fields")
public void byFieldsBlankIfEmpty() throws DocumentException {
assertEquals("", Where.byFields(List.of()));
}
@Test
@DisplayName("byFields generates one numeric field | PostgreSQL")
public void byFieldsOneFieldPostgres() throws DocumentException {
ForceDialect.postgres();
assertEquals("(data->>'it')::numeric = :that", Where.byFields(List.of(Field.equal("it", 9, ":that"))));
}
@Test
@DisplayName("byFields generates one alphanumeric field | PostgreSQL")
public void byFieldsOneAlphaFieldPostgres() throws DocumentException {
ForceDialect.postgres();
assertEquals("data->>'it' = :that", Where.byFields(List.of(Field.equal("it", "", ":that"))));
}
@Test
@DisplayName("byFields generates one field | SQLite")
public void byFieldsOneFieldSQLite() throws DocumentException {
ForceDialect.sqlite();
assertEquals("data->>'it' = :that", Where.byFields(List.of(Field.equal("it", "", ":that"))));
}
@Test
@DisplayName("byFields generates multiple fields w/ default match | PostgreSQL")
public void byFieldsMultipleDefaultPostgres() throws DocumentException {
ForceDialect.postgres();
assertEquals("data->>'1' = :one AND (data->>'2')::numeric = :two AND data->>'3' = :three",
Where.byFields(List.of(
Field.equal("1", "", ":one"), Field.equal("2", 0L, ":two"), Field.equal("3", "", ":three"))));
}
@Test
@DisplayName("byFields generates multiple fields w/ default match | SQLite")
public void byFieldsMultipleDefaultSQLite() throws DocumentException {
ForceDialect.sqlite();
assertEquals("data->>'1' = :one AND data->>'2' = :two AND data->>'3' = :three",
Where.byFields(List.of(
Field.equal("1", "", ":one"), Field.equal("2", 0L, ":two"), Field.equal("3", "", ":three"))));
}
@Test
@DisplayName("byFields generates multiple fields w/ ANY match | PostgreSQL")
public void byFieldsMultipleAnyPostgres() throws DocumentException {
ForceDialect.postgres();
assertEquals("data->>'1' = :one OR (data->>'2')::numeric = :two OR data->>'3' = :three",
Where.byFields(List.of(
Field.equal("1", "", ":one"), Field.equal("2", 0L, ":two"), Field.equal("3", "", ":three")),
FieldMatch.ANY));
}
@Test
@DisplayName("byFields generates multiple fields w/ ANY match | SQLite")
public void byFieldsMultipleAnySQLite() throws DocumentException {
ForceDialect.sqlite();
assertEquals("data->>'1' = :one OR data->>'2' = :two OR data->>'3' = :three",
Where.byFields(List.of(
Field.equal("1", "", ":one"), Field.equal("2", 0L, ":two"), Field.equal("3", "", ":three")),
FieldMatch.ANY));
}
@Test
@DisplayName("byId generates defaults for alphanumeric key | PostgreSQL")
public void byIdDefaultAlphaPostgres() throws DocumentException {
ForceDialect.postgres();
assertEquals("data->>'id' = :id", Where.byId(":id", ""));
}
@Test
@DisplayName("byId generates defaults for numeric key | PostgreSQL")
public void byIdDefaultNumericPostgres() throws DocumentException {
ForceDialect.postgres();
assertEquals("(data->>'id')::numeric = :id", Where.byId(":id", 5));
}
@Test
@DisplayName("byId generates defaults | SQLite")
public void byIdDefaultSQLite() throws DocumentException {
ForceDialect.sqlite();
assertEquals("data->>'id' = :id", Where.byId(":id", ""));
}
@Test
@DisplayName("byId generates named ID | PostgreSQL")
public void byIdDefaultNamedPostgres() throws DocumentException {
ForceDialect.postgres();
assertEquals("data->>'id' = :key", Where.byId(":key"));
}
@Test
@DisplayName("byId generates named ID | SQLite")
public void byIdDefaultNamedSQLite() throws DocumentException {
ForceDialect.sqlite();
assertEquals("data->>'id' = :key", Where.byId(":key"));
}
@Test
@DisplayName("jsonContains generates defaults | PostgreSQL")
public void jsonContainsDefaultPostgres() throws DocumentException {
ForceDialect.postgres();
assertEquals("data @> :criteria", Where.jsonContains());
}
@Test
@DisplayName("jsonContains generates named parameter | PostgreSQL")
public void jsonContainsNamedPostgres() throws DocumentException {
ForceDialect.postgres();
assertEquals("data @> :it", Where.jsonContains(":it"));
}
@Test
@DisplayName("jsonContains fails | SQLite")
public void jsonContainsFailsSQLite() {
ForceDialect.sqlite();
assertThrows(DocumentException.class, Where::jsonContains);
}
@Test
@DisplayName("jsonPathMatches generates defaults | PostgreSQL")
public void jsonPathMatchDefaultPostgres() throws DocumentException {
ForceDialect.postgres();
assertEquals("jsonb_path_exists(data, :path::jsonpath)", Where.jsonPathMatches());
}
@Test
@DisplayName("jsonPathMatches generates named parameter | PostgreSQL")
public void jsonPathMatchNamedPostgres() throws DocumentException {
ForceDialect.postgres();
assertEquals("jsonb_path_exists(data, :jp::jsonpath)", Where.jsonPathMatches(":jp"));
}
@Test
@DisplayName("jsonPathMatches fails | SQLite")
public void jsonPathFailsSQLite() {
ForceDialect.sqlite();
assertThrows(DocumentException.class, Where::jsonPathMatches);
}
}

View File

@ -4,6 +4,7 @@ import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.assertThrows
import solutions.bitbadger.documents.support.ForceDialect
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertNotSame import kotlin.test.assertNotSame
import kotlin.test.assertNull import kotlin.test.assertNull
@ -19,7 +20,7 @@ class FieldTest {
*/ */
@AfterEach @AfterEach
fun cleanUp() { fun cleanUp() {
Configuration.dialectValue = null ForceDialect.none()
} }
// ~~~ INSTANCE METHODS ~~~ // ~~~ INSTANCE METHODS ~~~
@ -120,178 +121,178 @@ class FieldTest {
"Path not correct") "Path not correct")
@Test @Test
@DisplayName("toWhere generates for exists w/o qualifier (PostgreSQL)") @DisplayName("toWhere generates for exists w/o qualifier | PostgreSQL")
fun toWhereExistsNoQualPostgres() { fun toWhereExistsNoQualPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL ForceDialect.postgres()
assertEquals("data->>'that_field' IS NOT NULL", Field.exists("that_field").toWhere(), assertEquals("data->>'that_field' IS NOT NULL", Field.exists("that_field").toWhere(),
"Field WHERE clause not generated correctly") "Field WHERE clause not generated correctly")
} }
@Test @Test
@DisplayName("toWhere generates for exists w/o qualifier (SQLite)") @DisplayName("toWhere generates for exists w/o qualifier | SQLite")
fun toWhereExistsNoQualSQLite() { fun toWhereExistsNoQualSQLite() {
Configuration.dialectValue = Dialect.SQLITE ForceDialect.sqlite()
assertEquals("data->>'that_field' IS NOT NULL", Field.exists("that_field").toWhere(), assertEquals("data->>'that_field' IS NOT NULL", Field.exists("that_field").toWhere(),
"Field WHERE clause not generated correctly") "Field WHERE clause not generated correctly")
} }
@Test @Test
@DisplayName("toWhere generates for not-exists w/o qualifier (PostgreSQL)") @DisplayName("toWhere generates for not-exists w/o qualifier | PostgreSQL")
fun toWhereNotExistsNoQualPostgres() { fun toWhereNotExistsNoQualPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL ForceDialect.postgres()
assertEquals("data->>'a_field' IS NULL", Field.notExists("a_field").toWhere(), assertEquals("data->>'a_field' IS NULL", Field.notExists("a_field").toWhere(),
"Field WHERE clause not generated correctly") "Field WHERE clause not generated correctly")
} }
@Test @Test
@DisplayName("toWhere generates for not-exists w/o qualifier (SQLite)") @DisplayName("toWhere generates for not-exists w/o qualifier | SQLite")
fun toWhereNotExistsNoQualSQLite() { fun toWhereNotExistsNoQualSQLite() {
Configuration.dialectValue = Dialect.SQLITE ForceDialect.sqlite()
assertEquals("data->>'a_field' IS NULL", Field.notExists("a_field").toWhere(), assertEquals("data->>'a_field' IS NULL", Field.notExists("a_field").toWhere(),
"Field WHERE clause not generated correctly") "Field WHERE clause not generated correctly")
} }
@Test @Test
@DisplayName("toWhere generates for BETWEEN w/o qualifier, numeric range (PostgreSQL)") @DisplayName("toWhere generates for BETWEEN w/o qualifier, numeric range | PostgreSQL")
fun toWhereBetweenNoQualNumericPostgres() { fun toWhereBetweenNoQualNumericPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL ForceDialect.postgres()
assertEquals("(data->>'age')::numeric BETWEEN @agemin AND @agemax", assertEquals("(data->>'age')::numeric BETWEEN @agemin AND @agemax",
Field.between("age", 13, 17, "@age").toWhere(), "Field WHERE clause not generated correctly") Field.between("age", 13, 17, "@age").toWhere(), "Field WHERE clause not generated correctly")
} }
@Test @Test
@DisplayName("toWhere generates for BETWEEN w/o qualifier, alphanumeric range (PostgreSQL)") @DisplayName("toWhere generates for BETWEEN w/o qualifier, alphanumeric range | PostgreSQL")
fun toWhereBetweenNoQualAlphaPostgres() { fun toWhereBetweenNoQualAlphaPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL ForceDialect.postgres()
assertEquals("data->>'city' BETWEEN :citymin AND :citymax", assertEquals("data->>'city' BETWEEN :citymin AND :citymax",
Field.between("city", "Atlanta", "Chicago", ":city").toWhere(), Field.between("city", "Atlanta", "Chicago", ":city").toWhere(),
"Field WHERE clause not generated correctly") "Field WHERE clause not generated correctly")
} }
@Test @Test
@DisplayName("toWhere generates for BETWEEN w/o qualifier (SQLite)") @DisplayName("toWhere generates for BETWEEN w/o qualifier | SQLite")
fun toWhereBetweenNoQualSQLite() { fun toWhereBetweenNoQualSQLite() {
Configuration.dialectValue = Dialect.SQLITE ForceDialect.sqlite()
assertEquals("data->>'age' BETWEEN @agemin AND @agemax", Field.between("age", 13, 17, "@age").toWhere(), assertEquals("data->>'age' BETWEEN @agemin AND @agemax", Field.between("age", 13, 17, "@age").toWhere(),
"Field WHERE clause not generated correctly") "Field WHERE clause not generated correctly")
} }
@Test @Test
@DisplayName("toWhere generates for BETWEEN w/ qualifier, numeric range (PostgreSQL)") @DisplayName("toWhere generates for BETWEEN w/ qualifier, numeric range | PostgreSQL")
fun toWhereBetweenQualNumericPostgres() { fun toWhereBetweenQualNumericPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL ForceDialect.postgres()
assertEquals("(test.data->>'age')::numeric BETWEEN @agemin AND @agemax", assertEquals("(test.data->>'age')::numeric BETWEEN @agemin AND @agemax",
Field.between("age", 13, 17, "@age").withQualifier("test").toWhere(), Field.between("age", 13, 17, "@age").withQualifier("test").toWhere(),
"Field WHERE clause not generated correctly") "Field WHERE clause not generated correctly")
} }
@Test @Test
@DisplayName("toWhere generates for BETWEEN w/ qualifier, alphanumeric range (PostgreSQL)") @DisplayName("toWhere generates for BETWEEN w/ qualifier, alphanumeric range | PostgreSQL")
fun toWhereBetweenQualAlphaPostgres() { fun toWhereBetweenQualAlphaPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL ForceDialect.postgres()
assertEquals("unit.data->>'city' BETWEEN :citymin AND :citymax", assertEquals("unit.data->>'city' BETWEEN :citymin AND :citymax",
Field.between("city", "Atlanta", "Chicago", ":city").withQualifier("unit").toWhere(), Field.between("city", "Atlanta", "Chicago", ":city").withQualifier("unit").toWhere(),
"Field WHERE clause not generated correctly") "Field WHERE clause not generated correctly")
} }
@Test @Test
@DisplayName("toWhere generates for BETWEEN w/ qualifier (SQLite)") @DisplayName("toWhere generates for BETWEEN w/ qualifier | SQLite")
fun toWhereBetweenQualSQLite() { fun toWhereBetweenQualSQLite() {
Configuration.dialectValue = Dialect.SQLITE ForceDialect.sqlite()
assertEquals("my.data->>'age' BETWEEN @agemin AND @agemax", assertEquals("my.data->>'age' BETWEEN @agemin AND @agemax",
Field.between("age", 13, 17, "@age").withQualifier("my").toWhere(), Field.between("age", 13, 17, "@age").withQualifier("my").toWhere(),
"Field WHERE clause not generated correctly") "Field WHERE clause not generated correctly")
} }
@Test @Test
@DisplayName("toWhere generates for IN/any, numeric values (PostgreSQL)") @DisplayName("toWhere generates for IN/any, numeric values | PostgreSQL")
fun toWhereAnyNumericPostgres() { fun toWhereAnyNumericPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL ForceDialect.postgres()
assertEquals("(data->>'even')::numeric IN (:nbr_0, :nbr_1, :nbr_2)", assertEquals("(data->>'even')::numeric IN (:nbr_0, :nbr_1, :nbr_2)",
Field.any("even", listOf(2, 4, 6), ":nbr").toWhere(), "Field WHERE clause not generated correctly") Field.any("even", listOf(2, 4, 6), ":nbr").toWhere(), "Field WHERE clause not generated correctly")
} }
@Test @Test
@DisplayName("toWhere generates for IN/any, alphanumeric values (PostgreSQL)") @DisplayName("toWhere generates for IN/any, alphanumeric values | PostgreSQL")
fun toWhereAnyAlphaPostgres() { fun toWhereAnyAlphaPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL ForceDialect.postgres()
assertEquals("data->>'test' IN (:city_0, :city_1)", assertEquals("data->>'test' IN (:city_0, :city_1)",
Field.any("test", listOf("Atlanta", "Chicago"), ":city").toWhere(), Field.any("test", listOf("Atlanta", "Chicago"), ":city").toWhere(),
"Field WHERE clause not generated correctly") "Field WHERE clause not generated correctly")
} }
@Test @Test
@DisplayName("toWhere generates for IN/any (SQLite)") @DisplayName("toWhere generates for IN/any | SQLite")
fun toWhereAnySQLite() { fun toWhereAnySQLite() {
Configuration.dialectValue = Dialect.SQLITE ForceDialect.sqlite()
assertEquals("data->>'test' IN (:city_0, :city_1)", assertEquals("data->>'test' IN (:city_0, :city_1)",
Field.any("test", listOf("Atlanta", "Chicago"), ":city").toWhere(), Field.any("test", listOf("Atlanta", "Chicago"), ":city").toWhere(),
"Field WHERE clause not generated correctly") "Field WHERE clause not generated correctly")
} }
@Test @Test
@DisplayName("toWhere generates for inArray (PostgreSQL)") @DisplayName("toWhere generates for inArray | PostgreSQL")
fun toWhereInArrayPostgres() { fun toWhereInArrayPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL ForceDialect.postgres()
assertEquals("data->'even' ??| ARRAY[:it_0, :it_1, :it_2, :it_3]", assertEquals("data->'even' ??| ARRAY[:it_0, :it_1, :it_2, :it_3]",
Field.inArray("even", "tbl", listOf(2, 4, 6, 8), ":it").toWhere(), Field.inArray("even", "tbl", listOf(2, 4, 6, 8), ":it").toWhere(),
"Field WHERE clause not generated correctly") "Field WHERE clause not generated correctly")
} }
@Test @Test
@DisplayName("toWhere generates for inArray (SQLite)") @DisplayName("toWhere generates for inArray | SQLite")
fun toWhereInArraySQLite() { fun toWhereInArraySQLite() {
Configuration.dialectValue = Dialect.SQLITE ForceDialect.sqlite()
assertEquals("EXISTS (SELECT 1 FROM json_each(tbl.data, '$.test') WHERE value IN (:city_0, :city_1))", assertEquals("EXISTS (SELECT 1 FROM json_each(tbl.data, '$.test') WHERE value IN (:city_0, :city_1))",
Field.inArray("test", "tbl", listOf("Atlanta", "Chicago"), ":city").toWhere(), Field.inArray("test", "tbl", listOf("Atlanta", "Chicago"), ":city").toWhere(),
"Field WHERE clause not generated correctly") "Field WHERE clause not generated correctly")
} }
@Test @Test
@DisplayName("toWhere generates for others w/o qualifier (PostgreSQL)") @DisplayName("toWhere generates for others w/o qualifier | PostgreSQL")
fun toWhereOtherNoQualPostgres() { fun toWhereOtherNoQualPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL ForceDialect.postgres()
assertEquals("data->>'some_field' = :value", Field.equal("some_field", "", ":value").toWhere(), assertEquals("data->>'some_field' = :value", Field.equal("some_field", "", ":value").toWhere(),
"Field WHERE clause not generated correctly") "Field WHERE clause not generated correctly")
} }
@Test @Test
@DisplayName("toWhere generates for others w/o qualifier (SQLite)") @DisplayName("toWhere generates for others w/o qualifier | SQLite")
fun toWhereOtherNoQualSQLite() { fun toWhereOtherNoQualSQLite() {
Configuration.dialectValue = Dialect.SQLITE ForceDialect.sqlite()
assertEquals("data->>'some_field' = :value", Field.equal("some_field", "", ":value").toWhere(), assertEquals("data->>'some_field' = :value", Field.equal("some_field", "", ":value").toWhere(),
"Field WHERE clause not generated correctly") "Field WHERE clause not generated correctly")
} }
@Test @Test
@DisplayName("toWhere generates no-parameter w/ qualifier (PostgreSQL)") @DisplayName("toWhere generates no-parameter w/ qualifier | PostgreSQL")
fun toWhereNoParamWithQualPostgres() { fun toWhereNoParamWithQualPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL ForceDialect.postgres()
assertEquals("test.data->>'no_field' IS NOT NULL", Field.exists("no_field").withQualifier("test").toWhere(), assertEquals("test.data->>'no_field' IS NOT NULL", Field.exists("no_field").withQualifier("test").toWhere(),
"Field WHERE clause not generated correctly") "Field WHERE clause not generated correctly")
} }
@Test @Test
@DisplayName("toWhere generates no-parameter w/ qualifier (SQLite)") @DisplayName("toWhere generates no-parameter w/ qualifier | SQLite")
fun toWhereNoParamWithQualSQLite() { fun toWhereNoParamWithQualSQLite() {
Configuration.dialectValue = Dialect.SQLITE ForceDialect.sqlite()
assertEquals("test.data->>'no_field' IS NOT NULL", Field.exists("no_field").withQualifier("test").toWhere(), assertEquals("test.data->>'no_field' IS NOT NULL", Field.exists("no_field").withQualifier("test").toWhere(),
"Field WHERE clause not generated correctly") "Field WHERE clause not generated correctly")
} }
@Test @Test
@DisplayName("toWhere generates parameter w/ qualifier (PostgreSQL)") @DisplayName("toWhere generates parameter w/ qualifier | PostgreSQL")
fun toWhereParamWithQualPostgres() { fun toWhereParamWithQualPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL ForceDialect.postgres()
assertEquals("(q.data->>'le_field')::numeric <= :it", assertEquals("(q.data->>'le_field')::numeric <= :it",
Field.lessOrEqual("le_field", 18, ":it").withQualifier("q").toWhere(), Field.lessOrEqual("le_field", 18, ":it").withQualifier("q").toWhere(),
"Field WHERE clause not generated correctly") "Field WHERE clause not generated correctly")
} }
@Test @Test
@DisplayName("toWhere generates parameter w/ qualifier (SQLite)") @DisplayName("toWhere generates parameter w/ qualifier | SQLite")
fun toWhereParamWithQualSQLite() { fun toWhereParamWithQualSQLite() {
Configuration.dialectValue = Dialect.SQLITE ForceDialect.sqlite()
assertEquals("q.data->>'le_field' <= :it", assertEquals("q.data->>'le_field' <= :it",
Field.lessOrEqual("le_field", 18, ":it").withQualifier("q").toWhere(), Field.lessOrEqual("le_field", 18, ":it").withQualifier("q").toWhere(),
"Field WHERE clause not generated correctly") "Field WHERE clause not generated correctly")

View File

@ -5,6 +5,7 @@ import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.assertThrows
import solutions.bitbadger.documents.* import solutions.bitbadger.documents.*
import solutions.bitbadger.documents.support.ForceDialect
import kotlin.test.assertEquals import kotlin.test.assertEquals
/** /**
@ -18,7 +19,7 @@ class WhereTest {
*/ */
@AfterEach @AfterEach
fun cleanUp() { fun cleanUp() {
Configuration.dialectValue = null ForceDialect.none()
} }
@Test @Test
@ -27,30 +28,30 @@ class WhereTest {
assertEquals("", Where.byFields(listOf())) assertEquals("", Where.byFields(listOf()))
@Test @Test
@DisplayName("byFields generates one numeric field (PostgreSQL)") @DisplayName("byFields generates one numeric field | PostgreSQL")
fun byFieldsOneFieldPostgres() { fun byFieldsOneFieldPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL ForceDialect.postgres()
assertEquals("(data->>'it')::numeric = :that", Where.byFields(listOf(Field.equal("it", 9, ":that")))) assertEquals("(data->>'it')::numeric = :that", Where.byFields(listOf(Field.equal("it", 9, ":that"))))
} }
@Test @Test
@DisplayName("byFields generates one alphanumeric field (PostgreSQL)") @DisplayName("byFields generates one alphanumeric field | PostgreSQL")
fun byFieldsOneAlphaFieldPostgres() { fun byFieldsOneAlphaFieldPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL ForceDialect.postgres()
assertEquals("data->>'it' = :that", Where.byFields(listOf(Field.equal("it", "", ":that")))) assertEquals("data->>'it' = :that", Where.byFields(listOf(Field.equal("it", "", ":that"))))
} }
@Test @Test
@DisplayName("byFields generates one field (SQLite)") @DisplayName("byFields generates one field | SQLite")
fun byFieldsOneFieldSQLite() { fun byFieldsOneFieldSQLite() {
Configuration.dialectValue = Dialect.SQLITE ForceDialect.sqlite()
assertEquals("data->>'it' = :that", Where.byFields(listOf(Field.equal("it", "", ":that")))) assertEquals("data->>'it' = :that", Where.byFields(listOf(Field.equal("it", "", ":that"))))
} }
@Test @Test
@DisplayName("byFields generates multiple fields w/ default match (PostgreSQL)") @DisplayName("byFields generates multiple fields w/ default match | PostgreSQL")
fun byFieldsMultipleDefaultPostgres() { fun byFieldsMultipleDefaultPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL ForceDialect.postgres()
assertEquals( assertEquals(
"data->>'1' = :one AND (data->>'2')::numeric = :two AND data->>'3' = :three", "data->>'1' = :one AND (data->>'2')::numeric = :two AND data->>'3' = :three",
Where.byFields( Where.byFields(
@ -60,9 +61,9 @@ class WhereTest {
} }
@Test @Test
@DisplayName("byFields generates multiple fields w/ default match (SQLite)") @DisplayName("byFields generates multiple fields w/ default match | SQLite")
fun byFieldsMultipleDefaultSQLite() { fun byFieldsMultipleDefaultSQLite() {
Configuration.dialectValue = Dialect.SQLITE ForceDialect.sqlite()
assertEquals( assertEquals(
"data->>'1' = :one AND data->>'2' = :two AND data->>'3' = :three", "data->>'1' = :one AND data->>'2' = :two AND data->>'3' = :three",
Where.byFields( Where.byFields(
@ -72,9 +73,9 @@ class WhereTest {
} }
@Test @Test
@DisplayName("byFields generates multiple fields w/ ANY match (PostgreSQL)") @DisplayName("byFields generates multiple fields w/ ANY match | PostgreSQL")
fun byFieldsMultipleAnyPostgres() { fun byFieldsMultipleAnyPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL ForceDialect.postgres()
assertEquals( assertEquals(
"data->>'1' = :one OR (data->>'2')::numeric = :two OR data->>'3' = :three", "data->>'1' = :one OR (data->>'2')::numeric = :two OR data->>'3' = :three",
Where.byFields( Where.byFields(
@ -85,9 +86,9 @@ class WhereTest {
} }
@Test @Test
@DisplayName("byFields generates multiple fields w/ ANY match (SQLite)") @DisplayName("byFields generates multiple fields w/ ANY match | SQLite")
fun byFieldsMultipleAnySQLite() { fun byFieldsMultipleAnySQLite() {
Configuration.dialectValue = Dialect.SQLITE ForceDialect.sqlite()
assertEquals( assertEquals(
"data->>'1' = :one OR data->>'2' = :two OR data->>'3' = :three", "data->>'1' = :one OR data->>'2' = :two OR data->>'3' = :three",
Where.byFields( Where.byFields(
@ -98,79 +99,79 @@ class WhereTest {
} }
@Test @Test
@DisplayName("byId generates defaults for alphanumeric key (PostgreSQL)") @DisplayName("byId generates defaults for alphanumeric key | PostgreSQL")
fun byIdDefaultAlphaPostgres() { fun byIdDefaultAlphaPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL ForceDialect.postgres()
assertEquals("data->>'id' = :id", Where.byId(docId = "")) assertEquals("data->>'id' = :id", Where.byId(docId = ""))
} }
@Test @Test
@DisplayName("byId generates defaults for numeric key (PostgreSQL)") @DisplayName("byId generates defaults for numeric key | PostgreSQL")
fun byIdDefaultNumericPostgres() { fun byIdDefaultNumericPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL ForceDialect.postgres()
assertEquals("(data->>'id')::numeric = :id", Where.byId(docId = 5)) assertEquals("(data->>'id')::numeric = :id", Where.byId(docId = 5))
} }
@Test @Test
@DisplayName("byId generates defaults (SQLite)") @DisplayName("byId generates defaults | SQLite")
fun byIdDefaultSQLite() { fun byIdDefaultSQLite() {
Configuration.dialectValue = Dialect.SQLITE ForceDialect.sqlite()
assertEquals("data->>'id' = :id", Where.byId(docId = "")) assertEquals("data->>'id' = :id", Where.byId(docId = ""))
} }
@Test @Test
@DisplayName("byId generates named ID (PostgreSQL)") @DisplayName("byId generates named ID | PostgreSQL")
fun byIdDefaultNamedPostgres() { fun byIdDefaultNamedPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL ForceDialect.postgres()
assertEquals("data->>'id' = :key", Where.byId<String>(":key")) assertEquals("data->>'id' = :key", Where.byId<String>(":key"))
} }
@Test @Test
@DisplayName("byId generates named ID (SQLite)") @DisplayName("byId generates named ID | SQLite")
fun byIdDefaultNamedSQLite() { fun byIdDefaultNamedSQLite() {
Configuration.dialectValue = Dialect.SQLITE ForceDialect.sqlite()
assertEquals("data->>'id' = :key", Where.byId<String>(":key")) assertEquals("data->>'id' = :key", Where.byId<String>(":key"))
} }
@Test @Test
@DisplayName("jsonContains generates defaults (PostgreSQL)") @DisplayName("jsonContains generates defaults | PostgreSQL")
fun jsonContainsDefaultPostgres() { fun jsonContainsDefaultPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL ForceDialect.postgres()
assertEquals("data @> :criteria", Where.jsonContains()) assertEquals("data @> :criteria", Where.jsonContains())
} }
@Test @Test
@DisplayName("jsonContains generates named parameter (PostgreSQL)") @DisplayName("jsonContains generates named parameter | PostgreSQL")
fun jsonContainsNamedPostgres() { fun jsonContainsNamedPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL ForceDialect.postgres()
assertEquals("data @> :it", Where.jsonContains(":it")) assertEquals("data @> :it", Where.jsonContains(":it"))
} }
@Test @Test
@DisplayName("jsonContains fails (SQLite)") @DisplayName("jsonContains fails | SQLite")
fun jsonContainsFailsSQLite() { fun jsonContainsFailsSQLite() {
Configuration.dialectValue = Dialect.SQLITE ForceDialect.sqlite()
assertThrows<DocumentException> { Where.jsonContains() } assertThrows<DocumentException> { Where.jsonContains() }
} }
@Test @Test
@DisplayName("jsonPathMatches generates defaults (PostgreSQL)") @DisplayName("jsonPathMatches generates defaults | PostgreSQL")
fun jsonPathMatchDefaultPostgres() { fun jsonPathMatchDefaultPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL ForceDialect.postgres()
assertEquals("jsonb_path_exists(data, :path::jsonpath)", Where.jsonPathMatches()) assertEquals("jsonb_path_exists(data, :path::jsonpath)", Where.jsonPathMatches())
} }
@Test @Test
@DisplayName("jsonPathMatches generates named parameter (PostgreSQL)") @DisplayName("jsonPathMatches generates named parameter | PostgreSQL")
fun jsonPathMatchNamedPostgres() { fun jsonPathMatchNamedPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL ForceDialect.postgres()
assertEquals("jsonb_path_exists(data, :jp::jsonpath)", Where.jsonPathMatches(":jp")) assertEquals("jsonb_path_exists(data, :jp::jsonpath)", Where.jsonPathMatches(":jp"))
} }
@Test @Test
@DisplayName("jsonPathMatches fails (SQLite)") @DisplayName("jsonPathMatches fails | SQLite")
fun jsonPathFailsSQLite() { fun jsonPathFailsSQLite() {
Configuration.dialectValue = Dialect.SQLITE ForceDialect.sqlite()
assertThrows<DocumentException> { Where.jsonPathMatches() } assertThrows<DocumentException> { Where.jsonPathMatches() }
} }
} }