Initial Development #1

Merged
danieljsummers merged 88 commits from v1-rc into main 2025-04-16 01:29:20 +00:00
16 changed files with 1370 additions and 44 deletions
Showing only changes of commit 2b86864f82 - Show all commits

View File

@ -0,0 +1,26 @@
package solutions.bitbadger.documents.groovy
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import solutions.bitbadger.documents.DocumentIndex
import static groovy.test.GroovyAssert.*
/**
* Unit tests for the `DocumentIndex` enum
*/
@DisplayName('JVM | Groovy | DocumentIndex')
class DocumentIndexTest {
@Test
@DisplayName('FULL uses proper SQL')
void fullSQL() {
assertEquals('The SQL for Full is incorrect', '', DocumentIndex.FULL.sql)
}
@Test
@DisplayName('OPTIMIZED uses proper SQL')
void optimizedSQL() {
assertEquals('The SQL for Optimized is incorrect', ' jsonb_path_ops', DocumentIndex.OPTIMIZED.sql)
}
}

View File

@ -0,0 +1,600 @@
package solutions.bitbadger.documents.groovy
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.Field
import solutions.bitbadger.documents.FieldFormat
import solutions.bitbadger.documents.Op
import solutions.bitbadger.documents.support.ForceDialect
import static groovy.test.GroovyAssert.*
/**
* Unit tests for the `Field` class
*/
@DisplayName('JVM | Groovy | Field')
class FieldTest {
/**
* Clear the connection string (resets Dialect)
*/
@AfterEach
void cleanUp() {
ForceDialect.none()
}
// ~~~ INSTANCE METHODS ~~~
// TODO: fix java.base open issue
// @Test
// @DisplayName('withParameterName fails for invalid name')
// void withParamNameFails() {
// assertThrows(DocumentException) { Field.equal('it', '').withParameterName('2424') }
// }
@Test
@DisplayName('withParameterName works with colon prefix')
void withParamNameColon() {
def field = Field.equal('abc', '22').withQualifier('me')
def withParam = field.withParameterName(':test')
assertNotSame('A new Field instance should have been created', field, withParam)
assertEquals('Name should have been preserved', field.name, withParam.name)
assertEquals('Comparison should have been preserved', field.comparison, withParam.comparison)
assertEquals('Parameter name not set correctly', ':test', withParam.parameterName)
assertEquals('Qualifier should have been preserved', field.qualifier, withParam.qualifier)
}
@Test
@DisplayName('withParameterName works with at-sign prefix')
void withParamNameAtSign() {
def field = Field.equal('def', '44')
def withParam = field.withParameterName('@unit')
assertNotSame('A new Field instance should have been created', field, withParam)
assertEquals('Name should have been preserved', field.name, withParam.name)
assertEquals('Comparison should have been preserved', field.comparison, withParam.comparison)
assertEquals('Parameter name not set correctly', '@unit', withParam.parameterName)
assertEquals('Qualifier should have been preserved', field.qualifier, withParam.qualifier)
}
@Test
@DisplayName('withQualifier sets qualifier correctly')
void withQualifier() {
def field = Field.equal('j', 'k')
def withQual = field.withQualifier('test')
assertNotSame('A new Field instance should have been created', field, withQual)
assertEquals('Name should have been preserved', field.name, withQual.name)
assertEquals('Comparison should have been preserved', field.comparison, withQual.comparison)
assertEquals('Parameter Name should have been preserved', field.parameterName, withQual.parameterName)
assertEquals('Qualifier not set correctly', 'test', withQual.qualifier)
}
@Test
@DisplayName('path generates for simple unqualified PostgreSQL field')
void pathPostgresSimpleUnqualified() {
assertEquals('Path not correct', "data->>'SomethingCool'",
Field.greaterOrEqual('SomethingCool', 18).path(Dialect.POSTGRESQL, FieldFormat.SQL))
}
@Test
@DisplayName('path generates for simple qualified PostgreSQL field')
void pathPostgresSimpleQualified() {
assertEquals('Path not correct', "this.data->>'SomethingElse'",
Field.less('SomethingElse', 9).withQualifier('this').path(Dialect.POSTGRESQL, FieldFormat.SQL))
}
@Test
@DisplayName('path generates for nested unqualified PostgreSQL field')
void pathPostgresNestedUnqualified() {
assertEquals('Path not correct', "data#>>'{My,Nested,Field}'",
Field.equal('My.Nested.Field', 'howdy').path(Dialect.POSTGRESQL, FieldFormat.SQL))
}
@Test
@DisplayName('path generates for nested qualified PostgreSQL field')
void pathPostgresNestedQualified() {
assertEquals('Path not correct', "bird.data#>>'{Nest,Away}'",
Field.equal('Nest.Away', 'doc').withQualifier('bird').path(Dialect.POSTGRESQL, FieldFormat.SQL))
}
@Test
@DisplayName('path generates for simple unqualified SQLite field')
void pathSQLiteSimpleUnqualified() {
assertEquals('Path not correct', "data->>'SomethingCool'",
Field.greaterOrEqual('SomethingCool', 18).path(Dialect.SQLITE, FieldFormat.SQL))
}
@Test
@DisplayName('path generates for simple qualified SQLite field')
void pathSQLiteSimpleQualified() {
assertEquals('Path not correct', "this.data->>'SomethingElse'",
Field.less('SomethingElse', 9).withQualifier('this').path(Dialect.SQLITE, FieldFormat.SQL))
}
@Test
@DisplayName('path generates for nested unqualified SQLite field')
void pathSQLiteNestedUnqualified() {
assertEquals('Path not correct', "data->'My'->'Nested'->>'Field'",
Field.equal('My.Nested.Field', 'howdy').path(Dialect.SQLITE, FieldFormat.SQL))
}
@Test
@DisplayName('path generates for nested qualified SQLite field')
void pathSQLiteNestedQualified() {
assertEquals('Path not correct', "bird.data->'Nest'->>'Away'",
Field.equal('Nest.Away', 'doc').withQualifier('bird').path(Dialect.SQLITE, FieldFormat.SQL))
}
@Test
@DisplayName('toWhere generates for exists w/o qualifier | PostgreSQL')
void toWhereExistsNoQualPostgres() {
ForceDialect.postgres()
assertEquals('Field WHERE clause not generated correctly', "data->>'that_field' IS NOT NULL",
Field.exists('that_field').toWhere())
}
@Test
@DisplayName('toWhere generates for exists w/o qualifier | SQLite')
void toWhereExistsNoQualSQLite() {
ForceDialect.sqlite()
assertEquals('Field WHERE clause not generated correctly', "data->>'that_field' IS NOT NULL",
Field.exists('that_field').toWhere())
}
@Test
@DisplayName('toWhere generates for not-exists w/o qualifier | PostgreSQL')
void toWhereNotExistsNoQualPostgres() {
ForceDialect.postgres()
assertEquals('Field WHERE clause not generated correctly', "data->>'a_field' IS NULL",
Field.notExists('a_field').toWhere())
}
@Test
@DisplayName('toWhere generates for not-exists w/o qualifier | SQLite')
void toWhereNotExistsNoQualSQLite() {
ForceDialect.sqlite()
assertEquals('Field WHERE clause not generated correctly', "data->>'a_field' IS NULL",
Field.notExists('a_field').toWhere())
}
@Test
@DisplayName('toWhere generates for BETWEEN w/o qualifier, numeric range | PostgreSQL')
void toWhereBetweenNoQualNumericPostgres() {
ForceDialect.postgres()
assertEquals('Field WHERE clause not generated correctly',
"(data->>'age')::numeric BETWEEN @agemin AND @agemax", Field.between('age', 13, 17, '@age').toWhere())
}
@Test
@DisplayName('toWhere generates for BETWEEN w/o qualifier, alphanumeric range | PostgreSQL')
void toWhereBetweenNoQualAlphaPostgres() {
ForceDialect.postgres()
assertEquals('Field WHERE clause not generated correctly', "data->>'city' BETWEEN :citymin AND :citymax",
Field.between('city', 'Atlanta', 'Chicago', ':city').toWhere())
}
@Test
@DisplayName('toWhere generates for BETWEEN w/o qualifier | SQLite')
void toWhereBetweenNoQualSQLite() {
ForceDialect.sqlite()
assertEquals('Field WHERE clause not generated correctly', "data->>'age' BETWEEN @agemin AND @agemax",
Field.between('age', 13, 17, '@age').toWhere())
}
@Test
@DisplayName('toWhere generates for BETWEEN w/ qualifier, numeric range | PostgreSQL')
void toWhereBetweenQualNumericPostgres() {
ForceDialect.postgres()
assertEquals('Field WHERE clause not generated correctly',
"(test.data->>'age')::numeric BETWEEN @agemin AND @agemax",
Field.between('age', 13, 17, '@age').withQualifier('test').toWhere())
}
@Test
@DisplayName('toWhere generates for BETWEEN w/ qualifier, alphanumeric range | PostgreSQL')
void toWhereBetweenQualAlphaPostgres() {
ForceDialect.postgres()
assertEquals('Field WHERE clause not generated correctly', "unit.data->>'city' BETWEEN :citymin AND :citymax",
Field.between('city', 'Atlanta', 'Chicago', ':city').withQualifier('unit').toWhere())
}
@Test
@DisplayName('toWhere generates for BETWEEN w/ qualifier | SQLite')
void toWhereBetweenQualSQLite() {
ForceDialect.sqlite()
assertEquals('Field WHERE clause not generated correctly', "my.data->>'age' BETWEEN @agemin AND @agemax",
Field.between('age', 13, 17, '@age').withQualifier('my').toWhere())
}
@Test
@DisplayName('toWhere generates for IN/any, numeric values | PostgreSQL')
void toWhereAnyNumericPostgres() {
ForceDialect.postgres()
assertEquals('Field WHERE clause not generated correctly',
"(data->>'even')::numeric IN (:nbr_0, :nbr_1, :nbr_2)",
Field.any('even', List.of(2, 4, 6), ':nbr').toWhere())
}
@Test
@DisplayName('toWhere generates for IN/any, alphanumeric values | PostgreSQL')
void toWhereAnyAlphaPostgres() {
ForceDialect.postgres()
assertEquals('Field WHERE clause not generated correctly', "data->>'test' IN (:city_0, :city_1)",
Field.any('test', List.of('Atlanta', 'Chicago'), ':city').toWhere())
}
@Test
@DisplayName('toWhere generates for IN/any | SQLite')
void toWhereAnySQLite() {
ForceDialect.sqlite()
assertEquals('Field WHERE clause not generated correctly', "data->>'test' IN (:city_0, :city_1)",
Field.any('test', List.of('Atlanta', 'Chicago'), ':city').toWhere())
}
@Test
@DisplayName('toWhere generates for inArray | PostgreSQL')
void toWhereInArrayPostgres() {
ForceDialect.postgres()
assertEquals('Field WHERE clause not generated correctly', "data->'even' ??| ARRAY[:it_0, :it_1, :it_2, :it_3]",
Field.inArray('even', 'tbl', List.of(2, 4, 6, 8), ':it').toWhere())
}
@Test
@DisplayName('toWhere generates for inArray | SQLite')
void toWhereInArraySQLite() {
ForceDialect.sqlite()
assertEquals('Field WHERE clause not generated correctly',
"EXISTS (SELECT 1 FROM json_each(tbl.data, '\$.test') WHERE value IN (:city_0, :city_1))",
Field.inArray('test', 'tbl', List.of('Atlanta', 'Chicago'), ':city').toWhere())
}
@Test
@DisplayName('toWhere generates for others w/o qualifier | PostgreSQL')
void toWhereOtherNoQualPostgres() {
ForceDialect.postgres()
assertEquals('Field WHERE clause not generated correctly', "data->>'some_field' = :value",
Field.equal('some_field', '', ':value').toWhere())
}
@Test
@DisplayName('toWhere generates for others w/o qualifier | SQLite')
void toWhereOtherNoQualSQLite() {
ForceDialect.sqlite()
assertEquals('Field WHERE clause not generated correctly', "data->>'some_field' = :value",
Field.equal('some_field', '', ':value').toWhere())
}
@Test
@DisplayName('toWhere generates no-parameter w/ qualifier | PostgreSQL')
void toWhereNoParamWithQualPostgres() {
ForceDialect.postgres()
assertEquals('Field WHERE clause not generated correctly', "test.data->>'no_field' IS NOT NULL",
Field.exists('no_field').withQualifier('test').toWhere())
}
@Test
@DisplayName('toWhere generates no-parameter w/ qualifier | SQLite')
void toWhereNoParamWithQualSQLite() {
ForceDialect.sqlite()
assertEquals('Field WHERE clause not generated correctly', "test.data->>'no_field' IS NOT NULL",
Field.exists('no_field').withQualifier('test').toWhere())
}
@Test
@DisplayName('toWhere generates parameter w/ qualifier | PostgreSQL')
void toWhereParamWithQualPostgres() {
ForceDialect.postgres()
assertEquals('Field WHERE clause not generated correctly', "(q.data->>'le_field')::numeric <= :it",
Field.lessOrEqual('le_field', 18, ':it').withQualifier('q').toWhere())
}
@Test
@DisplayName('toWhere generates parameter w/ qualifier | SQLite')
void toWhereParamWithQualSQLite() {
ForceDialect.sqlite()
assertEquals('Field WHERE clause not generated correctly', "q.data->>'le_field' <= :it",
Field.lessOrEqual('le_field', 18, ':it').withQualifier('q').toWhere())
}
// ~~~ STATIC CONSTRUCTOR TESTS ~~~
@Test
@DisplayName('equal constructs a field w/o parameter name')
void equalCtor() {
def field = Field.equal('Test', 14)
assertEquals('Field name not filled correctly', 'Test', field.name)
assertEquals('Field comparison operation not filled correctly', Op.EQUAL, field.comparison.op)
assertEquals('Field comparison value not filled correctly', 14, field.comparison.value)
assertNull('The parameter name should have been null', field.parameterName)
assertNull('The qualifier should have been null', field.qualifier)
}
@Test
@DisplayName('equal constructs a field w/ parameter name')
void equalParameterCtor() {
def field = Field.equal('Test', 14, ':w')
assertEquals('Field name not filled correctly', 'Test', field.name)
assertEquals('Field comparison operation not filled correctly', Op.EQUAL, field.comparison.op)
assertEquals('Field comparison value not filled correctly', 14, field.comparison.value)
assertEquals('Field parameter name not filled correctly', ':w', field.parameterName)
assertNull('The qualifier should have been null', field.qualifier)
}
@Test
@DisplayName('greater constructs a field w/o parameter name')
void greaterCtor() {
def field = Field.greater('Great', 'night')
assertEquals('Field name not filled correctly', 'Great', field.name)
assertEquals('Field comparison operation not filled correctly', Op.GREATER, field.comparison.op)
assertEquals('Field comparison value not filled correctly', 'night', field.comparison.value)
assertNull('The parameter name should have been null', field.parameterName)
assertNull('The qualifier should have been null', field.qualifier)
}
@Test
@DisplayName('greater constructs a field w/ parameter name')
void greaterParameterCtor() {
def field = Field.greater('Great', 'night', ':yeah')
assertEquals('Field name not filled correctly', 'Great', field.name)
assertEquals('Field comparison operation not filled correctly', Op.GREATER, field.comparison.op)
assertEquals('Field comparison value not filled correctly', 'night', field.comparison.value)
assertEquals('Field parameter name not filled correctly', ':yeah', field.parameterName)
assertNull('The qualifier should have been null', field.qualifier)
}
@Test
@DisplayName('greaterOrEqual constructs a field w/o parameter name')
void greaterOrEqualCtor() {
def field = Field.greaterOrEqual('Nice', 88L)
assertEquals('Field name not filled correctly', 'Nice', field.name)
assertEquals('Field comparison operation not filled correctly', Op.GREATER_OR_EQUAL, field.comparison.op)
assertEquals('Field comparison value not filled correctly', 88L, field.comparison.value)
assertNull('The parameter name should have been null', field.parameterName)
assertNull('The qualifier should have been null', field.qualifier)
}
@Test
@DisplayName('greaterOrEqual constructs a field w/ parameter name')
void greaterOrEqualParameterCtor() {
def field = Field.greaterOrEqual('Nice', 88L, ':nice')
assertEquals('Field name not filled correctly', 'Nice', field.name)
assertEquals('Field comparison operation not filled correctly', Op.GREATER_OR_EQUAL, field.comparison.op)
assertEquals('Field comparison value not filled correctly', 88L, field.comparison.value)
assertEquals('Field parameter name not filled correctly', ':nice', field.parameterName)
assertNull('The qualifier should have been null', field.qualifier)
}
@Test
@DisplayName('less constructs a field w/o parameter name')
void lessCtor() {
def field = Field.less('Lesser', 'seven')
assertEquals('Field name not filled correctly', 'Lesser', field.name)
assertEquals('Field comparison operation not filled correctly', Op.LESS, field.comparison.op)
assertEquals('Field comparison value not filled correctly', 'seven', field.comparison.value)
assertNull('The parameter name should have been null', field.parameterName)
assertNull('The qualifier should have been null', field.qualifier)
}
@Test
@DisplayName('less constructs a field w/ parameter name')
void lessParameterCtor() {
def field = Field.less('Lesser', 'seven', ':max')
assertEquals('Field name not filled correctly', 'Lesser', field.name)
assertEquals('Field comparison operation not filled correctly', Op.LESS, field.comparison.op)
assertEquals('Field comparison value not filled correctly', 'seven', field.comparison.value)
assertEquals('Field parameter name not filled correctly', ':max', field.parameterName)
assertNull('The qualifier should have been null', field.qualifier)
}
@Test
@DisplayName('lessOrEqual constructs a field w/o parameter name')
void lessOrEqualCtor() {
def field = Field.lessOrEqual('Nobody', 'KNOWS')
assertEquals('Field name not filled correctly', 'Nobody', field.name)
assertEquals('Field comparison operation not filled correctly', Op.LESS_OR_EQUAL, field.comparison.op)
assertEquals('Field comparison value not filled correctly', 'KNOWS', field.comparison.value)
assertNull('The parameter name should have been null', field.parameterName)
assertNull('The qualifier should have been null', field.qualifier)
}
@Test
@DisplayName('lessOrEqual constructs a field w/ parameter name')
void lessOrEqualParameterCtor() {
def field = Field.lessOrEqual('Nobody', 'KNOWS', ':nope')
assertEquals('Field name not filled correctly', 'Nobody', field.name)
assertEquals('Field comparison operation not filled correctly', Op.LESS_OR_EQUAL, field.comparison.op)
assertEquals('Field comparison value not filled correctly', 'KNOWS', field.comparison.value)
assertEquals('Field parameter name not filled correctly', ':nope', field.parameterName)
assertNull('The qualifier should have been null', field.qualifier)
}
@Test
@DisplayName('notEqual constructs a field w/o parameter name')
void notEqualCtor() {
def field = Field.notEqual('Park', 'here')
assertEquals('Field name not filled correctly', 'Park', field.name)
assertEquals('Field comparison operation not filled correctly', Op.NOT_EQUAL, field.comparison.op)
assertEquals('Field comparison value not filled correctly', 'here', field.comparison.value)
assertNull('The parameter name should have been null', field.parameterName)
assertNull('The qualifier should have been null', field.qualifier)
}
@Test
@DisplayName('notEqual constructs a field w/ parameter name')
void notEqualParameterCtor() {
def field = Field.notEqual('Park', 'here', ':now')
assertEquals('Field name not filled correctly', 'Park', field.name)
assertEquals('Field comparison operation not filled correctly', Op.NOT_EQUAL, field.comparison.op)
assertEquals('Field comparison value not filled correctly', 'here', field.comparison.value)
assertEquals('Field parameter name not filled correctly', ':now', field.parameterName)
assertNull('The qualifier should have been null', field.qualifier)
}
@Test
@DisplayName('between constructs a field w/o parameter name')
void betweenCtor() {
def field = Field.between('Age', 18, 49)
assertEquals('Field name not filled correctly', 'Age', field.name)
assertEquals('Field comparison operation not filled correctly', Op.BETWEEN, field.comparison.op)
assertEquals('Field comparison min value not filled correctly', 18, field.comparison.value.first)
assertEquals('Field comparison max value not filled correctly', 49, field.comparison.value.second, )
assertNull('The parameter name should have been null', field.parameterName)
assertNull('The qualifier should have been null', field.qualifier)
}
@Test
@DisplayName('between constructs a field w/ parameter name')
void betweenParameterCtor() {
def field = Field.between('Age', 18, 49, ':limit')
assertEquals('Field name not filled correctly', 'Age', field.name)
assertEquals('Field comparison operation not filled correctly', Op.BETWEEN, field.comparison.op)
assertEquals('Field comparison min value not filled correctly', 18, field.comparison.value.first)
assertEquals('Field comparison max value not filled correctly', 49, field.comparison.value.second)
assertEquals('Field parameter name not filled correctly', ':limit', field.parameterName)
assertNull('The qualifier should have been null', field.qualifier)
}
@Test
@DisplayName('any constructs a field w/o parameter name')
void anyCtor() {
def field = Field.any('Here', List.of(8, 16, 32))
assertEquals('Field name not filled correctly', 'Here', field.name)
assertEquals('Field comparison operation not filled correctly', Op.IN, field.comparison.op)
assertEquals('Field comparison value not filled correctly', List.of(8, 16, 32), field.comparison.value)
assertNull('The parameter name should have been null', field.parameterName)
assertNull('The qualifier should have been null', field.qualifier)
}
@Test
@DisplayName('any constructs a field w/ parameter name')
void anyParameterCtor() {
def field = Field.any('Here', List.of(8, 16, 32), ':list')
assertEquals('Field name not filled correctly', 'Here', field.name)
assertEquals('Field comparison operation not filled correctly', Op.IN, field.comparison.op)
assertEquals('Field comparison value not filled correctly', List.of(8, 16, 32), field.comparison.value)
assertEquals('Field parameter name not filled correctly', ':list', field.parameterName)
assertNull('The qualifier should have been null', field.qualifier)
}
@Test
@DisplayName('inArray constructs a field w/o parameter name')
void inArrayCtor() {
def field = Field.inArray('ArrayField', 'table', List.of('z'))
assertEquals('Field name not filled correctly', 'ArrayField', field.name)
assertEquals('Field comparison operation not filled correctly', Op.IN_ARRAY, field.comparison.op)
assertEquals('Field comparison table not filled correctly', 'table', field.comparison.value.first)
assertEquals('Field comparison values not filled correctly', List.of('z'), field.comparison.value.second)
assertNull('The parameter name should have been null', field.parameterName)
assertNull('The qualifier should have been null', field.qualifier)
}
@Test
@DisplayName('inArray constructs a field w/ parameter name')
void inArrayParameterCtor() {
def field = Field.inArray('ArrayField', 'table', List.of('z'), ':a')
assertEquals('Field name not filled correctly', 'ArrayField', field.name)
assertEquals('Field comparison operation not filled correctly', Op.IN_ARRAY, field.comparison.op)
assertEquals('Field comparison table not filled correctly', 'table', field.comparison.value.first)
assertEquals('Field comparison values not filled correctly', List.of('z'), field.comparison.value.second)
assertEquals('Field parameter name not filled correctly', ':a', field.parameterName)
assertNull('The qualifier should have been null', field.qualifier)
}
@Test
@DisplayName('exists constructs a field')
void existsCtor() {
def field = Field.exists 'Groovy'
assertEquals('Field name not filled correctly', 'Groovy', field.name)
assertEquals('Field comparison operation not filled correctly', Op.EXISTS, field.comparison.op)
assertEquals('Field comparison value not filled correctly', '', field.comparison.value)
assertNull('The parameter name should have been null', field.parameterName)
assertNull('The qualifier should have been null', field.qualifier)
}
@Test
@DisplayName('notExists constructs a field')
void notExistsCtor() {
def field = Field.notExists 'Groovy'
assertEquals('Field name not filled correctly', 'Groovy', field.name)
assertEquals('Field comparison operation not filled correctly', Op.NOT_EXISTS, field.comparison.op)
assertEquals('Field comparison value not filled correctly', '', field.comparison.value)
assertNull('The parameter name should have been null', field.parameterName)
assertNull('The qualifier should have been null', field.qualifier)
}
@Test
@DisplayName('named constructs a field')
void namedCtor() {
def field = Field.named('Tacos')
assertEquals('Field name not filled correctly', 'Tacos', field.name)
assertEquals('Field comparison operation not filled correctly', Op.EQUAL, field.comparison.op)
assertEquals('Field comparison value not filled correctly', '', field.comparison.value)
assertNull('The parameter name should have been null', field.parameterName)
assertNull('The qualifier should have been null', field.qualifier)
}
// TODO: fix java.base open issue
// @Test
// @DisplayName('static constructors fail for invalid parameter name')
// void staticCtorsFailOnParamName() {
// assertThrows(DocumentException) { Field.equal('a', 'b', "that ain't it, Jack...") }
// }
@Test
@DisplayName('nameToPath creates a simple PostgreSQL SQL name')
void nameToPathPostgresSimpleSQL() {
assertEquals('Path not constructed correctly', "data->>'Simple'",
Field.nameToPath('Simple', Dialect.POSTGRESQL, FieldFormat.SQL))
}
@Test
@DisplayName('nameToPath creates a simple SQLite SQL name')
void nameToPathSQLiteSimpleSQL() {
assertEquals('Path not constructed correctly', "data->>'Simple'",
Field.nameToPath('Simple', Dialect.SQLITE, FieldFormat.SQL))
}
@Test
@DisplayName('nameToPath creates a nested PostgreSQL SQL name')
void nameToPathPostgresNestedSQL() {
assertEquals('Path not constructed correctly', "data#>>'{A,Long,Path,to,the,Property}'",
Field.nameToPath('A.Long.Path.to.the.Property', Dialect.POSTGRESQL, FieldFormat.SQL))
}
@Test
@DisplayName('nameToPath creates a nested SQLite SQL name')
void nameToPathSQLiteNestedSQL() {
assertEquals('Path not constructed correctly', "data->'A'->'Long'->'Path'->'to'->'the'->>'Property'",
Field.nameToPath('A.Long.Path.to.the.Property', Dialect.SQLITE, FieldFormat.SQL))
}
@Test
@DisplayName('nameToPath creates a simple PostgreSQL JSON name')
void nameToPathPostgresSimpleJSON() {
assertEquals('Path not constructed correctly', "data->'Simple'",
Field.nameToPath('Simple', Dialect.POSTGRESQL, FieldFormat.JSON))
}
@Test
@DisplayName('nameToPath creates a simple SQLite JSON name')
void nameToPathSQLiteSimpleJSON() {
assertEquals('Path not constructed correctly', "data->'Simple'",
Field.nameToPath('Simple', Dialect.SQLITE, FieldFormat.JSON))
}
@Test
@DisplayName('nameToPath creates a nested PostgreSQL JSON name')
void nameToPathPostgresNestedJSON() {
assertEquals('Path not constructed correctly', "data#>'{A,Long,Path,to,the,Property}'",
Field.nameToPath('A.Long.Path.to.the.Property', Dialect.POSTGRESQL, FieldFormat.JSON))
}
@Test
@DisplayName('nameToPath creates a nested SQLite JSON name')
void nameToPathSQLiteNestedJSON() {
assertEquals('Path not constructed correctly', "data->'A'->'Long'->'Path'->'to'->'the'->'Property'",
Field.nameToPath('A.Long.Path.to.the.Property', Dialect.SQLITE, FieldFormat.JSON))
}
}

