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 2020/12/07 07:15:07 UTC

[phoenix] branch 4.x updated: PHOENIX-5728 : ExplainPlan with plan as attributes object

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

stoty pushed a commit to branch 4.x
in repository https://gitbox.apache.org/repos/asf/phoenix.git


The following commit(s) were added to refs/heads/4.x by this push:
     new e281ec6  PHOENIX-5728 : ExplainPlan with plan as attributes object
e281ec6 is described below

commit e281ec67e49b1dc8b924a7faa5f6fc8529bce1b3
Author: Viraj Jasani <vj...@apache.org>
AuthorDate: Mon Nov 30 23:02:12 2020 +0530

    PHOENIX-5728 : ExplainPlan with plan as attributes object
---
 .../apache/phoenix/iterate/MockResultIterator.java |   7 +
 .../phoenix/schema/stats/BaseStatsCollectorIT.java | 129 +++--
 .../org/apache/phoenix/compile/DeleteCompiler.java |  25 +-
 .../org/apache/phoenix/compile/ExplainPlan.java    |  19 +-
 .../phoenix/compile/ExplainPlanAttributes.java     | 598 +++++++++++++++++++++
 .../apache/phoenix/compile/GroupByCompiler.java    |  51 +-
 .../apache/phoenix/compile/ListJarsQueryPlan.java  |   7 +
 .../compile/MutatingParallelIteratorFactory.java   |   7 +
 .../org/apache/phoenix/compile/TraceQueryPlan.java |   7 +
 .../org/apache/phoenix/compile/UpsertCompiler.java |  28 +-
 .../org/apache/phoenix/execute/BaseQueryPlan.java  |  18 +-
 .../phoenix/execute/ClientAggregatePlan.java       |  34 +-
 .../org/apache/phoenix/execute/ClientScanPlan.java |  23 +-
 .../org/apache/phoenix/execute/CorrelatePlan.java  |  37 +-
 .../org/apache/phoenix/execute/HashJoinPlan.java   |   1 +
 .../execute/LiteralResultIterationPlan.java        |   9 +-
 .../apache/phoenix/execute/SortMergeJoinPlan.java  |  44 +-
 .../phoenix/execute/TupleProjectionPlan.java       |  14 +-
 .../java/org/apache/phoenix/execute/UnionPlan.java |  12 +-
 .../apache/phoenix/execute/UnnestArrayPlan.java    |  13 +-
 .../BaseGroupedAggregatingResultIterator.java      |   7 +
 .../apache/phoenix/iterate/BaseResultIterator.java |   7 +
 .../phoenix/iterate/BaseResultIterators.java       |  67 ++-
 .../phoenix/iterate/ChunkedResultIterator.java     |  14 +
 .../ClientHashAggregatingResultIterator.java       |   8 +
 .../phoenix/iterate/ConcatResultIterator.java      |  10 +
 .../phoenix/iterate/CursorResultIterator.java      |  10 +
 .../phoenix/iterate/DelegateResultIterator.java    |   8 +
 .../iterate/DistinctAggregatingResultIterator.java |  11 +
 .../org/apache/phoenix/iterate/ExplainTable.java   |  91 +++-
 .../iterate/FilterAggregatingResultIterator.java   |  20 +-
 .../phoenix/iterate/FilterResultIterator.java      |  20 +-
 .../phoenix/iterate/LimitingResultIterator.java    |  22 +-
 .../phoenix/iterate/LookAheadResultIterator.java   |   8 +
 .../MaterializedComparableResultIterator.java      |   8 +
 .../iterate/MaterializedResultIterator.java        |   7 +
 .../iterate/MergeSortRowKeyResultIterator.java     |  20 +-
 .../iterate/MergeSortTopNResultIterator.java       |  30 +-
 .../phoenix/iterate/OffsetResultIterator.java      |  10 +
 .../phoenix/iterate/OrderedResultIterator.java     |  20 +
 .../phoenix/iterate/PeekingResultIterator.java     |   7 +
 .../org/apache/phoenix/iterate/ResultIterator.java |  24 +
 .../apache/phoenix/iterate/ResultIterators.java    |  20 +
 .../phoenix/iterate/RoundRobinResultIterator.java  |  16 +
 .../RowKeyOrderedAggregateResultIterator.java      |  10 +
 .../phoenix/iterate/ScanningResultIterator.java    |   7 +
 .../phoenix/iterate/SequenceResultIterator.java    |  22 +-
 .../apache/phoenix/iterate/SerialIterators.java    |   7 +
 .../phoenix/iterate/SpoolingResultIterator.java    |  17 +
 .../phoenix/iterate/TableResultIterator.java       |   8 +
 .../iterate/TableSnapshotResultIterator.java       |  15 +-
 .../phoenix/iterate/UnionResultIterators.java      |  30 +-
 .../phoenix/iterate/ConcatResultIteratorTest.java  |   8 +
 .../iterate/MaterializedResultIterators.java       |   7 +
 .../iterate/MergeSortResultIteratorTest.java       |  17 +
 55 files changed, 1552 insertions(+), 144 deletions(-)

diff --git a/phoenix-core/src/it/java/org/apache/phoenix/iterate/MockResultIterator.java b/phoenix-core/src/it/java/org/apache/phoenix/iterate/MockResultIterator.java
index e842317..5b5b643 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/iterate/MockResultIterator.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/iterate/MockResultIterator.java
@@ -28,6 +28,8 @@ import org.apache.hadoop.hbase.Cell;
 import org.apache.hadoop.hbase.KeyValue;
 import org.apache.hadoop.hbase.client.Result;
 import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.execute.TupleProjector;
 import org.apache.phoenix.schema.PTable;
 import org.apache.phoenix.schema.tuple.ResultTuple;
