From b05e8b2e3773284873c69ae155d48bc5b2b39745 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Wed, 9 Sep 2026 15:48:33 +0200 Subject: [PATCH] test: cover parsesScalarFunctionCallWithMultipleArguments --- src/test/kotlin/kestrel/ParserTest.kt | 173 ++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) diff --git a/src/test/kotlin/kestrel/ParserTest.kt b/src/test/kotlin/kestrel/ParserTest.kt index 1c27d7d..390a9bd 100644 --- a/src/test/kotlin/kestrel/ParserTest.kt +++ b/src/test/kotlin/kestrel/ParserTest.kt @@ -419,4 +419,177 @@ class ParserTest { fun reportsUnknownStatement() { assertFailsWith { parse("EXPLAIN users") } } + + @Test + fun parsesScalarFunctionCallWithMultipleArguments() { + val expression = firstProjection("SELECT SUBSTR(name, 1, 3) FROM t") + assertEquals( + CallExpression("SUBSTR", listOf(ColumnExpression("name"), intLit(1), intLit(3))), + expression + ) + } + + @Test + fun parsesScalarFunctionCallWithNoArguments() { + assertEquals(CallExpression("NOW", emptyList()), firstProjection("SELECT NOW() FROM t")) + } + + @Test + fun aggregateFunctionWithWrongArityFails() { + assertFailsWith { parse("SELECT SUM(a, b) FROM t") } + assertFailsWith { parse("SELECT COUNT() FROM t") } + } + + @Test + fun stringAggParsesTheValueAndSeparatorWithNoOrderByLeavingItEmpty() { + val expression = firstProjection("SELECT STRING_AGG(name, ', ') FROM t") + assertEquals( + FunctionExpression( + "STRING_AGG", + ColumnExpression("name"), + star = false, + separator = LiteralExpression(Value.TextValue(", ")), + orderBy = emptyList() + ), + expression + ) + } + + @Test + fun stringAggParsesAnInternalOrderByWithMultipleKeysAndExplicitDirections() { + val expression = firstProjection("SELECT STRING_AGG(name, ',' ORDER BY dept ASC, name DESC) FROM t") + assertEquals( + FunctionExpression( + "STRING_AGG", + ColumnExpression("name"), + star = false, + separator = LiteralExpression(Value.TextValue(",")), + orderBy = listOf( + OrderKey(ColumnExpression("dept"), descending = false), + OrderKey(ColumnExpression("name"), descending = true) + ) + ), + expression + ) + } + + @Test + fun stringAggParsesAnExplicitNullsLastInsideItsInternalOrderBy() { + val expression = firstProjection("SELECT STRING_AGG(name, ',' ORDER BY dept NULLS LAST) FROM t") + assertEquals( + FunctionExpression( + "STRING_AGG", + ColumnExpression("name"), + star = false, + separator = LiteralExpression(Value.TextValue(",")), + orderBy = listOf(OrderKey(ColumnExpression("dept"), descending = false, nullsFirst = false)) + ), + expression + ) + } + + @Test + fun stringAggRequiresBothAValueAndASeparatorArgument() { + assertFailsWith { parse("SELECT STRING_AGG(name) FROM t") } + assertFailsWith { parse("SELECT STRING_AGG() FROM t") } + } + + @Test + fun stringAggCannotBeUsedAsAWindowFunction() { + assertFailsWith { parse("SELECT STRING_AGG(name, ',') OVER () FROM t") } + } + + @Test + fun countDistinctSetsTheDistinctFlagWithTheGivenArgumentAsTheSoleArgument() { + val expression = firstProjection("SELECT COUNT(DISTINCT dept) FROM t") + assertEquals( + FunctionExpression("COUNT", ColumnExpression("dept"), star = false, distinct = true), + expression + ) + } + + @Test + fun sumAvgMinMaxAcceptDistinctTooNotJustCount() { + for (name in listOf("SUM", "AVG", "MIN", "MAX")) { + val expression = firstProjection("SELECT $name(DISTINCT amount) FROM t") + assertEquals( + FunctionExpression(name, ColumnExpression("amount"), star = false, distinct = true), + expression + ) + } + } + + @Test + fun plainAggregateCallsDefaultDistinctToFalse() { + val expression = firstProjection("SELECT COUNT(dept) FROM t") as FunctionExpression + assertEquals(false, expression.distinct) + } + + @Test + fun countDistinctStarIsRejected() { + assertFailsWith { parse("SELECT COUNT(DISTINCT *) FROM t") } + } + + @Test + fun countDistinctWithMultipleArgumentsIsRejected() { + assertFailsWith { parse("SELECT COUNT(DISTINCT dept, region) FROM t") } + } + + @Test + fun distinctAggregateCannotBeUsedAsAWindowFunction() { + assertFailsWith { parse("SELECT COUNT(DISTINCT dept) OVER () FROM t") } + assertFailsWith { parse("SELECT SUM(DISTINCT amount) OVER () FROM t") } + } + + @Test + fun aggregateFilterSetsTheFilterFieldToTheWhereCondition() { + val expression = firstProjection("SELECT COUNT(dept) FILTER (WHERE amount > 100) FROM t") + assertEquals( + FunctionExpression( + "COUNT", + ColumnExpression("dept"), + star = false, + filter = BinaryExpression(BinaryOperator.Greater, ColumnExpression("amount"), LiteralExpression(Value.IntegerValue(100))) + ), + expression + ) + } + + @Test + fun countStarFilterKeepsStarTrueAlongsideTheFilter() { + val expression = firstProjection("SELECT COUNT(*) FILTER (WHERE amount > 0) FROM t") as FunctionExpression + assertEquals(true, expression.star) + assertEquals(true, expression.filter != null) + } + + @Test + fun plainAggregateCallsDefaultFilterToNull() { + val expression = firstProjection("SELECT COUNT(dept) FROM t") as FunctionExpression + assertEquals(null, expression.filter) + } + + @Test + fun filterCanBeCombinedWithDistinct() { + val expression = firstProjection("SELECT COUNT(DISTINCT dept) FILTER (WHERE amount > 0) FROM t") as FunctionExpression + assertEquals(true, expression.distinct) + assertEquals(true, expression.filter != null) + } + + @Test + fun filterRequiresAWhereClauseInsideItsParentheses() { + assertFailsWith { parse("SELECT COUNT(dept) FILTER (amount > 100) FROM t") } + } + + @Test + fun filterIsRejectedOnNonAggregateFunctions() { + assertFailsWith { parse("SELECT UPPER(dept) FILTER (WHERE amount > 0) FROM t") } + } + + @Test + fun filterCannotBeUsedWithAWindowFunction() { + assertFailsWith { parse("SELECT COUNT(dept) FILTER (WHERE amount > 0) OVER () FROM t") } + } + + @Test + fun filterColumnNamedFilterIsStillUsableAsAPlainIdentifier() { }