From 795ba0362056c1160e887da8998951d5c2c70d2a Mon Sep 17 00:00:00 2001 From: DualFroz Date: Tue, 8 Sep 2026 22:10:44 +0200 Subject: [PATCH] test: cover reverseFlipsTheCharactersOfAString --- src/test/kotlin/kestrel/EvaluatorTest.kt | 173 +++++++++++++++++++++++ 1 file changed, 173 insertions(+) diff --git a/src/test/kotlin/kestrel/EvaluatorTest.kt b/src/test/kotlin/kestrel/EvaluatorTest.kt index 245fff7..78f1df8 100644 --- a/src/test/kotlin/kestrel/EvaluatorTest.kt +++ b/src/test/kotlin/kestrel/EvaluatorTest.kt @@ -289,4 +289,177 @@ class EvaluatorTest { assertFailsWith { evalConstant(CallExpression("MOD", listOf(intLit(1), intLit(0)))) } } + @Test + fun reverseFlipsTheCharactersOfAString() { + assertEquals(Value.TextValue("cba"), evalConstant(CallExpression("REVERSE", listOf(textLit("abc"))))) + assertEquals(Value.TextValue(""), evalConstant(CallExpression("REVERSE", listOf(textLit(""))))) + assertFailsWith { evalConstant(CallExpression("REVERSE", listOf(intLit(1)))) } + } + + @Test + fun leftAndRightTakeCharactersFromEachEndAndClampToTheStringsLength() { + assertEquals(Value.TextValue("hel"), evalConstant(CallExpression("LEFT", listOf(textLit("hello"), intLit(3))))) + assertEquals(Value.TextValue("llo"), evalConstant(CallExpression("RIGHT", listOf(textLit("hello"), intLit(3))))) + assertEquals(Value.TextValue("hello"), evalConstant(CallExpression("LEFT", listOf(textLit("hello"), intLit(100))))) + assertEquals(Value.TextValue("hello"), evalConstant(CallExpression("RIGHT", listOf(textLit("hello"), intLit(100))))) + assertEquals(Value.TextValue(""), evalConstant(CallExpression("LEFT", listOf(textLit("hello"), intLit(0))))) + assertEquals(Value.TextValue(""), evalConstant(CallExpression("RIGHT", listOf(textLit("hello"), intLit(-1))))) + } + + @Test + fun signReturnsMinusOneZeroOrOneAcrossEveryNumericType() { + assertEquals(Value.IntegerValue(1), evalConstant(CallExpression("SIGN", listOf(intLit(5))))) + assertEquals(Value.IntegerValue(-1), evalConstant(CallExpression("SIGN", listOf(intLit(-5))))) + assertEquals(Value.IntegerValue(0), evalConstant(CallExpression("SIGN", listOf(intLit(0))))) + assertEquals(Value.IntegerValue(1), evalConstant(CallExpression("SIGN", listOf(realLit(0.1))))) + assertEquals(Value.IntegerValue(-1), evalConstant(CallExpression("SIGN", listOf(realLit(-0.1))))) + val negativeDecimal = LiteralExpression(Value.DecimalValue(BigDecimal("-3.50"))) + assertEquals(Value.IntegerValue(-1), evalConstant(CallExpression("SIGN", listOf(negativeDecimal)))) + assertFailsWith { evalConstant(CallExpression("SIGN", listOf(textLit("x")))) } + } + + @Test + fun truncCutsDigitsTowardZeroWithoutRoundingUnlikeRound() { + assertEquals(Value.IntegerValue(4), evalConstant(CallExpression("TRUNC", listOf(intLit(4))))) + assertEquals(Value.RealValue(1.9), evalConstant(CallExpression("TRUNC", listOf(realLit(1.99), intLit(1))))) + assertEquals(Value.RealValue(-1.9), evalConstant(CallExpression("TRUNC", listOf(realLit(-1.99), intLit(1))))) + val decimal = LiteralExpression(Value.DecimalValue(BigDecimal("1.999"))) + assertEquals( + Value.DecimalValue(BigDecimal("1.99")), + evalConstant(CallExpression("TRUNC", listOf(decimal, intLit(2)))) + ) + // TRUNC never rounds up, exactly the case where it must differ from ROUND's HALF_UP default. + assertEquals(Value.RealValue(1.0), evalConstant(CallExpression("TRUNC", listOf(realLit(1.9))))) + assertEquals(Value.RealValue(2.0), evalConstant(CallExpression("ROUND", listOf(realLit(1.9))))) + } + + @Test + fun greatestAndLeastPickTheOrderingExtremeAcrossMixedNumericTypes() { + assertEquals( + Value.IntegerValue(7), + evalConstant(CallExpression("GREATEST", listOf(intLit(3), intLit(7), intLit(1)))) + ) + assertEquals( + Value.IntegerValue(1), + evalConstant(CallExpression("LEAST", listOf(intLit(3), intLit(7), intLit(1)))) + ) + assertEquals( + Value.RealValue(7.5), + evalConstant(CallExpression("GREATEST", listOf(intLit(3), realLit(7.5), intLit(1)))) + ) + assertEquals(Value.TextValue("banana"), evalConstant(CallExpression("GREATEST", listOf(textLit("apple"), textLit("banana"))))) + } + + @Test + fun greatestAndLeastPropagateNullAndRequireAtLeastTwoArguments() { + assertEquals(Value.NullValue, evalConstant(CallExpression("GREATEST", listOf(intLit(1), nullLit)))) + assertEquals(Value.NullValue, evalConstant(CallExpression("LEAST", listOf(nullLit, intLit(1))))) + assertFailsWith { evalConstant(CallExpression("GREATEST", listOf(intLit(1)))) } + } + + @Test + fun functionRequiringTextRejectsWrongType() { + assertFailsWith { evalConstant(CallExpression("UPPER", listOf(intLit(1)))) } + } + + @Test + fun isNullDetectsNullness() { + assertEquals(Value.BooleanValue(true), evalConstant(IsNullExpression(nullLit, negated = false))) + assertEquals(Value.BooleanValue(false), evalConstant(IsNullExpression(intLit(1), negated = false))) + assertEquals(Value.BooleanValue(true), evalConstant(IsNullExpression(intLit(1), negated = true))) + assertEquals(Value.BooleanValue(false), evalConstant(IsNullExpression(nullLit, negated = true))) + } + + @Test + fun inMatchesAnyListMember() { + assertEquals( + Value.BooleanValue(true), + evalConstant(InExpression(intLit(2), listOf(intLit(1), intLit(2), intLit(3)), negated = false)) + ) + assertEquals( + Value.BooleanValue(false), + evalConstant(InExpression(intLit(9), listOf(intLit(1), intLit(2)), negated = false)) + ) + assertEquals( + Value.BooleanValue(false), + evalConstant(InExpression(intLit(2), listOf(intLit(1), intLit(2)), negated = true)) + ) + } + + @Test + fun inWithNullOperandIsNull() { + assertEquals(Value.NullValue, evalConstant(InExpression(nullLit, listOf(intLit(1)), negated = false))) + } + + @Test + fun inWithUnmatchedNullInListIsNullNotFalse() { + assertEquals( + Value.NullValue, + evalConstant(InExpression(intLit(9), listOf(intLit(1), nullLit), negated = false)) + ) + } + + @Test + fun betweenIsInclusiveOnBothEnds() { + assertEquals(Value.BooleanValue(true), evalConstant(BetweenExpression(intLit(18), intLit(18), intLit(65), negated = false))) + assertEquals(Value.BooleanValue(true), evalConstant(BetweenExpression(intLit(65), intLit(18), intLit(65), negated = false))) + assertEquals(Value.BooleanValue(false), evalConstant(BetweenExpression(intLit(17), intLit(18), intLit(65), negated = false))) + assertEquals(Value.BooleanValue(true), evalConstant(BetweenExpression(intLit(17), intLit(18), intLit(65), negated = true))) + } + + @Test + fun likeSupportsPercentAndUnderscoreWildcards() { + assertEquals(Value.BooleanValue(true), evalConstant(LikeExpression(textLit("hello"), textLit("h%"), negated = false))) + assertEquals(Value.BooleanValue(true), evalConstant(LikeExpression(textLit("hello"), textLit("h_llo"), negated = false))) + assertEquals(Value.BooleanValue(false), evalConstant(LikeExpression(textLit("hello"), textLit("world"), negated = false))) + assertEquals(Value.BooleanValue(false), evalConstant(LikeExpression(textLit("hello"), textLit("h%"), negated = true))) + } + + @Test + fun likeEscapesRegexMetacharactersInPattern() { + assertEquals(Value.BooleanValue(true), evalConstant(LikeExpression(textLit("a.b"), textLit("a.b"), negated = false))) + assertEquals(Value.BooleanValue(false), evalConstant(LikeExpression(textLit("axb"), textLit("a.b"), negated = false))) + } + + @Test + fun searchedCaseReturnsFirstMatchingBranch() { + val expression = CaseExpression( + operand = null, + branches = listOf( + CaseBranch(bin(BinaryOperator.Less, intLit(10), intLit(5)), textLit("small")), + CaseBranch(bin(BinaryOperator.GreaterEqual, intLit(10), intLit(5)), textLit("big")) + ), + elseExpression = textLit("none") + ) + assertEquals(Value.TextValue("big"), evalConstant(expression)) + } + + @Test + fun simpleCaseComparesOperandAgainstBranches() { + val expression = CaseExpression( + operand = intLit(2), + branches = listOf(CaseBranch(intLit(1), textLit("one")), CaseBranch(intLit(2), textLit("two"))), + elseExpression = textLit("other") + ) + assertEquals(Value.TextValue("two"), evalConstant(expression)) + } + + @Test + fun caseFallsThroughToElseWhenNoBranchMatches() { + val expression = CaseExpression( + operand = null, + branches = listOf(CaseBranch(falseLit, textLit("nope"))), + elseExpression = textLit("fallback") + ) + assertEquals(Value.TextValue("fallback"), evalConstant(expression)) + } + + @Test + fun caseWithoutElseYieldsNullWhenNoBranchMatches() { + val expression = CaseExpression(operand = null, branches = listOf(CaseBranch(falseLit, textLit("nope"))), elseExpression = null) + assertEquals(Value.NullValue, evalConstant(expression)) + } + + private fun cast(operand: Expression, type: ColumnType, precision: Int? = null, scale: Int? = null): Expression = + CastExpression(operand, type, precision, scale) }