phoenix-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From sama...@apache.org
Subject phoenix git commit: PHOENIX-3822 Surface byte and row estimates in a machine readable way when doing EXPLAIN PLAN
Date Fri, 26 May 2017 16:39:12 GMT
Repository: phoenix
Updated Branches:
  refs/heads/4.x-HBase-0.98 fd612986e -> 1ca38e87f


PHOENIX-3822 Surface byte and row estimates in a machine readable way when doing EXPLAIN PLAN


Project: http://git-wip-us.apache.org/repos/asf/phoenix/repo
Commit: http://git-wip-us.apache.org/repos/asf/phoenix/commit/1ca38e87
Tree: http://git-wip-us.apache.org/repos/asf/phoenix/tree/1ca38e87
Diff: http://git-wip-us.apache.org/repos/asf/phoenix/diff/1ca38e87

Branch: refs/heads/4.x-HBase-0.98
Commit: 1ca38e87fefbc5eec28f4d83416da4a00a7ff8c5
Parents: fd61298
Author: Samarth Jain <samarth@apache.org>
Authored: Fri May 26 09:39:13 2017 -0700
Committer: Samarth Jain <samarth@apache.org>
Committed: Fri May 26 09:39:13 2017 -0700

----------------------------------------------------------------------
 .../end2end/ExplainPlanWithStatsDisabledIT.java | 212 ++++++++++++++++
 .../end2end/ExplainPlanWithStatsEnabledIT.java  | 242 +++++++++++++++++++
 .../phoenix/compile/BaseMutationPlan.java       |  10 +
 .../phoenix/compile/CloseStatementCompiler.java |   7 +-
 .../phoenix/compile/DelegateMutationPlan.java   |  10 +
 .../apache/phoenix/compile/DeleteCompiler.java  |  49 ++++
 .../phoenix/compile/ListJarsQueryPlan.java      |  10 +
 .../phoenix/compile/OpenStatementCompiler.java  |   7 +-
 .../org/apache/phoenix/compile/QueryPlan.java   |   1 +
 .../apache/phoenix/compile/StatementPlan.java   |  13 +
 .../apache/phoenix/compile/TraceQueryPlan.java  |  10 +
 .../apache/phoenix/compile/UpsertCompiler.java  |  30 +++
 .../apache/phoenix/execute/AggregatePlan.java   |  12 +-
 .../apache/phoenix/execute/BaseQueryPlan.java   |  33 ++-
 .../phoenix/execute/DelegateQueryPlan.java      |  10 +
 .../apache/phoenix/execute/HashJoinPlan.java    |  30 ++-
 .../execute/LiteralResultIterationPlan.java     |  14 +-
 .../org/apache/phoenix/execute/ScanPlan.java    |  50 +++-
 .../phoenix/execute/SortMergeJoinPlan.java      |  24 ++
 .../org/apache/phoenix/execute/UnionPlan.java   |  28 ++-
 .../apache/phoenix/jdbc/PhoenixStatement.java   |  98 +++++++-
 .../org/apache/phoenix/util/NumberUtil.java     |  14 ++
 .../org/apache/phoenix/util/PhoenixRuntime.java |  15 ++
 .../phoenix/execute/CorrelatePlanTest.java      |   2 +-
 .../execute/LiteralResultIteratorPlanTest.java  |   2 +-
 .../query/ParallelIteratorsSplitTest.java       |  10 +
 26 files changed, 890 insertions(+), 53 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/phoenix/blob/1ca38e87/phoenix-core/src/it/java/org/apache/phoenix/end2end/ExplainPlanWithStatsDisabledIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ExplainPlanWithStatsDisabledIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ExplainPlanWithStatsDisabledIT.java
new file mode 100644
index 0000000..ff4127d
--- /dev/null
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ExplainPlanWithStatsDisabledIT.java
@@ -0,0 +1,212 @@
+/*
+ * 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.end2end;
+
+import static org.apache.phoenix.end2end.ExplainPlanWithStatsEnabledIT.getByteRowEstimates;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.util.List;
+
+import org.apache.hadoop.hbase.util.Pair;
+import org.apache.phoenix.util.ReadOnlyProps;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+/**
+ * This class has tests for asserting the bytes and rows information exposed in the explain plan
+ * when statistics are disabled.
+ */
+public class ExplainPlanWithStatsDisabledIT extends ParallelStatsDisabledIT {
+
+    private static String tableA;
+    private static String tableB;
+
+    @BeforeClass
+    public static void doSetup() throws Exception {
+        setUpTestDriver(new ReadOnlyProps(Maps.<String, String> newHashMap()));
+        tableA = generateUniqueName();
+        initData(tableA);
+        tableB = generateUniqueName();
+        initData(tableB);
+    }
+
+    private static void initData(String tableName) throws Exception {
+        try (Connection conn = DriverManager.getConnection(getUrl())) {
+            conn.createStatement().execute("CREATE TABLE " + tableName
+                    + " ( k INTEGER, c1.a bigint,c2.b bigint CONSTRAINT pk PRIMARY KEY (k))");
+            conn.createStatement().execute("upsert into " + tableName + " values (100,1,3)");
+            conn.createStatement().execute("upsert into " + tableName + " values (101,2,4)");
+            conn.createStatement().execute("upsert into " + tableName + " values (102,2,4)");
+            conn.createStatement().execute("upsert into " + tableName + " values (103,2,4)");
+            conn.createStatement().execute("upsert into " + tableName + " values (104,2,4)");
+            conn.createStatement().execute("upsert into " + tableName + " values (105,2,4)");
+            conn.createStatement().execute("upsert into " + tableName + " values (106,2,4)");
+            conn.createStatement().execute("upsert into " + tableName + " values (107,2,4)");
+            conn.createStatement().execute("upsert into " + tableName + " values (108,2,4)");
+            conn.createStatement().execute("upsert into " + tableName + " values (109,2,4)");
+            conn.commit();
+        }
+    }
+
+    @Test
+    public void testBytesRowsForSelect() throws Exception {
+        String sql = "SELECT * FROM " + tableA + " where k >= ?";
+        List<Object> binds = Lists.newArrayList();
+        binds.add(99);
+        try (Connection conn = DriverManager.getConnection(getUrl())) {
+            assertEstimatesAreNull(sql, binds, conn);
+        }
+    }
+
+    @Test
+    public void testBytesRowsForUnion() throws Exception {
+        String sql = "SELECT * FROM " + tableA + " UNION ALL SELECT * FROM " + tableB;
+        try (Connection conn = DriverManager.getConnection(getUrl())) {
+            assertEstimatesAreNull(sql, Lists.newArrayList(), conn);
+        }
+    }
+
+    @Test
+    public void testBytesRowsForHashJoin() throws Exception {
+        String sql =
+                "SELECT ta.c1.a, ta.c2.b FROM " + tableA + " ta JOIN " + tableB
+                        + " tb ON ta.k = tb.k";
+        try (Connection conn = DriverManager.getConnection(getUrl())) {
+            assertEstimatesAreNull(sql, Lists.newArrayList(), conn);
+        }
+    }
+
+    @Test
+    public void testBytesRowsForSortMergeJoin() throws Exception {
+        String sql =
+                "SELECT /*+ USE_SORT_MERGE_JOIN */ ta.c1.a, ta.c2.b FROM " + tableA + " ta JOIN "
+                        + tableB + " tb ON ta.k = tb.k";
+        try (Connection conn = DriverManager.getConnection(getUrl())) {
+            assertEstimatesAreNull(sql, Lists.newArrayList(), conn);
+        }
+    }
+
+    @Test
+    public void testBytesRowsForAggregateQuery() throws Exception {
+        String sql = "SELECT count(*) FROM " + tableA + " where k >= ?";
+        List<Object> binds = Lists.newArrayList();
+        binds.add(99);
+        try (Connection conn = DriverManager.getConnection(getUrl())) {
+            assertEstimatesAreNull(sql, binds, conn);
+        }
+    }
+
+    @Test
+    public void testBytesRowsForUpsertSelectServerSide() throws Exception {
+        String sql = "UPSERT INTO " + tableA + " SELECT * FROM " + tableA;
+        List<Object> binds = Lists.newArrayList();
+        try (Connection conn = DriverManager.getConnection(getUrl())) {
+            conn.setAutoCommit(true);
+            assertEstimatesAreNull(sql, binds, conn);
+        }
+    }
+
+    @Test
+    public void testBytesRowsForUpsertSelectClientSide() throws Exception {
+        String sql = "UPSERT INTO " + tableA + " SELECT * FROM " + tableA;
+        List<Object> binds = Lists.newArrayList();
+        try (Connection conn = DriverManager.getConnection(getUrl())) {
+            conn.setAutoCommit(false);
+            assertEstimatesAreNull(sql, binds, conn);
+        }
+    }
+
+    @Test
+    public void testBytesRowsForUpsertValues() throws Exception {
+        String sql = "UPSERT INTO " + tableA + " VALUES (?, ?, ?)";
+        List<Object> binds = Lists.newArrayList();
+        binds.add(99);
+        binds.add(99);
+        binds.add(99);
+        try (Connection conn = DriverManager.getConnection(getUrl())) {
+            assertEstimatesAreZero(sql, binds, conn);
+        }
+    }
+
+    @Test
+    public void testBytesRowsForDeleteServerSide() throws Exception {
+        String sql = "DELETE FROM " + tableA + " where k >= ?";
+        List<Object> binds = Lists.newArrayList();
+        binds.add(99);
+        try (Connection conn = DriverManager.getConnection(getUrl())) {
+            conn.setAutoCommit(true);
+            assertEstimatesAreNull(sql, binds, conn);
+        }
+    }
+
+    @Test
+    public void testBytesRowsForDeleteClientSideExecutedSerially() throws Exception {
+        String sql = "DELETE FROM " + tableA + " where k >= ? LIMIT 2";
+        List<Object> binds = Lists.newArrayList();
+        binds.add(99);
+        try (Connection conn = DriverManager.getConnection(getUrl())) {
+            conn.setAutoCommit(false);
+            Pair<Long, Long> info = getByteRowEstimates(conn, sql, binds);
+            assertEquals((Long) 200l, info.getSecond());
+            assertEquals((Long) 2l, info.getFirst());
+        }
+    }
+
+    @Test
+    public void testBytesRowsForPointDelete() throws Exception {
+        String sql = "DELETE FROM " + tableA + " where k = ?";
+        List<Object> binds = Lists.newArrayList();
+        binds.add(100);
+        try (Connection conn = DriverManager.getConnection(getUrl())) {
+            conn.setAutoCommit(false);
+            assertEstimatesAreZero(sql, binds, conn);
+        }
+    }
+    
+    @Test
+    public void testBytesRowsForSelectExecutedSerially() throws Exception {
+        String sql = "SELECT * FROM " + tableA + " LIMIT 2";
+        List<Object> binds = Lists.newArrayList();
+        try (Connection conn = DriverManager.getConnection(getUrl())) {
+            conn.setAutoCommit(false);
+            Pair<Long, Long> info = getByteRowEstimates(conn, sql, binds);
+            assertEquals((Long) 200l, info.getSecond());
+            assertEquals((Long) 2l, info.getFirst());
+        }
+    }
+
+    private void assertEstimatesAreNull(String sql, List<Object> binds, Connection conn)
+            throws Exception {
+        Pair<Long, Long> info = getByteRowEstimates(conn, sql, binds);
+        assertNull(info.getSecond());
+        assertNull(info.getFirst());
+    }
+
+    private void assertEstimatesAreZero(String sql, List<Object> binds, Connection conn)
+            throws Exception {
+        Pair<Long, Long> info = getByteRowEstimates(conn, sql, binds);
+        assertEquals((Long) 0l, info.getSecond());
+        assertEquals((Long) 0l, info.getFirst());
+    }
+}