View File

@ -0,0 +1,80 @@
package solutions.bitbadger.documents.groovy
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import solutions.bitbadger.documents.Op
import static groovy.test.GroovyAssert.*
/**
* Unit tests for the `Op` enum
*/
@DisplayName('JVM | Groovy | Op')
class OpTest {
@Test
@DisplayName('EQUAL uses proper SQL')
void equalSQL() {
assertEquals('The SQL for equal is incorrect', '=', Op.EQUAL.sql)
}
@Test
@DisplayName('GREATER uses proper SQL')
void greaterSQL() {
assertEquals('The SQL for greater is incorrect', '>', Op.GREATER.sql)
}
@Test
@DisplayName('GREATER_OR_EQUAL uses proper SQL')
void greaterOrEqualSQL() {
assertEquals('The SQL for greater-or-equal is incorrect', '>=', Op.GREATER_OR_EQUAL.sql)
}
@Test
@DisplayName('LESS uses proper SQL')
void lessSQL() {
assertEquals('The SQL for less is incorrect', '<', Op.LESS.sql)
}
@Test
@DisplayName('LESS_OR_EQUAL uses proper SQL')
void lessOrEqualSQL() {
assertEquals('The SQL for less-or-equal is incorrect', '<=', Op.LESS_OR_EQUAL.sql)
}
@Test
@DisplayName('NOT_EQUAL uses proper SQL')
void notEqualSQL() {
assertEquals('The SQL for not-equal is incorrect', '<>', Op.NOT_EQUAL.sql)
}
@Test
@DisplayName('BETWEEN uses proper SQL')
void betweenSQL() {
assertEquals('The SQL for between is incorrect', 'BETWEEN', Op.BETWEEN.sql)
}
@Test
@DisplayName('IN uses proper SQL')
void inSQL() {
assertEquals('The SQL for in is incorrect', 'IN', Op.IN.sql)
}
@Test
@DisplayName('IN_ARRAY uses proper SQL')
void inArraySQL() {
assertEquals('The SQL for in-array is incorrect', '??|', Op.IN_ARRAY.sql)
}
@Test
@DisplayName('EXISTS uses proper SQL')
void existsSQL() {
assertEquals('The SQL for exists is incorrect', 'IS NOT NULL', Op.EXISTS.sql)
}
@Test
@DisplayName('NOT_EXISTS uses proper SQL')
void notExistsSQL() {
assertEquals('The SQL for not-exists is incorrect', 'IS NULL', Op.NOT_EXISTS.sql)
}
}

