From 0ae84affd0f2b4c6ae0a0be195eaabd971ffd319 Mon Sep 17 00:00:00 2001 From: David Goss Date: Tue, 7 Nov 2023 15:10:27 +0000 Subject: [PATCH 1/3] 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. --- .../java/net/sf/jsqlparser/statement/merge/Merge.java | 3 +-- .../sf/jsqlparser/util/deparser/StatementDeParser.java | 3 +-- .../jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt | 2 +- .../net/sf/jsqlparser/statement/merge/MergeTest.java | 9 +++++++++ 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/sf/jsqlparser/statement/merge/Merge.java b/src/main/java/net/sf/jsqlparser/statement/merge/Merge.java index b658e0b9f..8d69bbce4 100644 --- a/src/main/java/net/sf/jsqlparser/statement/merge/Merge.java +++ b/src/main/java/net/sf/jsqlparser/statement/merge/Merge.java @@ -186,9 +186,8 @@ public String toString() { b.append(table); b.append(" USING "); b.append(fromItem); - b.append(" ON ("); + b.append(" ON "); b.append(onCondition); - b.append(")"); if (insertFirst && mergeInsert != null) { b.append(mergeInsert); diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java index 55b11a781..08e5049e7 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java @@ -216,9 +216,8 @@ public void visit(Merge merge) { buffer.append(" USING "); merge.getFromItem().accept(selectDeParser); - buffer.append(" ON ("); + buffer.append(" ON "); merge.getOnCondition().accept(expressionDeParser); - buffer.append(")"); MergeInsert mergeInsert = merge.getMergeInsert(); MergeUpdate mergeUpdate = merge.getMergeUpdate(); diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 7029df760..c34d2f458 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -1659,7 +1659,7 @@ Statement Merge( List with ) : { { { merge.setOracleHint(getOracleHint()); } table=TableWithAlias() { merge.setTable(table); } fromItem = FromItem() { merge.setFromItem(fromItem); } - "(" condition = Expression() { merge.setOnCondition(condition); } ")" + condition = Expression() { merge.setOnCondition(condition); } [ ( LOOKAHEAD(2) update = MergeUpdateClause() { merge.setMergeUpdate(update); } diff --git a/src/test/java/net/sf/jsqlparser/statement/merge/MergeTest.java b/src/test/java/net/sf/jsqlparser/statement/merge/MergeTest.java index 287b8b922..41cd1c7d5 100644 --- a/src/test/java/net/sf/jsqlparser/statement/merge/MergeTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/merge/MergeTest.java @@ -236,4 +236,13 @@ public void testOutputClause() throws JSQLParserException { + " TAB_MergeActions_RoomLocation"; assertSqlCanBeParsedAndDeparsed(sqlStr, true); } + + @Test + public void testSnowflakeMergeStatement() throws JSQLParserException { + String sql = "MERGE INTO target\n" + + " USING src ON target.k = src.k\n" + + " WHEN MATCHED THEN UPDATE SET target.v = src.v"; + + assertSqlCanBeParsedAndDeparsed(sql, true); + } } From 10457b67c378b5db7701533ebd6f2b72c65c840b Mon Sep 17 00:00:00 2001 From: David Goss Date: Wed, 8 Nov 2023 09:53:54 +0000 Subject: [PATCH 2/3] feat: add support for and predicate in merge update --- .../statement/merge/MergeUpdate.java | 27 ++++++++++++++++++- .../util/deparser/StatementDeParser.java | 7 ++++- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 9 ++++--- .../jsqlparser/statement/merge/MergeTest.java | 11 +++++++- 4 files changed, 48 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/sf/jsqlparser/statement/merge/MergeUpdate.java b/src/main/java/net/sf/jsqlparser/statement/merge/MergeUpdate.java index e69c595f9..10df947e7 100644 --- a/src/main/java/net/sf/jsqlparser/statement/merge/MergeUpdate.java +++ b/src/main/java/net/sf/jsqlparser/statement/merge/MergeUpdate.java @@ -18,9 +18,13 @@ public class MergeUpdate implements Serializable { private List updateSets; + private Expression andPredicate; private Expression whereCondition; private Expression deleteWhereCondition; + public MergeUpdate() { + } + public MergeUpdate(List updateSets) { this.updateSets = updateSets; } @@ -34,6 +38,14 @@ public MergeUpdate setUpdateSets(List updateSets) { return this; } + public Expression getAndPredicate() { + return andPredicate; + } + + public void setAndPredicate(Expression andPredicate) { + this.andPredicate = andPredicate; + } + public Expression getWhereCondition() { return whereCondition; } @@ -53,7 +65,11 @@ public void setDeleteWhereCondition(Expression deleteWhereCondition) { @Override public String toString() { StringBuilder b = new StringBuilder(); - b.append(" WHEN MATCHED THEN UPDATE SET "); + b.append(" WHEN MATCHED"); + if (andPredicate != null) { + b.append(" AND ").append(andPredicate.toString()); + } + b.append(" THEN UPDATE SET "); UpdateSet.appendUpdateSetsTo(b, updateSets); if (whereCondition != null) { @@ -65,6 +81,11 @@ public String toString() { return b.toString(); } + public MergeUpdate withAndPredicate(Expression andPredicate) { + this.setAndPredicate(andPredicate); + return this; + } + public MergeUpdate withWhereCondition(Expression whereCondition) { this.setWhereCondition(whereCondition); return this; @@ -75,6 +96,10 @@ public MergeUpdate withDeleteWhereCondition(Expression deleteWhereCondition) { return this; } + public E getAndPredicate(Class type) { + return type.cast(getAndPredicate()); +} + public E getWhereCondition(Class type) { return type.cast(getWhereCondition()); } diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java index 08e5049e7..93a2af08c 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java @@ -226,7 +226,12 @@ public void visit(Merge merge) { } if (mergeUpdate != null) { - buffer.append(" WHEN MATCHED THEN UPDATE SET "); + buffer.append(" WHEN MATCHED"); + if (mergeUpdate.getAndPredicate() != null) { + buffer.append(" AND "); + mergeUpdate.getAndPredicate().accept(expressionDeParser); + } + buffer.append(" THEN UPDATE SET "); deparseUpdateSets(mergeUpdate.getUpdateSets(), buffer, expressionDeParser); if (mergeUpdate.getWhereCondition() != null) { diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index c34d2f458..91d49c82b 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -1675,14 +1675,17 @@ Statement Merge( List with ) : { } MergeUpdate MergeUpdateClause() : { - MergeUpdate mu; + MergeUpdate mu = new MergeUpdate(); List updateSets; + Expression predicate; Expression condition; } { - + + [ predicate = Expression() { mu.setAndPredicate(predicate); } ] + - updateSets = UpdateSets() { mu = new MergeUpdate(updateSets); } + updateSets = UpdateSets() { mu.setUpdateSets(updateSets); } [ condition = Expression() { mu.setWhereCondition(condition); }] [ condition = Expression() { mu.setDeleteWhereCondition(condition); } ] diff --git a/src/test/java/net/sf/jsqlparser/statement/merge/MergeTest.java b/src/test/java/net/sf/jsqlparser/statement/merge/MergeTest.java index 41cd1c7d5..94ff0557d 100644 --- a/src/test/java/net/sf/jsqlparser/statement/merge/MergeTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/merge/MergeTest.java @@ -238,11 +238,20 @@ public void testOutputClause() throws JSQLParserException { } @Test - public void testSnowflakeMergeStatement() throws JSQLParserException { + public void testSnowflakeMergeStatementSimple() throws JSQLParserException { String sql = "MERGE INTO target\n" + " USING src ON target.k = src.k\n" + " WHEN MATCHED THEN UPDATE SET target.v = src.v"; assertSqlCanBeParsedAndDeparsed(sql, true); } + + @Test + public void testSnowflakeMergeStatementWithMatchedAndPredicate() throws JSQLParserException { + String sql = "MERGE INTO target\n" + + " USING src ON target.k = src.k\n" + + " WHEN MATCHED AND src.v = 11 THEN UPDATE SET target.v = src.v"; + + assertSqlCanBeParsedAndDeparsed(sql, true); + } } From 93b393013be7893ce3f0428bb67e9929ec6467d3 Mon Sep 17 00:00:00 2001 From: David Goss Date: Wed, 8 Nov 2023 15:10:21 +0000 Subject: [PATCH 3/3] feat: add support for and predicate in merge insert --- .../statement/merge/MergeInsert.java | 38 ++++++++++++++++--- .../util/deparser/StatementDeParser.java | 7 +++- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 5 ++- .../jsqlparser/statement/merge/MergeTest.java | 9 +++++ 4 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/sf/jsqlparser/statement/merge/MergeInsert.java b/src/main/java/net/sf/jsqlparser/statement/merge/MergeInsert.java index bc9ea695a..f6affa3ef 100644 --- a/src/main/java/net/sf/jsqlparser/statement/merge/MergeInsert.java +++ b/src/main/java/net/sf/jsqlparser/statement/merge/MergeInsert.java @@ -20,10 +20,19 @@ public class MergeInsert implements Serializable { + private Expression andPredicate; private ExpressionList columns; private ExpressionList values; private Expression whereCondition; + public Expression getAndPredicate() { + return andPredicate; + } + + public void setAndPredicate(Expression andPredicate) { + this.andPredicate = andPredicate; + } + public ExpressionList getColumns() { return columns; } @@ -50,12 +59,25 @@ public void setWhereCondition(Expression whereCondition) { @Override public String toString() { - return " WHEN NOT MATCHED THEN INSERT " - + (columns != null ? columns.toString() : "") - + " VALUES " + values.toString() - + (whereCondition != null - ? " WHERE " + whereCondition - : ""); + StringBuilder b = new StringBuilder(); + b.append(" WHEN NOT MATCHED"); + if (andPredicate != null) { + b.append(" AND ").append(andPredicate.toString()); + } + b.append(" THEN INSERT "); + if (columns != null) { + b.append(columns.toString()); + } + b.append(" VALUES ").append(values.toString()); + if (whereCondition != null) { + b.append(" WHERE ").append(whereCondition.toString()); + } + return b.toString(); + } + + public MergeInsert withAndPredicate(Expression andPredicate) { + this.setAndPredicate(andPredicate); + return this; } public MergeInsert withColumns(ExpressionList columns) { @@ -95,6 +117,10 @@ public MergeInsert withWhereCondition(Expression whereCondition) { return this; } + public E getAndPredicate(Class type) { + return type.cast(getAndPredicate()); + } + public E getWhereCondition(Class type) { return type.cast(getWhereCondition()); } diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java index 93a2af08c..732ef3e39 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java @@ -255,7 +255,12 @@ public void visit(Merge merge) { } private void deparseMergeInsert(MergeInsert mergeInsert) { - buffer.append(" WHEN NOT MATCHED THEN INSERT "); + buffer.append(" WHEN NOT MATCHED"); + if (mergeInsert.getAndPredicate() != null) { + buffer.append(" AND "); + mergeInsert.getAndPredicate().accept(expressionDeParser); + } + buffer.append(" THEN INSERT "); if (mergeInsert.getColumns() != null) { mergeInsert.getColumns().accept(expressionDeParser); } diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 91d49c82b..7905024a5 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -1695,12 +1695,15 @@ MergeUpdate MergeUpdateClause() : { MergeInsert MergeInsertClause() : { MergeInsert mi = new MergeInsert(); + Expression predicate; ExpressionList columns; ExpressionList expList; Expression condition; } { - + + [ predicate = Expression() { mi.setAndPredicate(predicate); } ] + [ "(" columns = ColumnList() ")" { diff --git a/src/test/java/net/sf/jsqlparser/statement/merge/MergeTest.java b/src/test/java/net/sf/jsqlparser/statement/merge/MergeTest.java index 94ff0557d..5034be8cd 100644 --- a/src/test/java/net/sf/jsqlparser/statement/merge/MergeTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/merge/MergeTest.java @@ -254,4 +254,13 @@ public void testSnowflakeMergeStatementWithMatchedAndPredicate() throws JSQLPars assertSqlCanBeParsedAndDeparsed(sql, true); } + + @Test + void testSnowflakeMergeStatementWithNotMatchedAndPredicate() throws JSQLParserException { + 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" + + " WHEN MATCHED THEN UPDATE SET target.v = b.v\n" + + " WHEN NOT MATCHED AND b.v != 11 THEN INSERT (k, v) VALUES (b.k, b.v)"; + + assertSqlCanBeParsedAndDeparsed(sql, true); + } }