From e1b4fd3b4ce5dc34fae05b71c5189c41d897df86 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Tue, 8 Sep 2026 23:39:08 +0200 Subject: [PATCH] test: cover scalarFunctionNotAllowedAsAggregateGroupingViolation --- .../kotlin/kestrel/ExpressionLanguageTest.kt | 167 ++++++++++++++++++ 1 file changed, 167 insertions(+) diff --git a/src/test/kotlin/kestrel/ExpressionLanguageTest.kt b/src/test/kotlin/kestrel/ExpressionLanguageTest.kt index cecce8e..bbc960c 100644 --- a/src/test/kotlin/kestrel/ExpressionLanguageTest.kt +++ b/src/test/kotlin/kestrel/ExpressionLanguageTest.kt @@ -156,3 +156,170 @@ class ExpressionLanguageTest { ) } + @Test + fun scalarFunctionNotAllowedAsAggregateGroupingViolation() { + seedUsers() + assertFailsWith { + select("SELECT UPPER(name), COUNT(*) FROM users GROUP BY age") + } + } + + @Test + fun newPredicateFormsCoexistWithIndexPushdownOnOtherConjuncts() { + seedUsers() + run("CREATE INDEX idxId ON users (id)") + val plan = (run("EXPLAIN SELECT name FROM users WHERE id = 1 AND age BETWEEN 1 AND 100").single() as ExplainResult).plan + assertTrue(plan.any { it.contains("Index Scan") }, "expected an index scan but was: $plan") + val result = select("SELECT name FROM users WHERE id = 1 AND age BETWEEN 1 AND 100") + assertEquals(listOf(listOf(text("ada"))), result.rows) + } + + @Test + fun unknownFunctionNameReportsClearError() { + seedUsers() + assertFailsWith { select("SELECT NOSUCHFUNC(name) FROM users") } + } + + private fun decimal(value: String) = Value.DecimalValue(BigDecimal(value)) + private fun date(value: String) = Value.DateValue(LocalDate.parse(value)) + + @Test + fun decimalColumnCoercesIntegerAndRealButRejectsBareText() { + run("CREATE TABLE orders (id INTEGER PRIMARY KEY, amount DECIMAL(10, 2))") + run("INSERT INTO orders VALUES (1, 5), (2, 5.5)") + val result = select("SELECT amount FROM orders ORDER BY id ASC") + assertEquals(listOf(listOf(decimal("5.00")), listOf(decimal("5.50"))), result.rows) + assertFailsWith { run("INSERT INTO orders VALUES (3, '5.00')") } + } + + @Test + fun dateColumnRequiresExplicitCastFromText() { + run("CREATE TABLE events (id INTEGER PRIMARY KEY, happened DATE)") + assertFailsWith { run("INSERT INTO events VALUES (1, '2024-01-01')") } + run("INSERT INTO events VALUES (1, CAST('2024-01-01' AS DATE))") + val result = select("SELECT happened FROM events") + assertEquals(listOf(listOf(date("2024-01-01"))), result.rows) + } + + @Test + fun decimalSumAndAverageStayExactAcrossGroupBy() { + run("CREATE TABLE orders (id INTEGER PRIMARY KEY, dept TEXT, amount DECIMAL(10, 2))") + run( + "INSERT INTO orders VALUES " + + "(1, 'eng', 10.10), (2, 'eng', 10.20), (3, 'eng', 10.30), (4, 'ops', 1.11)" + ) + val result = select("SELECT dept, SUM(amount), AVG(amount) FROM orders GROUP BY dept ORDER BY dept ASC") + assertEquals( + listOf( + listOf(text("eng"), decimal("30.60"), decimal("10.20")), + listOf(text("ops"), decimal("1.11"), decimal("1.11")) + ), + result.rows + ) + } + + @Test + fun dateArithmeticEndToEndThroughSelectAndWhere() { + run("CREATE TABLE events (id INTEGER PRIMARY KEY, happened DATE)") + run( + "INSERT INTO events VALUES " + + "(1, CAST('2024-01-01' AS DATE)), (2, CAST('2024-06-01' AS DATE))" + ) + val projected = select("SELECT happened + 7 FROM events ORDER BY id ASC") + assertEquals(listOf(listOf(date("2024-01-08")), listOf(date("2024-06-08"))), projected.rows) + + val filtered = select( + "SELECT id FROM events WHERE happened > CAST('2024-03-01' AS DATE)" + ) + assertEquals(listOf(listOf(int(2))), filtered.rows) + } + + @Test + fun castExpressionWorksInProjectionAndWhereAgainstOrdinaryColumns() { + seedUsers() + val projected = select("SELECT CAST(age AS TEXT) FROM users WHERE id = 1") + assertEquals(listOf(listOf(text("36"))), projected.rows) + + val filtered = select("SELECT name FROM users WHERE CAST(age AS REAL) > 40.0 ORDER BY name ASC") + assertEquals(listOf(listOf(text("grace")), listOf(text("linus"))), filtered.rows) + } + + @Test + fun dateComponentFunctionsEndToEndThroughSelectAndWhere() { + run("CREATE TABLE events (id INTEGER PRIMARY KEY, happened DATE)") + run( + "INSERT INTO events VALUES " + + "(1, CAST('2024-03-15' AS DATE)), (2, CAST('2023-11-02' AS DATE))" + ) + val projected = select("SELECT YEAR(happened), MONTH(happened), DAY(happened) FROM events ORDER BY id ASC") + assertEquals( + listOf(listOf(int(2024), int(3), int(15)), listOf(int(2023), int(11), int(2))), + projected.rows + ) + + val filtered = select("SELECT id FROM events WHERE YEAR(happened) = 2023") + assertEquals(listOf(listOf(int(2))), filtered.rows) + + val today = select("SELECT CURRENT_DATE() FROM events WHERE id = 1") + assertEquals(listOf(listOf(date(LocalDate.now().toString()))), today.rows) + } + + @Test + fun dateAddDateSubAndDatediffEndToEndThroughSelectAndWhere() { + run("CREATE TABLE events (id INTEGER PRIMARY KEY, happened DATE)") + run( + "INSERT INTO events VALUES " + + "(1, CAST('2024-03-15' AS DATE)), (2, CAST('2024-03-20' AS DATE))" + ) + val shifted = select( + "SELECT DATE_ADD(happened, 5), DATE_SUB(happened, 5) FROM events ORDER BY id ASC" + ) + assertEquals( + listOf( + listOf(date("2024-03-20"), date("2024-03-10")), + listOf(date("2024-03-25"), date("2024-03-15")) + ), + shifted.rows + ) + + val diff = select( + "SELECT DATEDIFF((SELECT happened FROM events WHERE id = 2), happened) FROM events WHERE id = 1" + ) + assertEquals(listOf(listOf(int(5))), diff.rows) + + val filtered = select("SELECT id FROM events WHERE DATE_ADD(happened, 5) = CAST('2024-03-25' AS DATE)") + assertEquals(listOf(listOf(int(2))), filtered.rows) + } + + @Test + fun repeatLpadAndRpadEndToEndThroughSelectAndUpdate() { + run("CREATE TABLE codes (id INTEGER PRIMARY KEY, code TEXT)") + run("INSERT INTO codes VALUES (1, '7'), (2, '42')") + val padded = select("SELECT id, LPAD(code, 4, '0') FROM codes ORDER BY id ASC") + assertEquals(listOf(listOf(int(1), text("0007")), listOf(int(2), text("0042"))), padded.rows) + + run("UPDATE codes SET code = RPAD(code, 5, '-') WHERE id = 1") + assertEquals(listOf(listOf(text("7----"))), select("SELECT code FROM codes WHERE id = 1").rows) + + val separator = select("SELECT REPEAT('-', 3) FROM codes WHERE id = 2") + assertEquals(listOf(listOf(text("---"))), separator.rows) + } + + @Test + fun signTruncGreatestAndLeastEndToEndThroughSelectWhereAndUpdate() { + run("CREATE TABLE accounts (id INTEGER PRIMARY KEY, balance INTEGER, limitAmount INTEGER, ratio REAL)") + run("INSERT INTO accounts VALUES (1, -50, 100, 0.0), (2, 75, 40, 0.0), (3, 0, 0, 0.0)") + + val signs = select("SELECT id, SIGN(balance) FROM accounts ORDER BY id ASC") + assertEquals(listOf(listOf(int(1), int(-1)), listOf(int(2), int(1)), listOf(int(3), int(0))), signs.rows) + + val capped = select("SELECT id, LEAST(balance, limitAmount) FROM accounts ORDER BY id ASC") + assertEquals(listOf(listOf(int(1), int(-50)), listOf(int(2), int(40)), listOf(int(3), int(0))), capped.rows) + + val overLimit = select("SELECT id FROM accounts WHERE GREATEST(balance, 0) > limitAmount") + assertEquals(listOf(listOf(int(2))), overLimit.rows) + + run("UPDATE accounts SET ratio = TRUNC(balance / 3.0) WHERE id = 2") + assertEquals(listOf(listOf(Value.RealValue(25.0))), select("SELECT ratio FROM accounts WHERE id = 2").rows) + } +}