Skip to content

Commit 36b806d

Browse files
authored
feat: add support for snowflake merge statements (#1887)
* feat: support snowflake merge statements Adds support for the ON clause of a MERGE statement to be without enclosing parens, as is the convention in Snowflake. * feat: add support for and predicate in merge update * feat: add support for and predicate in merge insert
1 parent 97e9229 commit 36b806d

File tree

6 files changed

+110
-18
lines changed

6 files changed

+110
-18
lines changed

src/main/java/net/sf/jsqlparser/statement/merge/Merge.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,9 +186,8 @@ public String toString() {
186186
b.append(table);
187187
b.append(" USING ");
188188
b.append(fromItem);
189-
b.append(" ON (");
189+
b.append(" ON ");
190190
b.append(onCondition);
191-
b.append(")");
192191

193192
if (insertFirst && mergeInsert != null) {
194193
b.append(mergeInsert);

src/main/java/net/sf/jsqlparser/statement/merge/MergeInsert.java

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,19 @@
2020

2121
public class MergeInsert implements Serializable {
2222

23+
private Expression andPredicate;
2324
private ExpressionList<Column> columns;
2425
private ExpressionList<Expression> values;
2526
private Expression whereCondition;
2627

28+
public Expression getAndPredicate() {
29+
return andPredicate;
30+
}
31+
32+
public void setAndPredicate(Expression andPredicate) {
33+
this.andPredicate = andPredicate;
34+
}
35+
2736
public ExpressionList<Column> getColumns() {
2837
return columns;
2938
}
@@ -50,12 +59,25 @@ public void setWhereCondition(Expression whereCondition) {
5059

5160
@Override
5261
public String toString() {
53-
return " WHEN NOT MATCHED THEN INSERT "
54-
+ (columns != null ? columns.toString() : "")
55-
+ " VALUES " + values.toString()
56-
+ (whereCondition != null
57-
? " WHERE " + whereCondition
58-
: "");
62+
StringBuilder b = new StringBuilder();
63+
b.append(" WHEN NOT MATCHED");
64+
if (andPredicate != null) {
65+
b.append(" AND ").append(andPredicate.toString());
66+
}
67+
b.append(" THEN INSERT ");
68+
if (columns != null) {
69+
b.append(columns.toString());
70+
}
71+
b.append(" VALUES ").append(values.toString());
72+
if (whereCondition != null) {
73+
b.append(" WHERE ").append(whereCondition.toString());
74+
}
75+
return b.toString();
76+
}
77+
78+
public MergeInsert withAndPredicate(Expression andPredicate) {
79+
this.setAndPredicate(andPredicate);
80+
return this;
5981
}
6082

6183
public MergeInsert withColumns(ExpressionList<Column> columns) {
@@ -95,6 +117,10 @@ public MergeInsert withWhereCondition(Expression whereCondition) {
95117
return this;
96118
}
97119

120+
public <E extends Expression> E getAndPredicate(Class<E> type) {
121+
return type.cast(getAndPredicate());
122+
}
123+
98124
public <E extends Expression> E getWhereCondition(Class<E> type) {
99125
return type.cast(getWhereCondition());
100126
}

src/main/java/net/sf/jsqlparser/statement/merge/MergeUpdate.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,13 @@
1818
public class MergeUpdate implements Serializable {
1919

2020
private List<UpdateSet> updateSets;
21+
private Expression andPredicate;
2122
private Expression whereCondition;
2223
private Expression deleteWhereCondition;
2324

25+
public MergeUpdate() {
26+
}
27+
2428
public MergeUpdate(List<UpdateSet> updateSets) {
2529
this.updateSets = updateSets;
2630
}
@@ -34,6 +38,14 @@ public MergeUpdate setUpdateSets(List<UpdateSet> updateSets) {
3438
return this;
3539
}
3640

41+
public Expression getAndPredicate() {
42+
return andPredicate;
43+
}
44+
45+
public void setAndPredicate(Expression andPredicate) {
46+
this.andPredicate = andPredicate;
47+
}
48+
3749
public Expression getWhereCondition() {
3850
return whereCondition;
3951
}
@@ -53,7 +65,11 @@ public void setDeleteWhereCondition(Expression deleteWhereCondition) {
5365
@Override
5466
public String toString() {
5567
StringBuilder b = new StringBuilder();
56-
b.append(" WHEN MATCHED THEN UPDATE SET ");
68+
b.append(" WHEN MATCHED");
69+
if (andPredicate != null) {
70+
b.append(" AND ").append(andPredicate.toString());
71+
}
72+
b.append(" THEN UPDATE SET ");
5773
UpdateSet.appendUpdateSetsTo(b, updateSets);
5874

5975
if (whereCondition != null) {
@@ -65,6 +81,11 @@ public String toString() {
6581
return b.toString();
6682
}
6783

84+
public MergeUpdate withAndPredicate(Expression andPredicate) {
85+
this.setAndPredicate(andPredicate);
86+
return this;
87+
}
88+
6889
public MergeUpdate withWhereCondition(Expression whereCondition) {
6990
this.setWhereCondition(whereCondition);
7091
return this;
@@ -75,6 +96,10 @@ public MergeUpdate withDeleteWhereCondition(Expression deleteWhereCondition) {
7596
return this;
7697
}
7798

99+
public <E extends Expression> E getAndPredicate(Class<E> type) {
100+
return type.cast(getAndPredicate());
101+
}
102+
78103
public <E extends Expression> E getWhereCondition(Class<E> type) {
79104
return type.cast(getWhereCondition());
80105
}

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

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -216,9 +216,8 @@ public void visit(Merge merge) {
216216
buffer.append(" USING ");
217217
merge.getFromItem().accept(selectDeParser);
218218

219-
buffer.append(" ON (");
219+
buffer.append(" ON ");
220220
merge.getOnCondition().accept(expressionDeParser);
221-
buffer.append(")");
222221

223222
MergeInsert mergeInsert = merge.getMergeInsert();
224223
MergeUpdate mergeUpdate = merge.getMergeUpdate();
@@ -227,7 +226,12 @@ public void visit(Merge merge) {
227226
}
228227

229228
if (mergeUpdate != null) {
230-
buffer.append(" WHEN MATCHED THEN UPDATE SET ");
229+
buffer.append(" WHEN MATCHED");
230+
if (mergeUpdate.getAndPredicate() != null) {
231+
buffer.append(" AND ");
232+
mergeUpdate.getAndPredicate().accept(expressionDeParser);
233+
}
234+
buffer.append(" THEN UPDATE SET ");
231235
deparseUpdateSets(mergeUpdate.getUpdateSets(), buffer, expressionDeParser);
232236

233237
if (mergeUpdate.getWhereCondition() != null) {
@@ -251,7 +255,12 @@ public void visit(Merge merge) {
251255
}
252256

253257
private void deparseMergeInsert(MergeInsert mergeInsert) {
254-
buffer.append(" WHEN NOT MATCHED THEN INSERT ");
258+
buffer.append(" WHEN NOT MATCHED");
259+
if (mergeInsert.getAndPredicate() != null) {
260+
buffer.append(" AND ");
261+
mergeInsert.getAndPredicate().accept(expressionDeParser);
262+
}
263+
buffer.append(" THEN INSERT ");
255264
if (mergeInsert.getColumns() != null) {
256265
mergeInsert.getColumns().accept(expressionDeParser);
257266
}

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

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1659,7 +1659,7 @@ Statement Merge( List<WithItem> with ) : {
16591659
{
16601660
<K_MERGE> { merge.setOracleHint(getOracleHint()); } <K_INTO> table=TableWithAlias() { merge.setTable(table); }
16611661
<K_USING> fromItem = FromItem() { merge.setFromItem(fromItem); }
1662-
<K_ON> "(" condition = Expression() { merge.setOnCondition(condition); } ")"
1662+
<K_ON> condition = Expression() { merge.setOnCondition(condition); }
16631663

16641664
[
16651665
( LOOKAHEAD(2) update = MergeUpdateClause() { merge.setMergeUpdate(update); }
@@ -1675,14 +1675,17 @@ Statement Merge( List<WithItem> with ) : {
16751675
}
16761676

16771677
MergeUpdate MergeUpdateClause() : {
1678-
MergeUpdate mu;
1678+
MergeUpdate mu = new MergeUpdate();
16791679
List<UpdateSet> updateSets;
1680+
Expression predicate;
16801681
Expression condition;
16811682
}
16821683
{
1683-
<K_WHEN> <K_MATCHED> <K_THEN> <K_UPDATE>
1684+
<K_WHEN> <K_MATCHED>
1685+
[ <K_AND> predicate = Expression() { mu.setAndPredicate(predicate); } ]
1686+
<K_THEN> <K_UPDATE>
16841687
<K_SET>
1685-
updateSets = UpdateSets() { mu = new MergeUpdate(updateSets); }
1688+
updateSets = UpdateSets() { mu.setUpdateSets(updateSets); }
16861689

16871690
[ <K_WHERE> condition = Expression() { mu.setWhereCondition(condition); }]
16881691
[ <K_DELETE> <K_WHERE> condition = Expression() { mu.setDeleteWhereCondition(condition); } ]
@@ -1692,12 +1695,15 @@ MergeUpdate MergeUpdateClause() : {
16921695

16931696
MergeInsert MergeInsertClause() : {
16941697
MergeInsert mi = new MergeInsert();
1698+
Expression predicate;
16951699
ExpressionList<Column> columns;
16961700
ExpressionList expList;
16971701
Expression condition;
16981702
}
16991703
{
1700-
<K_WHEN> <K_NOT> <K_MATCHED> <K_THEN>
1704+
<K_WHEN> <K_NOT> <K_MATCHED>
1705+
[ <K_AND> predicate = Expression() { mi.setAndPredicate(predicate); } ]
1706+
<K_THEN>
17011707
<K_INSERT>
17021708
[ "(" columns = ColumnList() ")"
17031709
{

src/test/java/net/sf/jsqlparser/statement/merge/MergeTest.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,4 +236,31 @@ public void testOutputClause() throws JSQLParserException {
236236
+ " TAB_MergeActions_RoomLocation";
237237
assertSqlCanBeParsedAndDeparsed(sqlStr, true);
238238
}
239+
240+
@Test
241+
public void testSnowflakeMergeStatementSimple() throws JSQLParserException {
242+
String sql = "MERGE INTO target\n" +
243+
" USING src ON target.k = src.k\n" +
244+
" WHEN MATCHED THEN UPDATE SET target.v = src.v";
245+
246+
assertSqlCanBeParsedAndDeparsed(sql, true);
247+
}
248+
249+
@Test
250+
public void testSnowflakeMergeStatementWithMatchedAndPredicate() throws JSQLParserException {
251+
String sql = "MERGE INTO target\n" +
252+
" USING src ON target.k = src.k\n" +
253+
" WHEN MATCHED AND src.v = 11 THEN UPDATE SET target.v = src.v";
254+
255+
assertSqlCanBeParsedAndDeparsed(sql, true);
256+
}
257+
258+
@Test
259+
void testSnowflakeMergeStatementWithNotMatchedAndPredicate() throws JSQLParserException {
260+
String sql = "MERGE INTO target USING (select k, max(v) as v from src group by k) AS b ON target.k = b.k\n" +
261+
" WHEN MATCHED THEN UPDATE SET target.v = b.v\n" +
262+
" WHEN NOT MATCHED AND b.v != 11 THEN INSERT (k, v) VALUES (b.k, b.v)";
263+
264+
assertSqlCanBeParsedAndDeparsed(sql, true);
265+
}
239266
}

0 commit comments

Comments
 (0)