test: cover stringAggJoinsWithTheGivenSeparatorInRowOrderWhenThereIsN...

This commit is contained in:
dualfroz committed 2026-09-08 15:39:16 +02:00
1 parent c481849908
commit 45ca433d69
1 file changed
+208
+208
View File
@@ -143,4 +143,212 @@ class AggregateTest {
assertEquals(listOf(text("eng"), int(303)), result.rows[0]) assertEquals(listOf(text("eng"), int(303)), result.rows[0])
assertEquals(listOf(text("ops"), int(122)), result.rows[1]) assertEquals(listOf(text("ops"), int(122)), result.rows[1])
} }
@Test
fun stringAggJoinsWithTheGivenSeparatorInRowOrderWhenThereIsNoOrderByClause() {
run("CREATE TABLE t (label TEXT)")
run("INSERT INTO t VALUES ('c'), ('a'), ('b')")
assertEquals(listOf(listOf(text("c, a, b"))), select("SELECT STRING_AGG(label, ', ') FROM t").rows)
}
@Test
fun stringAggOrderByInsideTheCallSortsIndependentlyOfInsertionOrder() {
run("CREATE TABLE t (label TEXT, rank INTEGER)")
run("INSERT INTO t VALUES ('c', 3), ('a', 1), ('b', 2)")
val result = select("SELECT STRING_AGG(label, '-' ORDER BY rank) FROM t")
assertEquals(listOf(listOf(text("a-b-c"))), result.rows)
}
@Test
fun stringAggOrderByDescendingReversesTheConcatenationOrder() {
run("CREATE TABLE t (label TEXT, rank INTEGER)")
run("INSERT INTO t VALUES ('a', 1), ('b', 2), ('c', 3)")
val result = select("SELECT STRING_AGG(label, '-' ORDER BY rank DESC) FROM t")
assertEquals(listOf(listOf(text("c-b-a"))), result.rows)
}
@Test
fun stringAggSkipsNullValuesButKeepsNonNullOnesInOrder() {
run("CREATE TABLE t (label TEXT)")
run("INSERT INTO t VALUES ('a'), (NULL), ('b')")
assertEquals(listOf(listOf(text("a, b"))), select("SELECT STRING_AGG(label, ', ') FROM t").rows)
}
@Test
fun stringAggOverOnlyNullValuesIsNull() {
run("CREATE TABLE t (label TEXT)")
run("INSERT INTO t VALUES (NULL), (NULL)")
assertEquals(listOf(listOf(Value.NullValue)), select("SELECT STRING_AGG(label, ', ') FROM t").rows)
}
@Test
fun stringAggOverAnEmptyTableIsNull() {
run("CREATE TABLE t (label TEXT)")
assertEquals(listOf(listOf(Value.NullValue)), select("SELECT STRING_AGG(label, ', ') FROM t").rows)
}
@Test
fun stringAggPerGroupWithGroupBy() {
seed()
val result = select("SELECT dept, STRING_AGG(region, '/' ORDER BY region) FROM sales GROUP BY dept ORDER BY dept ASC")
assertEquals(listOf(text("eng"), text("eu/us/us")), result.rows[0])
assertEquals(listOf(text("ops"), text("eu/eu")), result.rows[1])
}
@Test
fun stringAggRejectsANonTextValueArgument() {
seed()
val failure = assertFailsWith<KestrelException> { select("SELECT STRING_AGG(amount, ', ') FROM sales") }
assertEquals("function STRING_AGG requires a TEXT argument but got INTEGER", failure.message)
}
@Test
fun stringAggRejectsANonTextSeparator() {
run("CREATE TABLE t (label TEXT)")
run("INSERT INTO t VALUES ('a')")
assertFailsWith<KestrelException> { select("SELECT STRING_AGG(label, 1) FROM t") }
}
@Test
fun stringAggCannotBeUsedAsAWindowFunction() {
assertFailsWith<ParseException> {
Parser(Lexer("SELECT STRING_AGG(label, ', ') OVER () FROM t").tokenize()).parseProgram()
}
}
@Test
fun stringAggRequiresBothAValueAndASeparatorArgument() {
assertFailsWith<ParseException> {
Parser(Lexer("SELECT STRING_AGG(label) FROM t").tokenize()).parseProgram()
}
}
private fun seedDistinct() {
run("CREATE TABLE t (dept TEXT, amount INTEGER)")
run("INSERT INTO t VALUES ('eng', 100), ('eng', 100), ('eng', 200), ('ops', 100), ('ops', NULL)")
}
@Test
fun countDistinctCountsEachDistinctNonNullValueOnce() {
seedDistinct()
assertEquals(listOf(listOf(int(4))), select("SELECT COUNT(amount) FROM t").rows)
assertEquals(listOf(listOf(int(2))), select("SELECT COUNT(DISTINCT amount) FROM t").rows)
}
@Test
fun sumDistinctAddsEachDistinctValueOnce() {
seedDistinct()
assertEquals(listOf(listOf(int(500))), select("SELECT SUM(amount) FROM t").rows)
assertEquals(listOf(listOf(int(300))), select("SELECT SUM(DISTINCT amount) FROM t").rows)
}
@Test
fun avgDistinctAveragesEachDistinctValueOnce() {
seedDistinct()
assertEquals(listOf(listOf(real(125.0))), select("SELECT AVG(amount) FROM t").rows)
assertEquals(listOf(listOf(real(150.0))), select("SELECT AVG(DISTINCT amount) FROM t").rows)
}
@Test
fun minAndMaxDistinctAreUnaffectedByDeduplication() {
seedDistinct()
val result = select("SELECT MIN(DISTINCT amount) AS lo, MAX(DISTINCT amount) AS hi FROM t")
assertEquals(listOf(int(100), int(200)), result.rows[0])
}
@Test
fun countDistinctIgnoresNulls() {
seedDistinct()
assertEquals(listOf(listOf(int(0))), select("SELECT COUNT(DISTINCT amount) FROM t WHERE dept = 'nope'").rows)
}
@Test
fun countDistinctPerGroupWithGroupBy() {
seedDistinct()
val result = select("SELECT dept, COUNT(DISTINCT amount) FROM t GROUP BY dept ORDER BY dept ASC")
assertEquals(listOf(listOf(text("eng"), int(2)), listOf(text("ops"), int(1))), result.rows)
}
@Test
fun countDistinctStarAndDistinctOverAWindowAreRejectedAtParseTime() {
assertFailsWith<ParseException> { Parser(Lexer("SELECT COUNT(DISTINCT *) FROM t").tokenize()).parseProgram() }
assertFailsWith<ParseException> {
Parser(Lexer("SELECT SUM(DISTINCT amount) OVER () FROM t").tokenize()).parseProgram()
}
}
@Test
fun countStarFilterCountsOnlyRowsMatchingThePredicate() {
seedDistinct()
assertEquals(listOf(listOf(int(5))), select("SELECT COUNT(*) FROM t").rows)
assertEquals(listOf(listOf(int(1))), select("SELECT COUNT(*) FILTER (WHERE amount > 100) FROM t").rows)
}
@Test
fun countColumnFilterCombinesNullSkippingWithThePredicate() {
seedDistinct()
assertEquals(listOf(listOf(int(3))), select("SELECT COUNT(amount) FILTER (WHERE dept = 'eng') FROM t").rows)
assertEquals(listOf(listOf(int(1))), select("SELECT COUNT(amount) FILTER (WHERE dept = 'ops') FROM t").rows)
}
@Test
fun sumFilterSumsOnlyRowsMatchingThePredicate() {
seedDistinct()
assertEquals(listOf(listOf(int(400))), select("SELECT SUM(amount) FILTER (WHERE dept = 'eng') FROM t").rows)
assertEquals(listOf(listOf(int(100))), select("SELECT SUM(amount) FILTER (WHERE dept = 'ops') FROM t").rows)
}
@Test
fun avgMinMaxFilterOperateOnlyOnRowsMatchingThePredicate() {
seedDistinct()
val result = select(
"SELECT AVG(amount) FILTER (WHERE amount >= 150) AS a, " +
"MIN(amount) FILTER (WHERE dept = 'eng') AS lo, " +
"MAX(amount) FILTER (WHERE dept = 'eng') AS hi FROM t"
)
assertEquals(listOf(real(200.0), int(100), int(200)), result.rows[0])
}
@Test
fun filterExcludingEveryRowLeavesCountZeroAndSumAvgMinMaxNull() {
seedDistinct()
val result = select(
"SELECT COUNT(*) FILTER (WHERE dept = 'nope') AS c, " +
"SUM(amount) FILTER (WHERE dept = 'nope') AS s, " +
"AVG(amount) FILTER (WHERE dept = 'nope') AS a FROM t"
)
assertEquals(listOf(int(0), Value.NullValue, Value.NullValue), result.rows[0])
}
@Test
fun filterCombinesWithDistinct() {
seedDistinct()
assertEquals(
listOf(listOf(int(2))),
select("SELECT COUNT(DISTINCT amount) FILTER (WHERE dept = 'eng') FROM t").rows
)
}
@Test
fun filterAppliesIndependentlyPerAggregatePerGroup() {
seedDistinct()
val result = select(
"SELECT dept, COUNT(*) FILTER (WHERE amount > 50) AS matching, COUNT(*) AS total " +
"FROM t GROUP BY dept ORDER BY dept ASC"
)
assertEquals(
listOf(listOf(text("eng"), int(3), int(3)), listOf(text("ops"), int(1), int(2))),
result.rows
)
}
@Test
fun filterIsRejectedOnNonAggregateFunctionsAndOnWindowFunctions() {
assertFailsWith<ParseException> {
Parser(Lexer("SELECT UPPER(dept) FILTER (WHERE amount > 0) FROM t").tokenize()).parseProgram()
}
assertFailsWith<ParseException> {
Parser(Lexer("SELECT SUM(amount) FILTER (WHERE amount > 0) OVER () FROM t").tokenize()).parseProgram()
}
}
} }