test: cover stringFunctionsTransformText
This commit is contained in:
1 parent
1614395134
commit
5eedad6404
1 file changed
+183
@@ -1,12 +1,22 @@
|
||||
package kestrel
|
||||
|
||||
import java.math.BigDecimal
|
||||
import java.time.LocalDate
|
||||
import kestrel.catalog.Column
|
||||
import kestrel.catalog.TableSchema
|
||||
import kestrel.engine.Evaluator
|
||||
import kestrel.sql.ast.BetweenExpression
|
||||
import kestrel.sql.ast.BinaryExpression
|
||||
import kestrel.sql.ast.BinaryOperator
|
||||
import kestrel.sql.ast.CallExpression
|
||||
import kestrel.sql.ast.CaseBranch
|
||||
import kestrel.sql.ast.CaseExpression
|
||||
import kestrel.sql.ast.CastExpression
|
||||
import kestrel.sql.ast.ColumnExpression
|
||||
import kestrel.sql.ast.Expression
|
||||
import kestrel.sql.ast.InExpression
|
||||
import kestrel.sql.ast.IsNullExpression
|
||||
import kestrel.sql.ast.LikeExpression
|
||||
import kestrel.sql.ast.LiteralExpression
|
||||
import kestrel.sql.ast.UnaryExpression
|
||||
import kestrel.sql.ast.UnaryOperator
|
||||
@@ -106,4 +116,177 @@ class EvaluatorTest {
|
||||
fun logicalOperatorOnNumberFails() {
|
||||
assertFailsWith<KestrelException> { evalConstant(bin(BinaryOperator.And, intLit(1), trueLit)) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun stringFunctionsTransformText() {
|
||||
assertEquals(Value.TextValue("ADA"), evalConstant(CallExpression("UPPER", listOf(textLit("ada")))))
|
||||
assertEquals(Value.TextValue("ada"), evalConstant(CallExpression("LOWER", listOf(textLit("ADA")))))
|
||||
assertEquals(Value.IntegerValue(3), evalConstant(CallExpression("LENGTH", listOf(textLit("ada")))))
|
||||
assertEquals(Value.TextValue("ada"), evalConstant(CallExpression("TRIM", listOf(textLit(" ada ")))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun substrHandlesTwoAndThreeArgumentForms() {
|
||||
assertEquals(
|
||||
Value.TextValue("ell"),
|
||||
evalConstant(CallExpression("SUBSTR", listOf(textLit("hello"), intLit(2), intLit(3))))
|
||||
)
|
||||
assertEquals(Value.TextValue("llo"), evalConstant(CallExpression("SUBSTR", listOf(textLit("hello"), intLit(3)))))
|
||||
assertEquals(Value.TextValue(""), evalConstant(CallExpression("SUBSTR", listOf(textLit("hello"), intLit(99)))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun replaceAndConcat() {
|
||||
assertEquals(
|
||||
Value.TextValue("hexxo"),
|
||||
evalConstant(CallExpression("REPLACE", listOf(textLit("hello"), textLit("l"), textLit("x"))))
|
||||
)
|
||||
assertEquals(
|
||||
Value.TextValue("ab-cd"),
|
||||
evalConstant(CallExpression("CONCAT", listOf(textLit("ab"), textLit("-"), textLit("cd"))))
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun numericFunctionsComputeCorrectly() {
|
||||
assertEquals(Value.IntegerValue(4), evalConstant(CallExpression("ABS", listOf(intLit(-4)))))
|
||||
assertEquals(Value.RealValue(4.0), evalConstant(CallExpression("ABS", listOf(realLit(-4.0)))))
|
||||
assertEquals(Value.IntegerValue(3), evalConstant(CallExpression("CEIL", listOf(realLit(2.1)))))
|
||||
assertEquals(Value.IntegerValue(2), evalConstant(CallExpression("FLOOR", listOf(realLit(2.9)))))
|
||||
assertEquals(Value.RealValue(2.35), evalConstant(CallExpression("ROUND", listOf(realLit(2.346), intLit(2)))))
|
||||
assertEquals(Value.IntegerValue(5), evalConstant(CallExpression("ROUND", listOf(intLit(5)))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nullHandlingFunctionsSeeRawNulls() {
|
||||
assertEquals(Value.IntegerValue(7), evalConstant(CallExpression("COALESCE", listOf(nullLit, nullLit, intLit(7)))))
|
||||
assertEquals(Value.NullValue, evalConstant(CallExpression("COALESCE", listOf(nullLit, nullLit))))
|
||||
assertEquals(Value.IntegerValue(1), evalConstant(CallExpression("IFNULL", listOf(intLit(1), intLit(2)))))
|
||||
assertEquals(Value.IntegerValue(2), evalConstant(CallExpression("IFNULL", listOf(nullLit, intLit(2)))))
|
||||
assertEquals(Value.NullValue, evalConstant(CallExpression("NULLIF", listOf(intLit(5), intLit(5)))))
|
||||
assertEquals(Value.IntegerValue(5), evalConstant(CallExpression("NULLIF", listOf(intLit(5), intLit(6)))))
|
||||
assertEquals(Value.TextValue("NULL"), evalConstant(CallExpression("TYPEOF", listOf(nullLit))))
|
||||
assertEquals(Value.TextValue("INTEGER"), evalConstant(CallExpression("TYPEOF", listOf(intLit(1)))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun mostFunctionsPropagateNullArguments() {
|
||||
assertEquals(Value.NullValue, evalConstant(CallExpression("UPPER", listOf(nullLit))))
|
||||
assertEquals(Value.NullValue, evalConstant(CallExpression("ABS", listOf(nullLit))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun functionArityIsEnforced() {
|
||||
assertFailsWith<KestrelException> { evalConstant(CallExpression("UPPER", listOf(textLit("a"), textLit("b")))) }
|
||||
assertFailsWith<KestrelException> { evalConstant(CallExpression("SUBSTR", listOf(textLit("a")))) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun unknownFunctionFails() {
|
||||
assertFailsWith<KestrelException> { evalConstant(CallExpression("NOPE", listOf(intLit(1)))) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun currentDateReturnsTodaysWallClockDate() {
|
||||
assertEquals(Value.DateValue(LocalDate.now()), evalConstant(CallExpression("CURRENT_DATE", emptyList())))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dateComponentFunctionsExtractYearMonthDay() {
|
||||
val date = dateLit(LocalDate.of(2024, 3, 15))
|
||||
assertEquals(Value.IntegerValue(2024), evalConstant(CallExpression("YEAR", listOf(date))))
|
||||
assertEquals(Value.IntegerValue(3), evalConstant(CallExpression("MONTH", listOf(date))))
|
||||
assertEquals(Value.IntegerValue(15), evalConstant(CallExpression("DAY", listOf(date))))
|
||||
assertEquals(Value.IntegerValue(5), evalConstant(CallExpression("DAYOFWEEK", listOf(date))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dateComponentFunctionsPropagateNullAndRejectNonDateArguments() {
|
||||
assertEquals(Value.NullValue, evalConstant(CallExpression("YEAR", listOf(nullLit))))
|
||||
assertFailsWith<KestrelException> { evalConstant(CallExpression("MONTH", listOf(intLit(1)))) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dateAddAndDateSubShiftByWholeDaysInEitherDirection() {
|
||||
val date = dateLit(LocalDate.of(2024, 3, 15))
|
||||
assertEquals(
|
||||
Value.DateValue(LocalDate.of(2024, 3, 25)),
|
||||
evalConstant(CallExpression("DATE_ADD", listOf(date, intLit(10))))
|
||||
)
|
||||
assertEquals(
|
||||
Value.DateValue(LocalDate.of(2024, 3, 5)),
|
||||
evalConstant(CallExpression("DATE_SUB", listOf(date, intLit(10))))
|
||||
)
|
||||
// Crossing a month boundary exercises java.time's own carry logic rather than hand-rolled arithmetic.
|
||||
assertEquals(
|
||||
Value.DateValue(LocalDate.of(2024, 4, 2)),
|
||||
evalConstant(CallExpression("DATE_ADD", listOf(date, intLit(18))))
|
||||
)
|
||||
assertEquals(
|
||||
Value.DateValue(LocalDate.of(2024, 3, 9)),
|
||||
evalConstant(CallExpression("DATE_ADD", listOf(date, intLit(-6))))
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun datediffReturnsTheSignedDayCountBetweenTwoDates() {
|
||||
val later = dateLit(LocalDate.of(2024, 3, 20))
|
||||
val earlier = dateLit(LocalDate.of(2024, 3, 15))
|
||||
assertEquals(Value.IntegerValue(5), evalConstant(CallExpression("DATEDIFF", listOf(later, earlier))))
|
||||
assertEquals(Value.IntegerValue(-5), evalConstant(CallExpression("DATEDIFF", listOf(earlier, later))))
|
||||
assertEquals(Value.IntegerValue(0), evalConstant(CallExpression("DATEDIFF", listOf(later, later))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun repeatBuildsTheStringNTimesAndTreatsNonPositiveCountsAsEmpty() {
|
||||
assertEquals(Value.TextValue("ababab"), evalConstant(CallExpression("REPEAT", listOf(textLit("ab"), intLit(3)))))
|
||||
assertEquals(Value.TextValue(""), evalConstant(CallExpression("REPEAT", listOf(textLit("ab"), intLit(0)))))
|
||||
assertEquals(Value.TextValue(""), evalConstant(CallExpression("REPEAT", listOf(textLit("ab"), intLit(-1)))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun lpadAndRpadPadToTheTargetLengthOrTruncateWhenAlreadyLongEnough() {
|
||||
assertEquals(Value.TextValue("--ab"), evalConstant(CallExpression("LPAD", listOf(textLit("ab"), intLit(4), textLit("-")))))
|
||||
assertEquals(Value.TextValue("ab--"), evalConstant(CallExpression("RPAD", listOf(textLit("ab"), intLit(4), textLit("-")))))
|
||||
assertEquals(Value.TextValue(" ab"), evalConstant(CallExpression("LPAD", listOf(textLit("ab"), intLit(4)))))
|
||||
assertEquals(Value.TextValue("he"), evalConstant(CallExpression("LPAD", listOf(textLit("hello"), intLit(2), textLit("-")))))
|
||||
assertEquals(Value.TextValue("he"), evalConstant(CallExpression("RPAD", listOf(textLit("hello"), intLit(2), textLit("-")))))
|
||||
assertEquals(Value.TextValue(""), evalConstant(CallExpression("LPAD", listOf(textLit("hello"), intLit(0), textLit("-")))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun lpadCyclesAMultiCharacterPadStringAndTrimsItToExactlyFitTheTargetLength() {
|
||||
assertEquals(Value.TextValue("xyxab"), evalConstant(CallExpression("LPAD", listOf(textLit("ab"), intLit(5), textLit("xy")))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun padFunctionsRejectAnEmptyPadStringWhenPaddingIsActuallyNeeded() {
|
||||
assertFailsWith<KestrelException> {
|
||||
evalConstant(CallExpression("LPAD", listOf(textLit("ab"), intLit(4), textLit(""))))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun instrReturnsAOneBasedPositionOrZeroWhenNotFound() {
|
||||
assertEquals(Value.IntegerValue(3), evalConstant(CallExpression("INSTR", listOf(textLit("hello"), textLit("llo")))))
|
||||
assertEquals(Value.IntegerValue(1), evalConstant(CallExpression("INSTR", listOf(textLit("hello"), textLit("h")))))
|
||||
assertEquals(Value.IntegerValue(0), evalConstant(CallExpression("INSTR", listOf(textLit("hello"), textLit("z")))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun powerStaysExactForTwoIntegersButFallsBackToRealOtherwise() {
|
||||
assertEquals(Value.IntegerValue(1024), evalConstant(CallExpression("POWER", listOf(intLit(2), intLit(10)))))
|
||||
assertEquals(Value.IntegerValue(1), evalConstant(CallExpression("POWER", listOf(intLit(5), intLit(0)))))
|
||||
assertEquals(Value.RealValue(0.25), evalConstant(CallExpression("POWER", listOf(intLit(2), intLit(-2)))))
|
||||
assertEquals(Value.RealValue(6.25), evalConstant(CallExpression("POWER", listOf(realLit(2.5), intLit(2)))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun modComputesRemainderAndRejectsDivisionByZero() {
|
||||
assertEquals(Value.IntegerValue(1), evalConstant(CallExpression("MOD", listOf(intLit(7), intLit(3)))))
|
||||
assertEquals(Value.IntegerValue(-1), evalConstant(CallExpression("MOD", listOf(intLit(-7), intLit(3)))))
|
||||
assertEquals(Value.RealValue(0.5), evalConstant(CallExpression("MOD", listOf(realLit(2.5), intLit(1)))))
|
||||
assertFailsWith<KestrelException> { evalConstant(CallExpression("MOD", listOf(intLit(1), intLit(0)))) }
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user