Initial Development #1
1
.idea/misc.xml
generated
1
.idea/misc.xml
generated
@ -6,6 +6,7 @@
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/src/common/pom.xml" />
|
||||
<option value="$PROJECT_DIR$/src/sqlite/pom.xml" />
|
||||
<option value="$PROJECT_DIR$/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="ignoredFiles">
|
||||
|
@ -21,7 +21,11 @@ object Parameters {
|
||||
fun nameFields(fields: Collection<Field<*>>): Collection<Field<*>> {
|
||||
val name = ParameterName()
|
||||
return fields.map {
|
||||
if (it.name.isBlank()) it.withParameterName(name.derive(null)) else it
|
||||
if (it.parameterName.isNullOrEmpty() && !listOf(Op.EXISTS, Op.NOT_EXISTS).contains(it.comparison.op)) {
|
||||
it.withParameterName(name.derive(null))
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,7 +79,8 @@ object Parameters {
|
||||
is Int -> stmt.setInt(idx, param.value)
|
||||
is Long -> stmt.setLong(idx, param.value)
|
||||
else -> throw DocumentException(
|
||||
"Number parameter must be Byte, Short, Int, or Long (${param.value::class.simpleName})")
|
||||
"Number parameter must be Byte, Short, Int, or Long " +
|
||||
"(${param.value::class.simpleName})")
|
||||
}
|
||||
}
|
||||
ParameterType.STRING -> {
|
||||
|
@ -16,7 +16,7 @@ object Results {
|
||||
* @param rs A `ResultSet` set to the row with the document to be constructed
|
||||
* @return The constructed domain item
|
||||
*/
|
||||
inline fun <reified TDoc> fromDocument(field: String, rs: ResultSet): TDoc =
|
||||
inline fun <reified TDoc> fromDocument(field: String, rs: ResultSet) =
|
||||
Configuration.json.decodeFromString<TDoc>(rs.getString(field))
|
||||
|
||||
/**
|
||||
@ -25,8 +25,8 @@ object Results {
|
||||
* @param rs A `ResultSet` set to the row with the document to be constructed<
|
||||
* @return The constructed domain item
|
||||
*/
|
||||
inline fun <reified TDoc> fromData(rs: ResultSet): TDoc =
|
||||
fromDocument("data", rs)
|
||||
inline fun <reified TDoc> fromData(rs: ResultSet) =
|
||||
fromDocument<TDoc>("data", rs)
|
||||
|
||||
/**
|
||||
* Create a list of items for the results of the given command, using the specified mapping function
|
||||
@ -36,7 +36,7 @@ object Results {
|
||||
* @return A list of items from the query's result
|
||||
* @throws DocumentException If there is a problem executing the query
|
||||
*/
|
||||
inline fun <reified TDoc> toCustomList(stmt: PreparedStatement, mapFunc: (ResultSet) -> TDoc): List<TDoc> =
|
||||
inline fun <reified TDoc> toCustomList(stmt: PreparedStatement, mapFunc: (ResultSet) -> TDoc) =
|
||||
try {
|
||||
stmt.executeQuery().use {
|
||||
val results = mutableListOf<TDoc>()
|
||||
@ -55,7 +55,7 @@ object Results {
|
||||
* @param rs A `ResultSet` set to the row with the count to retrieve
|
||||
* @return The count from the row
|
||||
*/
|
||||
fun toCount(rs: ResultSet): Long =
|
||||
fun toCount(rs: ResultSet) =
|
||||
when (Configuration.dialect()) {
|
||||
Dialect.POSTGRESQL -> rs.getInt("it").toLong()
|
||||
Dialect.SQLITE -> rs.getLong("it")
|
||||
@ -67,7 +67,7 @@ object Results {
|
||||
* @param rs A `ResultSet` set to the row with the true/false value to retrieve
|
||||
* @return The true/false value from the row
|
||||
*/
|
||||
fun toExists(rs: ResultSet): Boolean =
|
||||
fun toExists(rs: ResultSet) =
|
||||
when (Configuration.dialect()) {
|
||||
Dialect.POSTGRESQL -> rs.getBoolean("it")
|
||||
Dialect.SQLITE -> toCount(rs) > 0L
|
||||
|
@ -3,9 +3,37 @@ package solutions.bitbadger.documents
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotSame
|
||||
import kotlin.test.assertSame
|
||||
|
||||
class ParametersTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("nameFields works with no changes")
|
||||
fun nameFieldsNoChange() {
|
||||
val fields = listOf(Field.equal("a", "", ":test"), Field.exists("q"), Field.equal("b", "", ":me"))
|
||||
val named = Parameters.nameFields(fields)
|
||||
assertEquals(fields.size, named.size, "There should have been 3 fields in the list")
|
||||
assertSame(fields.elementAt(0), named.elementAt(0), "The first field should be the same")
|
||||
assertSame(fields.elementAt(1), named.elementAt(1), "The second field should be the same")
|
||||
assertSame(fields.elementAt(2), named.elementAt(2), "The third field should be the same")
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("nameFields works when changing fields")
|
||||
fun nameFieldsChange() {
|
||||
val fields = listOf(
|
||||
Field.equal("a", ""), Field.equal("e", "", ":hi"), Field.equal("b", ""), Field.notExists("z"))
|
||||
val named = Parameters.nameFields(fields)
|
||||
assertEquals(fields.size, named.size, "There should have been 4 fields in the list")
|
||||
assertNotSame(fields.elementAt(0), named.elementAt(0), "The first field should not be the same")
|
||||
assertEquals(":field0", named.elementAt(0).parameterName, "First parameter name incorrect")
|
||||
assertSame(fields.elementAt(1), named.elementAt(1), "The second field should be the same")
|
||||
assertNotSame(fields.elementAt(2), named.elementAt(2), "The third field should not be the same")
|
||||
assertEquals(":field1", named.elementAt(2).parameterName, "Third parameter name incorrect")
|
||||
assertSame(fields.elementAt(3), named.elementAt(3), "The fourth field should be the same")
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("replaceNamesInQuery replaces successfully")
|
||||
fun replaceNamesInQuery() {
|
||||
|
@ -10,6 +10,12 @@ 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)
|
||||
*/
|
||||
@ -20,70 +26,166 @@ class QueryTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("statementWhere generates correctly")
|
||||
fun statementWhere() {
|
||||
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"))
|
||||
}
|
||||
|
||||
// ~~~ Definition ~~~
|
||||
|
||||
@Test
|
||||
@DisplayName("Definition.ensureTableFor generates correctly")
|
||||
fun ensureTableFor() {
|
||||
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.ensureKey generates correctly with schema")
|
||||
fun ensureKeyWithSchema() {
|
||||
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() {
|
||||
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() {
|
||||
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() {
|
||||
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() {
|
||||
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")
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("insert generates correctly")
|
||||
fun insert() {
|
||||
Configuration.connectionString = ":postgresql:"
|
||||
Configuration.connectionString = pg
|
||||
assertEquals("INSERT INTO $tbl VALUES (:data)", Query.insert(tbl), "INSERT statement not constructed correctly")
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("save generates correctly")
|
||||
fun save() {
|
||||
Configuration.connectionString = ":postgresql:"
|
||||
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")
|
||||
@ -91,34 +193,29 @@ class QueryTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("count generates correctly")
|
||||
fun count() {
|
||||
fun count() =
|
||||
assertEquals("SELECT COUNT(*) AS it FROM $tbl", Query.count(tbl), "Count query not constructed correctly")
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("exists generates correctly")
|
||||
fun exists() {
|
||||
fun exists() =
|
||||
assertEquals("SELECT EXISTS (SELECT 1 FROM $tbl WHERE turkey) AS it", Query.exists(tbl, "turkey"),
|
||||
"Exists query not constructed correctly")
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("find generates correctly")
|
||||
fun find() {
|
||||
fun find() =
|
||||
assertEquals("SELECT data FROM $tbl", Query.find(tbl), "Find query not constructed correctly")
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("update generates successfully")
|
||||
fun update() {
|
||||
fun update() =
|
||||
assertEquals("UPDATE $tbl SET data = :data", Query.update(tbl), "Update query not constructed correctly")
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("delete generates successfully")
|
||||
fun delete() {
|
||||
fun delete() =
|
||||
assertEquals("DELETE FROM $tbl", Query.delete(tbl), "Delete query not constructed correctly")
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("orderBy generates for no fields")
|
||||
@ -129,65 +226,57 @@ class QueryTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("orderBy generates single, no direction for PostgreSQL")
|
||||
fun orderBySinglePostgres() {
|
||||
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() {
|
||||
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() {
|
||||
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() {
|
||||
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() {
|
||||
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() {
|
||||
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() {
|
||||
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() {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user