@@ -56,6 +58,11 @@ public class MockResultIterator implements PeekingResultIterator {
     public void explain(List<String> planSteps) {}
 
     @Override
+    public void explain(List<String> planSteps,
+            ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+    }
+
+    @Override
     public void close() throws SQLException {}
 
     @Override
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/schema/stats/BaseStatsCollectorIT.java b/phoenix-core/src/it/java/org/apache/phoenix/schema/stats/BaseStatsCollectorIT.java
index 39c68e5..97cfa8f 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/schema/stats/BaseStatsCollectorIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/schema/stats/BaseStatsCollectorIT.java
@@ -26,6 +26,7 @@ import static org.apache.phoenix.util.TestUtil.getAllSplits;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -46,6 +47,9 @@ import org.apache.hadoop.hbase.HColumnDescriptor;
 import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.HRegionLocation;
 import org.apache.hadoop.hbase.TableName;
+import org.apache.phoenix.compile.ExplainPlan;
+import org.apache.phoenix.compile.ExplainPlanAttributes;
+import org.apache.phoenix.jdbc.PhoenixPreparedStatement;
 import org.apache.hadoop.hbase.client.HBaseAdmin;
 import org.apache.hadoop.hbase.client.Result;
 import org.apache.hadoop.hbase.client.ResultScanner;
@@ -252,12 +256,16 @@ public abstract class BaseStatsCollectorIT extends BaseUniqueNamesOwnClusterIT {
         conn.createStatement().execute(
                 "CREATE TABLE " + fullTableName +" ( k CHAR(1) PRIMARY KEY )"  + tableDDLOptions);
         collectStatistics(conn, fullTableName);
-        ResultSet rs = conn.createStatement().executeQuery("EXPLAIN SELECT * FROM " + fullTableName);
-        String explainPlan = QueryUtil.getExplainPlan(rs);
-        assertEquals(
-                "CLIENT 1-CHUNK 0 ROWS 20 BYTES PARALLEL 1-WAY FULL SCAN OVER " + physicalTableName + "\n" +
-                "    SERVER FILTER BY FIRST KEY ONLY",
-                explainPlan);
+        ExplainPlan plan = conn.prepareStatement("SELECT * FROM " + fullTableName)
+            .unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan();
+        ExplainPlanAttributes planAttributes = plan.getPlanStepsAsAttributes();
+        assertEquals(1, (int) planAttributes.getSplitsChunk());
+        assertEquals(0, (long) planAttributes.getEstimatedRows());
+        assertEquals(20, (long) planAttributes.getEstimatedSizeInBytes());
+        assertEquals("PARALLEL 1-WAY", planAttributes.getIteratorTypeAndScanSize());
+        assertEquals("FULL SCAN ", planAttributes.getExplainScanType());
+        assertEquals(physicalTableName, planAttributes.getTableName());
+        assertEquals("SERVER FILTER BY FIRST KEY ONLY", planAttributes.getServerWhereFilter());
         conn.close();
     }
 
@@ -270,30 +278,56 @@ public abstract class BaseStatsCollectorIT extends BaseUniqueNamesOwnClusterIT {
         conn.createStatement().execute("UPSERT INTO " + fullTableName + "(k,v1) VALUES('a','123456789')");
         collectStatistics(conn, fullTableName);
                 
-        ResultSet rs;
-        String explainPlan;
-        rs = conn.createStatement().executeQuery("EXPLAIN SELECT v2 FROM " + fullTableName + " WHERE v2='foo'");
-        explainPlan = QueryUtil.getExplainPlan(rs);
-        // if we are using the ONE_CELL_PER_COLUMN_FAMILY storage scheme, we will have the single kv even though there are no values for col family v2 
-        String stats = columnEncoded && !mutable ? "4-CHUNK 1 ROWS 38 BYTES" : "3-CHUNK 0 ROWS 20 BYTES";
-        assertEquals(
-                "CLIENT " + stats + " PARALLEL 3-WAY FULL SCAN OVER " + physicalTableName + "\n" +
-                "    SERVER FILTER BY B.V2 = 'foo'\n" + 
-                "CLIENT MERGE SORT",
-                explainPlan);
-        rs = conn.createStatement().executeQuery("EXPLAIN SELECT * FROM " + fullTableName);
-        explainPlan = QueryUtil.getExplainPlan(rs);
-        assertEquals(
-                "CLIENT 4-CHUNK 1 ROWS " + (columnEncoded ? "28" : TransactionFactory.Provider.OMID.name().equals(transactionProvider) ? "38" : "34") + " BYTES PARALLEL 3-WAY FULL SCAN OVER " + physicalTableName + "\n" +
-                "CLIENT MERGE SORT",
-                explainPlan);
-        rs = conn.createStatement().executeQuery("EXPLAIN SELECT * FROM " + fullTableName + " WHERE k = 'a'");
-        explainPlan = QueryUtil.getExplainPlan(rs);
-        assertEquals(
-                "CLIENT 1-CHUNK 1 ROWS " + (columnEncoded ? "204" : "202") + " BYTES PARALLEL 1-WAY POINT LOOKUP ON 1 KEY OVER " + physicalTableName + "\n" +
-                "CLIENT MERGE SORT",
-                explainPlan);
-        
+        // if we are using the ONE_CELL_PER_COLUMN_FAMILY storage scheme, we will have the single kv even though there are no values for col family v2
+
+        ExplainPlan plan = conn.prepareStatement(
+            "SELECT v2 FROM " + fullTableName + " WHERE v2='foo'")
+            .unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan();
+        ExplainPlanAttributes planAttributes = plan.getPlanStepsAsAttributes();
+        assertEquals(columnEncoded && !mutable ? 4 : 3,
+            (int) planAttributes.getSplitsChunk());
+        assertEquals(columnEncoded && !mutable ? 1 : 0,
+            (long) planAttributes.getEstimatedRows());
+        assertEquals(columnEncoded && !mutable ? 38 : 20,
+            (long) planAttributes.getEstimatedSizeInBytes());
+        assertEquals("PARALLEL 3-WAY", planAttributes.getIteratorTypeAndScanSize());
+        assertEquals("FULL SCAN ", planAttributes.getExplainScanType());
+        assertEquals(physicalTableName, planAttributes.getTableName());
+        assertEquals("SERVER FILTER BY B.V2 = 'foo'",
+            planAttributes.getServerWhereFilter());
+        assertEquals("CLIENT MERGE SORT", planAttributes.getClientSortAlgo());
+
+        long estimatedSizeInBytes = columnEncoded ? 28
+            : TransactionFactory.Provider.OMID.name().equals(transactionProvider)
+            ? 38 : 34;
+        plan = conn.prepareStatement(
+            "SELECT * FROM " + fullTableName)
+            .unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan();
+        planAttributes = plan.getPlanStepsAsAttributes();
+        assertEquals(4, (int) planAttributes.getSplitsChunk());
+        assertEquals(1, (long) planAttributes.getEstimatedRows());
+        assertEquals(estimatedSizeInBytes,
+            (long) planAttributes.getEstimatedSizeInBytes());
+        assertEquals("PARALLEL 3-WAY", planAttributes.getIteratorTypeAndScanSize());
+        assertEquals("FULL SCAN ", planAttributes.getExplainScanType());
+        assertEquals(physicalTableName, planAttributes.getTableName());
+        assertNull(planAttributes.getServerWhereFilter());
+        assertEquals("CLIENT MERGE SORT", planAttributes.getClientSortAlgo());
+
+        plan = conn.prepareStatement(
+            "SELECT * FROM " + fullTableName + " WHERE k = 'a'")
+            .unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan();
+        planAttributes = plan.getPlanStepsAsAttributes();
+        assertEquals(1, (int) planAttributes.getSplitsChunk());
+        assertEquals(1, (long) planAttributes.getEstimatedRows());
+        assertEquals(columnEncoded ? 204 : 202,
+            (long) planAttributes.getEstimatedSizeInBytes());
+        assertEquals("PARALLEL 1-WAY", planAttributes.getIteratorTypeAndScanSize());
+        assertEquals("POINT LOOKUP ON 1 KEY ", planAttributes.getExplainScanType());
+        assertEquals(physicalTableName, planAttributes.getTableName());
+        assertNull(planAttributes.getServerWhereFilter());
+        assertEquals("CLIENT MERGE SORT", planAttributes.getClientSortAlgo());
+
         conn.close();
     }
     
@@ -569,9 +603,23 @@ public abstract class BaseStatsCollectorIT extends BaseUniqueNamesOwnClusterIT {
         collectStatistics(conn, fullTableName);
         List<KeyRange> keyRanges = getAllSplits(conn, fullTableName);
         assertEquals(26, keyRanges.size());
-        rs = conn.createStatement().executeQuery("EXPLAIN SELECT * FROM " + fullTableName);
-        assertEquals("CLIENT 26-CHUNK 25 ROWS " + (columnEncoded ? ( mutable ? "12530" : "13902" ) : (TransactionFactory.Provider.OMID.name().equals(transactionProvider)) ? "25044" : "12420") + " BYTES PARALLEL 1-WAY FULL SCAN OVER " + physicalTableName,
-                QueryUtil.getExplainPlan(rs));
+
+        long sizeInBytes = columnEncoded ? (mutable ? 12530 : 13902)
+            : (TransactionFactory.Provider.OMID.name().equals(transactionProvider))
+            ? 25044 : 12420;
+
+        ExplainPlan plan = conn.prepareStatement(
+            "SELECT * FROM " + fullTableName)
+            .unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan();
+        ExplainPlanAttributes planAttributes = plan.getPlanStepsAsAttributes();
+        assertEquals(26, (int) planAttributes.getSplitsChunk());
+        assertEquals(25, (long) planAttributes.getEstimatedRows());
+        assertEquals(sizeInBytes,
+            (long) planAttributes.getEstimatedSizeInBytes());
+        assertEquals("PARALLEL 1-WAY",
+            planAttributes.getIteratorTypeAndScanSize());
+        assertEquals("FULL SCAN ", planAttributes.getExplainScanType());
+        assertEquals(physicalTableName, planAttributes.getTableName());
 
         ConnectionQueryServices services = conn.unwrap(PhoenixConnection.class).getQueryServices();
         List<HRegionLocation> regions = services.getAllTableRegions(Bytes.toBytes(physicalTableName));
@@ -625,9 +673,18 @@ public abstract class BaseStatsCollectorIT extends BaseUniqueNamesOwnClusterIT {
         assertTrue(rs.next());
         assertEquals(0, rs.getLong(1));
         assertFalse(rs.next());
-        rs = conn.createStatement().executeQuery("EXPLAIN SELECT * FROM " + fullTableName);
-        assertEquals("CLIENT 1-CHUNK PARALLEL 1-WAY FULL SCAN OVER " + physicalTableName,
-                QueryUtil.getExplainPlan(rs));
+
+        plan = conn.prepareStatement(
+            "SELECT * FROM " + fullTableName)
+            .unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan();
+        planAttributes = plan.getPlanStepsAsAttributes();
+        assertEquals(1, (int) planAttributes.getSplitsChunk());
+        assertNull(planAttributes.getEstimatedRows());
+        assertNull(planAttributes.getEstimatedSizeInBytes());
+        assertEquals("PARALLEL 1-WAY",
+          planAttributes.getIteratorTypeAndScanSize());
+        assertEquals("FULL SCAN ", planAttributes.getExplainScanType());
+        assertEquals(physicalTableName, planAttributes.getTableName());
     }
 
     @Test
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 8b43c8c..ebba909 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
@@ -36,6 +36,8 @@ import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
 import org.apache.hadoop.hbase.util.Pair;
 import org.apache.phoenix.cache.ServerCacheClient;
 import org.apache.phoenix.cache.ServerCacheClient.ServerCache;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.compile.GroupByCompiler.GroupBy;
 import org.apache.phoenix.compile.OrderByCompiler.OrderBy;
 import org.apache.phoenix.coprocessor.BaseScannerRegionObserver;
@@ -823,11 +825,18 @@ public class DeleteCompiler {
 
         @Override
         public ExplainPlan getExplainPlan() throws SQLException {
-            List<String> queryPlanSteps =  aggPlan.getExplainPlan().getPlanSteps();
-            List<String> planSteps = Lists.newArrayListWithExpectedSize(queryPlanSteps.size()+1);
+            ExplainPlan explainPlan = aggPlan.getExplainPlan();
+            List<String> queryPlanSteps = explainPlan.getPlanSteps();
+            ExplainPlanAttributes explainPlanAttributes =
+                explainPlan.getPlanStepsAsAttributes();
+            List<String> planSteps =
+                Lists.newArrayListWithExpectedSize(queryPlanSteps.size() + 1);
+            ExplainPlanAttributesBuilder newBuilder =
+                new ExplainPlanAttributesBuilder(explainPlanAttributes);
+            newBuilder.setAbstractExplainPlan("DELETE ROWS SERVER SELECT");
             planSteps.add("DELETE ROWS SERVER SELECT");
             planSteps.addAll(queryPlanSteps);
-            return new ExplainPlan(planSteps);
+            return new ExplainPlan(planSteps, newBuilder.build());
         }
 
         @Override
@@ -945,11 +954,17 @@ public class DeleteCompiler {
 
         @Override
         public ExplainPlan getExplainPlan() throws SQLException {
-            List<String> queryPlanSteps =  bestPlan.getExplainPlan().getPlanSteps();
+            ExplainPlan explainPlan = bestPlan.getExplainPlan();
+            List<String> queryPlanSteps = explainPlan.getPlanSteps();
+            ExplainPlanAttributes explainPlanAttributes =
+                explainPlan.getPlanStepsAsAttributes();
             List<String> planSteps = Lists.newArrayListWithExpectedSize(queryPlanSteps.size()+1);
+            ExplainPlanAttributesBuilder newBuilder =
+                new ExplainPlanAttributesBuilder(explainPlanAttributes);
+            newBuilder.setAbstractExplainPlan("DELETE ROWS CLIENT SELECT");
             planSteps.add("DELETE ROWS CLIENT SELECT");
             planSteps.addAll(queryPlanSteps);
-            return new ExplainPlan(planSteps);
+            return new ExplainPlan(planSteps, newBuilder.build());
         }
 
         @Override
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/ExplainPlan.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/ExplainPlan.java
index ef34daa..a180b6f 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/ExplainPlan.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/ExplainPlan.java
@@ -26,15 +26,28 @@ public class ExplainPlan {
     public static final ExplainPlan EMPTY_PLAN = new ExplainPlan(Collections.<String>emptyList());
 
     private final List<String> planSteps;
-    
+    private final ExplainPlanAttributes planStepsAsAttributes;
+
     public ExplainPlan(List<String> planSteps) {
         this.planSteps = ImmutableList.copyOf(planSteps);
+        this.planStepsAsAttributes =
+            ExplainPlanAttributes.getDefaultExplainPlan();
+    }
+
+    public ExplainPlan(List<String> planSteps,
+            ExplainPlanAttributes planStepsAsAttributes) {
+        this.planSteps = planSteps;
+        this.planStepsAsAttributes = planStepsAsAttributes;
     }
-    
+
     public List<String> getPlanSteps() {
         return planSteps;
     }
-    
+
+    public ExplainPlanAttributes getPlanStepsAsAttributes() {
+        return planStepsAsAttributes;
+    }
+
     @Override
     public String toString() {
         StringBuilder buf = new StringBuilder();
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/ExplainPlanAttributes.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/ExplainPlanAttributes.java
new file mode 100644
index 0000000..855ce96
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/ExplainPlanAttributes.java
@@ -0,0 +1,598 @@
+/*
+ * 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.compile;
+
+import org.apache.hadoop.hbase.client.Consistency;
+import org.apache.phoenix.parse.HintNode;
+import org.apache.phoenix.parse.HintNode.Hint;
+
+/**
+ * ExplainPlan attributes that contain individual attributes of ExplainPlan
+ * that we can assert against. This also makes attribute retrieval easier
+ * as an API rather than retrieving list of Strings containing entire plan.
+ */
+public class ExplainPlanAttributes {
+
+    private final String abstractExplainPlan;
+    private final Integer splitsChunk;
+    private final Long estimatedRows;
+    private final Long estimatedSizeInBytes;
+    private final String iteratorTypeAndScanSize;
+    private final Double samplingRate;
+    private final boolean useRoundRobinIterator;
+    private final String hexStringRVCOffset;
+    private final Consistency consistency;
+    private final Hint hint;
+    private final String serverSortedBy;
+    private final String explainScanType;
+    private final String tableName;
+    private final String keyRanges;
+    private final Long scanTimeRangeMin;
+    private final Long scanTimeRangeMax;
+    private final String serverWhereFilter;
+    private final String serverDistinctFilter;
+    private final Integer serverOffset;
+    private final Long serverRowLimit;
+    private final boolean serverArrayElementProjection;
+    private final String serverAggregate;
+    private final String clientFilterBy;
+    private final String clientAggregate;
+    private final String clientSortedBy;
+    private final String clientAfterAggregate;
+    private final String clientDistinctFilter;
+    private final Integer clientOffset;
+    private final Integer clientRowLimit;
+    private final Integer clientSequenceCount;
+    private final String clientCursorName;
+    private final String clientSortAlgo;
+    // This object represents PlanAttributes object for rhs query
+    // to be used only by Join queries. In case of Join query, lhs plan is
+    // represented by 'this' object and rhs plan is represented by
+    // 'rhsJoinQueryExplainPlan' object (which in turn should
+    // have null rhsJoinQueryExplainPlan)
+    // For non-Join queries related Plans, rhsJoinQueryExplainPlan will always
+    // be null
+    private final ExplainPlanAttributes rhsJoinQueryExplainPlan;
+
+    private static final ExplainPlanAttributes EXPLAIN_PLAN_INSTANCE =
+        new ExplainPlanAttributes();
+
+    private ExplainPlanAttributes() {
+        this.abstractExplainPlan = null;
+        this.splitsChunk = null;
+        this.estimatedRows = null;
+        this.estimatedSizeInBytes = null;
+        this.iteratorTypeAndScanSize = null;
+        this.samplingRate = null;
+        this.useRoundRobinIterator = false;
+        this.hexStringRVCOffset = null;
+        this.consistency = null;
+        this.hint = null;
+        this.serverSortedBy = null;
+        this.explainScanType = null;
+        this.tableName = null;
+        this.keyRanges = null;
+        this.scanTimeRangeMin = null;
+        this.scanTimeRangeMax = null;
+        this.serverWhereFilter = null;
+        this.serverDistinctFilter = null;
+        this.serverOffset = null;
+        this.serverRowLimit = null;
+        this.serverArrayElementProjection = false;
+        this.serverAggregate = null;
+        this.clientFilterBy = null;
+        this.clientAggregate = null;
+        this.clientSortedBy = null;
+        this.clientAfterAggregate = null;
+        this.clientDistinctFilter = null;
+        this.clientOffset = null;
+        this.clientRowLimit = null;
+        this.clientSequenceCount = null;
+        this.clientCursorName = null;
+        this.clientSortAlgo = null;
+        this.rhsJoinQueryExplainPlan = null;
+    }
+
+    public ExplainPlanAttributes(String abstractExplainPlan,
+            Integer splitsChunk, Long estimatedRows, Long estimatedSizeInBytes,
+            String iteratorTypeAndScanSize, Double samplingRate,
+            boolean useRoundRobinIterator,
+            String hexStringRVCOffset, Consistency consistency,
+            Hint hint, String serverSortedBy, String explainScanType,
+            String tableName, String keyRanges, Long scanTimeRangeMin,
+            Long scanTimeRangeMax, String serverWhereFilter,
+            String serverDistinctFilter,
+            Integer serverOffset, Long serverRowLimit,
+            boolean serverArrayElementProjection, String serverAggregate,
+            String clientFilterBy, String clientAggregate,
+            String clientSortedBy,
+            String clientAfterAggregate, String clientDistinctFilter,
+            Integer clientOffset, Integer clientRowLimit,
+            Integer clientSequenceCount, String clientCursorName,
+            String clientSortAlgo,
+            ExplainPlanAttributes rhsJoinQueryExplainPlan) {
+        this.abstractExplainPlan = abstractExplainPlan;
+        this.splitsChunk = splitsChunk;
+        this.estimatedRows = estimatedRows;
+        this.estimatedSizeInBytes = estimatedSizeInBytes;
+        this.iteratorTypeAndScanSize = iteratorTypeAndScanSize;
+        this.samplingRate = samplingRate;
+        this.useRoundRobinIterator = useRoundRobinIterator;
+        this.hexStringRVCOffset = hexStringRVCOffset;
+        this.consistency = consistency;
+        this.hint = hint;
+        this.serverSortedBy = serverSortedBy;
+        this.explainScanType = explainScanType;
+        this.tableName = tableName;
+        this.keyRanges = keyRanges;
+        this.scanTimeRangeMin = scanTimeRangeMin;
+        this.scanTimeRangeMax = scanTimeRangeMax;
+        this.serverWhereFilter = serverWhereFilter;
+        this.serverDistinctFilter = serverDistinctFilter;
+        this.serverOffset = serverOffset;
+        this.serverRowLimit = serverRowLimit;
+        this.serverArrayElementProjection = serverArrayElementProjection;
+        this.serverAggregate = serverAggregate;
+        this.clientFilterBy = clientFilterBy;
+        this.clientAggregate = clientAggregate;
+        this.clientSortedBy = clientSortedBy;
+        this.clientAfterAggregate = clientAfterAggregate;
+        this.clientDistinctFilter = clientDistinctFilter;
+        this.clientOffset = clientOffset;
+        this.clientRowLimit = clientRowLimit;
+        this.clientSequenceCount = clientSequenceCount;
+        this.clientCursorName = clientCursorName;
+        this.clientSortAlgo = clientSortAlgo;
+        this.rhsJoinQueryExplainPlan = rhsJoinQueryExplainPlan;
+    }
+
+    public String getAbstractExplainPlan() {
+        return abstractExplainPlan;
+    }
+
+    public Integer getSplitsChunk() {
+        return splitsChunk;
+    }
+
+    public Long getEstimatedRows() {
+        return estimatedRows;
+    }
+
+    public Long getEstimatedSizeInBytes() {
+        return estimatedSizeInBytes;
+    }
+
+    public String getIteratorTypeAndScanSize() {
+        return iteratorTypeAndScanSize;
+    }
+
+    public Double getSamplingRate() {
+        return samplingRate;
+    }
+
+    public boolean isUseRoundRobinIterator() {
+        return useRoundRobinIterator;
+    }
+
+    public String getHexStringRVCOffset() {
+        return hexStringRVCOffset;
+    }
+
+    public Consistency getConsistency() {
+        return consistency;
+    }
+
+    public Hint getHint() {
+        return hint;
+    }
+
+    public String getServerSortedBy() {
+        return serverSortedBy;
+    }
+
+    public String getExplainScanType() {
+        return explainScanType;
+    }
+
+    public String getTableName() {
+        return tableName;
+    }
+
+    public String getKeyRanges() {
+        return keyRanges;
+    }
+
+    public Long getScanTimeRangeMin() {
+        return scanTimeRangeMin;
+    }
+
+    public Long getScanTimeRangeMax() {
+        return scanTimeRangeMax;
+    }
+
+    public String getServerWhereFilter() {
+        return serverWhereFilter;
+    }
+
+    public String getServerDistinctFilter() {
+        return serverDistinctFilter;
+    }
+
+    public Integer getServerOffset() {
+        return serverOffset;
+    }
+
+    public Long getServerRowLimit() {
+        return serverRowLimit;
+    }
+
+    public boolean isServerArrayElementProjection() {
+        return serverArrayElementProjection;
+    }
+
+    public String getServerAggregate() {
+        return serverAggregate;
+    }
+
+    public String getClientFilterBy() {
+        return clientFilterBy;
+    }
+
+    public String getClientAggregate() {
+        return clientAggregate;
+    }
+
+    public String getClientSortedBy() {
+        return clientSortedBy;
+    }
+
+    public String getClientAfterAggregate() {
+        return clientAfterAggregate;
+    }
+
+    public String getClientDistinctFilter() {
+        return clientDistinctFilter;
+    }
+
+    public Integer getClientOffset() {
+        return clientOffset;
+    }
+
+    public Integer getClientRowLimit() {
+        return clientRowLimit;
+    }
+
+    public Integer getClientSequenceCount() {
+        return clientSequenceCount;
+    }
+
+    public String getClientCursorName() {
+        return clientCursorName;
+    }
+
+    public String getClientSortAlgo() {
+        return clientSortAlgo;
+    }
+
+    public ExplainPlanAttributes getRhsJoinQueryExplainPlan() {
+        return rhsJoinQueryExplainPlan;
+    }
+
+    public static ExplainPlanAttributes getDefaultExplainPlan() {
+        return EXPLAIN_PLAN_INSTANCE;
+    }
+
+    public static class ExplainPlanAttributesBuilder {
+        private String abstractExplainPlan;
+        private Integer splitsChunk;
+        private Long estimatedRows;
+        private Long estimatedSizeInBytes;
+        private String iteratorTypeAndScanSize;
+        private Double samplingRate;
+        private boolean useRoundRobinIterator;
+        private String hexStringRVCOffset;
+        private Consistency consistency;
+        private HintNode.Hint hint;
+        private String serverSortedBy;
+        private String explainScanType;
+        private String tableName;
+        private String keyRanges;
+        private Long scanTimeRangeMin;
+        private Long scanTimeRangeMax;
+        private String serverWhereFilter;
+        private String serverDistinctFilter;
+        private Integer serverOffset;
+        private Long serverRowLimit;
+        private boolean serverArrayElementProjection;
+        private String serverAggregate;
+        private String clientFilterBy;
+        private String clientAggregate;
+        private String clientSortedBy;
+        private String clientAfterAggregate;
+        private String clientDistinctFilter;
+        private Integer clientOffset;
+        private Integer clientRowLimit;
+        private Integer clientSequenceCount;
+        private String clientCursorName;
+        private String clientSortAlgo;
+        private ExplainPlanAttributes rhsJoinQueryExplainPlan;
+
+        public ExplainPlanAttributesBuilder() {
+            // default
+        }
+
+        public ExplainPlanAttributesBuilder(
+                ExplainPlanAttributes explainPlanAttributes) {
+            this.abstractExplainPlan =
+                explainPlanAttributes.getAbstractExplainPlan();
+            this.splitsChunk = explainPlanAttributes.getSplitsChunk();
+            this.estimatedRows = explainPlanAttributes.getEstimatedRows();
+            this.estimatedSizeInBytes =
+                explainPlanAttributes.getEstimatedSizeInBytes();
+            this.iteratorTypeAndScanSize = explainPlanAttributes.getIteratorTypeAndScanSize();
+            this.samplingRate = explainPlanAttributes.getSamplingRate();
+            this.useRoundRobinIterator =
+                explainPlanAttributes.isUseRoundRobinIterator();
+            this.hexStringRVCOffset =
+                explainPlanAttributes.getHexStringRVCOffset();
+            this.consistency = explainPlanAttributes.getConsistency();
+            this.hint = explainPlanAttributes.getHint();
+            this.serverSortedBy = explainPlanAttributes.getServerSortedBy();
+            this.explainScanType = explainPlanAttributes.getExplainScanType();
+            this.tableName = explainPlanAttributes.getTableName();
+            this.keyRanges = explainPlanAttributes.getKeyRanges();
+            this.scanTimeRangeMin = explainPlanAttributes.getScanTimeRangeMin();
+            this.scanTimeRangeMax = explainPlanAttributes.getScanTimeRangeMax();
+            this.serverWhereFilter =
+                explainPlanAttributes.getServerWhereFilter();
+            this.serverDistinctFilter =
+                explainPlanAttributes.getServerDistinctFilter();
+            this.serverOffset = explainPlanAttributes.getServerOffset();
+            this.serverRowLimit = explainPlanAttributes.getServerRowLimit();
+            this.serverArrayElementProjection =
+                explainPlanAttributes.isServerArrayElementProjection();
+            this.serverAggregate = explainPlanAttributes.getServerAggregate();
+            this.clientFilterBy = explainPlanAttributes.getClientFilterBy();
+            this.clientAggregate = explainPlanAttributes.getClientAggregate();
+            this.clientSortedBy = explainPlanAttributes.getClientSortedBy();
+            this.clientAfterAggregate =
+                explainPlanAttributes.getClientAfterAggregate();
+            this.clientDistinctFilter =
+                explainPlanAttributes.getClientDistinctFilter();
+            this.clientOffset = explainPlanAttributes.getClientOffset();
+            this.clientRowLimit = explainPlanAttributes.getClientRowLimit();
+            this.clientSequenceCount =
+                explainPlanAttributes.getClientSequenceCount();
+            this.clientCursorName = explainPlanAttributes.getClientCursorName();
+            this.clientSortAlgo = explainPlanAttributes.getClientSortAlgo();
+            this.rhsJoinQueryExplainPlan =
+                explainPlanAttributes.getRhsJoinQueryExplainPlan();
+        }
+
+        public ExplainPlanAttributesBuilder setAbstractExplainPlan(
+                String abstractExplainPlan) {
+            this.abstractExplainPlan = abstractExplainPlan;
+            return this;
+        }
+
+        public ExplainPlanAttributesBuilder setSplitsChunk(
+                Integer splitsChunk) {
+            this.splitsChunk = splitsChunk;
+            return this;
+        }
+
+        public ExplainPlanAttributesBuilder setEstimatedRows(
+                Long estimatedRows) {
+            this.estimatedRows = estimatedRows;
+            return this;
+        }
+
+        public ExplainPlanAttributesBuilder setEstimatedSizeInBytes(
+                Long estimatedSizeInBytes) {
+            this.estimatedSizeInBytes = estimatedSizeInBytes;
+            return this;
+        }
+
+        public ExplainPlanAttributesBuilder setIteratorTypeAndScanSize(
+                String iteratorTypeAndScanSize) {
+            this.iteratorTypeAndScanSize = iteratorTypeAndScanSize;
+            return this;
+        }
+
+        public ExplainPlanAttributesBuilder setSamplingRate(
+                Double samplingRate) {
+            this.samplingRate = samplingRate;
+            return this;
+        }
+
+        public ExplainPlanAttributesBuilder setUseRoundRobinIterator(
+                boolean useRoundRobinIterator) {
+            this.useRoundRobinIterator = useRoundRobinIterator;
+            return this;
+        }
+
+        public ExplainPlanAttributesBuilder setHexStringRVCOffset(
+                String hexStringRVCOffset) {
+            this.hexStringRVCOffset = hexStringRVCOffset;
+            return this;
+        }
+
+        public ExplainPlanAttributesBuilder setConsistency(
+                Consistency consistency) {
+            this.consistency = consistency;
+            return this;
+        }
+
+        public ExplainPlanAttributesBuilder setHint(HintNode.Hint hint) {
+            this.hint = hint;
+            return this;
+        }
+
+        public ExplainPlanAttributesBuilder setServerSortedBy(
+                String serverSortedBy) {
+            this.serverSortedBy = serverSortedBy;
+            return this;
+        }
+
+        public ExplainPlanAttributesBuilder setExplainScanType(
+                String explainScanType) {
+            this.explainScanType = explainScanType;
+            return this;
+        }
+
+        public ExplainPlanAttributesBuilder setTableName(String tableName) {
+            this.tableName = tableName;
+            return this;
+        }
+
+        public ExplainPlanAttributesBuilder setKeyRanges(String keyRanges) {
+            this.keyRanges = keyRanges;
+            return this;
+        }
+
+        public ExplainPlanAttributesBuilder setScanTimeRangeMin(
+                Long scanTimeRangeMin) {
+            this.scanTimeRangeMin = scanTimeRangeMin;
+            return this;
+        }
+
+        public ExplainPlanAttributesBuilder setScanTimeRangeMax(
+                Long scanTimeRangeMax) {
+            this.scanTimeRangeMax = scanTimeRangeMax;
+            return this;
+        }
+
+        public ExplainPlanAttributesBuilder setServerWhereFilter(
+                String serverWhereFilter) {
+            this.serverWhereFilter = serverWhereFilter;
+            return this;
+        }
+
+        public ExplainPlanAttributesBuilder setServerDistinctFilter(
+                String serverDistinctFilter) {
+            this.serverDistinctFilter = serverDistinctFilter;
+            return this;
+        }
+
+        public ExplainPlanAttributesBuilder setServerOffset(
+                Integer serverOffset) {
+            this.serverOffset = serverOffset;
+            return this;
+        }
+
+        public ExplainPlanAttributesBuilder setServerRowLimit(
+                Long serverRowLimit) {
+            this.serverRowLimit = serverRowLimit;
+            return this;
+        }
+
+        public ExplainPlanAttributesBuilder setServerArrayElementProjection(
+                boolean serverArrayElementProjection) {
+            this.serverArrayElementProjection = serverArrayElementProjection;
+            return this;
+        }
+
+        public ExplainPlanAttributesBuilder setServerAggregate(
+                String serverAggregate) {
+            this.serverAggregate = serverAggregate;
+            return this;
+        }
+
+        public ExplainPlanAttributesBuilder setClientFilterBy(
+                String clientFilterBy) {
+            this.clientFilterBy = clientFilterBy;
+            return this;
+        }
+
+        public ExplainPlanAttributesBuilder setClientAggregate(
+                String clientAggregate) {
+            this.clientAggregate = clientAggregate;
+            return this;
+        }
+
+        public ExplainPlanAttributesBuilder setClientSortedBy(
+                String clientSortedBy) {
+            this.clientSortedBy = clientSortedBy;
+            return this;
+        }
+
+        public ExplainPlanAttributesBuilder setClientAfterAggregate(
+                String clientAfterAggregate) {
+            this.clientAfterAggregate = clientAfterAggregate;
+            return this;
+        }
+
+        public ExplainPlanAttributesBuilder setClientDistinctFilter(
+                String clientDistinctFilter) {
+            this.clientDistinctFilter = clientDistinctFilter;
+            return this;
+        }
+
+        public ExplainPlanAttributesBuilder setClientOffset(
+                Integer clientOffset) {
+            this.clientOffset = clientOffset;
+            return this;
+        }
+
+        public ExplainPlanAttributesBuilder setClientRowLimit(
+                Integer clientRowLimit) {
+            this.clientRowLimit = clientRowLimit;
+            return this;
+        }
+
+        public ExplainPlanAttributesBuilder setClientSequenceCount(
+                Integer clientSequenceCount) {
+            this.clientSequenceCount = clientSequenceCount;
+            return this;
+        }
+
+        public ExplainPlanAttributesBuilder setClientCursorName(
+                String clientCursorName) {
+            this.clientCursorName = clientCursorName;
+            return this;
+        }
+
+        public ExplainPlanAttributesBuilder setClientSortAlgo(
+                String clientSortAlgo) {
+            this.clientSortAlgo = clientSortAlgo;
+            return this;
+        }
+
+        public ExplainPlanAttributesBuilder setRhsJoinQueryExplainPlan(
+                ExplainPlanAttributes rhsJoinQueryExplainPlan) {
+            this.rhsJoinQueryExplainPlan = rhsJoinQueryExplainPlan;
+            return this;
+        }
+
+        public ExplainPlanAttributes build() {
+            return new ExplainPlanAttributes(abstractExplainPlan, splitsChunk,
+                estimatedRows, estimatedSizeInBytes, iteratorTypeAndScanSize,
+                samplingRate, useRoundRobinIterator, hexStringRVCOffset,
+                consistency, hint, serverSortedBy, explainScanType, tableName,
+                keyRanges, scanTimeRangeMin, scanTimeRangeMax,
+                serverWhereFilter, serverDistinctFilter,
+                serverOffset, serverRowLimit,
+                serverArrayElementProjection, serverAggregate,
+                clientFilterBy, clientAggregate, clientSortedBy,
+                clientAfterAggregate, clientDistinctFilter, clientOffset,
+                clientRowLimit, clientSequenceCount, clientCursorName,
+                clientSortAlgo, rhsJoinQueryExplainPlan);
+        }
+    }
+}
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/GroupByCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/GroupByCompiler.java
index c47e9a0..12e1a46 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/GroupByCompiler.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/GroupByCompiler.java
@@ -26,6 +26,8 @@ import java.util.List;
 import net.jcip.annotations.Immutable;
 
 import org.apache.hadoop.hbase.util.Pair;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.compile.OrderPreservingTracker.Info;
 import org.apache.phoenix.compile.OrderPreservingTracker.Ordering;
 import org.apache.phoenix.coprocessor.BaseScannerRegionObserver;
@@ -73,12 +75,18 @@ public class GroupByCompiler {
             @Override
             public void explain(List<String> planSteps, Integer limit) {
             }
-            
+
+            @Override
+            public void explain(List<String> planSteps, Integer limit,
+                    ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+            }
+
             @Override
             public String getScanAttribName() {
                 return null;
             }
         };
+
         public static final GroupByCompiler.GroupBy UNGROUPED_GROUP_BY = new GroupBy(new GroupByBuilder().setIsOrderPreserving(true).setIsUngroupedAggregate(true)) {
             @Override
             public GroupBy compile(StatementContext context, QueryPlan innerQueryPlan, Expression whereExpression) throws SQLException {
@@ -89,7 +97,17 @@ public class GroupByCompiler {
             public void explain(List<String> planSteps, Integer limit) {
                 planSteps.add("    SERVER AGGREGATE INTO SINGLE ROW");
             }
-            
+
+            @Override
+            public void explain(List<String> planSteps, Integer limit,
+                    ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+                planSteps.add("    SERVER AGGREGATE INTO SINGLE ROW");
+                if (explainPlanAttributesBuilder != null) {
+                    explainPlanAttributesBuilder.setServerAggregate(
+                        "SERVER AGGREGATE INTO SINGLE ROW");
+                }
+            }
+
             @Override
             public String getScanAttribName() {
                 return BaseScannerRegionObserver.UNGROUPED_AGG;
@@ -335,14 +353,35 @@ public class GroupByCompiler {
         }
 
         public void explain(List<String> planSteps, Integer limit) {
+            explainUtil(planSteps, limit, null);
+        }
+
+        private void explainUtil(List<String> planSteps, Integer limit,
+                ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+            String serverAggregate;
             if (isUngroupedAggregate) {
-                planSteps.add("    SERVER AGGREGATE INTO SINGLE ROW");
-            } else if (isOrderPreserving) {
-                planSteps.add("    SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY " + getExpressions() + (limit == null ? "" : " LIMIT " + limit + " GROUP" + (limit.intValue() == 1 ? "" : "S")));                    
+                serverAggregate = "SERVER AGGREGATE INTO SINGLE ROW";
             } else {
-                planSteps.add("    SERVER AGGREGATE INTO DISTINCT ROWS BY " + getExpressions() + (limit == null ? "" : " LIMIT " + limit + " GROUP" + (limit.intValue() == 1 ? "" : "S")));                    
+                String groupLimit = limit == null ? "" : (" LIMIT " + limit
+                    + " GROUP" + (limit == 1 ? "" : "S"));
+                if (isOrderPreserving) {
+                    serverAggregate = "SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY "
+                        + getExpressions() + groupLimit;
+                } else {
+                    serverAggregate = "SERVER AGGREGATE INTO DISTINCT ROWS BY "
+                        + getExpressions() + groupLimit;
+                }
+            }
+            planSteps.add("    " + serverAggregate);
+            if (explainPlanAttributesBuilder != null) {
+                explainPlanAttributesBuilder.setServerAggregate(serverAggregate);
             }
         }
+
+        public void explain(List<String> planSteps, Integer limit,
+                ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+            explainUtil(planSteps, limit, explainPlanAttributesBuilder);
+        }
     }
 
     /**
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 e7db5b1..ec77621 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
@@ -37,6 +37,8 @@ import org.apache.hadoop.hbase.KeyValue.Type;
 import org.apache.hadoop.hbase.client.Result;
 import org.apache.hadoop.hbase.client.Scan;
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+  .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.compile.GroupByCompiler.GroupBy;
 import org.apache.phoenix.compile.OrderByCompiler.OrderBy;
 import org.apache.phoenix.execute.visitor.QueryPlanVisitor;
@@ -179,6 +181,11 @@ public class ListJarsQueryPlan implements QueryPlan {
             @Override
             public void explain(List<String> planSteps) {
             }
+
+            @Override
+            public void explain(List<String> planSteps,
+                    ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+            }
         };
     }
     
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/MutatingParallelIteratorFactory.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/MutatingParallelIteratorFactory.java
index 213ff0a..37a87de 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/MutatingParallelIteratorFactory.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/MutatingParallelIteratorFactory.java
@@ -27,6 +27,8 @@ import java.util.List;
 
 import org.apache.hadoop.hbase.KeyValue;
 import org.apache.hadoop.hbase.client.Scan;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.execute.MutationState;
 import org.apache.phoenix.iterate.ParallelIteratorFactory;
 import org.apache.phoenix.iterate.PeekingResultIterator;
@@ -98,6 +100,11 @@ public abstract class MutatingParallelIteratorFactory implements ParallelIterato
                 }
 
                 @Override
+                public void explain(List<String> planSteps,
+                    ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+                }
+
+                @Override
                 public void close() throws SQLException {
                     try {
                         /*
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 7d36f0b..1558752 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
@@ -33,6 +33,8 @@ import org.apache.hadoop.hbase.client.Scan;
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
 import org.apache.htrace.Sampler;
 import org.apache.htrace.TraceScope;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.compile.GroupByCompiler.GroupBy;
 import org.apache.phoenix.compile.OrderByCompiler.OrderBy;
 import org.apache.phoenix.execute.visitor.QueryPlanVisitor;
@@ -187,6 +189,11 @@ public class TraceQueryPlan implements QueryPlan {
             @Override
             public void explain(List<String> planSteps) {
             }
+
+            @Override
+            public void explain(List<String> planSteps,
+                ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+            }
         };
     }
 
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 48a23eb..6724e6a 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
@@ -40,6 +40,8 @@ import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.hadoop.hbase.util.Pair;
 import org.apache.phoenix.cache.ServerCacheClient;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.compile.GroupByCompiler.GroupBy;
 import org.apache.phoenix.compile.OrderByCompiler.OrderBy;
 import org.apache.phoenix.coprocessor.BaseScannerRegionObserver;
@@ -1138,11 +1140,18 @@ public class UpsertCompiler {
 
         @Override
         public ExplainPlan getExplainPlan() throws SQLException {
-            List<String> queryPlanSteps =  aggPlan.getExplainPlan().getPlanSteps();
-            List<String> planSteps = Lists.newArrayListWithExpectedSize(queryPlanSteps.size()+1);
+            ExplainPlan explainPlan = aggPlan.getExplainPlan();
+            List<String> queryPlanSteps = explainPlan.getPlanSteps();
+            ExplainPlanAttributes explainPlanAttributes =
+                explainPlan.getPlanStepsAsAttributes();
+            List<String> planSteps =
+                Lists.newArrayListWithExpectedSize(queryPlanSteps.size() + 1);
+            ExplainPlanAttributesBuilder newBuilder =
+                new ExplainPlanAttributesBuilder(explainPlanAttributes);
+            newBuilder.setAbstractExplainPlan("UPSERT ROWS");
             planSteps.add("UPSERT ROWS");
             planSteps.addAll(queryPlanSteps);
-            return new ExplainPlan(planSteps);
+            return new ExplainPlan(planSteps, newBuilder.build());
         }
 
         @Override
@@ -1418,11 +1427,18 @@ public class UpsertCompiler {
 
         @Override
         public ExplainPlan getExplainPlan() throws SQLException {
-            List<String> queryPlanSteps =  queryPlan.getExplainPlan().getPlanSteps();
-            List<String> planSteps = Lists.newArrayListWithExpectedSize(queryPlanSteps.size()+1);
+            ExplainPlan explainPlan = queryPlan.getExplainPlan();
+            List<String> queryPlanSteps = explainPlan.getPlanSteps();
+            ExplainPlanAttributes explainPlanAttributes =
+                explainPlan.getPlanStepsAsAttributes();
+            List<String> planSteps =
+                Lists.newArrayListWithExpectedSize(queryPlanSteps.size() + 1);
+            ExplainPlanAttributesBuilder newBuilder =
+                new ExplainPlanAttributesBuilder(explainPlanAttributes);
+            newBuilder.setAbstractExplainPlan("UPSERT SELECT");
             planSteps.add("UPSERT SELECT");
             planSteps.addAll(queryPlanSteps);
-            return new ExplainPlan(planSteps);
+            return new ExplainPlan(planSteps, newBuilder.build());
         }
 
         @Override
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 2b10780..dace7c0 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
@@ -28,6 +28,10 @@ import java.util.Map;
 import java.util.Set;
 
 import com.google.common.base.Optional;
+import org.apache.commons.math3.util.Pair;
+import org.apache.phoenix.compile.ExplainPlanAttributes;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.client.Scan;
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
@@ -516,7 +520,10 @@ public abstract class BaseQueryPlan implements QueryPlan {
         }
 
         ResultIterator iterator = iterator();
-        ExplainPlan explainPlan = new ExplainPlan(getPlanSteps(iterator));
+        Pair<List<String>, ExplainPlanAttributes> planSteps =
+            getPlanStepsV2(iterator);
+        ExplainPlan explainPlan = new ExplainPlan(planSteps.getFirst(),
+            planSteps.getSecond());
         iterator.close();
         return explainPlan;
     }
@@ -527,6 +534,15 @@ public abstract class BaseQueryPlan implements QueryPlan {
         return planSteps;
     }
 
+    private Pair<List<String>, ExplainPlanAttributes> getPlanStepsV2(
+            ResultIterator iterator) {
+        List<String> planSteps = Lists.newArrayListWithExpectedSize(5);
+        ExplainPlanAttributesBuilder builder =
+            new ExplainPlanAttributesBuilder();
+        iterator.explain(planSteps, builder);
+        return new Pair<>(planSteps, builder.build());
+    }
+
     @Override
     public boolean isRowKeyOrdered() {
         return groupBy.isEmpty() ? orderBy.getOrderByExpressions().isEmpty() : groupBy.isOrderPreserving();
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/execute/ClientAggregatePlan.java b/phoenix-core/src/main/java/org/apache/phoenix/execute/ClientAggregatePlan.java
index aa99cab..f0199ac 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/execute/ClientAggregatePlan.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/execute/ClientAggregatePlan.java
@@ -29,6 +29,9 @@ import org.apache.hadoop.hbase.KeyValue;
 import org.apache.hadoop.hbase.client.Scan;
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
 import org.apache.phoenix.compile.ExplainPlan;
+import org.apache.phoenix.compile.ExplainPlanAttributes;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.compile.GroupByCompiler.GroupBy;
 import org.apache.phoenix.compile.OrderByCompiler.OrderBy;
 import org.apache.phoenix.compile.QueryPlan;
@@ -220,45 +223,70 @@ public class ClientAggregatePlan extends ClientProcessingPlan {
 
     @Override
     public ExplainPlan getExplainPlan() throws SQLException {
-        List<String> planSteps = Lists.newArrayList(delegate.getExplainPlan().getPlanSteps());
+        ExplainPlan explainPlan = delegate.getExplainPlan();
+        List<String> planSteps = Lists.newArrayList(explainPlan.getPlanSteps());
+        ExplainPlanAttributes explainPlanAttributes =
+            explainPlan.getPlanStepsAsAttributes();
+        ExplainPlanAttributesBuilder newBuilder =
+            new ExplainPlanAttributesBuilder(explainPlanAttributes);
         if (where != null) {
             planSteps.add("CLIENT FILTER BY " + where.toString());
+            newBuilder.setClientFilterBy(where.toString());
         }
         if (groupBy.isEmpty()) {
             planSteps.add("CLIENT AGGREGATE INTO SINGLE ROW");
+            newBuilder.setClientAggregate("CLIENT AGGREGATE INTO SINGLE ROW");
         } else if (groupBy.isOrderPreserving()) {
             planSteps.add("CLIENT AGGREGATE INTO DISTINCT ROWS BY " + groupBy.getExpressions().toString());
+            newBuilder.setClientAggregate("CLIENT AGGREGATE INTO DISTINCT ROWS BY "
+                + groupBy.getExpressions().toString());
         } else if (useHashAgg) {
             planSteps.add("CLIENT HASH AGGREGATE INTO DISTINCT ROWS BY " + groupBy.getExpressions().toString());
+            newBuilder.setClientAggregate("CLIENT HASH AGGREGATE INTO DISTINCT ROWS BY "
+                + groupBy.getExpressions().toString());
             if (orderBy == OrderBy.FWD_ROW_KEY_ORDER_BY || orderBy == OrderBy.REV_ROW_KEY_ORDER_BY) {
                 planSteps.add("CLIENT SORTED BY " + groupBy.getKeyExpressions().toString());
+                newBuilder.setClientSortedBy(
+                    groupBy.getKeyExpressions().toString());
             }
         } else {
             planSteps.add("CLIENT SORTED BY " + groupBy.getKeyExpressions().toString());
             planSteps.add("CLIENT AGGREGATE INTO DISTINCT ROWS BY " + groupBy.getExpressions().toString());
+            newBuilder.setClientSortedBy(groupBy.getKeyExpressions().toString());
+            newBuilder.setClientAggregate("CLIENT AGGREGATE INTO DISTINCT ROWS BY "
+                + groupBy.getExpressions().toString());
         }
         if (having != null) {
             planSteps.add("CLIENT AFTER-AGGREGATION FILTER BY " + having.toString());
+            newBuilder.setClientAfterAggregate("CLIENT AFTER-AGGREGATION FILTER BY "
+                + having.toString());
         }
         if (statement.isDistinct() && statement.isAggregate()) {
             planSteps.add("CLIENT DISTINCT ON " + projector.toString());
+            newBuilder.setClientDistinctFilter(projector.toString());
         }
         if (offset != null) {
             planSteps.add("CLIENT OFFSET " + offset);
+            newBuilder.setClientOffset(offset);
         }
         if (orderBy.getOrderByExpressions().isEmpty()) {
             if (limit != null) {
                 planSteps.add("CLIENT " + limit + " ROW LIMIT");
+                newBuilder.setClientRowLimit(limit);
             }
         } else {
             planSteps.add("CLIENT" + (limit == null ? "" : " TOP " + limit + " ROW"  + (limit == 1 ? "" : "S"))  + " SORTED BY " + orderBy.getOrderByExpressions().toString());
+            newBuilder.setClientRowLimit(limit);
+            newBuilder.setClientSortedBy(
+                orderBy.getOrderByExpressions().toString());
         }
         if (context.getSequenceManager().getSequenceCount() > 0) {
             int nSequences = context.getSequenceManager().getSequenceCount();
             planSteps.add("CLIENT RESERVE VALUES FROM " + nSequences + " SEQUENCE" + (nSequences == 1 ? "" : "S"));
+            newBuilder.setClientSequenceCount(nSequences);
         }
-        
-        return new ExplainPlan(planSteps);
+
+        return new ExplainPlan(planSteps, newBuilder.build());
     }
 
     @Override
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/execute/ClientScanPlan.java b/phoenix-core/src/main/java/org/apache/phoenix/execute/ClientScanPlan.java
index f39e5bf..be4a65b 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/execute/ClientScanPlan.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/execute/ClientScanPlan.java
@@ -23,6 +23,9 @@ import java.util.List;
 
 import org.apache.hadoop.hbase.client.Scan;
 import org.apache.phoenix.compile.ExplainPlan;
+import org.apache.phoenix.compile.ExplainPlanAttributes;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.compile.OrderByCompiler.OrderBy;
 import org.apache.phoenix.compile.QueryPlan;
 import org.apache.phoenix.compile.RowProjector;
@@ -121,30 +124,44 @@ public class ClientScanPlan extends ClientProcessingPlan {
 
     @Override
     public ExplainPlan getExplainPlan() throws SQLException {
-        List<String> planSteps = Lists.newArrayList(delegate.getExplainPlan().getPlanSteps());
+        ExplainPlan explainPlan = delegate.getExplainPlan();
+        List<String> currentPlanSteps = explainPlan.getPlanSteps();
+        ExplainPlanAttributes explainPlanAttributes =
+            explainPlan.getPlanStepsAsAttributes();
+        List<String> planSteps = Lists.newArrayList(currentPlanSteps);
+        ExplainPlanAttributesBuilder newBuilder =
+            new ExplainPlanAttributesBuilder(explainPlanAttributes);
         if (where != null) {
             planSteps.add("CLIENT FILTER BY " + where.toString());
+            newBuilder.setClientFilterBy(where.toString());
         }
         if (!orderBy.getOrderByExpressions().isEmpty()) {
             if (offset != null) {
                 planSteps.add("CLIENT OFFSET " + offset);
+                newBuilder.setClientOffset(offset);
             }
             planSteps.add("CLIENT" + (limit == null ? "" : " TOP " + limit + " ROW" + (limit == 1 ? "" : "S"))
                     + " SORTED BY " + orderBy.getOrderByExpressions().toString());
+            newBuilder.setClientRowLimit(limit);
+            newBuilder.setClientSortedBy(
+                orderBy.getOrderByExpressions().toString());
         } else {
             if (offset != null) {
                 planSteps.add("CLIENT OFFSET " + offset);
+                newBuilder.setClientOffset(offset);
             }
             if (limit != null) {
                 planSteps.add("CLIENT " + limit + " ROW LIMIT");
+                newBuilder.setClientRowLimit(limit);
             }
         }
         if (context.getSequenceManager().getSequenceCount() > 0) {
             int nSequences = context.getSequenceManager().getSequenceCount();
             planSteps.add("CLIENT RESERVE VALUES FROM " + nSequences + " SEQUENCE" + (nSequences == 1 ? "" : "S"));
+            newBuilder.setClientSequenceCount(nSequences);
         }
-        
-        return new ExplainPlan(planSteps);
+
+        return new ExplainPlan(planSteps, newBuilder.build());
     }
 
     private static List<OrderBy> convertActualOutputOrderBy(
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/execute/CorrelatePlan.java b/phoenix-core/src/main/java/org/apache/phoenix/execute/CorrelatePlan.java
index 68b1505..6d9afd6 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/execute/CorrelatePlan.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/execute/CorrelatePlan.java
@@ -24,6 +24,9 @@ import java.util.List;
 import org.apache.hadoop.hbase.client.Scan;
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
 import org.apache.phoenix.compile.ExplainPlan;
+import org.apache.phoenix.compile.ExplainPlanAttributes;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.compile.QueryPlan;
 import org.apache.phoenix.exception.SQLExceptionCode;
 import org.apache.phoenix.exception.SQLExceptionInfo;
@@ -92,14 +95,33 @@ public class CorrelatePlan extends DelegateQueryPlan {
     public ExplainPlan getExplainPlan() throws SQLException {
         List<String> steps = Lists.newArrayList();
         steps.add("NESTED-LOOP-JOIN (" + joinType.toString().toUpperCase() + ") TABLES");
-        for (String step : delegate.getExplainPlan().getPlanSteps()) {
-            steps.add("    " + step);            
+        ExplainPlan lhsExplainPlan = delegate.getExplainPlan();
+        List<String> lhsPlanSteps = lhsExplainPlan.getPlanSteps();
+        ExplainPlanAttributes lhsPlanAttributes =
+            lhsExplainPlan.getPlanStepsAsAttributes();
+        ExplainPlanAttributesBuilder lhsPlanBuilder =
+            new ExplainPlanAttributesBuilder(lhsPlanAttributes);
+        lhsPlanBuilder.setAbstractExplainPlan("NESTED-LOOP-JOIN ("
+            + joinType.toString().toUpperCase() + ")");
+
+        for (String step : lhsPlanSteps) {
+            steps.add("    " + step);
         }
         steps.add("AND" + (rhsSchema.getFieldCount() == 0 ? " (SKIP MERGE)" : ""));
-        for (String step : rhs.getExplainPlan().getPlanSteps()) {
-            steps.add("    " + step);            
+
+        ExplainPlan rhsExplainPlan = rhs.getExplainPlan();
+        List<String> rhsPlanSteps = rhsExplainPlan.getPlanSteps();
+        ExplainPlanAttributes rhsPlanAttributes =
+            rhsExplainPlan.getPlanStepsAsAttributes();
+        ExplainPlanAttributesBuilder rhsPlanBuilder =
+            new ExplainPlanAttributesBuilder(rhsPlanAttributes);
+
+        lhsPlanBuilder.setRhsJoinQueryExplainPlan(rhsPlanBuilder.build());
+
+        for (String step : rhsPlanSteps) {
+            steps.add("    " + step);
         }
-        return new ExplainPlan(steps);
+        return new ExplainPlan(steps, lhsPlanBuilder.build());
     }
 
     @Override
@@ -217,6 +239,11 @@ public class CorrelatePlan extends DelegateQueryPlan {
         @Override
         public void explain(List<String> planSteps) { }
 
+        @Override
+        public void explain(List<String> planSteps,
+                ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+        }
+
         private ProjectedValueTuple convertLhs(Tuple lhs) throws IOException {
             ProjectedValueTuple tuple;
             if (lhs instanceof ProjectedValueTuple) {
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 71fb3fb..aa21ae2 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
@@ -295,6 +295,7 @@ public class HashJoinPlan extends DelegateQueryPlan {
 
     @Override
     public ExplainPlan getExplainPlan() throws SQLException {
+        // TODO : Support ExplainPlanAttributes for HashJoinPlan
         List<String> planSteps = Lists.newArrayList(delegate.getExplainPlan().getPlanSteps());
         int count = subPlans.length;
         for (int i = 0; i < count; i++) {
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 a9f9d8e..10e9031 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
@@ -26,6 +26,8 @@ import java.util.Map;
 import org.apache.hadoop.hbase.KeyValue;
 import org.apache.hadoop.hbase.client.Scan;
 import org.apache.phoenix.cache.ServerCacheClient.ServerCache;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.compile.GroupByCompiler.GroupBy;
 import org.apache.phoenix.compile.OrderByCompiler.OrderBy;
 import org.apache.phoenix.compile.RowProjector;
@@ -118,7 +120,12 @@ public class LiteralResultIterationPlan extends BaseQueryPlan {
             @Override
             public void explain(List<String> planSteps) {
             }
-            
+
+            @Override
+            public void explain(List<String> planSteps,
+                    ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+            }
+
         };
         
         if (context.getSequenceManager().getSequenceCount() > 0) {
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 94f8452..51ff20f 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
@@ -39,6 +39,9 @@ import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
 import org.apache.hadoop.hbase.util.Pair;
 import org.apache.phoenix.compile.ColumnResolver;
 import org.apache.phoenix.compile.ExplainPlan;
+import org.apache.phoenix.compile.ExplainPlanAttributes;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.compile.GroupByCompiler.GroupBy;
 import org.apache.phoenix.compile.OrderByCompiler.OrderBy;
 import org.apache.phoenix.compile.QueryCompiler;
@@ -197,14 +200,33 @@ public class SortMergeJoinPlan implements QueryPlan {
     public ExplainPlan getExplainPlan() throws SQLException {
         List<String> steps = Lists.newArrayList();
         steps.add("SORT-MERGE-JOIN (" + joinType.toString().toUpperCase() + ") TABLES");
-        for (String step : lhsPlan.getExplainPlan().getPlanSteps()) {
-            steps.add("    " + step);            
+        ExplainPlan lhsExplainPlan = lhsPlan.getExplainPlan();
+        List<String> lhsPlanSteps = lhsExplainPlan.getPlanSteps();
+        ExplainPlanAttributes lhsPlanAttributes =
+          lhsExplainPlan.getPlanStepsAsAttributes();
+        ExplainPlanAttributesBuilder lhsPlanBuilder =
+          new ExplainPlanAttributesBuilder(lhsPlanAttributes);
+        lhsPlanBuilder.setAbstractExplainPlan("SORT-MERGE-JOIN ("
+          + joinType.toString().toUpperCase() + ")");
+
+        for (String step : lhsPlanSteps) {
+            steps.add("    " + step);
         }
         steps.add("AND" + (rhsSchema.getFieldCount() == 0 ? " (SKIP MERGE)" : ""));
-        for (String step : rhsPlan.getExplainPlan().getPlanSteps()) {
-            steps.add("    " + step);            
+
+        ExplainPlan rhsExplainPlan = rhsPlan.getExplainPlan();
+        List<String> rhsPlanSteps = rhsExplainPlan.getPlanSteps();
+        ExplainPlanAttributes rhsPlanAttributes =
+          rhsExplainPlan.getPlanStepsAsAttributes();
+        ExplainPlanAttributesBuilder rhsPlanBuilder =
+          new ExplainPlanAttributesBuilder(rhsPlanAttributes);
+
+        lhsPlanBuilder.setRhsJoinQueryExplainPlan(rhsPlanBuilder.build());
+
+        for (String step : rhsPlanSteps) {
+            steps.add("    " + step);
         }
-        return new ExplainPlan(steps);
+        return new ExplainPlan(steps, lhsPlanBuilder.build());
     }
 
     @Override
@@ -481,6 +503,11 @@ public class SortMergeJoinPlan implements QueryPlan {
         public void explain(List<String> planSteps) {
         }
 
+        @Override
+        public void explain(List<String> planSteps,
+                ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+        }
+
         private void doInit(boolean lhs) throws SQLException {
             if(lhs) {
                 nextLhsTuple = lhsIterator.next();
@@ -745,7 +772,12 @@ public class SortMergeJoinPlan implements QueryPlan {
         @Override
         public void explain(List<String> planSteps) {
         }
-        
+
+        @Override
+        public void explain(List<String> planSteps,
+                ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+        }
+
         private void advance(boolean lhs) throws SQLException {
             if (lhs) {
                 lhsTuple = lhsIterator.next();
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/execute/TupleProjectionPlan.java b/phoenix-core/src/main/java/org/apache/phoenix/execute/TupleProjectionPlan.java
index 4b56c23..18b1aec 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/execute/TupleProjectionPlan.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/execute/TupleProjectionPlan.java
@@ -27,6 +27,9 @@ import java.util.Map;
 import org.apache.hadoop.hbase.client.Scan;
 import org.apache.phoenix.compile.ColumnResolver;
 import org.apache.phoenix.compile.ExplainPlan;
+import org.apache.phoenix.compile.ExplainPlanAttributes;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.compile.OrderPreservingTracker;
 import org.apache.phoenix.compile.OrderPreservingTracker.Info;
 import org.apache.phoenix.compile.QueryPlan;
@@ -151,12 +154,19 @@ public class TupleProjectionPlan extends DelegateQueryPlan {
 
     @Override
     public ExplainPlan getExplainPlan() throws SQLException {
-        List<String> planSteps = Lists.newArrayList(delegate.getExplainPlan().getPlanSteps());
+        ExplainPlan explainPlan = delegate.getExplainPlan();
+        List<String> planSteps = Lists.newArrayList(explainPlan.getPlanSteps());
+        ExplainPlanAttributes explainPlanAttributes =
+            explainPlan.getPlanStepsAsAttributes();
         if (postFilter != null) {
             planSteps.add("CLIENT FILTER BY " + postFilter.toString());
+            ExplainPlanAttributesBuilder newBuilder =
+                new ExplainPlanAttributesBuilder(explainPlanAttributes);
+            newBuilder.setClientFilterBy(postFilter.toString());
+            explainPlanAttributes = newBuilder.build();
         }
 
-        return new ExplainPlan(planSteps);
+        return new ExplainPlan(planSteps, explainPlanAttributes);
     }
 
     @Override
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 cefd7b6..a6631d5 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
@@ -29,6 +29,8 @@ import java.util.Set;
 
 import org.apache.hadoop.hbase.client.Scan;
 import org.apache.phoenix.compile.ExplainPlan;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.compile.GroupByCompiler.GroupBy;
 import org.apache.phoenix.compile.OrderByCompiler.OrderBy;
 import org.apache.phoenix.compile.QueryPlan;
@@ -182,15 +184,19 @@ public class UnionPlan implements QueryPlan {
     @Override
     public ExplainPlan getExplainPlan() throws SQLException {
         List<String> steps = new ArrayList<String>();
-        steps.add("UNION ALL OVER " + this.plans.size() + " QUERIES");
+        ExplainPlanAttributesBuilder builder = new ExplainPlanAttributesBuilder();
+        String abstractExplainPlan = "UNION ALL OVER " + this.plans.size()
+            + " QUERIES";
+        builder.setAbstractExplainPlan(abstractExplainPlan);
+        steps.add(abstractExplainPlan);
         ResultIterator iterator = iterator();
-        iterator.explain(steps);
+        iterator.explain(steps, builder);
         // Indent plans steps nested under union, except last client-side merge/concat step (if there is one)
         int offset = !orderBy.getOrderByExpressions().isEmpty() && limit != null ? 2 : limit != null ? 1 : 0;
         for (int i = 1 ; i < steps.size()-offset; i++) {
             steps.set(i, "    " + steps.get(i));
         }
-        return new ExplainPlan(steps);
+        return new ExplainPlan(steps, builder.build());
     }
 
 
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/execute/UnnestArrayPlan.java b/phoenix-core/src/main/java/org/apache/phoenix/execute/UnnestArrayPlan.java
index 896d1ed..19c9340 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/execute/UnnestArrayPlan.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/execute/UnnestArrayPlan.java
@@ -24,6 +24,9 @@ import java.util.List;
 import org.apache.hadoop.hbase.client.Scan;
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
 import org.apache.phoenix.compile.ExplainPlan;
+import org.apache.phoenix.compile.ExplainPlanAttributes;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.compile.QueryPlan;
 import org.apache.phoenix.compile.OrderByCompiler.OrderBy;
 import org.apache.phoenix.execute.visitor.QueryPlanVisitor;
@@ -57,9 +60,15 @@ public class UnnestArrayPlan extends DelegateQueryPlan {
 
     @Override
     public ExplainPlan getExplainPlan() throws SQLException {
-        List<String> planSteps = delegate.getExplainPlan().getPlanSteps();
+        ExplainPlan explainPlan = delegate.getExplainPlan();
+        List<String> planSteps = explainPlan.getPlanSteps();
+        ExplainPlanAttributes explainPlanAttributes =
+            explainPlan.getPlanStepsAsAttributes();
+        ExplainPlanAttributesBuilder newBuilder =
+            new ExplainPlanAttributesBuilder(explainPlanAttributes);
         planSteps.add("UNNEST");
-        return new ExplainPlan(planSteps);
+        newBuilder.setAbstractExplainPlan("UNNEST");
+        return new ExplainPlan(planSteps, newBuilder.build());
     }
     
     @Override
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/BaseGroupedAggregatingResultIterator.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/BaseGroupedAggregatingResultIterator.java
index 84d29ff..6b9f240 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/BaseGroupedAggregatingResultIterator.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/BaseGroupedAggregatingResultIterator.java
@@ -26,6 +26,8 @@ import java.util.List;
 
 import org.apache.hadoop.hbase.KeyValue;
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.expression.aggregator.Aggregator;
 import org.apache.phoenix.expression.aggregator.Aggregators;
 import org.apache.phoenix.schema.tuple.Tuple;
@@ -103,4 +105,9 @@ public abstract class BaseGroupedAggregatingResultIterator implements
         resultIterator.explain(planSteps);
     }
 
+    @Override
+    public void explain(List<String> planSteps,
+            ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+        resultIterator.explain(planSteps, explainPlanAttributesBuilder);
+    }
 }
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/BaseResultIterator.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/BaseResultIterator.java
index 59ab5c9..17b6372 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/BaseResultIterator.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/BaseResultIterator.java
@@ -17,6 +17,9 @@
  */
 package org.apache.phoenix.iterate;
 
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
+
 import java.sql.SQLException;
 import java.util.List;
 
@@ -38,4 +41,8 @@ public abstract class BaseResultIterator implements ResultIterator {
     public void explain(List<String> planSteps) {
     }
 
+    @Override
+    public void explain(List<String> planSteps,
+            ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+    }
 }
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/BaseResultIterators.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/BaseResultIterators.java
index c271487..151216c 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/BaseResultIterators.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/BaseResultIterators.java
@@ -72,6 +72,8 @@ import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.hadoop.hbase.util.Pair;
 import org.apache.phoenix.cache.ServerCacheClient.ServerCache;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.compile.GroupByCompiler.GroupBy;
 import org.apache.phoenix.compile.QueryPlan;
 import org.apache.phoenix.compile.RowProjector;
@@ -80,7 +82,6 @@ import org.apache.phoenix.compile.StatementContext;
 import org.apache.phoenix.coprocessor.BaseScannerRegionObserver;
 import org.apache.phoenix.coprocessor.HashJoinCacheNotFoundException;
 import org.apache.phoenix.coprocessor.UngroupedAggregateRegionObserver;
-import org.apache.phoenix.exception.SQLExceptionCode;
 import org.apache.phoenix.exception.SQLExceptionInfo;
 import org.apache.phoenix.execute.MutationState;
 import org.apache.phoenix.execute.ScanPlan;
@@ -1565,6 +1566,21 @@ public abstract class BaseResultIterators extends ExplainTable implements Result
 
     @Override
     public void explain(List<String> planSteps) {
+        explainUtil(planSteps, null);
+    }
+
+    /**
+     * Utility to generate ExplainPlan steps.
+     *
+     * @param planSteps Add generated plan in list of planSteps. This argument
+     *     is used to provide planSteps as whole statement consisting of
+     *     list of Strings.
+     * @param explainPlanAttributesBuilder Add generated plan in attributes
+     *     object. Having an API to provide planSteps as an object is easier
+     *     while comparing individual attributes of ExplainPlan.
+     */
+    private void explainUtil(List<String> planSteps,
+            ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
         boolean displayChunkCount = context.getConnection().getQueryServices().getProps().getBoolean(
                 QueryServices.EXPLAIN_CHUNK_COUNT_ATTRIB,
                 QueryServicesOptions.DEFAULT_EXPLAIN_CHUNK_COUNT);
@@ -1575,32 +1591,65 @@ public abstract class BaseResultIterators extends ExplainTable implements Result
                     QueryServices.EXPLAIN_ROW_COUNT_ATTRIB,
                     QueryServicesOptions.DEFAULT_EXPLAIN_ROW_COUNT);
             buf.append(this.splits.size()).append("-CHUNK ");
+            if (explainPlanAttributesBuilder != null) {
+                explainPlanAttributesBuilder.setSplitsChunk(this.splits.size());
+            }
             if (displayRowCount && estimatedRows != null) {
                 buf.append(estimatedRows).append(" ROWS ");
                 buf.append(estimatedSize).append(" BYTES ");
+                if (explainPlanAttributesBuilder != null) {
+                    explainPlanAttributesBuilder.setEstimatedRows(estimatedRows);
+                    explainPlanAttributesBuilder.setEstimatedSizeInBytes(estimatedSize);
+                }
             }
         }
-        buf.append(getName()).append(" ").append(size()).append("-WAY ");
-        
-        if(this.plan.getStatement().getTableSamplingRate()!=null){
-        	buf.append(plan.getStatement().getTableSamplingRate()/100D).append("-").append("SAMPLED ");
+        String iteratorTypeAndScanSize = getName() + " " + size() + "-WAY";
+        buf.append(iteratorTypeAndScanSize).append(" ");
+        if (explainPlanAttributesBuilder != null) {
+            explainPlanAttributesBuilder.setIteratorTypeAndScanSize(
+                iteratorTypeAndScanSize);
+        }
+
+        if (this.plan.getStatement().getTableSamplingRate() != null) {
+            Double samplingRate = plan.getStatement().getTableSamplingRate() / 100D;
+            buf.append(samplingRate).append("-").append("SAMPLED ");
+            if (explainPlanAttributesBuilder != null) {
+                explainPlanAttributesBuilder.setSamplingRate(samplingRate);
+            }
         }
         try {
             if (plan.useRoundRobinIterator()) {
                 buf.append("ROUND ROBIN ");
+                if (explainPlanAttributesBuilder != null) {
+                    explainPlanAttributesBuilder.setUseRoundRobinIterator(true);
+                }
             }
         } catch (SQLException e) {
             throw new RuntimeException(e);
         }
 
-        if(this.plan instanceof ScanPlan) {
+        if (this.plan instanceof ScanPlan) {
             ScanPlan scanPlan = (ScanPlan) this.plan;
-            if(scanPlan.getRowOffset().isPresent()) {
-                buf.append("With RVC Offset " + "0x" + Hex.encodeHexString(scanPlan.getRowOffset().get()) + " ");
+            if (scanPlan.getRowOffset().isPresent()) {
+                String rowOffset =
+                    Hex.encodeHexString(scanPlan.getRowOffset().get());
+                buf.append("With RVC Offset " + "0x")
+                    .append(rowOffset)
+                    .append(" ");
+                if (explainPlanAttributesBuilder != null) {
+                    explainPlanAttributesBuilder.setHexStringRVCOffset(
+                        "0x" + rowOffset);
+                }
             }
         }
 
-        explain(buf.toString(),planSteps);
+        explain(buf.toString(), planSteps, explainPlanAttributesBuilder);
+    }
+
+    @Override
+    public void explain(List<String> planSteps,
+            ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+        explainUtil(planSteps, explainPlanAttributesBuilder);
     }
 
     public Long getEstimatedRowCount() {
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/ChunkedResultIterator.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/ChunkedResultIterator.java
index 2fb7b72..8c8f810 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/ChunkedResultIterator.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/ChunkedResultIterator.java
@@ -26,6 +26,8 @@ import java.util.List;
 import org.apache.hadoop.hbase.client.Scan;
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
 import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.compile.QueryPlan;
 import org.apache.phoenix.compile.StatementContext;
 import org.apache.phoenix.execute.MutationState;
@@ -132,6 +134,12 @@ public class ChunkedResultIterator implements PeekingResultIterator {
     }
 
     @Override
+    public void explain(List<String> planSteps,
+            ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+        resultIterator.explain(planSteps, explainPlanAttributesBuilder);
+    }
+
+    @Override
     public void close() throws SQLException {
         resultIterator.close();
     }
@@ -210,6 +218,12 @@ public class ChunkedResultIterator implements PeekingResultIterator {
         }
 
         @Override
+        public void explain(List<String> planSteps,
+                ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+            delegate.explain(planSteps, explainPlanAttributesBuilder);
+        }
+
+        @Override
         public void close() throws SQLException {
             delegate.close();
         }
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/ClientHashAggregatingResultIterator.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/ClientHashAggregatingResultIterator.java
index a07ea16..3a4a86c 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/ClientHashAggregatingResultIterator.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/ClientHashAggregatingResultIterator.java
@@ -35,6 +35,8 @@ import java.util.Objects;
 import org.apache.hadoop.hbase.Cell;
 import org.apache.hadoop.hbase.KeyValue;
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.compile.StatementContext;
 import org.apache.phoenix.compile.OrderByCompiler.OrderBy;
 import org.apache.phoenix.expression.Expression;
@@ -144,6 +146,12 @@ public class ClientHashAggregatingResultIterator
     }
 
     @Override
+    public void explain(List<String> planSteps,
+            ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+        resultIterator.explain(planSteps, explainPlanAttributesBuilder);
+    }
+
+    @Override
         public String toString() {
         return "ClientHashAggregatingResultIterator [resultIterator="
             + resultIterator + ", aggregators=" + aggregators + ", groupByExpressions="
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/ConcatResultIterator.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/ConcatResultIterator.java
index fcc88aa..6a40177 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/ConcatResultIterator.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/ConcatResultIterator.java
@@ -20,6 +20,8 @@ package org.apache.phoenix.iterate;
 import java.sql.SQLException;
 import java.util.List;
 
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.schema.tuple.Tuple;
 import org.apache.phoenix.util.ServerUtil;
 
@@ -93,6 +95,14 @@ public class ConcatResultIterator implements PeekingResultIterator {
         }
     }
 
+    @Override
+    public void explain(List<String> planSteps,
+            ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+        if (resultIterators != null) {
+            resultIterators.explain(planSteps, explainPlanAttributesBuilder);
+        }
+    }
+
     private PeekingResultIterator currentIterator() throws SQLException {
         List<PeekingResultIterator> iterators = getIterators();
         while (index < iterators.size()) {
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/CursorResultIterator.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/CursorResultIterator.java
index 7ff2785..c09f2e1 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/CursorResultIterator.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/CursorResultIterator.java
@@ -17,6 +17,8 @@
  */
 package org.apache.phoenix.iterate;
 
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.schema.tuple.Tuple;
 import org.apache.phoenix.util.CursorUtil;
 
@@ -55,6 +57,14 @@ public class CursorResultIterator implements ResultIterator {
     }
 
     @Override
+    public void explain(List<String> planSteps,
+            ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+        delegate.explain(planSteps, explainPlanAttributesBuilder);
+        explainPlanAttributesBuilder.setClientCursorName(cursorName);
+        planSteps.add("CLIENT CURSOR " + cursorName);
+    }
+
+    @Override
     public String toString() {
         return "CursorResultIterator [cursor=" + cursorName + "]";
     }
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/DelegateResultIterator.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/DelegateResultIterator.java
index 63b3142..375e0c3 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/DelegateResultIterator.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/DelegateResultIterator.java
@@ -20,6 +20,8 @@ package org.apache.phoenix.iterate;
 import java.sql.SQLException;
 import java.util.List;
 
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.schema.tuple.Tuple;
 
 
@@ -49,4 +51,10 @@ public class DelegateResultIterator implements ResultIterator {
         delegate.explain(planSteps);
     }
 
+    @Override
+    public void explain(List<String> planSteps,
+            ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+        delegate.explain(planSteps, explainPlanAttributesBuilder);
+    }
+
 }
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/DistinctAggregatingResultIterator.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/DistinctAggregatingResultIterator.java
index 669b75a..106acea 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/DistinctAggregatingResultIterator.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/DistinctAggregatingResultIterator.java
@@ -25,6 +25,8 @@ import java.util.Set;
 
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
 import org.apache.phoenix.compile.ColumnProjector;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.compile.RowProjector;
 import org.apache.phoenix.expression.Expression;
 import org.apache.phoenix.expression.aggregator.Aggregator;
@@ -158,6 +160,15 @@ public class DistinctAggregatingResultIterator implements AggregatingResultItera
     }
 
     @Override
+    public void explain(List<String> planSteps,
+            ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+        delegate.explain(planSteps, explainPlanAttributesBuilder);
+        explainPlanAttributesBuilder.setClientDistinctFilter(
+            rowProjector.toString());
+        planSteps.add("CLIENT DISTINCT ON " + rowProjector.toString());
+    }
+
+    @Override
     public Aggregator[] aggregate(Tuple result) {
         return delegate.aggregate(result);
     }
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/ExplainTable.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/ExplainTable.java
index 1aaa3f9..31713d9 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/ExplainTable.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/ExplainTable.java
@@ -31,6 +31,8 @@ import org.apache.hadoop.hbase.filter.PageFilter;
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
 import org.apache.hadoop.hbase.io.TimeRange;
 import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.compile.GroupByCompiler.GroupBy;
 import org.apache.phoenix.compile.OrderByCompiler.OrderBy;
 import org.apache.phoenix.compile.ScanRanges;
@@ -78,7 +80,8 @@ public abstract class ExplainTable {
         this.offset = offset;
     }
 
-    private boolean explainSkipScan(StringBuilder buf) {
+    private String explainSkipScan() {
+        StringBuilder buf = new StringBuilder();
         ScanRanges scanRanges = context.getScanRanges();
         if (scanRanges.isPointLookup()) {
             int keyCount = scanRanges.getPointLookupCount();
@@ -102,10 +105,11 @@ public abstract class ExplainTable {
         } else {
             buf.append("RANGE SCAN ");
         }
-        return scanRanges.useSkipScanFilter();
+        return buf.toString();
     }
-    
-    protected void explain(String prefix, List<String> planSteps) {
+
+    protected void explain(String prefix, List<String> planSteps,
+            ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
         StringBuilder buf = new StringBuilder(prefix);
         ScanRanges scanRanges = context.getScanRanges();
         Scan scan = context.getScan();
@@ -119,19 +123,40 @@ public abstract class ExplainTable {
         if (OrderBy.REV_ROW_KEY_ORDER_BY.equals(orderBy)) {
             buf.append("REVERSE ");
         }
+        String scanTypeDetails;
         if (scanRanges.isEverything()) {
-            buf.append("FULL SCAN ");
+            scanTypeDetails = "FULL SCAN ";
         } else {
-            explainSkipScan(buf);
+            scanTypeDetails = explainSkipScan();
         }
+        buf.append(scanTypeDetails);
         buf.append("OVER ").append(tableRef.getTable().getPhysicalName().getString());
         if (!scanRanges.isPointLookup()) {
-            appendKeyRanges(buf);
+            buf.append(appendKeyRanges());
         }
         planSteps.add(buf.toString());
+        if (explainPlanAttributesBuilder != null) {
+            explainPlanAttributesBuilder.setConsistency(scan.getConsistency());
+            if (hint.hasHint(Hint.SMALL)) {
+                explainPlanAttributesBuilder.setHint(Hint.SMALL);
+            }
+            if (OrderBy.REV_ROW_KEY_ORDER_BY.equals(orderBy)) {
+                explainPlanAttributesBuilder.setClientSortedBy("REVERSE");
+            }
+            explainPlanAttributesBuilder.setExplainScanType(scanTypeDetails);
+            explainPlanAttributesBuilder.setTableName(tableRef.getTable()
+                .getPhysicalName().getString());
+            if (!scanRanges.isPointLookup()) {
+                explainPlanAttributesBuilder.setKeyRanges(appendKeyRanges());
+            }
+        }
         if (context.getScan() != null && tableRef.getTable().getRowTimestampColPos() != -1) {
             TimeRange range = context.getScan().getTimeRange();
             planSteps.add("    ROW TIMESTAMP FILTER [" + range.getMin() + ", " + range.getMax() + ")");
+            if (explainPlanAttributesBuilder != null) {
+                explainPlanAttributesBuilder.setScanTimeRangeMin(range.getMin());
+                explainPlanAttributesBuilder.setScanTimeRangeMax(range.getMax());
+            }
         }
         
         PageFilter pageFilter = null;
@@ -154,16 +179,40 @@ public abstract class ExplainTable {
             } while (filterIterator.hasNext());
         }
         if (whereFilter != null) {
-            planSteps.add("    SERVER FILTER BY " + (firstKeyOnlyFilter == null ? "" : "FIRST KEY ONLY AND ") + whereFilter.toString());
+            String serverWhereFilter = "SERVER FILTER BY "
+                + (firstKeyOnlyFilter == null ? "" : "FIRST KEY ONLY AND ")
+                + whereFilter.toString();
+            planSteps.add("    " + serverWhereFilter);
+            if (explainPlanAttributesBuilder != null) {
+                explainPlanAttributesBuilder.setServerWhereFilter(serverWhereFilter);
+            }
         } else if (firstKeyOnlyFilter != null) {
             planSteps.add("    SERVER FILTER BY FIRST KEY ONLY");
+            if (explainPlanAttributesBuilder != null) {
+                explainPlanAttributesBuilder.setServerWhereFilter(
+                    "SERVER FILTER BY FIRST KEY ONLY");
+            }
         }
         if (distinctFilter != null) {
-            planSteps.add("    SERVER DISTINCT PREFIX FILTER OVER "+groupBy.getExpressions().toString());
+            String serverDistinctFilter = "SERVER DISTINCT PREFIX FILTER OVER "
+                + groupBy.getExpressions().toString();
+            planSteps.add("    " + serverDistinctFilter);
+            if (explainPlanAttributesBuilder != null) {
+                explainPlanAttributesBuilder.setServerDistinctFilter(serverDistinctFilter);
+            }
         }
         if (!orderBy.getOrderByExpressions().isEmpty() && groupBy.isEmpty()) { // with GROUP BY, sort happens client-side
-            planSteps.add("    SERVER" + (limit == null ? "" : " TOP " + limit + " ROW" + (limit == 1 ? "" : "S"))
-                    + " SORTED BY " + orderBy.getOrderByExpressions().toString());
+            String orderByExpressions = "SERVER"
+                + (limit == null ? "" : " TOP " + limit + " ROW" + (limit == 1 ? "" : "S"))
+                + " SORTED BY " + orderBy.getOrderByExpressions().toString();
+            planSteps.add("    " + orderByExpressions);
+            if (explainPlanAttributesBuilder != null) {
+                if (limit != null) {
+                    explainPlanAttributesBuilder.setServerRowLimit(limit.longValue());
+                }
+                explainPlanAttributesBuilder.setServerSortedBy(
+                    orderBy.getOrderByExpressions().toString());
+            }
         } else {
             if (offset != null) {
                 planSteps.add("    SERVER OFFSET " + offset);
@@ -171,15 +220,25 @@ public abstract class ExplainTable {
             if (pageFilter != null) {
                 planSteps.add("    SERVER " + pageFilter.getPageSize() + " ROW LIMIT");
             }
+            if (explainPlanAttributesBuilder != null) {
+                explainPlanAttributesBuilder.setServerOffset(offset);
+                if (pageFilter != null) {
+                    explainPlanAttributesBuilder.setServerRowLimit(
+                        pageFilter.getPageSize());
+                }
+            }
         }
         Integer groupByLimit = null;
         byte[] groupByLimitBytes = scan.getAttribute(BaseScannerRegionObserver.GROUP_BY_LIMIT);
         if (groupByLimitBytes != null) {
             groupByLimit = (Integer) PInteger.INSTANCE.toObject(groupByLimitBytes);
         }
-        groupBy.explain(planSteps, groupByLimit);
+        groupBy.explain(planSteps, groupByLimit, explainPlanAttributesBuilder);
         if (scan.getAttribute(BaseScannerRegionObserver.SPECIFIC_ARRAY_INDEX) != null) {
             planSteps.add("    SERVER ARRAY ELEMENT PROJECTION");
+            if (explainPlanAttributesBuilder != null) {
+                explainPlanAttributesBuilder.setServerArrayElementProjection(true);
+            }
         }
     }
 
@@ -292,11 +351,12 @@ public abstract class ExplainTable {
             buf.append(',');
         }
     }
-    
-    private void appendKeyRanges(StringBuilder buf) {
+
+    private String appendKeyRanges() {
+        final StringBuilder buf = new StringBuilder();
         ScanRanges scanRanges = context.getScanRanges();
         if (scanRanges.isDegenerate() || scanRanges.isEverything()) {
-            return;
+            return "";
         }
         buf.append(" [");
         StringBuilder buf1 = new StringBuilder();
@@ -310,5 +370,6 @@ public abstract class ExplainTable {
             buf.append(buf2);
         }
         buf.setCharAt(buf.length()-1, ']');
+        return buf.toString();
     }
 }
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/FilterAggregatingResultIterator.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/FilterAggregatingResultIterator.java
index 5fd2028..bd47a78 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/FilterAggregatingResultIterator.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/FilterAggregatingResultIterator.java
@@ -21,6 +21,8 @@ import java.sql.SQLException;
 import java.util.List;
 
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.expression.Expression;
 import org.apache.phoenix.expression.aggregator.Aggregator;
 import org.apache.phoenix.schema.tuple.Tuple;
@@ -76,9 +78,17 @@ public class FilterAggregatingResultIterator  implements AggregatingResultIterat
         planSteps.add("CLIENT FILTER BY " + expression.toString());
     }
 
-	@Override
-	public String toString() {
-		return "FilterAggregatingResultIterator [delegate=" + delegate
-				+ ", expression=" + expression + ", ptr=" + ptr + "]";
-	}
+    @Override
+    public void explain(List<String> planSteps,
+            ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+        delegate.explain(planSteps, explainPlanAttributesBuilder);
+        explainPlanAttributesBuilder.setClientFilterBy(expression.toString());
+        planSteps.add("CLIENT FILTER BY " + expression.toString());
+    }
+
+    @Override
+    public String toString() {
+        return "FilterAggregatingResultIterator [delegate=" + delegate
+            + ", expression=" + expression + ", ptr=" + ptr + "]";
+    }
 }
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/FilterResultIterator.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/FilterResultIterator.java
index 65bcad2..bf97782 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/FilterResultIterator.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/FilterResultIterator.java
@@ -21,6 +21,8 @@ import java.sql.SQLException;
 import java.util.List;
 
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.expression.Expression;
 import org.apache.phoenix.schema.tuple.Tuple;
 import org.apache.phoenix.schema.types.PBoolean;
@@ -74,9 +76,17 @@ public class FilterResultIterator  extends LookAheadResultIterator {
         planSteps.add("CLIENT FILTER BY " + expression.toString());
     }
 
-	@Override
-	public String toString() {
-		return "FilterResultIterator [delegate=" + delegate + ", expression="
-				+ expression + ", ptr=" + ptr + "]";
-	}
+    @Override
+    public void explain(List<String> planSteps,
+            ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+        delegate.explain(planSteps, explainPlanAttributesBuilder);
+        explainPlanAttributesBuilder.setClientFilterBy(expression.toString());
+        planSteps.add("CLIENT FILTER BY " + expression.toString());
+    }
+
+    @Override
+    public String toString() {
+        return "FilterResultIterator [delegate=" + delegate + ", expression="
+            + expression + ", ptr=" + ptr + "]";
+    }
 }
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/LimitingResultIterator.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/LimitingResultIterator.java
index 7cf8d3e..6e1c52d 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/LimitingResultIterator.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/LimitingResultIterator.java
@@ -20,6 +20,8 @@ package org.apache.phoenix.iterate;
 import java.sql.SQLException;
 import java.util.List;
 
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.schema.tuple.Tuple;
 
 /**
@@ -50,12 +52,20 @@ public class LimitingResultIterator extends DelegateResultIterator {
     @Override
     public void explain(List<String> planSteps) {
         super.explain(planSteps);
-            planSteps.add("CLIENT " + limit + " ROW LIMIT");
+        planSteps.add("CLIENT " + limit + " ROW LIMIT");
     }
 
-	@Override
-	public String toString() {
-		return "LimitingResultIterator [rowCount=" + rowCount + ", limit="
-				+ limit + "]";
-	}
+    @Override
+    public void explain(List<String> planSteps,
+            ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+        super.explain(planSteps, explainPlanAttributesBuilder);
+        explainPlanAttributesBuilder.setClientRowLimit(limit);
+        planSteps.add("CLIENT " + limit + " ROW LIMIT");
+    }
+
+    @Override
+    public String toString() {
+        return "LimitingResultIterator [rowCount=" + rowCount + ", limit="
+            + limit + "]";
+    }
 }
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/LookAheadResultIterator.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/LookAheadResultIterator.java
index 1e5f09e..f7c46d4 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/LookAheadResultIterator.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/LookAheadResultIterator.java
@@ -20,6 +20,8 @@ package org.apache.phoenix.iterate;
 import java.sql.SQLException;
 import java.util.List;
 
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.schema.tuple.ResultTuple;
 import org.apache.phoenix.schema.tuple.Tuple;
 
@@ -38,6 +40,12 @@ abstract public class LookAheadResultIterator implements PeekingResultIterator {
             }
 
             @Override
+            public void explain(List<String> planSteps,
+                    ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+                iterator.explain(planSteps, explainPlanAttributesBuilder);
+            }
+
+            @Override
             public void close() throws SQLException {
                 iterator.close();
             }
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/MaterializedComparableResultIterator.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/MaterializedComparableResultIterator.java
index a76f1e3..5808a0e 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/MaterializedComparableResultIterator.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/MaterializedComparableResultIterator.java
@@ -21,6 +21,8 @@ import java.sql.SQLException;
 import java.util.Comparator;
 import java.util.List;
 
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.schema.tuple.Tuple;
 
 /**
@@ -72,4 +74,10 @@ public class MaterializedComparableResultIterator
     public void explain(List<String> planSteps) {
         delegate.explain(planSteps);
     }
+
+    @Override
+    public void explain(List<String> planSteps,
+            ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+        delegate.explain(planSteps, explainPlanAttributesBuilder);
+    }
 }
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/MaterializedResultIterator.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/MaterializedResultIterator.java
index befaa2d..a8c75af 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/MaterializedResultIterator.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/MaterializedResultIterator.java
@@ -20,6 +20,8 @@ package org.apache.phoenix.iterate;
 import java.sql.SQLException;
 import java.util.*;
 
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.schema.tuple.Tuple;
 
 
@@ -107,4 +109,9 @@ public class MaterializedResultIterator implements PeekingResultIterator {
     @Override
     public void explain(List<String> planSteps) {
     }
+
+    @Override
+    public void explain(List<String> planSteps,
+            ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+    }
 }
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/MergeSortRowKeyResultIterator.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/MergeSortRowKeyResultIterator.java
index 1da5142..16551b5 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/MergeSortRowKeyResultIterator.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/MergeSortRowKeyResultIterator.java
@@ -19,6 +19,8 @@ package org.apache.phoenix.iterate;
 
 import java.util.List;
 
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.schema.tuple.Tuple;
 import org.apache.phoenix.util.TupleUtil;
 
@@ -57,9 +59,17 @@ public class MergeSortRowKeyResultIterator extends MergeSortResultIterator {
         planSteps.add("CLIENT MERGE SORT");
     }
 
-	@Override
-	public String toString() {
-		return "MergeSortRowKeyResultIterator [keyOffset=" + keyOffset
-				+ ", factor=" + factor + "]";
-	}
+    @Override
+    public void explain(List<String> planSteps,
+            ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+        resultIterators.explain(planSteps, explainPlanAttributesBuilder);
+        explainPlanAttributesBuilder.setClientSortAlgo("CLIENT MERGE SORT");
+        planSteps.add("CLIENT MERGE SORT");
+    }
+
+    @Override
+    public String toString() {
+        return "MergeSortRowKeyResultIterator [keyOffset=" + keyOffset
+            + ", factor=" + factor + "]";
+    }
 }
\ No newline at end of file
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/MergeSortTopNResultIterator.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/MergeSortTopNResultIterator.java
index 42429b1..eace244 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/MergeSortTopNResultIterator.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/MergeSortTopNResultIterator.java
@@ -21,6 +21,8 @@ import java.sql.SQLException;
 import java.util.List;
 
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.expression.Expression;
 import org.apache.phoenix.expression.OrderByExpression;
 import org.apache.phoenix.schema.tuple.Tuple;
@@ -108,10 +110,26 @@ public class MergeSortTopNResultIterator extends MergeSortResultIterator {
         }
     }
 
-	@Override
-	public String toString() {
-		return "MergeSortTopNResultIterator [limit=" + limit + ", count="
-				+ count + ", orderByColumns=" + orderByColumns + ", ptr1="
-				+ ptr1 + ", ptr2=" + ptr2 + ",offset=" + offset + "]";
-	}
+    @Override
+    public void explain(List<String> planSteps,
+            ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+        resultIterators.explain(planSteps, explainPlanAttributesBuilder);
+        explainPlanAttributesBuilder.setClientSortAlgo("CLIENT MERGE SORT");
+        planSteps.add("CLIENT MERGE SORT");
+        if (offset > 0) {
+            explainPlanAttributesBuilder.setClientOffset(offset);
+            planSteps.add("CLIENT OFFSET " + offset);
+        }
+        if (limit > 0) {
+            explainPlanAttributesBuilder.setClientRowLimit(limit);
+            planSteps.add("CLIENT LIMIT " + limit);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "MergeSortTopNResultIterator [limit=" + limit + ", count="
+            + count + ", orderByColumns=" + orderByColumns + ", ptr1="
+            + ptr1 + ", ptr2=" + ptr2 + ",offset=" + offset + "]";
+    }
 }
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/OffsetResultIterator.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/OffsetResultIterator.java
index db53806..5c5a6d3 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/OffsetResultIterator.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/OffsetResultIterator.java
@@ -20,6 +20,8 @@ package org.apache.phoenix.iterate;
 import java.sql.SQLException;
 import java.util.List;
 
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.schema.tuple.Tuple;
 
 /**
@@ -52,6 +54,14 @@ public class OffsetResultIterator extends DelegateResultIterator {
     }
 
     @Override
+    public void explain(List<String> planSteps,
+            ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+        super.explain(planSteps, explainPlanAttributesBuilder);
+        explainPlanAttributesBuilder.setClientOffset(offset);
+        planSteps.add("CLIENT OFFSET " + offset);
+    }
+
+    @Override
     public String toString() {
         return "OffsetResultIterator [rowCount=" + rowCount + ", offset=" + offset + "]";
     }
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/OrderedResultIterator.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/OrderedResultIterator.java
index 5430226..a433759 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/OrderedResultIterator.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/OrderedResultIterator.java
@@ -31,6 +31,8 @@ import org.apache.hadoop.hbase.KeyValue;
 import org.apache.hadoop.hbase.client.Result;
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
 import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.execute.DescVarLengthFastByteComparisons;
 import org.apache.phoenix.expression.Expression;
 import org.apache.phoenix.expression.OrderByExpression;
@@ -303,6 +305,19 @@ public class OrderedResultIterator implements PeekingResultIterator {
     }
 
     @Override
+    public void explain(List<String> planSteps,
+            ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+        delegate.explain(planSteps, explainPlanAttributesBuilder);
+        explainPlanAttributesBuilder.setClientOffset(offset);
+        explainPlanAttributesBuilder.setClientRowLimit(limit);
+        explainPlanAttributesBuilder.setClientSortedBy(
+            orderByExpressions.toString());
+        planSteps.add("CLIENT" + (offset == null || offset == 0 ? "" : " OFFSET " + offset)
+            + (limit == null ? "" : " TOP " + limit + " ROW" + (limit == 1 ? "" : "S"))
+            + " SORTED BY " + orderByExpressions.toString());
+    }
+
+    @Override
     public String toString() {
         return "OrderedResultIterator [thresholdBytes=" + thresholdBytes
                 + ", limit=" + limit + ", offset=" + offset + ", delegate=" + delegate
@@ -356,6 +371,11 @@ public class OrderedResultIterator implements PeekingResultIterator {
         }
 
         @Override
+        public void explain(List<String> planSteps,
+                ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+        }
+
+        @Override
         public void close() throws SQLException {
             try {
                 queueEntries.close();
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/PeekingResultIterator.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/PeekingResultIterator.java
index 9470d56..f4a193e 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/PeekingResultIterator.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/PeekingResultIterator.java
@@ -20,6 +20,8 @@ package org.apache.phoenix.iterate;
 import java.sql.SQLException;
 import java.util.List;
 
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.schema.tuple.Tuple;
 
 
@@ -51,6 +53,11 @@ public interface PeekingResultIterator extends ResultIterator {
         @Override
         public void explain(List<String> planSteps) {
         }
+
+        @Override
+        public void explain(List<String> planSteps,
+            ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+        }
     };
 
     /**
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/ResultIterator.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/ResultIterator.java
index e2127f8..f721456 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/ResultIterator.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/ResultIterator.java
@@ -20,6 +20,8 @@ package org.apache.phoenix.iterate;
 import java.sql.SQLException;
 import java.util.List;
 
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.schema.tuple.Tuple;
 import org.apache.phoenix.util.SQLCloseable;
 
@@ -38,6 +40,11 @@ public interface ResultIterator extends SQLCloseable {
         @Override
         public void explain(List<String> planSteps) {
         }
+
+        @Override
+        public void explain(List<String> planSteps,
+                ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+        }
     };
 
     /**
@@ -49,4 +56,21 @@ public interface ResultIterator extends SQLCloseable {
     public Tuple next() throws SQLException;
     
     public void explain(List<String> planSteps);
+
+    /**
+     * Generate ExplainPlan steps and add steps as list of Strings in
+     * planSteps argument as readable statement as well as add same generated
+     * steps in explainPlanAttributesBuilder so that we prepare ExplainPlan
+     * result as an attribute object useful to retrieve individual plan step
+     * attributes.
+     *
+     * @param planSteps Add generated plan in list of planSteps. This argument
+     *     is used to provide planSteps as whole statement consisting of
+     *     list of Strings.
+     * @param explainPlanAttributesBuilder Add generated plan in attributes
+     *     object. Having an API to provide planSteps as an object is easier
+     *     while comparing individual attributes of ExplainPlan.
+     */
+    void explain(List<String> planSteps,
+            ExplainPlanAttributesBuilder explainPlanAttributesBuilder);
 }
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/ResultIterators.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/ResultIterators.java
index 16f8b41..8bc47cc 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/ResultIterators.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/ResultIterators.java
@@ -21,6 +21,8 @@ import java.sql.SQLException;
 import java.util.List;
 
 import org.apache.hadoop.hbase.client.Scan;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.query.KeyRange;
 import org.apache.phoenix.util.SQLCloseable;
 
@@ -30,4 +32,22 @@ public interface ResultIterators extends SQLCloseable {
     public List<List<Scan>> getScans();
     public void explain(List<String> planSteps);
     public List<PeekingResultIterator> getIterators() throws SQLException;
+
+    /**
+     * Generate ExplainPlan steps and add steps as list of Strings in
+     * planSteps argument as readable statement as well as add same generated
+     * steps in explainPlanAttributesBuilder so that we prepare ExplainPlan
+     * result as an attribute object useful to retrieve individual plan step
+     * attributes.
+     *
+     * @param planSteps Add generated plan in list of planSteps. This argument
+     *     is used to provide planSteps as whole statement consisting of
+     *     list of Strings.
+     * @param explainPlanAttributesBuilder Add generated plan in attributes
+     *     object. Having an API to provide planSteps as an object is easier
+     *     while comparing individual attributes of ExplainPlan.
+     */
+    void explain(List<String> planSteps,
+            ExplainPlanAttributesBuilder explainPlanAttributesBuilder);
+
 }
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/RoundRobinResultIterator.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/RoundRobinResultIterator.java
index bc77c98..232cdd6 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/RoundRobinResultIterator.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/RoundRobinResultIterator.java
@@ -28,6 +28,8 @@ import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
 
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.compile.QueryPlan;
 import org.apache.phoenix.compile.StatementContext;
 import org.apache.phoenix.query.ConnectionQueryServices;
@@ -154,6 +156,14 @@ public class RoundRobinResultIterator implements ResultIterator {
         }
     }
 
+    @Override
+    public void explain(List<String> planSteps,
+            ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+        if (resultIterators != null) {
+            resultIterators.explain(planSteps, explainPlanAttributesBuilder);
+        }
+    }
+
     @VisibleForTesting
     int getNumberOfParallelFetches() {
         return numParallelFetches;
@@ -315,6 +325,12 @@ public class RoundRobinResultIterator implements ResultIterator {
         }
 
         @Override
+        public void explain(List<String> planSteps,
+                ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+            delegate.explain(planSteps, explainPlanAttributesBuilder);
+        }
+
+        @Override
         public Tuple peek() throws SQLException {
             if (tuple != null) { return tuple; }
             return delegate.peek();
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/RowKeyOrderedAggregateResultIterator.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/RowKeyOrderedAggregateResultIterator.java
index 3c52e51..dc72d72 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/RowKeyOrderedAggregateResultIterator.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/RowKeyOrderedAggregateResultIterator.java
@@ -25,6 +25,8 @@ import java.sql.SQLException;
 import java.util.List;
 
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.expression.aggregator.Aggregator;
 import org.apache.phoenix.expression.aggregator.Aggregators;
 import org.apache.phoenix.schema.tuple.SingleKeyValueTuple;
@@ -104,6 +106,14 @@ public class RowKeyOrderedAggregateResultIterator extends LookAheadResultIterato
         }
     }
 
+    @Override
+    public void explain(List<String> planSteps,
+            ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+        if (resultIterators != null) {
+            resultIterators.explain(planSteps, explainPlanAttributesBuilder);
+        }
+    }
+
     private Tuple nextTuple() throws SQLException {
         List<PeekingResultIterator> iterators = getIterators();
         while (index < iterators.size()) {
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/ScanningResultIterator.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/ScanningResultIterator.java
index 6c521c1..4cafb88 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/ScanningResultIterator.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/ScanningResultIterator.java
@@ -51,6 +51,8 @@ import org.apache.hadoop.hbase.client.Result;
 import org.apache.hadoop.hbase.client.ResultScanner;
 import org.apache.hadoop.hbase.client.Scan;
 import org.apache.hadoop.hbase.client.metrics.ScanMetrics;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.monitoring.CombinableMetric;
 import org.apache.phoenix.monitoring.GlobalClientMetrics;
 import org.apache.phoenix.monitoring.ScanMetricsHolder;
@@ -178,6 +180,11 @@ public class ScanningResultIterator implements ResultIterator {
     }
 
     @Override
+    public void explain(List<String> planSteps,
+            ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+    }
+
+    @Override
     public String toString() {
         return "ScanningResultIterator [scanner=" + scanner + "]";
     }
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/SequenceResultIterator.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/SequenceResultIterator.java
index 80b5401..5674a6a 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/SequenceResultIterator.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/SequenceResultIterator.java
@@ -20,6 +20,8 @@ package org.apache.phoenix.iterate;
 import java.sql.SQLException;
 import java.util.List;
 
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.compile.SequenceManager;
 import org.apache.phoenix.schema.tuple.Tuple;
 
@@ -55,9 +57,19 @@ public class SequenceResultIterator extends DelegateResultIterator {
         planSteps.add("CLIENT RESERVE VALUES FROM " + nSequences + " SEQUENCE" + (nSequences == 1 ? "" : "S"));
     }
 
-	@Override
-	public String toString() {
-		return "SequenceResultIterator [sequenceManager=" + sequenceManager
-				+ "]";
-	}
+    @Override
+    public void explain(List<String> planSteps,
+            ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+        super.explain(planSteps, explainPlanAttributesBuilder);
+        int nSequences = sequenceManager.getSequenceCount();
+        explainPlanAttributesBuilder.setClientSequenceCount(nSequences);
+        planSteps.add("CLIENT RESERVE VALUES FROM " + nSequences
+            + " SEQUENCE" + (nSequences == 1 ? "" : "S"));
+    }
+
+    @Override
+    public String toString() {
+        return "SequenceResultIterator [sequenceManager=" + sequenceManager
+            + "]";
+    }
 }
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/SerialIterators.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/SerialIterators.java
index 1693421..4fd38c5 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/SerialIterators.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/SerialIterators.java
@@ -29,6 +29,8 @@ import org.apache.hadoop.hbase.client.Scan;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.hadoop.hbase.util.Pair;
 import org.apache.phoenix.cache.ServerCacheClient.ServerCache;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.compile.QueryPlan;
 import org.apache.phoenix.coprocessor.BaseScannerRegionObserver;
 import org.apache.phoenix.hbase.index.util.ImmutableBytesPtr;
@@ -207,6 +209,11 @@ public class SerialIterators extends BaseResultIterators {
         public void explain(List<String> planSteps) {}
 
         @Override
+        public void explain(List<String> planSteps,
+                ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+        }
+
+        @Override
         public void close() throws SQLException {
             if (currentIterator != null) {
                 currentIterator.close();
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/SpoolingResultIterator.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/SpoolingResultIterator.java
index 6995e79..b2d3794 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/SpoolingResultIterator.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/SpoolingResultIterator.java
@@ -36,6 +36,8 @@ import org.apache.commons.io.output.DeferredFileOutputStream;
 import org.apache.hadoop.hbase.client.Scan;
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
 import org.apache.hadoop.io.WritableUtils;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.compile.QueryPlan;
 import org.apache.phoenix.compile.StatementContext;
 import org.apache.phoenix.memory.MemoryManager;
@@ -253,6 +255,11 @@ public class SpoolingResultIterator implements PeekingResultIterator {
         @Override
         public void explain(List<String> planSteps) {
         }
+
+        @Override
+        public void explain(List<String> planSteps,
+                ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+        }
     }
 
     /**
@@ -354,9 +361,19 @@ public class SpoolingResultIterator implements PeekingResultIterator {
         @Override
         public void explain(List<String> planSteps) {
         }
+
+        @Override
+        public void explain(List<String> planSteps,
+                ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+        }
     }
 
     @Override
     public void explain(List<String> planSteps) {
     }
+
+    @Override
+    public void explain(List<String> planSteps,
+            ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+    }
 }
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/TableResultIterator.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/TableResultIterator.java
index c576ad8..2ae9223 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/TableResultIterator.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/TableResultIterator.java
@@ -44,6 +44,8 @@ import org.apache.hadoop.hbase.coprocessor.BaseRegionObserver;
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.phoenix.cache.ServerCacheClient.ServerCache;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.compile.QueryPlan;
 import org.apache.phoenix.coprocessor.BaseScannerRegionObserver;
 import org.apache.phoenix.coprocessor.HashJoinCacheNotFoundException;
@@ -297,4 +299,10 @@ public class TableResultIterator implements ResultIterator {
         scanIterator.explain(planSteps);
     }
 
+    @Override
+    public void explain(List<String> planSteps,
+            ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+        scanIterator.explain(planSteps, explainPlanAttributesBuilder);
+    }
+
 }
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/TableSnapshotResultIterator.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/TableSnapshotResultIterator.java
index 6f5e2ee..9cca642 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/TableSnapshotResultIterator.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/TableSnapshotResultIterator.java
@@ -28,6 +28,8 @@ import org.apache.hadoop.hbase.client.Scan;
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
 import org.apache.hadoop.hbase.snapshot.RestoreSnapshotHelper;
 import org.apache.hadoop.hbase.util.FSUtils;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.mapreduce.util.PhoenixConfigurationUtil;
 import org.apache.phoenix.monitoring.ScanMetricsHolder;
 import org.apache.phoenix.schema.tuple.Tuple;
@@ -171,9 +173,14 @@ public class TableSnapshotResultIterator implements ResultIterator {
     }
   }
 
-  @Override
-  public void explain(List<String> planSteps) {
-    // noop
-  }
+    @Override
+    public void explain(List<String> planSteps) {
+      // noop
+    }
+
+    @Override
+    public void explain(List<String> planSteps,
+            ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+    }
 
 }
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/UnionResultIterators.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/UnionResultIterators.java
index 910a514..a90e8a6 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/UnionResultIterators.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/UnionResultIterators.java
@@ -22,6 +22,9 @@ import java.util.Collections;
 import java.util.List;
 
 import org.apache.hadoop.hbase.client.Scan;
+import org.apache.phoenix.compile.ExplainPlanAttributes;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.compile.QueryPlan;
 import org.apache.phoenix.compile.StatementContext;
 import org.apache.phoenix.monitoring.OverAllQueryMetrics;
@@ -128,8 +131,8 @@ public class UnionResultIterators implements ResultIterators {
 
     @Override
     public void explain(List<String> planSteps) {
-        for (int index=0; index < iterators.size(); index++) {
-            iterators.get(index).explain(planSteps);
+        for (PeekingResultIterator iterator : iterators) {
+            iterator.explain(planSteps);
         }
     }
 
@@ -137,4 +140,27 @@ public class UnionResultIterators implements ResultIterators {
     public List<PeekingResultIterator> getIterators() throws SQLException {    
         return iterators;
     }
+
+    @Override
+    public void explain(List<String> planSteps,
+            ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+        boolean moreThanOneIters = false;
+        ExplainPlanAttributesBuilder lhsPointer = null;
+        // For more than one iterators, explainPlanAttributes will create
+        // chain of objects as lhs and rhs query plans.
+        for (PeekingResultIterator iterator : iterators) {
+            if (moreThanOneIters) {
+                ExplainPlanAttributesBuilder rhsBuilder =
+                    new ExplainPlanAttributesBuilder();
+                iterator.explain(planSteps, rhsBuilder);
+                ExplainPlanAttributes rhsPlans = rhsBuilder.build();
+                lhsPointer.setRhsJoinQueryExplainPlan(rhsPlans);
+                lhsPointer = rhsBuilder;
+            } else {
+                iterator.explain(planSteps, explainPlanAttributesBuilder);
+                lhsPointer = explainPlanAttributesBuilder;
+            }
+            moreThanOneIters = true;
+        }
+    }
 }
\ No newline at end of file
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/iterate/ConcatResultIteratorTest.java b/phoenix-core/src/test/java/org/apache/phoenix/iterate/ConcatResultIteratorTest.java
index 67d5cd0..106aa9d 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/iterate/ConcatResultIteratorTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/iterate/ConcatResultIteratorTest.java
@@ -29,6 +29,8 @@ import java.util.List;
 import org.apache.hadoop.hbase.KeyValue;
 import org.apache.hadoop.hbase.client.Scan;
 import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.query.KeyRange;
 import org.apache.phoenix.schema.tuple.SingleKeyValueTuple;
 import org.apache.phoenix.schema.tuple.Tuple;
@@ -98,6 +100,12 @@ public class ConcatResultIteratorTest {
             }
 
             @Override
+            public void explain(List<String> planSteps,
+                    ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+
+            }
+
+            @Override
             public int size() {
                 return results.size();
             }
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/iterate/MaterializedResultIterators.java b/phoenix-core/src/test/java/org/apache/phoenix/iterate/MaterializedResultIterators.java
index c4b0265..6efc211 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/iterate/MaterializedResultIterators.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/iterate/MaterializedResultIterators.java
@@ -22,6 +22,8 @@ import java.util.Collections;
 import java.util.List;
 
 import org.apache.hadoop.hbase.client.Scan;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.query.KeyRange;
 
 /**
@@ -42,6 +44,11 @@ public class MaterializedResultIterators implements ResultIterators {
     }
 
     @Override
+    public void explain(List<String> planSteps,
+            ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+    }
+
+    @Override
     public int size() {
         return results.size();
     }
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/iterate/MergeSortResultIteratorTest.java b/phoenix-core/src/test/java/org/apache/phoenix/iterate/MergeSortResultIteratorTest.java
index 9b2e8de..3879405 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/iterate/MergeSortResultIteratorTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/iterate/MergeSortResultIteratorTest.java
@@ -29,6 +29,8 @@ import java.util.List;
 import org.apache.hadoop.hbase.KeyValue;
 import org.apache.hadoop.hbase.client.Scan;
 import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.phoenix.compile.ExplainPlanAttributes
+    .ExplainPlanAttributesBuilder;
 import org.apache.phoenix.query.KeyRange;
 import org.apache.phoenix.schema.tuple.SingleKeyValueTuple;
 import org.apache.phoenix.schema.tuple.Tuple;
@@ -79,6 +81,11 @@ public class MergeSortResultIteratorTest {
             }
 
             @Override
+            public void explain(List<String> planSteps,
+                    ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+            }
+
+            @Override
             public int size() {
                 return results.size();
             }
@@ -109,6 +116,11 @@ public class MergeSortResultIteratorTest {
             }
 
             @Override
+            public void explain(List<String> planSteps,
+                    ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+            }
+
+            @Override
             public int size() {
                 return results.size();
             }
@@ -172,6 +184,11 @@ public class MergeSortResultIteratorTest {
             }
 
             @Override
+            public void explain(List<String> planSteps,
+                    ExplainPlanAttributesBuilder explainPlanAttributesBuilder) {
+            }
+
+            @Override
             public int size() {
                 return results.size();
             }