Initial Development #1

Merged
danieljsummers merged 88 commits from v1-rc into main 2025-04-16 01:29:20 +00:00
11 changed files with 646 additions and 199 deletions
Showing only changes of commit 623e49243d - Show all commits

View File

@ -45,15 +45,15 @@ enum class AutoId {
* @param document The document whose need of an automatic ID should be determined
* @param idProp The name of the document property containing the ID
* @return `true` if the document needs an automatic ID, `false` if not
* @throws IllegalArgumentException If bad input prevents the determination
* @throws DocumentException If bad input prevents the determination
*/
fun <T> needsAutoId(strategy: AutoId, document: T, idProp: String): Boolean {
if (document == null) throw IllegalArgumentException("document cannot be null")
if (document == null) throw DocumentException("document cannot be null")
if (strategy == DISABLED) return false;
val id = document!!::class.memberProperties.find { it.name == idProp }
if (id == null) throw IllegalArgumentException("$idProp not found in document")
if (id == null) throw DocumentException("$idProp not found in document")
if (strategy == NUMBER) {
return when (id.returnType) {
@ -61,7 +61,7 @@ enum class AutoId {
Short::class.createType() -> id.call(document) == 0.toShort()
Int::class.createType() -> id.call(document) == 0
Long::class.createType() -> id.call(document) == 0.toLong()
else -> throw IllegalArgumentException("$idProp was not a number; cannot auto-generate number ID")
else -> throw DocumentException("$idProp was not a number; cannot auto-generate number ID")
}
}
@ -69,7 +69,7 @@ enum class AutoId {
return id.call(document) == ""
}
throw IllegalArgumentException("$idProp was not a string; cannot auto-generate UUID or random string")
throw DocumentException("$idProp was not a string; cannot auto-generate UUID or random string")
}
}
}

View File