http://git-wip-us.apache.org/repos/asf/phoenix/blob/1ca38e87/phoenix-core/src/it/java/org/apache/phoenix/end2end/ExplainPlanWithStatsEnabledIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ExplainPlanWithStatsEnabledIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ExplainPlanWithStatsEnabledIT.java
new file mode 100644
index 0000000..2785f10
--- /dev/null
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ExplainPlanWithStatsEnabledIT.java
@@ -0,0 +1,242 @@
+/*
+ * 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.end2end;
+import static org.junit.Assert.assertEquals;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.hadoop.hbase.util.Pair;
+import org.apache.phoenix.query.QueryServices;
+import org.apache.phoenix.util.PhoenixRuntime;
+import org.apache.phoenix.util.ReadOnlyProps;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+/**
+ * This class has tests for asserting the bytes and rows information exposed in the explain plan
+ * when statistics are enabled.
+ */
+public class ExplainPlanWithStatsEnabledIT extends ParallelStatsEnabledIT {
+
+    private static String tableA;
+    private static String tableB;
+
+    @BeforeClass
+    public static void doSetup() throws Exception {
+        Map<String, String> props = Maps.newHashMapWithExpectedSize(1);
+        props.put(QueryServices.STATS_GUIDEPOST_WIDTH_BYTES_ATTRIB, Long.toString(20));
+        setUpTestDriver(new ReadOnlyProps(props.entrySet().iterator()));
+        tableA = generateUniqueName();
+        initDataAndStats(tableA);
+        tableB = generateUniqueName();
+        initDataAndStats(tableB);
+    }
+
+    private static void initDataAndStats(String tableName) throws Exception {
+        try (Connection conn = DriverManager.getConnection(getUrl())) {
+            conn.createStatement().execute("CREATE TABLE " + tableName
+                    + " ( k INTEGER, c1.a bigint,c2.b bigint CONSTRAINT pk PRIMARY KEY (k))");
+            conn.createStatement().execute("upsert into " + tableName + " values (100,1,3)");
+            conn.createStatement().execute("upsert into " + tableName + " values (101,2,4)");
+            conn.createStatement().execute("upsert into " + tableName + " values (102,2,4)");
+            conn.createStatement().execute("upsert into " + tableName + " values (103,2,4)");
+            conn.createStatement().execute("upsert into " + tableName + " values (104,2,4)");
+            conn.createStatement().execute("upsert into " + tableName + " values (105,2,4)");
+            conn.createStatement().execute("upsert into " + tableName + " values (106,2,4)");
+            conn.createStatement().execute("upsert into " + tableName + " values (107,2,4)");
+            conn.createStatement().execute("upsert into " + tableName + " values (108,2,4)");
+            conn.createStatement().execute("upsert into " + tableName + " values (109,2,4)");
+            conn.commit();
+            conn.createStatement().execute("UPDATE STATISTICS " + tableName);
+        }
+    }
+
+    @Test
+    public void testBytesRowsForSelect() throws Exception {
+        String sql = "SELECT * FROM " + tableA + " where k >= ?";
+        List<Object> binds = Lists.newArrayList();
+        binds.add(99);
+        try (Connection conn = DriverManager.getConnection(getUrl())) {
+            Pair<Long, Long> info = getByteRowEstimates(conn, sql, binds);
+            assertEquals((Long) 634l, info.getSecond());
+            assertEquals((Long) 10l, info.getFirst());
+        }
+    }
+
+    @Test
+    public void testBytesRowsForUnion() throws Exception {
+        String sql = "SELECT * FROM " + tableA + " UNION ALL SELECT * FROM " + tableB;
+        try (Connection conn = DriverManager.getConnection(getUrl())) {
+            Pair<Long, Long> info = getByteRowEstimates(conn, sql, Lists.newArrayList());
+            assertEquals((Long) (2 * 634l), info.getSecond());
+            assertEquals((Long) (2 * 10l), info.getFirst());
+        }
+    }
+
+    @Test
+    public void testBytesRowsForHashJoin() throws Exception {
+        String sql =
+                "SELECT ta.c1.a, ta.c2.b FROM " + tableA + " ta JOIN " + tableB
+                        + " tb ON ta.k = tb.k";
+        try (Connection conn = DriverManager.getConnection(getUrl())) {
+            Pair<Long, Long> info = getByteRowEstimates(conn, sql, Lists.newArrayList());
+            assertEquals((Long) (634l), info.getSecond());
+            assertEquals((Long) (10l), info.getFirst());
+        }
+    }
+
+    @Test
+    public void testBytesRowsForSortMergeJoin() throws Exception {
+        String sql =
+                "SELECT /*+ USE_SORT_MERGE_JOIN */ ta.c1.a, ta.c2.b FROM " + tableA + " ta JOIN "
+                        + tableB + " tb ON ta.k = tb.k";
+        try (Connection conn = DriverManager.getConnection(getUrl())) {
+            Pair<Long, Long> info = getByteRowEstimates(conn, sql, Lists.newArrayList());
+            assertEquals((Long) (2 * 634l), info.getSecond());
+            assertEquals((Long) (2 * 10l), info.getFirst());
+        }
+    }
+
+    @Test
+    public void testBytesRowsForAggregateQuery() throws Exception {
+        String sql = "SELECT count(*) FROM " + tableA + " where k >= ?";
+        List<Object> binds = Lists.newArrayList();
+        binds.add(99);
+        try (Connection conn = DriverManager.getConnection(getUrl())) {
+            Pair<Long, Long> info = getByteRowEstimates(conn, sql, binds);
+            assertEquals((Long) 634l, info.getSecond());
+            assertEquals((Long) 10l, info.getFirst());
+        }
+    }
+
+    @Test
+    public void testBytesRowsForUpsertSelectServerSide() throws Exception {
+        String sql = "UPSERT INTO " + tableA + " SELECT * FROM " + tableA;
+        List<Object> binds = Lists.newArrayList();
+        try (Connection conn = DriverManager.getConnection(getUrl())) {
+            conn.setAutoCommit(true);
+            Pair<Long, Long> info = getByteRowEstimates(conn, sql, binds);
+            assertEquals((Long) 634l, info.getSecond());
+            assertEquals((Long) 10l, info.getFirst());
+        }
+    }
+
+    @Test
+    public void testBytesRowsForUpsertSelectClientSide() throws Exception {
+        String sql = "UPSERT INTO " + tableA + " SELECT * FROM " + tableA;
+        List<Object> binds = Lists.newArrayList();
+        try (Connection conn = DriverManager.getConnection(getUrl())) {
+            conn.setAutoCommit(false);
+            Pair<Long, Long> info = getByteRowEstimates(conn, sql, binds);
+            assertEquals((Long) 634l, info.getSecond());
+            assertEquals((Long) 10l, info.getFirst());
+        }
+    }
+
+    @Test
+    public void testBytesRowsForUpsertValues() throws Exception {
+        String sql = "UPSERT INTO " + tableA + " VALUES (?, ?, ?)";
+        List<Object> binds = Lists.newArrayList();
+        binds.add(99);
+        binds.add(99);
+        binds.add(99);
+        try (Connection conn = DriverManager.getConnection(getUrl())) {
+            Pair<Long, Long> info = getByteRowEstimates(conn, sql, binds);
+            assertEquals((Long) 0l, info.getSecond());
+            assertEquals((Long) 0l, info.getFirst());
+        }
+    }
+
+    @Test
+    public void testBytesRowsForDeleteServerSide() throws Exception {
+        String sql = "DELETE FROM " + tableA + " where k >= ?";
+        List<Object> binds = Lists.newArrayList();
+        binds.add(99);
+        try (Connection conn = DriverManager.getConnection(getUrl())) {
+            conn.setAutoCommit(true);
+            Pair<Long, Long> info = getByteRowEstimates(conn, sql, binds);
+            assertEquals((Long) 634l, info.getSecond());
+            assertEquals((Long) 10l, info.getFirst());
+        }
+    }
+
+    @Test
+    public void testBytesRowsForDeleteClientSideExecutedSerially() throws Exception {
+        String sql = "DELETE FROM " + tableA + " where k >= ? LIMIT 2";
+        List<Object> binds = Lists.newArrayList();
+        binds.add(99);
+        try (Connection conn = DriverManager.getConnection(getUrl())) {
+            conn.setAutoCommit(false);
+            Pair<Long, Long> info = getByteRowEstimates(conn, sql, binds);
+            assertEquals((Long) 200l, info.getSecond());
+            assertEquals((Long) 2l, info.getFirst());
+        }
+    }
+
+    @Test
+    public void testBytesRowsForPointDelete() throws Exception {
+        String sql = "DELETE FROM " + tableA + " where k = ?";
+        List<Object> binds = Lists.newArrayList();
+        binds.add(100);
+        try (Connection conn = DriverManager.getConnection(getUrl())) {
+            conn.setAutoCommit(false);
+            Pair<Long, Long> info = getByteRowEstimates(conn, sql, binds);
+            assertEquals((Long) 0l, info.getSecond());
+            assertEquals((Long) 0l, info.getFirst());
+        }
+    }
+    
+    @Test
+    public void testBytesRowsForSelectExecutedSerially() throws Exception {
+        String sql = "SELECT * FROM " + tableA + " LIMIT 2";
+        List<Object> binds = Lists.newArrayList();
+        try (Connection conn = DriverManager.getConnection(getUrl())) {
+            conn.setAutoCommit(false);
+            Pair<Long, Long> info = getByteRowEstimates(conn, sql, binds);
+            assertEquals((Long) 200l, info.getSecond());
+            assertEquals((Long) 2l, info.getFirst());
+        }
+    }
+
+    public static Pair<Long, Long> getByteRowEstimates(Connection conn, String sql,
+            List<Object> bindValues) throws Exception {
+        String explainSql = "EXPLAIN " + sql;
+        Long estimatedBytes = null;
+        Long estimatedRows = null;
+        try (PreparedStatement statement = conn.prepareStatement(explainSql)) {
+            int paramIdx = 1;
+            for (Object bind : bindValues) {
+                statement.setObject(paramIdx++, bind);
+            }
+            ResultSet rs = statement.executeQuery(explainSql);
+            rs.next();
+            estimatedBytes = (Long) rs.getObject(PhoenixRuntime.EXPLAIN_PLAN_ESTIMATED_BYTES_READ_COLUMN);
+            estimatedRows = (Long) rs.getObject(PhoenixRuntime.EXPLAIN_PLAN_ESTIMATED_ROWS_READ_COLUMN);
+        }
+        return new Pair<>(estimatedRows, estimatedBytes);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/phoenix/blob/1ca38e87/phoenix-core/src/main/java/org/apache/phoenix/compile/BaseMutationPlan.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/BaseMutationPlan.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/BaseMutationPlan.java
index d82aa1f..276dc9b 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/BaseMutationPlan.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/BaseMutationPlan.java
@@ -64,4 +64,14 @@ public abstract class BaseMutationPlan implements MutationPlan {
         return Collections.emptySet();
     }
 
+    @Override
+    public Long getEstimatedRowsToScan() throws SQLException {
+        return 0l;
+    }
+
+    @Override
+    public Long getEstimatedBytesToScan() throws SQLException {
+        return 0l;
+    }
+
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/phoenix/blob/1ca38e87/phoenix-core/src/main/java/org/apache/phoenix/compile/CloseStatementCompiler.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/CloseStatementCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/CloseStatementCompiler.java
index cc53a9d..708262c 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/CloseStatementCompiler.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/CloseStatementCompiler.java
@@ -17,17 +17,16 @@
  */
 package org.apache.phoenix.compile;
 
+import java.sql.SQLException;
+import java.util.Collections;
+
 import org.apache.phoenix.execute.MutationState;
 import org.apache.phoenix.jdbc.PhoenixConnection;
 import org.apache.phoenix.jdbc.PhoenixStatement;
 import org.apache.phoenix.jdbc.PhoenixStatement.Operation;
 import org.apache.phoenix.parse.CloseStatement;
-import org.apache.phoenix.parse.OpenStatement;
 import org.apache.phoenix.schema.MetaDataClient;
 
-import java.sql.SQLException;
-import java.util.Collections;
-
 public class CloseStatementCompiler {
     private final PhoenixStatement statement;
     private final Operation operation;

http://git-wip-us.apache.org/repos/asf/phoenix/blob/1ca38e87/phoenix-core/src/main/java/org/apache/phoenix/compile/DelegateMutationPlan.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/DelegateMutationPlan.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/DelegateMutationPlan.java
index 7ef5c48..005ae1f 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/DelegateMutationPlan.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/DelegateMutationPlan.java
@@ -67,4 +67,14 @@ public class DelegateMutationPlan implements MutationPlan {
         this.plan = plan;
     }
 
+    @Override
+    public Long getEstimatedRowsToScan() throws SQLException {
+        return plan.getEstimatedRowsToScan();
+    }
+
+    @Override
+    public Long getEstimatedBytesToScan() throws SQLException {
+        return plan.getEstimatedBytesToScan();
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/1ca38e87/phoenix-core/src/main/java/org/apache/phoenix/compile/DeleteCompiler.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/DeleteCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/DeleteCompiler.java
index fe9be6e..5654d59 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/DeleteCompiler.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/DeleteCompiler.java
@@ -17,6 +17,7 @@
 package org.apache.phoenix.compile;
 
 import static org.apache.phoenix.execute.MutationState.RowTimestampColInfo.NULL_ROWTIMESTAMP_INFO;
+import static org.apache.phoenix.util.NumberUtil.add;
 
 import java.sql.ParameterMetaData;
 import java.sql.SQLException;
@@ -301,6 +302,24 @@ public class DeleteCompiler {
 		public Operation getOperation() {
 			return operation;
 		}
+
+        @Override
+        public Long getEstimatedRowsToScan() throws SQLException {
+            Long estRows = null;
+            for (MutationPlan plan : plans) {
+                estRows = add(estRows, plan.getEstimatedRowsToScan());
+            }
+            return estRows;
+        }
+
+        @Override
+        public Long getEstimatedBytesToScan() throws SQLException {
+            Long estBytes = null;
+            for (MutationPlan plan : plans) {
+                estBytes = add(estBytes, plan.getEstimatedBytesToScan());
+            }
+            return estBytes;
+        }
     }
     
     private static boolean hasNonPKIndexedColumns(Collection<PTable> immutableIndexes) {
@@ -530,6 +549,16 @@ public class DeleteCompiler {
             		public Operation getOperation() {
             			return operation;
             		}
+
+                    @Override
+                    public Long getEstimatedRowsToScan() throws SQLException {
+                        return 0l;
+                    }
+
+                    @Override
+                    public Long getEstimatedBytesToScan() throws SQLException {
+                        return 0l;
+                    }
                 });
             } else if (runOnServer) {
                 // TODO: better abstraction
@@ -621,6 +650,16 @@ public class DeleteCompiler {
                         planSteps.addAll(queryPlanSteps);
                         return new ExplainPlan(planSteps);
                     }
+
+                    @Override
+                    public Long getEstimatedRowsToScan() throws SQLException {
+                        return aggPlan.getEstimatedRowsToScan();
+                    }
+
+                    @Override
+                    public Long getEstimatedBytesToScan() throws SQLException {
+                        return aggPlan.getEstimatedBytesToScan();
+                    }
                 });
             } else {
                 List<TableRef> immutableIndexRefsToBe = Lists.newArrayListWithExpectedSize(dataPlan.getTableRef().getTable().getIndexes().size());
@@ -698,6 +737,16 @@ public class DeleteCompiler {
                         planSteps.addAll(queryPlanSteps);
                         return new ExplainPlan(planSteps);
                     }
+
+                    @Override
+                    public Long getEstimatedRowsToScan() throws SQLException {
+                        return plan.getEstimatedRowsToScan();
+                    }
+
+                    @Override
+                    public Long getEstimatedBytesToScan() throws SQLException {
+                        return plan.getEstimatedBytesToScan();
+                    }
                 });
             }
         }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/1ca38e87/phoenix-core/src/main/java/org/apache/phoenix/compile/ListJarsQueryPlan.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/ListJarsQueryPlan.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/ListJarsQueryPlan.java
