Skip to content

Commit 2ca20d8

Browse files
committed
fix: support ClickHouse parametric aggregate calls
Handle chained function args like quantile(0.95)(cost) and add regression test for #2125.
1 parent ecaa26d commit 2ca20d8

File tree

7 files changed

+60
-0
lines changed

7 files changed

+60
-0
lines changed

src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ public <S> T visit(Function function, S context) {
112112
if (function.getParameters() != null) {
113113
subExpressions.addAll(function.getParameters());
114114
}
115+
if (function.getChainedParameters() != null) {
116+
subExpressions.addAll(function.getChainedParameters());
117+
}
115118
if (function.getKeep() != null) {
116119
subExpressions.add(function.getKeep());
117120
}

src/main/java/net/sf/jsqlparser/expression/Function.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
public class Function extends ASTNodeAccessImpl implements Expression {
2727
private List<String> nameparts;
2828
private ExpressionList<?> parameters;
29+
private ExpressionList<?> chainedParameters;
2930
private NamedExpressionList<?> namedParameters;
3031
private boolean allColumns = false;
3132
private boolean distinct = false;
@@ -192,6 +193,20 @@ public void setParameters(ExpressionList<?> list) {
192193
parameters = list;
193194
}
194195

196+
/**
197+
* Additional function-call parameters for dialects that support chained function calls, e.g.
198+
* quantile(0.95)(cost) in ClickHouse.
199+
*
200+
* @return the chained parameters of the function (if any, else null)
201+
*/
202+
public ExpressionList<?> getChainedParameters() {
203+
return chainedParameters;
204+
}
205+
206+
public void setChainedParameters(ExpressionList<?> chainedParameters) {
207+
this.chainedParameters = chainedParameters;
208+
}
209+
195210
/**
196211
* the parameters might be named parameters, e.g. substring('foobar' from 2 for 3)
197212
*
@@ -335,6 +350,10 @@ public String toString() {
335350

336351
String ans = getName() + params;
337352

353+
if (chainedParameters != null) {
354+
ans += "(" + chainedParameters + ")";
355+
}
356+
338357
if (nullHandling != null && isIgnoreNullsOutside()) {
339358
switch (nullHandling) {
340359
case IGNORE_NULLS:
@@ -393,6 +412,11 @@ public Function withParameters(Expression... parameters) {
393412
return withParameters(new ExpressionList<>(parameters));
394413
}
395414

415+
public Function withChainedParameters(ExpressionList<?> chainedParameters) {
416+
this.setChainedParameters(chainedParameters);
417+
return this;
418+
}
419+
396420
public Function withNamedParameters(NamedExpressionList<?> namedParameters) {
397421
this.setNamedParameters(namedParameters);
398422
return this;

src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,10 @@ public <S> Void visit(Function function, S context) {
429429
if (exprList != null) {
430430
visit(exprList, context);
431431
}
432+
exprList = function.getChainedParameters();
433+
if (exprList != null) {
434+
visit(exprList, context);
435+
}
432436
return null;
433437
}
434438

src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -920,6 +920,12 @@ public <S> StringBuilder visit(Function function, S context) {
920920
builder.append(")");
921921
}
922922

923+
if (function.getChainedParameters() != null) {
924+
builder.append("(");
925+
function.getChainedParameters().accept(this, context);
926+
builder.append(")");
927+
}
928+
923929
if (function.getNullHandling() != null && function.isIgnoreNullsOutside()) {
924930
switch (function.getNullHandling()) {
925931
case IGNORE_NULLS:

src/main/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidator.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,7 @@ public <S> Void visit(Function function, S context) {
542542

543543
validateOptionalExpressionList(function.getNamedParameters());
544544
validateOptionalExpressionList(function.getParameters());
545+
validateOptionalExpressionList(function.getChainedParameters());
545546

546547
Object attribute = function.getAttribute();
547548
if (attribute instanceof Expression) {

src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8308,6 +8308,7 @@ Function InternalFunction(boolean escaped):
83088308
Function retval = new Function();
83098309
ObjectNames funcName;
83108310
ExpressionList expressionList = null;
8311+
ExpressionList chainedExpressionList = null;
83118312
KeepExpression keep = null;
83128313
Expression expr = null;
83138314
Expression attributeExpression = null;
@@ -8374,6 +8375,10 @@ Function InternalFunction(boolean escaped):
83748375

83758376
")"
83768377

8378+
[
8379+
LOOKAHEAD(2) "(" chainedExpressionList = ExpressionList() ")"
8380+
]
8381+
83778382

83788383
[ LOOKAHEAD(2) "." (
83798384
// tricky lookahead since we do need to support the following constructs
@@ -8409,6 +8414,7 @@ Function InternalFunction(boolean escaped):
84098414
{
84108415
retval.setEscaped(escaped);
84118416
retval.setParameters(expressionList);
8417+
retval.setChainedParameters(chainedExpressionList);
84128418
retval.setName(funcName.getNames());
84138419
retval.setKeep(keep);
84148420
return retval;

src/test/java/net/sf/jsqlparser/statement/select/ClickHouseTest.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
package net.sf.jsqlparser.statement.select;
1111

1212
import net.sf.jsqlparser.JSQLParserException;
13+
import net.sf.jsqlparser.expression.Function;
1314
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
1415
import org.junit.jupiter.api.Assertions;
1516
import org.junit.jupiter.api.Test;
@@ -116,4 +117,19 @@ public void testPreWhereWithWhereClause() throws JSQLParserException {
116117
Assertions.assertNotNull(select.getPreWhere());
117118
Assertions.assertNotNull(select.getWhere());
118119
}
120+
121+
@Test
122+
public void testParameterizedAggregateFunctionIssue2125() throws JSQLParserException {
123+
String sql =
124+
"SELECT toStartOfDay(timestamp) AS date, count(1) AS count, quantile(0.95)(cost) AS cost95 FROM apm_log_event";
125+
Select select = (Select) assertSqlCanBeParsedAndDeparsed(sql, true);
126+
127+
Function function = ((PlainSelect) select.getSelectBody())
128+
.getSelectItem(2)
129+
.getExpression(Function.class);
130+
Assertions.assertNotNull(function.getParameters());
131+
Assertions.assertNotNull(function.getChainedParameters());
132+
Assertions.assertEquals(1, function.getParameters().size());
133+
Assertions.assertEquals(1, function.getChainedParameters().size());
134+
}
119135
}

0 commit comments

Comments
 (0)