Skip to content

Commit cfe3f07

Browse files
committed
feat(parser): support DuckDB MAP literals and PIVOT queries
1 parent 44c0fe5 commit cfe3f07

16 files changed

Lines changed: 762 additions & 20 deletions

File tree

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,14 @@ default void visit(ArrayConstructor arrayConstructor) {
621621
this.visit(arrayConstructor, null);
622622
}
623623

624+
default <S> T visit(MapExpression mapExpression, S context) {
625+
return null;
626+
}
627+
628+
default void visit(MapExpression mapExpression) {
629+
this.visit(mapExpression, null);
630+
}
631+
624632
<S> T visit(VariableAssignment variableAssignment, S context);
625633

626634
default void visit(VariableAssignment variableAssignment) {

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.util.ArrayList;
1313
import java.util.Arrays;
1414
import java.util.Collection;
15+
import java.util.Map;
1516
import java.util.Optional;
1617
import net.sf.jsqlparser.expression.operators.arithmetic.Addition;
1718
import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseAnd;
@@ -693,6 +694,16 @@ public <S> T visit(ArrayConstructor arrayConstructor, S context) {
693694
return visitExpressions(arrayConstructor, context, arrayConstructor.getExpressions());
694695
}
695696

697+
@Override
698+
public <S> T visit(MapExpression mapExpression, S context) {
699+
ArrayList<Expression> subExpressions = new ArrayList<>();
700+
for (Map.Entry<Expression, Expression> entry : mapExpression.getEntries()) {
701+
subExpressions.add(entry.getKey());
702+
subExpressions.add(entry.getValue());
703+
}
704+
return visitExpressions(mapExpression, context, subExpressions);
705+
}
706+
696707
@Override
697708
public <S> T visit(VariableAssignment variableAssignment, S context) {
698709
return visitExpressions(variableAssignment, context, variableAssignment.getVariable(),
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2026 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.expression;
11+
12+
import java.util.AbstractMap;
13+
import java.util.ArrayList;
14+
import java.util.Collection;
15+
import java.util.List;
16+
import java.util.Map;
17+
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
18+
19+
/**
20+
* DuckDB's {@code MAP {key: value, ...}} literal expression.
21+
*
22+
* @see <a href="https://duckdb.org/docs/sql/data_types/map.html">DuckDB MAP type</a>
23+
*/
24+
public class MapExpression extends ASTNodeAccessImpl implements Expression {
25+
26+
private List<Map.Entry<Expression, Expression>> entries = new ArrayList<>();
27+
28+
public MapExpression() {}
29+
30+
public MapExpression(Collection<? extends Map.Entry<Expression, Expression>> entries) {
31+
this.entries.addAll(entries);
32+
}
33+
34+
public List<Map.Entry<Expression, Expression>> getEntries() {
35+
return entries;
36+
}
37+
38+
public void setEntries(List<Map.Entry<Expression, Expression>> entries) {
39+
this.entries = entries;
40+
}
41+
42+
public MapExpression withEntries(List<Map.Entry<Expression, Expression>> entries) {
43+
setEntries(entries);
44+
return this;
45+
}
46+
47+
public MapExpression addEntries(
48+
Collection<? extends Map.Entry<Expression, Expression>> entries) {
49+
this.entries.addAll(entries);
50+
return this;
51+
}
52+
53+
public MapExpression addEntry(Expression key, Expression value) {
54+
entries.add(new AbstractMap.SimpleEntry<>(key, value));
55+
return this;
56+
}
57+
58+
public StringBuilder appendTo(StringBuilder builder) {
59+
builder.append("MAP {");
60+
for (int i = 0; i < entries.size(); i++) {
61+
if (i > 0) {
62+
builder.append(", ");
63+
}
64+
Map.Entry<Expression, Expression> entry = entries.get(i);
65+
builder.append(entry.getKey()).append(": ").append(entry.getValue());
66+
}
67+
return builder.append("}");
68+
}
69+
70+
@Override
71+
public String toString() {
72+
return appendTo(new StringBuilder()).toString();
73+
}
74+
75+
@Override
76+
public <T, S> T accept(ExpressionVisitor<T> expressionVisitor, S context) {
77+
return expressionVisitor.visit(this, context);
78+
}
79+
}

src/main/java/net/sf/jsqlparser/statement/select/FromItemVisitor.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,12 @@
99
*/
1010
package net.sf.jsqlparser.statement.select;
1111

12+
import java.util.Collection;
13+
import java.util.List;
1214
import net.sf.jsqlparser.schema.Table;
1315
import net.sf.jsqlparser.statement.imprt.Import;
1416
import net.sf.jsqlparser.statement.piped.FromQuery;
1517

16-
import java.util.Collection;
17-
import java.util.List;
18-
1918
public interface FromItemVisitor<T> {
2019

2120
default <S> T visitFromItem(FromItem fromItem, S context) {
@@ -85,6 +84,14 @@ default void visit(PlainSelect plainSelect) {
8584
this.visit(plainSelect, null);
8685
}
8786

87+
default <S> T visit(PivotQuery pivotQuery, S context) {
88+
return null;
89+
}
90+
91+
default void visit(PivotQuery pivotQuery) {
92+
this.visit(pivotQuery, null);
93+
}
94+
8895
<S> T visit(SetOperationList setOperationList, S context);
8996

9097
default void visit(SetOperationList setOperationList) {

src/main/java/net/sf/jsqlparser/statement/select/FromItemVisitorAdapter.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
*/
1010
package net.sf.jsqlparser.statement.select;
1111

12+
import java.util.ArrayList;
13+
import java.util.Collection;
1214
import net.sf.jsqlparser.expression.Expression;
1315
import net.sf.jsqlparser.expression.ExpressionVisitor;
1416
import net.sf.jsqlparser.expression.ExpressionVisitorAdapter;
@@ -17,9 +19,6 @@
1719
import net.sf.jsqlparser.statement.imprt.Import;
1820
import net.sf.jsqlparser.statement.piped.FromQuery;
1921

20-
import java.util.ArrayList;
21-
import java.util.Collection;
22-
2322
@SuppressWarnings({"PMD.UncommentedEmptyMethodBody"})
2423
public class FromItemVisitorAdapter<T> implements FromItemVisitor<T> {
2524
private SelectVisitor<T> selectVisitor;
@@ -125,6 +124,11 @@ public <S> T visit(PlainSelect plainSelect, S context) {
125124
return plainSelect.accept(selectVisitor, context);
126125
}
127126

127+
@Override
128+
public <S> T visit(PivotQuery pivotQuery, S context) {
129+
return pivotQuery.accept(selectVisitor, context);
130+
}
131+
128132
@Override
129133
public <S> T visit(SetOperationList setOperationList, S context) {
130134
ArrayList<T> results = new ArrayList<>();
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2026 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.statement.select;
11+
12+
import java.util.ArrayList;
13+
import java.util.Arrays;
14+
import java.util.Collection;
15+
import java.util.Collections;
16+
import java.util.List;
17+
import java.util.Optional;
18+
import net.sf.jsqlparser.expression.Expression;
19+
import net.sf.jsqlparser.expression.Function;
20+
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
21+
22+
/**
23+
* DuckDB's simplified {@code PIVOT} query.
24+
*
25+
* <p>
26+
* Unlike {@link Pivot}, which is a clause attached to a {@link FromItem}, this construct is a query
27+
* in its own right and can therefore be used as a statement, CTE, or parenthesized subquery.
28+
*
29+
* @see <a href="https://duckdb.org/docs/sql/statements/pivot.html">DuckDB PIVOT</a>
30+
*/
31+
public class PivotQuery extends Select {
32+
33+
private FromItem fromItem;
34+
private ExpressionList<Expression> onExpressions;
35+
private List<SelectItem<Function>> usingItems;
36+
private ExpressionList<Expression> groupByExpressions;
37+
38+
public FromItem getFromItem() {
39+
return fromItem;
40+
}
41+
42+
public void setFromItem(FromItem fromItem) {
43+
this.fromItem = fromItem;
44+
}
45+
46+
public PivotQuery withFromItem(FromItem fromItem) {
47+
setFromItem(fromItem);
48+
return this;
49+
}
50+
51+
public ExpressionList<Expression> getOnExpressions() {
52+
return onExpressions;
53+
}
54+
55+
public void setOnExpressions(ExpressionList<Expression> onExpressions) {
56+
this.onExpressions = onExpressions;
57+
}
58+
59+
public PivotQuery withOnExpressions(ExpressionList<Expression> onExpressions) {
60+
setOnExpressions(onExpressions);
61+
return this;
62+
}
63+
64+
public PivotQuery addOnExpressions(Expression... expressions) {
65+
return addOnExpressions(Arrays.asList(expressions));
66+
}
67+
68+
public PivotQuery addOnExpressions(Collection<? extends Expression> expressions) {
69+
ExpressionList<Expression> collection =
70+
Optional.ofNullable(getOnExpressions()).orElseGet(ExpressionList::new);
71+
collection.addAll(expressions);
72+
return withOnExpressions(collection);
73+
}
74+
75+
public List<SelectItem<Function>> getUsingItems() {
76+
return usingItems;
77+
}
78+
79+
public void setUsingItems(List<SelectItem<Function>> usingItems) {
80+
this.usingItems = usingItems;
81+
}
82+
83+
public PivotQuery withUsingItems(List<SelectItem<Function>> usingItems) {
84+
setUsingItems(usingItems);
85+
return this;
86+
}
87+
88+
public PivotQuery addUsingItems(SelectItem<Function>... usingItems) {
89+
List<SelectItem<Function>> collection =
90+
Optional.ofNullable(getUsingItems()).orElseGet(ArrayList::new);
91+
Collections.addAll(collection, usingItems);
92+
return withUsingItems(collection);
93+
}
94+
95+
public PivotQuery addUsingItems(Collection<? extends SelectItem<Function>> usingItems) {
96+
List<SelectItem<Function>> collection =
97+
Optional.ofNullable(getUsingItems()).orElseGet(ArrayList::new);
98+
collection.addAll(usingItems);
99+
return withUsingItems(collection);
100+
}
101+
102+
public ExpressionList<Expression> getGroupByExpressions() {
103+
return groupByExpressions;
104+
}
105+
106+
public void setGroupByExpressions(ExpressionList<Expression> groupByExpressions) {
107+
this.groupByExpressions = groupByExpressions;
108+
}
109+
110+
public PivotQuery withGroupByExpressions(ExpressionList<Expression> groupByExpressions) {
111+
setGroupByExpressions(groupByExpressions);
112+
return this;
113+
}
114+
115+
public PivotQuery addGroupByExpressions(Expression... expressions) {
116+
return addGroupByExpressions(Arrays.asList(expressions));
117+
}
118+
119+
public PivotQuery addGroupByExpressions(Collection<? extends Expression> expressions) {
120+
ExpressionList<Expression> collection =
121+
Optional.ofNullable(getGroupByExpressions()).orElseGet(ExpressionList::new);
122+
collection.addAll(expressions);
123+
return withGroupByExpressions(collection);
124+
}
125+
126+
@Override
127+
public StringBuilder appendSelectBodyTo(StringBuilder builder) {
128+
builder.append("PIVOT ").append(fromItem);
129+
if (onExpressions != null) {
130+
builder.append(" ON ").append(onExpressions);
131+
}
132+
if (usingItems != null) {
133+
builder.append(" USING ").append(Select.getStringList(usingItems));
134+
}
135+
if (groupByExpressions != null) {
136+
builder.append(" GROUP BY ").append(groupByExpressions);
137+
}
138+
return builder;
139+
}
140+
141+
@Override
142+
public <T, S> T accept(SelectVisitor<T> selectVisitor, S context) {
143+
return selectVisitor.visit(this, context);
144+
}
145+
146+
@Override
147+
public <T, S> T accept(FromItemVisitor<T> fromItemVisitor, S context) {
148+
return fromItemVisitor.visit(this, context);
149+
}
150+
151+
@Override
152+
public SampleClause getSampleClause() {
153+
return null;
154+
}
155+
156+
@Override
157+
public FromItem setSampleClause(SampleClause sampleClause) {
158+
return null;
159+
}
160+
}

src/main/java/net/sf/jsqlparser/statement/select/SelectVisitor.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,10 @@
99
*/
1010
package net.sf.jsqlparser.statement.select;
1111

12+
import java.util.List;
1213
import net.sf.jsqlparser.statement.OutputClause;
1314
import net.sf.jsqlparser.statement.piped.FromQuery;
1415

15-
import java.util.List;
16-
1716
public interface SelectVisitor<T> {
1817
default <S> T visitWithItems(List<WithItem<?>> withItemsList, S context) {
1918
if (withItemsList != null) {
@@ -40,6 +39,14 @@ default void visit(PlainSelect plainSelect) {
4039
this.visit(plainSelect, null);
4140
}
4241

42+
default <S> T visit(PivotQuery pivotQuery, S context) {
43+
return null;
44+
}
45+
46+
default void visit(PivotQuery pivotQuery) {
47+
this.visit(pivotQuery, null);
48+
}
49+
4350
<S> T visit(FromQuery fromQuery, S context);
4451

4552
<S> T visit(SetOperationList setOpList, S context);

0 commit comments

Comments
 (0)