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 fb8c6305e8 PHOENIX-7651 Support RETURNING * with UPSERT and DELETE 
(#2226)
fb8c6305e8 is described below

commit fb8c6305e8f8ed42306e2910715616b15d4ad544
Author: Hari Krishna Dara <harid...@gmail.com>
AuthorDate: Wed Aug 20 00:56:13 2025 +0530

    PHOENIX-7651 Support RETURNING * with UPSERT and DELETE (#2226)
---
 .gitignore                                         |  5 ++
 phoenix-core-client/src/main/antlr3/PhoenixSQL.g   | 18 +++--
 .../org/apache/phoenix/jdbc/PhoenixStatement.java  | 30 ++++----
 .../org/apache/phoenix/parse/DeleteStatement.java  | 10 ++-
 .../org/apache/phoenix/parse/ParseNodeFactory.java |  9 +--
 .../phoenix/parse/RowReturningDMLStatement.java    | 22 ++++++
 .../org/apache/phoenix/parse/UpsertStatement.java  | 28 +++-----
 .../java/org/apache/phoenix/util/TupleUtil.java    | 22 ++++--
 .../java/org/apache/phoenix/end2end/Bson4IT.java   | 14 ++--
 .../apache/phoenix/end2end/OnDuplicateKey2IT.java  | 82 ++++++++++++----------
 .../org/apache/phoenix/end2end/UpsertValuesIT.java | 50 +++++++++++--
 .../org/apache/phoenix/parse/QueryParserTest.java  | 70 ++++++++++++++++++
 12 files changed, 259 insertions(+), 101 deletions(-)

diff --git a/.gitignore b/.gitignore
index 0851a687cb..29049f43fd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,3 +39,8 @@ phoenix-hbase-compat-1.5.0/
 # Code generators
 .codegenie
 /.vscode/
+
+# Some generated files
+ID
+tags
+filenametags
diff --git a/phoenix-core-client/src/main/antlr3/PhoenixSQL.g 
b/phoenix-core-client/src/main/antlr3/PhoenixSQL.g
index 5d3e0c926d..a2e913093e 100644
--- a/phoenix-core-client/src/main/antlr3/PhoenixSQL.g
+++ b/phoenix-core-client/src/main/antlr3/PhoenixSQL.g
@@ -160,6 +160,7 @@ tokens
     UNCOVERED = 'uncovered';
     REGIONS = 'regions';
     NOVERIFY = 'noverify';
+    RETURNING = 'returning';
 }
 
 
@@ -866,9 +867,13 @@ finally{ contextStack.pop(); }
 upsert_node returns [UpsertStatement ret]
     :   UPSERT (hint=hintClause)? INTO t=from_table_name
         (LPAREN p=upsert_column_refs RPAREN)?
-        ((VALUES LPAREN v=one_or_more_expressions RPAREN ( ON DUPLICATE KEY ( 
ig=IGNORE |
-         ( upd=UPDATE pairs=update_column_pairs ) | ( updo=UPDATE_ONLY 
upopairs=update_column_pairs ) ) )? )
-          | s=select_node)
+        ((VALUES LPAREN v=one_or_more_expressions RPAREN (
+            ON DUPLICATE KEY (
+                ig=IGNORE
+              | ( upd=UPDATE pairs=update_column_pairs )
+              | ( updo=UPDATE_ONLY upopairs=update_column_pairs )
+            ) )? )
+          | s=select_node) rc=( RETURNING ASTERISK )?
         {ret = factory.upsert(
             factory.namedTable(null,t,p == null ? null : p.getFirst()), 
             hint, p == null ? null : p.getSecond(), 
@@ -879,7 +884,8 @@ upsert_node returns [UpsertStatement ret]
             ig != null ? UpsertStatement.OnDuplicateKeyType.IGNORE :
             upd != null ? UpsertStatement.OnDuplicateKeyType.UPDATE :
             updo != null ? UpsertStatement.OnDuplicateKeyType.UPDATE_ONLY
-             : UpsertStatement.OnDuplicateKeyType.NONE); }
+             : UpsertStatement.OnDuplicateKeyType.NONE,
+            rc != null ? true : false); }
     ;
   
 update_column_pairs returns [ List<Pair<ColumnName,ParseNode>> ret]
@@ -924,7 +930,9 @@ delete_node returns [DeleteStatement ret]
         (WHERE v=expression)?
         (ORDER BY order=order_by)?
         (LIMIT l=limit)?
-        {ret = factory.delete(factory.namedTable(null,t), hint, v, order, l, 
getBindCount(), new HashMap<String, UDFParseNode>(udfParseNodes)); }
+        rc=(RETURNING ASTERISK)?
+        {ret = factory.delete(factory.namedTable(null,t), hint, v, order, l, 
getBindCount(), new
+        HashMap<String, UDFParseNode>(udfParseNodes), rc != null ? true : 
false); }
     ;
 
 limit returns [LimitNode ret]
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 c0cb8a7339..8ef621d535 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
@@ -179,6 +179,7 @@ import org.apache.phoenix.parse.PFunction;
 import org.apache.phoenix.parse.ParseNode;
 import org.apache.phoenix.parse.ParseNodeFactory;
 import org.apache.phoenix.parse.PrimaryKeyConstraint;
+import org.apache.phoenix.parse.RowReturningDMLStatement;
 import org.apache.phoenix.parse.SQLParser;
 import org.apache.phoenix.parse.SelectStatement;
 import org.apache.phoenix.parse.ShowCreateTable;
@@ -580,15 +581,15 @@ public class PhoenixStatement implements 
PhoenixMonitoredStatement, SQLCloseable
     return target;
   }
 
