Initial Development #1
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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]")
|
||||
}
|
||||
}
|
||||
|
@ -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, ""))
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
91
src/test/kotlin/ComparisonTest.kt
Normal file
91
src/test/kotlin/ComparisonTest.kt
Normal 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")
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
34
src/test/kotlin/DialectTest.kt
Normal file
34
src/test/kotlin/DialectTest.kt
Normal 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")
|
||||
}
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user