Move query to package

This commit is contained in:
2025-02-22 11:55:55 -05:00
parent 31817d5cec
commit 07edc6ee43
29 changed files with 1495 additions and 1190 deletions

View File

@@ -8,6 +8,10 @@ import kotlin.test.assertFalse
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
/**
* Unit tests for the `AutoId` enum
*/
@DisplayName("AutoId")
class AutoIdTest {
@Test

View File

@@ -5,6 +5,10 @@ import org.junit.jupiter.api.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
/**
* Unit tests for the `Comparison` class
*/
@DisplayName("Comparison")
class ComparisonTest {
@Test

View File

@@ -7,6 +7,10 @@ import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
/**
* Unit tests for the `Configuration` object
*/
@DisplayName("Configuration")
class ConfigurationTest {
@Test

View File

@@ -6,6 +6,10 @@ import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
/**
* Unit tests for the `Dialect` enum
*/
@DisplayName("Dialect")
class DialectTest {
@Test

View File

@@ -4,6 +4,10 @@ import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
/**
* Unit tests for the `FieldMatch` enum
*/
@DisplayName("FieldMatch")
class FieldMatchTest {
@Test

View File

@@ -8,6 +8,10 @@ import kotlin.test.assertEquals
import kotlin.test.assertNotSame
import kotlin.test.assertNull
/**
* Unit tests for the `Field` class
*/
@DisplayName("Field")
class FieldTest {
/**

View File

@@ -4,6 +4,10 @@ import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
/**
* Unit tests for the `Op` enum
*/
@DisplayName("Op")
class OpTest {
@Test

View File

@@ -4,6 +4,10 @@ import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
/**
* Unit tests for the `ParameterName` class
*/
@DisplayName("ParameterName")
class ParameterNameTest {
@Test

View File

@@ -6,6 +6,10 @@ import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull
/**
* Unit tests for the `Parameter` class
*/
@DisplayName("Parameter")
class ParameterTest {
@Test

View File

@@ -6,6 +6,10 @@ import kotlin.test.assertEquals
import kotlin.test.assertNotSame
import kotlin.test.assertSame
/**
* Unit tests for the `Parameters` object
*/
@DisplayName("Parameters")
class ParametersTest {
@Test

View File

@@ -1,685 +0,0 @@
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.assertTrue
class QueryTest {
/** Test table name */
private val tbl = "test_table"
/** Dummy connection string for PostgreSQL */
private val pg = ":postgresql:"
/** Dummy connection string for SQLite */
private val lite = ":sqlite:"
/**
* Clear the connection string (resets Dialect)
*/
@AfterEach
fun cleanUp() {
Configuration.connectionString = null
}
@Test
@DisplayName("statementWhere generates correctly")
fun statementWhere() =
assertEquals("x WHERE y", Query.statementWhere("x", "y"), "Statements not combined correctly")
// ~~~ Where ~~~
@Test
@DisplayName("Where.byFields is blank when given no fields")
fun whereByFieldsBlankIfEmpty() =
assertEquals("", Query.Where.byFields(listOf()))
@Test
@DisplayName("Where.byFields generates one numeric field (PostgreSQL)")
fun whereByFieldsOneFieldPostgres() {
Configuration.connectionString = pg
assertEquals("(data->>'it')::numeric = :that", Query.Where.byFields(listOf(Field.equal("it", 9, ":that"))))
}
@Test
@DisplayName("Where.byFields generates one alphanumeric field (PostgreSQL)")
fun whereByFieldsOneAlphaFieldPostgres() {
Configuration.connectionString = pg
assertEquals("data->>'it' = :that", Query.Where.byFields(listOf(Field.equal("it", "", ":that"))))
}
@Test
@DisplayName("Where.byFields generates one field (SQLite)")
fun whereByFieldsOneFieldSQLite() {
Configuration.connectionString = lite
assertEquals("data->>'it' = :that", Query.Where.byFields(listOf(Field.equal("it", "", ":that"))))
}
@Test
@DisplayName("Where.byFields generates multiple fields w/ default match (PostgreSQL)")
fun whereByFieldsMultipleDefaultPostgres() {
Configuration.connectionString = pg
assertEquals(
"data->>'1' = :one AND (data->>'2')::numeric = :two AND data->>'3' = :three",
Query.Where.byFields(
listOf(Field.equal("1", "", ":one"), Field.equal("2", 0L, ":two"), Field.equal("3", "", ":three"))
)
)
}
@Test
@DisplayName("Where.byFields generates multiple fields w/ default match (SQLite)")
fun whereByFieldsMultipleDefaultSQLite() {
Configuration.connectionString = lite
assertEquals(
"data->>'1' = :one AND data->>'2' = :two AND data->>'3' = :three",
Query.Where.byFields(
listOf(Field.equal("1", "", ":one"), Field.equal("2", 0L, ":two"), Field.equal("3", "", ":three"))
)
)
}
@Test
@DisplayName("Where.byFields generates multiple fields w/ ANY match (PostgreSQL)")
fun whereByFieldsMultipleAnyPostgres() {
Configuration.connectionString = pg
assertEquals(
"data->>'1' = :one OR (data->>'2')::numeric = :two OR data->>'3' = :three",
Query.Where.byFields(
listOf(Field.equal("1", "", ":one"), Field.equal("2", 0L, ":two"), Field.equal("3", "", ":three")),
FieldMatch.ANY
)
)
}
@Test
@DisplayName("Where.byFields generates multiple fields w/ ANY match (SQLite)")
fun whereByFieldsMultipleAnySQLite() {
Configuration.connectionString = lite
assertEquals(
"data->>'1' = :one OR data->>'2' = :two OR data->>'3' = :three",
Query.Where.byFields(
listOf(Field.equal("1", "", ":one"), Field.equal("2", 0L, ":two"), Field.equal("3", "", ":three")),
FieldMatch.ANY
)
)
}
@Test
@DisplayName("Where.byId generates defaults for alphanumeric key (PostgreSQL)")
fun whereByIdDefaultAlphaPostgres() {
Configuration.connectionString = pg
assertEquals("data->>'id' = :id", Query.Where.byId(docId = ""))
}
@Test
@DisplayName("Where.byId generates defaults for numeric key (PostgreSQL)")
fun whereByIdDefaultNumericPostgres() {
Configuration.connectionString = pg
assertEquals("(data->>'id')::numeric = :id", Query.Where.byId(docId = 5))
}
@Test
@DisplayName("Where.byId generates defaults (SQLite)")
fun whereByIdDefaultSQLite() {
Configuration.connectionString = lite
assertEquals("data->>'id' = :id", Query.Where.byId(docId = ""))
}
@Test
@DisplayName("Where.byId generates named ID (PostgreSQL)")
fun whereByIdDefaultNamedPostgres() {
Configuration.connectionString = pg
assertEquals("data->>'id' = :key", Query.Where.byId<String>(":key"))
}
@Test
@DisplayName("Where.byId generates named ID (SQLite)")
fun whereByIdDefaultNamedSQLite() {
Configuration.connectionString = lite
assertEquals("data->>'id' = :key", Query.Where.byId<String>(":key"))
}
@Test
@DisplayName("Where.jsonContains generates defaults (PostgreSQL)")
fun whereJsonContainsDefaultPostgres() {
Configuration.connectionString = pg
assertEquals("data @> :criteria", Query.Where.jsonContains())
}
@Test
@DisplayName("Where.jsonContains generates named parameter (PostgreSQL)")
fun whereJsonContainsNamedPostgres() {
Configuration.connectionString = pg
assertEquals("data @> :it", Query.Where.jsonContains(":it"))
}
@Test
@DisplayName("Where.jsonContains fails (SQLite)")
fun whereJsonContainsFailsSQLite() {
Configuration.connectionString = lite
assertThrows<DocumentException> { Query.Where.jsonContains() }
}
@Test
@DisplayName("Where.jsonPathMatch generates defaults (PostgreSQL)")
fun whereJsonPathMatchDefaultPostgres() {
Configuration.connectionString = pg
assertEquals("jsonb_path_exists(data, :path::jsonpath)", Query.Where.jsonPathMatches())
}
@Test
@DisplayName("Where.jsonPathMatch generates named parameter (PostgreSQL)")
fun whereJsonPathMatchNamedPostgres() {
Configuration.connectionString = pg
assertEquals("jsonb_path_exists(data, :jp::jsonpath)", Query.Where.jsonPathMatches(":jp"))
}
@Test
@DisplayName("Where.jsonPathMatch fails (SQLite)")
fun whereJsonPathFailsSQLite() {
Configuration.connectionString = lite
assertThrows<DocumentException> { Query.Where.jsonPathMatches() }
}
// ~~~ root functions ~~~
@Test
@DisplayName("byId generates a numeric ID query (PostgreSQL)")
fun byIdNumericPostgres() {
Configuration.connectionString = pg
assertEquals("test WHERE (data->>'id')::numeric = :id", Query.byId("test", 9))
}
@Test
@DisplayName("byId generates an alphanumeric ID query (PostgreSQL)")
fun byIdAlphaPostgres() {
Configuration.connectionString = pg
assertEquals("unit WHERE data->>'id' = :id", Query.byId("unit", "18"))
}
@Test
@DisplayName("byId generates ID query (SQLite)")
fun byIdSQLite() {
Configuration.connectionString = lite
assertEquals("yo WHERE data->>'id' = :id", Query.byId("yo", 27))
}
@Test
@DisplayName("byFields generates default field query (PostgreSQL)")
fun byFieldsMultipleDefaultPostgres() {
Configuration.connectionString = pg
assertEquals(
"this WHERE data->>'a' = :the_a AND (data->>'b')::numeric = :b_value",
Query.byFields("this", listOf(Field.equal("a", "", ":the_a"), Field.equal("b", 0, ":b_value")))
)
}
@Test
@DisplayName("byFields generates default field query (SQLite)")
fun byFieldsMultipleDefaultSQLite() {
Configuration.connectionString = lite
assertEquals(
"this WHERE data->>'a' = :the_a AND data->>'b' = :b_value",
Query.byFields("this", listOf(Field.equal("a", "", ":the_a"), Field.equal("b", 0, ":b_value")))
)
}
@Test
@DisplayName("byFields generates ANY field query (PostgreSQL)")
fun byFieldsMultipleAnyPostgres() {
Configuration.connectionString = pg
assertEquals(
"that WHERE data->>'a' = :the_a OR (data->>'b')::numeric = :b_value",
Query.byFields(
"that", listOf(Field.equal("a", "", ":the_a"), Field.equal("b", 0, ":b_value")),
FieldMatch.ANY
)
)
}
@Test
@DisplayName("byFields generates ANY field query (SQLite)")
fun byFieldsMultipleAnySQLite() {
Configuration.connectionString = lite
assertEquals(
"that WHERE data->>'a' = :the_a OR data->>'b' = :b_value",
Query.byFields(
"that", listOf(Field.equal("a", "", ":the_a"), Field.equal("b", 0, ":b_value")),
FieldMatch.ANY
)
)
}
// ~~~ Definition ~~~
@Test
@DisplayName("Definition.ensureTableFor generates correctly")
fun ensureTableFor() =
assertEquals(
"CREATE TABLE IF NOT EXISTS my.table (data JSONB NOT NULL)",
Query.Definition.ensureTableFor("my.table", "JSONB"), "CREATE TABLE statement not constructed correctly"
)
@Test
@DisplayName("Definition.ensureTable generates correctly (PostgreSQL)")
fun ensureTablePostgres() {
Configuration.connectionString = pg
assertEquals("CREATE TABLE IF NOT EXISTS $tbl (data JSONB NOT NULL)", Query.Definition.ensureTable(tbl))
}
@Test
@DisplayName("Definition.ensureTable generates correctly (SQLite)")
fun ensureTableSQLite() {
Configuration.connectionString = lite
assertEquals("CREATE TABLE IF NOT EXISTS $tbl (data TEXT NOT NULL)", Query.Definition.ensureTable(tbl))
}
@Test
@DisplayName("Definition.ensureTable fails when no dialect is set")
fun ensureTableFailsUnknown() {
assertThrows<DocumentException> { Query.Definition.ensureTable(tbl) }
}
@Test
@DisplayName("Definition.ensureKey generates correctly with schema")
fun ensureKeyWithSchema() =
assertEquals(
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON test.table ((data->>'id'))",
Query.Definition.ensureKey("test.table", Dialect.POSTGRESQL),
"CREATE INDEX for key statement with schema not constructed correctly"
)
@Test
@DisplayName("Definition.ensureKey generates correctly without schema")
fun ensureKeyWithoutSchema() =
assertEquals(
"CREATE UNIQUE INDEX IF NOT EXISTS idx_${tbl}_key ON $tbl ((data->>'id'))",
Query.Definition.ensureKey(tbl, Dialect.SQLITE),
"CREATE INDEX for key statement without schema not constructed correctly"
)
@Test
@DisplayName("Definition.ensureIndexOn generates multiple fields and directions")
fun ensureIndexOnMultipleFields() =
assertEquals(
"CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table ((data->>'taco'), (data->>'guac') DESC, (data->>'salsa') ASC)",
Query.Definition.ensureIndexOn(
"test.table", "gibberish", listOf("taco", "guac DESC", "salsa ASC"),
Dialect.POSTGRESQL
),
"CREATE INDEX for multiple field statement not constructed correctly"
)
@Test
@DisplayName("Definition.ensureIndexOn generates nested PostgreSQL field")
fun ensureIndexOnNestedPostgres() =
assertEquals(
"CREATE INDEX IF NOT EXISTS idx_${tbl}_nest ON $tbl ((data#>>'{a,b,c}'))",
Query.Definition.ensureIndexOn(tbl, "nest", listOf("a.b.c"), Dialect.POSTGRESQL),
"CREATE INDEX for nested PostgreSQL field incorrect"
)
@Test
@DisplayName("Definition.ensureIndexOn generates nested SQLite field")
fun ensureIndexOnNestedSQLite() =
assertEquals(
"CREATE INDEX IF NOT EXISTS idx_${tbl}_nest ON $tbl ((data->'a'->'b'->>'c'))",
Query.Definition.ensureIndexOn(tbl, "nest", listOf("a.b.c"), Dialect.SQLITE),
"CREATE INDEX for nested SQLite field incorrect"
)
// ~~~ root functions ~~~
@Test
@DisplayName("insert generates no auto ID (PostgreSQL)")
fun insertNoAutoPostgres() {
Configuration.connectionString = pg
assertEquals("INSERT INTO $tbl VALUES (:data)", Query.insert(tbl))
}
@Test
@DisplayName("insert generates no auto ID (SQLite)")
fun insertNoAutoSQLite() {
Configuration.connectionString = lite
assertEquals("INSERT INTO $tbl VALUES (:data)", Query.insert(tbl))
}
@Test
@DisplayName("insert generates auto number (PostgreSQL)")
fun insertAutoNumberPostgres() {
Configuration.connectionString = pg
assertEquals(
"INSERT INTO $tbl VALUES (:data::jsonb || ('{\"id\":' " +
"|| (SELECT COALESCE(MAX((data->>'id')::numeric), 0) + 1 FROM $tbl) || '}')::jsonb)",
Query.insert(tbl, AutoId.NUMBER)
)
}
@Test
@DisplayName("insert generates auto number (SQLite)")
fun insertAutoNumberSQLite() {
Configuration.connectionString = lite
assertEquals(
"INSERT INTO $tbl VALUES (json_set(:data, '$.id', " +
"(SELECT coalesce(max(data->>'id'), 0) + 1 FROM $tbl)))",
Query.insert(tbl, AutoId.NUMBER)
)
}
@Test
@DisplayName("insert generates auto UUID (PostgreSQL)")
fun insertAutoUUIDPostgres() {
Configuration.connectionString = pg
val query = Query.insert(tbl, AutoId.UUID)
assertTrue(
query.startsWith("INSERT INTO $tbl VALUES (:data::jsonb || '{\"id\":\""),
"Query start not correct (actual: $query)"
)
assertTrue(query.endsWith("\"}')"), "Query end not correct")
}
@Test
@DisplayName("insert generates auto UUID (SQLite)")
fun insertAutoUUIDSQLite() {
Configuration.connectionString = lite
val query = Query.insert(tbl, AutoId.UUID)
assertTrue(
query.startsWith("INSERT INTO $tbl VALUES (json_set(:data, '$.id', '"),
"Query start not correct (actual: $query)"
)
assertTrue(query.endsWith("'))"), "Query end not correct")
}
@Test
@DisplayName("insert generates auto random string (PostgreSQL)")
fun insertAutoRandomPostgres() {
try {
Configuration.connectionString = pg
Configuration.idStringLength = 8
val query = Query.insert(tbl, AutoId.RANDOM_STRING)
assertTrue(
query.startsWith("INSERT INTO $tbl VALUES (:data::jsonb || '{\"id\":\""),
"Query start not correct (actual: $query)"
)
assertTrue(query.endsWith("\"}')"), "Query end not correct")
assertEquals(
8,
query.replace("INSERT INTO $tbl VALUES (:data::jsonb || '{\"id\":\"", "").replace("\"}')", "").length,
"Random string length incorrect"
)
} finally {
Configuration.idStringLength = 16
}
}
@Test
@DisplayName("insert generates auto random string (SQLite)")
fun insertAutoRandomSQLite() {
Configuration.connectionString = lite
val query = Query.insert(tbl, AutoId.RANDOM_STRING)
assertTrue(
query.startsWith("INSERT INTO $tbl VALUES (json_set(:data, '$.id', '"),
"Query start not correct (actual: $query)"
)
assertTrue(query.endsWith("'))"), "Query end not correct")
assertEquals(
Configuration.idStringLength,
query.replace("INSERT INTO $tbl VALUES (json_set(:data, '$.id', '", "").replace("'))", "").length,
"Random string length incorrect"
)
}
@Test
@DisplayName("insert fails when no dialect is set")
fun insertFailsUnknown() {
assertThrows<DocumentException> { Query.insert(tbl) }
}
@Test
@DisplayName("save generates correctly")
fun save() {
Configuration.connectionString = pg
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
@DisplayName("Count.all generates correctly")
fun countAll() =
assertEquals("SELECT COUNT(*) AS it FROM $tbl", Query.Count.all(tbl), "Count query not constructed correctly")
@Test
@DisplayName("Count.byFields generates correctly (PostgreSQL)")
fun countByFieldsPostgres() {
Configuration.connectionString = pg
assertEquals(
"SELECT COUNT(*) AS it FROM $tbl WHERE data->>'test' = :field0",
Query.Count.byFields(tbl, listOf(Field.equal("test", "", ":field0"))),
"Count query not constructed correctly"
)
}
@Test
@DisplayName("Count.byFields generates correctly (PostgreSQL)")
fun countByFieldsSQLite() {
Configuration.connectionString = lite
assertEquals(
"SELECT COUNT(*) AS it FROM $tbl WHERE data->>'test' = :field0",
Query.Count.byFields(tbl, listOf(Field.equal("test", "", ":field0"))),
"Count query not constructed correctly"
)
}
@Test
@DisplayName("Count.byContains generates correctly (PostgreSQL)")
fun countByContainsPostgres() {
Configuration.connectionString = pg
assertEquals(
"SELECT COUNT(*) AS it FROM $tbl WHERE data @> :criteria", Query.Count.byContains(tbl),
"Count query not constructed correctly"
)
}
@Test
@DisplayName("Count.byContains fails (SQLite)")
fun countByContainsSQLite() {
Configuration.connectionString = lite
assertThrows<DocumentException> { Query.Count.byContains(tbl) }
}
@Test
@DisplayName("Count.byJsonPath generates correctly (PostgreSQL)")
fun countByJsonPathPostgres() {
Configuration.connectionString = pg
assertEquals(
"SELECT COUNT(*) AS it FROM $tbl WHERE jsonb_path_exists(data, :path::jsonpath)",
Query.Count.byJsonPath(tbl), "Count query not constructed correctly"
)
}
@Test
@DisplayName("Count.byJsonPath fails (SQLite)")
fun countByJsonPathSQLite() {
Configuration.connectionString = lite
assertThrows<DocumentException> { Query.Count.byJsonPath(tbl) }
}
@Test
@DisplayName("Exists.byId generates correctly (PostgreSQL)")
fun existsByIdPostgres() {
Configuration.connectionString = pg
assertEquals(
"SELECT EXISTS (SELECT 1 FROM $tbl WHERE data->>'id' = :id) AS it",
Query.Exists.byId<String>(tbl), "Exists query not constructed correctly"
)
}
@Test
@DisplayName("Exists.byId generates correctly (SQLite)")
fun existsByIdSQLite() {
Configuration.connectionString = lite
assertEquals(
"SELECT EXISTS (SELECT 1 FROM $tbl WHERE data->>'id' = :id) AS it",
Query.Exists.byId<String>(tbl), "Exists query not constructed correctly"
)
}
@Test
@DisplayName("Exists.byFields generates correctly (PostgreSQL)")
fun existsByFieldsPostgres() {
Configuration.connectionString = pg
assertEquals(
"SELECT EXISTS (SELECT 1 FROM $tbl WHERE (data->>'it')::numeric = :test) AS it",
Query.Exists.byFields(tbl, listOf(Field.equal("it", 7, ":test"))),
"Exists query not constructed correctly"
)
}
@Test
@DisplayName("Exists.byFields generates correctly (SQLite)")
fun existsByFieldsSQLite() {
Configuration.connectionString = lite
assertEquals(
"SELECT EXISTS (SELECT 1 FROM $tbl WHERE data->>'it' = :test) AS it",
Query.Exists.byFields(tbl, listOf(Field.equal("it", 7, ":test"))),
"Exists query not constructed correctly"
)
}
@Test
@DisplayName("Exists.byContains generates correctly (PostgreSQL)")
fun existsByContainsPostgres() {
Configuration.connectionString = pg
assertEquals(
"SELECT EXISTS (SELECT 1 FROM $tbl WHERE data @> :criteria) AS it", Query.Exists.byContains(tbl),
"Exists query not constructed correctly"
)
}
@Test
@DisplayName("Exists.byContains fails (SQLite)")
fun existsByContainsSQLite() {
Configuration.connectionString = lite
assertThrows<DocumentException> { Query.Exists.byContains(tbl) }
}
@Test
@DisplayName("Exists.byJsonPath generates correctly (PostgreSQL)")
fun existsByJsonPathPostgres() {
Configuration.connectionString = pg
assertEquals(
"SELECT EXISTS (SELECT 1 FROM $tbl WHERE jsonb_path_exists(data, :path::jsonpath)) AS it",
Query.Exists.byJsonPath(tbl), "Exists query not constructed correctly"
)
}
@Test
@DisplayName("Exists.byJsonPath fails (SQLite)")
fun existsByJsonPathSQLite() {
Configuration.connectionString = lite
assertThrows<DocumentException> { Query.Exists.byJsonPath(tbl) }
}
@Test
@DisplayName("Find.all generates correctly")
fun findAll() =
assertEquals("SELECT data FROM $tbl", Query.Find.all(tbl), "Find query not constructed correctly")
@Test
@DisplayName("update generates successfully")
fun update() =
assertEquals("UPDATE $tbl SET data = :data", Query.update(tbl), "Update query not constructed correctly")
// @Test
// @DisplayName("delete generates successfully")
// fun delete() =
// assertEquals("DELETE FROM $tbl", Query.delete(tbl), "Delete query not constructed correctly")
@Test
@DisplayName("orderBy generates for no fields")
fun orderByNone() {
assertEquals("", Query.orderBy(listOf(), Dialect.POSTGRESQL), "ORDER BY should have been blank (PostgreSQL)")
assertEquals("", Query.orderBy(listOf(), Dialect.SQLITE), "ORDER BY should have been blank (SQLite)")
}
@Test
@DisplayName("orderBy generates single, no direction for PostgreSQL")
fun orderBySinglePostgres() =
assertEquals(
" ORDER BY data->>'TestField'",
Query.orderBy(listOf(Field.named("TestField")), Dialect.POSTGRESQL), "ORDER BY not constructed correctly"
)
@Test
@DisplayName("orderBy generates single, no direction for SQLite")
fun orderBySingleSQLite() =
assertEquals(
" ORDER BY data->>'TestField'", Query.orderBy(listOf(Field.named("TestField")), Dialect.SQLITE),
"ORDER BY not constructed correctly"
)
@Test
@DisplayName("orderBy generates multiple with direction for PostgreSQL")
fun orderByMultiplePostgres() =
assertEquals(
" ORDER BY data#>>'{Nested,Test,Field}' DESC, data->>'AnotherField', data->>'It' DESC",
Query.orderBy(
listOf(Field.named("Nested.Test.Field DESC"), Field.named("AnotherField"), Field.named("It DESC")),
Dialect.POSTGRESQL
),
"ORDER BY not constructed correctly"
)
@Test
@DisplayName("orderBy generates multiple with direction for SQLite")
fun orderByMultipleSQLite() =
assertEquals(
" ORDER BY data->'Nested'->'Test'->>'Field' DESC, data->>'AnotherField', data->>'It' DESC",
Query.orderBy(
listOf(Field.named("Nested.Test.Field DESC"), Field.named("AnotherField"), Field.named("It DESC")),
Dialect.SQLITE
),
"ORDER BY not constructed correctly"
)
@Test
@DisplayName("orderBy generates numeric ordering PostgreSQL")
fun orderByNumericPostgres() =
assertEquals(
" ORDER BY (data->>'Test')::numeric",
Query.orderBy(listOf(Field.named("n:Test")), Dialect.POSTGRESQL), "ORDER BY not constructed correctly"
)
@Test
@DisplayName("orderBy generates numeric ordering for SQLite")
fun orderByNumericSQLite() =
assertEquals(
" ORDER BY data->>'Test'", Query.orderBy(listOf(Field.named("n:Test")), Dialect.SQLITE),
"ORDER BY not constructed correctly"
)
@Test
@DisplayName("orderBy generates case-insensitive ordering for PostgreSQL")
fun orderByCIPostgres() =
assertEquals(
" ORDER BY LOWER(data#>>'{Test,Field}') DESC NULLS FIRST",
Query.orderBy(listOf(Field.named("i:Test.Field DESC NULLS FIRST")), Dialect.POSTGRESQL),
"ORDER BY not constructed correctly"
)
@Test
@DisplayName("orderBy generates case-insensitive ordering for SQLite")
fun orderByCISQLite() =
assertEquals(
" ORDER BY data->'Test'->>'Field' COLLATE NOCASE ASC NULLS LAST",
Query.orderBy(listOf(Field.named("i:Test.Field ASC NULLS LAST")), Dialect.SQLITE),
"ORDER BY not constructed correctly"
)
}

View File

@@ -0,0 +1,87 @@
package solutions.bitbadger.documents.query
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 solutions.bitbadger.documents.*
import kotlin.test.assertEquals
/**
* Unit tests for the `Count` object
*/
@DisplayName("Count (Query)")
class CountTest {
/** Test table name */
private val tbl = "test_table"
/**
* Clear the connection string (resets Dialect)
*/
@AfterEach
fun cleanUp() {
Configuration.dialectValue = null
}
@Test
@DisplayName("all generates correctly")
fun all() =
assertEquals("SELECT COUNT(*) AS it FROM $tbl", Count.all(tbl), "Count query not constructed correctly")
@Test
@DisplayName("byFields generates correctly (PostgreSQL)")
fun byFieldsPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals(
"SELECT COUNT(*) AS it FROM $tbl WHERE data->>'test' = :field0",
Count.byFields(tbl, listOf(Field.equal("test", "", ":field0"))),
"Count query not constructed correctly"
)
}
@Test
@DisplayName("byFields generates correctly (PostgreSQL)")
fun byFieldsSQLite() {
Configuration.dialectValue = Dialect.SQLITE
assertEquals(
"SELECT COUNT(*) AS it FROM $tbl WHERE data->>'test' = :field0",
Count.byFields(tbl, listOf(Field.equal("test", "", ":field0"))),
"Count query not constructed correctly"
)
}
@Test
@DisplayName("byContains generates correctly (PostgreSQL)")
fun byContainsPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals(
"SELECT COUNT(*) AS it FROM $tbl WHERE data @> :criteria", Count.byContains(tbl),
"Count query not constructed correctly"
)
}
@Test
@DisplayName("byContains fails (SQLite)")
fun byContainsSQLite() {
Configuration.dialectValue = Dialect.SQLITE
assertThrows<DocumentException> { Count.byContains(tbl) }
}
@Test
@DisplayName("byJsonPath generates correctly (PostgreSQL)")
fun byJsonPathPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals(
"SELECT COUNT(*) AS it FROM $tbl WHERE jsonb_path_exists(data, :path::jsonpath)",
Count.byJsonPath(tbl), "Count query not constructed correctly"
)
}
@Test
@DisplayName("byJsonPath fails (SQLite)")
fun byJsonPathSQLite() {
Configuration.dialectValue = Dialect.SQLITE
assertThrows<DocumentException> { Count.byJsonPath(tbl) }
}
}

View File

@@ -0,0 +1,104 @@
package solutions.bitbadger.documents.query
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 solutions.bitbadger.documents.Configuration
import solutions.bitbadger.documents.Dialect
import solutions.bitbadger.documents.DocumentException
import kotlin.test.assertEquals
/**
* Unit tests for the `Definition` object
*/
@DisplayName("Definition (Query)")
class DefinitionTest {
/** Test table name */
private val tbl = "test_table"
/**
* Clear the connection string (resets Dialect)
*/
@AfterEach
fun cleanUp() {
Configuration.dialectValue = null
}
@Test
@DisplayName("ensureTableFor generates correctly")
fun ensureTableFor() =
assertEquals(
"CREATE TABLE IF NOT EXISTS my.table (data JSONB NOT NULL)",
Definition.ensureTableFor("my.table", "JSONB"), "CREATE TABLE statement not constructed correctly"
)
@Test
@DisplayName("ensureTable generates correctly (PostgreSQL)")
fun ensureTablePostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals("CREATE TABLE IF NOT EXISTS $tbl (data JSONB NOT NULL)", Definition.ensureTable(tbl))
}
@Test
@DisplayName("ensureTable generates correctly (SQLite)")
fun ensureTableSQLite() {
Configuration.dialectValue = Dialect.SQLITE
assertEquals("CREATE TABLE IF NOT EXISTS $tbl (data TEXT NOT NULL)", Definition.ensureTable(tbl))
}
@Test
@DisplayName("ensureTable fails when no dialect is set")
fun ensureTableFailsUnknown() {
assertThrows<DocumentException> { Definition.ensureTable(tbl) }
}
@Test
@DisplayName("ensureKey generates correctly with schema")
fun ensureKeyWithSchema() =
assertEquals(
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON test.table ((data->>'id'))",
Definition.ensureKey("test.table", Dialect.POSTGRESQL),
"CREATE INDEX for key statement with schema not constructed correctly"
)
@Test
@DisplayName("ensureKey generates correctly without schema")
fun ensureKeyWithoutSchema() =
assertEquals(
"CREATE UNIQUE INDEX IF NOT EXISTS idx_${tbl}_key ON $tbl ((data->>'id'))",
Definition.ensureKey(tbl, Dialect.SQLITE),
"CREATE INDEX for key statement without schema not constructed correctly"
)
@Test
@DisplayName("ensureIndexOn generates multiple fields and directions")
fun ensureIndexOnMultipleFields() =
assertEquals(
"CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table ((data->>'taco'), (data->>'guac') DESC, (data->>'salsa') ASC)",
Definition.ensureIndexOn(
"test.table", "gibberish", listOf("taco", "guac DESC", "salsa ASC"),
Dialect.POSTGRESQL
),
"CREATE INDEX for multiple field statement not constructed correctly"
)
@Test
@DisplayName("ensureIndexOn generates nested PostgreSQL field")
fun ensureIndexOnNestedPostgres() =
assertEquals(
"CREATE INDEX IF NOT EXISTS idx_${tbl}_nest ON $tbl ((data#>>'{a,b,c}'))",
Definition.ensureIndexOn(tbl, "nest", listOf("a.b.c"), Dialect.POSTGRESQL),
"CREATE INDEX for nested PostgreSQL field incorrect"
)
@Test
@DisplayName("ensureIndexOn generates nested SQLite field")
fun ensureIndexOnNestedSQLite() =
assertEquals(
"CREATE INDEX IF NOT EXISTS idx_${tbl}_nest ON $tbl ((data->'a'->'b'->>'c'))",
Definition.ensureIndexOn(tbl, "nest", listOf("a.b.c"), Dialect.SQLITE),
"CREATE INDEX for nested SQLite field incorrect"
)
}

View File

@@ -0,0 +1,147 @@
package solutions.bitbadger.documents.query
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 solutions.bitbadger.documents.*
import kotlin.test.assertEquals
import kotlin.test.assertTrue
/**
* Unit tests for the `Document` object
*/
@DisplayName("Document (Query)")
class DocumentTest {
/** Test table name */
private val tbl = "test_table"
/**
* Clear the connection string (resets Dialect)
*/
@AfterEach
fun cleanUp() {
Configuration.dialectValue = null
}
@Test
@DisplayName("insert generates no auto ID (PostgreSQL)")
fun insertNoAutoPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals("INSERT INTO $tbl VALUES (:data)", Document.insert(tbl))
}
@Test
@DisplayName("insert generates no auto ID (SQLite)")
fun insertNoAutoSQLite() {
Configuration.dialectValue = Dialect.SQLITE
assertEquals("INSERT INTO $tbl VALUES (:data)", Document.insert(tbl))
}
@Test
@DisplayName("insert generates auto number (PostgreSQL)")
fun insertAutoNumberPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals(
"INSERT INTO $tbl VALUES (:data::jsonb || ('{\"id\":' " +
"|| (SELECT COALESCE(MAX((data->>'id')::numeric), 0) + 1 FROM $tbl) || '}')::jsonb)",
Document.insert(tbl, AutoId.NUMBER)
)
}
@Test
@DisplayName("insert generates auto number (SQLite)")
fun insertAutoNumberSQLite() {
Configuration.dialectValue = Dialect.SQLITE
assertEquals(
"INSERT INTO $tbl VALUES (json_set(:data, '$.id', " +
"(SELECT coalesce(max(data->>'id'), 0) + 1 FROM $tbl)))",
Document.insert(tbl, AutoId.NUMBER)
)
}
@Test
@DisplayName("insert generates auto UUID (PostgreSQL)")
fun insertAutoUUIDPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL
val query = Document.insert(tbl, AutoId.UUID)
assertTrue(
query.startsWith("INSERT INTO $tbl VALUES (:data::jsonb || '{\"id\":\""),
"Query start not correct (actual: $query)"
)
assertTrue(query.endsWith("\"}')"), "Query end not correct")
}
@Test
@DisplayName("insert generates auto UUID (SQLite)")
fun insertAutoUUIDSQLite() {
Configuration.dialectValue = Dialect.SQLITE
val query = Document.insert(tbl, AutoId.UUID)
assertTrue(
query.startsWith("INSERT INTO $tbl VALUES (json_set(:data, '$.id', '"),
"Query start not correct (actual: $query)"
)
assertTrue(query.endsWith("'))"), "Query end not correct")
}
@Test
@DisplayName("insert generates auto random string (PostgreSQL)")
fun insertAutoRandomPostgres() {
try {
Configuration.dialectValue = Dialect.POSTGRESQL
Configuration.idStringLength = 8
val query = Document.insert(tbl, AutoId.RANDOM_STRING)
assertTrue(
query.startsWith("INSERT INTO $tbl VALUES (:data::jsonb || '{\"id\":\""),
"Query start not correct (actual: $query)"
)
assertTrue(query.endsWith("\"}')"), "Query end not correct")
assertEquals(
8,
query.replace("INSERT INTO $tbl VALUES (:data::jsonb || '{\"id\":\"", "").replace("\"}')", "").length,
"Random string length incorrect"
)
} finally {
Configuration.idStringLength = 16
}
}
@Test
@DisplayName("insert generates auto random string (SQLite)")
fun insertAutoRandomSQLite() {
Configuration.dialectValue = Dialect.SQLITE
val query = Document.insert(tbl, AutoId.RANDOM_STRING)
assertTrue(
query.startsWith("INSERT INTO $tbl VALUES (json_set(:data, '$.id', '"),
"Query start not correct (actual: $query)"
)
assertTrue(query.endsWith("'))"), "Query end not correct")
assertEquals(
Configuration.idStringLength,
query.replace("INSERT INTO $tbl VALUES (json_set(:data, '$.id', '", "").replace("'))", "").length,
"Random string length incorrect"
)
}
@Test
@DisplayName("insert fails when no dialect is set")
fun insertFailsUnknown() {
assertThrows<DocumentException> { Document.insert(tbl) }
}
@Test
@DisplayName("save generates correctly")
fun save() {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals(
"INSERT INTO $tbl VALUES (:data) ON CONFLICT ((data->>'id')) DO UPDATE SET data = EXCLUDED.data",
Document.save(tbl), "INSERT ON CONFLICT UPDATE statement not constructed correctly"
)
}
@Test
@DisplayName("update generates successfully")
fun update() =
assertEquals("UPDATE $tbl SET data = :data", Document.update(tbl), "Update query not constructed correctly")
}

View File

@@ -0,0 +1,102 @@
package solutions.bitbadger.documents.query
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 solutions.bitbadger.documents.*
import kotlin.test.assertEquals
/**
* Unit tests for the `Exists` object
*/
@DisplayName("Exists (Query)")
class ExistsTest {
/** Test table name */
private val tbl = "test_table"
/**
* Clear the connection string (resets Dialect)
*/
@AfterEach
fun cleanUp() {
Configuration.dialectValue = null
}
@Test
@DisplayName("byId generates correctly (PostgreSQL)")
fun byIdPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals(
"SELECT EXISTS (SELECT 1 FROM $tbl WHERE data->>'id' = :id) AS it",
Exists.byId<String>(tbl), "Exists query not constructed correctly"
)
}
@Test
@DisplayName("byId generates correctly (SQLite)")
fun byIdSQLite() {
Configuration.dialectValue = Dialect.SQLITE
assertEquals(
"SELECT EXISTS (SELECT 1 FROM $tbl WHERE data->>'id' = :id) AS it",
Exists.byId<String>(tbl), "Exists query not constructed correctly"
)
}
@Test
@DisplayName("byFields generates correctly (PostgreSQL)")
fun byFieldsPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals(
"SELECT EXISTS (SELECT 1 FROM $tbl WHERE (data->>'it')::numeric = :test) AS it",
Exists.byFields(tbl, listOf(Field.equal("it", 7, ":test"))),
"Exists query not constructed correctly"
)
}
@Test
@DisplayName("byFields generates correctly (SQLite)")
fun byFieldsSQLite() {
Configuration.dialectValue = Dialect.SQLITE
assertEquals(
"SELECT EXISTS (SELECT 1 FROM $tbl WHERE data->>'it' = :test) AS it",
Exists.byFields(tbl, listOf(Field.equal("it", 7, ":test"))),
"Exists query not constructed correctly"
)
}
@Test
@DisplayName("byContains generates correctly (PostgreSQL)")
fun byContainsPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals(
"SELECT EXISTS (SELECT 1 FROM $tbl WHERE data @> :criteria) AS it", Exists.byContains(tbl),
"Exists query not constructed correctly"
)
}
@Test
@DisplayName("byContains fails (SQLite)")
fun byContainsSQLite() {
Configuration.dialectValue = Dialect.SQLITE
assertThrows<DocumentException> { Exists.byContains(tbl) }
}
@Test
@DisplayName("byJsonPath generates correctly (PostgreSQL)")
fun byJsonPathPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals(
"SELECT EXISTS (SELECT 1 FROM $tbl WHERE jsonb_path_exists(data, :path::jsonpath)) AS it",
Exists.byJsonPath(tbl), "Exists query not constructed correctly"
)
}
@Test
@DisplayName("byJsonPath fails (SQLite)")
fun byJsonPathSQLite() {
Configuration.dialectValue = Dialect.SQLITE
assertThrows<DocumentException> { Exists.byJsonPath(tbl) }
}
}

