Initial Development #1
@ -3,7 +3,7 @@ package solutions.bitbadger.documents.groovy
|
|||||||
import org.junit.jupiter.api.DisplayName
|
import org.junit.jupiter.api.DisplayName
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import solutions.bitbadger.documents.AutoId
|
import solutions.bitbadger.documents.AutoId
|
||||||
import solutions.bitbadger.documents.DocumentException
|
//import solutions.bitbadger.documents.DocumentException
|
||||||
import solutions.bitbadger.documents.groovy.support.*
|
import solutions.bitbadger.documents.groovy.support.*
|
||||||
|
|
||||||
import static groovy.test.GroovyAssert.*
|
import static groovy.test.GroovyAssert.*
|
||||||
|
@ -0,0 +1,86 @@
|
|||||||
|
package solutions.bitbadger.documents.groovy.query
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
//import solutions.bitbadger.documents.DocumentException
|
||||||
|
import solutions.bitbadger.documents.Field
|
||||||
|
import solutions.bitbadger.documents.query.CountQuery
|
||||||
|
import solutions.bitbadger.documents.support.ForceDialect
|
||||||
|
|
||||||
|
import static solutions.bitbadger.documents.support.TypesKt.TEST_TABLE
|
||||||
|
import static groovy.test.GroovyAssert.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for the `Count` object
|
||||||
|
*/
|
||||||
|
@DisplayName('JVM | Groovy | Query | CountQuery')
|
||||||
|
class CountQueryTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the connection string (resets Dialect)
|
||||||
|
*/
|
||||||
|
@AfterEach
|
||||||
|
void cleanUp() {
|
||||||
|
ForceDialect.none()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('all generates correctly')
|
||||||
|
void all() {
|
||||||
|
assertEquals('Count query not constructed correctly', "SELECT COUNT(*) AS it FROM $TEST_TABLE".toString(),
|
||||||
|
CountQuery.all(TEST_TABLE))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('byFields generates correctly | PostgreSQL')
|
||||||
|
void byFieldsPostgres() {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
assertEquals('Count query not constructed correctly',
|
||||||
|
"SELECT COUNT(*) AS it FROM $TEST_TABLE WHERE data->>'test' = :field0".toString(),
|
||||||
|
CountQuery.byFields(TEST_TABLE, List.of(Field.equal('test', '', ':field0'))))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('byFields generates correctly | SQLite')
|
||||||
|
void byFieldsSQLite() {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
assertEquals('Count query not constructed correctly',
|
||||||
|
"SELECT COUNT(*) AS it FROM $TEST_TABLE WHERE data->>'test' = :field0".toString(),
|
||||||
|
CountQuery.byFields(TEST_TABLE, List.of(Field.equal('test', '', ':field0'))))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('byContains generates correctly | PostgreSQL')
|
||||||
|
void byContainsPostgres() {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
assertEquals('Count query not constructed correctly',
|
||||||
|
"SELECT COUNT(*) AS it FROM $TEST_TABLE WHERE data @> :criteria".toString(),
|
||||||
|
CountQuery.byContains(TEST_TABLE))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: resolve java.base open issue
|
||||||
|
// @Test
|
||||||
|
// @DisplayName('byContains fails | SQLite')
|
||||||
|
// void byContainsSQLite() {
|
||||||
|
// ForceDialect.sqlite()
|
||||||
|
// assertThrows(DocumentException) { CountQuery.byContains(TEST_TABLE) }
|
||||||
|
// }
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('byJsonPath generates correctly | PostgreSQL')
|
||||||
|
void byJsonPathPostgres() {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
assertEquals('Count query not constructed correctly',
|
||||||
|
"SELECT COUNT(*) AS it FROM $TEST_TABLE WHERE jsonb_path_exists(data, :path::jsonpath)".toString(),
|
||||||
|
CountQuery.byJsonPath(TEST_TABLE))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: resolve java.base open issue
|
||||||
|
// @Test
|
||||||
|
// @DisplayName('byJsonPath fails | SQLite')
|
||||||
|
// void byJsonPathSQLite() {
|
||||||
|
// ForceDialect.sqlite()
|
||||||
|
// assertThrows(DocumentException) { CountQuery.byJsonPath(TEST_TABLE) }
|
||||||
|
// }
|
||||||
|
}
|
@ -0,0 +1,128 @@
|
|||||||
|
package solutions.bitbadger.documents.groovy.query
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import solutions.bitbadger.documents.Dialect
|
||||||
|
//import solutions.bitbadger.documents.DocumentException
|
||||||
|
import solutions.bitbadger.documents.DocumentIndex
|
||||||
|
import solutions.bitbadger.documents.query.DefinitionQuery
|
||||||
|
import solutions.bitbadger.documents.support.ForceDialect
|
||||||
|
|
||||||
|
import static solutions.bitbadger.documents.support.TypesKt.TEST_TABLE
|
||||||
|
import static groovy.test.GroovyAssert.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for the `Definition` object
|
||||||
|
*/
|
||||||
|
@DisplayName('JVM | Groovy | Query | DefinitionQuery')
|
||||||
|
class DefinitionQueryTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the connection string (resets Dialect)
|
||||||
|
*/
|
||||||
|
@AfterEach
|
||||||
|
void cleanUp() {
|
||||||
|
ForceDialect.none()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('ensureTableFor generates correctly')
|
||||||
|
void ensureTableFor() {
|
||||||
|
assertEquals('CREATE TABLE statement not constructed correctly',
|
||||||
|
'CREATE TABLE IF NOT EXISTS my.table (data JSONB NOT NULL)',
|
||||||
|
DefinitionQuery.ensureTableFor('my.table', 'JSONB'))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('ensureTable generates correctly | PostgreSQL')
|
||||||
|
void ensureTablePostgres() {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
assertEquals("CREATE TABLE IF NOT EXISTS $TEST_TABLE (data JSONB NOT NULL)".toString(),
|
||||||
|
DefinitionQuery.ensureTable(TEST_TABLE))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('ensureTable generates correctly | SQLite')
|
||||||
|
void ensureTableSQLite() {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
assertEquals("CREATE TABLE IF NOT EXISTS $TEST_TABLE (data TEXT NOT NULL)".toString(),
|
||||||
|
DefinitionQuery.ensureTable(TEST_TABLE))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: resolve java.base open issue
|
||||||
|
// @Test
|
||||||
|
// @DisplayName('ensureTable fails when no dialect is set')
|
||||||
|
// void ensureTableFailsUnknown() {
|
||||||
|
// assertThrows(DocumentException) { DefinitionQuery.ensureTable(TEST_TABLE) }
|
||||||
|
// }
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('ensureKey generates correctly with schema')
|
||||||
|
void ensureKeyWithSchema() {
|
||||||
|
assertEquals('CREATE INDEX for key statement with schema not constructed correctly',
|
||||||
|
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON test.table ((data->>'id'))",
|
||||||
|
DefinitionQuery.ensureKey('test.table', Dialect.POSTGRESQL))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('ensureKey generates correctly without schema')
|
||||||
|
void ensureKeyWithoutSchema() {
|
||||||
|
assertEquals('CREATE INDEX for key statement without schema not constructed correctly',
|
||||||
|
"CREATE UNIQUE INDEX IF NOT EXISTS idx_${TEST_TABLE}_key ON $TEST_TABLE ((data->>'id'))".toString(),
|
||||||
|
DefinitionQuery.ensureKey(TEST_TABLE, Dialect.SQLITE))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('ensureIndexOn generates multiple fields and directions')
|
||||||
|
void ensureIndexOnMultipleFields() {
|
||||||
|
assertEquals('CREATE INDEX for multiple field statement not constructed correctly',
|
||||||
|
"CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table ((data->>'taco'), (data->>'guac') DESC, (data->>'salsa') ASC)"
|
||||||
|
.toString(),
|
||||||
|
DefinitionQuery.ensureIndexOn('test.table', 'gibberish', List.of('taco', 'guac DESC', 'salsa ASC'),
|
||||||
|
Dialect.POSTGRESQL))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('ensureIndexOn generates nested field | PostgreSQL')
|
||||||
|
void ensureIndexOnNestedPostgres() {
|
||||||
|
assertEquals('CREATE INDEX for nested PostgreSQL field incorrect',
|
||||||
|
"CREATE INDEX IF NOT EXISTS idx_${TEST_TABLE}_nest ON $TEST_TABLE ((data#>>'{a,b,c}'))".toString(),
|
||||||
|
DefinitionQuery.ensureIndexOn(TEST_TABLE, 'nest', List.of('a.b.c'), Dialect.POSTGRESQL))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('ensureIndexOn generates nested field | SQLite')
|
||||||
|
void ensureIndexOnNestedSQLite() {
|
||||||
|
assertEquals('CREATE INDEX for nested SQLite field incorrect',
|
||||||
|
"CREATE INDEX IF NOT EXISTS idx_${TEST_TABLE}_nest ON $TEST_TABLE ((data->'a'->'b'->>'c'))".toString(),
|
||||||
|
DefinitionQuery.ensureIndexOn(TEST_TABLE, 'nest', List.of('a.b.c'), Dialect.SQLITE))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('ensureDocumentIndexOn generates Full | PostgreSQL')
|
||||||
|
void ensureDocumentIndexOnFullPostgres() {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
assertEquals('CREATE INDEX for full document index incorrect',
|
||||||
|
"CREATE INDEX IF NOT EXISTS idx_${TEST_TABLE}_document ON $TEST_TABLE USING GIN (data)".toString(),
|
||||||
|
DefinitionQuery.ensureDocumentIndexOn(TEST_TABLE, DocumentIndex.FULL))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('ensureDocumentIndexOn generates Optimized | PostgreSQL')
|
||||||
|
void ensureDocumentIndexOnOptimizedPostgres() {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
assertEquals('CREATE INDEX for optimized document index incorrect',
|
||||||
|
"CREATE INDEX IF NOT EXISTS idx_${TEST_TABLE}_document ON $TEST_TABLE USING GIN (data jsonb_path_ops)"
|
||||||
|
.toString(),
|
||||||
|
DefinitionQuery.ensureDocumentIndexOn(TEST_TABLE, DocumentIndex.OPTIMIZED))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: resolve java.base open issue
|
||||||
|
// @Test
|
||||||
|
// @DisplayName('ensureDocumentIndexOn fails | SQLite')
|
||||||
|
// void ensureDocumentIndexOnFailsSQLite() {
|
||||||
|
// ForceDialect.sqlite()
|
||||||
|
// assertThrows(DocumentException) { DefinitionQuery.ensureDocumentIndexOn(TEST_TABLE, DocumentIndex.FULL) }
|
||||||
|
// }
|
||||||
|
}
|
@ -0,0 +1,94 @@
|
|||||||
|
package solutions.bitbadger.documents.groovy.query
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
//import solutions.bitbadger.documents.DocumentException
|
||||||
|
import solutions.bitbadger.documents.Field
|
||||||
|
import solutions.bitbadger.documents.query.DeleteQuery
|
||||||
|
import solutions.bitbadger.documents.support.ForceDialect
|
||||||
|
|
||||||
|
import static solutions.bitbadger.documents.support.TypesKt.TEST_TABLE
|
||||||
|
import static groovy.test.GroovyAssert.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for the `Delete` object
|
||||||
|
*/
|
||||||
|
@DisplayName('JVM | Groovy | Query | DeleteQuery')
|
||||||
|
class DeleteQueryTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the connection string (resets Dialect)
|
||||||
|
*/
|
||||||
|
@AfterEach
|
||||||
|
void cleanUp() {
|
||||||
|
ForceDialect.none()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('byId generates correctly | PostgreSQL')
|
||||||
|
void byIdPostgres() {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
assertEquals('Delete query not constructed correctly',
|
||||||
|
"DELETE FROM $TEST_TABLE WHERE data->>'id' = :id".toString(), DeleteQuery.byId(TEST_TABLE))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('byId generates correctly | SQLite')
|
||||||
|
void byIdSQLite() {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
assertEquals('Delete query not constructed correctly',
|
||||||
|
"DELETE FROM $TEST_TABLE WHERE data->>'id' = :id".toString(), DeleteQuery.byId(TEST_TABLE))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('byFields generates correctly | PostgreSQL')
|
||||||
|
void byFieldsPostgres() {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
assertEquals('Delete query not constructed correctly',
|
||||||
|
"DELETE FROM $TEST_TABLE WHERE data->>'a' = :b".toString(),
|
||||||
|
DeleteQuery.byFields(TEST_TABLE, List.of(Field.equal('a', '', ':b'))))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('byFields generates correctly | SQLite')
|
||||||
|
void byFieldsSQLite() {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
assertEquals('Delete query not constructed correctly',
|
||||||
|
"DELETE FROM $TEST_TABLE WHERE data->>'a' = :b".toString(),
|
||||||
|
DeleteQuery.byFields(TEST_TABLE, List.of(Field.equal('a', '', ':b'))))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('byContains generates correctly | PostgreSQL')
|
||||||
|
void byContainsPostgres() {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
assertEquals('Delete query not constructed correctly',
|
||||||
|
"DELETE FROM $TEST_TABLE WHERE data @> :criteria".toString(), DeleteQuery.byContains(TEST_TABLE))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: resolve java.base open issue
|
||||||
|
// @Test
|
||||||
|
// @DisplayName('byContains fails | SQLite')
|
||||||
|
// void byContainsSQLite() {
|
||||||
|
// ForceDialect.sqlite()
|
||||||
|
// assertThrows(DocumentException) { DeleteQuery.byContains(TEST_TABLE) }
|
||||||
|
// }
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('byJsonPath generates correctly | PostgreSQL')
|
||||||
|
void byJsonPathPostgres() {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
assertEquals('Delete query not constructed correctly',
|
||||||
|
"DELETE FROM $TEST_TABLE WHERE jsonb_path_exists(data, :path::jsonpath)".toString(),
|
||||||
|
DeleteQuery.byJsonPath(TEST_TABLE))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: resolve java.base open issue
|
||||||
|
// @Test
|
||||||
|
// @DisplayName('byJsonPath fails | SQLite')
|
||||||
|
// void byJsonPathSQLite() {
|
||||||
|
// ForceDialect.sqlite()
|
||||||
|
// assertThrows(DocumentException) { DeleteQuery.byJsonPath(TEST_TABLE) }
|
||||||
|
// }
|
||||||
|
}
|
@ -0,0 +1,135 @@
|
|||||||
|
package solutions.bitbadger.documents.groovy.query
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import solutions.bitbadger.documents.AutoId
|
||||||
|
import solutions.bitbadger.documents.Configuration
|
||||||
|
//import solutions.bitbadger.documents.DocumentException
|
||||||
|
import solutions.bitbadger.documents.query.DocumentQuery
|
||||||
|
import solutions.bitbadger.documents.support.ForceDialect
|
||||||
|
|
||||||
|
import static solutions.bitbadger.documents.support.TypesKt.TEST_TABLE
|
||||||
|
import static groovy.test.GroovyAssert.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for the `Document` object
|
||||||
|
*/
|
||||||
|
@DisplayName('JVM | Groovy | Query | DocumentQuery')
|
||||||
|
class DocumentQueryTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the connection string (resets Dialect)
|
||||||
|
*/
|
||||||
|
@AfterEach
|
||||||
|
void cleanUp() {
|
||||||
|
ForceDialect.none()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('insert generates no auto ID | PostgreSQL')
|
||||||
|
void insertNoAutoPostgres() {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
assertEquals("INSERT INTO $TEST_TABLE VALUES (:data)".toString(), DocumentQuery.insert(TEST_TABLE))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('insert generates no auto ID | SQLite')
|
||||||
|
void insertNoAutoSQLite() {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
assertEquals("INSERT INTO $TEST_TABLE VALUES (:data)".toString(), DocumentQuery.insert(TEST_TABLE))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('insert generates auto number | PostgreSQL')
|
||||||
|
void insertAutoNumberPostgres() {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
assertEquals("INSERT INTO $TEST_TABLE VALUES (:data::jsonb || ('{\"id\":' || (SELECT ".toString() +
|
||||||
|
"COALESCE(MAX((data->>'id')::numeric), 0) + 1 FROM $TEST_TABLE) || '}')::jsonb)".toString(),
|
||||||
|
DocumentQuery.insert(TEST_TABLE, AutoId.NUMBER))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('insert generates auto number | SQLite')
|
||||||
|
void insertAutoNumberSQLite() {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
assertEquals("INSERT INTO $TEST_TABLE VALUES (json_set(:data, '\$.id', ".toString() +
|
||||||
|
"(SELECT coalesce(max(data->>'id'), 0) + 1 FROM $TEST_TABLE)))".toString(),
|
||||||
|
DocumentQuery.insert(TEST_TABLE, AutoId.NUMBER))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('insert generates auto UUID | PostgreSQL')
|
||||||
|
void insertAutoUUIDPostgres() {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
def query = DocumentQuery.insert(TEST_TABLE, AutoId.UUID)
|
||||||
|
assertTrue("Query start not correct (actual: $query)",
|
||||||
|
query.startsWith("INSERT INTO $TEST_TABLE VALUES (:data::jsonb || '{\"id\":\""))
|
||||||
|
assertTrue('Query end not correct', query.endsWith("\"}')"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('insert generates auto UUID | SQLite')
|
||||||
|
void insertAutoUUIDSQLite() {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
def query = DocumentQuery.insert(TEST_TABLE, AutoId.UUID)
|
||||||
|
assertTrue("Query start not correct (actual: $query)",
|
||||||
|
query.startsWith("INSERT INTO $TEST_TABLE VALUES (json_set(:data, '\$.id', '"))
|
||||||
|
assertTrue('Query end not correct', query.endsWith("'))"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('insert generates auto random string | PostgreSQL')
|
||||||
|
void insertAutoRandomPostgres() {
|
||||||
|
try {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
Configuration.idStringLength = 8
|
||||||
|
def query = DocumentQuery.insert(TEST_TABLE, AutoId.RANDOM_STRING)
|
||||||
|
assertTrue("Query start not correct (actual: $query)",
|
||||||
|
query.startsWith("INSERT INTO $TEST_TABLE VALUES (:data::jsonb || '{\"id\":\""))
|
||||||
|
assertTrue('Query end not correct', query.endsWith("\"}')"))
|
||||||
|
assertEquals('Random string length incorrect', 8,
|
||||||
|
query.replace("INSERT INTO $TEST_TABLE VALUES (:data::jsonb || '{\"id\":\"", '')
|
||||||
|
.replace("\"}')", '').length())
|
||||||
|
} finally {
|
||||||
|
Configuration.idStringLength = 16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('insert generates auto random string | SQLite')
|
||||||
|
void insertAutoRandomSQLite() {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
def query = DocumentQuery.insert(TEST_TABLE, AutoId.RANDOM_STRING)
|
||||||
|
assertTrue("Query start not correct (actual: $query)",
|
||||||
|
query.startsWith("INSERT INTO $TEST_TABLE VALUES (json_set(:data, '\$.id', '"))
|
||||||
|
assertTrue('Query end not correct', query.endsWith("'))"))
|
||||||
|
assertEquals('Random string length incorrect', Configuration.idStringLength,
|
||||||
|
query.replace("INSERT INTO $TEST_TABLE VALUES (json_set(:data, '\$.id', '", '').replace("'))", '')
|
||||||
|
.length())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: resolve java.base open issue
|
||||||
|
// @Test
|
||||||
|
// @DisplayName('insert fails when no dialect is set')
|
||||||
|
// void insertFailsUnknown() {
|
||||||
|
// assertThrows(DocumentException) { DocumentQuery.insert(TEST_TABLE) }
|
||||||
|
// }
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('save generates correctly')
|
||||||
|
void save() {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
assertEquals('INSERT ON CONFLICT UPDATE statement not constructed correctly',
|
||||||
|
"INSERT INTO $TEST_TABLE VALUES (:data) ON CONFLICT ((data->>'id')) DO UPDATE SET data = EXCLUDED.data"
|
||||||
|
.toString(),
|
||||||
|
DocumentQuery.save(TEST_TABLE))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('update generates successfully')
|
||||||
|
void update() {
|
||||||
|
assertEquals('Update query not constructed correctly', "UPDATE $TEST_TABLE SET data = :data".toString(),
|
||||||
|
DocumentQuery.update(TEST_TABLE))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,101 @@
|
|||||||
|
package solutions.bitbadger.documents.groovy.query
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
//import solutions.bitbadger.documents.DocumentException
|
||||||
|
import solutions.bitbadger.documents.Field
|
||||||
|
import solutions.bitbadger.documents.query.FindQuery
|
||||||
|
import solutions.bitbadger.documents.support.ForceDialect
|
||||||
|
|
||||||
|
import static solutions.bitbadger.documents.support.TypesKt.TEST_TABLE
|
||||||
|
import static groovy.test.GroovyAssert.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for the `Find` object
|
||||||
|
*/
|
||||||
|
@DisplayName('JVM | Groovy | Query | FindQuery')
|
||||||
|
class FindQueryTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the connection string (resets Dialect)
|
||||||
|
*/
|
||||||
|
@AfterEach
|
||||||
|
void cleanUp() {
|
||||||
|
ForceDialect.none()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('all generates correctly')
|
||||||
|
void all() {
|
||||||
|
assertEquals('Find query not constructed correctly', "SELECT data FROM $TEST_TABLE".toString(),
|
||||||
|
FindQuery.all(TEST_TABLE))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('byId generates correctly | PostgreSQL')
|
||||||
|
void byIdPostgres() {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
assertEquals('Find query not constructed correctly',
|
||||||
|
"SELECT data FROM $TEST_TABLE WHERE data->>'id' = :id".toString(), FindQuery.byId(TEST_TABLE))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('byId generates correctly | SQLite')
|
||||||
|
void byIdSQLite() {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
assertEquals('Find query not constructed correctly',
|
||||||
|
"SELECT data FROM $TEST_TABLE WHERE data->>'id' = :id".toString(), FindQuery.byId(TEST_TABLE))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('byFields generates correctly | PostgreSQL')
|
||||||
|
void byFieldsPostgres() {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
assertEquals('Find query not constructed correctly',
|
||||||
|
"SELECT data FROM $TEST_TABLE WHERE data->>'a' = :b AND (data->>'c')::numeric < :d".toString(),
|
||||||
|
FindQuery.byFields(TEST_TABLE, List.of(Field.equal('a', '', ':b'), Field.less('c', 14, ':d'))))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('byFields generates correctly | SQLite')
|
||||||
|
void byFieldsSQLite() {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
assertEquals('Find query not constructed correctly',
|
||||||
|
"SELECT data FROM $TEST_TABLE WHERE data->>'a' = :b AND data->>'c' < :d".toString(),
|
||||||
|
FindQuery.byFields(TEST_TABLE, List.of(Field.equal('a', '', ':b'), Field.less('c', 14, ':d'))))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('byContains generates correctly | PostgreSQL')
|
||||||
|
void byContainsPostgres() {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
assertEquals('Find query not constructed correctly',
|
||||||
|
"SELECT data FROM $TEST_TABLE WHERE data @> :criteria".toString(), FindQuery.byContains(TEST_TABLE))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: resolve java.base open issue
|
||||||
|
// @Test
|
||||||
|
// @DisplayName('byContains fails | SQLite')
|
||||||
|
// void byContainsSQLite() {
|
||||||
|
// ForceDialect.sqlite()
|
||||||
|
// assertThrows(DocumentException) { FindQuery.byContains(TEST_TABLE) }
|
||||||
|
// }
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('byJsonPath generates correctly | PostgreSQL')
|
||||||
|
void byJsonPathPostgres() {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
assertEquals('Find query not constructed correctly',
|
||||||
|
"SELECT data FROM $TEST_TABLE WHERE jsonb_path_exists(data, :path::jsonpath)".toString(),
|
||||||
|
FindQuery.byJsonPath(TEST_TABLE))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: resolve java.base open issue
|
||||||
|
// @Test
|
||||||
|
// @DisplayName('byJsonPath fails | SQLite')
|
||||||
|
// void byJsonPathSQLite() {
|
||||||
|
// ForceDialect.sqlite()
|
||||||
|
// assertThrows(DocumentException) { FindQuery.byJsonPath(TEST_TABLE) }
|
||||||
|
// }
|
||||||
|
}
|
@ -0,0 +1,97 @@
|
|||||||
|
package solutions.bitbadger.documents.groovy.query
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
//import solutions.bitbadger.documents.DocumentException
|
||||||
|
import solutions.bitbadger.documents.Field
|
||||||
|
import solutions.bitbadger.documents.query.PatchQuery
|
||||||
|
import solutions.bitbadger.documents.support.ForceDialect
|
||||||
|
|
||||||
|
import static solutions.bitbadger.documents.support.TypesKt.TEST_TABLE
|
||||||
|
import static groovy.test.GroovyAssert.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for the `Patch` object
|
||||||
|
*/
|
||||||
|
@DisplayName('JVM | Groovy | Query | PatchQuery')
|
||||||
|
class PatchQueryTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the dialect
|
||||||
|
*/
|
||||||
|
@AfterEach
|
||||||
|
void cleanUp() {
|
||||||
|
ForceDialect.none()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('byId generates correctly | PostgreSQL')
|
||||||
|
void byIdPostgres() {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
assertEquals('Patch query not constructed correctly',
|
||||||
|
"UPDATE $TEST_TABLE SET data = data || :data WHERE data->>'id' = :id".toString(),
|
||||||
|
PatchQuery.byId(TEST_TABLE))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('byId generates correctly | SQLite')
|
||||||
|
void byIdSQLite() {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
assertEquals('Patch query not constructed correctly',
|
||||||
|
"UPDATE $TEST_TABLE SET data = json_patch(data, json(:data)) WHERE data->>'id' = :id".toString(),
|
||||||
|
PatchQuery.byId(TEST_TABLE))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('byFields generates correctly | PostgreSQL')
|
||||||
|
void byFieldsPostgres() {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
assertEquals('Patch query not constructed correctly',
|
||||||
|
"UPDATE $TEST_TABLE SET data = data || :data WHERE data->>'z' = :y".toString(),
|
||||||
|
PatchQuery.byFields(TEST_TABLE, List.of(Field.equal('z', '', ':y'))))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('byFields generates correctly | SQLite')
|
||||||
|
void byFieldsSQLite() {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
assertEquals('Patch query not constructed correctly',
|
||||||
|
"UPDATE $TEST_TABLE SET data = json_patch(data, json(:data)) WHERE data->>'z' = :y".toString(),
|
||||||
|
PatchQuery.byFields(TEST_TABLE, List.of(Field.equal('z', '', ':y'))))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('byContains generates correctly | PostgreSQL')
|
||||||
|
void byContainsPostgres() {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
assertEquals('Patch query not constructed correctly',
|
||||||
|
"UPDATE $TEST_TABLE SET data = data || :data WHERE data @> :criteria".toString(),
|
||||||
|
PatchQuery.byContains(TEST_TABLE))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: resolve java.base open issue
|
||||||
|
// @Test
|
||||||
|
// @DisplayName('byContains fails | SQLite')
|
||||||
|
// void byContainsSQLite() {
|
||||||
|
// ForceDialect.sqlite()
|
||||||
|
// assertThrows(DocumentException) { PatchQuery.byContains(TEST_TABLE) }
|
||||||
|
// }
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('byJsonPath generates correctly | PostgreSQL')
|
||||||
|
void byJsonPathPostgres() {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
assertEquals('Patch query not constructed correctly',
|
||||||
|
"UPDATE $TEST_TABLE SET data = data || :data WHERE jsonb_path_exists(data, :path::jsonpath)".toString(),
|
||||||
|
PatchQuery.byJsonPath(TEST_TABLE))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: resolve java.base open issue
|
||||||
|
// @Test
|
||||||
|
// @DisplayName('byJsonPath fails | SQLite')
|
||||||
|
// void byJsonPathSQLite() {
|
||||||
|
// ForceDialect.sqlite()
|
||||||
|
// assertThrows(DocumentException) { PatchQuery.byJsonPath(TEST_TABLE) }
|
||||||
|
// }
|
||||||
|
}
|
@ -0,0 +1,159 @@
|
|||||||
|
package solutions.bitbadger.documents.groovy.query
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import solutions.bitbadger.documents.Dialect
|
||||||
|
import solutions.bitbadger.documents.Field
|
||||||
|
import solutions.bitbadger.documents.FieldMatch
|
||||||
|
import solutions.bitbadger.documents.query.QueryUtils
|
||||||
|
import solutions.bitbadger.documents.support.ForceDialect
|
||||||
|
|
||||||
|
import static groovy.test.GroovyAssert.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for the `QueryUtils` class
|
||||||
|
*/
|
||||||
|
@DisplayName('JVM | Groovy | Query | QueryUtils')
|
||||||
|
class QueryUtilsTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the connection string (resets Dialect)
|
||||||
|
*/
|
||||||
|
@AfterEach
|
||||||
|
void cleanUp() {
|
||||||
|
ForceDialect.none()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('statementWhere generates correctly')
|
||||||
|
void statementWhere() {
|
||||||
|
assertEquals('Statements not combined correctly', 'x WHERE y', QueryUtils.statementWhere('x', 'y'))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('byId generates a numeric ID query | PostgreSQL')
|
||||||
|
void byIdNumericPostgres() {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
assertEquals("test WHERE (data->>'id')::numeric = :id", QueryUtils.byId('test', 9))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('byId generates an alphanumeric ID query | PostgreSQL')
|
||||||
|
void byIdAlphaPostgres() {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
assertEquals("unit WHERE data->>'id' = :id", QueryUtils.byId('unit', '18'))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('byId generates ID query | SQLite')
|
||||||
|
void byIdSQLite() {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
assertEquals("yo WHERE data->>'id' = :id", QueryUtils.byId('yo', 27))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('byFields generates default field query | PostgreSQL')
|
||||||
|
void byFieldsMultipleDefaultPostgres() {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
assertEquals("this WHERE data->>'a' = :the_a AND (data->>'b')::numeric = :b_value",
|
||||||
|
QueryUtils.byFields('this', List.of(Field.equal('a', '', ':the_a'), Field.equal('b', 0, ':b_value'))))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('byFields generates default field query | SQLite')
|
||||||
|
void byFieldsMultipleDefaultSQLite() {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
assertEquals("this WHERE data->>'a' = :the_a AND data->>'b' = :b_value",
|
||||||
|
QueryUtils.byFields('this', List.of(Field.equal('a', '', ':the_a'), Field.equal('b', 0, ':b_value'))))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('byFields generates ANY field query | PostgreSQL')
|
||||||
|
void byFieldsMultipleAnyPostgres() {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
assertEquals("that WHERE data->>'a' = :the_a OR (data->>'b')::numeric = :b_value",
|
||||||
|
QueryUtils.byFields('that', List.of(Field.equal('a', '', ':the_a'), Field.equal('b', 0, ':b_value')),
|
||||||
|
FieldMatch.ANY))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('byFields generates ANY field query | SQLite')
|
||||||
|
void byFieldsMultipleAnySQLite() {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
assertEquals("that WHERE data->>'a' = :the_a OR data->>'b' = :b_value",
|
||||||
|
QueryUtils.byFields('that', List.of(Field.equal('a', '', ':the_a'), Field.equal('b', 0, ':b_value')),
|
||||||
|
FieldMatch.ANY))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('orderBy generates for no fields')
|
||||||
|
void orderByNone() {
|
||||||
|
assertEquals('ORDER BY should have been blank (PostgreSQL)', '', QueryUtils.orderBy(List.of(),
|
||||||
|
Dialect.POSTGRESQL))
|
||||||
|
assertEquals('ORDER BY should have been blank (SQLite)', '', QueryUtils.orderBy(List.of(), Dialect.SQLITE))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('orderBy generates single, no direction | PostgreSQL')
|
||||||
|
void orderBySinglePostgres() {
|
||||||
|
assertEquals('ORDER BY not constructed correctly', " ORDER BY data->>'TestField'",
|
||||||
|
QueryUtils.orderBy(List.of(Field.named('TestField')), Dialect.POSTGRESQL))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('orderBy generates single, no direction | SQLite')
|
||||||
|
void orderBySingleSQLite() {
|
||||||
|
assertEquals('ORDER BY not constructed correctly', " ORDER BY data->>'TestField'",
|
||||||
|
QueryUtils.orderBy(List.of(Field.named('TestField')), Dialect.SQLITE))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('orderBy generates multiple with direction | PostgreSQL')
|
||||||
|
void orderByMultiplePostgres() {
|
||||||
|
assertEquals('ORDER BY not constructed correctly',
|
||||||
|
" ORDER BY data#>>'{Nested,Test,Field}' DESC, data->>'AnotherField', data->>'It' DESC",
|
||||||
|
QueryUtils.orderBy(List.of(Field.named('Nested.Test.Field DESC'), Field.named('AnotherField'),
|
||||||
|
Field.named('It DESC')),
|
||||||
|
Dialect.POSTGRESQL))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('orderBy generates multiple with direction | SQLite')
|
||||||
|
void orderByMultipleSQLite() {
|
||||||
|
assertEquals('ORDER BY not constructed correctly',
|
||||||
|
" ORDER BY data->'Nested'->'Test'->>'Field' DESC, data->>'AnotherField', data->>'It' DESC",
|
||||||
|
QueryUtils.orderBy(List.of(Field.named('Nested.Test.Field DESC'), Field.named('AnotherField'),
|
||||||
|
Field.named('It DESC')),
|
||||||
|
Dialect.SQLITE))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('orderBy generates numeric ordering | PostgreSQL')
|
||||||
|
void orderByNumericPostgres() {
|
||||||
|
assertEquals('ORDER BY not constructed correctly', " ORDER BY (data->>'Test')::numeric",
|
||||||
|
QueryUtils.orderBy(List.of(Field.named('n:Test')), Dialect.POSTGRESQL))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('orderBy generates numeric ordering | SQLite')
|
||||||
|
void orderByNumericSQLite() {
|
||||||
|
assertEquals('ORDER BY not constructed correctly', " ORDER BY data->>'Test'",
|
||||||
|
QueryUtils.orderBy(List.of(Field.named('n:Test')), Dialect.SQLITE))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('orderBy generates case-insensitive ordering | PostgreSQL')
|
||||||
|
void orderByCIPostgres() {
|
||||||
|
assertEquals('ORDER BY not constructed correctly', " ORDER BY LOWER(data#>>'{Test,Field}') DESC NULLS FIRST",
|
||||||
|
QueryUtils.orderBy(List.of(Field.named('i:Test.Field DESC NULLS FIRST')), Dialect.POSTGRESQL))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName('orderBy generates case-insensitive ordering | SQLite')
|
||||||
|
void orderByCISQLite() {
|
||||||
|
assertEquals('ORDER BY not constructed correctly',
|
||||||
|
" ORDER BY data->'Test'->>'Field' COLLATE NOCASE ASC NULLS LAST",
|
||||||
|
QueryUtils.orderBy(List.of(Field.named('i:Test.Field ASC NULLS LAST')), Dialect.SQLITE))
|
||||||
|
}
|
||||||
|
}
|
@ -29,10 +29,10 @@ class AutoIdSpec extends AnyFunSpec with Matchers {
|
|||||||
|
|
||||||
describe("needsAutoId") {
|
describe("needsAutoId") {
|
||||||
it("fails for null document") {
|
it("fails for null document") {
|
||||||
an [DocumentException] should be thrownBy AutoId.needsAutoId(AutoId.DISABLED, null, "id")
|
a [DocumentException] should be thrownBy AutoId.needsAutoId(AutoId.DISABLED, null, "id")
|
||||||
}
|
}
|
||||||
it("fails for missing ID property") {
|
it("fails for missing ID property") {
|
||||||
an [DocumentException] should be thrownBy AutoId.needsAutoId(AutoId.UUID, IntIdClass(0), "Id")
|
a [DocumentException] should be thrownBy AutoId.needsAutoId(AutoId.UUID, IntIdClass(0), "Id")
|
||||||
}
|
}
|
||||||
it("returns false if disabled") {
|
it("returns false if disabled") {
|
||||||
AutoId.needsAutoId(AutoId.DISABLED, "", "") shouldBe false
|
AutoId.needsAutoId(AutoId.DISABLED, "", "") shouldBe false
|
||||||
@ -62,7 +62,7 @@ class AutoIdSpec extends AnyFunSpec with Matchers {
|
|||||||
AutoId.needsAutoId(AutoId.NUMBER, LongIdClass(2), "id") shouldBe false
|
AutoId.needsAutoId(AutoId.NUMBER, LongIdClass(2), "id") shouldBe false
|
||||||
}
|
}
|
||||||
it("fails for Number strategy and non-number ID") {
|
it("fails for Number strategy and non-number ID") {
|
||||||
an [DocumentException] should be thrownBy AutoId.needsAutoId(AutoId.NUMBER, StringIdClass(""), "id")
|
a [DocumentException] should be thrownBy AutoId.needsAutoId(AutoId.NUMBER, StringIdClass(""), "id")
|
||||||
}
|
}
|
||||||
it("returns true for UUID strategy and blank ID") {
|
it("returns true for UUID strategy and blank ID") {
|
||||||
AutoId.needsAutoId(AutoId.UUID, StringIdClass(""), "id") shouldBe true
|
AutoId.needsAutoId(AutoId.UUID, StringIdClass(""), "id") shouldBe true
|
||||||
@ -71,7 +71,7 @@ class AutoIdSpec extends AnyFunSpec with Matchers {
|
|||||||
AutoId.needsAutoId(AutoId.UUID, StringIdClass("howdy"), "id") shouldBe false
|
AutoId.needsAutoId(AutoId.UUID, StringIdClass("howdy"), "id") shouldBe false
|
||||||
}
|
}
|
||||||
it("fails for UUID strategy and non-string ID") {
|
it("fails for UUID strategy and non-string ID") {
|
||||||
an [DocumentException] should be thrownBy AutoId.needsAutoId(AutoId.UUID, IntIdClass(5), "id")
|
a [DocumentException] should be thrownBy AutoId.needsAutoId(AutoId.UUID, IntIdClass(5), "id")
|
||||||
}
|
}
|
||||||
it("returns true for Random String strategy and blank ID") {
|
it("returns true for Random String strategy and blank ID") {
|
||||||
AutoId.needsAutoId(AutoId.RANDOM_STRING, StringIdClass(""), "id") shouldBe true
|
AutoId.needsAutoId(AutoId.RANDOM_STRING, StringIdClass(""), "id") shouldBe true
|
||||||
@ -80,7 +80,7 @@ class AutoIdSpec extends AnyFunSpec with Matchers {
|
|||||||
AutoId.needsAutoId(AutoId.RANDOM_STRING, StringIdClass("full"), "id") shouldBe false
|
AutoId.needsAutoId(AutoId.RANDOM_STRING, StringIdClass("full"), "id") shouldBe false
|
||||||
}
|
}
|
||||||
it("fails for Random String strategy and non-string ID") {
|
it("fails for Random String strategy and non-string ID") {
|
||||||
an [DocumentException] should be thrownBy AutoId.needsAutoId(AutoId.RANDOM_STRING, ShortIdClass(55), "id")
|
a [DocumentException] should be thrownBy AutoId.needsAutoId(AutoId.RANDOM_STRING, ShortIdClass(55), "id")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ class ConfigurationSpec extends AnyFunSpec with Matchers {
|
|||||||
describe("dialect") {
|
describe("dialect") {
|
||||||
it("is derived from connection string") {
|
it("is derived from connection string") {
|
||||||
try {
|
try {
|
||||||
an [DocumentException] should be thrownBy Configuration.dialect()
|
a [DocumentException] should be thrownBy Configuration.dialect()
|
||||||
Configuration.setConnectionString("jdbc:postgresql:db")
|
Configuration.setConnectionString("jdbc:postgresql:db")
|
||||||
Configuration.dialect() shouldEqual Dialect.POSTGRESQL
|
Configuration.dialect() shouldEqual Dialect.POSTGRESQL
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -13,7 +13,7 @@ class FieldSpec extends AnyFunSpec with ClearConfiguration with Matchers {
|
|||||||
|
|
||||||
describe("withParameterName") {
|
describe("withParameterName") {
|
||||||
it("fails for invalid name") {
|
it("fails for invalid name") {
|
||||||
an [DocumentException] should be thrownBy Field.equal("it", "").withParameterName("2424")
|
a [DocumentException] should be thrownBy Field.equal("it", "").withParameterName("2424")
|
||||||
}
|
}
|
||||||
it("works with colon prefix") {
|
it("works with colon prefix") {
|
||||||
val field = Field.equal("abc", "22").withQualifier("me")
|
val field = Field.equal("abc", "22").withQualifier("me")
|
||||||
@ -49,36 +49,36 @@ class FieldSpec extends AnyFunSpec with ClearConfiguration with Matchers {
|
|||||||
|
|
||||||
describe("path") {
|
describe("path") {
|
||||||
it("generates simple unqualified PostgreSQL field") {
|
it("generates simple unqualified PostgreSQL field") {
|
||||||
Field.greaterOrEqual("SomethingCool", 18)
|
Field.greaterOrEqual("SomethingCool", 18).path(Dialect.POSTGRESQL, FieldFormat.SQL) shouldEqual
|
||||||
.path(Dialect.POSTGRESQL, FieldFormat.SQL) shouldEqual "data->>'SomethingCool'"
|
"data->>'SomethingCool'"
|
||||||
}
|
}
|
||||||
it("generates simple qualified PostgreSQL field") {
|
it("generates simple qualified PostgreSQL field") {
|
||||||
Field.less("SomethingElse", 9).withQualifier("this")
|
Field.less("SomethingElse", 9).withQualifier("this").path(Dialect.POSTGRESQL, FieldFormat.SQL) shouldEqual
|
||||||
.path(Dialect.POSTGRESQL, FieldFormat.SQL) shouldEqual "this.data->>'SomethingElse'"
|
"this.data->>'SomethingElse'"
|
||||||
}
|
}
|
||||||
it("generates nested unqualified PostgreSQL field") {
|
it("generates nested unqualified PostgreSQL field") {
|
||||||
Field.equal("My.Nested.Field", "howdy")
|
Field.equal("My.Nested.Field", "howdy").path(Dialect.POSTGRESQL, FieldFormat.SQL) shouldEqual
|
||||||
.path(Dialect.POSTGRESQL, FieldFormat.SQL) shouldEqual "data#>>'{My,Nested,Field}'"
|
"data#>>'{My,Nested,Field}'"
|
||||||
}
|
}
|
||||||
it("generates nested qualified PostgreSQL field") {
|
it("generates nested qualified PostgreSQL field") {
|
||||||
Field.equal("Nest.Away", "doc").withQualifier("bird")
|
Field.equal("Nest.Away", "doc").withQualifier("bird").path(Dialect.POSTGRESQL, FieldFormat.SQL) shouldEqual
|
||||||
.path(Dialect.POSTGRESQL, FieldFormat.SQL) shouldEqual "bird.data#>>'{Nest,Away}'"
|
"bird.data#>>'{Nest,Away}'"
|
||||||
}
|
}
|
||||||
it("generates simple unqualified SQLite field") {
|
it("generates simple unqualified SQLite field") {
|
||||||
Field.greaterOrEqual("SomethingCool", 18)
|
Field.greaterOrEqual("SomethingCool", 18).path(Dialect.SQLITE, FieldFormat.SQL) shouldEqual
|
||||||
.path(Dialect.SQLITE, FieldFormat.SQL) shouldEqual "data->>'SomethingCool'"
|
"data->>'SomethingCool'"
|
||||||
}
|
}
|
||||||
it("generates simple qualified SQLite field") {
|
it("generates simple qualified SQLite field") {
|
||||||
Field.less("SomethingElse", 9).withQualifier("this")
|
Field.less("SomethingElse", 9).withQualifier("this").path(Dialect.SQLITE, FieldFormat.SQL) shouldEqual
|
||||||
.path(Dialect.SQLITE, FieldFormat.SQL) shouldEqual "this.data->>'SomethingElse'"
|
"this.data->>'SomethingElse'"
|
||||||
}
|
}
|
||||||
it("generates nested unqualified SQLite field") {
|
it("generates nested unqualified SQLite field") {
|
||||||
Field.equal("My.Nested.Field", "howdy")
|
Field.equal("My.Nested.Field", "howdy").path(Dialect.SQLITE, FieldFormat.SQL) shouldEqual
|
||||||
.path(Dialect.SQLITE, FieldFormat.SQL) shouldEqual "data->'My'->'Nested'->>'Field'"
|
"data->'My'->'Nested'->>'Field'"
|
||||||
}
|
}
|
||||||
it("generates nested qualified SQLite field") {
|
it("generates nested qualified SQLite field") {
|
||||||
Field.equal("Nest.Away", "doc").withQualifier("bird")
|
Field.equal("Nest.Away", "doc").withQualifier("bird").path(Dialect.SQLITE, FieldFormat.SQL) shouldEqual
|
||||||
.path(Dialect.SQLITE, FieldFormat.SQL) shouldEqual "bird.data->'Nest'->>'Away'"
|
"bird.data->'Nest'->>'Away'"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,8 +105,8 @@ class FieldSpec extends AnyFunSpec with ClearConfiguration with Matchers {
|
|||||||
}
|
}
|
||||||
it("generates BETWEEN w/o qualifier, alphanumeric range | PostgreSQL") {
|
it("generates BETWEEN w/o qualifier, alphanumeric range | PostgreSQL") {
|
||||||
ForceDialect.postgres()
|
ForceDialect.postgres()
|
||||||
Field.between("city", "Atlanta", "Chicago", ":city")
|
Field.between("city", "Atlanta", "Chicago", ":city").toWhere shouldEqual
|
||||||
.toWhere shouldEqual "data->>'city' BETWEEN :citymin AND :citymax"
|
"data->>'city' BETWEEN :citymin AND :citymax"
|
||||||
}
|
}
|
||||||
it("generates BETWEEN w/o qualifier | SQLite") {
|
it("generates BETWEEN w/o qualifier | SQLite") {
|
||||||
ForceDialect.sqlite()
|
ForceDialect.sqlite()
|
||||||
@ -114,43 +114,43 @@ class FieldSpec extends AnyFunSpec with ClearConfiguration with Matchers {
|
|||||||
}
|
}
|
||||||
it("generates BETWEEN w/ qualifier, numeric range | PostgreSQL") {
|
it("generates BETWEEN w/ qualifier, numeric range | PostgreSQL") {
|
||||||
ForceDialect.postgres()
|
ForceDialect.postgres()
|
||||||
Field.between("age", 13, 17, "@age").withQualifier("test")
|
Field.between("age", 13, 17, "@age").withQualifier("test").toWhere shouldEqual
|
||||||
.toWhere shouldEqual "(test.data->>'age')::numeric BETWEEN @agemin AND @agemax"
|
"(test.data->>'age')::numeric BETWEEN @agemin AND @agemax"
|
||||||
}
|
}
|
||||||
it("generates BETWEEN w/ qualifier, alphanumeric range | PostgreSQL") {
|
it("generates BETWEEN w/ qualifier, alphanumeric range | PostgreSQL") {
|
||||||
ForceDialect.postgres()
|
ForceDialect.postgres()
|
||||||
Field.between("city", "Atlanta", "Chicago", ":city").withQualifier("unit")
|
Field.between("city", "Atlanta", "Chicago", ":city").withQualifier("unit").toWhere shouldEqual
|
||||||
.toWhere shouldEqual "unit.data->>'city' BETWEEN :citymin AND :citymax"
|
"unit.data->>'city' BETWEEN :citymin AND :citymax"
|
||||||
}
|
}
|
||||||
it("generates BETWEEN w/ qualifier | SQLite") {
|
it("generates BETWEEN w/ qualifier | SQLite") {
|
||||||
ForceDialect.sqlite()
|
ForceDialect.sqlite()
|
||||||
Field.between("age", 13, 17, "@age").withQualifier("my")
|
Field.between("age", 13, 17, "@age").withQualifier("my").toWhere shouldEqual
|
||||||
.toWhere shouldEqual "my.data->>'age' BETWEEN @agemin AND @agemax"
|
"my.data->>'age' BETWEEN @agemin AND @agemax"
|
||||||
}
|
}
|
||||||
it("generates IN/any, numeric values | PostgreSQL") {
|
it("generates IN/any, numeric values | PostgreSQL") {
|
||||||
ForceDialect.postgres()
|
ForceDialect.postgres()
|
||||||
Field.any("even", List(2, 4, 6).asJava, ":nbr")
|
Field.any("even", List(2, 4, 6).asJava, ":nbr").toWhere shouldEqual
|
||||||
.toWhere shouldEqual "(data->>'even')::numeric IN (:nbr_0, :nbr_1, :nbr_2)"
|
"(data->>'even')::numeric IN (:nbr_0, :nbr_1, :nbr_2)"
|
||||||
}
|
}
|
||||||
it("generates IN/any, alphanumeric values | PostgreSQL") {
|
it("generates IN/any, alphanumeric values | PostgreSQL") {
|
||||||
ForceDialect.postgres()
|
ForceDialect.postgres()
|
||||||
Field.any("test", List("Atlanta", "Chicago").asJava, ":city")
|
Field.any("test", List("Atlanta", "Chicago").asJava, ":city").toWhere shouldEqual
|
||||||
.toWhere shouldEqual "data->>'test' IN (:city_0, :city_1)"
|
"data->>'test' IN (:city_0, :city_1)"
|
||||||
}
|
}
|
||||||
it("generates IN/any | SQLite") {
|
it("generates IN/any | SQLite") {
|
||||||
ForceDialect.sqlite()
|
ForceDialect.sqlite()
|
||||||
Field.any("test", List("Atlanta", "Chicago").asJava, ":city")
|
Field.any("test", List("Atlanta", "Chicago").asJava, ":city").toWhere shouldEqual
|
||||||
.toWhere shouldEqual "data->>'test' IN (:city_0, :city_1)"
|
"data->>'test' IN (:city_0, :city_1)"
|
||||||
}
|
}
|
||||||
it("generates inArray | PostgreSQL") {
|
it("generates inArray | PostgreSQL") {
|
||||||
ForceDialect.postgres()
|
ForceDialect.postgres()
|
||||||
Field.inArray("even", "tbl", List(2, 4, 6, 8).asJava, ":it")
|
Field.inArray("even", "tbl", List(2, 4, 6, 8).asJava, ":it").toWhere shouldEqual
|
||||||
.toWhere shouldEqual "data->'even' ??| ARRAY[:it_0, :it_1, :it_2, :it_3]"
|
"data->'even' ??| ARRAY[:it_0, :it_1, :it_2, :it_3]"
|
||||||
}
|
}
|
||||||
it("generates inArray | SQLite") {
|
it("generates inArray | SQLite") {
|
||||||
ForceDialect.sqlite()
|
ForceDialect.sqlite()
|
||||||
Field.inArray("test", "tbl", List("Atlanta", "Chicago").asJava, ":city")
|
Field.inArray("test", "tbl", List("Atlanta", "Chicago").asJava, ":city").toWhere shouldEqual
|
||||||
.toWhere shouldEqual "EXISTS (SELECT 1 FROM json_each(tbl.data, '$.test') WHERE value IN (:city_0, :city_1))"
|
"EXISTS (SELECT 1 FROM json_each(tbl.data, '$.test') WHERE value IN (:city_0, :city_1))"
|
||||||
}
|
}
|
||||||
it("generates others w/o qualifier | PostgreSQL") {
|
it("generates others w/o qualifier | PostgreSQL") {
|
||||||
ForceDialect.postgres()
|
ForceDialect.postgres()
|
||||||
@ -170,8 +170,8 @@ class FieldSpec extends AnyFunSpec with ClearConfiguration with Matchers {
|
|||||||
}
|
}
|
||||||
it("generates parameter w/ qualifier | PostgreSQL") {
|
it("generates parameter w/ qualifier | PostgreSQL") {
|
||||||
ForceDialect.postgres()
|
ForceDialect.postgres()
|
||||||
Field.lessOrEqual("le_field", 18, ":it").withQualifier("q")
|
Field.lessOrEqual("le_field", 18, ":it").withQualifier("q").toWhere shouldEqual
|
||||||
.toWhere shouldEqual "(q.data->>'le_field')::numeric <= :it"
|
"(q.data->>'le_field')::numeric <= :it"
|
||||||
}
|
}
|
||||||
it("generates parameter w/ qualifier | SQLite") {
|
it("generates parameter w/ qualifier | SQLite") {
|
||||||
ForceDialect.sqlite()
|
ForceDialect.sqlite()
|
||||||
@ -391,7 +391,7 @@ class FieldSpec extends AnyFunSpec with ClearConfiguration with Matchers {
|
|||||||
|
|
||||||
describe("static constructors") {
|
describe("static constructors") {
|
||||||
it("fail for invalid parameter name") {
|
it("fail for invalid parameter name") {
|
||||||
an [DocumentException] should be thrownBy Field.equal("a", "b", "that ain't it, Jack...")
|
a [DocumentException] should be thrownBy Field.equal("a", "b", "that ain't it, Jack...")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -403,12 +403,12 @@ class FieldSpec extends AnyFunSpec with ClearConfiguration with Matchers {
|
|||||||
Field.nameToPath("Simple", Dialect.SQLITE, FieldFormat.SQL) shouldEqual "data->>'Simple'"
|
Field.nameToPath("Simple", Dialect.SQLITE, FieldFormat.SQL) shouldEqual "data->>'Simple'"
|
||||||
}
|
}
|
||||||
it("creates a nested PostgreSQL SQL name") {
|
it("creates a nested PostgreSQL SQL name") {
|
||||||
(Field.nameToPath("A.Long.Path.to.the.Property", Dialect.POSTGRESQL, FieldFormat.SQL)
|
Field.nameToPath("A.Long.Path.to.the.Property", Dialect.POSTGRESQL, FieldFormat.SQL) shouldEqual
|
||||||
shouldEqual "data#>>'{A,Long,Path,to,the,Property}'")
|
"data#>>'{A,Long,Path,to,the,Property}'"
|
||||||
}
|
}
|
||||||
it("creates a nested SQLite SQL name") {
|
it("creates a nested SQLite SQL name") {
|
||||||
(Field.nameToPath("A.Long.Path.to.the.Property", Dialect.SQLITE, FieldFormat.SQL)
|
Field.nameToPath("A.Long.Path.to.the.Property", Dialect.SQLITE, FieldFormat.SQL) shouldEqual
|
||||||
shouldEqual "data->'A'->'Long'->'Path'->'to'->'the'->>'Property'")
|
"data->'A'->'Long'->'Path'->'to'->'the'->>'Property'"
|
||||||
}
|
}
|
||||||
it("creates a simple PostgreSQL JSON name") {
|
it("creates a simple PostgreSQL JSON name") {
|
||||||
Field.nameToPath("Simple", Dialect.POSTGRESQL, FieldFormat.JSON) shouldEqual "data->'Simple'"
|
Field.nameToPath("Simple", Dialect.POSTGRESQL, FieldFormat.JSON) shouldEqual "data->'Simple'"
|
||||||
@ -417,12 +417,12 @@ class FieldSpec extends AnyFunSpec with ClearConfiguration with Matchers {
|
|||||||
Field.nameToPath("Simple", Dialect.SQLITE, FieldFormat.JSON) shouldEqual "data->'Simple'"
|
Field.nameToPath("Simple", Dialect.SQLITE, FieldFormat.JSON) shouldEqual "data->'Simple'"
|
||||||
}
|
}
|
||||||
it("creates a nested PostgreSQL JSON name") {
|
it("creates a nested PostgreSQL JSON name") {
|
||||||
(Field.nameToPath("A.Long.Path.to.the.Property", Dialect.POSTGRESQL, FieldFormat.JSON)
|
Field.nameToPath("A.Long.Path.to.the.Property", Dialect.POSTGRESQL, FieldFormat.JSON) shouldEqual
|
||||||
shouldEqual "data#>'{A,Long,Path,to,the,Property}'")
|
"data#>'{A,Long,Path,to,the,Property}'"
|
||||||
}
|
}
|
||||||
it("creates a nested SQLite JSON name") {
|
it("creates a nested SQLite JSON name") {
|
||||||
(Field.nameToPath("A.Long.Path.to.the.Property", Dialect.SQLITE, FieldFormat.JSON)
|
Field.nameToPath("A.Long.Path.to.the.Property", Dialect.SQLITE, FieldFormat.JSON) shouldEqual
|
||||||
shouldEqual "data->'A'->'Long'->'Path'->'to'->'the'->'Property'")
|
"data->'A'->'Long'->'Path'->'to'->'the'->'Property'"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ class ParameterSpec extends AnyFunSpec with Matchers {
|
|||||||
p.getValue should be (null)
|
p.getValue should be (null)
|
||||||
}
|
}
|
||||||
it("fails with incorrect prefix") {
|
it("fails with incorrect prefix") {
|
||||||
an [DocumentException] should be thrownBy Parameter("it", ParameterType.JSON, "")
|
a [DocumentException] should be thrownBy Parameter("it", ParameterType.JSON, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
package solutions.bitbadger.documents.scala.query
|
||||||
|
|
||||||
|
import org.scalatest.funspec.AnyFunSpec
|
||||||
|
import org.scalatest.matchers.should.Matchers
|
||||||
|
import solutions.bitbadger.documents.{DocumentException, Field}
|
||||||
|
import solutions.bitbadger.documents.query.CountQuery
|
||||||
|
import solutions.bitbadger.documents.scala.ClearConfiguration
|
||||||
|
import solutions.bitbadger.documents.support.ForceDialect
|
||||||
|
import solutions.bitbadger.documents.support.TypesKt.TEST_TABLE
|
||||||
|
|
||||||
|
import scala.jdk.CollectionConverters.*
|
||||||
|
|
||||||
|
class CountQuerySpec extends AnyFunSpec with ClearConfiguration with Matchers {
|
||||||
|
|
||||||
|
describe("all") {
|
||||||
|
it("generates correctly") {
|
||||||
|
CountQuery.all(TEST_TABLE) shouldEqual s"SELECT COUNT(*) AS it FROM $TEST_TABLE"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("byFields") {
|
||||||
|
it("generates correctly | PostgreSQL") {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
CountQuery.byFields(TEST_TABLE, List(Field.equal("test", "", ":field0")).asJava) shouldEqual
|
||||||
|
s"SELECT COUNT(*) AS it FROM $TEST_TABLE WHERE data->>'test' = :field0"
|
||||||
|
}
|
||||||
|
it("generates correctly | SQLite") {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
CountQuery.byFields(TEST_TABLE, List(Field.equal("test", "", ":field0")).asJava) shouldEqual
|
||||||
|
s"SELECT COUNT(*) AS it FROM $TEST_TABLE WHERE data->>'test' = :field0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("byContains") {
|
||||||
|
it("generates correctly | PostgreSQL") {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
CountQuery.byContains(TEST_TABLE) shouldEqual s"SELECT COUNT(*) AS it FROM $TEST_TABLE WHERE data @> :criteria"
|
||||||
|
}
|
||||||
|
it("fails | SQLite") {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
a [DocumentException] should be thrownBy CountQuery.byContains(TEST_TABLE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("byJsonPath") {
|
||||||
|
it("generates correctly | PostgreSQL") {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
CountQuery.byJsonPath(TEST_TABLE) shouldEqual
|
||||||
|
s"SELECT COUNT(*) AS it FROM $TEST_TABLE WHERE jsonb_path_exists(data, :path::jsonpath)"
|
||||||
|
}
|
||||||
|
it("fails | SQLite") {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
a [DocumentException] should be thrownBy CountQuery.byJsonPath(TEST_TABLE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
package solutions.bitbadger.documents.scala.query
|
||||||
|
|
||||||
|
import org.scalatest.funspec.AnyFunSpec
|
||||||
|
import org.scalatest.matchers.should.Matchers
|
||||||
|
import solutions.bitbadger.documents.{Dialect, DocumentException, DocumentIndex}
|
||||||
|
import solutions.bitbadger.documents.query.DefinitionQuery
|
||||||
|
import solutions.bitbadger.documents.scala.ClearConfiguration
|
||||||
|
import solutions.bitbadger.documents.support.ForceDialect
|
||||||
|
import solutions.bitbadger.documents.support.TypesKt.TEST_TABLE
|
||||||
|
|
||||||
|
import scala.jdk.CollectionConverters.*
|
||||||
|
|
||||||
|
class DefinitionQuerySpec extends AnyFunSpec with ClearConfiguration with Matchers {
|
||||||
|
|
||||||
|
describe("ensureTableFor") {
|
||||||
|
it("generates correctly") {
|
||||||
|
DefinitionQuery.ensureTableFor("my.table", "JSONB") shouldEqual
|
||||||
|
"CREATE TABLE IF NOT EXISTS my.table (data JSONB NOT NULL)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("ensureTable") {
|
||||||
|
it("generates correctly | PostgreSQL") {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
DefinitionQuery.ensureTable(TEST_TABLE) shouldEqual
|
||||||
|
s"CREATE TABLE IF NOT EXISTS $TEST_TABLE (data JSONB NOT NULL)"
|
||||||
|
}
|
||||||
|
it("generates correctly | SQLite") {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
DefinitionQuery.ensureTable(TEST_TABLE) shouldEqual
|
||||||
|
s"CREATE TABLE IF NOT EXISTS $TEST_TABLE (data TEXT NOT NULL)"
|
||||||
|
}
|
||||||
|
it("fails when no dialect is set") {
|
||||||
|
a [DocumentException] should be thrownBy DefinitionQuery.ensureTable(TEST_TABLE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("ensureKey") {
|
||||||
|
it("generates correctly with schema") {
|
||||||
|
DefinitionQuery.ensureKey("test.table", Dialect.POSTGRESQL) shouldEqual
|
||||||
|
"CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON test.table ((data->>'id'))"
|
||||||
|
}
|
||||||
|
it("generates correctly without schema") {
|
||||||
|
DefinitionQuery.ensureKey(TEST_TABLE, Dialect.SQLITE) shouldEqual
|
||||||
|
s"CREATE UNIQUE INDEX IF NOT EXISTS idx_${TEST_TABLE}_key ON $TEST_TABLE ((data->>'id'))"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("ensureIndexOn") {
|
||||||
|
it("generates multiple fields and directions") {
|
||||||
|
DefinitionQuery.ensureIndexOn("test.table", "gibberish", List("taco", "guac DESC", "salsa ASC").asJava,
|
||||||
|
Dialect.POSTGRESQL) shouldEqual
|
||||||
|
"CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table ((data->>'taco'), (data->>'guac') DESC, (data->>'salsa') ASC)"
|
||||||
|
}
|
||||||
|
it("generates nested field | PostgreSQL") {
|
||||||
|
DefinitionQuery.ensureIndexOn(TEST_TABLE, "nest", List("a.b.c").asJava, Dialect.POSTGRESQL) shouldEqual
|
||||||
|
s"CREATE INDEX IF NOT EXISTS idx_${TEST_TABLE}_nest ON $TEST_TABLE ((data#>>'{a,b,c}'))"
|
||||||
|
}
|
||||||
|
it("generates nested field | SQLite") {
|
||||||
|
DefinitionQuery.ensureIndexOn(TEST_TABLE, "nest", List("a.b.c").asJava, Dialect.SQLITE) shouldEqual
|
||||||
|
s"CREATE INDEX IF NOT EXISTS idx_${TEST_TABLE}_nest ON $TEST_TABLE ((data->'a'->'b'->>'c'))"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("ensureDocumentOn") {
|
||||||
|
it("generates Full | PostgreSQL") {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
DefinitionQuery.ensureDocumentIndexOn(TEST_TABLE, DocumentIndex.FULL) shouldEqual
|
||||||
|
s"CREATE INDEX IF NOT EXISTS idx_${TEST_TABLE}_document ON $TEST_TABLE USING GIN (data)"
|
||||||
|
}
|
||||||
|
it("generates Optimized | PostgreSQL") {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
DefinitionQuery.ensureDocumentIndexOn(TEST_TABLE, DocumentIndex.OPTIMIZED) shouldEqual
|
||||||
|
s"CREATE INDEX IF NOT EXISTS idx_${TEST_TABLE}_document ON $TEST_TABLE USING GIN (data jsonb_path_ops)"
|
||||||
|
}
|
||||||
|
it("fails | SQLite") {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
a [DocumentException] should be thrownBy DefinitionQuery.ensureDocumentIndexOn(TEST_TABLE, DocumentIndex.FULL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
package solutions.bitbadger.documents.scala.query
|
||||||
|
|
||||||
|
import org.scalatest.funspec.AnyFunSpec
|
||||||
|
import org.scalatest.matchers.should.Matchers
|
||||||
|
import solutions.bitbadger.documents.{DocumentException, Field}
|
||||||
|
import solutions.bitbadger.documents.query.DeleteQuery
|
||||||
|
import solutions.bitbadger.documents.scala.ClearConfiguration
|
||||||
|
import solutions.bitbadger.documents.support.ForceDialect
|
||||||
|
import solutions.bitbadger.documents.support.TypesKt.TEST_TABLE
|
||||||
|
|
||||||
|
import scala.jdk.CollectionConverters.*
|
||||||
|
|
||||||
|
class DeleteQuerySpec extends AnyFunSpec with ClearConfiguration with Matchers {
|
||||||
|
|
||||||
|
describe("byId") {
|
||||||
|
it("generates correctly | PostgreSQL") {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
DeleteQuery.byId(TEST_TABLE) shouldEqual s"DELETE FROM $TEST_TABLE WHERE data->>'id' = :id"
|
||||||
|
}
|
||||||
|
it("generates correctly | SQLite") {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
DeleteQuery.byId(TEST_TABLE) shouldEqual s"DELETE FROM $TEST_TABLE WHERE data->>'id' = :id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("byFields") {
|
||||||
|
it("generates correctly | PostgreSQL") {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
DeleteQuery.byFields(TEST_TABLE, List(Field.equal("a", "", ":b")).asJava) shouldEqual
|
||||||
|
s"DELETE FROM $TEST_TABLE WHERE data->>'a' = :b"
|
||||||
|
}
|
||||||
|
it("generates correctly | SQLite") {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
DeleteQuery.byFields(TEST_TABLE, List(Field.equal("a", "", ":b")).asJava) shouldEqual
|
||||||
|
s"DELETE FROM $TEST_TABLE WHERE data->>'a' = :b"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("byContains") {
|
||||||
|
it("generates correctly | PostgreSQL") {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
DeleteQuery.byContains(TEST_TABLE) shouldEqual s"DELETE FROM $TEST_TABLE WHERE data @> :criteria"
|
||||||
|
}
|
||||||
|
it("fails | SQLite") {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
a [DocumentException] should be thrownBy DeleteQuery.byContains(TEST_TABLE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("byJsonPath") {
|
||||||
|
it("generates correctly | PostgreSQL") {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
DeleteQuery.byJsonPath(TEST_TABLE) shouldEqual
|
||||||
|
s"DELETE FROM $TEST_TABLE WHERE jsonb_path_exists(data, :path::jsonpath)"
|
||||||
|
}
|
||||||
|
it("fails | SQLite") {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
a [DocumentException] should be thrownBy DeleteQuery.byJsonPath(TEST_TABLE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
package solutions.bitbadger.documents.scala.query
|
||||||
|
|
||||||
|
import org.scalatest.funspec.AnyFunSpec
|
||||||
|
import org.scalatest.matchers.should.Matchers
|
||||||
|
import solutions.bitbadger.documents.{AutoId, Configuration, DocumentException}
|
||||||
|
import solutions.bitbadger.documents.query.DocumentQuery
|
||||||
|
import solutions.bitbadger.documents.scala.ClearConfiguration
|
||||||
|
import solutions.bitbadger.documents.support.ForceDialect
|
||||||
|
import solutions.bitbadger.documents.support.TypesKt.TEST_TABLE
|
||||||
|
|
||||||
|
class DocumentQuerySpec extends AnyFunSpec with ClearConfiguration with Matchers {
|
||||||
|
|
||||||
|
describe("insert") {
|
||||||
|
it("generates no auto ID | PostgreSQL") {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
DocumentQuery.insert(TEST_TABLE) shouldEqual s"INSERT INTO $TEST_TABLE VALUES (:data)"
|
||||||
|
}
|
||||||
|
it("generates no auto ID | SQLite") {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
DocumentQuery.insert(TEST_TABLE) shouldEqual s"INSERT INTO $TEST_TABLE VALUES (:data)"
|
||||||
|
}
|
||||||
|
it("generates auto number | PostgreSQL") {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
DocumentQuery.insert(TEST_TABLE, AutoId.NUMBER) shouldEqual
|
||||||
|
s"INSERT INTO $TEST_TABLE VALUES (:data::jsonb || ('{\"id\":' " +
|
||||||
|
s"|| (SELECT COALESCE(MAX((data->>'id')::numeric), 0) + 1 FROM $TEST_TABLE) || '}')::jsonb)"
|
||||||
|
}
|
||||||
|
it("generates auto number | SQLite") {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
DocumentQuery.insert(TEST_TABLE, AutoId.NUMBER) shouldEqual
|
||||||
|
s"INSERT INTO $TEST_TABLE VALUES (json_set(:data, '$$.id', " +
|
||||||
|
s"(SELECT coalesce(max(data->>'id'), 0) + 1 FROM $TEST_TABLE)))"
|
||||||
|
}
|
||||||
|
it("generates auto UUID | PostgreSQL") {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
val query = DocumentQuery.insert(TEST_TABLE, AutoId.UUID)
|
||||||
|
query should startWith (s"INSERT INTO $TEST_TABLE VALUES (:data::jsonb || '{\"id\":\"")
|
||||||
|
query should endWith ("\"}')")
|
||||||
|
}
|
||||||
|
it("generates auto UUID | SQLite") {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
val query = DocumentQuery.insert(TEST_TABLE, AutoId.UUID)
|
||||||
|
query should startWith (s"INSERT INTO $TEST_TABLE VALUES (json_set(:data, '$$.id', '")
|
||||||
|
query should endWith ("'))")
|
||||||
|
}
|
||||||
|
it("generates auto random string | PostgreSQL") {
|
||||||
|
try {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
Configuration.idStringLength = 8
|
||||||
|
val query = DocumentQuery.insert(TEST_TABLE, AutoId.RANDOM_STRING)
|
||||||
|
query should startWith (s"INSERT INTO $TEST_TABLE VALUES (:data::jsonb || '{\"id\":\"")
|
||||||
|
query should endWith ("\"}')")
|
||||||
|
query.replace(s"INSERT INTO $TEST_TABLE VALUES (:data::jsonb || '{\"id\":\"", "").replace("\"}')", "")
|
||||||
|
.length shouldEqual 8
|
||||||
|
} finally {
|
||||||
|
Configuration.idStringLength = 16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it("insert generates auto random string | SQLite") {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
val query = DocumentQuery.insert(TEST_TABLE, AutoId.RANDOM_STRING)
|
||||||
|
query should startWith (s"INSERT INTO $TEST_TABLE VALUES (json_set(:data, '$$.id', '")
|
||||||
|
query should endWith ("'))")
|
||||||
|
query.replace(s"INSERT INTO $TEST_TABLE VALUES (json_set(:data, '$$.id', '", "").replace("'))", "")
|
||||||
|
.length shouldEqual Configuration.idStringLength
|
||||||
|
}
|
||||||
|
it("fails when no dialect is set") {
|
||||||
|
a [DocumentException] should be thrownBy DocumentQuery.insert(TEST_TABLE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("save") {
|
||||||
|
it("generates correctly") {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
DocumentQuery.save(TEST_TABLE) shouldEqual
|
||||||
|
s"INSERT INTO $TEST_TABLE VALUES (:data) ON CONFLICT ((data->>'id')) DO UPDATE SET data = EXCLUDED.data"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("update") {
|
||||||
|
it("generates successfully") {
|
||||||
|
DocumentQuery.update(TEST_TABLE) shouldEqual s"UPDATE $TEST_TABLE SET data = :data"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
package solutions.bitbadger.documents.scala.query
|
||||||
|
|
||||||
|
import org.scalatest.funspec.AnyFunSpec
|
||||||
|
import org.scalatest.matchers.should.Matchers
|
||||||
|
import solutions.bitbadger.documents.{DocumentException, Field}
|
||||||
|
import solutions.bitbadger.documents.query.ExistsQuery
|
||||||
|
import solutions.bitbadger.documents.scala.ClearConfiguration
|
||||||
|
import solutions.bitbadger.documents.support.ForceDialect
|
||||||
|
import solutions.bitbadger.documents.support.TypesKt.TEST_TABLE
|
||||||
|
|
||||||
|
import scala.jdk.CollectionConverters.*
|
||||||
|
|
||||||
|
class ExistsQuerySpec extends AnyFunSpec with ClearConfiguration with Matchers {
|
||||||
|
|
||||||
|
describe("byId") {
|
||||||
|
it("generates correctly | PostgreSQL") {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
ExistsQuery.byId(TEST_TABLE) shouldEqual
|
||||||
|
s"SELECT EXISTS (SELECT 1 FROM $TEST_TABLE WHERE data->>'id' = :id) AS it"
|
||||||
|
}
|
||||||
|
it("generates correctly | SQLite") {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
ExistsQuery.byId(TEST_TABLE) shouldEqual
|
||||||
|
s"SELECT EXISTS (SELECT 1 FROM $TEST_TABLE WHERE data->>'id' = :id) AS it"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("byFields") {
|
||||||
|
it("generates correctly | PostgreSQL") {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
ExistsQuery.byFields(TEST_TABLE, List(Field.equal("it", 7, ":test")).asJava) shouldEqual
|
||||||
|
s"SELECT EXISTS (SELECT 1 FROM $TEST_TABLE WHERE (data->>'it')::numeric = :test) AS it"
|
||||||
|
}
|
||||||
|
it("generates correctly | SQLite") {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
ExistsQuery.byFields(TEST_TABLE, List(Field.equal("it", 7, ":test")).asJava) shouldEqual
|
||||||
|
s"SELECT EXISTS (SELECT 1 FROM $TEST_TABLE WHERE data->>'it' = :test) AS it"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("byContains") {
|
||||||
|
it("generates correctly | PostgreSQL") {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
ExistsQuery.byContains(TEST_TABLE) shouldEqual
|
||||||
|
s"SELECT EXISTS (SELECT 1 FROM $TEST_TABLE WHERE data @> :criteria) AS it"
|
||||||
|
}
|
||||||
|
it("fails | SQLite") {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
a [DocumentException] should be thrownBy ExistsQuery.byContains(TEST_TABLE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("byJsonPath") {
|
||||||
|
it("generates correctly | PostgreSQL") {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
ExistsQuery.byJsonPath(TEST_TABLE) shouldEqual
|
||||||
|
s"SELECT EXISTS (SELECT 1 FROM $TEST_TABLE WHERE jsonb_path_exists(data, :path::jsonpath)) AS it"
|
||||||
|
}
|
||||||
|
it("fails | SQLite") {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
a [DocumentException] should be thrownBy ExistsQuery.byJsonPath(TEST_TABLE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
package solutions.bitbadger.documents.scala.query
|
||||||
|
|
||||||
|
import org.scalatest.funspec.AnyFunSpec
|
||||||
|
import org.scalatest.matchers.should.Matchers
|
||||||
|
import solutions.bitbadger.documents.{DocumentException, Field}
|
||||||
|
import solutions.bitbadger.documents.query.FindQuery
|
||||||
|
import solutions.bitbadger.documents.scala.ClearConfiguration
|
||||||
|
import solutions.bitbadger.documents.support.ForceDialect
|
||||||
|
import solutions.bitbadger.documents.support.TypesKt.TEST_TABLE
|
||||||
|
|
||||||
|
import scala.jdk.CollectionConverters.*
|
||||||
|
|
||||||
|
class FindQuerySpec extends AnyFunSpec with ClearConfiguration with Matchers {
|
||||||
|
|
||||||
|
describe("all") {
|
||||||
|
it("generates correctly") {
|
||||||
|
FindQuery.all(TEST_TABLE) shouldEqual s"SELECT data FROM $TEST_TABLE"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("byId") {
|
||||||
|
it("generates correctly | PostgreSQL") {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
FindQuery.byId(TEST_TABLE) shouldEqual s"SELECT data FROM $TEST_TABLE WHERE data->>'id' = :id"
|
||||||
|
}
|
||||||
|
it("generates correctly | SQLite") {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
FindQuery.byId(TEST_TABLE) shouldEqual s"SELECT data FROM $TEST_TABLE WHERE data->>'id' = :id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("byFields") {
|
||||||
|
it("generates correctly | PostgreSQL") {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
FindQuery.byFields(TEST_TABLE, List(Field.equal("a", "", ":b"), Field.less("c", 14, ":d")).asJava) shouldEqual
|
||||||
|
s"SELECT data FROM $TEST_TABLE WHERE data->>'a' = :b AND (data->>'c')::numeric < :d"
|
||||||
|
}
|
||||||
|
it("generates correctly | SQLite") {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
FindQuery.byFields(TEST_TABLE, List(Field.equal("a", "", ":b"), Field.less("c", 14, ":d")).asJava) shouldEqual
|
||||||
|
s"SELECT data FROM $TEST_TABLE WHERE data->>'a' = :b AND data->>'c' < :d"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("byContains") {
|
||||||
|
it("generates correctly | PostgreSQL") {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
FindQuery.byContains(TEST_TABLE) shouldEqual s"SELECT data FROM $TEST_TABLE WHERE data @> :criteria"
|
||||||
|
}
|
||||||
|
it("fails | SQLite") {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
a [DocumentException] should be thrownBy FindQuery.byContains(TEST_TABLE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("byJsonPath") {
|
||||||
|
it("generates correctly | PostgreSQL") {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
FindQuery.byJsonPath(TEST_TABLE) shouldEqual
|
||||||
|
s"SELECT data FROM $TEST_TABLE WHERE jsonb_path_exists(data, :path::jsonpath)"
|
||||||
|
}
|
||||||
|
it("fails | SQLite") {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
a [DocumentException] should be thrownBy FindQuery.byJsonPath(TEST_TABLE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
package solutions.bitbadger.documents.scala.query
|
||||||
|
|
||||||
|
import org.scalatest.funspec.AnyFunSpec
|
||||||
|
import org.scalatest.matchers.should.Matchers
|
||||||
|
import solutions.bitbadger.documents.{DocumentException, Field}
|
||||||
|
import solutions.bitbadger.documents.query.PatchQuery
|
||||||
|
import solutions.bitbadger.documents.scala.ClearConfiguration
|
||||||
|
import solutions.bitbadger.documents.support.ForceDialect
|
||||||
|
import solutions.bitbadger.documents.support.TypesKt.TEST_TABLE
|
||||||
|
|
||||||
|
import scala.jdk.CollectionConverters.*
|
||||||
|
|
||||||
|
class PatchQuerySpec extends AnyFunSpec with ClearConfiguration with Matchers {
|
||||||
|
|
||||||
|
describe("byId") {
|
||||||
|
it("generates correctly | PostgreSQL") {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
PatchQuery.byId(TEST_TABLE) shouldEqual s"UPDATE $TEST_TABLE SET data = data || :data WHERE data->>'id' = :id"
|
||||||
|
}
|
||||||
|
it("generates correctly | SQLite") {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
PatchQuery.byId(TEST_TABLE) shouldEqual
|
||||||
|
s"UPDATE $TEST_TABLE SET data = json_patch(data, json(:data)) WHERE data->>'id' = :id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("byFields") {
|
||||||
|
it("generates correctly | PostgreSQL") {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
PatchQuery.byFields(TEST_TABLE, List(Field.equal("z", "", ":y")).asJava) shouldEqual
|
||||||
|
s"UPDATE $TEST_TABLE SET data = data || :data WHERE data->>'z' = :y"
|
||||||
|
}
|
||||||
|
it("generates correctly | SQLite") {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
PatchQuery.byFields(TEST_TABLE, List(Field.equal("z", "", ":y")).asJava) shouldEqual
|
||||||
|
s"UPDATE $TEST_TABLE SET data = json_patch(data, json(:data)) WHERE data->>'z' = :y"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("byContains") {
|
||||||
|
it("generates correctly | PostgreSQL") {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
PatchQuery.byContains(TEST_TABLE) shouldEqual
|
||||||
|
s"UPDATE $TEST_TABLE SET data = data || :data WHERE data @> :criteria"
|
||||||
|
}
|
||||||
|
it("fails | SQLite") {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
a [DocumentException] should be thrownBy PatchQuery.byContains(TEST_TABLE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("byJsonPath") {
|
||||||
|
it("generates correctly | PostgreSQL") {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
PatchQuery.byJsonPath(TEST_TABLE) shouldEqual
|
||||||
|
s"UPDATE $TEST_TABLE SET data = data || :data WHERE jsonb_path_exists(data, :path::jsonpath)"
|
||||||
|
}
|
||||||
|
it("fails | SQLite") {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
a [DocumentException] should be thrownBy PatchQuery.byJsonPath(TEST_TABLE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,103 @@
|
|||||||
|
package solutions.bitbadger.documents.scala.query
|
||||||
|
|
||||||
|
import org.scalatest.funspec.AnyFunSpec
|
||||||
|
import org.scalatest.matchers.should.Matchers
|
||||||
|
import solutions.bitbadger.documents.{Dialect, Field, FieldMatch}
|
||||||
|
import solutions.bitbadger.documents.query.QueryUtils
|
||||||
|
import solutions.bitbadger.documents.scala.ClearConfiguration
|
||||||
|
import solutions.bitbadger.documents.support.ForceDialect
|
||||||
|
|
||||||
|
import scala.jdk.CollectionConverters.*
|
||||||
|
|
||||||
|
class QueryUtilsSpec extends AnyFunSpec with ClearConfiguration with Matchers {
|
||||||
|
|
||||||
|
describe("statementWhere") {
|
||||||
|
it("generates correctly") {
|
||||||
|
QueryUtils.statementWhere("x", "y") shouldEqual "x WHERE y"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("byId") {
|
||||||
|
it("generates a numeric ID query | PostgreSQL") {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
QueryUtils.byId("test", 9) shouldEqual "test WHERE (data->>'id')::numeric = :id"
|
||||||
|
}
|
||||||
|
it("generates an alphanumeric ID query | PostgreSQL") {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
QueryUtils.byId("unit", "18") shouldEqual "unit WHERE data->>'id' = :id"
|
||||||
|
}
|
||||||
|
it("generates ID query | SQLite") {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
QueryUtils.byId("yo", 27) shouldEqual "yo WHERE data->>'id' = :id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("byFields") {
|
||||||
|
it("generates default field query | PostgreSQL") {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
QueryUtils.byFields("this",
|
||||||
|
List(Field.equal("a", "", ":the_a"), Field.equal("b", 0, ":b_value")).asJava) shouldEqual
|
||||||
|
"this WHERE data->>'a' = :the_a AND (data->>'b')::numeric = :b_value"
|
||||||
|
}
|
||||||
|
it("generates default field query | SQLite") {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
QueryUtils.byFields("this",
|
||||||
|
List(Field.equal("a", "", ":the_a"), Field.equal("b", 0, ":b_value")).asJava) shouldEqual
|
||||||
|
"this WHERE data->>'a' = :the_a AND data->>'b' = :b_value"
|
||||||
|
}
|
||||||
|
it("generates ANY field query | PostgreSQL") {
|
||||||
|
ForceDialect.postgres()
|
||||||
|
QueryUtils.byFields("that", List(Field.equal("a", "", ":the_a"), Field.equal("b", 0, ":b_value")).asJava,
|
||||||
|
FieldMatch.ANY) shouldEqual
|
||||||
|
"that WHERE data->>'a' = :the_a OR (data->>'b')::numeric = :b_value"
|
||||||
|
}
|
||||||
|
it("generates ANY field query | SQLite") {
|
||||||
|
ForceDialect.sqlite()
|
||||||
|
QueryUtils.byFields("that", List(Field.equal("a", "", ":the_a"), Field.equal("b", 0, ":b_value")).asJava,
|
||||||
|
FieldMatch.ANY) shouldEqual
|
||||||
|
"that WHERE data->>'a' = :the_a OR data->>'b' = :b_value"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("orderBy") {
|
||||||
|
it("generates for no fields") {
|
||||||
|
QueryUtils.orderBy(List().asJava, Dialect.POSTGRESQL) shouldEqual ""
|
||||||
|
QueryUtils.orderBy(List().asJava, Dialect.SQLITE) shouldEqual ""
|
||||||
|
}
|
||||||
|
it("generates single, no direction | PostgreSQL") {
|
||||||
|
QueryUtils.orderBy(List(Field.named("TestField")).asJava, Dialect.POSTGRESQL) shouldEqual
|
||||||
|
" ORDER BY data->>'TestField'"
|
||||||
|
}
|
||||||
|
it("generates single, no direction | SQLite") {
|
||||||
|
QueryUtils.orderBy(List(Field.named("TestField")).asJava, Dialect.SQLITE) shouldEqual
|
||||||
|
" ORDER BY data->>'TestField'"
|
||||||
|
}
|
||||||
|
it("generates multiple with direction | PostgreSQL") {
|
||||||
|
QueryUtils.orderBy(
|
||||||
|
List(Field.named("Nested.Test.Field DESC"), Field.named("AnotherField"), Field.named("It DESC")).asJava,
|
||||||
|
Dialect.POSTGRESQL) shouldEqual
|
||||||
|
" ORDER BY data#>>'{Nested,Test,Field}' DESC, data->>'AnotherField', data->>'It' DESC"
|
||||||
|
}
|
||||||
|
it("generates multiple with direction | SQLite") {
|
||||||
|
QueryUtils.orderBy(
|
||||||
|
List(Field.named("Nested.Test.Field DESC"), Field.named("AnotherField"), Field.named("It DESC")).asJava,
|
||||||
|
Dialect.SQLITE) shouldEqual
|
||||||
|
" ORDER BY data->'Nested'->'Test'->>'Field' DESC, data->>'AnotherField', data->>'It' DESC"
|
||||||
|
}
|
||||||
|
it("generates numeric ordering | PostgreSQL") {
|
||||||
|
QueryUtils.orderBy(List(Field.named("n:Test")).asJava, Dialect.POSTGRESQL) shouldEqual
|
||||||
|
" ORDER BY (data->>'Test')::numeric"
|
||||||
|
}
|
||||||
|
it("generates numeric ordering | SQLite") {
|
||||||
|
QueryUtils.orderBy(List(Field.named("n:Test")).asJava, Dialect.SQLITE) shouldEqual " ORDER BY data->>'Test'"
|
||||||
|
}
|
||||||
|
it("generates case-insensitive ordering | PostgreSQL") {
|
||||||
|
QueryUtils.orderBy(List(Field.named("i:Test.Field DESC NULLS FIRST")).asJava, Dialect.POSTGRESQL) shouldEqual
|
||||||
|
" ORDER BY LOWER(data#>>'{Test,Field}') DESC NULLS FIRST"
|
||||||
|
}
|
||||||
|
it("generates case-insensitive ordering | SQLite") {
|
||||||
|
QueryUtils.orderBy(List(Field.named("i:Test.Field ASC NULLS LAST")).asJava, Dialect.SQLITE) shouldEqual
|
||||||
|
" ORDER BY data->'Test'->>'Field' COLLATE NOCASE ASC NULLS LAST"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user