Skip to content

Commit f161feb

Browse files
Ajit Pratap SinghAjit Pratap Singh
authored andcommitted
feat(parser): ClickHouse parametric aggregates fn(p)(args) (#482)
1 parent daea668 commit f161feb

3 files changed

Lines changed: 63 additions & 0 deletions

File tree

pkg/sql/ast/ast.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,7 @@ func (i Identifier) Children() []Node { return nil }
692692
type FunctionCall struct {
693693
Name string
694694
Arguments []Expression // Renamed from Args for consistency
695+
Parameters []Expression // ClickHouse parametric aggregates: quantile(0.5)(x) — params before args
695696
Over *WindowSpec // For window functions
696697
Distinct bool
697698
Filter Expression // WHERE clause for aggregate functions
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright 2026 GoSQLX Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
5+
package parser_test
6+
7+
import (
8+
"testing"
9+
10+
"github.com/ajitpratap0/GoSQLX/pkg/gosqlx"
11+
"github.com/ajitpratap0/GoSQLX/pkg/sql/keywords"
12+
)
13+
14+
// TestClickHouseParametricAggregates verifies that ClickHouse parametric
15+
// aggregates of the form `funcName(params)(args)` parse. Regression for #482.
16+
func TestClickHouseParametricAggregates(t *testing.T) {
17+
queries := map[string]string{
18+
"quantile_tdigest": `SELECT quantileTDigest(0.95)(value) FROM events`,
19+
"top_k": `SELECT topK(10)(name) FROM users`,
20+
"quantiles": `SELECT quantiles(0.5, 0.9, 0.99)(latency_ms) FROM requests`,
21+
"with_group_by": `SELECT category, quantileTDigest(0.99)(price) FROM products GROUP BY category`,
22+
}
23+
for name, q := range queries {
24+
q := q
25+
t.Run(name, func(t *testing.T) {
26+
if _, err := gosqlx.ParseWithDialect(q, keywords.DialectClickHouse); err != nil {
27+
t.Fatalf("parse failed: %v", err)
28+
}
29+
})
30+
}
31+
}

pkg/sql/parser/window.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323

2424
"github.com/ajitpratap0/GoSQLX/pkg/models"
2525
"github.com/ajitpratap0/GoSQLX/pkg/sql/ast"
26+
"github.com/ajitpratap0/GoSQLX/pkg/sql/keywords"
2627
)
2728

2829
// SUM(salary) OVER (PARTITION BY dept ORDER BY date ROWS UNBOUNDED PRECEDING) -> window function with frame
@@ -153,6 +154,36 @@ func (p *Parser) parseFunctionCall(funcName string) (*ast.FunctionCall, error) {
153154
OrderBy: orderByExprs,
154155
}
155156

157+
// ClickHouse parametric aggregates: funcName(params)(args).
158+
// e.g. quantileTDigest(0.95)(value), topK(10)(name).
159+
// What we just parsed becomes Parameters; the next paren group is the
160+
// real arguments. Gated to ClickHouse to avoid false positives.
161+
if p.dialect == string(keywords.DialectClickHouse) && p.isType(models.TokenTypeLParen) {
162+
funcCall.Parameters = funcCall.Arguments
163+
funcCall.Arguments = nil
164+
p.advance() // Consume second (
165+
if !p.isType(models.TokenTypeRParen) {
166+
for {
167+
arg, err := p.parseExpression()
168+
if err != nil {
169+
return nil, err
170+
}
171+
funcCall.Arguments = append(funcCall.Arguments, arg)
172+
if p.isType(models.TokenTypeComma) {
173+
p.advance()
174+
} else if p.isType(models.TokenTypeRParen) {
175+
break
176+
} else {
177+
return nil, p.expectedError(", or )")
178+
}
179+
}
180+
}
181+
if !p.isType(models.TokenTypeRParen) {
182+
return nil, p.expectedError(")")
183+
}
184+
p.advance() // Consume second )
185+
}
186+
156187
// Check for WITHIN GROUP clause (SQL:2003 ordered-set aggregates)
157188
// Syntax: WITHIN GROUP (ORDER BY expression [ASC|DESC] [NULLS FIRST|LAST])
158189
// Used with: PERCENTILE_CONT, PERCENTILE_DISC, MODE, LISTAGG, etc.

0 commit comments

Comments
 (0)