Skip to content

Commit 0994664

Browse files
committed
Add window function support to keyword-as-function parsing
When keywords like ANY, ALL, etc. are used as function names (e.g., any(number)), they were being parsed by parseKeywordAsFunction which didn't handle OVER clause for window functions. Added support for: - DISTINCT inside keyword functions - IGNORE NULLS / RESPECT NULLS modifiers - FILTER clause for aggregate functions - OVER clause for window functions This fixes parsing of queries like: SELECT any(number) OVER () FROM numbers(2);
1 parent 9a0b0ad commit 0994664

5 files changed

Lines changed: 48 additions & 26 deletions

File tree

parser/expression.go

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1897,22 +1897,60 @@ func (p *Parser) parseKeywordAsFunction() ast.Expression {
18971897
return nil
18981898
}
18991899

1900-
var args []ast.Expression
1900+
fn := &ast.FunctionCall{
1901+
Position: pos,
1902+
Name: name,
1903+
}
1904+
1905+
// Handle DISTINCT
1906+
if p.currentIs(token.DISTINCT) {
1907+
fn.Distinct = true
1908+
p.nextToken()
1909+
}
1910+
19011911
// Handle view() and similar functions that take a subquery as argument
19021912
if name == "view" && (p.currentIs(token.SELECT) || p.currentIs(token.WITH)) {
19031913
subquery := p.parseSelectWithUnion()
1904-
args = []ast.Expression{&ast.Subquery{Position: pos, Query: subquery}}
1914+
fn.Arguments = []ast.Expression{&ast.Subquery{Position: pos, Query: subquery}}
19051915
} else if !p.currentIs(token.RPAREN) {
1906-
args = p.parseExpressionList()
1916+
fn.Arguments = p.parseExpressionList()
19071917
}
19081918

19091919
p.expect(token.RPAREN)
19101920

1911-
return &ast.FunctionCall{
1912-
Position: pos,
1913-
Name: name,
1914-
Arguments: args,
1921+
// Handle IGNORE NULLS / RESPECT NULLS (window function modifiers)
1922+
for p.currentIs(token.IDENT) {
1923+
upper := strings.ToUpper(p.current.Value)
1924+
if upper == "IGNORE" || upper == "RESPECT" {
1925+
p.nextToken()
1926+
if p.currentIs(token.NULLS) {
1927+
p.nextToken()
1928+
}
1929+
} else {
1930+
break
1931+
}
1932+
}
1933+
1934+
// Handle FILTER clause for aggregate functions: func() FILTER(WHERE condition)
1935+
if p.currentIs(token.IDENT) && strings.ToUpper(p.current.Value) == "FILTER" {
1936+
p.nextToken() // skip FILTER
1937+
if p.currentIs(token.LPAREN) {
1938+
p.nextToken() // skip (
1939+
if p.currentIs(token.WHERE) {
1940+
p.nextToken() // skip WHERE
1941+
p.parseExpression(LOWEST)
1942+
}
1943+
p.expect(token.RPAREN)
1944+
}
1945+
}
1946+
1947+
// Handle OVER clause for window functions
1948+
if p.currentIs(token.OVER) {
1949+
p.nextToken()
1950+
fn.Over = p.parseWindowSpec()
19151951
}
1952+
1953+
return fn
19161954
}
19171955

19181956
func (p *Parser) parseKeywordAsIdentifier() ast.Expression {

parser/testdata/01591_window_functions/metadata.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
"stmt110": true,
1212
"stmt111": true,
1313
"stmt112": true,
14-
"stmt28": true,
1514
"stmt31": true,
1615
"stmt34": true,
1716
"stmt35": true,
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt7": true
4-
}
5-
}
1+
{}
Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
11
{
22
"explain_todo": {
3-
"stmt36": true,
4-
"stmt38": true,
53
"stmt40": true,
6-
"stmt41": true,
7-
"stmt42": true,
8-
"stmt44": true,
9-
"stmt46": true,
10-
"stmt47": true,
11-
"stmt6": true
4+
"stmt41": true
125
}
136
}
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt4": true
4-
}
5-
}
1+
{}

0 commit comments

Comments
 (0)