Skip to content

Commit ff28f82

Browse files
feat: Fix GROUP_CONCAT SEPARATOR to accept expressions, not just string literals
The MySQLGroupConcat production hardcoded SEPARATOR <S_CHAR_LITERAL>, causing GROUP_CONCAT(col SEPARATOR CHR(10)) and similar to fail. Replace the dedicated MySQLGroupConcat production with a generic (KEYWORD expr)* tail in InternalFunction that captures dialect-specific keyword-expression pairs like SEPARATOR after the standard clauses. This routes GROUP_CONCAT through InternalFunction like any other function. - New: isKeywordArgumentAhead() semantic lookahead with 3-layer filter - New: KeywordArgumentName() BNF production (RelObjectName + K_USING) - New: Function.KeywordArgument inner class + List<KeywordArgument> on Function - New: keywordArguments support in AnalyticExpression and ExpressionDeParser - Removed: MySQLGroupConcat grammar production (absorbed by InternalFunction) Signed-off-by: Andreas Reichel <andreas@manticore-projects.com> Signed-off-by: manticore-projects <andreas@manticore-projects.com>
1 parent 137e014 commit ff28f82

File tree

9 files changed

+797
-27
lines changed

9 files changed

+797
-27
lines changed

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ public class AnalyticExpression extends ASTNodeAccessImpl implements Expression
5252

5353
private Limit limit = null;
5454

55+
private List<Function.KeywordArgument> keywordArguments = null;
56+
5557
public AnalyticExpression() {}
5658

5759
public AnalyticExpression(Function function) {
@@ -82,6 +84,7 @@ public AnalyticExpression(Function function) {
8284
this.onOverflowTruncate = function.getOnOverflowTruncate();
8385
this.limit = function.getLimit();
8486
this.keep = function.getKeep();
87+
this.keywordArguments = function.getKeywordArguments();
8588
}
8689

8790

@@ -263,6 +266,14 @@ public AnalyticExpression setLimit(Limit limit) {
263266
return this;
264267
}
265268

269+
public List<Function.KeywordArgument> getKeywordArguments() {
270+
return keywordArguments;
271+
}
272+
273+
public void setKeywordArguments(List<Function.KeywordArgument> keywordArguments) {
274+
this.keywordArguments = keywordArguments;
275+
}
276+
266277
@Override
267278
@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity",
268279
"PMD.MissingBreakInSwitch"})
@@ -313,6 +324,13 @@ public String toString() {
313324
b.append(limit);
314325
}
315326

