test: cover castTextToIntegerRealAndBoolean
This commit is contained in:
1 parent
795ba03620
commit
90139e824b
2 files changed
+182
No files matched your search
@@ -462,4 +462,102 @@ class EvaluatorTest {
|
|||||||
|
|
||||||
private fun cast(operand: Expression, type: ColumnType, precision: Int? = null, scale: Int? = null): Expression =
|
private fun cast(operand: Expression, type: ColumnType, precision: Int? = null, scale: Int? = null): Expression =
|
||||||
CastExpression(operand, type, precision, scale)
|
CastExpression(operand, type, precision, scale)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun castTextToIntegerRealAndBoolean() {
|
||||||
|
assertEquals(Value.IntegerValue(42), evalConstant(cast(textLit("42"), ColumnType.Integer)))
|
||||||
|
assertEquals(Value.RealValue(3.5), evalConstant(cast(textLit("3.5"), ColumnType.Real)))
|
||||||
|
assertEquals(Value.BooleanValue(true), evalConstant(cast(textLit("true"), ColumnType.Boolean)))
|
||||||
|
assertEquals(Value.BooleanValue(false), evalConstant(cast(textLit("FALSE"), ColumnType.Boolean)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun castTextToDecimalAppliesScale() {
|
||||||
|
val result = evalConstant(cast(textLit("19.999"), ColumnType.Decimal, 10, 2)) as Value.DecimalValue
|
||||||
|
assertEquals("20.00", result.value.toPlainString())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun castTextToDateParsesIsoFormat() {
|
||||||
|
val result = evalConstant(cast(textLit("2024-01-15"), ColumnType.Date))
|
||||||
|
assertEquals(Value.DateValue(LocalDate.of(2024, 1, 15)), result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun castNumbersAndDatesToText() {
|
||||||
|
assertEquals(Value.TextValue("42"), evalConstant(cast(intLit(42), ColumnType.Text)))
|
||||||
|
assertEquals(
|
||||||
|
Value.TextValue("2024-01-15"),
|
||||||
|
evalConstant(cast(LiteralExpression(Value.DateValue(LocalDate.of(2024, 1, 15))), ColumnType.Text))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun castOfNullIsAlwaysNull() {
|
||||||
|
assertEquals(Value.NullValue, evalConstant(cast(nullLit, ColumnType.Integer)))
|
||||||
|
assertEquals(Value.NullValue, evalConstant(cast(nullLit, ColumnType.Decimal)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun castRejectsUnparseableText() {
|
||||||
|
assertFailsWith<KestrelException> { evalConstant(cast(textLit("not a number"), ColumnType.Integer)) }
|
||||||
|
assertFailsWith<KestrelException> { evalConstant(cast(textLit("not a date"), ColumnType.Date)) }
|
||||||
|
assertFailsWith<KestrelException> { evalConstant(cast(textLit("nope"), ColumnType.Boolean)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun decimalOverflowBeyondDeclaredPrecisionFails() {
|
||||||
|
assertFailsWith<KestrelException> { evalConstant(cast(textLit("123456"), ColumnType.Decimal, 5, 0)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun decimalArithmeticIsExactUnlikeDoubles() {
|
||||||
|
val a = cast(textLit("0.1"), ColumnType.Decimal, 10, 2)
|
||||||
|
val b = cast(textLit("0.2"), ColumnType.Decimal, 10, 2)
|
||||||
|
val sum = evalConstant(bin(BinaryOperator.Add, a, b)) as Value.DecimalValue
|
||||||
|
assertEquals("0.30", sum.value.toPlainString())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun decimalEqualityIgnoresTrailingZeroScale() {
|
||||||
|
val fifteenTenths = LiteralExpression(Value.DecimalValue(BigDecimal("1.50")))
|
||||||
|
val fifteen = LiteralExpression(Value.DecimalValue(BigDecimal("1.5")))
|
||||||
|
assertEquals(Value.BooleanValue(true), evalConstant(bin(BinaryOperator.Equal, fifteenTenths, fifteen)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun decimalDivisionByZeroFails() {
|
||||||
|
val a = cast(textLit("1"), ColumnType.Decimal, 10, 2)
|
||||||
|
val zero = cast(textLit("0"), ColumnType.Decimal, 10, 2)
|
||||||
|
assertFailsWith<KestrelException> { evalConstant(bin(BinaryOperator.Divide, a, zero)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun decimalComparesCorrectlyAgainstIntegerAndReal() {
|
||||||
|
val decimal = LiteralExpression(Value.DecimalValue(BigDecimal("19.99")))
|
||||||
|
assertEquals(Value.BooleanValue(true), evalConstant(bin(BinaryOperator.Less, decimal, intLit(20))))
|
||||||
|
assertEquals(Value.BooleanValue(true), evalConstant(bin(BinaryOperator.Greater, decimal, realLit(19.98))))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dateLit(value: LocalDate): Expression = LiteralExpression(Value.DateValue(value))
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun addingAndSubtractingIntegerDaysFromADate() {
|
||||||
|
val start = dateLit(LocalDate.of(2024, 1, 1))
|
||||||
|
assertEquals(Value.DateValue(LocalDate.of(2024, 1, 6)), evalConstant(bin(BinaryOperator.Add, start, intLit(5))))
|
||||||
|
assertEquals(Value.DateValue(LocalDate.of(2023, 12, 27)), evalConstant(bin(BinaryOperator.Subtract, start, intLit(5))))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun subtractingTwoDatesYieldsIntegerDayDifference() {
|
||||||
|
val later = dateLit(LocalDate.of(2024, 1, 10))
|
||||||
|
val earlier = dateLit(LocalDate.of(2024, 1, 1))
|
||||||
|
assertEquals(Value.IntegerValue(9), evalConstant(bin(BinaryOperator.Subtract, later, earlier)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun multiplyingADateFails() {
|
||||||
|
val start = dateLit(LocalDate.of(2024, 1, 1))
|
||||||
|
assertFailsWith<KestrelException> { evalConstant(bin(BinaryOperator.Multiply, start, intLit(2))) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -77,6 +77,72 @@ class ExecutorTest {
|
|||||||
assertEquals(listOf(text("grace")), result.rows[0])
|
assertEquals(listOf(text("grace")), result.rows[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun offsetSkipsLeadingRowsWithoutALimit() {
|
||||||
|
seedUsers()
|
||||||
|
val result = select("SELECT name FROM users ORDER BY age ASC OFFSET 1")
|
||||||
|
assertEquals(listOf(listOf(text("linus")), listOf(text("grace"))), result.rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun limitAndOffsetTogetherPageThroughTheOrderedResult() {
|
||||||
|
seedUsers()
|
||||||
|
val result = select("SELECT name FROM users ORDER BY age ASC LIMIT 1 OFFSET 1")
|
||||||
|
assertEquals(listOf(listOf(text("linus"))), result.rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun offsetPastTheEndOfTheResultReturnsNoRows() {
|
||||||
|
seedUsers()
|
||||||
|
val result = select("SELECT name FROM users ORDER BY age ASC OFFSET 100")
|
||||||
|
assertEquals(emptyList<List<Value>>(), result.rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun offsetWrittenBeforeLimitStillAppliesLimitAfterTheSkip() {
|
||||||
|
seedUsers()
|
||||||
|
val result = select("SELECT name FROM users ORDER BY age ASC OFFSET 1 LIMIT 1")
|
||||||
|
assertEquals(listOf(listOf(text("linus"))), result.rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun orderByMultipleColumnsBreaksTiesWithTheSecondKey() {
|
||||||
|
run("CREATE TABLE staff (id INTEGER PRIMARY KEY, dept TEXT NOT NULL, name TEXT NOT NULL)")
|
||||||
|
run(
|
||||||
|
"INSERT INTO staff VALUES " +
|
||||||
|
"(1, 'eng', 'guido'), (2, 'eng', 'ada'), (3, 'ops', 'grace'), (4, 'eng', 'linus')"
|
||||||
|
)
|
||||||
|
val result = select("SELECT dept, name FROM staff ORDER BY dept ASC, name ASC")
|
||||||
|
assertEquals(
|
||||||
|
listOf(
|
||||||
|
listOf(text("eng"), text("ada")),
|
||||||
|
listOf(text("eng"), text("guido")),
|
||||||
|
listOf(text("eng"), text("linus")),
|
||||||
|
listOf(text("ops"), text("grace"))
|
||||||
|
),
|
||||||
|
result.rows
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun orderByMultipleColumnsCanMixAscendingAndDescending() {
|
||||||
|
run("CREATE TABLE staff (id INTEGER PRIMARY KEY, dept TEXT NOT NULL, name TEXT NOT NULL)")
|
||||||
|
run(
|
||||||
|
"INSERT INTO staff VALUES " +
|
||||||
|
"(1, 'eng', 'guido'), (2, 'eng', 'ada'), (3, 'ops', 'grace'), (4, 'eng', 'linus')"
|
||||||
|
)
|
||||||
|
val result = select("SELECT dept, name FROM staff ORDER BY dept ASC, name DESC")
|
||||||
|
assertEquals(
|
||||||
|
listOf(
|
||||||
|
listOf(text("eng"), text("linus")),
|
||||||
|
listOf(text("eng"), text("guido")),
|
||||||
|
listOf(text("eng"), text("ada")),
|
||||||
|
listOf(text("ops"), text("grace"))
|
||||||
|
),
|
||||||
|
result.rows
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun projectionSupportsExpressionsAndAliases() {
|
fun projectionSupportsExpressionsAndAliases() {
|
||||||
seedUsers()
|
seedUsers()
|
||||||
@@ -197,4 +263,22 @@ class ExecutorTest {
|
|||||||
val result = select("SELECT name FROM users WHERE age > 40 AND age < 80")
|
val result = select("SELECT name FROM users WHERE age > 40 AND age < 80")
|
||||||
assertEquals(listOf(listOf(text("linus"))), result.rows)
|
assertEquals(listOf(listOf(text("linus"))), result.rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun temporaryTableSupportsFullCrudJustLikeARegularTable() {
|
||||||
|
run("CREATE TEMP TABLE scratch (id INTEGER PRIMARY KEY, note TEXT NOT NULL)")
|
||||||
|
run("INSERT INTO scratch VALUES (1, 'first'), (2, 'second')")
|
||||||
|
run("UPDATE scratch SET note = 'updated' WHERE id = 1")
|
||||||
|
run("DELETE FROM scratch WHERE id = 2")
|
||||||
|
val result = select("SELECT id, note FROM scratch")
|
||||||
|
assertEquals(listOf(listOf(int(1), text("updated"))), result.rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun temporaryTableEnforcesItsOwnPrimaryKeyAndCanBeIndexed() {
|
||||||
|
run("CREATE TEMP TABLE scratch (id INTEGER PRIMARY KEY, tag TEXT)")
|
||||||
|
run("CREATE INDEX scratchTag ON scratch (tag)")
|
||||||
|
run("INSERT INTO scratch VALUES (1, 'a')")
|
||||||
|
assertFailsWith<KestrelException> { run("INSERT INTO scratch VALUES (1, 'b')") }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user