diff --git a/src/main/kotlin/AutoId.kt b/src/main/kotlin/AutoId.kt index 1d8da22..a5bfa54 100644 --- a/src/main/kotlin/AutoId.kt +++ b/src/main/kotlin/AutoId.kt @@ -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 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") } } } diff --git a/src/main/kotlin/Comparison.kt b/src/main/kotlin/Comparison.kt index bcb4226..987f946 100644 --- a/src/main/kotlin/Comparison.kt +++ b/src/main/kotlin/Comparison.kt @@ -10,16 +10,15 @@ class Comparison(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 + } } diff --git a/src/main/kotlin/Dialect.kt b/src/main/kotlin/Dialect.kt index e19807d..986dddb 100644 --- a/src/main/kotlin/Dialect.kt +++ b/src/main/kotlin/Dialect.kt @@ -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]") } } diff --git a/src/main/kotlin/Field.kt b/src/main/kotlin/Field.kt index 1c8102d..11fb00d 100644 --- a/src/main/kotlin/Field.kt +++ b/src/main/kotlin/Field.kt @@ -20,8 +20,11 @@ class Field 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 = - Field(name, comparison, paramName, qualifier) + fun withParameterName(paramName: String): Field { + 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 private constructor( * @param alias The table alias for this field * @return A new `Field` with the table qualifier specified */ - fun withQualifier(alias: String): Field = + fun withQualifier(alias: String) = Field(name, comparison, parameterName, alias) /** @@ -74,16 +77,16 @@ class Field 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 ?: 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 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 equal(name: String, value: T) = - Field(name, Comparison(Op.EQUAL, value)) + fun 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 greater(name: String, value: T) = - Field(name, Comparison(Op.GREATER, value)) + fun 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 greaterOrEqual(name: String, value: T) = - Field(name, Comparison(Op.GREATER_OR_EQUAL, value)) + fun 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 less(name: String, value: T) = - Field(name, Comparison(Op.LESS, value)) + fun 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 lessOrEqual(name: String, value: T) = - Field(name, Comparison(Op.LESS_OR_EQUAL, value)) + fun 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 notEqual(name: String, value: T) = - Field(name, Comparison(Op.NOT_EQUAL, value)) + fun 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 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 between(name: String, minValue: T, maxValue: T) = - Field(name, Comparison(Op.BETWEEN, Pair(minValue, maxValue))) + fun 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 any(name: String, values: List) = - Field(name, Comparison(Op.IN, values)) + fun any(name: String, values: Collection, 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 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 inArray(name: String, tableName: String, values: List) = - Field(name, Comparison(Op.IN_ARRAY, Pair(tableName, values))) + fun inArray(name: String, tableName: String, values: Collection, paramName: String? = null) = + Field(name, Comparison(Op.IN_ARRAY, Pair(tableName, values)), paramName) fun exists(name: String) = Field(name, Comparison(Op.EXISTS, "")) diff --git a/src/main/kotlin/Query.kt b/src/main/kotlin/Query.kt index 8348c35..6852f91 100644 --- a/src/main/kotlin/Query.kt +++ b/src/main/kotlin/Query.kt @@ -34,7 +34,7 @@ object Query { * @param docId The ID value (optional; used for type determinations, string assumed if not provided) */ fun 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) diff --git a/src/test/kotlin/AutoIdTest.kt b/src/test/kotlin/AutoIdTest.kt index 1098e1d..f99c0e0 100644 --- a/src/test/kotlin/AutoIdTest.kt +++ b/src/test/kotlin/AutoIdTest.kt @@ -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 { AutoId.needsAutoId(AutoId.DISABLED, null, "id") } + assertThrows { AutoId.needsAutoId(AutoId.DISABLED, null, "id") } } @Test @DisplayName("needsAutoId fails for missing ID property") fun needsAutoIdFailsForMissingId() { - assertThrows { AutoId.needsAutoId(AutoId.UUID, IntIdClass(0), "Id") } + assertThrows { 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 { AutoId.needsAutoId(AutoId.NUMBER, StringIdClass(""), "id") } + assertThrows { 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 { AutoId.needsAutoId(AutoId.UUID, IntIdClass(5), "id") } + assertThrows { 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 { AutoId.needsAutoId(AutoId.RANDOM_STRING, ShortIdClass(55), "id") } + assertThrows { 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) diff --git a/src/test/kotlin/ComparisonTest.kt b/src/test/kotlin/ComparisonTest.kt new file mode 100644 index 0000000..7c015e0 --- /dev/null +++ b/src/test/kotlin/ComparisonTest.kt @@ -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()).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(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(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(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(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") +} diff --git a/src/test/kotlin/ConfigurationTest.kt b/src/test/kotlin/ConfigurationTest.kt index 8dc67d1..cc8f06d 100644 --- a/src/test/kotlin/ConfigurationTest.kt +++ b/src/test/kotlin/ConfigurationTest.kt @@ -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 { Configuration.dialect() } + Configuration.connectionString = "jdbc:postgresql:db" + assertEquals(Dialect.POSTGRESQL, Configuration.dialect()) + } finally { + Configuration.connectionString = null + } + } } diff --git a/src/test/kotlin/DialectTest.kt b/src/test/kotlin/DialectTest.kt new file mode 100644 index 0000000..a7e4015 --- /dev/null +++ b/src/test/kotlin/DialectTest.kt @@ -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") + } + } +} diff --git a/src/test/kotlin/FieldTest.kt b/src/test/kotlin/FieldTest.kt index 1114e0b..9782532 100644 --- a/src/test/kotlin/FieldTest.kt +++ b/src/test/kotlin/FieldTest.kt @@ -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 { 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") - } } diff --git a/src/test/kotlin/QueryTest.kt b/src/test/kotlin/QueryTest.kt index 3799154..16de55a 100644 --- a/src/test/kotlin/QueryTest.kt +++ b/src/test/kotlin/QueryTest.kt @@ -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