You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@phoenix.apache.org by ma...@apache.org on 2016/02/25 21:39:07 UTC

[04/12] phoenix git commit: PHOENIX-2707 Differentiate between a table+family have zero guideposts from not having collected guideposts

PHOENIX-2707 Differentiate between a table+family have zero guideposts from not having collected guideposts


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

Branch: refs/heads/calcite
Commit: c25f88791c86cd65ea21810406eec199403d78aa
Parents: dbc9ee9
Author: James Taylor <jt...@salesforce.com>
Authored: Tue Feb 23 14:53:11 2016 -0800
Committer: James Taylor <jt...@salesforce.com>
Committed: Tue Feb 23 14:53:11 2016 -0800

----------------------------------------------------------------------
 .../org/apache/phoenix/end2end/IndexToolIT.java |   3 +-
 .../phoenix/end2end/MutableIndexToolIT.java     |   1 +
 .../end2end/StatsCollectionDisabledIT.java      |  21 ++-
 .../phoenix/end2end/StatsCollectorIT.java       |  57 +++++++-
 .../phoenix/iterate/BaseResultIterators.java    | 140 +++++++++++--------
 .../apache/phoenix/query/QueryConstants.java    |   2 +-
 .../org/apache/phoenix/query/QueryServices.java |   1 +
 .../phoenix/query/QueryServicesOptions.java     |   7 +
 .../stats/DefaultStatisticsCollector.java       |  33 ++---
 .../phoenix/schema/stats/GuidePostsInfo.java    |   2 +-
 .../schema/stats/GuidePostsInfoBuilder.java     |   6 -
 .../phoenix/schema/stats/PTableStatsImpl.java   |   5 +-
 .../phoenix/schema/stats/StatisticsUtil.java    |  23 +--
 .../phoenix/schema/stats/StatisticsWriter.java  |  60 ++++----
 .../apache/phoenix/util/PrefixByteCodec.java    |  37 ++---
 .../phoenix/query/QueryServicesTestImpl.java    |   2 +
 16 files changed, 246 insertions(+), 154 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/phoenix/blob/c25f8879/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexToolIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexToolIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexToolIT.java