index 8265de8..9cf1211 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/ListJarsQueryPlan.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/ListJarsQueryPlan.java
@@ -253,4 +253,14 @@ public class ListJarsQueryPlan implements QueryPlan {
 	public Operation getOperation() {
 		return stmt.getUpdateOperation();
 	}
+
+    @Override
+    public Long getEstimatedRowsToScan() {
+        return 0l;
+    }
+
+    @Override
+    public Long getEstimatedBytesToScan() {
+        return 0l;
+    }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/phoenix/blob/1ca38e87/phoenix-core/src/main/java/org/apache/phoenix/compile/OpenStatementCompiler.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/OpenStatementCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/OpenStatementCompiler.java
index b6125fd..bed8fd1 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/OpenStatementCompiler.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/OpenStatementCompiler.java
@@ -17,17 +17,16 @@
  */
 package org.apache.phoenix.compile;
 
+import java.sql.SQLException;
+import java.util.Collections;
+
 import org.apache.phoenix.execute.MutationState;
 import org.apache.phoenix.jdbc.PhoenixConnection;
 import org.apache.phoenix.jdbc.PhoenixStatement;
 import org.apache.phoenix.jdbc.PhoenixStatement.Operation;
-import org.apache.phoenix.parse.DeclareCursorStatement;
 import org.apache.phoenix.parse.OpenStatement;
 import org.apache.phoenix.schema.MetaDataClient;
 
-import java.sql.SQLException;
-import java.util.Collections;
-
 public class OpenStatementCompiler {
     private final PhoenixStatement statement;
     private final Operation operation;

http://git-wip-us.apache.org/repos/asf/phoenix/blob/1ca38e87/phoenix-core/src/main/java/org/apache/phoenix/compile/QueryPlan.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/QueryPlan.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/QueryPlan.java
index ea77d79..f7cdcbf 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/QueryPlan.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/QueryPlan.java
@@ -86,4 +86,5 @@ public interface QueryPlan extends StatementPlan {
      * @throws SQLException 
      */
     public boolean useRoundRobinIterator() throws SQLException;
+
 }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/1ca38e87/phoenix-core/src/main/java/org/apache/phoenix/compile/StatementPlan.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/StatementPlan.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/StatementPlan.java
index cfdb8e9..6d381d9 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/StatementPlan.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/StatementPlan.java
@@ -35,4 +35,17 @@ public interface StatementPlan {
     ExplainPlan getExplainPlan() throws SQLException;
     public Set<TableRef> getSourceRefs();
     Operation getOperation();
+
+    /**
+     * @return estimated number of rows that will be scanned when this statement plan is been executed.
+     *         Returns null if the estimate cannot be provided.
+     * @throws SQLException
+     */
+    public Long getEstimatedRowsToScan() throws SQLException;
+
+    /**
+     * @return estimated number of bytes that will be scanned when this statement plan is been executed.
+     *         Returns null if the estimate cannot be provided.
+     */
+    public Long getEstimatedBytesToScan() throws SQLException;
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/phoenix/blob/1ca38e87/phoenix-core/src/main/java/org/apache/phoenix/compile/TraceQueryPlan.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/TraceQueryPlan.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/TraceQueryPlan.java
index 8fb435d..915c636 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/TraceQueryPlan.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/TraceQueryPlan.java
@@ -267,4 +267,14 @@ public class TraceQueryPlan implements QueryPlan {
     public boolean useRoundRobinIterator() {
         return false;
     }
+
+    @Override
+    public Long getEstimatedRowsToScan() {
+        return 0l;
+    }
+
+    @Override
+    public Long getEstimatedBytesToScan() {
+        return 0l;
+    }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/phoenix/blob/1ca38e87/phoenix-core/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java
index 7b50125..67619fb 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java
@@ -802,6 +802,16 @@ public class UpsertCompiler {
                             planSteps.addAll(queryPlanSteps);
                             return new ExplainPlan(planSteps);
                         }
+
+                        @Override
+                        public Long getEstimatedRowsToScan() throws SQLException {
+                            return aggPlan.getEstimatedRowsToScan();
+                        }
+
+                        @Override
+                        public Long getEstimatedBytesToScan() throws SQLException {
+                            return aggPlan.getEstimatedBytesToScan();
+                        }
                     };
                 }
             }
@@ -874,6 +884,16 @@ public class UpsertCompiler {
                     planSteps.addAll(queryPlanSteps);
                     return new ExplainPlan(planSteps);
                 }
+
+                @Override
+                public Long getEstimatedRowsToScan() throws SQLException {
+                    return queryPlan.getEstimatedRowsToScan();
+                }
+
+                @Override
+                public Long getEstimatedBytesToScan() throws SQLException {
+                    return queryPlan.getEstimatedBytesToScan();
+                }
                 
             };
         }