View File

@@ -0,0 +1,110 @@
package solutions.bitbadger.documents.query
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 solutions.bitbadger.documents.Configuration
import solutions.bitbadger.documents.Dialect
import solutions.bitbadger.documents.DocumentException
import solutions.bitbadger.documents.Field
import kotlin.test.assertEquals
/**
* Unit tests for the `Exists` object
*/
@DisplayName("Find (Query)")
class FindTest {
/** Test table name */
private val tbl = "test_table"
/**
* Clear the connection string (resets Dialect)
*/
@AfterEach
fun cleanUp() {
Configuration.dialectValue = null
}
@Test
@DisplayName("all generates correctly")
fun all() =
assertEquals("SELECT data FROM $tbl", Find.all(tbl), "Find query not constructed correctly")
@Test
@DisplayName("byId generates correctly (PostgreSQL)")
fun byIdPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals(
"SELECT data FROM $tbl WHERE data->>'id' = :id",
Find.byId<String>(tbl), "Find query not constructed correctly"
)
}
@Test
@DisplayName("byId generates correctly (SQLite)")
fun byIdSQLite() {
Configuration.dialectValue = Dialect.SQLITE
assertEquals(
"SELECT data FROM $tbl WHERE data->>'id' = :id",
Find.byId<String>(tbl), "Find query not constructed correctly"
)
}
@Test
@DisplayName("byFields generates correctly (PostgreSQL)")
fun byFieldsPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals(
"SELECT data FROM $tbl WHERE data->>'a' = :b AND (data->>'c')::numeric < :d",
Find.byFields(tbl, listOf(Field.equal("a", "", ":b"), Field.less("c", 14, ":d"))),
"Find query not constructed correctly"
)
}
@Test
@DisplayName("byFields generates correctly (SQLite)")
fun byFieldsSQLite() {
Configuration.dialectValue = Dialect.SQLITE
assertEquals(
"SELECT data FROM $tbl WHERE data->>'a' = :b AND data->>'c' < :d",
Find.byFields(tbl, listOf(Field.equal("a", "", ":b"), Field.less("c", 14, ":d"))),
"Find query not constructed correctly"
)
}
@Test
@DisplayName("byContains generates correctly (PostgreSQL)")
fun byContainsPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals(
"SELECT data FROM $tbl WHERE data @> :criteria", Find.byContains(tbl),
"Find query not constructed correctly"
)
}
@Test
@DisplayName("byContains fails (SQLite)")
fun byContainsSQLite() {
Configuration.dialectValue = Dialect.SQLITE
assertThrows<DocumentException> { Find.byContains(tbl) }
}
@Test
@DisplayName("byJsonPath generates correctly (PostgreSQL)")
fun byJsonPathPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals(
"SELECT data FROM $tbl WHERE jsonb_path_exists(data, :path::jsonpath)", Find.byJsonPath(tbl),
"Find query not constructed correctly"
)
}
@Test
@DisplayName("byJsonPath fails (SQLite)")
fun byJsonPathSQLite() {
Configuration.dialectValue = Dialect.SQLITE
assertThrows<DocumentException> { Find.byJsonPath(tbl) }
}
}