View File

@ -0,0 +1,30 @@
package solutions.bitbadger.documents.groovy
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import solutions.bitbadger.documents.ParameterName
import static groovy.test.GroovyAssert.assertEquals
/**
* Unit tests for the `ParameterName` class
*/
@DisplayName('JVM | Groovy | ParameterName')
class ParameterNameTest {
@Test
@DisplayName('derive works when given existing names')
void withExisting() {
def names = new ParameterName()
assertEquals('Name should have been :taco', ':taco', names.derive(':taco'))
assertEquals('Counter should not have advanced for named field', ':field0', names.derive(null))
}
@Test
@DisplayName('derive works when given all anonymous fields')
void allAnonymous() {
def names = new ParameterName()
assertEquals('Anonymous field name should have been returned', ':field0', names.derive(null))
assertEquals('Counter should have advanced from previous call', ':field1', names.derive(null))
assertEquals('Counter should have advanced from previous call', ':field2', names.derive(null))
assertEquals('Counter should have advanced from previous call', ':field3', names.derive(null))
}
}

View File

@ -0,0 +1,41 @@
package solutions.bitbadger.documents.groovy
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
//import solutions.bitbadger.documents.DocumentException
import solutions.bitbadger.documents.Parameter
import solutions.bitbadger.documents.ParameterType
import static groovy.test.GroovyAssert.*
/**
* Unit tests for the `Parameter` class
*/
@DisplayName('JVM | Groovy | Parameter')
class ParameterTest {
@Test
@DisplayName("Construction with colon-prefixed name")
void ctorWithColon() {
def p = new Parameter(":test", ParameterType.STRING, "ABC")
assertEquals("Parameter name was incorrect", ":test", p.name)
assertEquals("Parameter type was incorrect", ParameterType.STRING, p.type)
assertEquals("Parameter value was incorrect", "ABC", p.value)
}
@Test
@DisplayName("Construction with at-sign-prefixed name")
void ctorWithAtSign() {
def p = new Parameter("@yo", ParameterType.NUMBER, null)
assertEquals("Parameter name was incorrect", "@yo", p.name)
assertEquals("Parameter type was incorrect", ParameterType.NUMBER, p.type)
assertNull("Parameter value was incorrect", p.value)
}
// TODO: resolve java.base open issue
// @Test
// @DisplayName("Construction fails with incorrect prefix")
// void ctorFailsForPrefix() {
// assertThrows(DocumentException) { new Parameter("it", ParameterType.JSON, "") }
// }
}