@@ -1097,6 +1117,16 @@ public class UpsertCompiler {
                 return new ExplainPlan(planSteps);
             }
 
+            @Override
+            public Long getEstimatedRowsToScan() throws SQLException {
+                return 0l;
+            }
+
+            @Override
+            public Long getEstimatedBytesToScan() throws SQLException {
+                return 0l;
+            }
+
         };
     }
     

http://git-wip-us.apache.org/repos/asf/phoenix/blob/1ca38e87/phoenix-core/src/main/java/org/apache/phoenix/execute/AggregatePlan.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/execute/AggregatePlan.java b/phoenix-core/src/main/java/org/apache/phoenix/execute/AggregatePlan.java
index 84bb402..2cdaac7 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/execute/AggregatePlan.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/execute/AggregatePlan.java
@@ -23,6 +23,7 @@ import java.util.Collections;
 import java.util.List;
 
 import org.apache.hadoop.hbase.client.Scan;
+import org.apache.hadoop.hbase.util.Pair;
 import org.apache.phoenix.compile.GroupByCompiler.GroupBy;
 import org.apache.phoenix.compile.OrderByCompiler.OrderBy;
 import org.apache.phoenix.compile.QueryPlan;
@@ -82,11 +83,10 @@ public class AggregatePlan extends BaseQueryPlan {
     private List<List<Scan>> scans;
     private static final Logger logger = LoggerFactory.getLogger(AggregatePlan.class);
     private boolean isSerial;
-    
 
     public AggregatePlan(StatementContext context, FilterableStatement statement, TableRef table,
             RowProjector projector, Integer limit, Integer offset, OrderBy orderBy,
-            ParallelIteratorFactory parallelIteratorFactory, GroupBy groupBy, Expression having) {
+            ParallelIteratorFactory parallelIteratorFactory, GroupBy groupBy, Expression having) throws SQLException {
         this(context, statement, table, projector, limit, offset, orderBy, parallelIteratorFactory, groupBy, having,
                 null);
     }
@@ -94,7 +94,7 @@ public class AggregatePlan extends BaseQueryPlan {
     private AggregatePlan(StatementContext context, FilterableStatement statement, TableRef table,
             RowProjector projector, Integer limit, Integer offset, OrderBy orderBy,
             ParallelIteratorFactory parallelIteratorFactory, GroupBy groupBy, Expression having,
-            Expression dynamicFilter) {
+            Expression dynamicFilter) throws SQLException {
         super(context, statement, table, projector, context.getBindManager().getParameterMetaData(), limit, offset,
                 orderBy, groupBy, parallelIteratorFactory, dynamicFilter);
         this.having = having;
@@ -223,11 +223,10 @@ public class AggregatePlan extends BaseQueryPlan {
         BaseResultIterators iterators = isSerial
                 ? new SerialIterators(this, null, null, wrapParallelIteratorFactory(), scanGrouper, scan)
                 : new ParallelIterators(this, null, wrapParallelIteratorFactory(), scan, false);
-
+        estimatedRows = iterators.getEstimatedRowCount();
+        estimatedSize = iterators.getEstimatedByteCount();
         splits = iterators.getSplits();
         scans = iterators.getScans();
-        estimatedSize = iterators.getEstimatedByteCount();
-        estimatedRows = iterators.getEstimatedRowCount();
 
         AggregatingResultIterator aggResultIterator;
         // No need to merge sort for ungrouped aggregation
@@ -273,4 +272,5 @@ public class AggregatePlan extends BaseQueryPlan {
     public boolean useRoundRobinIterator() throws SQLException {
         return false;
     }
+
 }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/1ca38e87/phoenix-core/src/main/java/org/apache/phoenix/execute/BaseQueryPlan.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/execute/BaseQueryPlan.java b/phoenix-core/src/main/java/org/apache/phoenix/execute/BaseQueryPlan.java
index 7b0451a..15af929 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/execute/BaseQueryPlan.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/execute/BaseQueryPlan.java
@@ -63,14 +63,13 @@ import org.apache.phoenix.schema.KeyValueSchema;
 import org.apache.phoenix.schema.PColumn;
 import org.apache.phoenix.schema.PName;
 import org.apache.phoenix.schema.PTable;
-import org.apache.phoenix.schema.PTable.IndexType;
 import org.apache.phoenix.schema.PTable.ImmutableStorageScheme;
+import org.apache.phoenix.schema.PTable.IndexType;
 import org.apache.phoenix.schema.PTableType;
 import org.apache.phoenix.schema.TableRef;
 import org.apache.phoenix.trace.TracingIterator;
 import org.apache.phoenix.trace.util.Tracing;
 import org.apache.phoenix.util.ByteUtil;
-import org.apache.phoenix.util.EncodedColumnsUtil;
 import org.apache.phoenix.util.IndexUtil;
 import org.apache.phoenix.util.LogUtil;
 import org.apache.phoenix.util.SQLCloseable;
@@ -113,6 +112,7 @@ public abstract class BaseQueryPlan implements QueryPlan {
     protected final Expression dynamicFilter;
     protected Long estimatedRows;
     protected Long estimatedSize;
+    private boolean explainPlanCalled;
     
 
     protected BaseQueryPlan(
@@ -134,15 +134,6 @@ public abstract class BaseQueryPlan implements QueryPlan {
         this.dynamicFilter = dynamicFilter;
     }
 
-    public Long getEstimatedRowCount() {
-        return this.estimatedRows;
-    }
-    
-    public Long getEstimatedByteCount() {
-        return this.estimatedSize;
-    }
-    
-
 	@Override
 	public Operation getOperation() {
 		return Operation.QUERY;
@@ -496,13 +487,15 @@ public abstract class BaseQueryPlan implements QueryPlan {
 
     @Override
     public ExplainPlan getExplainPlan() throws SQLException {
+        explainPlanCalled = true;
         if (context.getScanRanges() == ScanRanges.NOTHING) {
             return new ExplainPlan(Collections.singletonList("DEGENERATE SCAN OVER " + getTableRef().getTable().getName().getString()));
         }
         
         // Optimize here when getting explain plan, as queries don't get optimized until after compilation
         QueryPlan plan = context.getConnection().getQueryServices().getOptimizer().optimize(context.getStatement(), this);
-        return plan instanceof BaseQueryPlan ? new ExplainPlan(getPlanSteps(plan.iterator())) : plan.getExplainPlan();
+        ExplainPlan exp = plan instanceof BaseQueryPlan ? new ExplainPlan(getPlanSteps(plan.iterator())) : plan.getExplainPlan();
+        return exp;
     }
 
     private List<String> getPlanSteps(ResultIterator iterator){
@@ -516,4 +509,20 @@ public abstract class BaseQueryPlan implements QueryPlan {
         return groupBy.isEmpty() ? orderBy.getOrderByExpressions().isEmpty() : groupBy.isOrderPreserving();
     }
     
+    @Override
+    public Long getEstimatedRowsToScan() throws SQLException {
+        if (!explainPlanCalled) {
+            getExplainPlan();
+        }
+        return estimatedRows;
+    }
+
+    @Override
+    public Long getEstimatedBytesToScan() throws SQLException {
+        if (!explainPlanCalled) {
+            getExplainPlan();
+        }
+        return estimatedSize;
+    }
+
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/phoenix/blob/1ca38e87/phoenix-core/src/main/java/org/apache/phoenix/execute/DelegateQueryPlan.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/execute/DelegateQueryPlan.java b/phoenix-core/src/main/java/org/apache/phoenix/execute/DelegateQueryPlan.java
index 015b8f9..cde1410 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/execute/DelegateQueryPlan.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/execute/DelegateQueryPlan.java
@@ -141,4 +141,14 @@ public abstract class DelegateQueryPlan implements QueryPlan {
     public QueryPlan getDelegate() {
         return delegate;
     }
+
+    @Override
+    public Long getEstimatedRowsToScan() throws SQLException {
+        return delegate.getEstimatedRowsToScan();
+    }
+
+    @Override
+    public Long getEstimatedBytesToScan() throws SQLException {
+        return delegate.getEstimatedBytesToScan();
+    }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/phoenix/blob/1ca38e87/phoenix-core/src/main/java/org/apache/phoenix/execute/HashJoinPlan.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/execute/HashJoinPlan.java b/phoenix-core/src/main/java/org/apache/phoenix/execute/HashJoinPlan.java
index dce797d..64e2ce2 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/execute/HashJoinPlan.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/execute/HashJoinPlan.java
@@ -19,6 +19,7 @@ package org.apache.phoenix.execute;
 
 import static org.apache.phoenix.monitoring.TaskExecutionMetricsHolder.NO_OP_INSTANCE;
 import static org.apache.phoenix.util.LogUtil.addCustomAnnotations;
+import static org.apache.phoenix.util.NumberUtil.add;
 
 import java.sql.SQLException;
 import java.util.Collections;
@@ -91,9 +92,12 @@ public class HashJoinPlan extends DelegateQueryPlan {
     private HashCacheClient hashClient;
     private AtomicLong firstJobEndTime;
     private List<Expression> keyRangeExpressions;
+    private Long estimatedRows;
+    private Long estimatedBytes;
+    private boolean explainPlanCalled;
     
     public static HashJoinPlan create(SelectStatement statement, 
-            QueryPlan plan, HashJoinInfo joinInfo, SubPlan[] subPlans) {
+            QueryPlan plan, HashJoinInfo joinInfo, SubPlan[] subPlans) throws SQLException {
         if (!(plan instanceof HashJoinPlan))
             return new HashJoinPlan(statement, plan, joinInfo, subPlans, joinInfo == null, Collections.<SQLCloseable>emptyList());
         
@@ -111,7 +115,7 @@ public class HashJoinPlan extends DelegateQueryPlan {
     }
     
     private HashJoinPlan(SelectStatement statement, 
-            QueryPlan plan, HashJoinInfo joinInfo, SubPlan[] subPlans, boolean recompileWhereClause, List<SQLCloseable> dependencies) {
+            QueryPlan plan, HashJoinInfo joinInfo, SubPlan[] subPlans, boolean recompileWhereClause, List<SQLCloseable> dependencies) throws SQLException {
         super(plan);
         this.dependencies.addAll(dependencies);
         this.statement = statement;
@@ -238,6 +242,7 @@ public class HashJoinPlan extends DelegateQueryPlan {
 
     @Override
     public ExplainPlan getExplainPlan() throws SQLException {
+        explainPlanCalled = true;
         List<String> planSteps = Lists.newArrayList(delegate.getExplainPlan().getPlanSteps());
         int count = subPlans.length;
         for (int i = 0; i < count; i++) {
@@ -253,7 +258,10 @@ public class HashJoinPlan extends DelegateQueryPlan {
         if (joinInfo != null && joinInfo.getLimit() != null) {
             planSteps.add("    JOIN-SCANNER " + joinInfo.getLimit() + " ROW LIMIT");
         }
-
+        for (SubPlan subPlan : subPlans) {
+            estimatedBytes = add(estimatedBytes, subPlan.getInnerPlan().getEstimatedBytesToScan());
+            estimatedRows = add(estimatedRows, subPlan.getInnerPlan().getEstimatedRowsToScan());
+        }
         return new ExplainPlan(planSteps);
     }
 
@@ -440,6 +448,22 @@ public class HashJoinPlan extends DelegateQueryPlan {
             return plan;
         }
     }
+
+    @Override
+    public Long getEstimatedRowsToScan() throws SQLException {
+        if (!explainPlanCalled) {
+            getExplainPlan();
+        }
+        return estimatedRows;
+    }
+
+    @Override
+    public Long getEstimatedBytesToScan() throws SQLException {
+        if (!explainPlanCalled) {
+            getExplainPlan();
+        }
+        return estimatedBytes;
+    }
 }
 
 

http://git-wip-us.apache.org/repos/asf/phoenix/blob/1ca38e87/phoenix-core/src/main/java/org/apache/phoenix/execute/LiteralResultIterationPlan.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/execute/LiteralResultIterationPlan.java b/phoenix-core/src/main/java/org/apache/phoenix/execute/LiteralResultIterationPlan.java
index 7b95cf9..1853c45 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/execute/LiteralResultIterationPlan.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/execute/LiteralResultIterationPlan.java
@@ -43,14 +43,14 @@ public class LiteralResultIterationPlan extends BaseQueryPlan {
 
     public LiteralResultIterationPlan(StatementContext context, 
             FilterableStatement statement, TableRef tableRef, RowProjector projection, 
-            Integer limit, Integer offset, OrderBy orderBy, ParallelIteratorFactory parallelIteratorFactory) {
+            Integer limit, Integer offset, OrderBy orderBy, ParallelIteratorFactory parallelIteratorFactory) throws SQLException {
         this(Collections.<Tuple> singletonList(new SingleKeyValueTuple(KeyValue.LOWESTKEY)), 
                 context, statement, tableRef, projection, limit, offset, orderBy, parallelIteratorFactory);
     }
 
     public LiteralResultIterationPlan(Iterable<Tuple> tuples, StatementContext context, 
             FilterableStatement statement, TableRef tableRef, RowProjector projection, 
-            Integer limit, Integer offset, OrderBy orderBy, ParallelIteratorFactory parallelIteratorFactory) {
+            Integer limit, Integer offset, OrderBy orderBy, ParallelIteratorFactory parallelIteratorFactory) throws SQLException {
         super(context, statement, tableRef, projection, context.getBindManager().getParameterMetaData(), limit, offset, orderBy, GroupBy.EMPTY_GROUP_BY, parallelIteratorFactory, null);
         this.tuples = tuples;
     }
@@ -110,4 +110,14 @@ public class LiteralResultIterationPlan extends BaseQueryPlan {
         
         return scanner;
     }
+
+	@Override
+	public Long getEstimatedRowsToScan() {
+		return 0l;
+	}
+
+	@Override
+	public Long getEstimatedBytesToScan() {
+		return 0l;
+	}
 }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/1ca38e87/phoenix-core/src/main/java/org/apache/phoenix/execute/ScanPlan.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/execute/ScanPlan.java b/phoenix-core/src/main/java/org/apache/phoenix/execute/ScanPlan.java
index ebe4441..2990b77 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/execute/ScanPlan.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/execute/ScanPlan.java
@@ -27,6 +27,7 @@ import java.util.List;
 
 import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.client.Scan;
+import org.apache.hadoop.hbase.util.Pair;
 import org.apache.phoenix.compile.GroupByCompiler.GroupBy;
 import org.apache.phoenix.compile.OrderByCompiler.OrderBy;
 import org.apache.phoenix.compile.RowProjector;
@@ -82,7 +83,9 @@ public class ScanPlan extends BaseQueryPlan {
     private boolean allowPageFilter;
     private boolean isSerial;
     private boolean isDataToScanWithinThreshold;
-    
+    private Long serialRowsEstimate;
+    private Long serialBytesEstimate;
+
     public ScanPlan(StatementContext context, FilterableStatement statement, TableRef table, RowProjector projector, Integer limit, Integer offset, OrderBy orderBy, ParallelIteratorFactory parallelIteratorFactory, boolean allowPageFilter) throws SQLException {
         this(context, statement, table, projector, limit, offset, orderBy, parallelIteratorFactory, allowPageFilter, null);
     }
@@ -102,8 +105,13 @@ public class ScanPlan extends BaseQueryPlan {
         }
         Integer perScanLimit = !allowPageFilter || isOrdered ? null : limit;
         perScanLimit = QueryUtil.getOffsetLimit(perScanLimit, offset);
-        this.isDataToScanWithinThreshold = isAmountOfDataToScanWithinThreshold(context, table.getTable(), perScanLimit);
+        Pair<Long, Long> estimate = getEstimateOfDataSizeToScanIfWithinThreshold(context, table.getTable(), perScanLimit);
+        this.isDataToScanWithinThreshold = estimate != null;
         this.isSerial = isSerial(context, statement, tableRef, orderBy, isDataToScanWithinThreshold);
+        if (isSerial) {
+            serialBytesEstimate = estimate.getFirst();
+            serialRowsEstimate = estimate.getSecond();
+        }
     }
 
     private static boolean isSerial(StatementContext context, FilterableStatement statement,
@@ -123,7 +131,13 @@ public class ScanPlan extends BaseQueryPlan {
         return false;
     }
     
-    private static boolean isAmountOfDataToScanWithinThreshold(StatementContext context, PTable table, Integer perScanLimit) throws SQLException {
+    /**
+     * @return Pair of numbers in which the first part is estimated number of bytes that will be
+     *         scanned and the second part is estimated number of rows. Returned value is null if
+     *         estimated size of data to scan is beyond a threshold.
+     * @throws SQLException
+     */
+    private static Pair<Long, Long> getEstimateOfDataSizeToScanIfWithinThreshold(StatementContext context, PTable table, Integer perScanLimit) throws SQLException {
         Scan scan = context.getScan();
         ConnectionQueryServices services = context.getConnection().getQueryServices();
         long estRowSize = SchemaUtil.estimateRowSize(table);
@@ -134,20 +148,22 @@ public class ScanPlan extends BaseQueryPlan {
              * If a limit is not provided or if we have a filter, then we are not able to decide whether
              * the amount of data we need to scan is less than the threshold.
              */
-            return false;
+            return null;
         } 
         float factor =
             services.getProps().getFloat(QueryServices.LIMITED_QUERY_SERIAL_THRESHOLD,
                 QueryServicesOptions.DEFAULT_LIMITED_QUERY_SERIAL_THRESHOLD);
         long threshold = (long)(factor * regionSize);
-        return (perScanLimit * estRowSize < threshold);
+        long estimatedBytes = perScanLimit * estRowSize;
+        long estimatedRows = perScanLimit;
+        return (perScanLimit * estRowSize < threshold) ? new Pair<>(estimatedBytes, estimatedRows) : null;
     }
     
     @SuppressWarnings("deprecation")
     private static ParallelIteratorFactory buildResultIteratorFactory(StatementContext context, FilterableStatement statement,
             TableRef tableRef, OrderBy orderBy, Integer limit,Integer offset, boolean allowPageFilter) throws SQLException {
 
-        if ((isSerial(context, statement, tableRef, orderBy, isAmountOfDataToScanWithinThreshold(context, tableRef.getTable(), QueryUtil.getOffsetLimit(limit, offset)))
+        if ((isSerial(context, statement, tableRef, orderBy, getEstimateOfDataSizeToScanIfWithinThreshold(context, tableRef.getTable(), QueryUtil.getOffsetLimit(limit, offset)) != null)
                 || isRoundRobinPossible(orderBy, context) || isPacingScannersPossible(context))) {
             return ParallelIteratorFactory.NOOP_FACTORY;
         }
@@ -219,10 +235,10 @@ public class ScanPlan extends BaseQueryPlan {
         } else {
             iterators = new ParallelIterators(this, perScanLimit, parallelIteratorFactory, scanGrouper, scan, initFirstScanOnly);
         }
+        estimatedRows = iterators.getEstimatedRowCount();
+        estimatedSize = iterators.getEstimatedByteCount();
         splits = iterators.getSplits();
         scans = iterators.getScans();
-        estimatedSize = iterators.getEstimatedByteCount();
-        estimatedRows = iterators.getEstimatedRowCount();
         if (isOffsetOnServer) {
             scanner = new ConcatResultIterator(iterators);
             if (limit != null) {
@@ -266,4 +282,22 @@ public class ScanPlan extends BaseQueryPlan {
     public boolean useRoundRobinIterator() throws SQLException {
         return ScanUtil.isRoundRobinPossible(orderBy, context);
     }
+
+    @Override
+    public Long getEstimatedRowsToScan() throws SQLException {
+        if (isSerial) {
+            return serialRowsEstimate;
+        }
+        return super.getEstimatedRowsToScan();
+    }
+
+    @Override
+    public Long getEstimatedBytesToScan() throws SQLException {
+        if (isSerial) {
+            return serialBytesEstimate;
+        }
+        return super.getEstimatedBytesToScan();
+    }
+
+
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/phoenix/blob/1ca38e87/phoenix-core/src/main/java/org/apache/phoenix/execute/SortMergeJoinPlan.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/execute/SortMergeJoinPlan.java b/phoenix-core/src/main/java/org/apache/phoenix/execute/SortMergeJoinPlan.java
index 75bd11c..4b63c50 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/execute/SortMergeJoinPlan.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/execute/SortMergeJoinPlan.java
@@ -17,6 +17,8 @@
  */
 package org.apache.phoenix.execute;
 
+import static org.apache.phoenix.util.NumberUtil.add;
+
 import java.io.IOException;
 import java.nio.MappedByteBuffer;
 import java.sql.ParameterMetaData;
@@ -88,6 +90,9 @@ public class SortMergeJoinPlan implements QueryPlan {
     private final boolean isSingleValueOnly;
     private final Set<TableRef> tableRefs;
     private final int thresholdBytes;
+    private Long estimatedBytes;
+    private Long estimatedRows;
+    private boolean explainPlanCalled;
 
     public SortMergeJoinPlan(StatementContext context, FilterableStatement statement, TableRef table, 
             JoinType type, QueryPlan lhsPlan, QueryPlan rhsPlan, List<Expression> lhsKeyExpressions, List<Expression> rhsKeyExpressions,
@@ -149,6 +154,7 @@ public class SortMergeJoinPlan implements QueryPlan {
 
     @Override
     public ExplainPlan getExplainPlan() throws SQLException {
+        explainPlanCalled = true;
         List<String> steps = Lists.newArrayList();
         steps.add("SORT-MERGE-JOIN (" + type.toString().toUpperCase() + ") TABLES");
         for (String step : lhsPlan.getExplainPlan().getPlanSteps()) {
@@ -158,6 +164,8 @@ public class SortMergeJoinPlan implements QueryPlan {
         for (String step : rhsPlan.getExplainPlan().getPlanSteps()) {
             steps.add("    " + step);            
         }
+        estimatedBytes = add(add(estimatedBytes, lhsPlan.getEstimatedBytesToScan()), rhsPlan.getEstimatedBytesToScan());
+        estimatedRows = add(add(estimatedRows, lhsPlan.getEstimatedRowsToScan()), rhsPlan.getEstimatedRowsToScan());
         return new ExplainPlan(steps);
     }
 
@@ -679,4 +687,20 @@ public class SortMergeJoinPlan implements QueryPlan {
     public QueryPlan getRhsPlan() {
         return rhsPlan;
     }
+
+    @Override
+    public Long getEstimatedRowsToScan() throws SQLException {
+        if (!explainPlanCalled) {
+            getExplainPlan();
+        }
+        return estimatedRows;
+    }
+
+    @Override
+    public Long getEstimatedBytesToScan() throws SQLException {
+        if (!explainPlanCalled) {
+            getExplainPlan();
+        }
+        return estimatedBytes;
+    }
 }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/1ca38e87/phoenix-core/src/main/java/org/apache/phoenix/execute/UnionPlan.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/execute/UnionPlan.java b/phoenix-core/src/main/java/org/apache/phoenix/execute/UnionPlan.java
index c6c5dd8..fd50a83 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/execute/UnionPlan.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/execute/UnionPlan.java
@@ -17,6 +17,8 @@
  */
 package org.apache.phoenix.execute;
 
+import static org.apache.phoenix.util.NumberUtil.add;
+
 import java.sql.ParameterMetaData;
 import java.sql.SQLException;
 import java.util.ArrayList;
@@ -62,6 +64,9 @@ public class UnionPlan implements QueryPlan {
     private final boolean isDegenerate;
     private final List<QueryPlan> plans;
     private UnionResultIterators iterators;
+    private Long estimatedRows;
+    private Long estimatedBytes;
+    private boolean explainPlanCalled;
 
     public UnionPlan(StatementContext context, FilterableStatement statement, TableRef table, RowProjector projector,
             Integer limit, Integer offset, OrderBy orderBy, GroupBy groupBy, List<QueryPlan> plans, ParameterMetaData paramMetaData) throws SQLException {
@@ -80,7 +85,7 @@ public class UnionPlan implements QueryPlan {
             if (plan.getContext().getScanRanges() != ScanRanges.NOTHING) {
                 isDegen = false;
                 break;
-            } 
+            }
         }
         this.isDegenerate = isDegen;     
     }
@@ -166,6 +171,7 @@ public class UnionPlan implements QueryPlan {
 
     @Override
     public ExplainPlan getExplainPlan() throws SQLException {
+        explainPlanCalled = true;
         List<String> steps = new ArrayList<String>();
         steps.add("UNION ALL OVER " + this.plans.size() + " QUERIES");
         ResultIterator iterator = iterator();
@@ -175,6 +181,10 @@ public class UnionPlan implements QueryPlan {
         for (int i = 1 ; i < steps.size()-offset; i++) {
             steps.set(i, "    " + steps.get(i));
         }
+        for (QueryPlan plan : plans) {
+            estimatedRows = add(estimatedRows, plan.getEstimatedRowsToScan());
+            estimatedBytes = add(estimatedBytes, plan.getEstimatedBytesToScan());
+        }
         return new ExplainPlan(steps);
     }
 
@@ -227,4 +237,20 @@ public class UnionPlan implements QueryPlan {
 		}
 		return sources;
 	}
+
+    @Override
+    public Long getEstimatedRowsToScan() throws SQLException {
+        if (!explainPlanCalled) {
+            getExplainPlan();
+        }
+        return estimatedRows;
+    }
+
+    @Override
+    public Long getEstimatedBytesToScan() throws SQLException {
+        if (!explainPlanCalled) {
+            getExplainPlan();
+        }
+        return estimatedBytes;
+    }
 }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/1ca38e87/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
----------------------------------------------------------------------
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 9a094ba..3688455 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
@@ -43,6 +43,7 @@ import java.util.Set;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.FileSystem;
 import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.hbase.Cell;
 import org.apache.hadoop.hbase.client.Scan;
 import org.apache.hadoop.hbase.util.Pair;
 import org.apache.phoenix.call.CallRunner;
@@ -83,6 +84,7 @@ import org.apache.phoenix.exception.SQLExceptionCode;
 import org.apache.phoenix.exception.SQLExceptionInfo;
 import org.apache.phoenix.exception.UpgradeRequiredException;
 import org.apache.phoenix.execute.MutationState;
+import org.apache.phoenix.expression.KeyValueColumnExpression;
 import org.apache.phoenix.expression.RowKeyColumnExpression;
 import org.apache.phoenix.iterate.MaterializedResultIterator;
 import org.apache.phoenix.iterate.ParallelScanGrouper;
@@ -95,12 +97,12 @@ import org.apache.phoenix.parse.BindableStatement;
 import org.apache.phoenix.parse.CloseStatement;
 import org.apache.phoenix.parse.ColumnDef;
 import org.apache.phoenix.parse.ColumnName;
-import org.apache.phoenix.parse.CursorName;
 import org.apache.phoenix.parse.CreateFunctionStatement;
 import org.apache.phoenix.parse.CreateIndexStatement;
 import org.apache.phoenix.parse.CreateSchemaStatement;
 import org.apache.phoenix.parse.CreateSequenceStatement;
 import org.apache.phoenix.parse.CreateTableStatement;
+import org.apache.phoenix.parse.CursorName;
 import org.apache.phoenix.parse.DeclareCursorStatement;
 import org.apache.phoenix.parse.DeleteJarStatement;
 import org.apache.phoenix.parse.DeleteStatement;
@@ -111,8 +113,8 @@ import org.apache.phoenix.parse.DropSchemaStatement;
 import org.apache.phoenix.parse.DropSequenceStatement;
 import org.apache.phoenix.parse.DropTableStatement;
 import org.apache.phoenix.parse.ExecuteUpgradeStatement;
-import org.apache.phoenix.parse.FetchStatement;
 import org.apache.phoenix.parse.ExplainStatement;
+import org.apache.phoenix.parse.FetchStatement;
 import org.apache.phoenix.parse.FilterableStatement;
 import org.apache.phoenix.parse.HintNode;
 import org.apache.phoenix.parse.IndexKeyConstraint;
@@ -146,8 +148,10 @@ import org.apache.phoenix.schema.ExecuteQueryNotApplicableException;
 import org.apache.phoenix.schema.ExecuteUpdateNotApplicableException;
 import org.apache.phoenix.schema.FunctionNotFoundException;
 import org.apache.phoenix.schema.MetaDataClient;
+import org.apache.phoenix.schema.PColumnImpl;
 import org.apache.phoenix.schema.PDatum;
 import org.apache.phoenix.schema.PIndexState;
+import org.apache.phoenix.schema.PNameFactory;
 import org.apache.phoenix.schema.PTable.IndexType;
 import org.apache.phoenix.schema.PTableType;
 import org.apache.phoenix.schema.RowKeyValueAccessor;
@@ -155,9 +159,10 @@ import org.apache.phoenix.schema.Sequence;
 import org.apache.phoenix.schema.SortOrder;
 import org.apache.phoenix.schema.TableRef;
 import org.apache.phoenix.schema.stats.StatisticsCollectionScope;
-import org.apache.phoenix.schema.tuple.SingleKeyValueTuple;
+import org.apache.phoenix.schema.tuple.MultiKeyValueTuple;
 import org.apache.phoenix.schema.tuple.Tuple;
 import org.apache.phoenix.schema.types.PDataType;
+import org.apache.phoenix.schema.types.PLong;
 import org.apache.phoenix.schema.types.PVarchar;
 import org.apache.phoenix.trace.util.Tracing;
 import org.apache.phoenix.util.ByteUtil;
@@ -464,12 +469,45 @@ public class PhoenixStatement implements Statement, SQLCloseable {
             return SortOrder.getDefault();
         }
     };
+    private static final String EXPLAIN_PLAN_BYTES_ESTIMATE_COLUMN_NAME = "BytesEstimate";
+    private static final byte[] EXPLAIN_PLAN_BYTES_ESTIMATE =
+            PVarchar.INSTANCE.toBytes(EXPLAIN_PLAN_BYTES_ESTIMATE_COLUMN_NAME);
+    private static final String EXPLAIN_PLAN_ROWS_ESTIMATE_COLUMN_NAME = "RowsEstimate";
+    private static final byte[] EXPLAIN_PLAN_ROWS_ESTIMATE =
+            PVarchar.INSTANCE.toBytes(EXPLAIN_PLAN_ROWS_ESTIMATE_COLUMN_NAME);
+
+    public static final String EXPLAIN_PLAN_BYTES_ESTIMATE_COLUMN_ALIAS = "EST_BYTES_READ";
+    public static final String EXPLAIN_PLAN_ROWS_COLUMN_ALIAS = "EST_ROWS_READ";
+
+    private static final PColumnImpl EXPLAIN_PLAN_BYTES_ESTIMATE_COLUMN =
+            new PColumnImpl(PNameFactory.newName(EXPLAIN_PLAN_BYTES_ESTIMATE),
+                    PNameFactory.newName(EXPLAIN_PLAN_FAMILY), PLong.INSTANCE, null, null, false, 1,
+                    SortOrder.getDefault(), 0, null, false, null, false, false,
+                    EXPLAIN_PLAN_BYTES_ESTIMATE);
+
+    private static final PColumnImpl EXPLAIN_PLAN_ROWS_ESTIMATE_COLUMN =
+            new PColumnImpl(PNameFactory.newName(EXPLAIN_PLAN_ROWS_ESTIMATE),
+                    PNameFactory.newName(EXPLAIN_PLAN_FAMILY), PLong.INSTANCE, null, null, false, 2,
+                    SortOrder.getDefault(), 0, null, false, null, false, false,
+                    EXPLAIN_PLAN_ROWS_ESTIMATE);
+
+    private static final RowProjector EXPLAIN_PLAN_ROW_PROJECTOR_WITH_BYTE_ROW_ESTIMATES =
+            new RowProjector(Arrays
+                    .<ColumnProjector> asList(
+                        new ExpressionProjector(EXPLAIN_PLAN_ALIAS, EXPLAIN_PLAN_TABLE_NAME,
+                                new RowKeyColumnExpression(EXPLAIN_PLAN_DATUM,
+                                        new RowKeyValueAccessor(Collections
+                                                .<PDatum> singletonList(EXPLAIN_PLAN_DATUM), 0)),
+                                false),
+                        new ExpressionProjector(
+                                EXPLAIN_PLAN_BYTES_ESTIMATE_COLUMN_ALIAS, EXPLAIN_PLAN_TABLE_NAME,
+                                new KeyValueColumnExpression(EXPLAIN_PLAN_BYTES_ESTIMATE_COLUMN), false),
+                        new ExpressionProjector(EXPLAIN_PLAN_ROWS_COLUMN_ALIAS,
+                                EXPLAIN_PLAN_TABLE_NAME, new KeyValueColumnExpression(
+                                        EXPLAIN_PLAN_ROWS_ESTIMATE_COLUMN),
+                                false)),
+                    0, true);
 
-    private static final RowProjector EXPLAIN_PLAN_ROW_PROJECTOR = new RowProjector(Arrays.<ColumnProjector>asList(
-            new ExpressionProjector(EXPLAIN_PLAN_ALIAS, EXPLAIN_PLAN_TABLE_NAME, 
-                    new RowKeyColumnExpression(EXPLAIN_PLAN_DATUM,
-                            new RowKeyValueAccessor(Collections.<PDatum>singletonList(EXPLAIN_PLAN_DATUM), 0)), false)
-            ), 0, true);
     private static class ExecutableExplainStatement extends ExplainStatement implements CompilableStatement {
 
         public ExecutableExplainStatement(BindableStatement statement) {
@@ -493,10 +531,28 @@ public class PhoenixStatement implements Statement, SQLCloseable {
             final StatementPlan plan = compilableStmt.compilePlan(stmt, Sequence.ValueOp.VALIDATE_SEQUENCE);
             List<String> planSteps = plan.getExplainPlan().getPlanSteps();
             List<Tuple> tuples = Lists.newArrayListWithExpectedSize(planSteps.size());
+            Long estimatedBytesToScan = plan.getEstimatedBytesToScan();
+            Long estimatedRowsToScan = plan.getEstimatedRowsToScan();
             for (String planStep : planSteps) {
-                Tuple tuple = new SingleKeyValueTuple(KeyValueUtil.newKeyValue(PVarchar.INSTANCE.toBytes(planStep), EXPLAIN_PLAN_FAMILY, EXPLAIN_PLAN_COLUMN, MetaDataProtocol.MIN_TABLE_TIMESTAMP, ByteUtil.EMPTY_BYTE_ARRAY));
+                byte[] row = PVarchar.INSTANCE.toBytes(planStep);
+                List<Cell> cells = Lists.newArrayListWithCapacity(3);
+                cells.add(KeyValueUtil.newKeyValue(row, EXPLAIN_PLAN_FAMILY, EXPLAIN_PLAN_COLUMN,
+                    MetaDataProtocol.MIN_TABLE_TIMESTAMP, ByteUtil.EMPTY_BYTE_ARRAY));
+                if (estimatedBytesToScan != null) {
+                    cells.add(KeyValueUtil.newKeyValue(row, EXPLAIN_PLAN_FAMILY, EXPLAIN_PLAN_BYTES_ESTIMATE,
+                        MetaDataProtocol.MIN_TABLE_TIMESTAMP,
+                        PLong.INSTANCE.toBytes(estimatedBytesToScan)));
+                }
+                if (estimatedRowsToScan != null) {
+                    cells.add(KeyValueUtil.newKeyValue(row, EXPLAIN_PLAN_FAMILY, EXPLAIN_PLAN_ROWS_ESTIMATE,
+                        MetaDataProtocol.MIN_TABLE_TIMESTAMP,
+                        PLong.INSTANCE.toBytes(estimatedRowsToScan)));
+                }
+                Tuple tuple = new MultiKeyValueTuple(cells);
                 tuples.add(tuple);
             }
+            final Long estimatedBytes = estimatedBytesToScan;
+            final Long estimatedRows = estimatedRowsToScan;
             final ResultIterator iterator = new MaterializedResultIterator(tuples);
             return new QueryPlan() {
 
@@ -542,7 +598,7 @@ public class PhoenixStatement implements Statement, SQLCloseable {
 
                 @Override
                 public RowProjector getProjector() {
-                    return EXPLAIN_PLAN_ROW_PROJECTOR;
+                    return EXPLAIN_PLAN_ROW_PROJECTOR_WITH_BYTE_ROW_ESTIMATES;
                 }
 
                 @Override
@@ -603,6 +659,16 @@ public class PhoenixStatement implements Statement, SQLCloseable {
                 public boolean useRoundRobinIterator() throws SQLException {
                     return false;
                 }
+
+                @Override
+                public Long getEstimatedRowsToScan() {
+                    return estimatedRows;
+                }
+
+                @Override
+                public Long getEstimatedBytesToScan() {
+                    return estimatedBytes;
+                }
                 
             };
         }
@@ -778,7 +844,7 @@ public class PhoenixStatement implements Statement, SQLCloseable {
             
         }
     }
-
+    
     private static class ExecutableDeclareCursorStatement extends DeclareCursorStatement implements CompilableStatement {
         public ExecutableDeclareCursorStatement(CursorName cursor, SelectStatement select){
             super(cursor, select);
@@ -1159,6 +1225,16 @@ public class PhoenixStatement implements Statement, SQLCloseable {
                     phxConn.getQueryServices().upgradeSystemTables(phxConn.getURL(), props);
                     return MutationState.emptyMutationState(-1, phxConn);
                 }
+
+                @Override
+                public Long getEstimatedRowsToScan() throws SQLException {
+                    return 0l;
+                }
+
+                @Override
+                public Long getEstimatedBytesToScan() throws SQLException {
+                    return 0l;
+                }
             };
         }
     }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/1ca38e87/phoenix-core/src/main/java/org/apache/phoenix/util/NumberUtil.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/util/NumberUtil.java b/phoenix-core/src/main/java/org/apache/phoenix/util/NumberUtil.java
index 56e0137..67b298f 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/util/NumberUtil.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/util/NumberUtil.java
@@ -53,4 +53,18 @@ public class NumberUtil {
         }
         return decimal;
     }
+
+    public static Long add(Long num1, Long num2) {
+        if (num1 == null) {
+            if (num2 == null) {
+                return null;
+            }
+            return num2;
+        } else {
+            if (num2 == null) {
+                return num1;
+            }
+            return num1 + num2;
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/1ca38e87/phoenix-core/src/main/java/org/apache/phoenix/util/PhoenixRuntime.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/util/PhoenixRuntime.java b/phoenix-core/src/main/java/org/apache/phoenix/util/PhoenixRuntime.java
index 0a1fd79..959ca1c 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/util/PhoenixRuntime.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/util/PhoenixRuntime.java
@@ -66,6 +66,7 @@ import org.apache.phoenix.expression.RowKeyColumnExpression;
 import org.apache.phoenix.jdbc.PhoenixConnection;
 import org.apache.phoenix.jdbc.PhoenixPreparedStatement;
 import org.apache.phoenix.jdbc.PhoenixResultSet;
+import org.apache.phoenix.jdbc.PhoenixStatement;
 import org.apache.phoenix.monitoring.GlobalClientMetrics;
 import org.apache.phoenix.monitoring.GlobalMetric;
 import org.apache.phoenix.query.QueryConstants;
@@ -188,6 +189,20 @@ public class PhoenixRuntime {
     public static final String REQUEST_METRIC_ATTRIB = "RequestMetric";
 
     /**
+     * Use this column name on the row returned by explain plan result set to get estimate of number
+     * of bytes read.
+     */
+    public static final String EXPLAIN_PLAN_ESTIMATED_BYTES_READ_COLUMN =
+            PhoenixStatement.EXPLAIN_PLAN_BYTES_ESTIMATE_COLUMN_ALIAS;
+
+    /**
+     * Use this column name on the row returned by explain plan result set to get estimate of number
+     * of rows read.
+     */
+    public static final String EXPLAIN_PLAN_ESTIMATED_ROWS_READ_COLUMN =
+            PhoenixStatement.EXPLAIN_PLAN_ROWS_COLUMN_ALIAS;
+
+    /**
      * All Phoenix specific connection properties
      * TODO: use enum instead
      */

http://git-wip-us.apache.org/repos/asf/phoenix/blob/1ca38e87/phoenix-core/src/test/java/org/apache/phoenix/execute/CorrelatePlanTest.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/execute/CorrelatePlanTest.java b/phoenix-core/src/test/java/org/apache/phoenix/execute/CorrelatePlanTest.java
index 896fd24..e04c787 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/execute/CorrelatePlanTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/execute/CorrelatePlanTest.java
@@ -230,7 +230,7 @@ public class CorrelatePlanTest {
         }
     }
 
-    private QueryPlan newLiteralResultIterationPlan(Object[][] rows, Integer offset) {
+    private QueryPlan newLiteralResultIterationPlan(Object[][] rows, Integer offset) throws SQLException {
         List<Tuple> tuples = Lists.newArrayList();
         Tuple baseTuple = new SingleKeyValueTuple(KeyValue.LOWESTKEY);
         for (Object[] row : rows) {

http://git-wip-us.apache.org/repos/asf/phoenix/blob/1ca38e87/phoenix-core/src/test/java/org/apache/phoenix/execute/LiteralResultIteratorPlanTest.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/execute/LiteralResultIteratorPlanTest.java b/phoenix-core/src/test/java/org/apache/phoenix/execute/LiteralResultIteratorPlanTest.java
index df55379..5f4691f 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/execute/LiteralResultIteratorPlanTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/execute/LiteralResultIteratorPlanTest.java
@@ -152,7 +152,7 @@ public class LiteralResultIteratorPlanTest {
         assertNull(iter.next());
     }
 
-    private QueryPlan newLiteralResultIterationPlan(Integer offset, Integer limit) {
+    private QueryPlan newLiteralResultIterationPlan(Integer offset, Integer limit) throws SQLException {
         List<Tuple> tuples = Lists.newArrayList();
 
         Tuple baseTuple = new SingleKeyValueTuple(KeyValue.LOWESTKEY);

http://git-wip-us.apache.org/repos/asf/phoenix/blob/1ca38e87/phoenix-core/src/test/java/org/apache/phoenix/query/ParallelIteratorsSplitTest.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/query/ParallelIteratorsSplitTest.java b/phoenix-core/src/test/java/org/apache/phoenix/query/ParallelIteratorsSplitTest.java
index 05fbf81..a0696c0 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/query/ParallelIteratorsSplitTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/query/ParallelIteratorsSplitTest.java
@@ -471,6 +471,16 @@ public class ParallelIteratorsSplitTest extends BaseConnectionlessQueryTest {
             public boolean useRoundRobinIterator() {
                 return false;
             }
+
+            @Override
+            public Long getEstimatedRowsToScan() {
+                return null;
+            }
+
+            @Override
+            public Long getEstimatedBytesToScan() {
+                return null;
+            }
             
         }, null, new SpoolingResultIterator.SpoolingResultIteratorFactory(context.getConnection().getQueryServices()), context.getScan(), false);
         List<KeyRange> keyRanges = parallelIterators.getSplits();


Mime
View raw message