test: cover castTextToIntegerRealAndBoolean

This commit is contained in:
dualfroz committed 2026-09-08 22:22:41 +02:00
1 parent 795ba03620
commit 90139e824b
2 files changed
+182

No files matched your search

+98
View File
@@ -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))) }
}
} }
+84
View File
@@ -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')") }
}
} }