327+
// Generic keyword arguments (e.g. SEPARATOR ',')
328+
if (keywordArguments != null) {
329+
for (Function.KeywordArgument ka : keywordArguments) {
330+
ka.appendTo(b);
331+
}
332+
}
333+
316334
b.append(") ");
317335
if (keep != null) {
318336
b.append(keep).append(" ");

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

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@
1616
import net.sf.jsqlparser.statement.select.Limit;
1717
import net.sf.jsqlparser.statement.select.OrderByElement;
1818

19+
import java.io.Serializable;
20+
import java.util.ArrayList;
1921
import java.util.Arrays;
2022
import java.util.Collections;
2123
import java.util.List;
24+
import java.util.Objects;
2225

2326
/**
2427
* A function as MAX,COUNT...
@@ -43,6 +46,14 @@ public class Function extends ASTNodeAccessImpl implements Expression {
4346
private String onOverflowTruncate = null;
4447
private String extraKeyword = null;
4548

49+
/**
50+
* Generic keyword arguments captured inside function parentheses, e.g.
51+
* {@code GROUP_CONCAT(col ORDER BY col SEPARATOR ',')} where {@code SEPARATOR ','} is a keyword
52+
* argument. This acts as a catch-all for dialect-specific {@code KEYWORD expr} pairs that don't
53+
* have dedicated grammar branches.
54+
*/
55+
private List<KeywordArgument> keywordArguments = null;
56+
4657
public Function() {}
4758

4859
public Function(String name, Expression... parameters) {
@@ -281,6 +292,53 @@ public Function setExtraKeyword(String extraKeyword) {
281292
return this;
282293
}
283294

295+
// ── Generic keyword argument support ───────────────────────────────
296+
297+
/**
298+
* Returns the list of generic keyword arguments, e.g. {@code SEPARATOR ','}.
299+
*
300+
* @return keyword arguments or {@code null}
301+
*/
302+
public List<KeywordArgument> getKeywordArguments() {
303+
return keywordArguments;
304+
}
305+
306+
public void setKeywordArguments(List<KeywordArgument> keywordArguments) {
307+
this.keywordArguments = keywordArguments;
308+
}
309+
310+
/**
311+
* Adds a single keyword argument (appends to the list, creating it if needed).
312+
*/
313+
public Function addKeywordArgument(String keyword, Expression expression) {
314+
if (this.keywordArguments == null) {
315+
this.keywordArguments = new ArrayList<>();
316+
}
317+
this.keywordArguments.add(new KeywordArgument(keyword, expression));
318+
return this;
319+
}
320+
321+
public Function withKeywordArguments(List<KeywordArgument> keywordArguments) {
322+
this.keywordArguments = keywordArguments;
323+
return this;
324+
}
325+
326+
/**
327+
* Convenience lookup: returns the expression for the first keyword argument matching the given
328+
* keyword (case-insensitive), or {@code null}.
329+
*/
330+
public Expression getKeywordArgumentValue(String keyword) {
331+
if (keywordArguments == null) {
332+
return null;
333+
}
334+
for (KeywordArgument ka : keywordArguments) {
335+
if (ka.getKeyword().equalsIgnoreCase(keyword)) {
336+
return ka.getExpression();
337+
}
338+
}
339+
return null;
340+
}
341+
284342
@Override
285343
@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"})
286344
public String toString() {
@@ -339,6 +397,13 @@ public String toString() {
339397
b.append(" ON OVERFLOW ").append(onOverflowTruncate);
340398
}
341399

400+
// Generic keyword arguments (e.g. SEPARATOR ',')
401+
if (keywordArguments != null) {
402+
for (KeywordArgument ka : keywordArguments) {
403+
ka.appendTo(b);
404+
}
405+
}
406+
342407
b.append(")");
343408
params = b.toString();
344409
} else {
@@ -462,6 +527,74 @@ public enum NullHandling {
462527
IGNORE_NULLS, RESPECT_NULLS;
463528
}
464529

530+
// ── KeywordArgument inner class ────────────────────────────────────
531+
532+
/**
533+
* Represents a generic {@code KEYWORD expression} pair inside a function call.
534+
* <p>
535+
* Examples:
536+
* <ul>
537+
* <li>{@code GROUP_CONCAT(col SEPARATOR ',')} → keyword="SEPARATOR", expression=','</li>
538+
* </ul>
539+
*/
540+
public static class KeywordArgument implements Serializable {
541+
private String keyword;
542+
private Expression expression;
543+
544+
public KeywordArgument() {}
545+
546+
public KeywordArgument(String keyword, Expression expression) {
547+
this.keyword = keyword;
548+
this.expression = expression;
549+
}
550+
551+
public String getKeyword() {
552+
return keyword;
553+
}
554+
555+
public KeywordArgument setKeyword(String keyword) {
556+
this.keyword = keyword;
557+
return this;
558+
}
559+
560+
public Expression getExpression() {
561+
return expression;
562+
}
563+
564+
public KeywordArgument setExpression(Expression expression) {
565+
this.expression = expression;
566+
return this;
567+
}
568+
569+
public StringBuilder appendTo(StringBuilder builder) {
570+
builder.append(" ").append(keyword).append(" ").append(expression);
571+
return builder;
572+
}
573+
574+
@Override
575+
public String toString() {
576+
return keyword + " " + expression;
577+
}
578+
579+
@Override
580+
public boolean equals(Object o) {
581+
if (this == o) {
582+
return true;
583+
}
584+
if (!(o instanceof KeywordArgument)) {
585+
return false;
586+
}
587+
KeywordArgument that = (KeywordArgument) o;
588+
return Objects.equals(keyword, that.keyword)
589+
&& Objects.equals(expression, that.expression);
590+
}
591+
592+
@Override
593+
public int hashCode() {
594+
return Objects.hash(keyword, expression);
595+
}
596+
}
597+
465598
public static class HavingClause extends ASTNodeAccessImpl implements Expression {
466599
HavingType havingType;
467600
Expression expression;

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,15 @@ public <S> StringBuilder visit(Function function, S context) {
917917
if (function.getLimit() != null) {
918918
new LimitDeparser(this, builder).deParse(function.getLimit());
919919
}
920+
921+
// Generic keyword arguments (e.g. SEPARATOR ',', USING utf8)
922+
if (function.getKeywordArguments() != null) {
923+
for (Function.KeywordArgument ka : function.getKeywordArguments()) {
924+
builder.append(" ").append(ka.getKeyword()).append(" ");
925+
ka.getExpression().accept(this, context);
926+
}
927+
}
928+
920929
builder.append(")");
921930
}
922931

0 commit comments

Comments
 (0)