View File

@@ -0,0 +1,175 @@
package solutions.bitbadger.documents.query
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import solutions.bitbadger.documents.*
import kotlin.test.assertEquals
/**
* Unit tests for the top-level query functions
*/
@DisplayName("Query")
class QueryTest {
/**
* Clear the connection string (resets Dialect)
*/
@AfterEach
fun cleanUp() {
Configuration.dialectValue = null
}
@Test
@DisplayName("statementWhere generates correctly")
fun statementWhere() =
assertEquals("x WHERE y", statementWhere("x", "y"), "Statements not combined correctly")
@Test
@DisplayName("byId generates a numeric ID query (PostgreSQL)")
fun byIdNumericPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals("test WHERE (data->>'id')::numeric = :id", byId("test", 9))
}
@Test
@DisplayName("byId generates an alphanumeric ID query (PostgreSQL)")
fun byIdAlphaPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals("unit WHERE data->>'id' = :id", byId("unit", "18"))
}
@Test
@DisplayName("byId generates ID query (SQLite)")
fun byIdSQLite() {
Configuration.dialectValue = Dialect.SQLITE
assertEquals("yo WHERE data->>'id' = :id", byId("yo", 27))
}
@Test
@DisplayName("byFields generates default field query (PostgreSQL)")
fun byFieldsMultipleDefaultPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals(
"this WHERE data->>'a' = :the_a AND (data->>'b')::numeric = :b_value",
byFields("this", listOf(Field.equal("a", "", ":the_a"), Field.equal("b", 0, ":b_value")))
)
}
@Test
@DisplayName("byFields generates default field query (SQLite)")
fun byFieldsMultipleDefaultSQLite() {
Configuration.dialectValue = Dialect.SQLITE
assertEquals(
"this WHERE data->>'a' = :the_a AND data->>'b' = :b_value",
byFields("this", listOf(Field.equal("a", "", ":the_a"), Field.equal("b", 0, ":b_value")))
)
}
@Test
@DisplayName("byFields generates ANY field query (PostgreSQL)")
fun byFieldsMultipleAnyPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals(
"that WHERE data->>'a' = :the_a OR (data->>'b')::numeric = :b_value",
byFields(
"that", listOf(Field.equal("a", "", ":the_a"), Field.equal("b", 0, ":b_value")),
FieldMatch.ANY
)
)
}
@Test
@DisplayName("byFields generates ANY field query (SQLite)")
fun byFieldsMultipleAnySQLite() {
Configuration.dialectValue = Dialect.SQLITE
assertEquals(
"that WHERE data->>'a' = :the_a OR data->>'b' = :b_value",
byFields(
"that", listOf(Field.equal("a", "", ":the_a"), Field.equal("b", 0, ":b_value")),
FieldMatch.ANY
)
)
}
@Test
@DisplayName("orderBy generates for no fields")
fun orderByNone() {
assertEquals("", orderBy(listOf(), Dialect.POSTGRESQL), "ORDER BY should have been blank (PostgreSQL)")
assertEquals("", orderBy(listOf(), Dialect.SQLITE), "ORDER BY should have been blank (SQLite)")
}
@Test
@DisplayName("orderBy generates single, no direction for PostgreSQL")
fun orderBySinglePostgres() =
assertEquals(
" ORDER BY data->>'TestField'",
orderBy(listOf(Field.named("TestField")), Dialect.POSTGRESQL), "ORDER BY not constructed correctly"
)
@Test
@DisplayName("orderBy generates single, no direction for SQLite")
fun orderBySingleSQLite() =
assertEquals(
" ORDER BY data->>'TestField'", orderBy(listOf(Field.named("TestField")), Dialect.SQLITE),
"ORDER BY not constructed correctly"
)
@Test
@DisplayName("orderBy generates multiple with direction for PostgreSQL")
fun orderByMultiplePostgres() =
assertEquals(
" ORDER BY data#>>'{Nested,Test,Field}' DESC, data->>'AnotherField', data->>'It' DESC",
orderBy(
listOf(Field.named("Nested.Test.Field DESC"), Field.named("AnotherField"), Field.named("It DESC")),
Dialect.POSTGRESQL
),
"ORDER BY not constructed correctly"
)
@Test
@DisplayName("orderBy generates multiple with direction for SQLite")
fun orderByMultipleSQLite() =
assertEquals(
" ORDER BY data->'Nested'->'Test'->>'Field' DESC, data->>'AnotherField', data->>'It' DESC",
orderBy(
listOf(Field.named("Nested.Test.Field DESC"), Field.named("AnotherField"), Field.named("It DESC")),
Dialect.SQLITE
),
"ORDER BY not constructed correctly"
)
@Test
@DisplayName("orderBy generates numeric ordering PostgreSQL")
fun orderByNumericPostgres() =
assertEquals(
" ORDER BY (data->>'Test')::numeric",
orderBy(listOf(Field.named("n:Test")), Dialect.POSTGRESQL), "ORDER BY not constructed correctly"
)
@Test
@DisplayName("orderBy generates numeric ordering for SQLite")
fun orderByNumericSQLite() =
assertEquals(
" ORDER BY data->>'Test'", orderBy(listOf(Field.named("n:Test")), Dialect.SQLITE),
"ORDER BY not constructed correctly"
)
@Test
@DisplayName("orderBy generates case-insensitive ordering for PostgreSQL")
fun orderByCIPostgres() =
assertEquals(
" ORDER BY LOWER(data#>>'{Test,Field}') DESC NULLS FIRST",
orderBy(listOf(Field.named("i:Test.Field DESC NULLS FIRST")), Dialect.POSTGRESQL),
"ORDER BY not constructed correctly"
)
@Test
@DisplayName("orderBy generates case-insensitive ordering for SQLite")
fun orderByCISQLite() =
assertEquals(
" ORDER BY data->'Test'->>'Field' COLLATE NOCASE ASC NULLS LAST",
orderBy(listOf(Field.named("i:Test.Field ASC NULLS LAST")), Dialect.SQLITE),
"ORDER BY not constructed correctly"
)
}