View File

@ -452,7 +452,7 @@ class FieldTest {
assertEquals(Op.BETWEEN, field.comparison.op, "Field comparison operation not filled correctly") assertEquals(Op.BETWEEN, field.comparison.op, "Field comparison operation not filled correctly")
assertEquals(18, field.comparison.value.first, "Field comparison min value not filled correctly") assertEquals(18, field.comparison.value.first, "Field comparison min value not filled correctly")
assertEquals(49, field.comparison.value.second, "Field comparison max value not filled correctly") assertEquals(49, field.comparison.value.second, "Field comparison max value not filled correctly")
assertEquals(":limit", field.parameterName, "The parameter name should have been null") assertEquals(":limit", field.parameterName, "Field parameter name not filled correctly")
assertNull(field.qualifier, "The qualifier should have been null") assertNull(field.qualifier, "The qualifier should have been null")
} }

View File

@ -1,85 +1,86 @@
package solutions.bitbadger.documents.scala package solutions.bitbadger.documents.scala
import org.scalatest.funspec.AnyFunSpec import org.scalatest.funspec.AnyFunSpec
import org.scalatest.matchers.should.Matchers
import solutions.bitbadger.documents.scala.support.{ByteIdClass, IntIdClass, LongIdClass, ShortIdClass, StringIdClass} import solutions.bitbadger.documents.scala.support.{ByteIdClass, IntIdClass, LongIdClass, ShortIdClass, StringIdClass}
import solutions.bitbadger.documents.{AutoId, DocumentException} import solutions.bitbadger.documents.{AutoId, DocumentException}
class AutoIdSpec extends AnyFunSpec { class AutoIdSpec extends AnyFunSpec with Matchers {
describe("generateUUID") { describe("generateUUID") {
it("generates a UUID string") { it("generates a UUID string") {
assert(32 == AutoId.generateUUID().length) AutoId.generateUUID().length shouldEqual 32
} }
} }
describe("generateRandomString") { describe("generateRandomString") {
it("generates a random hex character string of an even length") { it("generates a random hex character string of an even length") {
assert(8 == AutoId.generateRandomString(8).length) AutoId.generateRandomString(8).length shouldEqual 8
} }
it("generates a random hex character string of an odd length") { it("generates a random hex character string of an odd length") {
assert(11 == AutoId.generateRandomString(11).length) AutoId.generateRandomString(11).length shouldEqual 11
} }
it("generates different random hex character strings") { it("generates different random hex character strings") {
val result1 = AutoId.generateRandomString(16) val result1 = AutoId.generateRandomString(16)
val result2 = AutoId.generateRandomString(16) val result2 = AutoId.generateRandomString(16)
assert(result1 != result2) result1 should not be theSameInstanceAs (result2)
} }
} }
describe("needsAutoId") { describe("needsAutoId") {
it("fails for null document") { it("fails for null document") {
assertThrows[DocumentException] { AutoId.needsAutoId(AutoId.DISABLED, null, "id") } an [DocumentException] should be thrownBy AutoId.needsAutoId(AutoId.DISABLED, null, "id")
} }
it("fails for missing ID property") { it("fails for missing ID property") {
assertThrows[DocumentException] { AutoId.needsAutoId(AutoId.UUID, IntIdClass(0), "Id") } an [DocumentException] should be thrownBy AutoId.needsAutoId(AutoId.UUID, IntIdClass(0), "Id")
} }
it("returns false if disabled") { it("returns false if disabled") {
assert(!AutoId.needsAutoId(AutoId.DISABLED, "", "")) AutoId.needsAutoId(AutoId.DISABLED, "", "") shouldBe false
} }
it("returns true for Number strategy and byte ID of 0") { it("returns true for Number strategy and byte ID of 0") {
assert(AutoId.needsAutoId(AutoId.NUMBER, ByteIdClass(0), "id")) AutoId.needsAutoId(AutoId.NUMBER, ByteIdClass(0), "id") shouldBe true
} }
it("returns false for Number strategy and byte ID of non-0") { it("returns false for Number strategy and byte ID of non-0") {
assert(!AutoId.needsAutoId(AutoId.NUMBER, ByteIdClass(77), "id")) AutoId.needsAutoId(AutoId.NUMBER, ByteIdClass(77), "id") shouldBe false
} }
it("returns true for Number strategy and short ID of 0") { it("returns true for Number strategy and short ID of 0") {
assert(AutoId.needsAutoId(AutoId.NUMBER, ShortIdClass(0), "id")) AutoId.needsAutoId(AutoId.NUMBER, ShortIdClass(0), "id") shouldBe true
} }
it("returns false for Number strategy and short ID of non-0") { it("returns false for Number strategy and short ID of non-0") {
assert(!AutoId.needsAutoId(AutoId.NUMBER, ShortIdClass(31), "id")) AutoId.needsAutoId(AutoId.NUMBER, ShortIdClass(31), "id") shouldBe false
} }
it("returns true for Number strategy and int ID of 0") { it("returns true for Number strategy and int ID of 0") {
assert(AutoId.needsAutoId(AutoId.NUMBER, IntIdClass(0), "id")) AutoId.needsAutoId(AutoId.NUMBER, IntIdClass(0), "id") shouldBe true
} }
it("returns false for Number strategy and int ID of non-0") { it("returns false for Number strategy and int ID of non-0") {
assert(!AutoId.needsAutoId(AutoId.NUMBER, IntIdClass(6), "id")) AutoId.needsAutoId(AutoId.NUMBER, IntIdClass(6), "id") shouldBe false
} }
it("returns true for Number strategy and long ID of 0") { it("returns true for Number strategy and long ID of 0") {
assert(AutoId.needsAutoId(AutoId.NUMBER, LongIdClass(0), "id")) AutoId.needsAutoId(AutoId.NUMBER, LongIdClass(0), "id") shouldBe true
} }
it("returns false for Number strategy and long ID of non-0") { it("returns false for Number strategy and long ID of non-0") {
assert(!AutoId.needsAutoId(AutoId.NUMBER, LongIdClass(2), "id")) 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") {
assertThrows[DocumentException] { AutoId.needsAutoId(AutoId.NUMBER, StringIdClass(""), "id") } an [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") {
assert(AutoId.needsAutoId(AutoId.UUID, StringIdClass(""), "id")) AutoId.needsAutoId(AutoId.UUID, StringIdClass(""), "id") shouldBe true
} }
it("returns false for UUID strategy and non-blank ID") { it("returns false for UUID strategy and non-blank ID") {
assert(!AutoId.needsAutoId(AutoId.UUID, StringIdClass("howdy"), "id")) 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") {
assertThrows[DocumentException] { AutoId.needsAutoId(AutoId.UUID, IntIdClass(5), "id") } an [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") {
assert(AutoId.needsAutoId(AutoId.RANDOM_STRING, StringIdClass(""), "id")) AutoId.needsAutoId(AutoId.RANDOM_STRING, StringIdClass(""), "id") shouldBe true
} }
it("returns false for Random String strategy and non-blank ID") { it("returns false for Random String strategy and non-blank ID") {
assert(!AutoId.needsAutoId(AutoId.RANDOM_STRING, StringIdClass("full"), "id")) 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") {
assertThrows[DocumentException] { AutoId.needsAutoId(AutoId.RANDOM_STRING, ShortIdClass(55), "id") } an [DocumentException] should be thrownBy AutoId.needsAutoId(AutoId.RANDOM_STRING, ShortIdClass(55), "id")
} }
} }
} }

View File

@ -0,0 +1,11 @@
package solutions.bitbadger.documents.scala
import org.scalatest.{BeforeAndAfterEach, Suite}
import solutions.bitbadger.documents.support.ForceDialect
trait ClearConfiguration extends BeforeAndAfterEach { this: Suite =>
override def afterEach(): Unit =
try super.afterEach ()
finally ForceDialect.none()
}

View File

@ -1,34 +1,35 @@
package solutions.bitbadger.documents.scala package solutions.bitbadger.documents.scala
import org.scalatest.funspec.AnyFunSpec import org.scalatest.funspec.AnyFunSpec
import org.scalatest.matchers.should.Matchers
import solutions.bitbadger.documents.{AutoId, Configuration, Dialect, DocumentException} import solutions.bitbadger.documents.{AutoId, Configuration, Dialect, DocumentException}
class ConfigurationSpec extends AnyFunSpec { class ConfigurationSpec extends AnyFunSpec with Matchers {
describe("idField") { describe("idField") {
it("defaults to `id`") { it("defaults to `id`") {
assert("id" == Configuration.idField) Configuration.idField shouldEqual "id"
} }
} }
describe("autoIdStrategy") { describe("autoIdStrategy") {
it("defaults to `DISABLED`") { it("defaults to `DISABLED`") {
assert(AutoId.DISABLED == Configuration.autoIdStrategy) Configuration.autoIdStrategy shouldEqual AutoId.DISABLED
} }
} }
describe("idStringLength") { describe("idStringLength") {
it("defaults to 16") { it("defaults to 16") {
assert(16 == Configuration.idStringLength) Configuration.idStringLength shouldEqual 16
} }
} }
describe("dialect") { describe("dialect") {
it("is derived from connection string") { it("is derived from connection string") {
try { try {
assertThrows[DocumentException] { Configuration.dialect() } an [DocumentException] should be thrownBy Configuration.dialect()
Configuration.setConnectionString("jdbc:postgresql:db") Configuration.setConnectionString("jdbc:postgresql:db")
assert(Dialect.POSTGRESQL == Configuration.dialect()) Configuration.dialect() shouldEqual Dialect.POSTGRESQL
} finally { } finally {
Configuration.setConnectionString(null) Configuration.setConnectionString(null)
} }

View File

@ -1,16 +1,17 @@
package solutions.bitbadger.documents.scala package solutions.bitbadger.documents.scala
import org.scalatest.funspec.AnyFunSpec import org.scalatest.funspec.AnyFunSpec
import org.scalatest.matchers.should.Matchers
import solutions.bitbadger.documents.{Dialect, DocumentException} import solutions.bitbadger.documents.{Dialect, DocumentException}
class DialectSpec extends AnyFunSpec { class DialectSpec extends AnyFunSpec with Matchers {
describe("deriveFromConnectionString") { describe("deriveFromConnectionString") {
it("derives PostgreSQL correctly") { it("derives PostgreSQL correctly") {
assert(Dialect.POSTGRESQL == Dialect.deriveFromConnectionString("jdbc:postgresql:db")) Dialect.deriveFromConnectionString("jdbc:postgresql:db") shouldEqual Dialect.POSTGRESQL
} }
it("derives SQLite correctly") { it("derives SQLite correctly") {
assert(Dialect.SQLITE == Dialect.deriveFromConnectionString("jdbc:sqlite:memory")) Dialect.deriveFromConnectionString("jdbc:sqlite:memory") shouldEqual Dialect.SQLITE
} }
it("fails when the connection string is unknown") { it("fails when the connection string is unknown") {
try { try {
@ -18,8 +19,8 @@ class DialectSpec extends AnyFunSpec {
fail("Dialect derivation should have failed") fail("Dialect derivation should have failed")
} catch { } catch {
case ex: DocumentException => case ex: DocumentException =>
assert(ex.getMessage != null) ex.getMessage should not be null
assert(ex.getMessage.contains("[SQL Server]")) ex.getMessage should include ("[SQL Server]")
} }
} }
} }

View File

@ -0,0 +1,17 @@
package solutions.bitbadger.documents.scala
import org.scalatest.funspec.AnyFunSpec
import org.scalatest.matchers.should.Matchers
import solutions.bitbadger.documents.DocumentIndex
class DocumentIndexSpec extends AnyFunSpec with Matchers {
describe("sql") {
it("returns blank for FULL") {
DocumentIndex.FULL.getSql shouldEqual ""
}
it("returns jsonb_path_ops for OPTIMIZED") {
DocumentIndex.OPTIMIZED.getSql shouldEqual " jsonb_path_ops"
}
}
}

View File

@ -1,23 +1,20 @@
package solutions.bitbadger.documents.scala package solutions.bitbadger.documents.scala
import org.scalatest.funspec.AnyFunSpec import org.scalatest.funspec.AnyFunSpec
import org.scalatest.matchers.should.Matchers
import solutions.bitbadger.documents.FieldMatch import solutions.bitbadger.documents.FieldMatch
/** /**
* Unit tests for the `FieldMatch` enum * Unit tests for the `FieldMatch` enum
*/ */
class FieldMatchSpec extends AnyFunSpec { class FieldMatchSpec extends AnyFunSpec with Matchers {
describe("sql") { describe("sql") {
describe("ANY") { it("returns OR for ANY") {
it("should use OR") { FieldMatch.ANY.getSql shouldEqual "OR"
assert("OR" == FieldMatch.ANY.getSql)
}
}
describe("ALL") {
it("should use AND") {
assert("AND" == FieldMatch.ALL.getSql)
} }
it("returns AND for ALL") {
FieldMatch.ALL.getSql shouldEqual "AND"
} }
} }
} }

View File

@ -0,0 +1,428 @@
package solutions.bitbadger.documents.scala
import org.scalatest.funspec.AnyFunSpec
import org.scalatest.matchers.should.Matchers
import solutions.bitbadger.documents.support.ForceDialect
import solutions.bitbadger.documents.{Dialect, DocumentException, Field, FieldFormat, Op}
import scala.jdk.CollectionConverters.*
class FieldSpec extends AnyFunSpec with ClearConfiguration with Matchers {
// ~~~ INSTANCE METHODS ~~~
describe("withParameterName") {
it("fails for invalid name") {
an [DocumentException] should be thrownBy Field.equal("it", "").withParameterName("2424")
}
it("works with colon prefix") {
val field = Field.equal("abc", "22").withQualifier("me")
val withParam = field.withParameterName(":test")
withParam should not be theSameInstanceAs (field)
withParam.getName shouldEqual field.getName
withParam.getComparison shouldEqual field.getComparison
withParam.getParameterName shouldEqual ":test"
withParam.getQualifier shouldEqual field.getQualifier
}
it("works with at-sign prefix") {
val field = Field.equal("def", "44")
val withParam = field.withParameterName("@unit")
withParam should not be theSameInstanceAs (field)
withParam.getName shouldEqual field.getName
withParam.getComparison shouldEqual field.getComparison
withParam.getParameterName shouldEqual "@unit"
withParam.getQualifier shouldEqual field.getQualifier
}
}
describe("withQualifier") {
it("sets qualifier correctly") {
val field = Field.equal("j", "k")
val withQual = field.withQualifier("test")
withQual should not be theSameInstanceAs (field)
withQual.getName shouldEqual field.getName
withQual.getComparison shouldEqual field.getComparison
withQual.getParameterName shouldEqual field.getParameterName
withQual.getQualifier shouldEqual "test"
}
}
describe("path") {
it("generates simple unqualified PostgreSQL field") {
Field.greaterOrEqual("SomethingCool", 18)
.path(Dialect.POSTGRESQL, FieldFormat.SQL) shouldEqual "data->>'SomethingCool'"
}
it("generates simple qualified PostgreSQL field") {
Field.less("SomethingElse", 9).withQualifier("this")
.path(Dialect.POSTGRESQL, FieldFormat.SQL) shouldEqual "this.data->>'SomethingElse'"
}
it("generates nested unqualified PostgreSQL field") {
Field.equal("My.Nested.Field", "howdy")
.path(Dialect.POSTGRESQL, FieldFormat.SQL) shouldEqual "data#>>'{My,Nested,Field}'"
}
it("generates nested qualified PostgreSQL field") {
Field.equal("Nest.Away", "doc").withQualifier("bird")
.path(Dialect.POSTGRESQL, FieldFormat.SQL) shouldEqual "bird.data#>>'{Nest,Away}'"
}
it("generates simple unqualified SQLite field") {
Field.greaterOrEqual("SomethingCool", 18)
.path(Dialect.SQLITE, FieldFormat.SQL) shouldEqual "data->>'SomethingCool'"
}
it("generates simple qualified SQLite field") {
Field.less("SomethingElse", 9).withQualifier("this")
.path(Dialect.SQLITE, FieldFormat.SQL) shouldEqual "this.data->>'SomethingElse'"
}
it("generates nested unqualified SQLite field") {
Field.equal("My.Nested.Field", "howdy")
.path(Dialect.SQLITE, FieldFormat.SQL) shouldEqual "data->'My'->'Nested'->>'Field'"
}
it("generates nested qualified SQLite field") {
Field.equal("Nest.Away", "doc").withQualifier("bird")
.path(Dialect.SQLITE, FieldFormat.SQL) shouldEqual "bird.data->'Nest'->>'Away'"
}
}
describe("toWhere") {
it("generates exists w/o qualifier | PostgreSQL") {
ForceDialect.postgres()
Field.exists("that_field").toWhere shouldEqual "data->>'that_field' IS NOT NULL"
}
it("generates exists w/o qualifier | SQLite") {
ForceDialect.sqlite()
Field.exists("that_field").toWhere shouldEqual "data->>'that_field' IS NOT NULL"
}
it("generates not-exists w/o qualifier | PostgreSQL") {
ForceDialect.postgres()
Field.notExists("a_field").toWhere shouldEqual "data->>'a_field' IS NULL"
}
it("generates not-exists w/o qualifier | SQLite") {
ForceDialect.sqlite()
Field.notExists("a_field").toWhere shouldEqual "data->>'a_field' IS NULL"
}
it("generates BETWEEN w/o qualifier, numeric range | PostgreSQL") {
ForceDialect.postgres()
Field.between("age", 13, 17, "@age").toWhere shouldEqual "(data->>'age')::numeric BETWEEN @agemin AND @agemax"
}
it("generates BETWEEN w/o qualifier, alphanumeric range | PostgreSQL") {
ForceDialect.postgres()
Field.between("city", "Atlanta", "Chicago", ":city")
.toWhere shouldEqual "data->>'city' BETWEEN :citymin AND :citymax"
}
it("generates BETWEEN w/o qualifier | SQLite") {
ForceDialect.sqlite()
Field.between("age", 13, 17, "@age").toWhere shouldEqual "data->>'age' BETWEEN @agemin AND @agemax"
}
it("generates BETWEEN w/ qualifier, numeric range | PostgreSQL") {
ForceDialect.postgres()
Field.between("age", 13, 17, "@age").withQualifier("test")
.toWhere shouldEqual "(test.data->>'age')::numeric BETWEEN @agemin AND @agemax"
}
it("generates BETWEEN w/ qualifier, alphanumeric range | PostgreSQL") {
ForceDialect.postgres()
Field.between("city", "Atlanta", "Chicago", ":city").withQualifier("unit")
.toWhere shouldEqual "unit.data->>'city' BETWEEN :citymin AND :citymax"
}
it("generates BETWEEN w/ qualifier | SQLite") {
ForceDialect.sqlite()
Field.between("age", 13, 17, "@age").withQualifier("my")
.toWhere shouldEqual "my.data->>'age' BETWEEN @agemin AND @agemax"
}
it("generates IN/any, numeric values | PostgreSQL") {
ForceDialect.postgres()
Field.any("even", List(2, 4, 6).asJava, ":nbr")
.toWhere shouldEqual "(data->>'even')::numeric IN (:nbr_0, :nbr_1, :nbr_2)"
}
it("generates IN/any, alphanumeric values | PostgreSQL") {
ForceDialect.postgres()
Field.any("test", List("Atlanta", "Chicago").asJava, ":city")
.toWhere shouldEqual "data->>'test' IN (:city_0, :city_1)"
}
it("generates IN/any | SQLite") {
ForceDialect.sqlite()
Field.any("test", List("Atlanta", "Chicago").asJava, ":city")
.toWhere shouldEqual "data->>'test' IN (:city_0, :city_1)"
}
it("generates inArray | PostgreSQL") {
ForceDialect.postgres()
Field.inArray("even", "tbl", List(2, 4, 6, 8).asJava, ":it")
.toWhere shouldEqual "data->'even' ??| ARRAY[:it_0, :it_1, :it_2, :it_3]"
}
it("generates inArray | SQLite") {
ForceDialect.sqlite()
Field.inArray("test", "tbl", List("Atlanta", "Chicago").asJava, ":city")
.toWhere shouldEqual "EXISTS (SELECT 1 FROM json_each(tbl.data, '$.test') WHERE value IN (:city_0, :city_1))"
}
it("generates others w/o qualifier | PostgreSQL") {
ForceDialect.postgres()
Field.equal("some_field", "", ":value").toWhere shouldEqual "data->>'some_field' = :value"
}
it("generates others w/o qualifier | SQLite") {
ForceDialect.sqlite()
Field.equal("some_field", "", ":value").toWhere shouldEqual "data->>'some_field' = :value"
}
it("generates no-parameter w/ qualifier | PostgreSQL") {
ForceDialect.postgres()
Field.exists("no_field").withQualifier("test").toWhere shouldEqual "test.data->>'no_field' IS NOT NULL"
}
it("generates no-parameter w/ qualifier | SQLite") {
ForceDialect.sqlite()
Field.exists("no_field").withQualifier("test").toWhere shouldEqual "test.data->>'no_field' IS NOT NULL"
}
it("generates parameter w/ qualifier | PostgreSQL") {
ForceDialect.postgres()
Field.lessOrEqual("le_field", 18, ":it").withQualifier("q")
.toWhere shouldEqual "(q.data->>'le_field')::numeric <= :it"
}
it("generates parameter w/ qualifier | SQLite") {
ForceDialect.sqlite()
Field.lessOrEqual("le_field", 18, ":it").withQualifier("q").toWhere shouldEqual "q.data->>'le_field' <= :it"
}
}
// ~~~ STATIC CONSTRUCTOR TESTS ~~~
describe("equal") {
it("constructs a field w/o parameter name") {
val field = Field.equal("Test", 14)
field.getName shouldEqual "Test"
field.getComparison.getOp shouldEqual Op.EQUAL
field.getComparison.getValue shouldEqual 14
field.getParameterName should be (null)
field.getQualifier should be (null)
}
it("constructs a field w/ parameter name") {
val field = Field.equal("Test", 14, ":w")
field.getName shouldEqual "Test"
field.getComparison.getOp shouldEqual Op.EQUAL
field.getComparison.getValue shouldEqual 14
field.getParameterName shouldEqual ":w"
field.getQualifier should be (null)
}
}
describe("greater") {
it("constructs a field w/o parameter name") {
val field = Field.greater("Great", "night")
field.getName shouldEqual "Great"
field.getComparison.getOp shouldEqual Op.GREATER
field.getComparison.getValue shouldEqual "night"
field.getParameterName should be (null)
field.getQualifier should be (null)
}
it("constructs a field w/ parameter name") {
val field = Field.greater("Great", "night", ":yeah")
field.getName shouldEqual "Great"
field.getComparison.getOp shouldEqual Op.GREATER
field.getComparison.getValue shouldEqual "night"
field.getParameterName shouldEqual ":yeah"
field.getQualifier should be (null)
}
}
describe("greaterOrEqual") {
it("constructs a field w/o parameter name") {
val field = Field.greaterOrEqual("Nice", 88L)
field.getName shouldEqual "Nice"
field.getComparison.getOp shouldEqual Op.GREATER_OR_EQUAL
field.getComparison.getValue shouldEqual 88L
field.getParameterName should be (null)
field.getQualifier should be (null)
}
it("constructs a field w/ parameter name") {
val field = Field.greaterOrEqual("Nice", 88L, ":nice")
field.getName shouldEqual "Nice"
field.getComparison.getOp shouldEqual Op.GREATER_OR_EQUAL
field.getComparison.getValue shouldEqual 88L
field.getParameterName shouldEqual ":nice"
field.getQualifier should be (null)
}
}
describe("less") {
it("constructs a field w/o parameter name") {
val field = Field.less("Lesser", "seven")
field.getName shouldEqual "Lesser"
field.getComparison.getOp shouldEqual Op.LESS
field.getComparison.getValue shouldEqual "seven"
field.getParameterName should be (null)
field.getQualifier should be (null)
}
it("constructs a field w/ parameter name") {
val field = Field.less("Lesser", "seven", ":max")
field.getName shouldEqual "Lesser"
field.getComparison.getOp shouldEqual Op.LESS
field.getComparison.getValue shouldEqual "seven"
field.getParameterName shouldEqual ":max"
field.getQualifier should be (null)
}
}
describe("lessOrEqual") {
it("constructs a field w/o parameter name") {
val field = Field.lessOrEqual("Nobody", "KNOWS")
field.getName shouldEqual "Nobody"
field.getComparison.getOp shouldEqual Op.LESS_OR_EQUAL
field.getComparison.getValue shouldEqual "KNOWS"
field.getParameterName should be (null)
field.getQualifier should be (null)
}
it("constructs a field w/ parameter name") {
val field = Field.lessOrEqual("Nobody", "KNOWS", ":nope")
field.getName shouldEqual "Nobody"
field.getComparison.getOp shouldEqual Op.LESS_OR_EQUAL
field.getComparison.getValue shouldEqual "KNOWS"
field.getParameterName shouldEqual ":nope"
field.getQualifier should be (null)
}
}
describe("notEqual") {
it("constructs a field w/o parameter name") {
val field = Field.notEqual("Park", "here")
field.getName shouldEqual "Park"
field.getComparison.getOp shouldEqual Op.NOT_EQUAL
field.getComparison.getValue shouldEqual "here"
field.getParameterName should be (null)
field.getQualifier should be (null)
}
it("constructs a field w/ parameter name") {
val field = Field.notEqual("Park", "here", ":now")
field.getName shouldEqual "Park"
field.getComparison.getOp shouldEqual Op.NOT_EQUAL
field.getComparison.getValue shouldEqual "here"
field.getParameterName shouldEqual ":now"
field.getQualifier should be (null)
}
}
describe("between") {
it("constructs a field w/o parameter name") {
val field = Field.between("Age", 18, 49)
field.getName shouldEqual "Age"
field.getComparison.getOp shouldEqual Op.BETWEEN
field.getComparison.getValue.getFirst shouldEqual 18
field.getComparison.getValue.getSecond shouldEqual 49
field.getParameterName should be (null)
field.getQualifier should be (null)
}
it("constructs a field w/ parameter name") {
val field = Field.between("Age", 18, 49, ":limit")
field.getName shouldEqual "Age"
field.getComparison.getOp shouldEqual Op.BETWEEN
field.getComparison.getValue.getFirst shouldEqual 18
field.getComparison.getValue.getSecond shouldEqual 49
field.getParameterName shouldEqual ":limit"
field.getQualifier should be (null)
}
}
describe("any") {
it("constructs a field w/o parameter name") {
val field = Field.any("Here", List(8, 16, 32).asJava)
field.getName shouldEqual "Here"
field.getComparison.getOp shouldEqual Op.IN
field.getComparison.getValue should be (List(8, 16, 32).asJava)
field.getParameterName should be (null)
field.getQualifier should be (null)
}
it("constructs a field w/ parameter name") {
val field = Field.any("Here", List(8, 16, 32).asJava, ":list")
field.getName shouldEqual "Here"
field.getComparison.getOp shouldEqual Op.IN
field.getComparison.getValue should be (List(8, 16, 32).asJava)
field.getParameterName shouldEqual ":list"
field.getQualifier should be (null)
}
}
describe("inArray") {
it("constructs a field w/o parameter name") {
val field = Field.inArray("ArrayField", "table", List("z").asJava)
field.getName shouldEqual "ArrayField"
field.getComparison.getOp shouldEqual Op.IN_ARRAY
field.getComparison.getValue.getFirst shouldEqual "table"
field.getComparison.getValue.getSecond should be (List("z").asJava)
field.getParameterName should be (null)
field.getQualifier should be (null)
}
it("constructs a field w/ parameter name") {
val field = Field.inArray("ArrayField", "table", List("z").asJava, ":a")
field.getName shouldEqual "ArrayField"
field.getComparison.getOp shouldEqual Op.IN_ARRAY
field.getComparison.getValue.getFirst shouldEqual "table"
field.getComparison.getValue.getSecond should be (List("z").asJava)
field.getParameterName shouldEqual ":a"
field.getQualifier should be (null)
}
}
describe("exists") {
it("constructs a field") {
val field = Field.exists("Groovy")
field.getName shouldEqual "Groovy"
field.getComparison.getOp shouldEqual Op.EXISTS
field.getComparison.getValue shouldEqual ""
field.getParameterName should be (null)
field.getQualifier should be (null)
}
}
describe("notExists") {
it("constructs a field") {
val field = Field.notExists("Groovy")
field.getName shouldEqual "Groovy"
field.getComparison.getOp shouldEqual Op.NOT_EXISTS
field.getComparison.getValue shouldEqual ""
field.getParameterName should be (null)
field.getQualifier should be (null)
}
}
describe("named") {
it("named constructs a field") {
val field = Field.named("Tacos")
field.getName shouldEqual "Tacos"
field.getComparison.getOp shouldEqual Op.EQUAL
field.getComparison.getValue shouldEqual ""
field.getParameterName should be (null)
field.getQualifier should be (null)
}
}
describe("static constructors") {
it("fail for invalid parameter name") {
an [DocumentException] should be thrownBy Field.equal("a", "b", "that ain't it, Jack...")
}
}
describe("nameToPath") {
it("creates a simple PostgreSQL SQL name") {
Field.nameToPath("Simple", Dialect.POSTGRESQL, FieldFormat.SQL) shouldEqual "data->>'Simple'"
}
it("creates a simple SQLite SQL name") {
Field.nameToPath("Simple", Dialect.SQLITE, FieldFormat.SQL) shouldEqual "data->>'Simple'"
}
it("creates a nested PostgreSQL SQL name") {
(Field.nameToPath("A.Long.Path.to.the.Property", Dialect.POSTGRESQL, FieldFormat.SQL)
shouldEqual "data#>>'{A,Long,Path,to,the,Property}'")
}
it("creates a nested SQLite SQL name") {
(Field.nameToPath("A.Long.Path.to.the.Property", Dialect.SQLITE, FieldFormat.SQL)
shouldEqual "data->'A'->'Long'->'Path'->'to'->'the'->>'Property'")
}
it("creates a simple PostgreSQL JSON name") {
Field.nameToPath("Simple", Dialect.POSTGRESQL, FieldFormat.JSON) shouldEqual "data->'Simple'"
}
it("creates a simple SQLite JSON name") {
Field.nameToPath("Simple", Dialect.SQLITE, FieldFormat.JSON) shouldEqual "data->'Simple'"
}
it("creates a nested PostgreSQL JSON name") {
(Field.nameToPath("A.Long.Path.to.the.Property", Dialect.POSTGRESQL, FieldFormat.JSON)
shouldEqual "data#>'{A,Long,Path,to,the,Property}'")
}
it("creates a nested SQLite JSON name") {
(Field.nameToPath("A.Long.Path.to.the.Property", Dialect.SQLITE, FieldFormat.JSON)
shouldEqual "data->'A'->'Long'->'Path'->'to'->'the'->'Property'")
}
}
}

View File

@ -0,0 +1,44 @@
package solutions.bitbadger.documents.scala
import org.scalatest.funspec.AnyFunSpec
import org.scalatest.matchers.should.Matchers
import solutions.bitbadger.documents.Op
class OpSpec extends AnyFunSpec with Matchers {
describe("sql") {
it("returns = for EQUAL") {
Op.EQUAL.getSql shouldEqual "="
}
it("returns > for GREATER") {
Op.GREATER.getSql shouldEqual ">"
}
it("returns >= for GREATER_OR_EQUAL") {
Op.GREATER_OR_EQUAL.getSql shouldEqual ">="
}
it("returns < for LESS") {
Op.LESS.getSql shouldEqual "<"
}
it("returns <= for LESS_OR_EQUAL") {
Op.LESS_OR_EQUAL.getSql shouldEqual "<="
}
it("returns <> for NOT_EQUAL") {
Op.NOT_EQUAL.getSql shouldEqual "<>"
}
it("returns BETWEEN for BETWEEN") {
Op.BETWEEN.getSql shouldEqual "BETWEEN"
}
it("returns IN for IN") {
Op.IN.getSql shouldEqual "IN"
}
it("returns ??| for IN_ARRAY") {
Op.IN_ARRAY.getSql shouldEqual "??|"
}
it("returns IS NOT NULL for EXISTS") {
Op.EXISTS.getSql shouldEqual "IS NOT NULL"
}
it("returns IS NULL for NOT_EXISTS") {
Op.NOT_EXISTS.getSql shouldEqual "IS NULL"
}
}
}

View File

@ -0,0 +1,23 @@
package solutions.bitbadger.documents.scala
import org.scalatest.funspec.AnyFunSpec
import org.scalatest.matchers.should.Matchers
import solutions.bitbadger.documents.ParameterName
class ParameterNameSpec extends AnyFunSpec with Matchers {
describe("derive") {
it("works when given existing names") {
val names = new ParameterName()
names.derive(":taco") shouldEqual ":taco"
names.derive(null) shouldEqual ":field0"
}
it("works when given all anonymous fields") {
val names = new ParameterName()
names.derive(null) shouldEqual ":field0"
names.derive(null) shouldEqual ":field1"
names.derive(null) shouldEqual ":field2"
names.derive(null) shouldEqual ":field3"
}
}
}

View File

@ -0,0 +1,26 @@
package solutions.bitbadger.documents.scala
import org.scalatest.funspec.AnyFunSpec
import org.scalatest.matchers.should.Matchers
import solutions.bitbadger.documents.{DocumentException, Parameter, ParameterType}
class ParameterSpec extends AnyFunSpec with Matchers {
describe("constructor") {
it("succeeds with colon-prefixed name") {
val p = new Parameter(":test", ParameterType.STRING, "ABC")
p.getName shouldEqual ":test"
p.getType shouldEqual ParameterType.STRING
p.getValue shouldEqual "ABC"
}
it("succeeds with at-sign-prefixed name") {
val p = Parameter("@yo", ParameterType.NUMBER, null)
p.getName shouldEqual "@yo"
p.getType shouldEqual ParameterType.NUMBER
p.getValue should be (null)
}
it("fails with incorrect prefix") {
an [DocumentException] should be thrownBy Parameter("it", ParameterType.JSON, "")
}
}
}