index aba9c11..fe95470 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexToolIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexToolIT.java
@@ -108,7 +108,8 @@ public class IndexToolIT extends BaseOwnClusterHBaseManagedTimeIT {
         final String fullTableName = SchemaUtil.getTableName(schemaName, dataTable);
         final String indxTable = String.format("%s_%s", dataTable, "INDX");
         Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
-        props.setProperty(QueryServices.TRANSACTIONS_ENABLED, "true");
+        props.setProperty(QueryServices.TRANSACTIONS_ENABLED, Boolean.TRUE.toString());
+        props.setProperty(QueryServices.EXPLAIN_ROW_COUNT_ATTRIB, Boolean.FALSE.toString());
         Connection conn = DriverManager.getConnection(getUrl(), props);
         Statement stmt = conn.createStatement();
         try {

http://git-wip-us.apache.org/repos/asf/phoenix/blob/c25f8879/phoenix-core/src/it/java/org/apache/phoenix/end2end/MutableIndexToolIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/MutableIndexToolIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/MutableIndexToolIT.java
index 0791479..8125007 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/MutableIndexToolIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/MutableIndexToolIT.java
@@ -61,6 +61,7 @@ public class MutableIndexToolIT extends BaseOwnClusterHBaseManagedTimeIT {
         final String dataTable = "DATA_TABLE5";
         final String indxTable = String.format("%s_%s",dataTable,"INDX");
         Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
+        props.setProperty(QueryServices.EXPLAIN_ROW_COUNT_ATTRIB, Boolean.FALSE.toString());
         Connection conn = DriverManager.getConnection(getUrl(), props);
         Statement stmt = conn.createStatement();
         try {

http://git-wip-us.apache.org/repos/asf/phoenix/blob/c25f8879/phoenix-core/src/it/java/org/apache/phoenix/end2end/StatsCollectionDisabledIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/StatsCollectionDisabledIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/StatsCollectionDisabledIT.java
index a92a665..54ffa7c 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/StatsCollectionDisabledIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/StatsCollectionDisabledIT.java
@@ -17,24 +17,26 @@
  */
 package org.apache.phoenix.end2end;
 
+import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
 import java.sql.Connection;
 import java.sql.DriverManager;
-import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
 import java.util.Map;
 import java.util.Properties;
 
-import com.google.common.collect.Maps;
 import org.apache.phoenix.query.QueryServices;
 import org.apache.phoenix.util.PropertiesUtil;
+import org.apache.phoenix.util.QueryUtil;
 import org.apache.phoenix.util.ReadOnlyProps;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
-import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES;
-import static org.junit.Assert.assertFalse;
+import com.google.common.collect.Maps;
 
 /**
  * Verifies that statistics are not collected if they are disabled via a setting
@@ -43,10 +45,12 @@ public class StatsCollectionDisabledIT extends StatsCollectorAbstractIT {
 
     @BeforeClass
     public static void doSetup() throws Exception {
-        Map<String,String> props = Maps.newHashMapWithExpectedSize(3);
+        Map<String,String> props = Maps.newHashMapWithExpectedSize(5);
         // Must update config before starting server
         props.put(QueryServices.STATS_GUIDEPOST_WIDTH_BYTES_ATTRIB, Long.toString(20));
         props.put(QueryServices.STATS_ENABLED_ATTRIB, Boolean.toString(false));
+        props.put(QueryServices.EXPLAIN_CHUNK_COUNT_ATTRIB, Boolean.TRUE.toString());
+        props.put(QueryServices.EXPLAIN_ROW_COUNT_ATTRIB, Boolean.TRUE.toString());
         setUpTestDriver(new ReadOnlyProps(props.entrySet().iterator()));
     }
 
@@ -65,6 +69,11 @@ public class StatsCollectionDisabledIT extends StatsCollectorAbstractIT {
         assertFalse(rs.next());
         rs.close();
         stmt.close();
-        conn.close();
+        rs = conn.createStatement().executeQuery("EXPLAIN SELECT * FROM T1");
+        String explainPlan = QueryUtil.getExplainPlan(rs);
+        assertEquals(
+                "CLIENT 1-CHUNK PARALLEL 1-WAY FULL SCAN OVER T1",
+                explainPlan);
+       conn.close();
     }
 }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/c25f8879/phoenix-core/src/it/java/org/apache/phoenix/end2end/StatsCollectorIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/StatsCollectorIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/StatsCollectorIT.java
index e72f41f..bc575fd 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/StatsCollectorIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/StatsCollectorIT.java
@@ -44,6 +44,7 @@ import org.apache.phoenix.query.KeyRange;
 import org.apache.phoenix.query.QueryServices;
 import org.apache.phoenix.query.QueryServicesOptions;
 import org.apache.phoenix.util.PropertiesUtil;
+import org.apache.phoenix.util.QueryUtil;
 import org.apache.phoenix.util.ReadOnlyProps;
 import org.apache.phoenix.util.SchemaUtil;
 import org.apache.phoenix.util.TestUtil;
@@ -65,10 +66,11 @@ public class StatsCollectorIT extends StatsCollectorAbstractIT {
         
     @BeforeClass
     public static void doSetup() throws Exception {
-        Map<String,String> props = Maps.newHashMapWithExpectedSize(3);
+        Map<String,String> props = Maps.newHashMapWithExpectedSize(10);
         // Must update config before starting server
         props.put(QueryServices.STATS_GUIDEPOST_WIDTH_BYTES_ATTRIB, Long.toString(20));
         props.put(QueryServices.EXPLAIN_CHUNK_COUNT_ATTRIB, Boolean.TRUE.toString());
+        props.put(QueryServices.EXPLAIN_ROW_COUNT_ATTRIB, Boolean.TRUE.toString());
         props.put(QueryServices.QUEUE_SIZE_ATTRIB, Integer.toString(1024));
         props.put(QueryServices.TRANSACTIONS_ENABLED, Boolean.toString(true));
         setUpTestDriver(new ReadOnlyProps(props.entrySet().iterator()));
@@ -86,6 +88,55 @@ public class StatsCollectorIT extends StatsCollectorAbstractIT {
     }
 
     @Test
+    public void testUpdateEmptyStats() throws Exception {
+        Connection conn;
+        Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
+        conn = DriverManager.getConnection(getUrl(), props);
+        conn.setAutoCommit(true);
+        conn.createStatement().execute(
+                "CREATE TABLE " + fullTableName +" ( k CHAR(1) PRIMARY KEY )"  + tableDDLOptions);
+        conn.createStatement().execute("UPDATE STATISTICS " + tableName);
+        ResultSet rs = conn.createStatement().executeQuery("EXPLAIN SELECT * FROM " + fullTableName);
+        String explainPlan = QueryUtil.getExplainPlan(rs);
+        assertEquals(
+                "CLIENT 1-CHUNK 0 ROWS 0 BYTES PARALLEL 1-WAY FULL SCAN OVER " + fullTableName + "\n" + 
+                "    SERVER FILTER BY FIRST KEY ONLY",
+                explainPlan);
+        conn.close();
+    }
+    
+    @Test
+    public void testSomeUpdateEmptyStats() throws Exception {
+        Connection conn;
+        Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
+        String fullTableName = this.fullTableName + "_SALTED";
+        // props.setProperty(PhoenixRuntime.CURRENT_SCN_ATTRIB, Long.toString(ts + 10));
+        conn = DriverManager.getConnection(getUrl(), props);
+        conn.setAutoCommit(true);
+        conn.createStatement().execute(
+                "CREATE TABLE " + fullTableName +" ( k VARCHAR PRIMARY KEY, a.v1 VARCHAR, b.v2 VARCHAR ) " + tableDDLOptions + (tableDDLOptions.isEmpty() ? "" : ",") + "SALT_BUCKETS = 3");
+        conn.createStatement().execute("UPSERT INTO " + fullTableName + "(k,v1) VALUES('a','123456789')");
+        conn.createStatement().execute("UPDATE STATISTICS " + fullTableName);
+        ResultSet rs;
+        String explainPlan;
+        rs = conn.createStatement().executeQuery("EXPLAIN SELECT v2 FROM " + fullTableName + " WHERE v2='foo'");
+        explainPlan = QueryUtil.getExplainPlan(rs);
+        assertEquals(
+                "CLIENT 3-CHUNK 0 ROWS 0 BYTES PARALLEL 3-WAY FULL SCAN OVER " + fullTableName + "\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 34 BYTES PARALLEL 3-WAY FULL SCAN OVER " + fullTableName + "\n" +
+                "CLIENT MERGE SORT",
+                explainPlan);
+        
+        conn.close();
+    }
+    
+    @Test
     public void testUpdateStats() throws SQLException, IOException,
 			InterruptedException {
 		Connection conn;
@@ -336,7 +387,7 @@ public class StatsCollectorIT extends StatsCollectorAbstractIT {
             // If we've set MIN_STATS_UPDATE_FREQ_MS_ATTRIB, an UPDATE STATISTICS will invalidate the cache
             // and forcing the new stats to be pulled over.
             int rowCount = conn.createStatement().executeUpdate("UPDATE STATISTICS " + tableName);
-            assertEquals(0, rowCount);
+            assertEquals(10, rowCount);
         }
         List<KeyRange>keyRanges = getAllSplits(conn, tableName);
         assertEquals(nRows+1, keyRanges.size());
@@ -356,7 +407,7 @@ public class StatsCollectorIT extends StatsCollectorAbstractIT {
             // If we've set MIN_STATS_UPDATE_FREQ_MS_ATTRIB, an UPDATE STATISTICS will invalidate the cache
             // and force us to pull over the new stats
             int rowCount = conn.createStatement().executeUpdate("UPDATE STATISTICS " + tableName);
-            assertEquals(0, rowCount);
+            assertEquals(5, rowCount);
             keyRanges = getAllSplits(conn, tableName);
         }
         assertEquals(nRows/2+1, keyRanges.size());

http://git-wip-us.apache.org/repos/asf/phoenix/blob/c25f8879/phoenix-core/src/main/java/org/apache/phoenix/iterate/BaseResultIterators.java
----------------------------------------------------------------------
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 fc3edbe..a48de13 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
@@ -95,6 +95,7 @@ import org.slf4j.LoggerFactory;
 import com.google.common.base.Function;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
+import com.google.common.io.Closeables;
 
 
 /**
@@ -120,8 +121,9 @@ public abstract class BaseResultIterators extends ExplainTable implements Result
     private final ParallelScanGrouper scanGrouper;
     // TODO: too much nesting here - breakup into new classes.
     private final List<List<List<Pair<Scan,Future<PeekingResultIterator>>>>> allFutures;
-    private long estimatedRows;
-    private long estimatedSize;
+    private Long estimatedRows;
+    private Long estimatedSize;
+    private boolean hasGuidePosts;
     
     static final Function<HRegionLocation, KeyRange> TO_KEY_RANGE = new Function<HRegionLocation, KeyRange>() {
         @Override
@@ -401,7 +403,7 @@ public abstract class BaseResultIterators extends ExplainTable implements Result
          * be further parallelized. TODO: pref test to verify 2) We're collecting stats, as in this case we need to scan
          * entire regions worth of data to track where to put the guide posts.
          */
-        if (!useStats()) { return GuidePostsInfo.EMPTY_GUIDEPOST; }
+        if (!useStats()) { return GuidePostsInfo.NO_GUIDEPOST; }
 
         GuidePostsInfo gps = null;
         PTable table = getTable();
@@ -424,7 +426,7 @@ public abstract class BaseResultIterators extends ExplainTable implements Result
                 }
             }
         }
-        if (gps == null) { return GuidePostsInfo.EMPTY_GUIDEPOST; }
+        if (gps == null) { return GuidePostsInfo.NO_GUIDEPOST; }
         return gps;
     }
 