View File

@@ -0,0 +1,176 @@
package solutions.bitbadger.documents.query
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 solutions.bitbadger.documents.*
import kotlin.test.assertEquals
/**
* Unit tests for the `Where` object
*/
@DisplayName("Where (Query)")
class WhereTest {
/**
* Clear the connection string (resets Dialect)
*/
@AfterEach
fun cleanUp() {
Configuration.dialectValue = null
}
@Test
@DisplayName("byFields is blank when given no fields")
fun byFieldsBlankIfEmpty() =
assertEquals("", Where.byFields(listOf()))
@Test
@DisplayName("byFields generates one numeric field (PostgreSQL)")
fun byFieldsOneFieldPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals("(data->>'it')::numeric = :that", Where.byFields(listOf(Field.equal("it", 9, ":that"))))
}
@Test
@DisplayName("byFields generates one alphanumeric field (PostgreSQL)")
fun byFieldsOneAlphaFieldPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals("data->>'it' = :that", Where.byFields(listOf(Field.equal("it", "", ":that"))))
}
@Test
@DisplayName("byFields generates one field (SQLite)")
fun byFieldsOneFieldSQLite() {
Configuration.dialectValue = Dialect.SQLITE
assertEquals("data->>'it' = :that", Where.byFields(listOf(Field.equal("it", "", ":that"))))
}
@Test
@DisplayName("byFields generates multiple fields w/ default match (PostgreSQL)")
fun byFieldsMultipleDefaultPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals(
"data->>'1' = :one AND (data->>'2')::numeric = :two AND data->>'3' = :three",
Where.byFields(
listOf(Field.equal("1", "", ":one"), Field.equal("2", 0L, ":two"), Field.equal("3", "", ":three"))
)
)
}
@Test
@DisplayName("byFields generates multiple fields w/ default match (SQLite)")
fun byFieldsMultipleDefaultSQLite() {
Configuration.dialectValue = Dialect.SQLITE
assertEquals(
"data->>'1' = :one AND data->>'2' = :two AND data->>'3' = :three",
Where.byFields(
listOf(Field.equal("1", "", ":one"), Field.equal("2", 0L, ":two"), Field.equal("3", "", ":three"))
)
)
}
@Test
@DisplayName("byFields generates multiple fields w/ ANY match (PostgreSQL)")
fun byFieldsMultipleAnyPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals(
"data->>'1' = :one OR (data->>'2')::numeric = :two OR data->>'3' = :three",
Where.byFields(
listOf(Field.equal("1", "", ":one"), Field.equal("2", 0L, ":two"), Field.equal("3", "", ":three")),
FieldMatch.ANY
)
)
}
@Test
@DisplayName("byFields generates multiple fields w/ ANY match (SQLite)")
fun byFieldsMultipleAnySQLite() {
Configuration.dialectValue = Dialect.SQLITE
assertEquals(
"data->>'1' = :one OR data->>'2' = :two OR data->>'3' = :three",
Where.byFields(
listOf(Field.equal("1", "", ":one"), Field.equal("2", 0L, ":two"), Field.equal("3", "", ":three")),
FieldMatch.ANY
)
)
}
@Test
@DisplayName("byId generates defaults for alphanumeric key (PostgreSQL)")
fun byIdDefaultAlphaPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals("data->>'id' = :id", Where.byId(docId = ""))
}
@Test
@DisplayName("byId generates defaults for numeric key (PostgreSQL)")
fun byIdDefaultNumericPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals("(data->>'id')::numeric = :id", Where.byId(docId = 5))
}
@Test
@DisplayName("byId generates defaults (SQLite)")
fun byIdDefaultSQLite() {
Configuration.dialectValue = Dialect.SQLITE
assertEquals("data->>'id' = :id", Where.byId(docId = ""))
}
@Test
@DisplayName("byId generates named ID (PostgreSQL)")
fun byIdDefaultNamedPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals("data->>'id' = :key", Where.byId<String>(":key"))
}
@Test
@DisplayName("byId generates named ID (SQLite)")
fun byIdDefaultNamedSQLite() {
Configuration.dialectValue = Dialect.SQLITE
assertEquals("data->>'id' = :key", Where.byId<String>(":key"))
}
@Test
@DisplayName("jsonContains generates defaults (PostgreSQL)")
fun jsonContainsDefaultPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals("data @> :criteria", Where.jsonContains())
}
@Test
@DisplayName("jsonContains generates named parameter (PostgreSQL)")
fun jsonContainsNamedPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals("data @> :it", Where.jsonContains(":it"))
}
@Test
@DisplayName("jsonContains fails (SQLite)")
fun jsonContainsFailsSQLite() {
Configuration.dialectValue = Dialect.SQLITE
assertThrows<DocumentException> { Where.jsonContains() }
}
@Test
@DisplayName("jsonPathMatches generates defaults (PostgreSQL)")
fun jsonPathMatchDefaultPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals("jsonb_path_exists(data, :path::jsonpath)", Where.jsonPathMatches())
}
@Test
@DisplayName("jsonPathMatches generates named parameter (PostgreSQL)")
fun jsonPathMatchNamedPostgres() {
Configuration.dialectValue = Dialect.POSTGRESQL
assertEquals("jsonb_path_exists(data, :jp::jsonpath)", Where.jsonPathMatches(":jp"))
}
@Test
@DisplayName("jsonPathMatches fails (SQLite)")
fun jsonPathFailsSQLite() {
Configuration.dialectValue = Dialect.SQLITE
assertThrows<DocumentException> { Where.jsonPathMatches() }
}
}