test: cover stringFunctionsTransformText
This commit is contained in:
1 parent
1614395134
commit
5eedad6404
1 file changed
+183
@@ -1,12 +1,22 @@
|
|||||||
package kestrel
|
package kestrel
|
||||||
|
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import java.time.LocalDate
|
||||||
import kestrel.catalog.Column
|
import kestrel.catalog.Column
|
||||||
import kestrel.catalog.TableSchema
|
import kestrel.catalog.TableSchema
|
||||||
import kestrel.engine.Evaluator
|
import kestrel.engine.Evaluator
|
||||||
|
import kestrel.sql.ast.BetweenExpression
|
||||||
import kestrel.sql.ast.BinaryExpression
|
import kestrel.sql.ast.BinaryExpression
|
||||||
import kestrel.sql.ast.BinaryOperator
|
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.ColumnExpression
|
||||||
import kestrel.sql.ast.Expression
|
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.LiteralExpression
|
||||||
import kestrel.sql.ast.UnaryExpression
|
import kestrel.sql.ast.UnaryExpression
|
||||||
import kestrel.sql.ast.UnaryOperator
|
import kestrel.sql.ast.UnaryOperator
|
||||||
@@ -106,4 +116,177 @@ class EvaluatorTest {
|
|||||||
fun logicalOperatorOnNumberFails() {
|
fun logicalOperatorOnNumberFails() {
|
||||||
assertFailsWith<KestrelException> { evalConstant(bin(BinaryOperator.And, intLit(1), trueLit)) }
|
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