-  private boolean isResultSetExpected(final CompilableStatement stmt) {
-    return stmt instanceof ExecutableUpsertStatement
-      && ((ExecutableUpsertStatement) stmt).getOnDupKeyPairs() != null;
+  private boolean isReturningRowStatement(final CompilableStatement stmt) {
+    return stmt instanceof RowReturningDMLStatement
+      && ((RowReturningDMLStatement) stmt).isReturningRow();
   }
 
   protected int executeMutation(final CompilableStatement stmt, final 
AuditQueryLogger queryLogger)
     throws SQLException {
     return executeMutation(stmt, true, queryLogger,
-      isResultSetExpected(stmt) ? ReturnResult.NEW_ROW_ON_SUCCESS : 
null).getFirst();
+      isReturningRowStatement(stmt) ? ReturnResult.NEW_ROW_ON_SUCCESS : 
null).getFirst();
   }
 
   Pair<Integer, ResultSet> executeMutation(final CompilableStatement stmt,
@@ -695,7 +696,8 @@ public class PhoenixStatement implements 
PhoenixMonitoredStatement, SQLCloseable
                 : (isDelete ? ((ExecutableDeleteStatement) 
stmt).getTable().getName() : null);
               ResultSet rs = result == null || result.isEmpty()
                 ? null
-                : TupleUtil.getResultSet(new ResultTuple(result), 
tableNameVal, connection);
+                : TupleUtil.getResultSet(new ResultTuple(result), 
tableNameVal, connection,
+                  !isReturningRowStatement(stmt));
               setLastResultSet(rs);
               return new Pair<>(lastUpdateCount, rs);
             }
@@ -1181,9 +1183,9 @@ public class PhoenixStatement implements 
PhoenixMonitoredStatement, SQLCloseable
     private ExecutableUpsertStatement(NamedTableNode table, HintNode hintNode,
       List<ColumnName> columns, List<ParseNode> values, SelectStatement 
select, int bindCount,
       Map<String, UDFParseNode> udfParseNodes, List<Pair<ColumnName, 
ParseNode>> onDupKeyPairs,
-      OnDuplicateKeyType onDupKeyType) {
+      OnDuplicateKeyType onDupKeyType, boolean returningRow) {
       super(table, hintNode, columns, values, select, bindCount, 
udfParseNodes, onDupKeyPairs,
-        onDupKeyType);
+        onDupKeyType, returningRow);
     }
 
     @SuppressWarnings("unchecked")
@@ -1204,8 +1206,8 @@ public class PhoenixStatement implements 
PhoenixMonitoredStatement, SQLCloseable
     implements CompilableStatement {
     private ExecutableDeleteStatement(NamedTableNode table, HintNode hint, 
ParseNode whereNode,
       List<OrderByNode> orderBy, LimitNode limit, int bindCount,
-      Map<String, UDFParseNode> udfParseNodes) {
-      super(table, hint, whereNode, orderBy, limit, bindCount, udfParseNodes);
+      Map<String, UDFParseNode> udfParseNodes, boolean returningRow) {
+      super(table, hint, whereNode, orderBy, limit, bindCount, udfParseNodes, 
returningRow);
     }
 
     @SuppressWarnings("unchecked")
@@ -2126,9 +2128,9 @@ public class PhoenixStatement implements 
PhoenixMonitoredStatement, SQLCloseable
     public ExecutableUpsertStatement upsert(NamedTableNode table, HintNode 
hintNode,
       List<ColumnName> columns, List<ParseNode> values, SelectStatement 
select, int bindCount,
       Map<String, UDFParseNode> udfParseNodes, List<Pair<ColumnName, 
ParseNode>> onDupKeyPairs,
-      UpsertStatement.OnDuplicateKeyType onDupKeyType) {
+      UpsertStatement.OnDuplicateKeyType onDupKeyType, boolean returningRow) {
       return new ExecutableUpsertStatement(table, hintNode, columns, values, 
select, bindCount,
-        udfParseNodes, onDupKeyPairs, onDupKeyType);
+        udfParseNodes, onDupKeyPairs, onDupKeyType, returningRow);
     }
 
     @Override
@@ -2155,9 +2157,9 @@ public class PhoenixStatement implements 
PhoenixMonitoredStatement, SQLCloseable
     @Override
     public ExecutableDeleteStatement delete(NamedTableNode table, HintNode 
hint,
       ParseNode whereNode, List<OrderByNode> orderBy, LimitNode limit, int 
bindCount,
-      Map<String, UDFParseNode> udfParseNodes) {
+      Map<String, UDFParseNode> udfParseNodes, boolean returningRow) {
       return new ExecutableDeleteStatement(table, hint, whereNode, orderBy, 
limit, bindCount,
-        udfParseNodes);
+        udfParseNodes, returningRow);
     }
 
     @Override
@@ -2667,7 +2669,7 @@ public class PhoenixStatement implements 
PhoenixMonitoredStatement, SQLCloseable
       }
       executeMutation(stmt, createAuditQueryLogger(stmt, sql));
       flushIfNecessary();
-      return isResultSetExpected(stmt);
+      return isReturningRowStatement(stmt);
     }
 
     executeQuery(stmt, createQueryLogger(stmt, sql));
diff --git 
a/phoenix-core-client/src/main/java/org/apache/phoenix/parse/DeleteStatement.java
 
b/phoenix-core-client/src/main/java/org/apache/phoenix/parse/DeleteStatement.java
index b4e82e0209..be33d7938a 100644
--- 
a/phoenix-core-client/src/main/java/org/apache/phoenix/parse/DeleteStatement.java
+++ 
b/phoenix-core-client/src/main/java/org/apache/phoenix/parse/DeleteStatement.java
@@ -22,20 +22,23 @@ import java.util.List;
 import java.util.Map;
 import org.apache.phoenix.jdbc.PhoenixStatement.Operation;
 
