You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@phoenix.apache.org by st...@apache.org on 2023/11/15 09:46:20 UTC

(phoenix) branch master updated: PHOENIX-7095 Implement Statement.closeOnCompletion() and fix related close() bugs

This is an automated email from the ASF dual-hosted git repository.

stoty 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 b64a9736b7 PHOENIX-7095 Implement Statement.closeOnCompletion() and fix related close() bugs
b64a9736b7 is described below

commit b64a9736b78ae2d513669849986fad3bb5894279
Author: Istvan Toth <st...@apache.org>
AuthorDate: Thu Nov 2 09:52:07 2023 +0100

    PHOENIX-7095 Implement Statement.closeOnCompletion() and fix related close() bugs
    
    also set closeOnCompletion() on the statements generating metadata resultsets
---
 .../phoenix/end2end/ClientHashAggregateIT.java     |  38 +-
 .../java/org/apache/phoenix/end2end/InListIT.java  |   7 +-
 .../phoenix/end2end/QueryDatabaseMetaDataIT.java   |  50 ++
 .../org/apache/phoenix/jdbc/PhoenixConnection.java |  45 +-
 .../phoenix/jdbc/PhoenixDatabaseMetaData.java      | 502 +++++++++++----------
 .../org/apache/phoenix/jdbc/PhoenixResultSet.java  |   7 +-
 .../org/apache/phoenix/jdbc/PhoenixStatement.java  |  54 ++-
 .../apache/phoenix/jdbc/PhoenixStatementTest.java  |  38 ++
 8 files changed, 436 insertions(+), 305 deletions(-)

diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ClientHashAggregateIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ClientHashAggregateIT.java
index b4b8ec731c..c071b7a836 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ClientHashAggregateIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ClientHashAggregateIT.java
@@ -41,13 +41,10 @@ public class ClientHashAggregateIT extends ParallelStatsDisabledIT {
     public void testSalted() throws Exception { 
 
         Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
-        Connection conn = DriverManager.getConnection(getUrl(), props);
-   
-        try {
+
+        try (Connection conn = DriverManager.getConnection(getUrl(), props)) {
             String table = createSalted(conn);
             testTable(conn, table);
-        } finally {
-            conn.close();
         }
     }
     
@@ -168,36 +165,39 @@ public class ClientHashAggregateIT extends ParallelStatsDisabledIT {
         String hashQuery = getQuery(table, true, swap, sort);
         String sortQuery = getQuery(table, false, swap, sort);
         Statement stmt = conn.createStatement();
-        ResultSet hrs = stmt.executeQuery(hashQuery);
-        ResultSet srs = stmt.executeQuery(sortQuery);
 
-        try {
+        try (ResultSet hrs = stmt.executeQuery(hashQuery);) {
             if (c1 > 0) {
                 assertTrue(hrs.next());
-                assertTrue(srs.next());
-                assertEquals(hrs.getInt("v1"), srs.getInt("v1"));
-                assertEquals(hrs.getInt("v2"), srs.getInt("v2"));
-                assertEquals(hrs.getInt("c"), srs.getInt("c"));
                 assertEquals(hrs.getInt("v1"), 1);
                 assertEquals(hrs.getInt("v2"), 2);
                 assertEquals(hrs.getInt("c"), c1);
             }
             if (c2 > 0) {
                 assertTrue(hrs.next());
-                assertTrue(srs.next());
-                assertEquals(hrs.getInt("v1"), srs.getInt("v1"));
-                assertEquals(hrs.getInt("v2"), srs.getInt("v2"));
-                assertEquals(hrs.getInt("c"), srs.getInt("c"));
                 assertEquals(hrs.getInt("v1"), 2);
                 assertEquals(hrs.getInt("v2"), 1);
                 assertEquals(hrs.getInt("c"), c2);
             }
             assertFalse(hrs.next());
+        }
+
+        try (ResultSet srs = stmt.executeQuery(sortQuery)) {
+            if (c1 > 0) {
+                assertTrue(srs.next());
+                assertEquals(srs.getInt("v1"), 1);
+                assertEquals(srs.getInt("v2"), 2);
+                assertEquals(srs.getInt("c"), c1);
+            }
+            if (c2 > 0) {
+                assertTrue(srs.next());
+                assertEquals(srs.getInt("v1"), 2);
+                assertEquals(srs.getInt("v2"), 1);
+                assertEquals(srs.getInt("c"), c2);
+            }
             assertFalse(srs.next());
-        } finally {
-            hrs.close();
-            srs.close();
         }
+
     }
 
     private void dropTable(Connection conn, String table) throws Exception {
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/InListIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/InListIT.java
index 6728f6286e..d154ecdfd5 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/InListIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/InListIT.java
@@ -792,12 +792,13 @@ public class InListIT extends ParallelStatsDisabledIT {
             Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM SYSTEM.CATALOG WHERE " +
                     "TENANT_ID IN ('', 'FOO')");
+            assertTrue(rs.next());
+            int result1 = rs.getInt(1);
+            assertEquals(0, result1);
             ResultSet rs2 = stmt.executeQuery("SELECT COUNT(*) FROM SYSTEM.CATALOG WHERE " +
                     "TENANT_ID = '' OR TENANT_ID = 'FOO'");
-            assertTrue(rs.next());
             assertTrue(rs2.next());
-            assertEquals(rs.getInt(1), rs2.getInt(1));
-            assertEquals(0, rs.getInt(1));
+            assertEquals(result1, rs2.getInt(1));
         }
     }
 
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryDatabaseMetaDataIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryDatabaseMetaDataIT.java
index 89dbbde5ad..abdec219d4 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryDatabaseMetaDataIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryDatabaseMetaDataIT.java
@@ -139,6 +139,8 @@ public class QueryDatabaseMetaDataIT extends ParallelStatsDisabledIT {
             assertEquals(rs.getString("TABLE_NAME"), viewName);
             assertEquals(PTableType.VIEW.toString(), rs.getString("TABLE_TYPE"));
             assertFalse(rs.next());
+            rs.close();
+            assertTrue(rs.getStatement().isClosed());
         }
     }
     
@@ -163,6 +165,8 @@ public class QueryDatabaseMetaDataIT extends ParallelStatsDisabledIT {
             assertEquals(rs.getString(3), tableAName);
             assertEquals(PTableType.TABLE.toString(), rs.getString(4));
             assertFalse(rs.next());
+            rs.close();
+            assertTrue(rs.getStatement().isClosed());
 
             rs = dbmd.getTables(null, null, null, null);
             assertTrue(rs.next());
@@ -214,6 +218,8 @@ public class QueryDatabaseMetaDataIT extends ParallelStatsDisabledIT {
             assertEquals(PTableType.TABLE.toString(), rs.getString("TABLE_TYPE"));
             assertEquals("false", rs.getString(PhoenixDatabaseMetaData.TRANSACTIONAL));
             assertEquals(Boolean.FALSE, rs.getBoolean(PhoenixDatabaseMetaData.IS_NAMESPACE_MAPPED));
+            rs.close();
+            assertTrue(rs.getStatement().isClosed());
 
             rs = dbmd.getTables(null, tableCSchema, tableC, null);
             assertTrue(rs.next());
@@ -227,6 +233,8 @@ public class QueryDatabaseMetaDataIT extends ParallelStatsDisabledIT {
             assertEquals(tableC, rs.getString("TABLE_NAME"));
             assertEquals(PTableType.TABLE.toString(), rs.getString("TABLE_TYPE"));
             assertFalse(rs.next());
+            rs.close();
+            assertTrue(rs.getStatement().isClosed());
 
             rs = dbmd.getTables(null, "", "%TABLE", new String[] { PTableType.TABLE.toString() });
             assertTrue(rs.next());
@@ -238,6 +246,8 @@ public class QueryDatabaseMetaDataIT extends ParallelStatsDisabledIT {
             assertEquals(tableS, rs.getString("TABLE_NAME"));
             assertEquals(PTableType.TABLE.toString(), rs.getString("TABLE_TYPE"));
             assertFalse(rs.next());
+            rs.close();
+            assertTrue(rs.getStatement().isClosed());
         }
     }
 
@@ -257,6 +267,8 @@ public class QueryDatabaseMetaDataIT extends ParallelStatsDisabledIT {
             assertTrue(rs.next());
             assertEquals("VIEW", rs.getString(1));
             assertFalse(rs.next());
+            rs.close();
+            assertTrue(rs.getStatement().isClosed());
         }
     }
 
@@ -302,6 +314,8 @@ public class QueryDatabaseMetaDataIT extends ParallelStatsDisabledIT {
             assertEquals(schema4, rs.getString("TABLE_SCHEM"));
             assertEquals(seq4, rs.getString("TABLE_NAME"));
             assertFalse(rs.next());
+            rs.close();
+            assertTrue(rs.getStatement().isClosed());
 
             String foo = generateUniqueName();
             String basSchema = generateUniqueName();
@@ -345,6 +359,8 @@ public class QueryDatabaseMetaDataIT extends ParallelStatsDisabledIT {
             assertEquals(bas, rs.getString("TABLE_NAME"));
             assertEquals(PTableType.TABLE.toString(), rs.getString("TABLE_TYPE"));
             assertFalse(rs.next());
+            rs.close();
+            assertTrue(rs.getStatement().isClosed());
 
             rs =
                     dbmd.getTables(null, "B%", null,
@@ -360,6 +376,8 @@ public class QueryDatabaseMetaDataIT extends ParallelStatsDisabledIT {
             assertEquals(seq3, rs.getString("TABLE_NAME"));
             assertEquals(PhoenixDatabaseMetaData.SEQUENCE_TABLE_TYPE, rs.getString("TABLE_TYPE"));
             assertFalse(rs.next());
+            rs.close();
+            assertTrue(rs.getStatement().isClosed());
         }
     }
 
@@ -433,12 +451,16 @@ public class QueryDatabaseMetaDataIT extends ParallelStatsDisabledIT {
             assertEquals(rs.getString(1), schema1);
             assertEquals(rs.getString(2), null);
             assertFalse(rs.next());
+            rs.close();
+            assertTrue(rs.getStatement().isClosed());
 
             rs = dbmd.getSchemas(null, "");
             assertTrue(rs.next());
             assertEquals(rs.getString(1), null);
             assertEquals(rs.getString(2), null);
             assertFalse(rs.next());
+            rs.close();
+            assertTrue(rs.getStatement().isClosed());
 
             rs = dbmd.getSchemas(null, null);
             assertTrue(rs.next());
@@ -452,6 +474,8 @@ public class QueryDatabaseMetaDataIT extends ParallelStatsDisabledIT {
             assertEquals(schema1, rs.getString("TABLE_SCHEM"));
             assertEquals(null, rs.getString("TABLE_CATALOG"));
             assertFalse(rs.next());
+            rs.close();
+            assertTrue(rs.getStatement().isClosed());
         }
     }
 
@@ -538,6 +562,8 @@ public class QueryDatabaseMetaDataIT extends ParallelStatsDisabledIT {
         assertEquals(3, rs.getInt("DECIMAL_DIGITS"));
 
         assertFalse(rs.next());
+        rs.close();
+        assertTrue(rs.getStatement().isClosed());
 
         // Look up only columns in a column family
         rs = dbmd.getColumns(null, "", table, "A.");
@@ -555,6 +581,8 @@ public class QueryDatabaseMetaDataIT extends ParallelStatsDisabledIT {
         assertTrue(rs.wasNull());
 
         assertFalse(rs.next());
+        rs.close();
+        assertTrue(rs.getStatement().isClosed());
 
         // Look up KV columns in a column family
         rs = dbmd.getColumns("", "", table, "%.COL%");
@@ -621,6 +649,8 @@ public class QueryDatabaseMetaDataIT extends ParallelStatsDisabledIT {
         assertEquals(3, rs.getInt("DECIMAL_DIGITS"));
 
         assertFalse(rs.next());
+        rs.close();
+        assertTrue(rs.getStatement().isClosed());
 
         // Look up KV columns in a column family
         rs = dbmd.getColumns("", "", table, "B.COL2");
@@ -630,6 +660,8 @@ public class QueryDatabaseMetaDataIT extends ParallelStatsDisabledIT {
         assertEquals(SchemaUtil.normalizeIdentifier("b"), rs.getString("COLUMN_FAMILY"));
         assertEquals(SchemaUtil.normalizeIdentifier("col2"), rs.getString("COLUMN_NAME"));
         assertFalse(rs.next());
+        rs.close();
+        assertTrue(rs.getStatement().isClosed());
 
         String table2 = generateUniqueName();
         ensureTableCreated(getUrl(), table2, TABLE_WITH_SALTING, null);
@@ -637,6 +669,8 @@ public class QueryDatabaseMetaDataIT extends ParallelStatsDisabledIT {
         assertTrue(rs.next());
         assertEquals(1, rs.getInt("ORDINAL_POSITION"));
         assertFalse(rs.next());
+        rs.close();
+        assertTrue(rs.getStatement().isClosed());
 
     }
 
@@ -660,6 +694,8 @@ public class QueryDatabaseMetaDataIT extends ParallelStatsDisabledIT {
             assertEquals(1, rs.getInt("KEY_SEQ"));
             assertEquals(null, rs.getString("PK_NAME"));
             assertFalse(rs.next());
+            rs.close();
+            assertTrue(rs.getStatement().isClosed());
 
             rs = dbmd.getPrimaryKeys(null, schema2, table2);
             assertTrue(rs.next());
@@ -693,6 +729,8 @@ public class QueryDatabaseMetaDataIT extends ParallelStatsDisabledIT {
                                                                                          // row
 
             assertFalse(rs.next());
+            rs.close();
+            assertTrue(rs.getStatement().isClosed());
 
             rs = dbmd.getColumns("", schema2, table2, null);
             assertTrue(rs.next());
@@ -735,6 +773,8 @@ public class QueryDatabaseMetaDataIT extends ParallelStatsDisabledIT {
             assertEquals(SchemaUtil.normalizeIdentifier("key_prefix"), rs.getString("COLUMN_NAME"));
 
             assertFalse(rs.next());
+            rs.close();
+            assertTrue(rs.getStatement().isClosed());
 
             String table3 = generateUniqueName();
             conn.createStatement().execute(
@@ -749,6 +789,8 @@ public class QueryDatabaseMetaDataIT extends ParallelStatsDisabledIT {
             assertEquals(1, rs.getInt("KEY_SEQ"));
             assertEquals(null, rs.getString("PK_NAME"));
             assertFalse(rs.next());
+            rs.close();
+            assertTrue(rs.getStatement().isClosed());
         }
     }
 
@@ -811,6 +853,8 @@ public class QueryDatabaseMetaDataIT extends ParallelStatsDisabledIT {
             assertEquals(SchemaUtil.normalizeIdentifier("b"), rs.getString("COLUMN_FAMILY"));
             assertEquals(SchemaUtil.normalizeIdentifier("col5"), rs.getString("COLUMN_NAME"));
             assertFalse(rs.next());
+            rs.close();
+            assertTrue(rs.getStatement().isClosed());
         }
     }
 
@@ -959,6 +1003,8 @@ public class QueryDatabaseMetaDataIT extends ParallelStatsDisabledIT {
             assertTrue(rs.next());
             assertEquals(ViewType.MAPPED.name(), rs.getString(PhoenixDatabaseMetaData.VIEW_TYPE));
             assertFalse(rs.next());
+            rs.close();
+            assertTrue(rs.getStatement().isClosed());
 
             String deleteStmt = "DELETE FROM " + tableName;
             PreparedStatement ps = pconn.prepareStatement(deleteStmt);
@@ -1172,6 +1218,8 @@ public class QueryDatabaseMetaDataIT extends ParallelStatsDisabledIT {
             assertEquals(schema1, rs.getString("TABLE_SCHEM"));
             assertEquals(table1, rs.getString("TABLE_NAME"));
             assertFalse(rs.next());
+            rs.close();
+            assertTrue(rs.getStatement().isClosed());
         }
     }
 
@@ -1201,5 +1249,7 @@ public class QueryDatabaseMetaDataIT extends ParallelStatsDisabledIT {
             }
         }
         assertTrue("Could not find REMARKS column", foundRemarksColumn);
+        rs.close();
+        assertTrue(rs.getStatement().isClosed());
     }
 }
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixConnection.java b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixConnection.java
index 3c078d6ecc..c7afcb0b9e 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixConnection.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixConnection.java
@@ -50,13 +50,12 @@ import java.sql.Savepoint;
 import java.sql.Statement;
 import java.sql.Struct;
 import java.text.Format;
-
-
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.HashMap;
 import java.util.Properties;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentLinkedQueue;
@@ -120,6 +119,11 @@ import org.apache.phoenix.schema.types.PUnsignedDate;
 import org.apache.phoenix.schema.types.PUnsignedTime;
 import org.apache.phoenix.schema.types.PUnsignedTimestamp;
 import org.apache.phoenix.schema.types.PVarbinary;
+import org.apache.phoenix.thirdparty.com.google.common.annotations.VisibleForTesting;
+import org.apache.phoenix.thirdparty.com.google.common.base.Objects;
+import org.apache.phoenix.thirdparty.com.google.common.base.Strings;
+import org.apache.phoenix.thirdparty.com.google.common.collect.ImmutableMap;
+import org.apache.phoenix.thirdparty.com.google.common.collect.ImmutableMap.Builder;
 import org.apache.phoenix.trace.util.Tracing;
 import org.apache.phoenix.transaction.PhoenixTransactionContext;
 import org.apache.phoenix.util.DateUtil;
@@ -134,13 +138,6 @@ import org.apache.phoenix.util.SQLCloseables;
 import org.apache.phoenix.util.SchemaUtil;
 import org.apache.phoenix.util.VarBinaryFormatter;
 
-import org.apache.phoenix.thirdparty.com.google.common.annotations.VisibleForTesting;
-import org.apache.phoenix.thirdparty.com.google.common.base.Objects;
-import org.apache.phoenix.thirdparty.com.google.common.base.Strings;
-import org.apache.phoenix.thirdparty.com.google.common.collect.ImmutableMap;
-import org.apache.phoenix.thirdparty.com.google.common.collect.ImmutableMap.Builder;
-import org.apache.phoenix.thirdparty.com.google.common.collect.Lists;
-
 /**
  * 
  * JDBC Connection implementation of Phoenix. Currently the following are
@@ -162,7 +159,7 @@ public class PhoenixConnection implements MetaDataMutated, SQLCloseable, Phoenix
     private final Long scn;
     private final boolean buildingIndex;
     private MutationState mutationState;
-    private List<PhoenixStatement> statements = new ArrayList<>();
+    private HashSet<PhoenixStatement> statements = new HashSet<>();
     private boolean isAutoFlush = false;
     private boolean isAutoCommit = false;
     private final PName tenantId;
@@ -720,17 +717,17 @@ public class PhoenixConnection implements MetaDataMutated, SQLCloseable, Phoenix
     }
 
     private void closeStatements() throws SQLException {
-        List<? extends PhoenixStatement> statements = this.statements;
-        // create new list to prevent close of statements
-        // from modifying this list.
-        this.statements = Lists.newArrayList();
         try {
             mutationState.rollback();
         } catch (SQLException e) {
             // ignore any exceptions while rolling back
         } finally {
             try {
-                SQLCloseables.closeAll(statements);
+                // create new set to prevent close of statements from modifying this collection.
+                // TODO This could be optimized out by decoupling closing the stmt and removing it
+                // from the connection.
+                HashSet<? extends PhoenixStatement> statementsCopy = new HashSet<>(this.statements);
+                SQLCloseables.closeAll(statementsCopy);
             } finally {
                 statements.clear();
             }
@@ -784,15 +781,13 @@ public class PhoenixConnection implements MetaDataMutated, SQLCloseable, Phoenix
                 clearMetrics();
             }
             try {
-                if (traceScope != null) {
-                    traceScope.close();
-                }
                 closeStatements();
                 if (childConnections != null) {
                     SQLCloseables.closeAllQuietly(childConnections);
                 }
-
-
+                if (traceScope != null) {
+                    traceScope.close();
+                }
             } finally {
                 services.removeConnection(this);
             }
@@ -868,10 +863,6 @@ public class PhoenixConnection implements MetaDataMutated, SQLCloseable, Phoenix
         throw new SQLFeatureNotSupportedException();
     }
 
-    public List<PhoenixStatement> getStatements() {
-        return statements;
-    }
-
     @Override
     public Statement createStatement() throws SQLException {
         checkOpen();
@@ -1360,20 +1351,24 @@ public class PhoenixConnection implements MetaDataMutated, SQLCloseable, Phoenix
         this.traceScope = traceScope;
     }
 
+    @Override
     public Map<String, Map<MetricType, Long>> getMutationMetrics() {
         return mutationState.getMutationMetricQueue().aggregate();
     }
 
+    @Override
     public Map<String, Map<MetricType, Long>> getReadMetrics() {
         return mutationState.getReadMetricQueue() != null ? mutationState
                 .getReadMetricQueue().aggregate() : Collections
                 .<String, Map<MetricType, Long>> emptyMap();
     }
 
+    @Override
     public boolean isRequestLevelMetricsEnabled() {
         return isRequestLevelMetricsEnabled;
     }
 
+    @Override
     public void clearMetrics() {
         mutationState.getMutationMetricQueue().clearMetrics();
         if (mutationState.getReadMetricQueue() != null) {
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixDatabaseMetaData.java b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixDatabaseMetaData.java
index ada35cc3e2..112cabd7d2 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixDatabaseMetaData.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixDatabaseMetaData.java
@@ -362,7 +362,6 @@ public class PhoenixDatabaseMetaData implements DatabaseMetaData {
     public static final byte[] SYSTEM_MUTEX_FAMILY_NAME_BYTES = TABLE_FAMILY_BYTES;
     
     private final PhoenixConnection connection;
-    private final ResultSet emptyResultSet;
 
     public static final int MAX_LOCAL_SI_VERSION_DISALLOW = VersionUtil.encodeVersion("0", "98", "8");
     public static final int MIN_LOCAL_SI_VERSION_DISALLOW = VersionUtil.encodeVersion("0", "98", "6");
@@ -455,10 +454,15 @@ public class PhoenixDatabaseMetaData implements DatabaseMetaData {
             
     
     PhoenixDatabaseMetaData(PhoenixConnection connection) throws SQLException {
-        this.emptyResultSet = new PhoenixResultSet(ResultIterator.EMPTY_ITERATOR, RowProjector.EMPTY_PROJECTOR, new StatementContext(new PhoenixStatement(connection), false));
         this.connection = connection;
     }
 
+    private PhoenixResultSet getEmptyResultSet() throws SQLException {
+        PhoenixStatement stmt = new PhoenixStatement(connection);
+        stmt.closeOnCompletion();
+        return new PhoenixResultSet(ResultIterator.EMPTY_ITERATOR, RowProjector.EMPTY_PROJECTOR, new StatementContext(stmt, false));
+    }
+
     @Override
     public boolean allProceduresAreCallable() throws SQLException {
         return false;
@@ -497,13 +501,13 @@ public class PhoenixDatabaseMetaData implements DatabaseMetaData {
     @Override
     public ResultSet getAttributes(String catalog, String schemaPattern, String typeNamePattern,
             String attributeNamePattern) throws SQLException {
-        return emptyResultSet;
+        return getEmptyResultSet();
     }
 
     @Override
     public ResultSet getBestRowIdentifier(String catalog, String schema, String table, int scope, boolean nullable)
             throws SQLException {
-        return emptyResultSet;
+        return getEmptyResultSet();
     }
 
     @Override
@@ -519,18 +523,19 @@ public class PhoenixDatabaseMetaData implements DatabaseMetaData {
     @Override
     public ResultSet getCatalogs() throws SQLException {
         PreparedStatement stmt = QueryUtil.getCatalogsStmt(connection);
+        stmt.closeOnCompletion();
         return stmt.executeQuery();
     }
 
     @Override
     public ResultSet getClientInfoProperties() throws SQLException {
-        return emptyResultSet;
+        return getEmptyResultSet();
     }
 
     @Override
     public ResultSet getColumnPrivileges(String catalog, String schema, String table, String columnNamePattern)
             throws SQLException {
-        return emptyResultSet;
+        return getEmptyResultSet();
     }
 
     public static final String GLOBAL_TENANANTS_ONLY = "null";
@@ -776,177 +781,180 @@ public class PhoenixDatabaseMetaData implements DatabaseMetaData {
                 }
             }
         }
-        ResultSet rs = getTables(catalog, schemaPattern, tableNamePattern, null);
-        while (rs.next()) {
-            String schemaName = rs.getString(TABLE_SCHEM);
-            String tableName = rs.getString(TABLE_NAME);
-            String tenantId = rs.getString(TABLE_CAT);
-            String fullTableName = SchemaUtil.getTableName(schemaName, tableName);
-            PTable table = PhoenixRuntime.getTableNoCache(connection, fullTableName);
-            boolean isSalted = table.getBucketNum()!=null;
-            boolean tenantColSkipped = false;
-            List<PColumn> columns = table.getColumns();
-            int startOffset = isSalted ? 1 : 0;
-			columns = Lists.newArrayList(columns.subList(startOffset, columns.size()));
-            for (PColumn column : columns) {
-                if (isTenantSpecificConnection && column.equals(table.getPKColumns().get(startOffset))) {
-                    // skip the tenant column
-                    tenantColSkipped = true;
-                    continue;
-                }
-                String columnFamily = column.getFamilyName()!=null ? column.getFamilyName().getString() : null;
-                String columnName = column.getName().getString();
-                if (cfPattern != null && cfPattern.length() > 0) { // if null or empty, will pick up all columns
-                    if (columnFamily==null || !match(columnFamily, cfPattern)) {
+        try (ResultSet rs = getTables(catalog, schemaPattern, tableNamePattern, null)) {
+            while (rs.next()) {
+                String schemaName = rs.getString(TABLE_SCHEM);
+                String tableName = rs.getString(TABLE_NAME);
+                String tenantId = rs.getString(TABLE_CAT);
+                String fullTableName = SchemaUtil.getTableName(schemaName, tableName);
+                PTable table = PhoenixRuntime.getTableNoCache(connection, fullTableName);
+                boolean isSalted = table.getBucketNum()!=null;
+                boolean tenantColSkipped = false;
+                List<PColumn> columns = table.getColumns();
+                int startOffset = isSalted ? 1 : 0;
+                columns = Lists.newArrayList(columns.subList(startOffset, columns.size()));
+                for (PColumn column : columns) {
+                    if (isTenantSpecificConnection && column.equals(table.getPKColumns().get(startOffset))) {
+                        // skip the tenant column
+                        tenantColSkipped = true;
                         continue;
                     }
-                }
-                if (colPattern != null && colPattern.length() > 0) {
-                    if (!match(columnName, colPattern)) {
-                        continue;
+                    String columnFamily = column.getFamilyName()!=null ? column.getFamilyName().getString() : null;
+                    String columnName = column.getName().getString();
+                    if (cfPattern != null && cfPattern.length() > 0) { // if null or empty, will pick up all columns
+                        if (columnFamily==null || !match(columnFamily, cfPattern)) {
+                            continue;
+                        }
                     }
-                }
-                // generate row key
-                // TENANT_ID, TABLE_SCHEM, TABLE_NAME , COLUMN_NAME are row key columns
-                byte[] rowKey =
-                        SchemaUtil.getColumnKey(tenantId, schemaName, tableName, columnName, null);
-
-                // add one cell for each column info
-                List<Cell> cells = Lists.newArrayListWithCapacity(25);
-                // DATA_TYPE
-                cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
-                    DATA_TYPE_BYTES,
-                    MetaDataProtocol.MIN_TABLE_TIMESTAMP,
-                    PInteger.INSTANCE.toBytes(column.getDataType().getResultSetSqlType())));
-                // TYPE_NAME
-                cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
-                    Bytes.toBytes(TYPE_NAME), MetaDataProtocol.MIN_TABLE_TIMESTAMP,
-                    column.getDataType().getSqlTypeNameBytes()));
-                // COLUMN_SIZE
-                cells.add(
-                    PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES, COLUMN_SIZE_BYTES,
+                    if (colPattern != null && colPattern.length() > 0) {
+                        if (!match(columnName, colPattern)) {
+                            continue;
+                        }
+                    }
+                    // generate row key
+                    // TENANT_ID, TABLE_SCHEM, TABLE_NAME , COLUMN_NAME are row key columns
+                    byte[] rowKey =
+                            SchemaUtil.getColumnKey(tenantId, schemaName, tableName, columnName, null);
+
+                    // add one cell for each column info
+                    List<Cell> cells = Lists.newArrayListWithCapacity(25);
+                    // DATA_TYPE
+                    cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
+                        DATA_TYPE_BYTES,
+                        MetaDataProtocol.MIN_TABLE_TIMESTAMP,
+                        PInteger.INSTANCE.toBytes(column.getDataType().getResultSetSqlType())));
+                    // TYPE_NAME
+                    cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
+                        Bytes.toBytes(TYPE_NAME), MetaDataProtocol.MIN_TABLE_TIMESTAMP,
+                        column.getDataType().getSqlTypeNameBytes()));
+                    // COLUMN_SIZE
+                    cells.add(
+                        PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES, COLUMN_SIZE_BYTES,
+                            MetaDataProtocol.MIN_TABLE_TIMESTAMP,
+                            column.getMaxLength() != null
+                                    ? PInteger.INSTANCE.toBytes(column.getMaxLength())
+                                    : ByteUtil.EMPTY_BYTE_ARRAY));
+                    // BUFFER_LENGTH
+                    cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
+                        Bytes.toBytes(BUFFER_LENGTH), MetaDataProtocol.MIN_TABLE_TIMESTAMP,
+                        ByteUtil.EMPTY_BYTE_ARRAY));
+                    // DECIMAL_DIGITS
+                    cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
+                        DECIMAL_DIGITS_BYTES,
                         MetaDataProtocol.MIN_TABLE_TIMESTAMP,
-                        column.getMaxLength() != null
-                                ? PInteger.INSTANCE.toBytes(column.getMaxLength())
+                        column.getScale() != null ? PInteger.INSTANCE.toBytes(column.getScale())
                                 : ByteUtil.EMPTY_BYTE_ARRAY));
-                // BUFFER_LENGTH
-                cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
-                    Bytes.toBytes(BUFFER_LENGTH), MetaDataProtocol.MIN_TABLE_TIMESTAMP,
-                    ByteUtil.EMPTY_BYTE_ARRAY));
-                // DECIMAL_DIGITS
-                cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
-                    DECIMAL_DIGITS_BYTES,
-                    MetaDataProtocol.MIN_TABLE_TIMESTAMP,
-                    column.getScale() != null ? PInteger.INSTANCE.toBytes(column.getScale())
-                            : ByteUtil.EMPTY_BYTE_ARRAY));
-                // NUM_PREC_RADIX
-                cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
-                    Bytes.toBytes(NUM_PREC_RADIX), MetaDataProtocol.MIN_TABLE_TIMESTAMP,
-                    ByteUtil.EMPTY_BYTE_ARRAY));
-                // NULLABLE
-                cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
-                    NULLABLE_BYTES,
-                    MetaDataProtocol.MIN_TABLE_TIMESTAMP,
-                    PInteger.INSTANCE.toBytes(SchemaUtil.getIsNullableInt(column.isNullable()))));
-                // REMARKS
-                cells.add(
-                    PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
-                        Bytes.toBytes(REMARKS),
-                        MetaDataProtocol.MIN_TABLE_TIMESTAMP, ByteUtil.EMPTY_BYTE_ARRAY));
-                // COLUMN_DEF
-                cells.add(
-                    PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
-                        Bytes.toBytes(COLUMN_DEF),
+                    // NUM_PREC_RADIX
+                    cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
+                        Bytes.toBytes(NUM_PREC_RADIX), MetaDataProtocol.MIN_TABLE_TIMESTAMP,
+                        ByteUtil.EMPTY_BYTE_ARRAY));
+                    // NULLABLE
+                    cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
+                        NULLABLE_BYTES,
                         MetaDataProtocol.MIN_TABLE_TIMESTAMP,
-                        PVarchar.INSTANCE.toBytes(column.getExpressionStr())));
-                // SQL_DATA_TYPE
-                cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
-                    Bytes.toBytes(SQL_DATA_TYPE), MetaDataProtocol.MIN_TABLE_TIMESTAMP,
-                    ByteUtil.EMPTY_BYTE_ARRAY));
-                // SQL_DATETIME_SUB
-                cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
-                    Bytes.toBytes(SQL_DATETIME_SUB), MetaDataProtocol.MIN_TABLE_TIMESTAMP,
-                    ByteUtil.EMPTY_BYTE_ARRAY));
-                // CHAR_OCTET_LENGTH
-                cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
-                    Bytes.toBytes(CHAR_OCTET_LENGTH), MetaDataProtocol.MIN_TABLE_TIMESTAMP,
-                    ByteUtil.EMPTY_BYTE_ARRAY));
-                // ORDINAL_POSITION
-                int ordinal =
-                        column.getPosition() + (isSalted ? 0 : 1) - (tenantColSkipped ? 1 : 0);
-                cells.add(
-                    PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
-                        ORDINAL_POSITION_BYTES,
-                        MetaDataProtocol.MIN_TABLE_TIMESTAMP, PInteger.INSTANCE.toBytes(ordinal)));
-                String isNullable =
-                        column.isNullable() ? Boolean.TRUE.toString() : Boolean.FALSE.toString();
-                // IS_NULLABLE
-                cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
-                    Bytes.toBytes(IS_NULLABLE), MetaDataProtocol.MIN_TABLE_TIMESTAMP,
-                    PVarchar.INSTANCE.toBytes(isNullable)));
-                // SCOPE_CATALOG
-                cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
-                    Bytes.toBytes(SCOPE_CATALOG), MetaDataProtocol.MIN_TABLE_TIMESTAMP,
-                    ByteUtil.EMPTY_BYTE_ARRAY));
-                // SCOPE_SCHEMA
-                cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
-                    Bytes.toBytes(SCOPE_SCHEMA), MetaDataProtocol.MIN_TABLE_TIMESTAMP,
-                    ByteUtil.EMPTY_BYTE_ARRAY));
-                // SCOPE_TABLE
-                cells.add(
-                    PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
-                        Bytes.toBytes(SCOPE_TABLE),
-                        MetaDataProtocol.MIN_TABLE_TIMESTAMP, ByteUtil.EMPTY_BYTE_ARRAY));
-                // SOURCE_DATA_TYPE
-                cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
-                    Bytes.toBytes(SOURCE_DATA_TYPE), MetaDataProtocol.MIN_TABLE_TIMESTAMP,
-                    ByteUtil.EMPTY_BYTE_ARRAY));
-                // IS_AUTOINCREMENT
-                cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
-                    Bytes.toBytes(IS_AUTOINCREMENT), MetaDataProtocol.MIN_TABLE_TIMESTAMP,
-                    ByteUtil.EMPTY_BYTE_ARRAY));
-                // ARRAY_SIZE
-                cells.add(
-                    PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES, ARRAY_SIZE_BYTES,
+                        PInteger.INSTANCE.toBytes(SchemaUtil.getIsNullableInt(column.isNullable()))));
+                    // REMARKS
+                    cells.add(
+                        PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
+                            Bytes.toBytes(REMARKS),
+                            MetaDataProtocol.MIN_TABLE_TIMESTAMP, ByteUtil.EMPTY_BYTE_ARRAY));
+                    // COLUMN_DEF
+                    cells.add(
+                        PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
+                            Bytes.toBytes(COLUMN_DEF),
+                            MetaDataProtocol.MIN_TABLE_TIMESTAMP,
+                            PVarchar.INSTANCE.toBytes(column.getExpressionStr())));
+                    // SQL_DATA_TYPE
+                    cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
+                        Bytes.toBytes(SQL_DATA_TYPE), MetaDataProtocol.MIN_TABLE_TIMESTAMP,
+                        ByteUtil.EMPTY_BYTE_ARRAY));
+                    // SQL_DATETIME_SUB
+                    cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
+                        Bytes.toBytes(SQL_DATETIME_SUB), MetaDataProtocol.MIN_TABLE_TIMESTAMP,
+                        ByteUtil.EMPTY_BYTE_ARRAY));
+                    // CHAR_OCTET_LENGTH
+                    cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
+                        Bytes.toBytes(CHAR_OCTET_LENGTH), MetaDataProtocol.MIN_TABLE_TIMESTAMP,
+                        ByteUtil.EMPTY_BYTE_ARRAY));
+                    // ORDINAL_POSITION
+                    int ordinal =
+                            column.getPosition() + (isSalted ? 0 : 1) - (tenantColSkipped ? 1 : 0);
+                    cells.add(
+                        PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
+                            ORDINAL_POSITION_BYTES,
+                            MetaDataProtocol.MIN_TABLE_TIMESTAMP, PInteger.INSTANCE.toBytes(ordinal)));
+                    String isNullable =
+                            column.isNullable() ? Boolean.TRUE.toString() : Boolean.FALSE.toString();
+                    // IS_NULLABLE
+                    cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
+                        Bytes.toBytes(IS_NULLABLE), MetaDataProtocol.MIN_TABLE_TIMESTAMP,
+                        PVarchar.INSTANCE.toBytes(isNullable)));
+                    // SCOPE_CATALOG
+                    cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
+                        Bytes.toBytes(SCOPE_CATALOG), MetaDataProtocol.MIN_TABLE_TIMESTAMP,
+                        ByteUtil.EMPTY_BYTE_ARRAY));
+                    // SCOPE_SCHEMA
+                    cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
+                        Bytes.toBytes(SCOPE_SCHEMA), MetaDataProtocol.MIN_TABLE_TIMESTAMP,
+                        ByteUtil.EMPTY_BYTE_ARRAY));
+                    // SCOPE_TABLE
+                    cells.add(
+                        PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
+                            Bytes.toBytes(SCOPE_TABLE),
+                            MetaDataProtocol.MIN_TABLE_TIMESTAMP, ByteUtil.EMPTY_BYTE_ARRAY));
+                    // SOURCE_DATA_TYPE
+                    cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
+                        Bytes.toBytes(SOURCE_DATA_TYPE), MetaDataProtocol.MIN_TABLE_TIMESTAMP,
+                        ByteUtil.EMPTY_BYTE_ARRAY));
+                    // IS_AUTOINCREMENT
+                    cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
+                        Bytes.toBytes(IS_AUTOINCREMENT), MetaDataProtocol.MIN_TABLE_TIMESTAMP,
+                        ByteUtil.EMPTY_BYTE_ARRAY));
+                    // ARRAY_SIZE
+                    cells.add(
+                        PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES, ARRAY_SIZE_BYTES,
+                            MetaDataProtocol.MIN_TABLE_TIMESTAMP,
+                            column.getArraySize() != null
+                                    ? PInteger.INSTANCE.toBytes(column.getArraySize())
+                                    : ByteUtil.EMPTY_BYTE_ARRAY));
+                    // COLUMN_FAMILY
+                    cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
+                        COLUMN_FAMILY_BYTES,
+                        MetaDataProtocol.MIN_TABLE_TIMESTAMP, column.getFamilyName() != null
+                                ? column.getFamilyName().getBytes() : ByteUtil.EMPTY_BYTE_ARRAY));
+                    // TYPE_ID
+                    cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
+                        Bytes.toBytes(TYPE_ID), MetaDataProtocol.MIN_TABLE_TIMESTAMP,
+                        PInteger.INSTANCE.toBytes(column.getDataType().getSqlType())));
+                    // VIEW_CONSTANT
+                    cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
+                        VIEW_CONSTANT_BYTES,
+                        MetaDataProtocol.MIN_TABLE_TIMESTAMP, column.getViewConstant() != null
+                                ? column.getViewConstant() : ByteUtil.EMPTY_BYTE_ARRAY));
+                    // MULTI_TENANT
+                    cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
+                        MULTI_TENANT_BYTES,
                         MetaDataProtocol.MIN_TABLE_TIMESTAMP,
-                        column.getArraySize() != null
-                                ? PInteger.INSTANCE.toBytes(column.getArraySize())
-                                : ByteUtil.EMPTY_BYTE_ARRAY));
-                // COLUMN_FAMILY
-                cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
-                    COLUMN_FAMILY_BYTES,
-                    MetaDataProtocol.MIN_TABLE_TIMESTAMP, column.getFamilyName() != null
-                            ? column.getFamilyName().getBytes() : ByteUtil.EMPTY_BYTE_ARRAY));
-                // TYPE_ID
-                cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
-                    Bytes.toBytes(TYPE_ID), MetaDataProtocol.MIN_TABLE_TIMESTAMP,
-                    PInteger.INSTANCE.toBytes(column.getDataType().getSqlType())));
-                // VIEW_CONSTANT
-                cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
-                    VIEW_CONSTANT_BYTES,
-                    MetaDataProtocol.MIN_TABLE_TIMESTAMP, column.getViewConstant() != null
-                            ? column.getViewConstant() : ByteUtil.EMPTY_BYTE_ARRAY));
-                // MULTI_TENANT
-                cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
-                    MULTI_TENANT_BYTES,
-                    MetaDataProtocol.MIN_TABLE_TIMESTAMP,
-                    PBoolean.INSTANCE.toBytes(table.isMultiTenant())));
-                // KEY_SEQ_COLUMN
-                byte[] keySeqBytes = ByteUtil.EMPTY_BYTE_ARRAY;
-                int pkPos = table.getPKColumns().indexOf(column);
-                if (pkPos!=-1) {
-                    short keySeq = (short) (pkPos + 1 - startOffset - (tenantColSkipped ? 1 : 0));
-                    keySeqBytes = PSmallint.INSTANCE.toBytes(keySeq);
+                        PBoolean.INSTANCE.toBytes(table.isMultiTenant())));
+                    // KEY_SEQ_COLUMN
+                    byte[] keySeqBytes = ByteUtil.EMPTY_BYTE_ARRAY;
+                    int pkPos = table.getPKColumns().indexOf(column);
+                    if (pkPos!=-1) {
+                        short keySeq = (short) (pkPos + 1 - startOffset - (tenantColSkipped ? 1 : 0));
+                        keySeqBytes = PSmallint.INSTANCE.toBytes(keySeq);
+                    }
+                    cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES, KEY_SEQ_BYTES,
+                            MetaDataProtocol.MIN_TABLE_TIMESTAMP, keySeqBytes));
+                    Collections.sort(cells, new CellComparatorImpl());
+                    Tuple tuple = new MultiKeyValueTuple(cells);
+                    tuples.add(tuple);
                 }
-                cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES, KEY_SEQ_BYTES,
-                        MetaDataProtocol.MIN_TABLE_TIMESTAMP, keySeqBytes));
-                Collections.sort(cells, new CellComparatorImpl());
-                Tuple tuple = new MultiKeyValueTuple(cells);
-                tuples.add(tuple);
             }
         }
 
-        return new PhoenixResultSet(new MaterializedResultIterator(tuples), GET_COLUMNS_ROW_PROJECTOR, new StatementContext(new PhoenixStatement(connection), false));
+        PhoenixStatement stmt = new PhoenixStatement(connection);
+        stmt.closeOnCompletion();
+        return new PhoenixResultSet(new MaterializedResultIterator(tuples), GET_COLUMNS_ROW_PROJECTOR, new StatementContext(stmt, false));
         } finally {
             if (connection.getAutoCommit()) {
                 connection.commit();
@@ -962,7 +970,7 @@ public class PhoenixDatabaseMetaData implements DatabaseMetaData {
     @Override
     public ResultSet getCrossReference(String parentCatalog, String parentSchema, String parentTable,
             String foreignCatalog, String foreignSchema, String foreignTable) throws SQLException {
-        return emptyResultSet;
+        return getEmptyResultSet();
     }
 
     @Override
@@ -1012,7 +1020,7 @@ public class PhoenixDatabaseMetaData implements DatabaseMetaData {
 
     @Override
     public ResultSet getExportedKeys(String catalog, String schema, String table) throws SQLException {
-        return emptyResultSet;
+        return getEmptyResultSet();
     }
 
     @Override
@@ -1023,12 +1031,12 @@ public class PhoenixDatabaseMetaData implements DatabaseMetaData {
     @Override
     public ResultSet getFunctionColumns(String catalog, String schemaPattern, String functionNamePattern,
             String columnNamePattern) throws SQLException {
-        return emptyResultSet;
+        return getEmptyResultSet();
     }
 
     @Override
     public ResultSet getFunctions(String catalog, String schemaPattern, String functionNamePattern) throws SQLException {
-        return emptyResultSet;
+        return getEmptyResultSet();
     }
 
     @Override
@@ -1038,7 +1046,7 @@ public class PhoenixDatabaseMetaData implements DatabaseMetaData {
 
     @Override
     public ResultSet getImportedKeys(String catalog, String schema, String table) throws SQLException {
-        return emptyResultSet;
+        return getEmptyResultSet();
     }
 
     @Override
@@ -1046,7 +1054,10 @@ public class PhoenixDatabaseMetaData implements DatabaseMetaData {
         boolean approximate) throws SQLException {
         PreparedStatement stmt = QueryUtil.getIndexInfoStmt(connection, catalog, schema, table,
             unique, approximate);
-        if (stmt == null) return emptyResultSet;
+        if (stmt == null) {
+            return getEmptyResultSet();
+        }
+        stmt.closeOnCompletion();
         return stmt.executeQuery();
     }
 
@@ -1169,7 +1180,7 @@ public class PhoenixDatabaseMetaData implements DatabaseMetaData {
     public ResultSet getPrimaryKeys(String catalog, String schemaName, String tableName)
             throws SQLException {
         if (tableName == null || tableName.length() == 0) {
-            return emptyResultSet;
+            return getEmptyResultSet();
         }
         String fullTableName = SchemaUtil.getTableName(schemaName, tableName);
         PTable table = PhoenixRuntime.getTableNoCache(connection, fullTableName);
@@ -1187,68 +1198,72 @@ public class PhoenixDatabaseMetaData implements DatabaseMetaData {
 
         try {
         List<Tuple> tuples = Lists.newArrayListWithExpectedSize(10);
-        ResultSet rs = getTables(catalog, schemaName, tableName, null);
-        while (rs.next()) {
-            String tenantId = rs.getString(TABLE_CAT);
-            for (PColumn column : sorderPkColumns) {
-                String columnName = column.getName().getString();
-                // generate row key
-                // TENANT_ID, TABLE_SCHEM, TABLE_NAME , COLUMN_NAME are row key columns
-                byte[] rowKey =
-                        SchemaUtil.getColumnKey(tenantId, schemaName, tableName, columnName, null);
-
-                // add one cell for each column info
-                List<Cell> cells = Lists.newArrayListWithCapacity(8);
-                // KEY_SEQ_COLUMN
-                byte[] keySeqBytes = ByteUtil.EMPTY_BYTE_ARRAY;
-                int pkPos = pkColumns.indexOf(column);
-                if (pkPos != -1) {
-                    short keySeq =
-                            (short) (pkPos + 1 - (isSalted ? 1 : 0) - (tenantColSkipped ? 1 : 0));
-                    keySeqBytes = PSmallint.INSTANCE.toBytes(keySeq);
-                }
-                cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES, KEY_SEQ_BYTES,
-                    MetaDataProtocol.MIN_TABLE_TIMESTAMP, keySeqBytes));
-                // PK_NAME
-                cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES, PK_NAME_BYTES,
-                    MetaDataProtocol.MIN_TABLE_TIMESTAMP, table.getPKName() != null
-                            ? table.getPKName().getBytes() : ByteUtil.EMPTY_BYTE_ARRAY));
-                // ASC_OR_DESC
-                char sortOrder = column.getSortOrder() == SortOrder.ASC ? 'A' : 'D';
-                cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
-                    ASC_OR_DESC_BYTES, MetaDataProtocol.MIN_TABLE_TIMESTAMP,
-                    Bytes.toBytes(sortOrder)));
-                // DATA_TYPE
-                cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES, DATA_TYPE_BYTES,
-                    MetaDataProtocol.MIN_TABLE_TIMESTAMP,
-                    PInteger.INSTANCE.toBytes(column.getDataType().getResultSetSqlType())));
-                // TYPE_NAME
-                cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
-                    Bytes.toBytes(TYPE_NAME), MetaDataProtocol.MIN_TABLE_TIMESTAMP,
-                    column.getDataType().getSqlTypeNameBytes()));
-                // COLUMN_SIZE
-                cells.add(
-                    PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES, COLUMN_SIZE_BYTES,
+        try (ResultSet rs = getTables(catalog, schemaName, tableName, null)) {
+            while (rs.next()) {
+                String tenantId = rs.getString(TABLE_CAT);
+                for (PColumn column : sorderPkColumns) {
+                    String columnName = column.getName().getString();
+                    // generate row key
+                    // TENANT_ID, TABLE_SCHEM, TABLE_NAME , COLUMN_NAME are row key columns
+                    byte[] rowKey =
+                            SchemaUtil.getColumnKey(tenantId, schemaName, tableName, columnName, null);
+
+                    // add one cell for each column info
+                    List<Cell> cells = Lists.newArrayListWithCapacity(8);
+                    // KEY_SEQ_COLUMN
+                    byte[] keySeqBytes = ByteUtil.EMPTY_BYTE_ARRAY;
+                    int pkPos = pkColumns.indexOf(column);
+                    if (pkPos != -1) {
+                        short keySeq =
+                                (short) (pkPos + 1 - (isSalted ? 1 : 0) - (tenantColSkipped ? 1 : 0));
+                        keySeqBytes = PSmallint.INSTANCE.toBytes(keySeq);
+                    }
+                    cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES, KEY_SEQ_BYTES,
+                        MetaDataProtocol.MIN_TABLE_TIMESTAMP, keySeqBytes));
+                    // PK_NAME
+                    cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES, PK_NAME_BYTES,
+                        MetaDataProtocol.MIN_TABLE_TIMESTAMP, table.getPKName() != null
+                                ? table.getPKName().getBytes() : ByteUtil.EMPTY_BYTE_ARRAY));
+                    // ASC_OR_DESC
+                    char sortOrder = column.getSortOrder() == SortOrder.ASC ? 'A' : 'D';
+                    cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
+                        ASC_OR_DESC_BYTES, MetaDataProtocol.MIN_TABLE_TIMESTAMP,
+                        Bytes.toBytes(sortOrder)));
+                    // DATA_TYPE
+                    cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES, DATA_TYPE_BYTES,
                         MetaDataProtocol.MIN_TABLE_TIMESTAMP,
-                        column.getMaxLength() != null
-                                ? PInteger.INSTANCE.toBytes(column.getMaxLength())
-                                : ByteUtil.EMPTY_BYTE_ARRAY));
-                // TYPE_ID
-                cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
-                    Bytes.toBytes(TYPE_ID), MetaDataProtocol.MIN_TABLE_TIMESTAMP,
-                    PInteger.INSTANCE.toBytes(column.getDataType().getSqlType())));
-                // VIEW_CONSTANT
-                cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES, VIEW_CONSTANT_BYTES,
-                    MetaDataProtocol.MIN_TABLE_TIMESTAMP, column.getViewConstant() != null
-                            ? column.getViewConstant() : ByteUtil.EMPTY_BYTE_ARRAY));
-                Collections.sort(cells, new CellComparatorImpl());
-                Tuple tuple = new MultiKeyValueTuple(cells);
-                tuples.add(tuple);
+                        PInteger.INSTANCE.toBytes(column.getDataType().getResultSetSqlType())));
+                    // TYPE_NAME
+                    cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
+                        Bytes.toBytes(TYPE_NAME), MetaDataProtocol.MIN_TABLE_TIMESTAMP,
+                        column.getDataType().getSqlTypeNameBytes()));
+                    // COLUMN_SIZE
+                    cells.add(
+                        PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES, COLUMN_SIZE_BYTES,
+                            MetaDataProtocol.MIN_TABLE_TIMESTAMP,
+                            column.getMaxLength() != null
+                                    ? PInteger.INSTANCE.toBytes(column.getMaxLength())
+                                    : ByteUtil.EMPTY_BYTE_ARRAY));
+                    // TYPE_ID
+                    cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES,
+                        Bytes.toBytes(TYPE_ID), MetaDataProtocol.MIN_TABLE_TIMESTAMP,
+                        PInteger.INSTANCE.toBytes(column.getDataType().getSqlType())));
+                    // VIEW_CONSTANT
+                    cells.add(PhoenixKeyValueUtil.newKeyValue(rowKey, TABLE_FAMILY_BYTES, VIEW_CONSTANT_BYTES,
+                        MetaDataProtocol.MIN_TABLE_TIMESTAMP, column.getViewConstant() != null
+                                ? column.getViewConstant() : ByteUtil.EMPTY_BYTE_ARRAY));
+                    Collections.sort(cells, new CellComparatorImpl());
+                    Tuple tuple = new MultiKeyValueTuple(cells);
+                    tuples.add(tuple);
+                }
             }
         }
+
+        PhoenixStatement stmt = new PhoenixStatement(connection);
+        stmt.closeOnCompletion();
         return new PhoenixResultSet(new MaterializedResultIterator(tuples),
                 GET_PRIMARY_KEYS_ROW_PROJECTOR,
-                new StatementContext(new PhoenixStatement(connection), false));
+                new StatementContext(stmt, false));
         } finally {
             if (connection.getAutoCommit()) {
                 connection.commit();
@@ -1259,7 +1274,7 @@ public class PhoenixDatabaseMetaData implements DatabaseMetaData {
     @Override
     public ResultSet getProcedureColumns(String catalog, String schemaPattern, String procedureNamePattern,
             String columnNamePattern) throws SQLException {
-        return emptyResultSet;
+        return getEmptyResultSet();
     }
 
     @Override
@@ -1270,7 +1285,7 @@ public class PhoenixDatabaseMetaData implements DatabaseMetaData {
     @Override
     public ResultSet getProcedures(String catalog, String schemaPattern, String procedureNamePattern)
             throws SQLException {
-        return emptyResultSet;
+        return getEmptyResultSet();
     }
 
     @Override
@@ -1306,6 +1321,7 @@ public class PhoenixDatabaseMetaData implements DatabaseMetaData {
     @Override
     public ResultSet getSchemas(String catalog, String schemaPattern) throws SQLException {
         PreparedStatement stmt = QueryUtil.getSchemasStmt(connection, catalog, schemaPattern);
+        stmt.closeOnCompletion();
         return stmt.executeQuery();
     }
 
@@ -1325,12 +1341,13 @@ public class PhoenixDatabaseMetaData implements DatabaseMetaData {
         throws SQLException {
         PreparedStatement stmt = QueryUtil.getSuperTablesStmt(connection, catalog, schemaPattern,
             tableNamePattern);
+        stmt.closeOnCompletion();
         return stmt.executeQuery();
     }
 
     @Override
     public ResultSet getSuperTypes(String catalog, String schemaPattern, String typeNamePattern) throws SQLException {
-        return emptyResultSet;
+        return getEmptyResultSet();
     }
 
     @Override
@@ -1341,7 +1358,7 @@ public class PhoenixDatabaseMetaData implements DatabaseMetaData {
     @Override
     public ResultSet getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern)
             throws SQLException {
-        return emptyResultSet;
+        return getEmptyResultSet();
     }
     
     private static final PDatum TABLE_TYPE_DATUM = new PDatum() {
@@ -1390,7 +1407,9 @@ public class PhoenixDatabaseMetaData implements DatabaseMetaData {
      */
     @Override
     public ResultSet getTableTypes() throws SQLException {
-        return new PhoenixResultSet(new MaterializedResultIterator(TABLE_TYPE_TUPLES), TABLE_TYPE_ROW_PROJECTOR, new StatementContext(new PhoenixStatement(connection), false));
+        PhoenixStatement stmt = new PhoenixStatement(connection);
+        stmt.closeOnCompletion();
+        return new PhoenixResultSet(new MaterializedResultIterator(TABLE_TYPE_TUPLES), TABLE_TYPE_ROW_PROJECTOR, new StatementContext(stmt, false));
     }
 
     @Override
@@ -1398,7 +1417,10 @@ public class PhoenixDatabaseMetaData implements DatabaseMetaData {
         String[] types) throws SQLException {
         PreparedStatement stmt = QueryUtil.getTablesStmt(connection, catalog, schemaPattern,
             tableNamePattern, types);
-        if (stmt == null) return emptyResultSet;
+        if (stmt == null) {
+            return getEmptyResultSet();
+        }
+        stmt.closeOnCompletion();
         return stmt.executeQuery();
     }
 
@@ -1409,13 +1431,13 @@ public class PhoenixDatabaseMetaData implements DatabaseMetaData {
 
     @Override
     public ResultSet getTypeInfo() throws SQLException {
-        return emptyResultSet;
+        return getEmptyResultSet();
     }
 
     @Override
     public ResultSet getUDTs(String catalog, String schemaPattern, String typeNamePattern, int[] types)
             throws SQLException {
-        return emptyResultSet;
+        return getEmptyResultSet();
     }
 
     @Override
@@ -1431,7 +1453,7 @@ public class PhoenixDatabaseMetaData implements DatabaseMetaData {
 
     @Override
     public ResultSet getVersionColumns(String catalog, String schema, String table) throws SQLException {
-        return emptyResultSet;
+        return getEmptyResultSet();
     }
 
     @Override
@@ -1911,7 +1933,7 @@ public class PhoenixDatabaseMetaData implements DatabaseMetaData {
     @Override
     public ResultSet getPseudoColumns(String catalog, String schemaPattern, String tableNamePattern,
             String columnNamePattern) throws SQLException {
-        return this.emptyResultSet;
+        return getEmptyResultSet();
     }
 
     @Override
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixResultSet.java b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixResultSet.java
index d01e88b88b..8e91d90447 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixResultSet.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixResultSet.java
@@ -173,6 +173,7 @@ public class PhoenixResultSet implements PhoenixMonitoredResultSet, SQLCloseable
         this.scanner = resultIterator;
         this.context = ctx;
         this.statement = context.getStatement();
+        statement.setLastResultSet(this);
         this.readMetricsQueue = context.getReadMetricsQueue();
         this.overAllQueryMetrics = context.getOverallQueryMetrics();
         this.queryLogger = context.getQueryLogger() != null ? context.getQueryLogger() : QueryLogger.NO_OP_INSTANCE;
@@ -217,12 +218,14 @@ public class PhoenixResultSet implements PhoenixMonitoredResultSet, SQLCloseable
 
     @Override
     public void close() throws SQLException {
-        if (isClosed) { return; }
+        if (isClosed) {
+            return;
+        }
         try {
             scanner.close();
         } finally {
             isClosed = true;
-            statement.getResultSets().remove(this);
+            statement.removeResultSet(this);
             overAllQueryMetrics.endQuery();
             overAllQueryMetrics.stopResultSetWatch();
             if (context.getCurrentTable() != null && context.getCurrentTable().getTable() != null
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
index 145d08c1ce..d3df464709 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
@@ -218,7 +218,6 @@ import org.apache.phoenix.util.PhoenixContextExecutor;
 import org.apache.phoenix.util.PhoenixRuntime;
 import org.apache.phoenix.util.QueryUtil;
 import org.apache.phoenix.util.SQLCloseable;
-import org.apache.phoenix.util.SQLCloseables;
 import org.apache.phoenix.util.ServerUtil;
 import org.apache.phoenix.util.ParseNodeUtil.RewriteResult;
 import org.slf4j.Logger;
@@ -278,7 +277,6 @@ public class PhoenixStatement implements PhoenixMonitoredStatement, SQLCloseable
     protected final PhoenixConnection connection;
     private static final int NO_UPDATE = -1;
     private static final String TABLE_UNKNOWN = "";
-    private List<PhoenixResultSet> resultSets = new ArrayList<PhoenixResultSet>();
     private QueryPlan lastQueryPlan;
     private PhoenixResultSet lastResultSet;
     private int lastUpdateCount = NO_UPDATE;
@@ -286,6 +284,7 @@ public class PhoenixStatement implements PhoenixMonitoredStatement, SQLCloseable
     private String lastUpdateTable = TABLE_UNKNOWN;
     private Operation lastUpdateOperation;
     private boolean isClosed = false;
+    private boolean closeOnCompletion = false;
     private int maxRows;
     private int fetchSize = -1;
     private int queryTimeoutMillis;
@@ -307,7 +306,11 @@ public class PhoenixStatement implements PhoenixMonitoredStatement, SQLCloseable
     }
 
     protected List<PhoenixResultSet> getResultSets() {
-        return resultSets;
+        if (lastResultSet != null) {
+            return Collections.singletonList(lastResultSet);
+        } else {
+            return Collections.emptyList();
+        }
     }
     
     public PhoenixResultSet newResultSet(ResultIterator iterator, RowProjector projector, StatementContext context) throws SQLException {
@@ -341,6 +344,7 @@ public class PhoenixStatement implements PhoenixMonitoredStatement, SQLCloseable
                             boolean success = false;
                             boolean pointLookup = false;
                             String tableName = null;
+                            clearResultSet();
                             PhoenixResultSet rs = null;
                             try {
                                 PhoenixConnection conn = getConnection();
@@ -397,9 +401,8 @@ public class PhoenixStatement implements PhoenixMonitoredStatement, SQLCloseable
                                 rs =
                                         newResultSet(resultIterator, plan.getProjector(),
                                                 plan.getContext());
-                                resultSets.add(rs);
+                                // newResultset sets lastResultset
                                 setLastQueryPlan(plan);
-                                setLastResultSet(rs);
                                 setLastUpdateCount(NO_UPDATE);
                                 setLastUpdateTable(tableName == null ? TABLE_UNKNOWN : tableName);
                                 setLastUpdateOperation(stmt.getOperation());
@@ -538,6 +541,7 @@ public class PhoenixStatement implements PhoenixMonitoredStatement, SQLCloseable
                             MutationState state = null;
                             MutationPlan plan = null;
                             final long startExecuteMutationTime = EnvironmentEdgeManager.currentTimeMillis();
+                            clearResultSet();
                             try {
                                 PhoenixConnection conn = getConnection();
                                 if (conn.getQueryServices().isUpgradeRequired() && !conn.isRunningUpgrade()
@@ -566,7 +570,6 @@ public class PhoenixStatement implements PhoenixMonitoredStatement, SQLCloseable
                                 if (connection.getAutoCommit()) {
                                     connection.commit();
                                 }
-                                setLastResultSet(null);
                                 setLastQueryPlan(null);
                                 // Unfortunately, JDBC uses an int for update count, so we
                                 // just max out at Integer.MAX_VALUE
@@ -2077,12 +2080,7 @@ public class PhoenixStatement implements PhoenixMonitoredStatement, SQLCloseable
     @Override
     public void close() throws SQLException {
         try {
-            List<PhoenixResultSet> resultSets = this.resultSets;
-            // Create new list so that remove of the PhoenixResultSet
-            // during closeAll doesn't needless do a linear search
-            // on this list.
-            this.resultSets = Lists.newArrayList();
-            SQLCloseables.closeAll(resultSets);
+            clearResultSet();
         } finally {
             try {
                 connection.removeStatement(this);
@@ -2092,6 +2090,30 @@ public class PhoenixStatement implements PhoenixMonitoredStatement, SQLCloseable
         }
     }
 
+    // From the ResultSet javadoc:
+    // A ResultSet object is automatically closed when the Statement object that generated it is
+    // closed, re-executed, or used to retrieve the next result from a sequence of multiple results.
+    private void clearResultSet() throws SQLException {
+        if (lastResultSet != null) {
+            try {
+                lastResultSet.close();
+            } finally {
+                lastResultSet = null;
+            }
+        }
+    }
+
+    // Called from ResultSet.close(). rs is already closed.
+    // We use a separate function to avoid calling close() again
+    void removeResultSet(ResultSet rs) throws SQLException {
+        if (rs == lastResultSet) {
+            lastResultSet = null;
+            if (closeOnCompletion) {
+                this.close();
+            }
+        }
+    }
+
     public List<Object> getParameters() {
         return Collections.<Object>emptyList();
     }
@@ -2321,7 +2343,6 @@ public class PhoenixStatement implements PhoenixMonitoredStatement, SQLCloseable
     @Override
     public ResultSet getResultSet() throws SQLException {
         ResultSet rs = getLastResultSet();
-        setLastResultSet(null);
         return rs;
     }
 
@@ -2341,6 +2362,7 @@ public class PhoenixStatement implements PhoenixMonitoredStatement, SQLCloseable
         return ResultSet.TYPE_FORWARD_ONLY;
     }
 
+    @Override
     public Operation getUpdateOperation() {
         return getLastUpdateOperation();
     }
@@ -2468,19 +2490,19 @@ public class PhoenixStatement implements PhoenixMonitoredStatement, SQLCloseable
 
     @Override
     public void closeOnCompletion() throws SQLException {
-        throw new SQLFeatureNotSupportedException();
+        closeOnCompletion = true;
     }
 
     @Override
     public boolean isCloseOnCompletion() throws SQLException {
-        throw new SQLFeatureNotSupportedException();
+        return closeOnCompletion;
     }
 
     private PhoenixResultSet getLastResultSet() {
         return lastResultSet;
     }
 
-    private void setLastResultSet(PhoenixResultSet lastResultSet) {
+    void setLastResultSet(PhoenixResultSet lastResultSet) {
         this.lastResultSet = lastResultSet;
     }
 
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/jdbc/PhoenixStatementTest.java b/phoenix-core/src/test/java/org/apache/phoenix/jdbc/PhoenixStatementTest.java
index 2af97b73f8..882221354c 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/jdbc/PhoenixStatementTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/jdbc/PhoenixStatementTest.java
@@ -20,6 +20,7 @@ package org.apache.phoenix.jdbc;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
@@ -213,4 +214,41 @@ public class PhoenixStatementTest extends BaseConnectionlessQueryTest {
         assertNull(ex.getUpdateCounts());
     }
 
+    @Test
+    public void testRecursiveClose() throws SQLException {
+        Connection connection = DriverManager.getConnection(getUrl());
+        Statement stmt1 = connection.createStatement();
+        ResultSet rs11 = stmt1.executeQuery("select * from atable");
+        rs11.close();
+        assertTrue(rs11.isClosed());
+        ResultSet rs12 = stmt1.executeQuery("select * from atable");
+        stmt1.close();
+        assertTrue(stmt1.isClosed());
+        assertTrue(rs12.isClosed());
+
+        Statement stmt2 = connection.createStatement();
+        stmt2.closeOnCompletion();
+        ResultSet rs21 = stmt2.executeQuery("select * from atable");
+        rs21.close();
+        assertTrue(stmt2.isClosed());
+
+        Statement stmt3 = connection.createStatement();
+        ResultSet rs31 = stmt3.executeQuery("select * from atable");
+        stmt3.executeUpdate("upsert into ATABLE VALUES ('1', '2', '3')");
+        assertTrue(rs31.isClosed());
+        ResultSet rs32 = stmt3.executeQuery("select * from atable");
+        ResultSet rs33 = stmt3.executeQuery("select * from atable");
+        assertTrue(rs32.isClosed());
+
+        Statement stmt4 = connection.createStatement();
+        Statement stmt5 = connection.createStatement();
+        ResultSet rs41 = stmt3.executeQuery("select * from atable");
+        ResultSet rs51 = stmt3.executeQuery("select * from atable");
+        connection.close();
+        assertTrue(connection.isClosed());
+        assertTrue(stmt4.isClosed());
+        assertTrue(stmt5.isClosed());
+        assertTrue(rs41.isClosed());
+        assertTrue(rs51.isClosed());
+    }
 }