@ -10,16 +10,15 @@ class Comparison<T>(val op: Op, val value: T) {
/** Is the value for this comparison a numeric value? */
val isNumeric: Boolean
get() =
if (op == Op.IN || op == Op.BETWEEN) {
val values = value as? Collection<*>
if (values.isNullOrEmpty()) {
false
} else {
val first = values.elementAt(0)
first is Byte || first is Short || first is Int || first is Long
get() {
val toCheck = when (op) {
Op.IN -> {
val values = value as? Collection<*>
if (values.isNullOrEmpty()) "" else values.elementAt(0)
}
} else {
value is Byte || value is Short || value is Int || value is Long
Op.BETWEEN -> (value as Pair<*, *>).first
else -> value
}
return toCheck is Byte || toCheck is Short || toCheck is Int || toCheck is Long
}
}

View File

@ -20,8 +20,8 @@ enum class Dialect {
*/
fun deriveFromConnectionString(connectionString: String): Dialect =
when {
connectionString.contains("sqlite") -> SQLITE
connectionString.contains("postgresql") -> POSTGRESQL
connectionString.contains(":sqlite:") -> SQLITE
connectionString.contains(":postgresql:") -> POSTGRESQL
else -> throw DocumentException("Cannot determine dialect from [$connectionString]")
}
}

View File

@ -20,8 +20,11 @@ class Field<T> private constructor(
* @param paramName The parameter name to use for this field
* @return A new `Field` with the parameter name specified
*/
fun withParameterName(paramName: String): Field<T> =
Field(name, comparison, paramName, qualifier)
fun withParameterName(paramName: String): Field<T> {
if (!paramName.startsWith(':') && !paramName.startsWith('@'))
throw DocumentException("Parameter must start with : or @ ($paramName)")
return Field(name, comparison, paramName, qualifier)
}
/**
* Specify a qualifier (alias) for the document table
@ -29,7 +32,7 @@ class Field<T> private constructor(
* @param alias The table alias for this field
* @return A new `Field` with the table qualifier specified
*/
fun withQualifier(alias: String): Field<T> =
fun withQualifier(alias: String) =
Field(name, comparison, parameterName, alias)
/**
@ -74,16 +77,16 @@ class Field<T> private constructor(
in listOf(Op.EXISTS, Op.NOT_EXISTS) -> ""
Op.BETWEEN -> " ${parameterName}min AND ${parameterName}max"
Op.IN -> " ($inParameterNames)"
Op.IN_ARRAY -> if (dialect == Dialect.POSTGRESQL) " ARRAY['$inParameterNames']" else ""
Op.IN_ARRAY -> if (dialect == Dialect.POSTGRESQL) " ARRAY[$inParameterNames]" else ""
else -> " $parameterName"
}
@Suppress("UNCHECKED_CAST")
return if (dialect == Dialect.SQLITE && comparison.op == Op.IN_ARRAY) {
val (table, _) = comparison.value as? Pair<String, *> ?: throw DocumentException("InArray field invalid")
"EXISTS (SELECT 1 FROM json_each($table.data, '$.$name') WHERE value IN ($inParameterNames)"
"EXISTS (SELECT 1 FROM json_each($table.data, '$.$name') WHERE value IN ($inParameterNames))"
} else {
"$fieldPath ${comparison.op.sql} $criteria"
"$fieldPath ${comparison.op.sql}$criteria"
}
}
@ -94,60 +97,66 @@ class Field<T> private constructor(
*
* @param name The name of the field to be compared
* @param value The value for the comparison
* @param paramName The parameter name for the field (optional, defaults to auto-generated)
* @return A `Field` with the given comparison
*/
fun <T> equal(name: String, value: T) =
Field(name, Comparison(Op.EQUAL, value))
fun <T> equal(name: String, value: T, paramName: String? = null) =
Field(name, Comparison(Op.EQUAL, value), paramName)
/**
* Create a field greater-than comparison
*
* @param name The name of the field to be compared
* @param value The value for the comparison
* @param paramName The parameter name for the field (optional, defaults to auto-generated)
* @return A `Field` with the given comparison
*/
fun <T> greater(name: String, value: T) =
Field(name, Comparison(Op.GREATER, value))
fun <T> greater(name: String, value: T, paramName: String? = null) =
Field(name, Comparison(Op.GREATER, value), paramName)
/**
* Create a field greater-than-or-equal-to comparison
*
* @param name The name of the field to be compared
* @param value The value for the comparison
* @param paramName The parameter name for the field (optional, defaults to auto-generated)
* @return A `Field` with the given comparison
*/
fun <T> greaterOrEqual(name: String, value: T) =
Field(name, Comparison(Op.GREATER_OR_EQUAL, value))
fun <T> greaterOrEqual(name: String, value: T, paramName: String? = null) =
Field(name, Comparison(Op.GREATER_OR_EQUAL, value), paramName)
/**
* Create a field less-than comparison
*
* @param name The name of the field to be compared
* @param value The value for the comparison
* @param paramName The parameter name for the field (optional, defaults to auto-generated)
* @return A `Field` with the given comparison
*/
fun <T> less(name: String, value: T) =
Field(name, Comparison(Op.LESS, value))
fun <T> less(name: String, value: T, paramName: String? = null) =
Field(name, Comparison(Op.LESS, value), paramName)
/**
* Create a field less-than-or-equal-to comparison
*
* @param name The name of the field to be compared
* @param value The value for the comparison
* @param paramName The parameter name for the field (optional, defaults to auto-generated)
* @return A `Field` with the given comparison
*/
fun <T> lessOrEqual(name: String, value: T) =
Field(name, Comparison(Op.LESS_OR_EQUAL, value))
fun <T> lessOrEqual(name: String, value: T, paramName: String? = null) =
Field(name, Comparison(Op.LESS_OR_EQUAL, value), paramName)
/**
* Create a field inequality comparison
*
* @param name The name of the field to be compared
* @param value The value for the comparison
* @param paramName The parameter name for the field (optional, defaults to auto-generated)
* @return A `Field` with the given comparison
*/
fun <T> notEqual(name: String, value: T) =
Field(name, Comparison(Op.NOT_EQUAL, value))
fun <T> notEqual(name: String, value: T, paramName: String? = null) =
Field(name, Comparison(Op.NOT_EQUAL, value), paramName)
/**
* Create a field range comparison
@ -155,20 +164,22 @@ class Field<T> private constructor(
* @param name The name of the field to be compared
* @param minValue The lower value for the comparison
* @param maxValue The upper value for the comparison
* @param paramName The parameter name for the field (optional, defaults to auto-generated)
* @return A `Field` with the given comparison
*/
fun <T> between(name: String, minValue: T, maxValue: T) =
Field(name, Comparison(Op.BETWEEN, Pair(minValue, maxValue)))
fun <T> between(name: String, minValue: T, maxValue: T, paramName: String? = null) =
Field(name, Comparison(Op.BETWEEN, Pair(minValue, maxValue)), paramName)
/**
* Create a field where any values match (SQL `IN`)
*
* @param name The name of the field to be compared
* @param values The values for the comparison
* @param paramName The parameter name for the field (optional, defaults to auto-generated)
* @return A `Field` with the given comparison
*/
fun <T> any(name: String, values: List<T>) =
Field(name, Comparison(Op.IN, values))
fun <T> any(name: String, values: Collection<T>, paramName: String? = null) =
Field(name, Comparison(Op.IN, values), paramName)
/**
* Create a field where values should exist in a document's array
@ -176,10 +187,11 @@ class Field<T> private constructor(
* @param name The name of the field to be compared
* @param tableName The name of the document table
* @param values The values for the comparison
* @param paramName The parameter name for the field (optional, defaults to auto-generated)
* @return A `Field` with the given comparison
*/
fun <T> inArray(name: String, tableName: String, values: List<T>) =
Field(name, Comparison(Op.IN_ARRAY, Pair(tableName, values)))
fun <T> inArray(name: String, tableName: String, values: Collection<T>, paramName: String? = null) =
Field(name, Comparison(Op.IN_ARRAY, Pair(tableName, values)), paramName)
fun exists(name: String) =
Field(name, Comparison(Op.EXISTS, ""))

View File

@ -34,7 +34,7 @@ object Query {
* @param docId The ID value (optional; used for type determinations, string assumed if not provided)
*/
fun <TKey> byId(parameterName: String = ":id", docId: TKey? = null) =
byFields(listOf(Field.equal(Configuration.idField, docId ?: "").withParameterName(parameterName)))
byFields(listOf(Field.equal(Configuration.idField, docId ?: "", parameterName)))
/**
* Create a `WHERE` clause fragment to implement a JSON containment query (PostgreSQL only)

View File

@ -12,9 +12,8 @@ class AutoIdTest {
@Test
@DisplayName("Generates a UUID string")
fun generateUUID() {
fun generateUUID() =
assertEquals(32, AutoId.generateUUID().length, "The UUID should have been a 32-character string")
}
@Test
@DisplayName("Generates a random hex character string of an even length")
@ -41,120 +40,107 @@ class AutoIdTest {
@Test
@DisplayName("needsAutoId fails for null document")
fun needsAutoIdFailsForNullDocument() {
assertThrows<IllegalArgumentException> { AutoId.needsAutoId(AutoId.DISABLED, null, "id") }
assertThrows<DocumentException> { AutoId.needsAutoId(AutoId.DISABLED, null, "id") }
}
@Test
@DisplayName("needsAutoId fails for missing ID property")
fun needsAutoIdFailsForMissingId() {
assertThrows<IllegalArgumentException> { AutoId.needsAutoId(AutoId.UUID, IntIdClass(0), "Id") }
assertThrows<DocumentException> { AutoId.needsAutoId(AutoId.UUID, IntIdClass(0), "Id") }
}
@Test
@DisplayName("needsAutoId returns false if disabled")
fun needsAutoIdFalseIfDisabled() {
fun needsAutoIdFalseIfDisabled() =
assertFalse(AutoId.needsAutoId(AutoId.DISABLED, "", ""), "Disabled Auto ID should always return false")
}
@Test
@DisplayName("needsAutoId returns true for Number strategy and byte ID of 0")
fun needsAutoIdTrueForByteWithZero() {
fun needsAutoIdTrueForByteWithZero() =
assertTrue(AutoId.needsAutoId(AutoId.NUMBER, ByteIdClass(0), "id"), "Number Auto ID with 0 should return true")
}
@Test
@DisplayName("needsAutoId returns false for Number strategy and byte ID of non-0")
fun needsAutoIdFalseForByteWithNonZero() {
fun needsAutoIdFalseForByteWithNonZero() =
assertFalse(AutoId.needsAutoId(AutoId.NUMBER, ByteIdClass(77), "id"),
"Number Auto ID with 77 should return false")
}
@Test
@DisplayName("needsAutoId returns true for Number strategy and short ID of 0")
fun needsAutoIdTrueForShortWithZero() {
fun needsAutoIdTrueForShortWithZero() =
assertTrue(AutoId.needsAutoId(AutoId.NUMBER, ShortIdClass(0), "id"), "Number Auto ID with 0 should return true")
}
@Test
@DisplayName("needsAutoId returns false for Number strategy and short ID of non-0")
fun needsAutoIdFalseForShortWithNonZero() {
fun needsAutoIdFalseForShortWithNonZero() =
assertFalse(AutoId.needsAutoId(AutoId.NUMBER, ShortIdClass(31), "id"),
"Number Auto ID with 31 should return false")
}
@Test
@DisplayName("needsAutoId returns true for Number strategy and int ID of 0")
fun needsAutoIdTrueForIntWithZero() {
fun needsAutoIdTrueForIntWithZero() =
assertTrue(AutoId.needsAutoId(AutoId.NUMBER, IntIdClass(0), "id"), "Number Auto ID with 0 should return true")
}
@Test
@DisplayName("needsAutoId returns false for Number strategy and int ID of non-0")
fun needsAutoIdFalseForIntWithNonZero() {
fun needsAutoIdFalseForIntWithNonZero() =
assertFalse(AutoId.needsAutoId(AutoId.NUMBER, IntIdClass(6), "id"), "Number Auto ID with 6 should return false")
}
@Test
@DisplayName("needsAutoId returns true for Number strategy and long ID of 0")
fun needsAutoIdTrueForLongWithZero() {
fun needsAutoIdTrueForLongWithZero() =
assertTrue(AutoId.needsAutoId(AutoId.NUMBER, LongIdClass(0), "id"), "Number Auto ID with 0 should return true")
}
@Test
@DisplayName("needsAutoId returns false for Number strategy and long ID of non-0")
fun needsAutoIdFalseForLongWithNonZero() {
fun needsAutoIdFalseForLongWithNonZero() =
assertFalse(AutoId.needsAutoId(AutoId.NUMBER, IntIdClass(2), "id"), "Number Auto ID with 2 should return false")
}
@Test
@DisplayName("needsAutoId fails for Number strategy and non-number ID")
fun needsAutoIdFailsForNumberWithStringId() {
assertThrows<IllegalArgumentException> { AutoId.needsAutoId(AutoId.NUMBER, StringIdClass(""), "id") }
assertThrows<DocumentException> { AutoId.needsAutoId(AutoId.NUMBER, StringIdClass(""), "id") }
}
@Test
@DisplayName("needsAutoId returns true for UUID strategy and blank ID")
fun needsAutoIdTrueForUUIDWithBlank() {
fun needsAutoIdTrueForUUIDWithBlank() =
assertTrue(AutoId.needsAutoId(AutoId.UUID, StringIdClass(""), "id"),
"UUID Auto ID with blank should return true")
}
@Test
@DisplayName("needsAutoId returns false for UUID strategy and non-blank ID")
fun needsAutoIdFalseForUUIDNotBlank() {
fun needsAutoIdFalseForUUIDNotBlank() =
assertFalse(AutoId.needsAutoId(AutoId.UUID, StringIdClass("howdy"), "id"),
"UUID Auto ID with non-blank should return false")
}
@Test
@DisplayName("needsAutoId fails for UUID strategy and non-string ID")
fun needsAutoIdFailsForUUIDNonString() {
assertThrows<IllegalArgumentException> { AutoId.needsAutoId(AutoId.UUID, IntIdClass(5), "id") }
assertThrows<DocumentException> { AutoId.needsAutoId(AutoId.UUID, IntIdClass(5), "id") }
}
@Test
@DisplayName("needsAutoId returns true for Random String strategy and blank ID")
fun needsAutoIdTrueForRandomWithBlank() {
fun needsAutoIdTrueForRandomWithBlank() =
assertTrue(AutoId.needsAutoId(AutoId.RANDOM_STRING, StringIdClass(""), "id"),
"Random String Auto ID with blank should return true")
}
@Test
@DisplayName("needsAutoId returns false for Random String strategy and non-blank ID")
fun needsAutoIdFalseForRandomNotBlank() {
fun needsAutoIdFalseForRandomNotBlank() =
assertFalse(AutoId.needsAutoId(AutoId.RANDOM_STRING, StringIdClass("full"), "id"),
"Random String Auto ID with non-blank should return false")
}
@Test
@DisplayName("needsAutoId fails for Random String strategy and non-string ID")
fun needsAutoIdFailsForRandomNonString() {
assertThrows<IllegalArgumentException> { AutoId.needsAutoId(AutoId.RANDOM_STRING, ShortIdClass(55), "id") }
assertThrows<DocumentException> { AutoId.needsAutoId(AutoId.RANDOM_STRING, ShortIdClass(55), "id") }
}
}
data class ByteIdClass(var id: Byte)
data class ShortIdClass(var id: Short)
data class IntIdClass(var id: Int)
data class LongIdClass(var id: Long)
data class StringIdClass(var id: String)
data class ByteIdClass(val id: Byte)
data class ShortIdClass(val id: Short)
data class IntIdClass(val id: Int)
data class LongIdClass(val id: Long)
data class StringIdClass(val id: String)

View File

@ -0,0 +1,91 @@
package solutions.bitbadger.documents
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class ComparisonTest {
@Test
@DisplayName("isNumeric is false for empty list of values")
fun isNumericFalseForEmptyList() =
assertFalse(Comparison(Op.IN, listOf<Int>()).isNumeric, "An IN with empty list should not be numeric")
@Test
@DisplayName("isNumeric is false for IN with strings")
fun isNumericFalseForStringsAndIn() =
assertFalse(Comparison(Op.IN, listOf("a", "b", "c")).isNumeric, "An IN with strings should not be numeric")
@Test
@DisplayName("isNumeric is true for IN with bytes")
fun isNumericTrueForByteAndIn() =
assertTrue(Comparison(Op.IN, listOf<Byte>(4, 8)).isNumeric, "An IN with bytes should be numeric")
@Test
@DisplayName("isNumeric is true for IN with shorts")
fun isNumericTrueForShortAndIn() =
assertTrue(Comparison(Op.IN, listOf<Short>(18, 22)).isNumeric, "An IN with shorts should be numeric")
@Test
@DisplayName("isNumeric is true for IN with ints")
fun isNumericTrueForIntAndIn() =
assertTrue(Comparison(Op.IN, listOf(7, 8, 9)).isNumeric, "An IN with ints should be numeric")
@Test
@DisplayName("isNumeric is true for IN with longs")
fun isNumericTrueForLongAndIn() =
assertTrue(Comparison(Op.IN, listOf(3L)).isNumeric, "An IN with longs should be numeric")
@Test
@DisplayName("isNumeric is false for BETWEEN with strings")
fun isNumericFalseForStringsAndBetween() =
assertFalse(Comparison(Op.BETWEEN, Pair("eh", "zed")).isNumeric,
"A BETWEEN with strings should not be numeric")
@Test
@DisplayName("isNumeric is true for BETWEEN with bytes")
fun isNumericTrueForByteAndBetween() =
assertTrue(Comparison(Op.BETWEEN, Pair<Byte, Byte>(7, 11)).isNumeric, "A BETWEEN with bytes should be numeric")
@Test
@DisplayName("isNumeric is true for BETWEEN with shorts")
fun isNumericTrueForShortAndBetween() =
assertTrue(Comparison(Op.BETWEEN, Pair<Short, Short>(0, 9)).isNumeric,
"A BETWEEN with shorts should be numeric")
@Test
@DisplayName("isNumeric is true for BETWEEN with ints")
fun isNumericTrueForIntAndBetween() =
assertTrue(Comparison(Op.BETWEEN, Pair(15, 44)).isNumeric, "A BETWEEN with ints should be numeric")
@Test
@DisplayName("isNumeric is true for BETWEEN with longs")
fun isNumericTrueForLongAndBetween() =
assertTrue(Comparison(Op.BETWEEN, Pair(9L, 12L)).isNumeric, "A BETWEEN with longs should be numeric")
@Test
@DisplayName("isNumeric is false for string value")
fun isNumericFalseForString() =
assertFalse(Comparison(Op.EQUAL, "80").isNumeric, "A string should not be numeric")
@Test
@DisplayName("isNumeric is true for byte value")
fun isNumericTrueForByte() =
assertTrue(Comparison(Op.EQUAL, 47.toByte()).isNumeric, "A byte should be numeric")
@Test
@DisplayName("isNumeric is true for short value")
fun isNumericTrueForShort() =
assertTrue(Comparison(Op.EQUAL, 2.toShort()).isNumeric, "A short should be numeric")
@Test
@DisplayName("isNumeric is true for int value")
fun isNumericTrueForInt() =
assertTrue(Comparison(Op.EQUAL, 555).isNumeric, "An int should be numeric")
@Test
@DisplayName("isNumeric is true for long value")
fun isNumericTrueForLong() =
assertTrue(Comparison(Op.EQUAL, 82L).isNumeric, "A long should be numeric")
}

View File

@ -2,10 +2,20 @@ package solutions.bitbadger.documents
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class ConfigurationTest {
@Test
@DisplayName("Default JSON options are as expected")
fun defaultJsonOptions() {
assertTrue(Configuration.json.configuration.encodeDefaults, "Encode Defaults should have been set")
assertFalse(Configuration.json.configuration.explicitNulls, "Explicit Nulls should not have been set")
}
@Test
@DisplayName("Default ID field is `id`")
fun defaultIdField() {
@ -23,4 +33,16 @@ class ConfigurationTest {
fun defaultIdStringLength() {
assertEquals(16, Configuration.idStringLength, "Default ID string length should be 16")
}
@Test
@DisplayName("Dialect is derived from connection string")
fun dialectIsDerived() {
try {
assertThrows<DocumentException> { Configuration.dialect() }
Configuration.connectionString = "jdbc:postgresql:db"
assertEquals(Dialect.POSTGRESQL, Configuration.dialect())
} finally {
Configuration.connectionString = null
}
}
}

View File

@ -0,0 +1,34 @@
package solutions.bitbadger.documents
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
class DialectTest {
@Test
@DisplayName("deriveFromConnectionString derives PostgreSQL correctly")
fun derivesPostgres() =
assertEquals(Dialect.POSTGRESQL, Dialect.deriveFromConnectionString("jdbc:postgresql:db"),
"Dialect should have been PostgreSQL")
@Test
@DisplayName("deriveFromConnectionString derives PostgreSQL correctly")
fun derivesSQLite() =
assertEquals(Dialect.SQLITE, Dialect.deriveFromConnectionString("jdbc:sqlite:memory"),
"Dialect should have been SQLite")
@Test
@DisplayName("deriveFromConnectionString fails when the connection string is unknown")
fun deriveFailsWhenUnknown() {
try {
Dialect.deriveFromConnectionString("SQL Server")
} catch (ex: DocumentException) {
assertNotNull(ex.message, "The exception message should not have been null")
assertTrue(ex.message!!.contains("[SQL Server]"),
"The connection string should have been in the exception message")
}
}
}

View File

@ -1,14 +1,302 @@
package solutions.bitbadger.documents
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import kotlin.test.assertEquals
import kotlin.test.assertNotSame
import kotlin.test.assertNull
class FieldTest {
/**
* Clear the connection string (resets Dialect)
*/
@AfterEach
fun cleanUp() {
Configuration.connectionString = null
}
// ~~~ INSTANCE METHODS ~~~
@Test
@DisplayName("equal constructs a field")
@DisplayName("withParameterName fails for invalid name")
fun withParamNameFails() {
assertThrows<DocumentException> { Field.equal("it", "").withParameterName("2424") }
}
@Test
@DisplayName("withParameterName works with colon prefix")
fun withParamNameColon() {
val field = Field.equal("abc", "22").withQualifier("me")
val withParam = field.withParameterName(":test")
assertNotSame(field, withParam, "A new Field instance should have been created")
assertEquals(field.name, withParam.name, "Name should have been preserved")
assertEquals(field.comparison, withParam.comparison, "Comparison should have been preserved")
assertEquals(":test", withParam.parameterName, "Parameter name not set correctly")
assertEquals(field.qualifier, withParam.qualifier, "Qualifier should have been preserved")
}
@Test
@DisplayName("withParameterName works with at-sign prefix")
fun withParamNameAtSign() {
val field = Field.equal("def", "44")
val withParam = field.withParameterName("@unit")
assertNotSame(field, withParam, "A new Field instance should have been created")
assertEquals(field.name, withParam.name, "Name should have been preserved")
assertEquals(field.comparison, withParam.comparison, "Comparison should have been preserved")
assertEquals("@unit", withParam.parameterName, "Parameter name not set correctly")
assertEquals(field.qualifier, withParam.qualifier, "Qualifier should have been preserved")
}
@Test
@DisplayName("withQualifier sets qualifier correctly")
fun withQualifier() {
val field = Field.equal("j", "k")
val withQual = field.withQualifier("test")
assertNotSame(field, withQual, "A new Field instance should have been created")
assertEquals(field.name, withQual.name, "Name should have been preserved")
assertEquals(field.comparison, withQual.comparison, "Comparison should have been preserved")
assertEquals(field.parameterName, withQual.parameterName, "Parameter Name should have been preserved")
assertEquals("test", withQual.qualifier, "Qualifier not set correctly")
}
@Test
@DisplayName("path generates for simple unqualified PostgreSQL field")
fun pathPostgresSimpleUnqualified() =
assertEquals("data->>'SomethingCool'",
Field.greaterOrEqual("SomethingCool", 18).path(Dialect.POSTGRESQL, FieldFormat.SQL), "Path not correct")
@Test
@DisplayName("path generates for simple qualified PostgreSQL field")
fun pathPostgresSimpleQualified() =
assertEquals("this.data->>'SomethingElse'",
Field.less("SomethingElse", 9).withQualifier("this").path(Dialect.POSTGRESQL, FieldFormat.SQL),
"Path not correct")
@Test
@DisplayName("path generates for nested unqualified PostgreSQL field")
fun pathPostgresNestedUnqualified() {
assertEquals("data#>>'{My,Nested,Field}'",
Field.equal("My.Nested.Field", "howdy").path(Dialect.POSTGRESQL, FieldFormat.SQL), "Path not correct")
}
@Test
@DisplayName("path generates for nested qualified PostgreSQL field")
fun pathPostgresNestedQualified() =
assertEquals("bird.data#>>'{Nest,Away}'",
Field.equal("Nest.Away", "doc").withQualifier("bird").path(Dialect.POSTGRESQL, FieldFormat.SQL),
"Path not correct")
@Test
@DisplayName("path generates for simple unqualified SQLite field")
fun pathSQLiteSimpleUnqualified() =
assertEquals("data->>'SomethingCool'",
Field.greaterOrEqual("SomethingCool", 18).path(Dialect.SQLITE, FieldFormat.SQL), "Path not correct")
@Test
@DisplayName("path generates for simple qualified SQLite field")
fun pathSQLiteSimpleQualified() =
assertEquals("this.data->>'SomethingElse'",
Field.less("SomethingElse", 9).withQualifier("this").path(Dialect.SQLITE, FieldFormat.SQL),
"Path not correct")
@Test
@DisplayName("path generates for nested unqualified SQLite field")
fun pathSQLiteNestedUnqualified() =
assertEquals("data->'My'->'Nested'->>'Field'",
Field.equal("My.Nested.Field", "howdy").path(Dialect.SQLITE, FieldFormat.SQL), "Path not correct")
@Test
@DisplayName("path generates for nested qualified SQLite field")
fun pathSQLiteNestedQualified() =
assertEquals("bird.data->'Nest'->>'Away'",
Field.equal("Nest.Away", "doc").withQualifier("bird").path(Dialect.SQLITE, FieldFormat.SQL),
"Path not correct")
@Test
@DisplayName("toWhere generates for exists w/o qualifier (PostgreSQL)")
fun toWhereExistsNoQualPostgres() {
Configuration.connectionString = ":postgresql:"
assertEquals("data->>'that_field' IS NOT NULL", Field.exists("that_field").toWhere(),
"Field WHERE clause not generated correctly")
}
@Test
@DisplayName("toWhere generates for exists w/o qualifier (SQLite)")
fun toWhereExistsNoQualSQLite() {
Configuration.connectionString = ":sqlite:"
assertEquals("data->>'that_field' IS NOT NULL", Field.exists("that_field").toWhere(),
"Field WHERE clause not generated correctly")
}
@Test
@DisplayName("toWhere generates for not-exists w/o qualifier (PostgreSQL)")
fun toWhereNotExistsNoQualPostgres() {
Configuration.connectionString = ":postgresql:"
assertEquals("data->>'a_field' IS NULL", Field.notExists("a_field").toWhere(),
"Field WHERE clause not generated correctly")
}
@Test
@DisplayName("toWhere generates for not-exists w/o qualifier (SQLite)")
fun toWhereNotExistsNoQualSQLite() {
Configuration.connectionString = ":sqlite:"
assertEquals("data->>'a_field' IS NULL", Field.notExists("a_field").toWhere(),
"Field WHERE clause not generated correctly")
}
@Test
@DisplayName("toWhere generates for BETWEEN w/o qualifier, numeric range (PostgreSQL)")
fun toWhereBetweenNoQualNumericPostgres() {
Configuration.connectionString = ":postgresql:"
assertEquals("(data->>'age')::numeric BETWEEN @agemin AND @agemax",
Field.between("age", 13, 17, "@age").toWhere(), "Field WHERE clause not generated correctly")
}
@Test
@DisplayName("toWhere generates for BETWEEN w/o qualifier, alphanumeric range (PostgreSQL)")
fun toWhereBetweenNoQualAlphaPostgres() {
Configuration.connectionString = ":postgresql:"
assertEquals("data->>'city' BETWEEN :citymin AND :citymax",
Field.between("city", "Atlanta", "Chicago", ":city").toWhere(),
"Field WHERE clause not generated correctly")
}
@Test
@DisplayName("toWhere generates for BETWEEN w/o qualifier (SQLite)")
fun toWhereBetweenNoQualSQLite() {
Configuration.connectionString = ":sqlite:"
assertEquals("data->>'age' BETWEEN @agemin AND @agemax", Field.between("age", 13, 17, "@age").toWhere(),
"Field WHERE clause not generated correctly")
}
@Test
@DisplayName("toWhere generates for BETWEEN w/ qualifier, numeric range (PostgreSQL)")
fun toWhereBetweenQualNumericPostgres() {
Configuration.connectionString = ":postgresql:"
assertEquals("(test.data->>'age')::numeric BETWEEN @agemin AND @agemax",
Field.between("age", 13, 17, "@age").withQualifier("test").toWhere(),
"Field WHERE clause not generated correctly")
}
@Test
@DisplayName("toWhere generates for BETWEEN w/ qualifier, alphanumeric range (PostgreSQL)")
fun toWhereBetweenQualAlphaPostgres() {
Configuration.connectionString = ":postgresql:"
assertEquals("unit.data->>'city' BETWEEN :citymin AND :citymax",
Field.between("city", "Atlanta", "Chicago", ":city").withQualifier("unit").toWhere(),
"Field WHERE clause not generated correctly")
}
@Test
@DisplayName("toWhere generates for BETWEEN w/ qualifier (SQLite)")
fun toWhereBetweenQualSQLite() {
Configuration.connectionString = ":sqlite:"
assertEquals("my.data->>'age' BETWEEN @agemin AND @agemax",
Field.between("age", 13, 17, "@age").withQualifier("my").toWhere(),
"Field WHERE clause not generated correctly")
}
@Test
@DisplayName("toWhere generates for IN/any, numeric values (PostgreSQL)")
fun toWhereAnyNumericPostgres() {
Configuration.connectionString = ":postgresql:"
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")
}
@Test
@DisplayName("toWhere generates for IN/any, alphanumeric values (PostgreSQL)")
fun toWhereAnyAlphaPostgres() {
Configuration.connectionString = ":postgresql:"
assertEquals("data->>'test' IN (:city_0, :city_1)",
Field.any("test", listOf("Atlanta", "Chicago"), ":city").toWhere(),
"Field WHERE clause not generated correctly")
}
@Test
@DisplayName("toWhere generates for IN/any (SQLite)")
fun toWhereAnySQLite() {
Configuration.connectionString = ":sqlite:"
assertEquals("data->>'test' IN (:city_0, :city_1)",
Field.any("test", listOf("Atlanta", "Chicago"), ":city").toWhere(),
"Field WHERE clause not generated correctly")
}
@Test
@DisplayName("toWhere generates for inArray (PostgreSQL)")
fun toWhereInArrayPostgres() {
Configuration.connectionString = ":postgresql:"
assertEquals("data->'even' ?| ARRAY[:it_0, :it_1, :it_2, :it_3]",
Field.inArray("even", "tbl", listOf(2, 4, 6, 8), ":it").toWhere(),
"Field WHERE clause not generated correctly")
}
@Test
@DisplayName("toWhere generates for inArray (SQLite)")
fun toWhereInArraySQLite() {
Configuration.connectionString = ":sqlite:"
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 WHERE clause not generated correctly")
}
@Test
@DisplayName("toWhere generates for others w/o qualifier (PostgreSQL)")
fun toWhereOtherNoQualPostgres() {
Configuration.connectionString = ":postgresql:"
assertEquals("data->>'some_field' = :value", Field.equal("some_field", "", ":value").toWhere(),
"Field WHERE clause not generated correctly")
}
@Test
@DisplayName("toWhere generates for others w/o qualifier (SQLite)")
fun toWhereOtherNoQualSQLite() {
Configuration.connectionString = ":sqlite:"
assertEquals("data->>'some_field' = :value", Field.equal("some_field", "", ":value").toWhere(),
"Field WHERE clause not generated correctly")
}
@Test
@DisplayName("toWhere generates no-parameter w/ qualifier (PostgreSQL)")
fun toWhereNoParamWithQualPostgres() {
Configuration.connectionString = ":postgresql:"
assertEquals("test.data->>'no_field' IS NOT NULL", Field.exists("no_field").withQualifier("test").toWhere(),
"Field WHERE clause not generated correctly")
}
@Test
@DisplayName("toWhere generates no-parameter w/ qualifier (SQLite)")
fun toWhereNoParamWithQualSQLite() {
Configuration.connectionString = ":sqlite:"
assertEquals("test.data->>'no_field' IS NOT NULL", Field.exists("no_field").withQualifier("test").toWhere(),
"Field WHERE clause not generated correctly")
}
@Test
@DisplayName("toWhere generates parameter w/ qualifier (PostgreSQL)")
fun toWhereParamWithQualPostgres() {
Configuration.connectionString = ":postgresql:"
assertEquals("(q.data->>'le_field')::numeric <= :it",
Field.lessOrEqual("le_field", 18, ":it").withQualifier("q").toWhere(),
"Field WHERE clause not generated correctly")
}
@Test
@DisplayName("toWhere generates parameter w/ qualifier (SQLite)")
fun toWhereParamWithQualSQLite() {
Configuration.connectionString = ":sqlite:"
assertEquals("q.data->>'le_field' <= :it",
Field.lessOrEqual("le_field", 18, ":it").withQualifier("q").toWhere(),
"Field WHERE clause not generated correctly")
}
// ~~~ COMPANION OBJECT TESTS ~~~
@Test
@DisplayName("equal constructs a field w/o parameter name")
fun equalCtor() {
val field = Field.equal("Test", 14)
assertEquals("Test", field.name, "Field name not filled correctly")
@ -19,7 +307,18 @@ class FieldTest {
}
@Test
@DisplayName("greater constructs a field")
@DisplayName("equal constructs a field w/ parameter name")
fun equalParameterCtor() {
val field = Field.equal("Test", 14, ":w")
assertEquals("Test", field.name, "Field name not filled correctly")
assertEquals(Op.EQUAL, field.comparison.op, "Field comparison operation not filled correctly")
assertEquals(14, field.comparison.value, "Field comparison value not filled correctly")
assertEquals(":w", field.parameterName, "Field parameter name not filled correctly")
assertNull(field.qualifier, "The qualifier should have been null")
}
@Test
@DisplayName("greater constructs a field w/o parameter name")
fun greaterCtor() {
val field = Field.greater("Great", "night")
assertEquals("Great", field.name, "Field name not filled correctly")
@ -30,7 +329,18 @@ class FieldTest {
}
@Test
@DisplayName("greaterOrEqual constructs a field")
@DisplayName("greater constructs a field w/ parameter name")
fun greaterParameterCtor() {
val field = Field.greater("Great", "night", ":yeah")
assertEquals("Great", field.name, "Field name not filled correctly")
assertEquals(Op.GREATER, field.comparison.op, "Field comparison operation not filled correctly")
assertEquals("night", field.comparison.value, "Field comparison value not filled correctly")
assertEquals(":yeah", field.parameterName, "Field parameter name not filled correctly")
assertNull(field.qualifier, "The qualifier should have been null")
}
@Test
@DisplayName("greaterOrEqual constructs a field w/o parameter name")
fun greaterOrEqualCtor() {
val field = Field.greaterOrEqual("Nice", 88L)
assertEquals("Nice", field.name, "Field name not filled correctly")
@ -41,7 +351,18 @@ class FieldTest {
}
@Test
@DisplayName("less constructs a field")
@DisplayName("greaterOrEqual constructs a field w/ parameter name")
fun greaterOrEqualParameterCtor() {
val field = Field.greaterOrEqual("Nice", 88L, ":nice")
assertEquals("Nice", field.name, "Field name not filled correctly")
assertEquals(Op.GREATER_OR_EQUAL, field.comparison.op, "Field comparison operation not filled correctly")
assertEquals(88L, field.comparison.value, "Field comparison value not filled correctly")
assertEquals(":nice", field.parameterName, "Field parameter name not filled correctly")
assertNull(field.qualifier, "The qualifier should have been null")
}
@Test
@DisplayName("less constructs a field w/o parameter name")
fun lessCtor() {
val field = Field.less("Lesser", "seven")
assertEquals("Lesser", field.name, "Field name not filled correctly")
@ -52,7 +373,18 @@ class FieldTest {
}
@Test
@DisplayName("lessOrEqual constructs a field")
@DisplayName("less constructs a field w/ parameter name")
fun lessParameterCtor() {
val field = Field.less("Lesser", "seven", ":max")
assertEquals("Lesser", field.name, "Field name not filled correctly")
assertEquals(Op.LESS, field.comparison.op, "Field comparison operation not filled correctly")
assertEquals("seven", field.comparison.value, "Field comparison value not filled correctly")
assertEquals(":max", field.parameterName, "Field parameter name not filled correctly")
assertNull(field.qualifier, "The qualifier should have been null")
}
@Test
@DisplayName("lessOrEqual constructs a field w/o parameter name")
fun lessOrEqualCtor() {
val field = Field.lessOrEqual("Nobody", "KNOWS")
assertEquals("Nobody", field.name, "Field name not filled correctly")
@ -63,7 +395,18 @@ class FieldTest {
}
@Test
@DisplayName("notEqual constructs a field")
@DisplayName("lessOrEqual constructs a field w/ parameter name")
fun lessOrEqualParameterCtor() {
val field = Field.lessOrEqual("Nobody", "KNOWS", ":nope")
assertEquals("Nobody", field.name, "Field name not filled correctly")
assertEquals(Op.LESS_OR_EQUAL, field.comparison.op, "Field comparison operation not filled correctly")
assertEquals("KNOWS", field.comparison.value, "Field comparison value not filled correctly")
assertEquals(":nope", field.parameterName, "Field parameter name not filled correctly")
assertNull(field.qualifier, "The qualifier should have been null")
}
@Test
@DisplayName("notEqual constructs a field w/o parameter name")
fun notEqualCtor() {
val field = Field.notEqual("Park", "here")
assertEquals("Park", field.name, "Field name not filled correctly")
@ -74,7 +417,18 @@ class FieldTest {
}
@Test
@DisplayName("between constructs a field")
@DisplayName("notEqual constructs a field w/ parameter name")
fun notEqualParameterCtor() {
val field = Field.notEqual("Park", "here", ":now")
assertEquals("Park", field.name, "Field name not filled correctly")
assertEquals(Op.NOT_EQUAL, field.comparison.op, "Field comparison operation not filled correctly")
assertEquals("here", field.comparison.value, "Field comparison value not filled correctly")
assertEquals(":now", field.parameterName, "Field parameter name not filled correctly")
assertNull(field.qualifier, "The qualifier should have been null")
}
@Test
@DisplayName("between constructs a field w/o parameter name")
fun betweenCtor() {
val field = Field.between("Age", 18, 49)
assertEquals("Age", field.name, "Field name not filled correctly")
@ -86,8 +440,20 @@ class FieldTest {
}
@Test
@DisplayName("any constructs a field")
fun inCtor() {
@DisplayName("between constructs a field w/ parameter name")
fun betweenParameterCtor() {
val field = Field.between("Age", 18, 49, ":limit")
assertEquals("Age", field.name, "Field name not filled correctly")
assertEquals(Op.BETWEEN, field.comparison.op, "Field comparison operation not filled correctly")
assertEquals(18, field.comparison.value.first, "Field comparison min value not filled correctly")
assertEquals(49, field.comparison.value.second, "Field comparison max value not filled correctly")
assertEquals(":limit", field.parameterName, "The parameter name should have been null")
assertNull(field.qualifier, "The qualifier should have been null")
}
@Test
@DisplayName("any constructs a field w/o parameter name")
fun anyCtor() {
val field = Field.any("Here", listOf(8, 16, 32))
assertEquals("Here", field.name, "Field name not filled correctly")
assertEquals(Op.IN, field.comparison.op, "Field comparison operation not filled correctly")
@ -97,7 +463,18 @@ class FieldTest {
}
@Test
@DisplayName("inArray constructs a field")
@DisplayName("any constructs a field w/ parameter name")
fun anyParameterCtor() {
val field = Field.any("Here", listOf(8, 16, 32), ":list")
assertEquals("Here", field.name, "Field name not filled correctly")
assertEquals(Op.IN, field.comparison.op, "Field comparison operation not filled correctly")
assertEquals(listOf(8, 16, 32), field.comparison.value, "Field comparison value not filled correctly")
assertEquals(":list", field.parameterName, "Field parameter name not filled correctly")
assertNull(field.qualifier, "The qualifier should have been null")
}
@Test
@DisplayName("inArray constructs a field w/o parameter name")
fun inArrayCtor() {
val field = Field.inArray("ArrayField", "table", listOf("z"))
assertEquals("ArrayField", field.name, "Field name not filled correctly")
@ -108,6 +485,18 @@ class FieldTest {
assertNull(field.qualifier, "The qualifier should have been null")
}
@Test
@DisplayName("inArray constructs a field w/ parameter name")
fun inArrayParameterCtor() {
val field = Field.inArray("ArrayField", "table", listOf("z"), ":a")
assertEquals("ArrayField", field.name, "Field name not filled correctly")
assertEquals(Op.IN_ARRAY, field.comparison.op, "Field comparison operation not filled correctly")
assertEquals("table", field.comparison.value.first, "Field comparison table not filled correctly")
assertEquals(listOf("z"), field.comparison.value.second, "Field comparison values not filled correctly")
assertEquals(":a", field.parameterName, "Field parameter name not filled correctly")
assertNull(field.qualifier, "The qualifier should have been null")
}
@Test
@DisplayName("exists constructs a field")
fun existsCtor() {
@ -143,135 +532,53 @@ class FieldTest {
@Test
@DisplayName("nameToPath creates a simple PostgreSQL SQL name")
fun nameToPathPostgresSimpleSQL() {
fun nameToPathPostgresSimpleSQL() =
assertEquals("data->>'Simple'", Field.nameToPath("Simple", Dialect.POSTGRESQL, FieldFormat.SQL),
"Path not constructed correctly")
}
@Test
@DisplayName("nameToPath creates a simple SQLite SQL name")
fun nameToPathSQLiteSimpleSQL() {
fun nameToPathSQLiteSimpleSQL() =
assertEquals("data->>'Simple'", Field.nameToPath("Simple", Dialect.SQLITE, FieldFormat.SQL),
"Path not constructed correctly")
}
@Test
@DisplayName("nameToPath creates a nested PostgreSQL SQL name")
fun nameToPathPostgresNestedSQL() {
fun nameToPathPostgresNestedSQL() =
assertEquals("data#>>'{A,Long,Path,to,the,Property}'",
Field.nameToPath("A.Long.Path.to.the.Property", Dialect.POSTGRESQL, FieldFormat.SQL),
"Path not constructed correctly")
}
@Test
@DisplayName("nameToPath creates a nested SQLite SQL name")
fun nameToPathSQLiteNestedSQL() {
fun nameToPathSQLiteNestedSQL() =
assertEquals("data->'A'->'Long'->'Path'->'to'->'the'->>'Property'",
Field.nameToPath("A.Long.Path.to.the.Property", Dialect.SQLITE, FieldFormat.SQL),
"Path not constructed correctly")
}
@Test
@DisplayName("nameToPath creates a simple PostgreSQL JSON name")
fun nameToPathPostgresSimpleJSON() {
fun nameToPathPostgresSimpleJSON() =
assertEquals("data->'Simple'", Field.nameToPath("Simple", Dialect.POSTGRESQL, FieldFormat.JSON),
"Path not constructed correctly")
}
@Test
@DisplayName("nameToPath creates a simple SQLite JSON name")
fun nameToPathSQLiteSimpleJSON() {
fun nameToPathSQLiteSimpleJSON() =
assertEquals("data->'Simple'", Field.nameToPath("Simple", Dialect.SQLITE, FieldFormat.JSON),
"Path not constructed correctly")
}
@Test
@DisplayName("nameToPath creates a nested PostgreSQL JSON name")
fun nameToPathPostgresNestedJSON() {
fun nameToPathPostgresNestedJSON() =
assertEquals("data#>'{A,Long,Path,to,the,Property}'",
Field.nameToPath("A.Long.Path.to.the.Property", Dialect.POSTGRESQL, FieldFormat.JSON),
"Path not constructed correctly")
}
@Test
@DisplayName("nameToPath creates a nested SQLite JSON name")
fun nameToPathSQLiteNestedJSON() {
fun nameToPathSQLiteNestedJSON() =
assertEquals("data->'A'->'Long'->'Path'->'to'->'the'->'Property'",
Field.nameToPath("A.Long.Path.to.the.Property", Dialect.SQLITE, FieldFormat.JSON),
"Path not constructed correctly")
}
@Test
@DisplayName("withParameterName adjusts the parameter name")
fun withParameterName() {
assertEquals(":name", Field.equal("Bob", "Tom").withParameterName(":name").parameterName,
"Parameter name not filled correctly")
}
@Test
@DisplayName("withQualifier adjust the table qualifier")
fun withQualifier() {
assertEquals("joe", Field.equal("Bill", "Matt").withQualifier("joe").qualifier,
"Qualifier not filled correctly")
}
@Test
@DisplayName("path generates for simple unqualified PostgreSQL field")
fun pathPostgresSimpleUnqualified() {
assertEquals("data->>'SomethingCool'",
Field.greaterOrEqual("SomethingCool", 18).path(Dialect.POSTGRESQL, FieldFormat.SQL), "Path not correct")
}
@Test
@DisplayName("path generates for simple qualified PostgreSQL field")
fun pathPostgresSimpleQualified() {
assertEquals("this.data->>'SomethingElse'",
Field.less("SomethingElse", 9).withQualifier("this").path(Dialect.POSTGRESQL, FieldFormat.SQL),
"Path not correct")
}
@Test
@DisplayName("path generates for nested unqualified PostgreSQL field")
fun pathPostgresNestedUnqualified() {
assertEquals("data#>>'{My,Nested,Field}'",
Field.equal("My.Nested.Field", "howdy").path(Dialect.POSTGRESQL, FieldFormat.SQL), "Path not correct")
}
@Test
@DisplayName("path generates for nested qualified PostgreSQL field")
fun pathPostgresNestedQualified() {
assertEquals("bird.data#>>'{Nest,Away}'",
Field.equal("Nest.Away", "doc").withQualifier("bird").path(Dialect.POSTGRESQL, FieldFormat.SQL),
"Path not correct")
}
@Test
@DisplayName("path generates for simple unqualified SQLite field")
fun pathSQLiteSimpleUnqualified() {
assertEquals("data->>'SomethingCool'",
Field.greaterOrEqual("SomethingCool", 18).path(Dialect.SQLITE, FieldFormat.SQL), "Path not correct")
}
@Test
@DisplayName("path generates for simple qualified SQLite field")
fun pathSQLiteSimpleQualified() {
assertEquals("this.data->>'SomethingElse'",
Field.less("SomethingElse", 9).withQualifier("this").path(Dialect.SQLITE, FieldFormat.SQL),
"Path not correct")
}
@Test
@DisplayName("path generates for nested unqualified SQLite field")
fun pathSQLiteNestedUnqualified() {
assertEquals("data->'My'->'Nested'->>'Field'",
Field.equal("My.Nested.Field", "howdy").path(Dialect.SQLITE, FieldFormat.SQL), "Path not correct")
}
@Test
@DisplayName("path generates for nested qualified SQLite field")
fun pathSQLiteNestedQualified() {
assertEquals("bird.data->'Nest'->>'Away'",
Field.equal("Nest.Away", "doc").withQualifier("bird").path(Dialect.SQLITE, FieldFormat.SQL),
"Path not correct")
}
}

View File

@ -1,5 +1,6 @@
package solutions.bitbadger.documents
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
@ -9,6 +10,14 @@ class QueryTest {
/** Test table name */
private val tbl = "test_table"
/**
* Clear the connection string (resets Dialect)
*/
@AfterEach
fun cleanUp() {
Configuration.connectionString = null
}
@Test
@DisplayName("statementWhere generates correctly")
fun statementWhere() {
@ -67,30 +76,17 @@ class QueryTest {
@Test
@DisplayName("insert generates correctly")
fun insert() {
try {
Configuration.connectionString = "postgresql"
assertEquals(
"INSERT INTO $tbl VALUES (:data)",
Query.insert(tbl),
"INSERT statement not constructed correctly"
)
} finally {
Configuration.connectionString = null
}
Configuration.connectionString = ":postgresql:"
assertEquals("INSERT INTO $tbl VALUES (:data)", Query.insert(tbl), "INSERT statement not constructed correctly")
}
@Test
@DisplayName("save generates correctly")
fun save() {
try {
Configuration.connectionString = "postgresql"
assertEquals(
"INSERT INTO $tbl VALUES (:data) ON CONFLICT ((data->>'id')) DO UPDATE SET data = EXCLUDED.data",
Query.save(tbl), "INSERT ON CONFLICT UPDATE statement not constructed correctly"
)
} finally {
Configuration.connectionString = null
}
Configuration.connectionString = ":postgresql:"
assertEquals(
"INSERT INTO $tbl VALUES (:data) ON CONFLICT ((data->>'id')) DO UPDATE SET data = EXCLUDED.data",
Query.save(tbl), "INSERT ON CONFLICT UPDATE statement not constructed correctly")
}
@Test