-public class DeleteStatement extends DMLStatement implements 
FilterableStatement {
+public class DeleteStatement extends DMLStatement
+  implements FilterableStatement, RowReturningDMLStatement {
   private final ParseNode whereNode;
   private final List<OrderByNode> orderBy;
   private final LimitNode limit;
   private final HintNode hint;
+  private final boolean returningRow;
 
   public DeleteStatement(NamedTableNode table, HintNode hint, ParseNode 
whereNode,
     List<OrderByNode> orderBy, LimitNode limit, int bindCount,
-    Map<String, UDFParseNode> udfParseNodes) {
+    Map<String, UDFParseNode> udfParseNodes, boolean returningRow) {
     super(table, bindCount, udfParseNodes);
     this.whereNode = whereNode;
     this.orderBy = orderBy == null ? Collections.<OrderByNode> emptyList() : 
orderBy;
     this.limit = limit;
     this.hint = hint == null ? HintNode.EMPTY_HINT_NODE : hint;
+    this.returningRow = returningRow;
   }
 
   @Override
@@ -83,4 +86,7 @@ public class DeleteStatement extends DMLStatement implements 
FilterableStatement
     throw new UnsupportedOperationException("Table sampling is not allowd for 
Deletion");
   }
 
+  public boolean isReturningRow() {
+    return returningRow;
+  }
 }
diff --git 
a/phoenix-core-client/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java
 
b/phoenix-core-client/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java
index d28dcc7837..6a22664695 100644
--- 
a/phoenix-core-client/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java
+++ 
b/phoenix-core-client/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java
@@ -900,9 +900,9 @@ public class ParseNodeFactory {
   public UpsertStatement upsert(NamedTableNode table, HintNode hint, 
List<ColumnName> columns,
     List<ParseNode> values, SelectStatement select, int bindCount,
     Map<String, UDFParseNode> udfParseNodes, List<Pair<ColumnName, ParseNode>> 
onDupKeyPairs,
-    UpsertStatement.OnDuplicateKeyType onDupKeyType) {
+    UpsertStatement.OnDuplicateKeyType onDupKeyType, boolean returningRow) {
     return new UpsertStatement(table, hint, columns, values, select, 
bindCount, udfParseNodes,
-      onDupKeyPairs, onDupKeyType);
+      onDupKeyPairs, onDupKeyType, returningRow);
   }
 
   public CursorName cursorName(String name) {
@@ -927,8 +927,9 @@ public class ParseNodeFactory {
 
   public DeleteStatement delete(NamedTableNode table, HintNode hint, ParseNode 
node,
     List<OrderByNode> orderBy, LimitNode limit, int bindCount,
-    Map<String, UDFParseNode> udfParseNodes) {
-    return new DeleteStatement(table, hint, node, orderBy, limit, bindCount, 
udfParseNodes);
+    Map<String, UDFParseNode> udfParseNodes, boolean returningRow) {
+    return new DeleteStatement(table, hint, node, orderBy, limit, bindCount, 
udfParseNodes,
+      returningRow);
   }
 
   public SelectStatement select(SelectStatement statement, ParseNode where) {
diff --git 
a/phoenix-core-client/src/main/java/org/apache/phoenix/parse/RowReturningDMLStatement.java
 
b/phoenix-core-client/src/main/java/org/apache/phoenix/parse/RowReturningDMLStatement.java
new file mode 100644
index 0000000000..ba368e7bbb
--- /dev/null
+++ 
b/phoenix-core-client/src/main/java/org/apache/phoenix/parse/RowReturningDMLStatement.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.phoenix.parse;
+
+public interface RowReturningDMLStatement {
+  boolean isReturningRow();
+}
diff --git 
a/phoenix-core-client/src/main/java/org/apache/phoenix/parse/UpsertStatement.java
 
b/phoenix-core-client/src/main/java/org/apache/phoenix/parse/UpsertStatement.java
index 279ddfa477..d89b48bdb5 100644
--- 
a/phoenix-core-client/src/main/java/org/apache/phoenix/parse/UpsertStatement.java
+++ 
b/phoenix-core-client/src/main/java/org/apache/phoenix/parse/UpsertStatement.java
@@ -22,7 +22,7 @@ import java.util.List;
 import java.util.Map;
 import org.apache.hadoop.hbase.util.Pair;
 
-public class UpsertStatement extends DMLStatement {
+public class UpsertStatement extends DMLStatement implements 
RowReturningDMLStatement {
 
   public enum OnDuplicateKeyType {
     NONE,
@@ -37,29 +37,12 @@ public class UpsertStatement extends DMLStatement {
   private final HintNode hint;
   private final List<Pair<ColumnName, ParseNode>> onDupKeyPairs;
   private final OnDuplicateKeyType onDupKeyType;
-
-  public UpsertStatement(NamedTableNode table, HintNode hint, List<ColumnName> 
columns,
-    List<ParseNode> values, SelectStatement select, int bindCount,
-    Map<String, UDFParseNode> udfParseNodes, List<Pair<ColumnName, ParseNode>> 
onDupKeyPairs) {
-    super(table, bindCount, udfParseNodes);
-    this.columns = columns == null ? Collections.<ColumnName> emptyList() : 
columns;
-    this.values = values;
-    this.select = select;
-    this.hint = hint == null ? HintNode.EMPTY_HINT_NODE : hint;
-    this.onDupKeyPairs = onDupKeyPairs;
-    if (onDupKeyPairs == null) {
-      this.onDupKeyType = OnDuplicateKeyType.NONE;
-    } else if (onDupKeyPairs.isEmpty()) {
-      this.onDupKeyType = OnDuplicateKeyType.IGNORE;
-    } else {
-      this.onDupKeyType = OnDuplicateKeyType.UPDATE;
-    }
-  }
+  private final boolean returningRow;
 
   public UpsertStatement(NamedTableNode table, HintNode hint, List<ColumnName> 
columns,
     List<ParseNode> values, SelectStatement select, int bindCount,
     Map<String, UDFParseNode> udfParseNodes, List<Pair<ColumnName, ParseNode>> 
onDupKeyPairs,
-    OnDuplicateKeyType onDupKeyType) {
+    OnDuplicateKeyType onDupKeyType, boolean returningRow) {
     super(table, bindCount, udfParseNodes);
     this.columns = columns == null ? Collections.emptyList() : columns;
     this.values = values;
@@ -67,6 +50,7 @@ public class UpsertStatement extends DMLStatement {
     this.hint = hint == null ? HintNode.EMPTY_HINT_NODE : hint;
     this.onDupKeyPairs = onDupKeyPairs;
     this.onDupKeyType = onDupKeyType;
+    this.returningRow = returningRow;
   }
 
   public List<ColumnName> getColumns() {
@@ -92,4 +76,8 @@ public class UpsertStatement extends DMLStatement {
   public OnDuplicateKeyType getOnDupKeyType() {
     return onDupKeyType;
   }
+
+  public boolean isReturningRow() {
+    return returningRow;
+  }
 }
diff --git 
a/phoenix-core-client/src/main/java/org/apache/phoenix/util/TupleUtil.java 
b/phoenix-core-client/src/main/java/org/apache/phoenix/util/TupleUtil.java
index de70f23080..b8f6a769d1 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/util/TupleUtil.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/util/TupleUtil.java
@@ -24,6 +24,7 @@ import static 
org.apache.phoenix.query.QueryConstants.SINGLE_COLUMN_FAMILY;
 import static org.apache.phoenix.query.QueryConstants.VALUE_COLUMN_FAMILY;
 import static org.apache.phoenix.query.QueryConstants.VALUE_COLUMN_QUALIFIER;
 
+import edu.umd.cs.findbugs.annotations.SuppressWarnings;
 import java.io.DataOutput;
 import java.io.IOException;
 import java.sql.Connection;
@@ -32,6 +33,7 @@ import java.sql.SQLException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+
 import org.apache.hadoop.hbase.Cell;
 import org.apache.hadoop.hbase.CellUtil;
 import org.apache.hadoop.hbase.DoNotRetryIOException;
@@ -226,14 +228,20 @@ public class TupleUtil {
   /**
    * Convert the given Tuple containing list of Cells to ResultSet with 
similar effect as if SELECT
    * * FROM <table-name> is queried.
-   * @param toProject Tuple to be projected.
-   * @param tableName Table name.
-   * @param conn      Phoenix Connection object.
+   * @param toProject    Tuple to be projected.
+   * @param tableName    Table name.
+   * @param conn         Phoenix Connection object.
+   * @param withPrefetch When {@code true}, the returned ResultSet is 
prefetched, otherwise one
+   *                     needs to call next() on it.
    * @return ResultSet for the give single row.
    * @throws SQLException If any SQL operation fails.
    */
-  public static ResultSet getResultSet(Tuple toProject, TableName tableName, 
Connection conn)
-    throws SQLException {
+  @SuppressWarnings(value = "OBL_UNSATISFIED_OBLIGATION_EXCEPTION_EDGE",
+          justification = "Tge statement object needs to be kept open for the 
returned RS to be "
+          + "valid, however this is acceptable as not 
callingPhoenixStatement.close() "
+          + "causes no resource leak")
+  public static ResultSet getResultSet(Tuple toProject, TableName tableName, 
Connection conn,
+    boolean withPrefetch) throws SQLException {
     if (tableName == null) {
       return null;
     }
@@ -268,7 +276,9 @@ public class TupleUtil {
       ResultSet newResultSet = new 
PhoenixPrefetchedResultSet(resultSet.getRowProjector(),
         resultSet.getContext(), Collections
           .singletonList(new 
ResultTuple(Result.create(Collections.singletonList(newCell)))));
-      newResultSet.next();
+      if (withPrefetch) {
+        newResultSet.next();
+      }
       return newResultSet;
     }
   }
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson4IT.java 
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson4IT.java
index b1e534703e..f8884472ab 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson4IT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson4IT.java
@@ -578,7 +578,8 @@ public class Bson4IT extends ParallelStatsDisabledIT {
       stmt = conn.prepareStatement(
         "UPSERT INTO " + tableName + " VALUES (?) ON DUPLICATE KEY UPDATE_ONLY 
COL = CASE WHEN"
           + " BSON_CONDITION_EXPRESSION(COL, '" + conditionDoc.toJson() + "')"
-          + " THEN BSON_UPDATE_EXPRESSION(COL, '" + updateExp + "') ELSE COL 
END," + " C1 = ?");
+          + " THEN BSON_UPDATE_EXPRESSION(COL, '" + updateExp + "') ELSE COL 
END,"
+          + " C1 = ? RETURNING *");
       stmt.setString(1, "pk0001");
       stmt.setString(2, "0003");
 
@@ -611,7 +612,7 @@ public class Bson4IT extends ParallelStatsDisabledIT {
       stmt = conn.prepareStatement(
         "UPSERT INTO " + tableName + " VALUES (?) ON DUPLICATE KEY UPDATE COL 
= CASE WHEN"
           + " BSON_CONDITION_EXPRESSION(COL, '" + conditionDoc.toJson() + "')"
-          + " THEN BSON_UPDATE_EXPRESSION(COL, '" + updateExp + "') ELSE COL 
END");
+          + " THEN BSON_UPDATE_EXPRESSION(COL, '" + updateExp + "') ELSE COL 
END RETURNING *");
 
       stmt.setString(1, "pk1010");
 
@@ -634,7 +635,7 @@ public class Bson4IT extends ParallelStatsDisabledIT {
       stmt = conn.prepareStatement(
         "UPSERT INTO " + tableName + " VALUES (?) ON DUPLICATE KEY UPDATE COL 
= CASE WHEN"
           + " BSON_CONDITION_EXPRESSION(COL, '" + conditionDoc.toJson() + "')"
-          + " THEN BSON_UPDATE_EXPRESSION(COL, '" + updateExp + "') ELSE COL 
END");
+          + " THEN BSON_UPDATE_EXPRESSION(COL, '" + updateExp + "') ELSE COL 
END RETURNING *");
 
       stmt.setString(1, "pk1011");
 
@@ -659,7 +660,7 @@ public class Bson4IT extends ParallelStatsDisabledIT {
       stmt = conn.prepareStatement(
         "UPSERT INTO " + tableName + " VALUES (?) ON DUPLICATE KEY UPDATE COL 
= CASE WHEN"
           + " BSON_CONDITION_EXPRESSION(COL, '" + conditionDoc.toJson() + "')"
-          + " THEN BSON_UPDATE_EXPRESSION(COL, '" + updateExp + "') ELSE COL 
END");
+          + " THEN BSON_UPDATE_EXPRESSION(COL, '" + updateExp + "') ELSE COL 
END RETURNING *");
       stmt.setString(1, "pk0001");
 
       // Conditional Upsert not successful
@@ -668,7 +669,7 @@ public class Bson4IT extends ParallelStatsDisabledIT {
       stmt = conn.prepareStatement(
         "UPSERT INTO " + tableName + " VALUES (?) ON DUPLICATE KEY UPDATE_ONLY 
COL = CASE WHEN"
           + " BSON_CONDITION_EXPRESSION(COL, '" + conditionDoc.toJson() + "')"
-          + " THEN BSON_UPDATE_EXPRESSION(COL, '" + updateExp + "') ELSE COL 
END");
+          + " THEN BSON_UPDATE_EXPRESSION(COL, '" + updateExp + "') ELSE COL 
END RETURNING *");
       // the row does not exist already
       stmt.setString(1, "pk000111");
 
@@ -680,7 +681,7 @@ public class Bson4IT extends ParallelStatsDisabledIT {
       stmt = conn.prepareStatement(
         "UPSERT INTO " + tableName + " VALUES (?) ON DUPLICATE KEY UPDATE COL 
= CASE WHEN"
           + " BSON_CONDITION_EXPRESSION(COL, '" + conditionDoc.toJson() + "')"
-          + " THEN BSON_UPDATE_EXPRESSION(COL, '" + updateExp + "') ELSE COL 
END");
+          + " THEN BSON_UPDATE_EXPRESSION(COL, '" + updateExp + "') ELSE COL 
END RETURNING *");
       // the row does not exist already
       stmt.setString(1, "pk000123456");
 
@@ -1085,6 +1086,7 @@ public class Bson4IT extends ParallelStatsDisabledIT {
     stmt.execute();
     assertEquals(success ? 1 : 0, stmt.getUpdateCount());
     ResultSet resultSet = stmt.getResultSet();
+    assertTrue(resultSet.next());
     assertEquals(jsonPath == null ? null : 
RawBsonDocument.parse(getJsonString(jsonPath)),
       resultSet.getObject(3));
   }
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 6d0165f14f..b5a7b055f9 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
@@ -124,8 +124,8 @@ public class OnDuplicateKey2IT extends 
ParallelStatsDisabledIT {
     createIndex(conn, tableName);
     conn.createStatement().execute("UPSERT INTO " + tableName + " 
VALUES('a',10)");
 
-    int actualReturnValue = conn.createStatement()
-      .executeUpdate("UPSERT INTO " + tableName + " VALUES('a',0) ON DUPLICATE 
KEY IGNORE");
+    int actualReturnValue = conn.createStatement().executeUpdate(
+      "UPSERT INTO " + tableName + " VALUES('a',0) ON DUPLICATE KEY IGNORE " + 
"" + "RETURNING *");
     assertEquals(0, actualReturnValue);
 
     conn.close();
@@ -158,8 +158,8 @@ public class OnDuplicateKey2IT extends 
ParallelStatsDisabledIT {
 
       validateAtomicUpsertReturnRow(tableName, conn, bsonDocument1, 
bsonDocument2);
 
-      PreparedStatement ps = conn
-        .prepareStatement("DELETE FROM " + tableName + " WHERE PK1 = ? AND PK2 
= ? AND PK3 = ?");
+      PreparedStatement ps = conn.prepareStatement(
+        "DELETE FROM " + tableName + " WHERE PK1 = ? AND PK2 = ? AND PK3 = ? " 
+ "RETURNING *");
       ps.setString(1, "pk000");
       ps.setDouble(2, -123.98);
       ps.setString(3, "pk003");
@@ -199,16 +199,16 @@ public class OnDuplicateKey2IT extends 
ParallelStatsDisabledIT {
 
       verifyIndexRow(conn, tableName, false);
 
-      PreparedStatement ps = conn.prepareStatement(
-        "DELETE FROM " + tableName + " WHERE PK1 = ? AND PK2 = ? AND PK3 = ? 
AND COL4 = ?");
+      PreparedStatement ps = conn.prepareStatement("DELETE FROM " + tableName
+        + " WHERE PK1 = ? AND PK2 = ? AND PK3 = ? AND COL4 = ? " + "RETURNING 
*");
       ps.setString(1, "pk000");
       ps.setDouble(2, -123.98);
       ps.setString(3, "pk003");
       ps.setInt(4, 235);
       validateReturnedRowAfterDelete(ps, "col2_001", true, false, 
bsonDocument2, 234);
 
-      ps = conn.prepareStatement(
-        "DELETE FROM " + tableName + " WHERE PK1 = ? AND PK2 = ? AND PK3 = ? 
AND COL4 = ?");
+      ps = conn.prepareStatement("DELETE FROM " + tableName
+        + " WHERE PK1 = ? AND PK2 = ? AND PK3 = ? AND COL4 = ? " + "RETURNING 
*");
       ps.setString(1, "pk000");
       ps.setDouble(2, -123.98);
       ps.setString(3, "pk003");
@@ -252,16 +252,16 @@ public class OnDuplicateKey2IT extends 
ParallelStatsDisabledIT {
 
       validateAtomicUpsertReturnRow(tableName, conn, bsonDocument1, 
bsonDocument2);
 
-      PreparedStatement ps = conn.prepareStatement(
-        "DELETE FROM " + tableName + " WHERE PK1 = ? AND PK2 = ? AND PK3 = ? 
AND COL4 = ?");
+      PreparedStatement ps = conn.prepareStatement("DELETE FROM " + tableName
+        + " WHERE PK1 = ? AND PK2 = ? AND PK3 = ? AND COL4 = ? " + "RETURNING 
*");
       ps.setString(1, "pk000");
       ps.setDouble(2, -123.98);
       ps.setString(3, "pk003");
       ps.setInt(4, 235);
       validateReturnedRowAfterDelete(ps, "col2_001", true, false, 
bsonDocument2, 234);
 
-      ps = conn.prepareStatement(
-        "DELETE FROM " + tableName + " WHERE PK1 = ? AND PK2 = ? AND PK3 = ? 
AND COL4 = ?");
+      ps = conn.prepareStatement("DELETE FROM " + tableName
+        + " WHERE PK1 = ? AND PK2 = ? AND PK3 = ? AND COL4 = ? " + "RETURNING 
*");
       ps.setString(1, "pk000");
       ps.setDouble(2, -123.98);
       ps.setString(3, "pk003");
@@ -304,16 +304,16 @@ public class OnDuplicateKey2IT extends 
ParallelStatsDisabledIT {
 
       validateAtomicUpsertOnlyReturnRow(tableName, conn, bsonDocument1, 
bsonDocument2);
 
-      PreparedStatement ps = conn.prepareStatement(
-        "DELETE FROM " + tableName + " WHERE PK1 = ? AND PK2 = ? AND PK3 = ? 
AND COL4 = ?");
+      PreparedStatement ps = conn.prepareStatement("DELETE FROM " + tableName
+        + " WHERE PK1 = ? AND PK2 = ? AND PK3 = ? AND COL4 = ? " + "RETURNING 
*");
       ps.setString(1, "pk000");
       ps.setDouble(2, -123.98);
       ps.setString(3, "pk003");
       ps.setInt(4, 235);
       validateReturnedRowAfterDelete(ps, "col2_001", true, false, 
bsonDocument2, 234);
 
-      ps = conn.prepareStatement(
-        "DELETE FROM " + tableName + " WHERE PK1 = ? AND PK2 = ? AND PK3 = ? 
AND COL4 = ?");
+      ps = conn.prepareStatement("DELETE FROM " + tableName
+        + " WHERE PK1 = ? AND PK2 = ? AND PK3 = ? AND COL4 = ? " + "RETURNING 
*");
       ps.setString(1, "pk000");
       ps.setDouble(2, -123.98);
       ps.setString(3, "pk003");
@@ -356,8 +356,8 @@ public class OnDuplicateKey2IT extends 
ParallelStatsDisabledIT {
 
       validateAtomicUpsertReturnRow(tableName, conn, bsonDocument1, 
bsonDocument2);
 
-      PreparedStatement ps = conn
-        .prepareStatement("DELETE FROM " + tableName + " WHERE PK1 = ? AND PK2 
= ? AND PK3 = ?");
+      PreparedStatement ps = conn.prepareStatement(
+        "DELETE FROM " + tableName + " WHERE PK1 = ? AND PK2 = ? AND PK3 = ? " 
+ "RETURNING *");
       ps.setString(1, "pk000");
       ps.setDouble(2, -123.98);
       ps.setString(3, "pk003");
@@ -395,13 +395,13 @@ public class OnDuplicateKey2IT extends 
ParallelStatsDisabledIT {
     BsonDocument bsonDocument2) throws SQLException {
     addRows(tableName, conn);
 
-    PreparedStatement ps =
-      conn.prepareStatement("DELETE FROM " + tableName + " WHERE PK1 = ? AND 
PK2 = ?");
+    PreparedStatement ps = conn
+      .prepareStatement("DELETE FROM " + tableName + " WHERE PK1 = ? AND PK2 = 
? " + "RETURNING *");
     ps.setString(1, "pk001");
     ps.setDouble(2, 122.34);
     validateReturnedRowAfterDelete(ps, "col2_001", false, false, 
bsonDocument2, 234);
 
-    ps = conn.prepareStatement("DELETE FROM " + tableName);
+    ps = conn.prepareStatement("DELETE FROM " + tableName + " RETURNING *");
     validateReturnedRowAfterDelete(ps, "col2_001", false, false, 
bsonDocument2, 234);
 
     ResultSet rs = conn.createStatement().executeQuery("SELECT * FROM " + 
tableName);
@@ -409,8 +409,8 @@ public class OnDuplicateKey2IT extends 
ParallelStatsDisabledIT {
 
     addRows(tableName, conn);
 
-    ps = conn.prepareStatement(
-      "DELETE FROM " + tableName + " WHERE PK1 IN (?) AND PK2 IN (?) AND PK3 
IN (?, ?)");
+    ps = conn.prepareStatement("DELETE FROM " + tableName
+      + " WHERE PK1 IN (?) AND PK2 IN (?) AND PK3 IN (?, ?) " + "RETURNING *");
     ps.setString(1, "pk001");
     ps.setDouble(2, 122.34);
     ps.setString(3, "pk004");
@@ -421,12 +421,13 @@ public class OnDuplicateKey2IT extends 
ParallelStatsDisabledIT {
   private static void validateAtomicUpsertReturnRow(String tableName, 
Connection conn,
     BsonDocument bsonDocument1, BsonDocument bsonDocument2) throws 
SQLException {
     String upsertSql = "UPSERT INTO " + tableName + " (PK1, PK2, PK3, 
COUNTER1, COL3, COL4)"
-      + " VALUES('pk000', -123.98, 'pk003', 1011.202, ?, 123) ON DUPLICATE KEY 
" + "IGNORE";
+      + " VALUES('pk000', -123.98, 'pk003', 1011.202, ?, 123) ON DUPLICATE KEY 
" + "IGNORE "
+      + "RETURNING *";
     validateReturnedRowAfterUpsert(conn, upsertSql, tableName, 1011.202, null, 
true, bsonDocument1,
       bsonDocument1, 123);
 
     upsertSql = "UPSERT INTO " + tableName + " (PK1, PK2, PK3, COUNTER1) "
-      + "VALUES('pk000', -123.98, 'pk003', 0) ON DUPLICATE KEY IGNORE";
+      + "VALUES('pk000', -123.98, 'pk003', 0) ON DUPLICATE KEY IGNORE " + 
"RETURNING *";
     validateReturnedRowAfterUpsert(conn, upsertSql, tableName, 1011.202, null, 
false, null,
       bsonDocument1, 123);
 
@@ -440,14 +441,15 @@ public class OnDuplicateKey2IT extends 
ParallelStatsDisabledIT {
       + " (PK1, PK2, PK3) VALUES('pk000', -123.98, 'pk003') ON DUPLICATE KEY 
UPDATE "
       + "COUNTER1 = CASE WHEN COUNTER1 < 2000 THEN COUNTER1 + 1999.99 ELSE 
COUNTER1" + " END, "
       + "COUNTER2 = CASE WHEN COUNTER2 = 'col2_000' THEN 'col2_001' ELSE 
COUNTER2 " + "END, "
-      + "COL3 = ?, " + "COL4 = 234";
+      + "COL3 = ?, " + "COL4 = 234 RETURNING *";
     validateReturnedRowAfterUpsert(conn, upsertSql, tableName, 2233.99, 
"col2_001", true,
       bsonDocument2, bsonDocument2, 234);
 
     upsertSql = "UPSERT INTO " + tableName
       + " (PK1, PK2, PK3) VALUES('pk000', -123.98, 'pk003') ON DUPLICATE KEY 
UPDATE "
       + "COUNTER1 = CASE WHEN COUNTER1 < 2000 THEN COUNTER1 + 1999.99 ELSE 
COUNTER1" + " END,"
-      + "COUNTER2 = CASE WHEN COUNTER2 = 'col2_000' THEN 'col2_001' ELSE 
COUNTER2 " + "END";
+      + "COUNTER2 = CASE WHEN COUNTER2 = 'col2_000' THEN 'col2_001' ELSE 
COUNTER2 " + "END "
+      + "RETURNING *";
     validateReturnedRowAfterUpsert(conn, upsertSql, tableName, 2233.99, 
"col2_001", false, null,
       bsonDocument2, 234);
   }
@@ -455,12 +457,13 @@ public class OnDuplicateKey2IT extends 
ParallelStatsDisabledIT {
   private static void validateAtomicUpsertOnlyReturnRow(String tableName, 
Connection conn,
     BsonDocument bsonDocument1, BsonDocument bsonDocument2) throws 
SQLException {
     String upsertSql = "UPSERT INTO " + tableName + " (PK1, PK2, PK3, 
COUNTER1, COL3, COL4)"
-      + " VALUES('pk000', -123.98, 'pk003', 1011.202, ?, 123) ON DUPLICATE KEY 
" + "IGNORE";
+      + " VALUES('pk000', -123.98, 'pk003', 1011.202, ?, 123) ON DUPLICATE KEY 
" + "IGNORE "
+      + "RETURNING *";
     validateReturnedRowAfterUpsert(conn, upsertSql, tableName, 1011.202, null, 
true, bsonDocument1,
       bsonDocument1, 123);
 
     upsertSql = "UPSERT INTO " + tableName + " (PK1, PK2, PK3, COUNTER1) "
-      + "VALUES('pk000', -123.98, 'pk003', 0) ON DUPLICATE KEY IGNORE";
+      + "VALUES('pk000', -123.98, 'pk003', 0) ON DUPLICATE KEY IGNORE " + 
"RETURNING *";
     validateReturnedRowAfterUpsert(conn, upsertSql, tableName, 1011.202, null, 
false, null,
       bsonDocument1, 123);
 
@@ -474,14 +477,15 @@ public class OnDuplicateKey2IT extends 
ParallelStatsDisabledIT {
       + " (PK1, PK2, PK3) VALUES('pk000', -123.98, 'pk003') ON DUPLICATE KEY 
UPDATE_ONLY "
       + "COUNTER1 = CASE WHEN COUNTER1 < 2000 THEN COUNTER1 + 1999.99 ELSE 
COUNTER1" + " END, "
       + "COUNTER2 = CASE WHEN COUNTER2 = 'col2_000' THEN 'col2_001' ELSE 
COUNTER2 " + "END, "
-      + "COL3 = ?, " + "COL4 = 234";
+      + "COL3 = ?, " + "COL4 = 234 RETURNING *";
     validateReturnedRowAfterUpsert(conn, upsertSql, tableName, 2233.99, 
"col2_001", true,
       bsonDocument2, bsonDocument2, 234);
 
     upsertSql = "UPSERT INTO " + tableName
       + " (PK1, PK2, PK3) VALUES('pk000', -123.98, 'pk003') ON DUPLICATE KEY 
UPDATE_ONLY "
       + "COUNTER1 = CASE WHEN COUNTER1 < 2000 THEN COUNTER1 + 1999.99 ELSE 
COUNTER1" + " END,"
-      + "COUNTER2 = CASE WHEN COUNTER2 = 'col2_000' THEN 'col2_001' ELSE 
COUNTER2 " + "END";
+      + "COUNTER2 = CASE WHEN COUNTER2 = 'col2_000' THEN 'col2_001' ELSE 
COUNTER2 " + "END "
+      + "RETURNING *";
     validateReturnedRowAfterUpsert(conn, upsertSql, tableName, 2233.99, 
"col2_001", false, null,
       bsonDocument2, 234);
   }
@@ -505,13 +509,15 @@ public class OnDuplicateKey2IT extends 
ParallelStatsDisabledIT {
   private static void validateReturnedRowAfterDelete(PreparedStatement ps, 
String col2,
     boolean isSinglePointLookup, boolean atomicDeleteSuccessful, BsonDocument 
expectedDoc,
     Integer col4) throws SQLException {
-    final Pair<Integer, ResultSet> resultPair =
-      ps.unwrap(PhoenixPreparedStatement.class).executeAtomicUpdateReturnRow();
-    ResultSet resultSet = resultPair.getSecond();
+    ps.executeUpdate();
+    ResultSet resultSet = ps.getResultSet();
     if (!isSinglePointLookup) {
       assertNull(resultSet);
       return;
     }
+    if (resultSet != null) {
+      assertTrue(resultSet.next());
+    }
     if (!atomicDeleteSuccessful) {
       assertTrue(resultSet == null || resultSet.getObject(4) == null);
       return;
@@ -553,8 +559,9 @@ public class OnDuplicateKey2IT extends 
ParallelStatsDisabledIT {
       resultSet = stmt.execute(upsertSql) ? stmt.getResultSet() : null;
       updateCount = stmt.getUpdateCount();
     }
-    boolean isOnDuplicateKey = upsertSql.toUpperCase().contains("ON DUPLICATE 
KEY");
-    if (conn.getAutoCommit() && isOnDuplicateKey) {
+    boolean isReturningRow = upsertSql.toUpperCase().contains("RETURNING *");
+    if (conn.getAutoCommit() && isReturningRow) {
+      assertTrue(resultSet.next());
       assertEquals(success ? 1 : 0, updateCount);
       assertEquals("pk000", resultSet.getString(1));
       assertEquals(-123.98, resultSet.getDouble(2), 0.0);
@@ -591,8 +598,7 @@ public class OnDuplicateKey2IT extends 
ParallelStatsDisabledIT {
       updateCount = resultPair.getFirst();
       resultSet = resultPair.getSecond();
     }
-    boolean isOnDuplicateKey = upsertSql.toUpperCase().contains("ON DUPLICATE 
KEY");
-    if (conn.getAutoCommit() && isOnDuplicateKey) {
+    if (conn.getAutoCommit()) {
       assertEquals(success ? 1 : 0, updateCount);
       if (resultSet != null) {
         assertEquals("pk000", resultSet.getString(1));
diff --git 
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertValuesIT.java 
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertValuesIT.java
index 8ac0978864..d1e5b33e8e 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertValuesIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertValuesIT.java
@@ -23,6 +23,7 @@ import static org.apache.phoenix.util.TestUtil.closeStatement;
 import static org.apache.phoenix.util.TestUtil.closeStmtAndConn;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
@@ -101,6 +102,32 @@ public class UpsertValuesIT extends 
ParallelStatsDisabledIT {
     conn.close();
   }
 
+  @Test
+  public void testPlainUpsertWithReturning() throws Exception {
+    String tableName = generateUniqueName();
+    ensureTableCreated(getUrl(), tableName, TestUtil.PTSDB_NAME, null, null, 
null);
+    Properties props = new Properties();
+    props.put(QueryServices.TASK_HANDLING_INTERVAL_MS_ATTRIB, 
Long.toString(Long.MAX_VALUE));
+    props.put("hbase.client.scanner.timeout.period", "6000000");
+    props.put("phoenix.query.timeoutMs", "6000000");
+    props.put("zookeeper.session.timeout", "6000000");
+    props.put("hbase.rpc.timeout", "6000000");
+    Connection conn = DriverManager.getConnection(getUrl(), props);
+    conn.setAutoCommit(true);
+    PreparedStatement stmt = conn.prepareStatement("UPSERT INTO " + tableName
+      + " (inst,host,\"DATE\") VALUES(?,'b',CURRENT_DATE()) RETURNING *");
+    stmt.setString(1, "a");
+    stmt.execute();
+    ResultSet rs = stmt.getResultSet();
+    assertNotNull(rs);
+    assertTrue(rs.next());
+    assertEquals(1, stmt.getUpdateCount());
+    assertEquals("a", rs.getString(1));
+    assertEquals("b", rs.getString(2));
+    assertFalse(rs.next());
+    conn.close();
+  }
+
   @Test
   public void testUpsertDateValues() throws Exception {
     String tableName = generateUniqueName();
@@ -865,7 +892,8 @@ public class UpsertValuesIT extends ParallelStatsDisabledIT 
{
       createTableStatement.execute();
     }
 
-    testGlobalSequenceUpsertWithTenantConnection(tableName);
+    testGlobalSequenceUpsertWithTenantConnection(tableName, false);
+    testGlobalSequenceUpsertWithTenantConnection(tableName, true);
     testGlobalSequenceUpsertWithGlobalConnection(tableName);
     testTenantSequenceUpsertWithSameTenantConnection(tableName);
     testTenantSequenceUpsertWithDifferentTenantConnection(tableName);
@@ -970,7 +998,8 @@ public class UpsertValuesIT extends ParallelStatsDisabledIT 
{
     }
   }
 
-  private void testGlobalSequenceUpsertWithTenantConnection(String tableName) 
throws Exception {
+  private void testGlobalSequenceUpsertWithTenantConnection(String tableName,
+    boolean withReturningRow) throws Exception {
     String sequenceName = generateUniqueSequenceName();
 
     try (Connection conn = DriverManager.getConnection(getUrl())) {
@@ -984,11 +1013,20 @@ public class UpsertValuesIT extends 
ParallelStatsDisabledIT {
       tenantConn.setAutoCommit(true);
 
       Statement executeUpdateStatement = tenantConn.createStatement();
-      executeUpdateStatement
-        .execute(String.format("UPSERT INTO %s ( SEQUENCE_NUMBER) VALUES " + 
"( NEXT VALUE FOR %s)",
-          tableName, sequenceName));
+      String sql = String.format("UPSERT INTO %s (SEQUENCE_NUMBER) VALUES " + 
"(NEXT VALUE FOR %s)",
+        tableName, sequenceName);
+      if (withReturningRow) {
+        sql += " RETURNING *";
+      }
+      executeUpdateStatement.execute(sql);
 
-      ResultSet rs = executeUpdateStatement.executeQuery("select * from " + 
tableName);
+      ResultSet rs;
+      if (withReturningRow) {
+        rs = executeUpdateStatement.getResultSet();
+      } else {
+        rs = executeUpdateStatement.executeQuery("select * from " + tableName);
+      }
+      assertNotNull(rs);
       assertTrue(rs.next());
       assertEquals("1", rs.getString(1));
       assertFalse(rs.next());
diff --git 
a/phoenix-core/src/test/java/org/apache/phoenix/parse/QueryParserTest.java 
b/phoenix-core/src/test/java/org/apache/phoenix/parse/QueryParserTest.java
index 7eecceeb7c..1b5c8008f7 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/parse/QueryParserTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/parse/QueryParserTest.java
@@ -18,6 +18,7 @@
 package org.apache.phoenix.parse;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -706,6 +707,75 @@ public class QueryParserTest {
     parseQuery(sql);
   }
 
+  @Test
+  public void testPlainUpsertReturningRow() throws Exception {
+    String sql = "upsert into t (k, v) values ( 1, 2 ) RETURNING *";
+    UpsertStatement stmt = parseQuery(sql, UpsertStatement.class);
+    assertTrue(stmt.isReturningRow());
+  }
+
+  @Test
+  public void testPlainUpsertNotReturningRow() throws Exception {
+    String sql = "upsert into t (k, v) values ( 1, 2 )";
+    UpsertStatement stmt = parseQuery(sql, UpsertStatement.class);
+    assertFalse(stmt.isReturningRow());
+  }
+
+  @Test
+  public void testUpsertWithOnDuplicateKey() throws Exception {
+    String sql = "upsert into t (k, v) values ( 1, 2 ) " + "ON DUPLICATE KEY 
UPDATE k = k + 1";
+    parseQuery(sql);
+  }
+
+  @Test
+  public void testUpsertInvalidReturningProjections() throws Exception {
+    String sql =
+      "upsert into t (k, v) values ( 1, 2 ) " + "ON DUPLICATE KEY UPDATE k = k 
+ 1 RETURNING k";
+    try {
+      parseQuery(sql);
+      fail();
+    } catch (PhoenixParserException e) {
+      assertEquals(SQLExceptionCode.MISMATCHED_TOKEN.getErrorCode(), 
e.getErrorCode());
+    }
+  }
+
+  @Test
+  public void testUpsertReturningRow() throws Exception {
+    String sql =
+      "upsert into t (k, v) values ( 1, 2 ) " + "ON DUPLICATE KEY UPDATE k = k 
+ 1 RETURNING *";
+    UpsertStatement stmt = parseQuery(sql, UpsertStatement.class);
+    assertTrue(stmt.isReturningRow());
+  }
+
+  @Test
+  public void testDeleteReturningRow() throws Exception {
+    String sql = "delete from t RETURNING *";
+    parseQuery(sql);
+  }
+
+  @Test
+  public void testDeleteWhereReturningRow() throws Exception {
+    String sql = "DELETE FROM T WHERE PK1 = ? AND PK2 = ? RETURNING *";
+    parseQuery(sql);
+  }
+
+  @Test
+  public void testDeleteWithOrderLimitWhereReturningRow() throws Exception {
+    String sql = "DELETE FROM T WHERE PK1 = ? AND PK2 = ? ORDER BY PK2 LIMIT 1 
RETURNING *";
+    parseQuery(sql);
+  }
+
+  @Test
+  public void testDeleteInvalidReturningRow() throws Exception {
+    String sql = "DELETE FROM T RETURNING PK1";
+    try {
+      parseQuery(sql);
+      fail();
+    } catch (PhoenixParserException e) {
+      assertEquals(SQLExceptionCode.MISMATCHED_TOKEN.getErrorCode(), 
e.getErrorCode());
+    }
+  }
+
   @Test
   public void testHavingWithNot() throws Exception {
     String sql = (("select\n" + "\"WEB_STAT_ALIAS\".\"DOMAIN\" as \"c0\"\n"


Reply via email to