@@ -490,6 +492,7 @@ public abstract class BaseResultIterators extends ExplainTable implements Result
             }
         }
         GuidePostsInfo gps = getGuidePosts(whereConditions);
+        hasGuidePosts = gps != GuidePostsInfo.NO_GUIDEPOST;
         boolean traverseAllRegions = isSalted || isLocalIndex;
         if (!traverseAllRegions) {
             byte[] scanStartRow = scan.getStartRow();
@@ -528,63 +531,75 @@ public abstract class BaseResultIterators extends ExplainTable implements Result
         DataInput input = null;
         PrefixByteDecoder decoder = null;
         int guideIndex = 0;
-        if (gpsSize > 0) {
-            stream = new ByteArrayInputStream(guidePosts.get(), guidePosts.getOffset(), guidePosts.getLength());
-            input = new DataInputStream(stream);
-            decoder = new PrefixByteDecoder(gps.getMaxLength());
-            try {
-                while (currentKey.compareTo(currentGuidePost = PrefixByteCodec.decode(decoder, input)) >= 0
-                        && currentKey.getLength() != 0) {
-                    guideIndex++;
-                }
-            } catch (EOFException e) {}
-        }
-        byte[] currentKeyBytes = currentKey.copyBytes();
-
-        // Merge bisect with guideposts for all but the last region
-        while (regionIndex <= stopIndex) {
-            byte[] currentGuidePostBytes = currentGuidePost.copyBytes();
-            byte[] endKey, endRegionKey = EMPTY_BYTE_ARRAY;
-            if (regionIndex == stopIndex) {
-                endKey = stopKey;
-            } else {
-                endKey = regionBoundaries.get(regionIndex);
-            }
-            HRegionLocation regionLocation = regionLocations.get(regionIndex);
-            if (isLocalIndex) {
-                HRegionInfo regionInfo = regionLocation.getRegionInfo();
-                endRegionKey = regionInfo.getEndKey();
-                keyOffset = ScanUtil.getRowKeyOffset(regionInfo.getStartKey(), endRegionKey);
+        long estimatedRows = 0;
+        long estimatedSize = 0;
+        try {
+            if (gpsSize > 0) {
+                stream = new ByteArrayInputStream(guidePosts.get(), guidePosts.getOffset(), guidePosts.getLength());
+                input = new DataInputStream(stream);
+                decoder = new PrefixByteDecoder(gps.getMaxLength());
+                try {
+                    while (currentKey.compareTo(currentGuidePost = PrefixByteCodec.decode(decoder, input)) >= 0
+                            && currentKey.getLength() != 0) {
+                        guideIndex++;
+                    }
+                } catch (EOFException e) {}
             }
-            try {
-                while (guideIndex < gpsSize && (currentGuidePost.compareTo(endKey) <= 0 || endKey.length == 0)) {
-                    Scan newScan = scanRanges.intersectScan(scan, currentKeyBytes, currentGuidePostBytes, keyOffset,
-                            false);
-                    estimatedRows += gps.getRowCounts().get(guideIndex);
-                    estimatedSize += gps.getByteCounts().get(guideIndex);
-                    scans = addNewScan(parallelScans, scans, newScan, currentGuidePostBytes, false, regionLocation);
-                    currentKeyBytes = currentGuidePost.copyBytes();
-                    currentGuidePost = PrefixByteCodec.decode(decoder, input);
-                    currentGuidePostBytes = currentGuidePost.copyBytes();
-                    guideIndex++;
+            byte[] currentKeyBytes = currentKey.copyBytes();
+    
+            // Merge bisect with guideposts for all but the last region
+            while (regionIndex <= stopIndex) {
+                byte[] currentGuidePostBytes = currentGuidePost.copyBytes();
+                byte[] endKey, endRegionKey = EMPTY_BYTE_ARRAY;
+                if (regionIndex == stopIndex) {
+                    endKey = stopKey;
+                } else {
+                    endKey = regionBoundaries.get(regionIndex);
                 }
-            } catch (EOFException e) {}
-            Scan newScan = scanRanges.intersectScan(scan, currentKeyBytes, endKey, keyOffset, true);
-            if (isLocalIndex) {
-                if (newScan != null) {
-                    newScan.setAttribute(EXPECTED_UPPER_REGION_KEY, endRegionKey);
-                } else if (!scans.isEmpty()) {
-                    scans.get(scans.size()-1).setAttribute(EXPECTED_UPPER_REGION_KEY, endRegionKey);
+                HRegionLocation regionLocation = regionLocations.get(regionIndex);
+                if (isLocalIndex) {
+                    HRegionInfo regionInfo = regionLocation.getRegionInfo();
+                    endRegionKey = regionInfo.getEndKey();
+                    keyOffset = ScanUtil.getRowKeyOffset(regionInfo.getStartKey(), endRegionKey);
                 }
+                try {
+                    while (guideIndex < gpsSize && (currentGuidePost.compareTo(endKey) <= 0 || endKey.length == 0)) {
+                        Scan newScan = scanRanges.intersectScan(scan, currentKeyBytes, currentGuidePostBytes, keyOffset,
+                                false);
+                        estimatedRows += gps.getRowCounts().get(guideIndex);
+                        estimatedSize += gps.getByteCounts().get(guideIndex);
+                        scans = addNewScan(parallelScans, scans, newScan, currentGuidePostBytes, false, regionLocation);
+                        currentKeyBytes = currentGuidePost.copyBytes();
+                        currentGuidePost = PrefixByteCodec.decode(decoder, input);
+                        currentGuidePostBytes = currentGuidePost.copyBytes();
+                        guideIndex++;
+                    }
+                } catch (EOFException e) {}
+                Scan newScan = scanRanges.intersectScan(scan, currentKeyBytes, endKey, keyOffset, true);
+                if (isLocalIndex) {
+                    if (newScan != null) {
+                        newScan.setAttribute(EXPECTED_UPPER_REGION_KEY, endRegionKey);
+                    } else if (!scans.isEmpty()) {
+                        scans.get(scans.size()-1).setAttribute(EXPECTED_UPPER_REGION_KEY, endRegionKey);
+                    }
+                }
+                scans = addNewScan(parallelScans, scans, newScan, endKey, true, regionLocation);
+                currentKeyBytes = endKey;
+                regionIndex++;
             }
-            scans = addNewScan(parallelScans, scans, newScan, endKey, true, regionLocation);
-            currentKeyBytes = endKey;
-            regionIndex++;
-        }
-        if (!scans.isEmpty()) { // Add any remaining scans
-            parallelScans.add(scans);
+            if (hasGuidePosts) {
+                this.estimatedRows = estimatedRows;
+                this.estimatedSize = estimatedSize;
+            } else {
+                this.estimatedRows = null;
+                this.estimatedSize = null;
+            }
+            if (!scans.isEmpty()) { // Add any remaining scans
+                parallelScans.add(scans);
+            }
+        } finally {
+            if (stream != null) Closeables.closeQuietly(stream);
         }
-        PrefixByteCodec.close(stream);
         return parallelScans;
     }
 
@@ -857,8 +872,11 @@ public abstract class BaseResultIterators extends ExplainTable implements Result
         StringBuilder buf = new StringBuilder();
         buf.append("CLIENT ");
         if (displayChunkCount) {
+            boolean displayRowCount = context.getConnection().getQueryServices().getProps().getBoolean(
+                    QueryServices.EXPLAIN_ROW_COUNT_ATTRIB,
+                    QueryServicesOptions.DEFAULT_EXPLAIN_ROW_COUNT);
             buf.append(this.splits.size()).append("-CHUNK ");
-            if (estimatedRows > 0) {
+            if (displayRowCount && hasGuidePosts) {
                 buf.append(estimatedRows).append(" ROWS ");
                 buf.append(estimatedSize).append(" BYTES ");
             }
@@ -867,6 +885,14 @@ public abstract class BaseResultIterators extends ExplainTable implements Result
         explain(buf.toString(),planSteps);
     }
 
+    public Long getEstimatedRowCount() {
+        return this.estimatedRows;
+    }
+    
+    public Long getEstimatedByteCount() {
+        return this.estimatedSize;
+    }
+    
     @Override
     public String toString() {
         return "ResultIterators [name=" + getName() + ",id=" + scanId + ",scans=" + scans + "]";

http://git-wip-us.apache.org/repos/asf/phoenix/blob/c25f8879/phoenix-core/src/main/java/org/apache/phoenix/query/QueryConstants.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/query/QueryConstants.java b/phoenix-core/src/main/java/org/apache/phoenix/query/QueryConstants.java
index 63d4e07..471ac73 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/query/QueryConstants.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/query/QueryConstants.java
@@ -271,7 +271,7 @@ public interface QueryConstants {
             "CREATE TABLE " + SYSTEM_CATALOG_SCHEMA + ".\"" + SYSTEM_STATS_TABLE + "\"(\n" +
             // PK columns
             PHYSICAL_NAME  + " VARCHAR NOT NULL," +
-            COLUMN_FAMILY + " VARCHAR," +
+            COLUMN_FAMILY + " VARCHAR NOT NULL," +
             GUIDE_POST_KEY  + " VARBINARY," +
             GUIDE_POSTS_WIDTH + " BIGINT," +
             LAST_STATS_UPDATE_TIME+ " DATE, "+

http://git-wip-us.apache.org/repos/asf/phoenix/blob/c25f8879/phoenix-core/src/main/java/org/apache/phoenix/query/QueryServices.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/query/QueryServices.java b/phoenix-core/src/main/java/org/apache/phoenix/query/QueryServices.java
index 1efcd8c..32b4fc0 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/query/QueryServices.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/query/QueryServices.java
@@ -162,6 +162,7 @@ public interface QueryServices extends SQLCloseable {
     public static final String SEQUENCE_SALT_BUCKETS_ATTRIB = "phoenix.sequence.saltBuckets";
     public static final String COPROCESSOR_PRIORITY_ATTRIB = "phoenix.coprocessor.priority";
     public static final String EXPLAIN_CHUNK_COUNT_ATTRIB = "phoenix.explain.displayChunkCount";
+    public static final String EXPLAIN_ROW_COUNT_ATTRIB = "phoenix.explain.displayRowCount";
     public static final String ALLOW_ONLINE_TABLE_SCHEMA_UPDATE = "hbase.online.schema.update.enable";
     public static final String NUM_RETRIES_FOR_SCHEMA_UPDATE_CHECK = "phoenix.schema.change.retries";
     public static final String DELAY_FOR_SCHEMA_UPDATE_CHECK = "phoenix.schema.change.delay";

http://git-wip-us.apache.org/repos/asf/phoenix/blob/c25f8879/phoenix-core/src/main/java/org/apache/phoenix/query/QueryServicesOptions.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/query/QueryServicesOptions.java b/phoenix-core/src/main/java/org/apache/phoenix/query/QueryServicesOptions.java
index 27c5693..3e0ffe1 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/query/QueryServicesOptions.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/query/QueryServicesOptions.java
@@ -29,6 +29,7 @@ import static org.apache.phoenix.query.QueryServices.DATE_FORMAT_TIMEZONE_ATTRIB
 import static org.apache.phoenix.query.QueryServices.DELAY_FOR_SCHEMA_UPDATE_CHECK;
 import static org.apache.phoenix.query.QueryServices.DROP_METADATA_ATTRIB;
 import static org.apache.phoenix.query.QueryServices.EXPLAIN_CHUNK_COUNT_ATTRIB;
+import static org.apache.phoenix.query.QueryServices.EXPLAIN_ROW_COUNT_ATTRIB;
 import static org.apache.phoenix.query.QueryServices.EXTRA_JDBC_ARGUMENTS_ATTRIB;
 import static org.apache.phoenix.query.QueryServices.FORCE_ROW_KEY_ORDER_ATTRIB;
 import static org.apache.phoenix.query.QueryServices.GLOBAL_METRICS_ENABLED;
@@ -196,6 +197,7 @@ public class QueryServicesOptions {
      */
     public static final int DEFAULT_COPROCESSOR_PRIORITY = Coprocessor.PRIORITY_SYSTEM/2 + Coprocessor.PRIORITY_USER/2; // Divide individually to prevent any overflow
     public static final boolean DEFAULT_EXPLAIN_CHUNK_COUNT = true;
+    public static final boolean DEFAULT_EXPLAIN_ROW_COUNT = true;
     public static final boolean DEFAULT_ALLOW_ONLINE_TABLE_SCHEMA_UPDATE = true;
     public static final int DEFAULT_RETRIES_FOR_SCHEMA_UPDATE_CHECK = 10;
     public static final long DEFAULT_DELAY_FOR_SCHEMA_UPDATE_CHECK = 5 * 1000; // 5 seconds.
@@ -573,6 +575,11 @@ public class QueryServicesOptions {
         return this;
     }
 
+    public QueryServicesOptions setExplainRowCount(boolean showRowCount) {
+        config.setBoolean(EXPLAIN_ROW_COUNT_ATTRIB, showRowCount);
+        return this;
+    }
+
     public QueryServicesOptions setAllowOnlineSchemaUpdate(boolean allow) {
         config.setBoolean(ALLOW_ONLINE_TABLE_SCHEMA_UPDATE, allow);
         return this;

http://git-wip-us.apache.org/repos/asf/phoenix/blob/c25f8879/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/DefaultStatisticsCollector.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/DefaultStatisticsCollector.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/DefaultStatisticsCollector.java
index cb6f5d4..b81d206 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/DefaultStatisticsCollector.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/DefaultStatisticsCollector.java
@@ -24,13 +24,11 @@ import java.util.Map;
 
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.Cell;
-import org.apache.hadoop.hbase.HRegionInfo;
 import org.apache.hadoop.hbase.KeyValue;
 import org.apache.hadoop.hbase.KeyValueUtil;
 import org.apache.hadoop.hbase.client.Mutation;
 import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
-import org.apache.hadoop.hbase.regionserver.HRegion;
 import org.apache.hadoop.hbase.regionserver.InternalScanner;
 import org.apache.hadoop.hbase.regionserver.Region;
 import org.apache.hadoop.hbase.regionserver.Store;
@@ -50,18 +48,15 @@ import com.google.common.collect.Maps;
 
 /**
  * A default implementation of the Statistics tracker that helps to collect stats like min key, max key and guideposts.
- * TODO: review timestamps used for stats. We support the user controlling the timestamps, so we should honor that with
- * timestamps for stats as well. The issue is for compaction, though. I don't know of a way for the user to specify any
- * timestamp for that. Perhaps best to use current time across the board for now.
  */
 class DefaultStatisticsCollector implements StatisticsCollector {
     private static final Logger logger = LoggerFactory.getLogger(DefaultStatisticsCollector.class);
+    private final Map<ImmutableBytesPtr, Pair<Long, GuidePostsInfoBuilder>> guidePostsInfoWriterMap = Maps.newHashMap();
+    private final StatisticsWriter statsWriter;
+    private final Pair<Long, GuidePostsInfoBuilder> cachedGps;
 
     private long guidepostDepth;
     private long maxTimeStamp = MetaDataProtocol.MIN_TABLE_TIMESTAMP;
-    private Map<ImmutableBytesPtr, Pair<Long, GuidePostsInfoBuilder>> guidePostsInfoWriterMap = Maps.newHashMap();
-    protected StatisticsWriter statsTable;
-    private Pair<Long, GuidePostsInfoBuilder> cachedGps = null;
 
     DefaultStatisticsCollector(RegionCoprocessorEnvironment env, String tableName, long clientTimeStamp, byte[] family,
             byte[] gp_width_bytes, byte[] gp_per_region_bytes) throws IOException {
@@ -87,12 +82,14 @@ class DefaultStatisticsCollector implements StatisticsCollector {
         }
         // Get the stats table associated with the current table on which the CP is
         // triggered
-        this.statsTable = StatisticsWriter.newWriter(env, tableName, clientTimeStamp);
+        this.statsWriter = StatisticsWriter.newWriter(env, tableName, clientTimeStamp);
         // in a compaction we know the one family ahead of time
         if (family != null) {
             ImmutableBytesPtr cfKey = new ImmutableBytesPtr(family);
             cachedGps = new Pair<Long, GuidePostsInfoBuilder>(0l, new GuidePostsInfoBuilder());
             guidePostsInfoWriterMap.put(cfKey, cachedGps);
+        } else {
+            cachedGps = null;
         }
     }
 
@@ -103,7 +100,7 @@ class DefaultStatisticsCollector implements StatisticsCollector {
 
     @Override
     public void close() throws IOException {
-        this.statsTable.close();
+        this.statsWriter.close();
     }
 
     @Override
@@ -129,9 +126,13 @@ class DefaultStatisticsCollector implements StatisticsCollector {
             // Delete statistics for a region if no guidepost is collected for that region during UPDATE STATISTICS
             // This will not impact a stats collection of single column family during compaction as
             // guidePostsInfoWriterMap cannot be empty in this case.
-            if (guidePostsInfoWriterMap.keySet().isEmpty()) {
+            if (cachedGps == null) {
                 for (Store store : region.getStores()) {
-                    statsTable.deleteStats(region, this, new ImmutableBytesPtr(store.getFamily().getName()), mutations);
+                    ImmutableBytesPtr cfKey = new ImmutableBytesPtr(store.getFamily().getName());
+                    if (!guidePostsInfoWriterMap.containsKey(cfKey)) {
+                        Pair<Long, GuidePostsInfoBuilder> emptyGps = new Pair<Long, GuidePostsInfoBuilder>(0l, new GuidePostsInfoBuilder());
+                        guidePostsInfoWriterMap.put(cfKey, emptyGps);
+                    }
                 }
             }
             for (ImmutableBytesPtr fam : guidePostsInfoWriterMap.keySet()) {
@@ -139,12 +140,12 @@ class DefaultStatisticsCollector implements StatisticsCollector {
                     if (logger.isDebugEnabled()) {
                         logger.debug("Deleting the stats for the region " + region.getRegionInfo());
                     }
-                    statsTable.deleteStats(region, this, fam, mutations);
+                    statsWriter.deleteStats(region, this, fam, mutations);
                 }
                 if (logger.isDebugEnabled()) {
                     logger.debug("Adding new stats for the region " + region.getRegionInfo());
                 }
-                statsTable.addStats(this, fam, mutations);
+                statsWriter.addStats(this, fam, mutations);
             }
         } catch (IOException e) {
             logger.error("Failed to update statistics table!", e);
@@ -153,7 +154,7 @@ class DefaultStatisticsCollector implements StatisticsCollector {
     }
 
     private void commitStats(List<Mutation> mutations) throws IOException {
-        statsTable.commitStats(mutations);
+        statsWriter.commitStats(mutations);
     }
 
     /**
@@ -212,7 +213,7 @@ class DefaultStatisticsCollector implements StatisticsCollector {
 
     protected InternalScanner getInternalScanner(RegionCoprocessorEnvironment env, InternalScanner internalScan,
             ImmutableBytesPtr family) {
-        return new StatisticsScanner(this, statsTable, env, internalScan, family);
+        return new StatisticsScanner(this, statsWriter, env, internalScan, family);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/phoenix/blob/c25f8879/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/GuidePostsInfo.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/GuidePostsInfo.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/GuidePostsInfo.java
index b8cc3f1..291cb6d 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/GuidePostsInfo.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/GuidePostsInfo.java
@@ -38,7 +38,7 @@ public class GuidePostsInfo {
      */
     private int maxLength;
     
-    public final static GuidePostsInfo EMPTY_GUIDEPOST = new GuidePostsInfo(new ArrayList<Long>(),
+    public final static GuidePostsInfo NO_GUIDEPOST = new GuidePostsInfo(new ArrayList<Long>(),
             new ImmutableBytesWritable(ByteUtil.EMPTY_BYTE_ARRAY), new ArrayList<Long>(), 0, 0);
 
     public int getMaxLength() {

http://git-wip-us.apache.org/repos/asf/phoenix/blob/c25f8879/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/GuidePostsInfoBuilder.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/GuidePostsInfoBuilder.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/GuidePostsInfoBuilder.java
index 2133349..10d59ee 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/GuidePostsInfoBuilder.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/GuidePostsInfoBuilder.java
@@ -24,7 +24,6 @@ import java.util.List;
 
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
 import org.apache.phoenix.util.ByteUtil;
-import org.apache.phoenix.util.PrefixByteCodec;
 import org.apache.phoenix.util.PrefixByteEncoder;
 import org.apache.phoenix.util.TrustedByteArrayOutputStream;
 
@@ -107,15 +106,10 @@ public class GuidePostsInfoBuilder {
         return addGuidePosts(new ImmutableBytesWritable(row), byteCount, rowCount);
     }
 
-    private void close() {
-        PrefixByteCodec.close(stream);
-    }
-
     public GuidePostsInfo build() {
         this.guidePosts.set(stream.getBuffer(), 0, stream.size());
         GuidePostsInfo guidePostsInfo = new GuidePostsInfo(this.byteCounts, this.guidePosts, this.rowCounts,
                 this.maxLength, this.guidePostsCount);
-        this.close();
         return guidePostsInfo;
     }
 

http://git-wip-us.apache.org/repos/asf/phoenix/blob/c25f8879/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/PTableStatsImpl.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/PTableStatsImpl.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/PTableStatsImpl.java
index dacc213..42e7bed 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/PTableStatsImpl.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/PTableStatsImpl.java
@@ -32,6 +32,7 @@ import org.apache.phoenix.util.PrefixByteCodec;
 import org.apache.phoenix.util.PrefixByteDecoder;
 import org.apache.phoenix.util.SizedUtil;
 
+import com.google.common.io.Closeables;
 import com.sun.istack.NotNull;
  
  /**
@@ -89,13 +90,13 @@ public class PTableStatsImpl implements PTableStats {
                     } catch (EOFException e) { // Ignore as this signifies we're done
 
                     } finally {
-                        PrefixByteCodec.close(stream);
+                        Closeables.closeQuietly(stream);
                     }
                     buf.setLength(buf.length() - 1);
                 }
                 buf.append(")");
             } finally {
-                PrefixByteCodec.close(stream);
+                Closeables.closeQuietly(stream);
             }
         }
         buf.append("]");

http://git-wip-us.apache.org/repos/asf/phoenix/blob/c25f8879/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/StatisticsUtil.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/StatisticsUtil.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/StatisticsUtil.java
index 5b47104..5e03be5 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/StatisticsUtil.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/StatisticsUtil.java
@@ -62,15 +62,19 @@ public class StatisticsUtil {
 
     public static byte[] getRowKey(byte[] table, ImmutableBytesPtr fam, ImmutableBytesWritable guidePostStartKey) {
         // always starts with the source table
-        byte[] rowKey = new byte[table.length + fam.getLength() + guidePostStartKey.getLength() + 2];
+        int guidePostLength = guidePostStartKey.getLength();
+        boolean hasGuidePost = guidePostLength > 0;
+        byte[] rowKey = new byte[table.length + fam.getLength() + guidePostLength + (hasGuidePost ? 2 : 1)];
         int offset = 0;
         System.arraycopy(table, 0, rowKey, offset, table.length);
         offset += table.length;
         rowKey[offset++] = QueryConstants.SEPARATOR_BYTE; // assumes stats table columns not DESC
         System.arraycopy(fam.get(), fam.getOffset(), rowKey, offset, fam.getLength());
-        offset += fam.getLength();
-        rowKey[offset++] = QueryConstants.SEPARATOR_BYTE; // assumes stats table columns not DESC
-        System.arraycopy(guidePostStartKey.get(), 0, rowKey, offset, guidePostStartKey.getLength());
+        if (hasGuidePost) {
+            offset += fam.getLength();
+            rowKey[offset++] = QueryConstants.SEPARATOR_BYTE; // assumes stats table columns not DESC
+            System.arraycopy(guidePostStartKey.get(), 0, rowKey, offset, guidePostLength);
+        }
         return rowKey;
     }
 
@@ -215,9 +219,12 @@ public class StatisticsUtil {
     }
 
 	public static byte[] getGuidePostsInfoFromRowKey(byte[] tableNameBytes, byte[] fam, byte[] row) {
-		ImmutableBytesWritable ptr = new ImmutableBytesWritable();
-		int gpOffset = tableNameBytes.length + 1 + fam.length + 1;
-		ptr.set(row, gpOffset, row.length - gpOffset);
-		return ByteUtil.copyKeyBytesIfNecessary(ptr);
+	    if (row.length > tableNameBytes.length + 1 + fam.length) {
+    		ImmutableBytesWritable ptr = new ImmutableBytesWritable();
+    		int gpOffset = tableNameBytes.length + 1 + fam.length + 1;
+    		ptr.set(row, gpOffset, row.length - gpOffset);
+    		return ByteUtil.copyKeyBytesIfNecessary(ptr);
+	    }
+	    return ByteUtil.EMPTY_BYTE_ARRAY;
 	}
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/phoenix/blob/c25f8879/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/StatisticsWriter.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/StatisticsWriter.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/StatisticsWriter.java
index f8c888d..a727c1c 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/StatisticsWriter.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/StatisticsWriter.java
@@ -49,7 +49,6 @@ import org.apache.phoenix.query.QueryConstants;
 import org.apache.phoenix.schema.types.PDate;
 import org.apache.phoenix.schema.types.PLong;
 import org.apache.phoenix.util.ByteUtil;
-import org.apache.phoenix.util.PrefixByteCodec;
 import org.apache.phoenix.util.PrefixByteDecoder;
 import org.apache.phoenix.util.ServerUtil;
 import org.apache.phoenix.util.TimeKeeper;
@@ -143,33 +142,46 @@ public class StatisticsWriter implements Closeable {
             List<Long> byteCounts = gps.getByteCounts();
             List<Long> rowCounts = gps.getRowCounts();
             ImmutableBytesWritable keys = gps.getGuidePosts();
-            ByteArrayInputStream stream = new ByteArrayInputStream(keys.get(), keys.getOffset(), keys.getLength());
-            DataInput input = new DataInputStream(stream);
-            PrefixByteDecoder decoder = new PrefixByteDecoder(gps.getMaxLength());
-            int guidePostCount = 0;
-            try {
-                while (true) {
-                    ImmutableBytesWritable ptr = decoder.decode(input);
-                    byte[] prefix = StatisticsUtil.getRowKey(tableName, cfKey, ptr);
-                    Put put = new Put(prefix);
-                    put.add(QueryConstants.DEFAULT_COLUMN_FAMILY_BYTES, PhoenixDatabaseMetaData.GUIDE_POSTS_WIDTH_BYTES,
-                            timeStamp, PLong.INSTANCE.toBytes(byteCounts.get(guidePostCount)));
-                    put.add(QueryConstants.DEFAULT_COLUMN_FAMILY_BYTES,
-                            PhoenixDatabaseMetaData.GUIDE_POSTS_ROW_COUNT_BYTES, timeStamp,
-                            PLong.INSTANCE.toBytes(rowCounts.get(guidePostCount)));
-                    // Add our empty column value so queries behave correctly
-                    put.add(QueryConstants.DEFAULT_COLUMN_FAMILY_BYTES, QueryConstants.EMPTY_COLUMN_BYTES, timeStamp,
-                            ByteUtil.EMPTY_BYTE_ARRAY);
-                    mutations.add(put);
-                    guidePostCount++;
-                }
-            } catch (EOFException e) { // Ignore as this signifies we're done
+            boolean hasGuidePosts = keys.getLength() > 0;
+            if (hasGuidePosts) {
+                int guidePostCount = 0;
+                try (ByteArrayInputStream stream = new ByteArrayInputStream(keys.get(), keys.getOffset(), keys.getLength())) {
+                    DataInput input = new DataInputStream(stream);
+                    PrefixByteDecoder decoder = new PrefixByteDecoder(gps.getMaxLength());
+                    do {
+                        ImmutableBytesWritable ptr = decoder.decode(input);
+                        addGuidepost(cfKey, mutations, ptr, byteCounts.get(guidePostCount), rowCounts.get(guidePostCount), timeStamp);
+                        guidePostCount++;
+                    } while (decoder != null);
+                } catch (EOFException e) { // Ignore as this signifies we're done
 
-            } finally {
-                PrefixByteCodec.close(stream);
+                }
+                // If we've written guideposts with a guidepost key, then delete the
+                // empty guidepost indicator that may have been written by other
+                // regions.
+                byte[] rowKey = StatisticsUtil.getRowKey(tableName, cfKey, ByteUtil.EMPTY_IMMUTABLE_BYTE_ARRAY);
+                Delete delete = new Delete(rowKey, timeStamp);
+                mutations.add(delete);
+            } else {
+                addGuidepost(cfKey, mutations, ByteUtil.EMPTY_IMMUTABLE_BYTE_ARRAY, 0, 0, timeStamp);
             }
         }
     }
+    
+    @SuppressWarnings("deprecation")
+    private void addGuidepost(ImmutableBytesPtr cfKey, List<Mutation> mutations, ImmutableBytesWritable ptr, long byteCount, long rowCount, long timeStamp) {
+        byte[] prefix = StatisticsUtil.getRowKey(tableName, cfKey, ptr);
+        Put put = new Put(prefix);
+        put.add(QueryConstants.DEFAULT_COLUMN_FAMILY_BYTES, PhoenixDatabaseMetaData.GUIDE_POSTS_WIDTH_BYTES,
+                timeStamp, PLong.INSTANCE.toBytes(byteCount));
+        put.add(QueryConstants.DEFAULT_COLUMN_FAMILY_BYTES,
+                PhoenixDatabaseMetaData.GUIDE_POSTS_ROW_COUNT_BYTES, timeStamp,
+                PLong.INSTANCE.toBytes(rowCount));
+        // Add our empty column value so queries behave correctly
+        put.add(QueryConstants.DEFAULT_COLUMN_FAMILY_BYTES, QueryConstants.EMPTY_COLUMN_BYTES, timeStamp,
+                ByteUtil.EMPTY_BYTE_ARRAY);
+        mutations.add(put);
+    }
 
     private static MutationType getMutationType(Mutation m) throws IOException {
         if (m instanceof Put) {

http://git-wip-us.apache.org/repos/asf/phoenix/blob/c25f8879/phoenix-core/src/main/java/org/apache/phoenix/util/PrefixByteCodec.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/util/PrefixByteCodec.java b/phoenix-core/src/main/java/org/apache/phoenix/util/PrefixByteCodec.java
index 8c3aa80..2641b6a 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/util/PrefixByteCodec.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/util/PrefixByteCodec.java
@@ -18,7 +18,6 @@
 package org.apache.phoenix.util;
 
 import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
 import java.io.DataInput;
 import java.io.DataInputStream;
 import java.io.DataOutput;
@@ -52,15 +51,15 @@ public class PrefixByteCodec {
     }
     
     public static int encodeBytes(List<byte[]> listOfBytes, ImmutableBytesWritable ptr) throws IOException {
-        TrustedByteArrayOutputStream stream = new TrustedByteArrayOutputStream(calculateSize(listOfBytes));
-        DataOutput output = new DataOutputStream(stream);
-        PrefixByteEncoder encoder = new PrefixByteEncoder();
-        for (byte[] bytes : listOfBytes) {
-            encoder.encode(output, bytes, 0, bytes.length);
+        try (TrustedByteArrayOutputStream stream = new TrustedByteArrayOutputStream(calculateSize(listOfBytes))) {
+            DataOutput output = new DataOutputStream(stream);
+            PrefixByteEncoder encoder = new PrefixByteEncoder();
+            for (byte[] bytes : listOfBytes) {
+                encoder.encode(output, bytes, 0, bytes.length);
+            }
+            ptr.set(stream.getBuffer(), 0, stream.size());
+            return encoder.getMaxLength();
         }
-        close(stream);
-        ptr.set(stream.getBuffer(), 0, stream.size());
-        return encoder.getMaxLength();
     }
     
     public static int calculateSize(List<byte[]> listOfBytes) {
@@ -81,24 +80,4 @@ public class PrefixByteCodec {
             throw new RuntimeException(e);
         }
     }
-
-    public static void close(ByteArrayInputStream stream) {
-        if (stream != null) {
-            try {
-                stream.close();
-            } catch (IOException e) {
-                throw new RuntimeException(e);
-            }
-        }
-    }
-
-    public static void close(ByteArrayOutputStream stream) {
-        if (stream != null) {
-            try {
-                stream.close();
-            } catch (IOException e) {
-                throw new RuntimeException(e);
-            }
-        }
-    }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/phoenix/blob/c25f8879/phoenix-core/src/test/java/org/apache/phoenix/query/QueryServicesTestImpl.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/query/QueryServicesTestImpl.java b/phoenix-core/src/test/java/org/apache/phoenix/query/QueryServicesTestImpl.java
index 29a7001..6ae655c 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/query/QueryServicesTestImpl.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/query/QueryServicesTestImpl.java
@@ -54,6 +54,7 @@ public final class QueryServicesTestImpl extends BaseQueryServicesImpl {
     public static final long DEFAULT_MAX_CLIENT_METADATA_CACHE_SIZE =  1024L*1024L*2L; // 2 Mb
     public static final int DEFAULT_MIN_STATS_UPDATE_FREQ_MS = 0;
     public static final boolean DEFAULT_EXPLAIN_CHUNK_COUNT = false; // TODO: update explain plans in test and set to true
+    public static final boolean DEFAULT_EXPLAIN_ROW_COUNT = false; // TODO: update explain plans in test and set to true
     public static final String DEFAULT_EXTRA_JDBC_ARGUMENTS = PhoenixRuntime.PHOENIX_TEST_DRIVER_URL_PARAM;
     private static final boolean DEFAULT_RUN_UPDATE_STATS_ASYNC = false;
     private static final boolean DEFAULT_COMMIT_STATS_ASYNC = false;
@@ -80,6 +81,7 @@ public final class QueryServicesTestImpl extends BaseQueryServicesImpl {
     private static QueryServicesOptions getDefaultServicesOptions() {
     	return withDefaults()
     	        .setExplainChunkCount(DEFAULT_EXPLAIN_CHUNK_COUNT)
+                .setExplainRowCount(DEFAULT_EXPLAIN_ROW_COUNT)
     	        .setSequenceSaltBuckets(DEFAULT_SEQUENCE_TABLE_SALT_BUCKETS)
                 .setMinStatsUpdateFrequencyMs(DEFAULT_MIN_STATS_UPDATE_FREQ_MS)
                 .setThreadPoolSize(DEFAULT_THREAD_POOL_SIZE)