Skip to content

Commit 1315d03

Browse files
committed
fixes #401
1 parent 6fb15a1 commit 1315d03

File tree

3 files changed

+140
-93
lines changed

3 files changed

+140
-93
lines changed

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

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public class Merge implements Statement {
4242
private Expression onCondition;
4343
private MergeInsert mergeInsert;
4444
private MergeUpdate mergeUpdate;
45+
private boolean insertFirst = false;
4546

4647
public Table getTable() {
4748
return table;
@@ -107,6 +108,14 @@ public void accept(StatementVisitor statementVisitor) {
107108
statementVisitor.visit(this);
108109
}
109110

111+
public boolean isInsertFirst() {
112+
return insertFirst;
113+
}
114+
115+
public void setInsertFirst(boolean insertFirst) {
116+
this.insertFirst = insertFirst;
117+
}
118+
110119
@Override
111120
public String toString() {
112121
StringBuilder b = new StringBuilder();
@@ -118,20 +127,28 @@ public String toString() {
118127
} else if (usingSelect != null) {
119128
b.append("(").append(usingSelect.toString()).append(")");
120129
}
121-
130+
122131
if (usingAlias != null) {
123132
b.append(usingAlias.toString());
124133
}
125134
b.append(" ON (");
126135
b.append(onCondition);
127136
b.append(")");
128137

138+
if (insertFirst) {
139+
if (mergeInsert != null) {
140+
b.append(mergeInsert.toString());
141+
}
142+
}
143+
129144
if (mergeUpdate != null) {
130145
b.append(mergeUpdate.toString());
131146
}
132147

133-
if (mergeInsert != null) {
134-
b.append(mergeInsert.toString());
148+
if (!insertFirst) {
149+
if (mergeInsert != null) {
150+
b.append(mergeInsert.toString());
151+
}
135152
}
136153

137154
return b.toString();

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -684,9 +684,18 @@ Statement Merge() : {
684684
[ alias = Alias() { merge.setUsingAlias(alias); } ] <K_ON>
685685
"(" condition = Expression() { merge.setOnCondition(condition); } ")"
686686

687-
[ LOOKAHEAD(2) update = MergeUpdateClause() { merge.setMergeUpdate(update); } ]
687+
[
688+
( LOOKAHEAD(2) update = MergeUpdateClause() { merge.setMergeUpdate(update); }
689+
[ insert = MergeInsertClause() { merge.setMergeInsert(insert); } ]
690+
| insert = MergeInsertClause() { merge.setMergeInsert(insert); merge.setInsertFirst(true); }
691+
[ update = MergeUpdateClause() { merge.setMergeUpdate(update); } ]
692+
)
693+
]
694+
695+
696+
/*[ LOOKAHEAD(2) update = MergeUpdateClause() { merge.setMergeUpdate(update); } ]
688697

689-
[ insert = MergeInsertClause() { merge.setMergeInsert(insert); } ]
698+
[ insert = MergeInsertClause() { merge.setMergeInsert(insert); } ]*/
690699

691700
{ return merge; }
692701
}

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

Lines changed: 109 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import net.sf.jsqlparser.statement.Statement;
2424
import org.junit.Test;
2525
import static net.sf.jsqlparser.test.TestUtils.*;
26+
import static org.junit.Assert.fail;
2627

2728
/**
2829
*
@@ -45,108 +46,128 @@ public void testOracleMergeIntoStatement() throws JSQLParserException {
4546
+ " VALUES (E.employee_id, E.salary * 0.05) ";
4647

4748
Statement statement = CCJSqlParserUtil.parse(sql);
48-
49+
4950
System.out.println(statement.toString());
50-
51+
5152
assertSqlCanBeParsedAndDeparsed(sql, true);
5253
}
53-
54+
5455
@Test
5556
public void testMergeIssue232() throws JSQLParserException {
56-
String sql = "MERGE INTO xyz using dual " +
57-
"ON ( custom_id = ? ) " +
58-
"WHEN matched THEN " +
59-
"UPDATE SET abc = sysdate " +
60-
"WHEN NOT matched THEN " +
61-
"INSERT (custom_id) VALUES (?)";
62-
57+
String sql = "MERGE INTO xyz using dual "
58+
+ "ON ( custom_id = ? ) "
59+
+ "WHEN matched THEN "
60+
+ "UPDATE SET abc = sysdate "
61+
+ "WHEN NOT matched THEN "
62+
+ "INSERT (custom_id) VALUES (?)";
63+
6364
assertSqlCanBeParsedAndDeparsed(sql, true);
6465
}
6566

6667
@Test
6768
public void testComplexOracleMergeIntoStatement() throws JSQLParserException {
6869
String sql = "MERGE INTO DestinationValue Dest USING\n"
69-
+ "(SELECT TheMonth ,\n"
70-
+ " IdentifyingKey ,\n"
71-
+ " SUM(NetPrice) NetPrice ,\n"
72-
+ " SUM(NetDeductionPrice) NetDeductionPrice ,\n"
73-
+ " MAX(CASE RowNumberMain WHEN 1 THEN QualityIndicator ELSE NULL END) QualityIndicatorMain ,\n"
74-
+ " MAX(CASE RowNumberDeduction WHEN 1 THEN QualityIndicator ELSE NULL END) QualityIndicatorDeduction \n"
75-
+ "FROM\n"
76-
+ " (SELECT pd.TheMonth ,\n"
77-
+ " COALESCE(pd.IdentifyingKey, 0) IdentifyingKey ,\n"
78-
+ " COALESCE(CASE pd.IsDeduction WHEN 1 THEN NULL ELSE ConvertedCalculatedValue END, 0) NetPrice ,\n"
79-
+ " COALESCE(CASE pd.IsDeduction WHEN 1 THEN ConvertedCalculatedValue ELSE NULL END, 0) NetDeductionPrice ,\n"
80-
+ " pd.QualityIndicator ,\n"
81-
+ " row_number() OVER (PARTITION BY pd.TheMonth , pd.IdentifyingKey ORDER BY COALESCE(pd.QualityMonth, to_date('18991230', 'yyyymmdd')) DESC ) RowNumberMain ,\n"
82-
+ " NULL RowNumberDeduction\n"
83-
+ " FROM PricingData pd\n"
84-
+ " WHERE pd.ThingsKey IN (:ThingsKeys)\n"
85-
+ " AND pd.TheMonth >= :startdate\n"
86-
+ " AND pd.TheMonth <= :enddate\n"
87-
+ " AND pd.IsDeduction = 0\n"
88-
+ " UNION ALL\n"
89-
+ " SELECT pd.TheMonth ,\n"
90-
+ " COALESCE(pd.IdentifyingKey, 0) IdentifyingKey ,\n"
91-
+ " COALESCE(CASE pd.IsDeduction WHEN 1 THEN NULL ELSE ConvertedCalculatedValue END, 0) NetPrice ,\n"
92-
+ " COALESCE(CASE pd.IsDeduction WHEN 1 THEN ConvertedCalculatedValue ELSE NULL END, 0) NetDeductionPrice ,\n"
93-
+ " pd.QualityIndicator ,\n"
94-
+ " NULL RowNumberMain ,\n"
95-
+ " row_number() OVER (PARTITION BY pd.TheMonth , pd.IdentifyingKey ORDER BY COALESCE(pd.QualityMonth, to_date('18991230', 'yyyymmdd')) DESC ) RowNumberDeduction \n"
96-
+ " FROM PricingData pd\n"
97-
+ " WHERE pd.ThingsKey IN (:ThingsKeys)\n"
98-
+ " AND pd.TheMonth >= :startdate\n"
99-
+ " AND pd.TheMonth <= :enddate\n"
100-
+ " AND pd.IsDeduction <> 0\n"
101-
+ " )\n"
102-
+ "GROUP BY TheMonth ,\n"
103-
+ " IdentifyingKey\n"
104-
+ ") Data ON ( Dest.TheMonth = Data.TheMonth \n"
105-
+ " AND COALESCE(Dest.IdentifyingKey,0) = Data.IdentifyingKey )\n"
106-
+ "WHEN MATCHED THEN\n"
107-
+ " UPDATE\n"
108-
+ " SET NetPrice = ROUND(Data.NetPrice, PriceDecimalScale) ,\n"
109-
+ " DeductionPrice = ROUND(Data.NetDeductionPrice, PriceDecimalScale) ,\n"
110-
+ " SubTotalPrice = ROUND(Data.NetPrice + (Data.NetDeductionPrice * Dest.HasDeductions), PriceDecimalScale) ,\n"
111-
+ " QualityIndicator =\n"
112-
+ " CASE Dest.HasDeductions\n"
113-
+ " WHEN 0\n"
114-
+ " THEN Data.QualityIndicatorMain\n"
115-
+ " ELSE\n"
116-
+ " CASE\n"
117-
+ " WHEN COALESCE(Data.CheckMonth1, to_date('18991230', 'yyyymmdd'))> COALESCE(Data.CheckMonth2,to_date('18991230', 'yyyymmdd'))\n"
118-
+ " THEN Data.QualityIndicatorMain\n"
119-
+ " ELSE Data.QualityIndicatorDeduction\n"
120-
+ " END\n"
121-
+ " END ,\n"
122-
+ " RecUser = :recuser ,\n"
123-
+ " RecDate = :recdate\n"
124-
+ " WHERE 1 =1\n"
125-
+ " AND IsImportant = 1\n"
126-
+ " AND COALESCE(Data.SomeFlag,-1) <> COALESCE(ROUND(Something, 1),-1)\n"
127-
+ " DELETE WHERE\n"
128-
+ " IsImportant = 0\n"
129-
+ " OR COALESCE(Data.SomeFlag,-1) = COALESCE(ROUND(Something, 1),-1)\n"
130-
+ " WHEN NOT MATCHED THEN \n"
131-
+ " INSERT\n"
132-
+ " (\n"
133-
+ " TheMonth ,\n"
134-
+ " ThingsKey ,\n"
135-
+ " IsDeduction ,\n"
136-
+ " CreatedAt \n"
137-
+ " )\n"
138-
+ " VALUES\n"
139-
+ " (\n"
140-
+ " Data.TheMonth ,\n"
141-
+ " Data.ThingsKey ,\n"
142-
+ " Data.IsDeduction ,\n"
143-
+ " SYSDATE\n"
144-
+ " )\n";
70+
+ "(SELECT TheMonth ,\n"
71+
+ " IdentifyingKey ,\n"
72+
+ " SUM(NetPrice) NetPrice ,\n"
73+
+ " SUM(NetDeductionPrice) NetDeductionPrice ,\n"
74+
+ " MAX(CASE RowNumberMain WHEN 1 THEN QualityIndicator ELSE NULL END) QualityIndicatorMain ,\n"
75+
+ " MAX(CASE RowNumberDeduction WHEN 1 THEN QualityIndicator ELSE NULL END) QualityIndicatorDeduction \n"
76+
+ "FROM\n"
77+
+ " (SELECT pd.TheMonth ,\n"
78+
+ " COALESCE(pd.IdentifyingKey, 0) IdentifyingKey ,\n"
79+
+ " COALESCE(CASE pd.IsDeduction WHEN 1 THEN NULL ELSE ConvertedCalculatedValue END, 0) NetPrice ,\n"
80+
+ " COALESCE(CASE pd.IsDeduction WHEN 1 THEN ConvertedCalculatedValue ELSE NULL END, 0) NetDeductionPrice ,\n"
81+
+ " pd.QualityIndicator ,\n"
82+
+ " row_number() OVER (PARTITION BY pd.TheMonth , pd.IdentifyingKey ORDER BY COALESCE(pd.QualityMonth, to_date('18991230', 'yyyymmdd')) DESC ) RowNumberMain ,\n"
83+
+ " NULL RowNumberDeduction\n"
84+
+ " FROM PricingData pd\n"
85+
+ " WHERE pd.ThingsKey IN (:ThingsKeys)\n"
86+
+ " AND pd.TheMonth >= :startdate\n"
87+
+ " AND pd.TheMonth <= :enddate\n"
88+
+ " AND pd.IsDeduction = 0\n"
89+
+ " UNION ALL\n"
90+
+ " SELECT pd.TheMonth ,\n"
91+
+ " COALESCE(pd.IdentifyingKey, 0) IdentifyingKey ,\n"
92+
+ " COALESCE(CASE pd.IsDeduction WHEN 1 THEN NULL ELSE ConvertedCalculatedValue END, 0) NetPrice ,\n"
93+
+ " COALESCE(CASE pd.IsDeduction WHEN 1 THEN ConvertedCalculatedValue ELSE NULL END, 0) NetDeductionPrice ,\n"
94+
+ " pd.QualityIndicator ,\n"
95+
+ " NULL RowNumberMain ,\n"
96+
+ " row_number() OVER (PARTITION BY pd.TheMonth , pd.IdentifyingKey ORDER BY COALESCE(pd.QualityMonth, to_date('18991230', 'yyyymmdd')) DESC ) RowNumberDeduction \n"
97+
+ " FROM PricingData pd\n"
98+
+ " WHERE pd.ThingsKey IN (:ThingsKeys)\n"
99+
+ " AND pd.TheMonth >= :startdate\n"
100+
+ " AND pd.TheMonth <= :enddate\n"
101+
+ " AND pd.IsDeduction <> 0\n"
102+
+ " )\n"
103+
+ "GROUP BY TheMonth ,\n"
104+
+ " IdentifyingKey\n"
105+
+ ") Data ON ( Dest.TheMonth = Data.TheMonth \n"
106+
+ " AND COALESCE(Dest.IdentifyingKey,0) = Data.IdentifyingKey )\n"
107+
+ "WHEN MATCHED THEN\n"
108+
+ " UPDATE\n"
109+
+ " SET NetPrice = ROUND(Data.NetPrice, PriceDecimalScale) ,\n"
110+
+ " DeductionPrice = ROUND(Data.NetDeductionPrice, PriceDecimalScale) ,\n"
111+
+ " SubTotalPrice = ROUND(Data.NetPrice + (Data.NetDeductionPrice * Dest.HasDeductions), PriceDecimalScale) ,\n"
112+
+ " QualityIndicator =\n"
113+
+ " CASE Dest.HasDeductions\n"
114+
+ " WHEN 0\n"
115+
+ " THEN Data.QualityIndicatorMain\n"
116+
+ " ELSE\n"
117+
+ " CASE\n"
118+
+ " WHEN COALESCE(Data.CheckMonth1, to_date('18991230', 'yyyymmdd'))> COALESCE(Data.CheckMonth2,to_date('18991230', 'yyyymmdd'))\n"
119+
+ " THEN Data.QualityIndicatorMain\n"
120+
+ " ELSE Data.QualityIndicatorDeduction\n"
121+
+ " END\n"
122+
+ " END ,\n"
123+
+ " RecUser = :recuser ,\n"
124+
+ " RecDate = :recdate\n"
125+
+ " WHERE 1 =1\n"
126+
+ " AND IsImportant = 1\n"
127+
+ " AND COALESCE(Data.SomeFlag,-1) <> COALESCE(ROUND(Something, 1),-1)\n"
128+
+ " DELETE WHERE\n"
129+
+ " IsImportant = 0\n"
130+
+ " OR COALESCE(Data.SomeFlag,-1) = COALESCE(ROUND(Something, 1),-1)\n"
131+
+ " WHEN NOT MATCHED THEN \n"
132+
+ " INSERT\n"
133+
+ " (\n"
134+
+ " TheMonth ,\n"
135+
+ " ThingsKey ,\n"
136+
+ " IsDeduction ,\n"
137+
+ " CreatedAt \n"
138+
+ " )\n"
139+
+ " VALUES\n"
140+
+ " (\n"
141+
+ " Data.TheMonth ,\n"
142+
+ " Data.ThingsKey ,\n"
143+
+ " Data.IsDeduction ,\n"
144+
+ " SYSDATE\n"
145+
+ " )\n";
145146

146147
Statement statement = CCJSqlParserUtil.parse(sql);
147-
148+
148149
System.out.println(statement.toString());
149-
150+
150151
assertSqlCanBeParsedAndDeparsed(sql, true);
151152
}
153+
154+
@Test
155+
public void testMergeUpdateInsertOrderIssue401() throws JSQLParserException {
156+
assertSqlCanBeParsedAndDeparsed("MERGE INTO a USING dual ON (col3 = ? AND col1 = ? AND col2 = ?) WHEN NOT MATCHED THEN INSERT (col1, col2, col3, col4) VALUES (?, ?, ?, ?) WHEN MATCHED THEN UPDATE SET col4 = col4 + ?");
157+
}
158+
159+
@Test
160+
public void testMergeUpdateInsertOrderIssue401_2() throws JSQLParserException {
161+
assertSqlCanBeParsedAndDeparsed("MERGE INTO a USING dual ON (col3 = ? AND col1 = ? AND col2 = ?) WHEN MATCHED THEN UPDATE SET col4 = col4 + ? WHEN NOT MATCHED THEN INSERT (col1, col2, col3, col4) VALUES (?, ?, ?, ?)");
162+
}
163+
164+
@Test
165+
public void testMergeUpdateInsertOrderIssue401_3() throws JSQLParserException {
166+
try {
167+
assertSqlCanBeParsedAndDeparsed("MERGE INTO a USING dual ON (col3 = ? AND col1 = ? AND col2 = ?) WHEN MATCHED THEN UPDATE SET col4 = col4 + ? WHEN NOT MATCHED THEN INSERT (col1, col2, col3, col4) VALUES (?, ?, ?, ?) WHEN MATCHED THEN UPDATE SET col4 = col4 + ?");
168+
fail("syntaxerror parsed");
169+
} catch (JSQLParserException ex) {
170+
171+
}
172+
}
152173
}

0 commit comments

Comments
 (0)