This is an automated email from the ASF dual-hosted git repository.
vjasani pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/phoenix.git
The following commit(s) were added to refs/heads/master by this push:
new 9f31e3e1a9 PHOENIX-7411 Atomic Delete: PhoenixStatement API to return
row if single row is atomically deleted (#1985)
9f31e3e1a9 is described below
commit 9f31e3e1a9ae81a13662805bdfb2f7aba872e808
Author: Viraj Jasani <[email protected]>
AuthorDate: Wed Oct 9 09:57:39 2024 -0700
PHOENIX-7411 Atomic Delete: PhoenixStatement API to return row if single
row is atomically deleted (#1985)
---
.../org/apache/phoenix/execute/MutationState.java | 12 ++
.../org/apache/phoenix/jdbc/PhoenixStatement.java | 16 +-
.../phoenix/hbase/index/IndexRegionObserver.java | 19 ++-
.../apache/phoenix/end2end/OnDuplicateKey2IT.java | 180 +++++++++++++++++++--
4 files changed, 209 insertions(+), 18 deletions(-)
diff --git
a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/MutationState.java
b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/MutationState.java
index 419b273c72..6d3c9df480 100644
---
a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/MutationState.java
+++
b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/MutationState.java
@@ -495,6 +495,10 @@ public class MutationState implements SQLCloseable {
return this.result;
}
+ public void clearResult() {
+ this.result = null;
+ }
+
private MultiRowMutationState getLastMutationBatch(Map<TableRef,
List<MultiRowMutationState>> mutations, TableRef tableRef) {
List<MultiRowMutationState> mutationBatches = mutations.get(tableRef);
if (mutationBatches == null || mutationBatches.isEmpty()) {
@@ -841,6 +845,14 @@ public class MutationState implements SQLCloseable {
mutation.setAttribute(SOURCE_OPERATION_ATTRIB,
sourceOfDeleteBytes);
}
}
+ if (this.returnResult != null) {
+ if (this.returnResult == ReturnResult.ROW) {
+ for (Mutation mutation : rowMutations) {
+
mutation.setAttribute(PhoenixIndexBuilderHelper.RETURN_RESULT,
+
PhoenixIndexBuilderHelper.RETURN_RESULT_ROW);
+ }
+ }
+ }
// The DeleteCompiler already generates the deletes for
indexes, so no need to do it again
rowMutationsPertainingToIndex = Collections.emptyList();
diff --git
a/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
b/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
index b9f895b777..c361868405 100644
---
a/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
+++
b/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
@@ -647,13 +647,16 @@ public class PhoenixStatement implements
PhoenixMonitoredStatement, SQLCloseable
lastState.getUpdateCount());
Result result = null;
if (connection.getAutoCommit()) {
- state.setReturnResult(returnResult);
+ if (isSingleRowUpdatePlan(isUpsert,
isDelete, plan)) {
+ state.setReturnResult(returnResult);
+ }
connection.commit();
if (isAtomicUpsert) {
lastUpdateCount =
connection.getMutationState()
.getNumUpdatedRowsForAutoCommit();
}
result =
connection.getMutationState().getResult();
+
connection.getMutationState().clearResult();
}
setLastQueryPlan(null);
setLastUpdateCount(lastUpdateCount);
@@ -755,6 +758,17 @@ public class PhoenixStatement implements
PhoenixMonitoredStatement, SQLCloseable
}
}
+ private static boolean isSingleRowUpdatePlan(boolean isUpsert, boolean
isDelete,
+ MutationPlan plan) {
+ boolean isSingleRowUpdate = false;
+ if (isUpsert) {
+ isSingleRowUpdate = true;
+ } else if (isDelete) {
+ isSingleRowUpdate =
plan.getContext().getScanRanges().getPointLookupCount() == 1;
+ }
+ return isSingleRowUpdate;
+ }
+
protected static interface CompilableStatement extends BindableStatement {
public <T extends StatementPlan> T compilePlan (PhoenixStatement stmt,
Sequence.ValueOp seqAction) throws SQLException;
}
diff --git
a/phoenix-core-server/src/main/java/org/apache/phoenix/hbase/index/IndexRegionObserver.java
b/phoenix-core-server/src/main/java/org/apache/phoenix/hbase/index/IndexRegionObserver.java
index cf612f4600..d4c977c5cb 100644
---
a/phoenix-core-server/src/main/java/org/apache/phoenix/hbase/index/IndexRegionObserver.java
+++
b/phoenix-core-server/src/main/java/org/apache/phoenix/hbase/index/IndexRegionObserver.java
@@ -529,7 +529,7 @@ public class IndexRegionObserver implements
RegionCoprocessor, RegionObserver {
BatchMutateContext context) {
for (int i = 0; i < miniBatchOp.size(); i++) {
Mutation m = miniBatchOp.getOperation(i);
- if (this.builder.isAtomicOp(m) || this.builder.returnResult(m) ||
+ if (this.builder.isAtomicOp(m) || context.returnResult ||
this.builder.isEnabled(m)) {
ImmutableBytesPtr row = new ImmutableBytesPtr(m.getRow());
context.rowsToLock.add(row);
@@ -599,6 +599,16 @@ public class IndexRegionObserver implements
RegionCoprocessor, RegionObserver {
miniBatchOp.setOperationStatus(i,
new OperationStatus(SUCCESS, result));
}
+ } else if (context.returnResult) {
+ Map<ColumnReference, Pair<Cell, Boolean>> currColumnCellExprMap
= new HashMap<>();
+ byte[] rowKey = m.getRow();
+ ImmutableBytesPtr rowKeyPtr = new ImmutableBytesPtr(rowKey);
+ Pair<Put, Put> dataRowState =
context.dataRowStates.get(rowKeyPtr);
+ Put currentDataRowState = dataRowState != null ?
dataRowState.getFirst() : null;
+ if (currentDataRowState != null) {
+ updateCurrColumnCellExpr(currentDataRowState,
currColumnCellExprMap);
+ context.currColumnCellExprMap = currColumnCellExprMap;
+ }
}
}
}
@@ -1502,8 +1512,13 @@ public class IndexRegionObserver implements
RegionCoprocessor, RegionObserver {
if (context.returnResult) {
Map<ColumnReference, Pair<Cell, Boolean>> currColumnCellExprMap =
context.currColumnCellExprMap;
+ if (currColumnCellExprMap == null) {
+ return;
+ }
Mutation mutation = miniBatchOp.getOperation(0);
- updateColumnCellExprMap(mutation, currColumnCellExprMap);
+ if (mutation instanceof Put) {
+ updateColumnCellExprMap(mutation, currColumnCellExprMap);
+ }
Mutation[] mutations =
miniBatchOp.getOperationsFromCoprocessors(0);
if (mutations != null) {
for (Mutation m : mutations) {
diff --git
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/OnDuplicateKey2IT.java
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/OnDuplicateKey2IT.java
index e5e2c116e4..064659ac26 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/OnDuplicateKey2IT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/OnDuplicateKey2IT.java
@@ -30,6 +30,7 @@ import java.net.URL;
import java.nio.charset.Charset;
import java.sql.Connection;
import java.sql.DriverManager;
+import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
@@ -198,37 +199,105 @@ public class OnDuplicateKey2IT extends
ParallelStatsDisabledIT {
+ "END";
validateReturnedRowAfterUpsert(conn, upsertSql, tableName,
2233.99, "col2_001", false
, null, bsonDocument2, 234);
+
+ PhoenixPreparedStatement ps =
+ conn.prepareStatement(
+ "DELETE FROM " + tableName + " WHERE PK1 = ? AND
PK2 " +
+ "= ? AND PK3 =
?").unwrap(PhoenixPreparedStatement.class);
+ ps.setString(1, "pk000");
+ ps.setDouble(2, -123.98);
+ ps.setString(3, "pk003");
+ validateReturnedRowAfterDelete(conn, ps, tableName, 2233.99,
"col2_001", true, true,
+ bsonDocument2, 234);
+ validateReturnedRowAfterDelete(conn, ps, tableName, 2233.99,
"col2_001", true, false,
+ bsonDocument2, 234);
+
+ addRows(tableName, conn);
+
+ ps = conn.prepareStatement(
+ "DELETE FROM " + tableName + " WHERE PK1 = ? AND PK2 = ?")
+ .unwrap(PhoenixPreparedStatement.class);
+ ps.setString(1, "pk001");
+ ps.setDouble(2, 122.34);
+ validateReturnedRowAfterDelete(conn, ps, tableName, 2233.99,
"col2_001", false, false,
+ bsonDocument2, 234);
+
+ ps = conn.prepareStatement(
+ "DELETE FROM " +
tableName).unwrap(PhoenixPreparedStatement.class);
+ validateReturnedRowAfterDelete(conn, ps, tableName, 2233.99,
"col2_001", false, false,
+ bsonDocument2, 234);
+
+ ResultSet rs = conn.createStatement().executeQuery("SELECT * FROM
" + tableName);
+ assertFalse(rs.next());
+
+ addRows(tableName, conn);
+
+ ps = conn.prepareStatement(
+ "DELETE FROM " + tableName + " WHERE PK1 IN (?)
AND PK2 IN (?) AND " +
+ "PK3 IN (?, ?)")
+ .unwrap(PhoenixPreparedStatement.class);
+ ps.setString(1, "pk001");
+ ps.setDouble(2, 122.34);
+ ps.setString(3, "pk004");
+ ps.setString(4, "pk005");
+ validateReturnedRowAfterDelete(conn, ps, tableName, 2233.99,
"col2_001", false, false,
+ bsonDocument2, 234);
}
}
- private static void validateReturnedRowAfterUpsert(Connection conn,
- String upsertSql,
+ private static void addRows(String tableName, Connection conn) throws
SQLException {
+ String upsertSql;
+ upsertSql =
+ "UPSERT INTO " + tableName
+ + " (PK1, PK2, PK3, COUNTER1, COUNTER2)
VALUES('pk001', 122.34, "
+ + "'pk004', 23, 'col2_001')";
+ conn.createStatement().execute(upsertSql);
+ upsertSql =
+ "UPSERT INTO " + tableName
+ + " (PK1, PK2, PK3, COUNTER1, COUNTER2)
VALUES('pk001', 122.34, "
+ + "'pk005', 23, 'col2_001')";
+ conn.createStatement().execute(upsertSql);
+ upsertSql =
+ "UPSERT INTO " + tableName
+ + " (PK1, PK2, PK3, COUNTER1, COUNTER2)
VALUES('pk003', 122.34, "
+ + "'pk005', 23, 'col2_001')";
+ conn.createStatement().execute(upsertSql);
+ }
+
+ private static void validateReturnedRowAfterDelete(Connection conn,
+
PhoenixPreparedStatement ps,
String tableName,
Double col1,
String col2,
- boolean success,
- BsonDocument inputDoc,
+ boolean
isSinglePointLookup,
+ boolean
atomicDeleteSuccessful,
BsonDocument
expectedDoc,
Integer col4)
throws SQLException {
- final Pair<Integer, Tuple> resultPair;
- if (inputDoc != null) {
- PhoenixPreparedStatement ps =
-
conn.prepareStatement(upsertSql).unwrap(PhoenixPreparedStatement.class);
- ps.setObject(1, inputDoc);
- resultPair = ps.executeUpdateReturnRow();
- } else {
- resultPair = conn.createStatement().unwrap(PhoenixStatement.class)
- .executeUpdateReturnRow(upsertSql);
- }
- assertEquals(success ? 1 : 0, resultPair.getFirst().intValue());
+ final Pair<Integer, Tuple> resultPair = ps.executeUpdateReturnRow();
ResultTuple result = (ResultTuple) resultPair.getSecond();
+
PTable table =
conn.unwrap(PhoenixConnection.class).getTable(tableName);
+ if (!isSinglePointLookup) {
+ assertNull(result.getResult());
+ return;
+ }
Cell cell =
result.getResult()
.getColumnLatestCell(table.getColumns().get(3).getFamilyName().getBytes(),
table.getColumns().get(3).getColumnQualifierBytes());
+ if (!atomicDeleteSuccessful) {
+ assertNull(cell);
+ return;
+ }
+
+ validateReturnedRowResult(col1, col2, expectedDoc, col4, table, cell,
result);
+ }
+
+ private static void validateReturnedRowResult(Double col1, String col2,
+ BsonDocument expectedDoc,
Integer col4,
+ PTable table, Cell cell,
ResultTuple result) {
ImmutableBytesPtr ptr = new ImmutableBytesPtr();
table.getRowKeySchema().iterator(cell.getRowArray(), ptr, 1);
assertEquals("pk000",
@@ -281,6 +350,87 @@ public class OnDuplicateKey2IT extends
ParallelStatsDisabledIT {
}
}
+ private static void validateReturnedRowAfterUpsert(Connection conn,
+ String upsertSql,
+ String tableName,
+ Double col1,
+ String col2,
+ boolean success,
+ BsonDocument inputDoc,
+ BsonDocument
expectedDoc,
+ Integer col4)
+ throws SQLException {
+ final Pair<Integer, Tuple> resultPair;
+ if (inputDoc != null) {
+ PhoenixPreparedStatement ps =
+
conn.prepareStatement(upsertSql).unwrap(PhoenixPreparedStatement.class);
+ ps.setObject(1, inputDoc);
+ resultPair = ps.executeUpdateReturnRow();
+ } else {
+ resultPair = conn.createStatement().unwrap(PhoenixStatement.class)
+ .executeUpdateReturnRow(upsertSql);
+ }
+ assertEquals(success ? 1 : 0, resultPair.getFirst().intValue());
+ ResultTuple result = (ResultTuple) resultPair.getSecond();
+ PTable table =
conn.unwrap(PhoenixConnection.class).getTable(tableName);
+
+ Cell cell =
+ result.getResult()
+
.getColumnLatestCell(table.getColumns().get(3).getFamilyName().getBytes(),
+
table.getColumns().get(3).getColumnQualifierBytes());
+ validateReturnedRowResult(col1, col2, expectedDoc, col4, table, cell,
result);
+ }
+
+ @Test
+ public void testReturnRowResultForMultiPointLookup() throws Exception {
+ Assume.assumeTrue("Set correct result to RegionActionResult on hbase
versions " +
+ "2.4.18+, 2.5.9+, and 2.6.0+",
isSetCorrectResultEnabledOnHBase());
+
+ Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
+ try (Connection conn = DriverManager.getConnection(getUrl(), props)) {
+ conn.setAutoCommit(true);
+ String tableName = generateUniqueName();
+ String ddl = "CREATE TABLE " + tableName
+ + "(PK VARCHAR PRIMARY KEY, COL1 DOUBLE, COL2 VARCHAR,
COUNTER1 "
+ + "DOUBLE, COUNTER2 VARCHAR)";
+ conn.createStatement().execute(ddl);
+ createIndex(conn, tableName);
+
+ addRows2(tableName, conn);
+
+ PhoenixPreparedStatement ps = conn.prepareStatement(
+ "DELETE FROM " + tableName + " WHERE PK IN (?, ?,
?)")
+ .unwrap(PhoenixPreparedStatement.class);
+ ps.setString(1, "pk001");
+ ps.setString(2, "pk002");
+ ps.setString(3, "pk003");
+ validateReturnedRowAfterDelete(conn, ps, tableName, 2233.99,
"col2_001", false, false,
+ null, 234);
+
+ ResultSet rs = conn.createStatement().executeQuery("SELECT * FROM
" + tableName);
+ assertFalse(rs.next());
+ }
+ }
+
+ private static void addRows2(String tableName, Connection conn) throws
SQLException {
+ String upsertSql;
+ upsertSql =
+ "UPSERT INTO " + tableName
+ + " (PK, COL1, COL2, COUNTER1, COUNTER2)
VALUES('pk001', 122.34, "
+ + "'pk004', 23, 'col2_001')";
+ conn.createStatement().execute(upsertSql);
+ upsertSql =
+ "UPSERT INTO " + tableName
+ + " (PK, COL1, COL2, COUNTER1, COUNTER2)
VALUES('pk002', 122.34, "
+ + "'pk005', 23, 'col2_001')";
+ conn.createStatement().execute(upsertSql);
+ upsertSql =
+ "UPSERT INTO " + tableName
+ + " (PK, COL1, COL2, COUNTER1, COUNTER2)
VALUES('pk003', 122.34, "
+ + "'pk005', 23, 'col2_001')";
+ conn.createStatement().execute(upsertSql);
+ }
+
@Test
public void testReturnRowResultWithoutAutoCommit() throws Exception {
Assume.assumeTrue("Set correct result to RegionActionResult on hbase
versions " +