Skip to content

Commit 8cfb7e3

Browse files
author
Richard Antal
committed
PHOENIX-7198 support for multi row constructors in single upsert query
1 parent 5466a0d commit 8cfb7e3

File tree

7 files changed

+347
-161
lines changed

7 files changed

+347
-161
lines changed

phoenix-core-client/src/main/antlr3/PhoenixSQL.g

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -865,9 +865,10 @@ finally{ contextStack.pop(); }
865865

866866
// Parse a full upsert expression structure.
867867
upsert_node returns [UpsertStatement ret]
868+
@init{List<List<ParseNode>> v = new ArrayList<List<ParseNode>>(); }
868869
: UPSERT (hint=hintClause)? INTO t=from_table_name
869870
(LPAREN p=upsert_column_refs RPAREN)?
870-
((VALUES LPAREN v=one_or_more_expressions RPAREN (
871+
((VALUES LPAREN e = one_or_more_expressions {v.add(e);} RPAREN (COMMA LPAREN e = one_or_more_expressions {v.add(e);} RPAREN )* (
871872
ON DUPLICATE KEY (
872873
ig=IGNORE
873874
| ( upd=UPDATE pairs=update_column_pairs )

phoenix-core-client/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java

Lines changed: 180 additions & 154 deletions
Large diffs are not rendered by default.

phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1181,7 +1181,7 @@ private static class ExecutableUpsertStatement extends UpsertStatement
11811181
implements CompilableStatement {
11821182

11831183
private ExecutableUpsertStatement(NamedTableNode table, HintNode hintNode,
1184-
List<ColumnName> columns, List<ParseNode> values, SelectStatement select, int bindCount,
1184+
List<ColumnName> columns, List<List<ParseNode>> values, SelectStatement select, int bindCount,
11851185
Map<String, UDFParseNode> udfParseNodes, List<Pair<ColumnName, ParseNode>> onDupKeyPairs,
11861186
OnDuplicateKeyType onDupKeyType, boolean returningRow) {
11871187
super(table, hintNode, columns, values, select, bindCount, udfParseNodes, onDupKeyPairs,
@@ -2126,7 +2126,7 @@ public ExecutableSelectStatement select(TableNode from, HintNode hint, boolean i
21262126

21272127
@Override
21282128
public ExecutableUpsertStatement upsert(NamedTableNode table, HintNode hintNode,
2129-
List<ColumnName> columns, List<ParseNode> values, SelectStatement select, int bindCount,
2129+
List<ColumnName> columns, List<List<ParseNode>> values, SelectStatement select, int bindCount,
21302130
Map<String, UDFParseNode> udfParseNodes, List<Pair<ColumnName, ParseNode>> onDupKeyPairs,
21312131
UpsertStatement.OnDuplicateKeyType onDupKeyType, boolean returningRow) {
21322132
return new ExecutableUpsertStatement(table, hintNode, columns, values, select, bindCount,

phoenix-core-client/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -898,7 +898,7 @@ public SelectStatement select(TableNode from, HintNode hint, boolean isDistinct,
898898
}
899899

900900
public UpsertStatement upsert(NamedTableNode table, HintNode hint, List<ColumnName> columns,
901-
List<ParseNode> values, SelectStatement select, int bindCount,
901+
List<List<ParseNode>> values, SelectStatement select, int bindCount,
902902
Map<String, UDFParseNode> udfParseNodes, List<Pair<ColumnName, ParseNode>> onDupKeyPairs,
903903
UpsertStatement.OnDuplicateKeyType onDupKeyType, boolean returningRow) {
904904
return new UpsertStatement(table, hint, columns, values, select, bindCount, udfParseNodes,

phoenix-core-client/src/main/java/org/apache/phoenix/parse/UpsertStatement.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,15 @@ public enum OnDuplicateKeyType {
3232
}
3333

3434
private final List<ColumnName> columns;
35-
private final List<ParseNode> values;
35+
private final List<List<ParseNode>> values;
3636
private final SelectStatement select;
3737
private final HintNode hint;
3838
private final List<Pair<ColumnName, ParseNode>> onDupKeyPairs;
3939
private final OnDuplicateKeyType onDupKeyType;
4040
private final boolean returningRow;
4141

4242
public UpsertStatement(NamedTableNode table, HintNode hint, List<ColumnName> columns,
43-
List<ParseNode> values, SelectStatement select, int bindCount,
43+
List<List<ParseNode>> values, SelectStatement select, int bindCount,
4444
Map<String, UDFParseNode> udfParseNodes, List<Pair<ColumnName, ParseNode>> onDupKeyPairs,
4545
OnDuplicateKeyType onDupKeyType, boolean returningRow) {
4646
super(table, bindCount, udfParseNodes);
@@ -57,7 +57,7 @@ public List<ColumnName> getColumns() {
5757
return columns;
5858
}
5959

60-
public List<ParseNode> getValues() {
60+
public List<List<ParseNode>> getValues() {
6161
return values;
6262
}
6363

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.phoenix.end2end;
19+
20+
import static org.junit.Assert.assertEquals;
21+
import static org.junit.Assert.assertFalse;
22+
import static org.junit.Assert.assertTrue;
23+
24+
import java.sql.Connection;
25+
import java.sql.DriverManager;
26+
import java.sql.ResultSet;
27+
import java.util.Properties;
28+
import org.junit.Test;
29+
import org.junit.experimental.categories.Category;
30+
31+
@Category(ParallelStatsDisabledTest.class)
32+
public class MultipleUpsertIT extends ParallelStatsDisabledIT {
33+
@Test
34+
public void testUpsertMultiple() throws Exception {
35+
Properties props = new Properties();
36+
Connection conn = DriverManager.getConnection(getUrl(), props);
37+
String tableName = generateUniqueName();
38+
String ddl =
39+
"CREATE TABLE " + tableName + "(K VARCHAR NOT NULL PRIMARY KEY, INT INTEGER, INT2 INTEGER)";
40+
conn.createStatement().execute(ddl);
41+
42+
conn.createStatement().execute("UPSERT INTO " + tableName + " VALUES ('A', 11, 12)");
43+
conn.createStatement().execute("UPSERT INTO " + tableName + "(K, INT) VALUES ('B', 2)");
44+
conn.createStatement()
45+
.execute("UPSERT INTO " + tableName + "(K, INT, INT2) VALUES ('E', 5, 5),('F', 61, 6)");
46+
conn.createStatement()
47+
.execute("UPSERT INTO " + tableName + " VALUES ('C', 31, 32),('D', 41, 42)");
48+
conn.createStatement().execute("UPSERT INTO " + tableName + " VALUES ('G', 7, 72),('H', 8)");
49+
conn.createStatement().execute("UPSERT INTO " + tableName + " VALUES ('I', 9),('I', 10)");
50+
conn.commit();
51+
52+
ResultSet rs = conn.createStatement().executeQuery("SELECT COUNT(*) FROM " + tableName);
53+
assertTrue(rs.next());
54+
assertEquals(9, rs.getInt(1));
55+
56+
rs = conn.createStatement().executeQuery("SELECT * FROM " + tableName + " ORDER BY K");
57+
rs.next();
58+
assertEquals(rs.getString(1), "A");
59+
assertEquals(rs.getInt(2), 11);
60+
assertEquals(rs.getInt(3), 12);
61+
rs.next();
62+
assertEquals(rs.getString(1), "B");
63+
assertEquals(rs.getInt(2), 2);
64+
rs.next();
65+
assertEquals(rs.getString(1), "C");
66+
assertEquals(rs.getInt(2), 31);
67+
assertEquals(rs.getInt(3), 32);
68+
rs.next();
69+
assertEquals(rs.getString(1), "D");
70+
assertEquals(rs.getInt(2), 41);
71+
assertEquals(rs.getInt(3), 42);
72+
rs.next();
73+
assertEquals(rs.getString(1), "E");
74+
assertEquals(rs.getInt(2), 5);
75+
assertEquals(rs.getInt(3), 5);
76+
rs.next();
77+
assertEquals(rs.getString(1), "F");
78+
assertEquals(rs.getInt(2), 61);
79+
assertEquals(rs.getInt(3), 6);
80+
rs.next();
81+
assertEquals(rs.getString(1), "G");
82+
assertEquals(rs.getInt(2), 7);
83+
assertEquals(rs.getInt(3), 72);
84+
rs.next();
85+
assertEquals(rs.getString(1), "H");
86+
assertEquals(rs.getInt(2), 8);
87+
rs.next();
88+
assertEquals(rs.getString(1), "I");
89+
assertEquals(rs.getInt(2), 10);
90+
}
91+
92+
@Test
93+
public void testUpsertMultiple2() throws Exception {
94+
Properties props = new Properties();
95+
Connection conn = DriverManager.getConnection(getUrl(), props);
96+
String tableName = generateUniqueName();
97+
String ddl = "CREATE TABLE " + tableName + "(K VARCHAR NOT NULL PRIMARY KEY, INT INTEGER)";
98+
conn.createStatement().execute(ddl);
99+
100+
conn.createStatement()
101+
.execute("UPSERT INTO " + tableName + " VALUES ('A', 1),(SUBSTR('APPLE',0,2), 2*2)");
102+
conn.createStatement()
103+
.execute("UPSERT INTO " + tableName + " VALUES (SUBSTR('DELTA',0,1), 5),('C', 2*3)");
104+
conn.commit();
105+
106+
ResultSet rs =
107+
conn.createStatement().executeQuery("SELECT * FROM " + tableName + " ORDER BY K");
108+
rs.next();
109+
assertEquals(rs.getString(1), "A");
110+
assertEquals(rs.getInt(2), 1);
111+
rs.next();
112+
assertEquals(rs.getString(1), "AP");
113+
assertEquals(rs.getInt(2), 4);
114+
rs.next();
115+
assertEquals(rs.getString(1), "C");
116+
assertEquals(rs.getInt(2), 6);
117+
rs.next();
118+
assertEquals(rs.getString(1), "D");
119+
assertEquals(rs.getInt(2), 5);
120+
assertFalse(rs.next());
121+
122+
}
123+
}

phoenix-core/src/test/java/org/apache/phoenix/parse/QueryParserTest.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,42 @@ public void testDeleteWithOrderLimitWhereReturningRow() throws Exception {
765765
parseQuery(sql);
766766
}
767767

768+
@Test
769+
public void testValidMultipleUpsert() throws Exception {
770+
String sql = (("upsert into t VALUES(1,2),(3,4)"));
771+
parseQuery(sql);
772+
}
773+
774+
@Test
775+
public void testValidMultipleUpsert2() throws Exception {
776+
String sql = "upsert into t(a,b) VALUES(1,2),(3,4)";
777+
parseQuery(sql);
778+
}
779+
780+
@Test
781+
public void testValidMultipleUpsert3() throws Exception {
782+
String sql = (("upsert into t(a,b) VALUES(1,2),(3,4),"));
783+
parseQueryThatShouldFail(sql);
784+
}
785+
786+
@Test
787+
public void testValidMultipleUpsert4() throws Exception {
788+
String sql = (("upsert into t(a,b) VALUES()"));
789+
parseQueryThatShouldFail(sql);
790+
}
791+
792+
@Test
793+
public void testValidMultipleUpsert5() throws Exception {
794+
String sql = (("upsert into t(a,b) VALUES(1,2)(3,4)"));
795+
parseQueryThatShouldFail(sql);
796+
}
797+
798+
@Test
799+
public void testValidMultipleUpsert6() throws Exception {
800+
String sql = (("upsert into t(a,b) VALUES(1,2),(3,4"));
801+
parseQueryThatShouldFail(sql);
802+
}
803+
768804
@Test
769805
public void testDeleteInvalidReturningRow() throws Exception {
770806
String sql = "DELETE FROM T RETURNING PK1";

0 commit comments

Comments
 (0)