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

tdsilva pushed a commit to branch phoenix-stats
in repository https://gitbox.apache.org/repos/asf/phoenix.git


The following commit(s) were added to refs/heads/phoenix-stats by this push:
     new ef6dcb5  Phoenix-Stats Initial commit and provide utility functions.
     new f681949  Merge pull request #463 from dbwong/phoenix-stats
ef6dcb5 is described below

commit ef6dcb5ece73638ef007be0aac7b24f19f65e01a
Author: Daniel Wong <[email protected]>
AuthorDate: Tue Mar 12 19:32:38 2019 -0700

    Phoenix-Stats Initial commit and provide utility functions.
---
 .../org/apache/phoenix/compile/ScanRanges.java     | 123 ++++++
 .../phoenix/iterate/BaseResultIterators.java       | 140 ++++---
 .../phoenix/compile/ScanRangesIntersectTest.java   | 438 ++++++++++++++++++++-
 .../org/apache/phoenix/query/KeyRangeMoreTest.java |  14 +
 4 files changed, 651 insertions(+), 64 deletions(-)

diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/compile/ScanRanges.java 
b/phoenix-core/src/main/java/org/apache/phoenix/compile/ScanRanges.java
index 2a7dbb4..c802678 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/ScanRanges.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/ScanRanges.java
@@ -694,4 +694,127 @@ public class ScanRanges {
         return rowTimestampRange;
     }
 
+    /**
+     * Produces the list of KeyRanges representing the fully qualified row key 
by calling into ScanUtil.setKey
+     * repeatedly for every combination of KeyRanges in the ranges field. The 
bounds will be set according to the
+     * properties of the setKey method.
+     * 
+     * @return list of KeyRanges representing the fully qualified rowkey, 
coalesced
+     */
+    public List<KeyRange> getRowKeyRanges() {
+        // If its scanRanges are everything or nothing, then we short circuit 
and leave
+        // as schema is not filled in
+        if (isEverything()) { return 
Lists.newArrayList(KeyRange.EVERYTHING_RANGE); }
+        if (isDegenerate()) { return Lists.newArrayList(KeyRange.EMPTY_RANGE); 
}
+
+        List<KeyRange> queryRowKeyRanges = 
Lists.newArrayListWithExpectedSize(this.ranges.size());
+
+
+        // If scanRanges.ranges has no information then should be in the 
scanRanges.scanRange
+        if (ranges.size() == 0) {
+            queryRowKeyRanges.add(this.getScanRange());
+        } else { // We have a composite key need the row key from the 
combination
+            // make a copy of ranges as we may add items to fully qualify our 
rowkey
+            List<List<KeyRange>> newRanges = new ArrayList<>(this.getRanges());
+
+            int[] slotSpans = this.getSlotSpans();
+
+            // If the ranges here do not qualify all the keys then those keys 
are unbound
+            if (newRanges.size() < schema.getMaxFields()) {
+                int originalSize = newRanges.size();
+                for (int i = 0; i < schema.getMaxFields() - originalSize; i++) 
{
+                    
newRanges.add(Lists.newArrayList(KeyRange.EVERYTHING_RANGE));
+                }
+                slotSpans = new int[schema.getMaxFields()];
+                System.arraycopy(this.getSlotSpans(), 0, slotSpans, 0, 
this.getSlotSpans().length);
+            }
+
+            // Product to bound our counting loop for safety
+            int rangesPermutationsCount = 1;
+            for (int i = 0; i < newRanges.size(); i++) {
+                rangesPermutationsCount *= newRanges.get(i).size();
+            }
+
+            // Have to construct the intersection
+            List<KeyRange> expandedRanges = 
Lists.newArrayListWithCapacity(rangesPermutationsCount);
+            int[] positions = new int[slotSpans.length];
+
+            int maxKeyLength = SchemaUtil.getMaxKeyLength(schema, newRanges);
+            byte[] keyBuffer = new byte[maxKeyLength];
+
+            // Counting Loop
+            int count = 0;
+            while (count < rangesPermutationsCount) {
+                byte[] lowerBound;
+                byte[] upperBound;
+
+                // Note ScanUtil.setKey internally handles the upper/lower 
exclusive from a Scan
+                // point of view. It would be good to break it out in the 
future for rowkey
+                // construction vs ScanKey construction To handle differences 
between
+                // hbase 2 and hbase 1 scan boundaries
+                int result = ScanUtil.setKey(schema, newRanges, slotSpans, 
positions, KeyRange.Bound.LOWER, keyBuffer,
+                        0, 0, slotSpans.length);
+
+                if (result < 0) {
+                    // unbound
+                    lowerBound = KeyRange.UNBOUND;
+                } else {
+                    lowerBound = Arrays.copyOf(keyBuffer, result);
+                }
+
+                result = ScanUtil.setKey(schema, newRanges, slotSpans, 
positions, KeyRange.Bound.UPPER, keyBuffer, 0, 0,
+                        slotSpans.length);
+
+                if (result < 0) {
+                    // unbound
+                    upperBound = KeyRange.UNBOUND;
+                } else {
+                    upperBound = Arrays.copyOf(keyBuffer, result);
+                }
+
+
+                /*
+                 * This is already considered inside of ScanUtil.setKey we may 
want to refactor to pull these out.
+                 * range/single    boundary       bound      increment
+                 *  range          inclusive      lower         no
+                 *  range          inclusive      upper         yes, at the 
end if occurs at any slots.
+                 *  range          exclusive      lower         yes
+                 *  range          exclusive      upper         no
+                 *  single         inclusive      lower         no
+                 *  single         inclusive      upper         yes, at the 
end if it is the last slots.
+                 */
+
+                boolean lowerInclusive = true;
+                // Don't send a null range, send an empty range.
+                if (lowerBound.length == 0 && upperBound.length == 0) {
+                    lowerInclusive = false;
+                }
+
+                KeyRange keyRange = KeyRange.getKeyRange(lowerBound, 
lowerInclusive, upperBound, false);
+                expandedRanges.add(keyRange);
+
+                // update position
+                // This loops through all settings of each of the primary keys 
by counting from
+                // the trailing edge based on the number of settings of that 
key.
+                int i;
+                for (i = positions.length - 1; i >= 0; i--) {
+                    if (positions[i] < newRanges.get(i).size() - 1) {
+                        positions[i]++;
+                        break;
+                    } else {
+                        positions[i] = 0;
+                    }
+                }
+
+                if (i < 0) {
+                    break;
+                }
+                count++;
+            }
+            queryRowKeyRanges.addAll(expandedRanges);
+        }
+
+        return queryRowKeyRanges;
+    }
+
 }
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 34e1f89..07f5e09 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
@@ -138,7 +138,7 @@ import com.google.common.collect.Lists;
  * Class that parallelizes the scan over a table using the ExecutorService 
provided.  Each region of the table will be scanned in parallel with
  * the results accessible through {@link #getIterators()}
  *
- * 
+ *
  * @since 0.1
  */
 public abstract class BaseResultIterators extends ExplainTable implements 
ResultIterators {
@@ -162,20 +162,13 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
     private final boolean useStatsForParallelization;
     protected Map<ImmutableBytesPtr,ServerCache> caches;
     private final QueryPlan dataPlan;
-    
-    static final Function<HRegionLocation, KeyRange> TO_KEY_RANGE = new 
Function<HRegionLocation, KeyRange>() {
-        @Override
-        public KeyRange apply(HRegionLocation region) {
-            return KeyRange.getKeyRange(region.getRegionInfo().getStartKey(), 
region.getRegionInfo().getEndKey());
-        }
-    };
 
     private PTable getTable() {
         return plan.getTableRef().getTable();
     }
-    
+
     abstract protected boolean isSerial();
-    
+
     protected boolean useStats() {
         /*
          * Don't use guide posts:
@@ -188,7 +181,7 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
         }
         return !isSerial();
     }
-    
+
     private static void initializeScan(QueryPlan plan, Integer perScanLimit, 
Integer offset, Scan scan) throws SQLException {
         StatementContext context = plan.getContext();
         TableRef tableRef = plan.getTableRef();
@@ -232,7 +225,7 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
                     optimizeProjection = true;
                     if (projector.projectEveryRow()) {
                         if (table.getViewType() == ViewType.MAPPED) {
-                            // Since we don't have the empty key value in 
MAPPED tables, 
+                            // Since we don't have the empty key value in 
MAPPED tables,
                             // we must project all CFs in HRS. However, only 
the
                             // selected column values are returned back to 
client.
                             context.getWhereConditionColumns().clear();
@@ -258,7 +251,7 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
             if (perScanLimit != null) {
                 ScanUtil.andFilterAtEnd(scan, new PageFilter(perScanLimit));
             }
-            
+
             if(offset!=null){
                 ScanUtil.addOffsetAttribute(scan, offset);
             }
@@ -268,7 +261,7 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
                 cols < 
plan.getTableRef().getTable().getRowKeySchema().getFieldCount() &&
                 plan.getGroupBy().isOrderPreserving() &&
                 (context.getAggregationManager().isEmpty() || 
plan.getGroupBy().isUngroupedAggregate())) {
-                
+
                 ScanUtil.andFilterAtEnd(scan,
                         new 
DistinctPrefixFilter(plan.getTableRef().getTable().getRowKeySchema(),
                                 cols));
@@ -290,7 +283,7 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
             }
         }
     }
-    
+
     private static void setQualifierRanges(boolean keyOnlyFilter, PTable 
table, Scan scan,
             StatementContext context) throws SQLException {
         if (EncodedColumnsUtil.useEncodedQualifierListOptimization(table, 
scan)) {
@@ -328,7 +321,7 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
                                 EncodedColumnsUtil.getEmptyKeyValueInfo(table);
                         qualifierSet.add(emptyKeyValueInfo.getFirst());
                     }
-                    // In case of a keyOnlyFilter, we only need to project the 
+                    // In case of a keyOnlyFilter, we only need to project the
                     // empty key value column
                     if (!keyOnlyFilter) {
                         Pair<Integer, Integer> qualifierRangeForFamily =
@@ -366,11 +359,11 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
             }
         }
     }
-    
+
     private static void optimizeProjection(StatementContext context, Scan 
scan, PTable table, FilterableStatement statement) {
         Map<byte[], NavigableSet<byte[]>> familyMap = scan.getFamilyMap();
         // columnsTracker contain cf -> qualifiers which should get returned.
-        Map<ImmutableBytesPtr, NavigableSet<ImmutableBytesPtr>> columnsTracker 
= 
+        Map<ImmutableBytesPtr, NavigableSet<ImmutableBytesPtr>> columnsTracker 
=
                 new TreeMap<ImmutableBytesPtr, 
NavigableSet<ImmutableBytesPtr>>();
         Set<byte[]> conditionOnlyCfs = new 
TreeSet<byte[]>(Bytes.BYTES_COMPARATOR);
         int referencedCfCount = familyMap.size();
@@ -452,7 +445,7 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
                     // cols is null means the whole CF will get scanned.
                     if (cols != null) {
                         if (whereCol.getSecond() == null) {
-                            scan.addFamily(family);                            
+                            scan.addFamily(family);
                         } else {
                             scan.addColumn(family, whereCol.getSecond());
                         }
@@ -477,13 +470,13 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
             // in the scan in this case. We still want the other optimization 
that causes
             // the ExplicitColumnTracker not to be used, though.
             if (!statement.isAggregate() && filteredColumnNotInProjection) {
-                ScanUtil.andFilterAtEnd(scan, 
+                ScanUtil.andFilterAtEnd(scan,
                     trackedColumnsBitset != null ? new 
EncodedQualifiersColumnProjectionFilter(SchemaUtil.getEmptyColumnFamily(table), 
trackedColumnsBitset, conditionOnlyCfs, table.getEncodingScheme()) : new 
ColumnProjectionFilter(SchemaUtil.getEmptyColumnFamily(table),
                         columnsTracker, conditionOnlyCfs, 
EncodedColumnsUtil.usesEncodedColumnNames(table.getEncodingScheme())));
             }
         }
     }
-    
+
     public BaseResultIterators(QueryPlan plan, Integer perScanLimit, Integer 
offset, ParallelScanGrouper scanGrouper, Scan scan, 
Map<ImmutableBytesPtr,ServerCache> caches, QueryPlan dataPlan) throws 
SQLException {
         super(plan.getContext(), plan.getTableRef(), plan.getGroupBy(), 
plan.getOrderBy(),
                 plan.getStatement().getHint(), 
QueryUtil.getOffsetLimit(plan.getLimit(), plan.getOffset()), offset);
@@ -505,7 +498,7 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
         }
         // Used to tie all the scans together during logging
         scanId = new UUID(ThreadLocalRandom.current().nextLong(), 
ThreadLocalRandom.current().nextLong()).toString();
-        
+
         initializeScan(plan, perScanLimit, offset, scan);
         this.useStatsForParallelization = 
PhoenixConfigurationUtil.getStatsForParallelizationProp(context.getConnection(),
 table);
         this.scans = getParallelScans();
@@ -550,7 +543,34 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
         }
         return ranges;
     }
-    
+
+    /**
+     * @return List of KeyRanges to represent each region
+     * @throws SQLException
+     */
+    private List<KeyRange> getRegionRowKeyRanges() throws SQLException {
+        List<HRegionLocation> regionLocations = 
getRegionBoundaries(scanGrouper); // Load the region information
+
+        List<KeyRange> regionKeyRanges = 
Lists.newArrayListWithCapacity(regionLocations.size());
+
+        // Map each HRegionLocation to a KeyRange - no Java 8
+        for (int i = 0; i < regionLocations.size(); i++) {
+            HRegionLocation regionLocation = regionLocations.get(i);
+            HRegionInfo regionInfo = regionLocation.getRegionInfo();
+
+            byte[] startKey = regionInfo.getStartKey();
+            byte[] endKey = regionInfo.getEndKey();
+
+            // Region is lowerInclusive true by definition
+            // Region is upperInclusive false by definition
+            // Unless it returns HConstants.EMPTY_BYTE_ARRAY which indicates 
it is unbound
+            KeyRange range = KeyRange.getKeyRange(startKey, endKey);
+
+            regionKeyRanges.add(range);
+        }
+        return regionKeyRanges;
+    }
+
     private static int getIndexContainingInclusive(List<byte[]> boundaries, 
byte[] inclusiveKey) {
         int guideIndex = Collections.binarySearch(boundaries, inclusiveKey, 
Bytes.BYTES_COMPARATOR);
         // If we found an exact match, return the index+1, as the inclusiveKey 
will be contained
@@ -558,7 +578,7 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
         guideIndex = (guideIndex < 0 ? -(guideIndex + 1) : (guideIndex + 1));
         return guideIndex;
     }
-    
+
     private static int getIndexContainingExclusive(List<byte[]> boundaries, 
byte[] exclusiveKey) {
         int guideIndex = Collections.binarySearch(boundaries, exclusiveKey, 
Bytes.BYTES_COMPARATOR);
         // If we found an exact match, return the index we found as the 
exclusiveKey won't be
@@ -613,7 +633,7 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
                 Math.min(estimate.lastUpdated,
                     gps.getGuidePostTimestamps()[guideIndex]);
     }
-    
+
     private List<Scan> addNewScan(List<List<Scan>> parallelScans, List<Scan> 
scans, Scan scan,
             byte[] startKey, boolean crossedRegionBoundary, HRegionLocation 
regionLocation) {
         boolean startNewScan = scanGrouper.shouldStartNewScan(plan, scans, 
startKey, crossedRegionBoundary);
@@ -730,7 +750,7 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
             // on PK column types (namely that you can only have a fixed width 
nullable
             // column as your last column), the type check is more of a sanity 
check
             // since it wouldn't make sense to have an index with every column 
in common.
-            if (indexColumn.getDataType() == dataColumn.getDataType() 
+            if (indexColumn.getDataType() == dataColumn.getDataType()
                     && 
dataColumnName.equals(IndexUtil.getDataColumnName(indexColumnName))) {
                 nColumnsInCommon++;
                 continue;
@@ -739,13 +759,13 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
         }
         return nColumnsInCommon;
     }
-     
+
     // public for testing
     public static ScanRanges computePrefixScanRanges(ScanRanges 
dataScanRanges, int nColumnsInCommon) {
         if (nColumnsInCommon == 0) {
             return ScanRanges.EVERYTHING;
         }
-        
+
         int offset = 0;
         List<List<KeyRange>> cnf = 
Lists.newArrayListWithExpectedSize(nColumnsInCommon);
         int[] slotSpan = new int[nColumnsInCommon];
@@ -784,7 +804,7 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
         ScanRanges commonScanRanges = 
ScanRanges.create(dataScanRanges.getSchema(), cnf, slotSpan, null, useSkipScan, 
-1);
         return commonScanRanges;
     }
-        
+
     /**
      * Truncates range to be a max of rangeSpan fields
      * @param schema row key schema
@@ -823,7 +843,7 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
                 newRange = true;
             }
         }
-        
+
         return newRange ? KeyRange.getKeyRange(lowerRange, lowerInclusive, 
upperRange, upperInclusive) : range;
     }
 
@@ -864,7 +884,7 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
         // return true if we've clipped the rowKey
         return maxOffset != offset;
     }
-    
+
     /**
      * Compute the list of parallel scans to run for a given query. The inner 
scans
      * may be concatenated together directly, while the other ones may need to 
be
@@ -899,7 +919,7 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
         hasGuidePosts = gps != GuidePostsInfo.NO_GUIDEPOST;
         // Case when stats collection did run but there possibly wasn't enough 
data. In such a
         // case we generate an empty guide post with the byte estimate being 
set as guide post
-        // width. 
+        // width.
         boolean emptyGuidePost = gps.isEmptyGuidePost();
         byte[] startRegionBoundaryKey = startKey;
         byte[] stopRegionBoundaryKey = stopKey;
@@ -930,7 +950,7 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
                 stopRegionBoundaryKey = stopKey = scanStopRow;
             }
         }
-        
+
         int regionIndex = 0;
         int startRegionIndex = 0;
         int stopIndex = regionBoundaries.size();
@@ -944,9 +964,9 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
             }
         }
         List<List<Scan>> parallelScans = 
Lists.newArrayListWithExpectedSize(stopIndex - regionIndex + 1);
-        
+
         ImmutableBytesWritable currentKey = new 
ImmutableBytesWritable(startKey);
-        
+
         int gpsSize = gps.getGuidePostsCount();
         int estGuidepostsPerRegion = gpsSize == 0 ? 1 : gpsSize / 
regionLocations.size() + 1;
         int keyOffset = 0;
@@ -1099,14 +1119,14 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
                 // may not have been entered if our scan end key is smaller 
than the
                 // first guide post in that region).
                 boolean gpsAfterStopKey = false;
-                gpsAvailableForAllRegions &= 
+                gpsAvailableForAllRegions &=
                     ( gpsInThisRegion && everNotDelayed) || // GP in this 
region
                     ( regionIndex == startRegionIndex && gpsForFirstRegion ) 
|| // GP in first region (before start key)
                     ( gpsAfterStopKey = ( regionIndex == stopIndex && 
intersectWithGuidePosts && // GP in last region (after stop key)
                             ( endRegionKey.length == 0 || // then check if gp 
is in the region
-                            currentGuidePost.compareTo(endRegionKey) < 0)  ) 
);            
+                            currentGuidePost.compareTo(endRegionKey) < 0)  ) );
                 if (gpsAfterStopKey) {
-                    // If gp after stop key, but still in last region, track 
min ts as fallback 
+                    // If gp after stop key, but still in last region, track 
min ts as fallback
                     fallbackTs =
                             Math.min(fallbackTs,
                                 gps.getGuidePostTimestamps()[guideIndex]);
@@ -1174,7 +1194,7 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
         return pageLimit;
     }
 
-    private static Long computeMinTimestamp(boolean gpsAvailableForAllRegions, 
+    private static Long computeMinTimestamp(boolean gpsAvailableForAllRegions,
             GuidePostEstimate estimates,
             long fallbackTs) {
         if (gpsAvailableForAllRegions) {
@@ -1189,19 +1209,19 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
     }
 
     /**
-     * Loop through List<List<Scan>> parallelScans object, 
+     * Loop through List<List<Scan>> parallelScans object,
      * rolling dice on each scan based on startRowKey.
-     * 
-     * All FilterableStatement should have tableSamplingRate. 
-     * In case it is delete statement, an unsupported message is raised. 
+     *
+     * All FilterableStatement should have tableSamplingRate.
+     * In case it is delete statement, an unsupported message is raised.
      * In case it is null tableSamplingRate, 100% sampling rate will be 
applied by default.
-     *  
+     *
      * @param parallelScans
      */
     private void sampleScans(final List<List<Scan>> parallelScans, final 
Double tableSamplingRate){
        if(tableSamplingRate==null||tableSamplingRate==100d) return;
        final Predicate<byte[]> 
tableSamplerPredicate=TableSamplerPredicate.of(tableSamplingRate);
-       
+
        for(Iterator<List<Scan>> is = parallelScans.iterator();is.hasNext();){
                for(Iterator<Scan> i=is.next().iterator();i.hasNext();){
                        final Scan scan=i.next();
@@ -1209,14 +1229,14 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
                }
        }
     }
-   
+
     public static <T> List<T> reverseIfNecessary(List<T> list, boolean 
reverse) {
         if (!reverse) {
             return list;
         }
         return Lists.reverse(list);
     }
-    
+
     /**
      * Executes the scan in parallel across all regions, blocking until all 
scans are complete.
      * @return the result iterators for the scan of each region
@@ -1282,7 +1302,7 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
                     try {
                         long timeOutForScan = maxQueryEndTime - 
EnvironmentEdgeManager.currentTimeMillis();
                         if (timeOutForScan < 0) {
-                            throw new 
SQLExceptionInfo.Builder(SQLExceptionCode.OPERATION_TIMED_OUT).setMessage(". 
Query couldn't be completed in the alloted time: " + queryTimeOut + " 
ms").build().buildException(); 
+                            throw new 
SQLExceptionInfo.Builder(SQLExceptionCode.OPERATION_TIMED_OUT).setMessage(". 
Query couldn't be completed in the alloted time: " + queryTimeOut + " 
ms").build().buildException();
                         }
                         // make sure we apply the iterators in order
                         if (isLocalIndex && previousScan != null && 
previousScan.getScan() != null
@@ -1290,7 +1310,7 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
                                         previousScan.getScan().getStopRow()) < 
0)
                                 || (isReverse && 
previousScan.getScan().getStopRow().length > 0 && 
Bytes.compareTo(scanPair.getFirst().getAttribute(SCAN_ACTUAL_START_ROW),
                                         previousScan.getScan().getStopRow()) > 
0)
-                                || 
(Bytes.compareTo(scanPair.getFirst().getStopRow(), 
previousScan.getScan().getStopRow()) == 0)) 
+                                || 
(Bytes.compareTo(scanPair.getFirst().getStopRow(), 
previousScan.getScan().getStopRow()) == 0))
                                     && 
Bytes.compareTo(scanPair.getFirst().getAttribute(SCAN_START_ROW_SUFFIX), 
previousScan.getScan().getAttribute(SCAN_START_ROW_SUFFIX))==0)) {
                             continue;
                         }
@@ -1335,7 +1355,7 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
                                             iterators, isReverse, 
maxQueryEndTime, previousScan,
                                             clearedCache, concatIterators, 
scanPairItr, scanPair, retryCount);
                             }
-                            
+
                         }
                     }
                 }
@@ -1410,11 +1430,11 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
                 maxQueryEndTime, newNestedScans.size(), previousScan, 
retryCount);
         return concatIterators;
     }
-    
+
 
     @Override
     public void close() throws SQLException {
-       
+
         // Don't call cancel on already started work, as it causes the 
HConnection
         // to get into a funk. Instead, just cancel queued work.
         boolean cancelledWork = false;
@@ -1470,7 +1490,7 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
             if (plan.useRoundRobinIterator()) {
                 /*
                  * When using a round robin iterator we shouldn't concatenate 
the iterators together. This is because a
-                 * round robin iterator should be calling next() on these 
iterators directly after selecting them in a 
+                 * round robin iterator should be calling next() on these 
iterators directly after selecting them in a
                  * round robin fashion. This helps take advantage of loading 
the underlying scanners' caches in parallel
                  * as well as preventing errors arising out of scanner lease 
expirations.
                  */
@@ -1487,7 +1507,7 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
        private final Scan scan;
        private final boolean isFirstScan;
        private final boolean isLastScan;
-       
+
        public ScanLocator(Scan scan, int outerListIndex, int innerListIndex, 
boolean isFirstScan, boolean isLastScan) {
                this.outerListIndex = outerListIndex;
                this.innerListIndex = innerListIndex;
@@ -1511,12 +1531,12 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
            return isLastScan;
        }
     }
-    
 
-    abstract protected String getName();    
+
+    abstract protected String getName();
     abstract protected void submitWork(List<List<Scan>> nestedScans, 
List<List<Pair<Scan,Future<PeekingResultIterator>>>> nestedFutures,
             Queue<PeekingResultIterator> allIterators, int estFlattenedSize, 
boolean isReverse, ParallelScanGrouper scanGrouper) throws SQLException;
-    
+
     @Override
     public int size() {
         return this.scans.size();
@@ -1540,7 +1560,7 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
             }
         }
         buf.append(getName()).append(" ").append(size()).append("-WAY ");
-        
+
         if(this.plan.getStatement().getTableSamplingRate()!=null){
                
buf.append(plan.getStatement().getTableSamplingRate()/100D).append("-").append("SAMPLED
 ");
         }
@@ -1557,11 +1577,11 @@ public abstract class BaseResultIterators extends 
ExplainTable implements Result
     public Long getEstimatedRowCount() {
         return this.estimatedRows;
     }
-    
+
     public Long getEstimatedByteCount() {
         return this.estimatedSize;
     }
-    
+
     @Override
     public String toString() {
         return "ResultIterators [name=" + getName() + ",id=" + scanId + 
",scans=" + scans + "]";
diff --git 
a/phoenix-core/src/test/java/org/apache/phoenix/compile/ScanRangesIntersectTest.java
 
b/phoenix-core/src/test/java/org/apache/phoenix/compile/ScanRangesIntersectTest.java
index 6b9af9d..accc28e 100644
--- 
a/phoenix-core/src/test/java/org/apache/phoenix/compile/ScanRangesIntersectTest.java
+++ 
b/phoenix-core/src/test/java/org/apache/phoenix/compile/ScanRangesIntersectTest.java
@@ -4,12 +4,12 @@
  * 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 maynot use this file except in compliance
+ * "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 applicablelaw or agreed to in writing, software
+ * 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
@@ -17,16 +17,25 @@
  */
 package org.apache.phoenix.compile;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
 import org.apache.hadoop.hbase.client.Scan;
 import org.apache.phoenix.filter.SkipScanFilter;
 import org.apache.phoenix.query.KeyRange;
+import org.apache.phoenix.schema.PDatum;
+import org.apache.phoenix.schema.RowKeySchema;
+import org.apache.phoenix.schema.SortOrder;
+import org.apache.phoenix.schema.types.PChar;
+import org.apache.phoenix.schema.types.PDataType;
 import org.apache.phoenix.schema.types.PVarchar;
 import org.junit.Test;
 
@@ -58,12 +67,433 @@ public class ScanRangesIntersectTest {
             assertEquals(expectedKeys, filter.getSlots().get(0));
         }
     }
-    
+
+    private static byte[] stringToByteArray(String str){
+        return PVarchar.INSTANCE.toBytes(str);
+    }
+
+    private static String ByteArrayToString(byte[] bytes){
+        String literalString = PVarchar.INSTANCE.toStringLiteral(bytes,null);
+        return literalString.substring(1,literalString.length() - 1);
+    }
+
     private static List<KeyRange> points(String... points) {
         List<KeyRange> keys = 
Lists.newArrayListWithExpectedSize(points.length);
         for (String point : points) {
-            keys.add(KeyRange.getKeyRange(PVarchar.INSTANCE.toBytes(point)));
+            keys.add(KeyRange.getKeyRange(stringToByteArray(point)));
         }
         return keys;
     }
+
+    private static PDatum SIMPLE_CHAR = new PDatum() {
+        @Override
+        public boolean isNullable() {
+            return false;
+        }
+
+        @Override
+        public PDataType getDataType() {
+            return PChar.INSTANCE;
+        }
+
+        @Override
+        public Integer getMaxLength() {
+            return 1;
+        }
+
+        @Override
+        public Integer getScale() {
+            return null;
+        }
+
+        @Override
+        public SortOrder getSortOrder() {
+            return SortOrder.getDefault();
+        }
+    };
+
+    // Does not handle some edge conditions like empty string
+    private String handleScanNextKey(String key) {
+        char lastChar = key.charAt(key.length() - 1);
+        return key.substring(0, key.length() - 1) + 
String.valueOf((char)(lastChar + 1));
+    }
+
+    @Test
+    public void getRowKeyRangesTestEverythingRange() {
+        int rowKeySchemaFields = 1;
+        RowKeySchema schema = buildSimpleRowKeySchema(rowKeySchemaFields);
+
+        List<List<KeyRange>> ranges = new ArrayList<>();
+        ranges.add(Lists.newArrayList(KeyRange.EVERYTHING_RANGE));
+        ScanRanges scanRanges = ScanRanges.createSingleSpan(schema, ranges);
+
+        List<KeyRange> rowKeyRanges = scanRanges.getRowKeyRanges();
+        assertEquals(1, rowKeyRanges.size());
+        assertEquals(KeyRange.EVERYTHING_RANGE,rowKeyRanges.get(0));
+    }
+
+    @Test
+    public void getRowKeyRangesTestEmptyRange() {
+        int rowKeySchemaFields = 1;
+        RowKeySchema schema = buildSimpleRowKeySchema(rowKeySchemaFields);
+
+        List<List<KeyRange>> ranges = new ArrayList<>();
+        ranges.add(Lists.newArrayList(KeyRange.EMPTY_RANGE));
+        ScanRanges scanRanges = ScanRanges.createSingleSpan(schema, ranges);
+
+        List<KeyRange> rowKeyRanges = scanRanges.getRowKeyRanges();
+        assertEquals(1, rowKeyRanges.size());
+        assertEquals(KeyRange.EMPTY_RANGE,rowKeyRanges.get(0));
+    }
+
+    @Test
+    public void getRowKeyRangesTestPointLookUp() {
+        String pointString = "A";
+        KeyRange pointKeyRange = 
KeyRange.getKeyRange(stringToByteArray(pointString));
+        ScanRanges singlePointScanRange = 
ScanRanges.createPointLookup(Lists.newArrayList(pointKeyRange));
+
+        List<KeyRange> rowKeyRanges = singlePointScanRange.getRowKeyRanges();
+        assertEquals(1, rowKeyRanges.size());
+        assertEquals(false, rowKeyRanges.get(0).isSingleKey());
+        assertEquals(singleKeyToScanRange(pointString), 
rowKeyRanges.get(0).toString());
+    }
+
+    @Test
+    public void getRowKeyRangesTestPointsLookUp() {
+        String pointString1 = "A";
+        String pointString2 = "B";
+        KeyRange pointKeyRange1 = 
KeyRange.getKeyRange(stringToByteArray(pointString1));
+        KeyRange pointKeyRange2 = 
KeyRange.getKeyRange(stringToByteArray(pointString2));
+        ScanRanges singlePointScanRange = ScanRanges
+                .createPointLookup(Lists.newArrayList(pointKeyRange1, 
pointKeyRange2));
+
+        List<KeyRange> rowKeyRanges = singlePointScanRange.getRowKeyRanges();
+        assertEquals(2, rowKeyRanges.size());
+        assertEquals(false, rowKeyRanges.get(0).isSingleKey());
+        assertEquals(singleKeyToScanRange(pointString1), 
rowKeyRanges.get(0).toString());
+        assertEquals(false, rowKeyRanges.get(1).isSingleKey());
+        assertEquals(singleKeyToScanRange(pointString2), 
rowKeyRanges.get(1).toString());
+    }
+
+    @Test
+    public void getRowKeyRangesTestOneRangeLookUp() {
+        RowKeySchema schema = buildSimpleRowKeySchema(1);
+
+        String lowerString = "A";
+        String upperString = "B";
+
+        KeyRange rangeKeyRange = 
KeyRange.getKeyRange(stringToByteArray(lowerString), 
stringToByteArray(upperString));
+        List<List<KeyRange>> ranges = new ArrayList<>();
+        ranges.add(Lists.newArrayList(rangeKeyRange));
+        ScanRanges scanRanges = ScanRanges.createSingleSpan(schema, ranges);
+
+        List<KeyRange> rowKeyRanges = scanRanges.getRowKeyRanges();
+        assertEquals(1, rowKeyRanges.size());
+        assertEquals(false, rowKeyRanges.get(0).isSingleKey());
+        assertEquals(lowerString, 
ByteArrayToString(rowKeyRanges.get(0).getLowerRange()));
+        assertEquals(upperString, 
ByteArrayToString(rowKeyRanges.get(0).getUpperRange()));
+    }
+
+    @Test
+    public void getRowKeyRangesTestOneExclusiveRangeLookUp() {
+        RowKeySchema schema = buildSimpleRowKeySchema(2);
+
+        String lowerString = "A";
+        String upperString = "C";
+
+        KeyRange rangeKeyRange = 
KeyRange.getKeyRange(stringToByteArray(lowerString),false, 
stringToByteArray(upperString),false);
+        List<List<KeyRange>> ranges = new ArrayList<>();
+        ranges.add(Lists.newArrayList(rangeKeyRange));
+        ScanRanges scanRanges = ScanRanges.createSingleSpan(schema, ranges);
+
+        List<KeyRange> rowKeyRanges = scanRanges.getRowKeyRanges();
+        assertEquals(1, rowKeyRanges.size());
+        assertEquals(false, rowKeyRanges.get(0).isSingleKey());
+        assertEquals("[B - C)", rowKeyRanges.get(0).toString());
+    }
+
+    @Test
+    public void getRowKeyRangesTestOneExclusiveRangeNotFullyQualifiedLookUp() {
+        RowKeySchema schema = buildSimpleRowKeySchema(2);
+
+        String lowerString = "A";
+        String upperString = "C";
+
+        KeyRange rangeKeyRange = 
KeyRange.getKeyRange(stringToByteArray(lowerString),false, 
stringToByteArray(upperString),false);
+        List<List<KeyRange>> ranges = new ArrayList<>();
+        ranges.add(Lists.newArrayList(rangeKeyRange));
+        ScanRanges scanRanges = ScanRanges.createSingleSpan(schema, ranges);
+
+        List<KeyRange> rowKeyRanges = scanRanges.getRowKeyRanges();
+        assertEquals(1, rowKeyRanges.size());
+        assertEquals(false, rowKeyRanges.get(0).isSingleKey());
+        assertEquals("[B - C)", rowKeyRanges.get(0).toString());
+    }
+
+    @Test
+    public void getRowKeyRangesTestTwoRangesLookUp() {
+        int rowKeySchemaFields = 2;
+        RowKeySchema schema = buildSimpleRowKeySchema(rowKeySchemaFields);
+        int[] slotSpan = new int[rowKeySchemaFields];
+
+        String lowerString = "A";
+        String upperString = "B";
+
+        String lowerString2 = "C";
+        String upperString2 = "D";
+
+        KeyRange rangeKeyRange1 = 
KeyRange.getKeyRange(stringToByteArray(lowerString), true,
+                stringToByteArray(upperString), true);
+        KeyRange rangeKeyRange2 = 
KeyRange.getKeyRange(stringToByteArray(lowerString2), true,
+                stringToByteArray(upperString2), true);
+
+        List<List<KeyRange>> ranges = new ArrayList<>();
+        ranges.add(Lists.newArrayList(rangeKeyRange1));
+        ranges.add(Lists.newArrayList(rangeKeyRange2));
+
+        ScanRanges scanRanges = ScanRanges.create(schema, ranges, slotSpan, 
null, true, -1);
+
+        List<KeyRange> rowKeyRanges = scanRanges.getRowKeyRanges();
+        assertEquals(1, rowKeyRanges.size());
+        assertEquals(false, rowKeyRanges.get(0).isSingleKey());
+        assertEquals(lowerString + lowerString2, 
ByteArrayToString(rowKeyRanges.get(0).getLowerRange()));
+        assertEquals(handleScanNextKey(upperString + upperString2),
+                ByteArrayToString(rowKeyRanges.get(0).getUpperRange()));
+    }
+
+    @Test
+    public void getRowKeyRangesTestNotFullyQualifiedRowKeyLookUp() {
+        int rowKeySchemaFields = 2;
+        RowKeySchema schema = buildSimpleRowKeySchema(rowKeySchemaFields);
+        int[] slotSpan = new int[rowKeySchemaFields];
+
+        String keyString1 = "A";
+        String keyString2 = "B";
+
+        KeyRange rangeKeyRange1 = 
KeyRange.getKeyRange(stringToByteArray(keyString1));
+        KeyRange rangeKeyRange2 = 
KeyRange.getKeyRange(stringToByteArray(keyString2));
+
+        List<List<KeyRange>> ranges = new ArrayList<>();
+        ranges.add(Lists.newArrayList(rangeKeyRange1, rangeKeyRange2));
+
+        ScanRanges scanRanges = ScanRanges.create(schema, ranges, slotSpan, 
null, true, -1);
+
+        List<KeyRange> rowKeyRanges = scanRanges.getRowKeyRanges();
+        assertEquals(2, rowKeyRanges.size());
+        assertEquals(false, rowKeyRanges.get(0).isSingleKey());
+        assertEquals(keyString1, 
ByteArrayToString(rowKeyRanges.get(0).getLowerRange()));
+        assertEquals(handleScanNextKey(keyString1), 
ByteArrayToString(rowKeyRanges.get(0).getUpperRange()));
+        assertEquals(false, rowKeyRanges.get(1).isSingleKey());
+        assertEquals(keyString2, 
ByteArrayToString(rowKeyRanges.get(1).getLowerRange()));
+        assertEquals(handleScanNextKey(keyString2), 
ByteArrayToString(rowKeyRanges.get(1).getUpperRange()));
+    }
+
+    @Test
+    public void getRowKeyRangesTestValuesAndRangesLookUp() {
+        int rowKeySchemaFields = 2;
+        RowKeySchema schema = buildSimpleRowKeySchema(rowKeySchemaFields);
+        int[] slotSpan = new int[rowKeySchemaFields];
+
+        String point1 = "A";
+        String point2 = "B";
+
+        String lowerString2 = "C";
+        String upperString2 = "D";
+
+        KeyRange pointKeyRange1 = 
KeyRange.getKeyRange(stringToByteArray(point1));
+        KeyRange pointKeyRange2 = 
KeyRange.getKeyRange(stringToByteArray(point2));
+
+        KeyRange rangeKeyRange = 
KeyRange.getKeyRange(stringToByteArray(lowerString2), true,
+                stringToByteArray(upperString2), true);
+
+        List<List<KeyRange>> ranges = new ArrayList<>();
+        ranges.add(Lists.newArrayList(pointKeyRange1, pointKeyRange2));
+        ranges.add(Lists.newArrayList(rangeKeyRange));
+
+        ScanRanges scanRanges = ScanRanges.create(schema, ranges, slotSpan, 
null, true, -1);
+
+        List<KeyRange> rowKeyRanges = scanRanges.getRowKeyRanges();
+        assertEquals(2, rowKeyRanges.size());
+        assertEquals(false, rowKeyRanges.get(0).isSingleKey());
+        assertEquals(point1 + lowerString2, 
ByteArrayToString(rowKeyRanges.get(0).getLowerRange()));
+        assertEquals(handleScanNextKey(point1 + upperString2), 
ByteArrayToString(rowKeyRanges.get(0).getUpperRange()));
+
+        assertEquals(false, rowKeyRanges.get(1).isSingleKey());
+        assertEquals(point2 + lowerString2, 
ByteArrayToString(rowKeyRanges.get(1).getLowerRange()));
+        assertEquals(handleScanNextKey(point2 + upperString2), 
ByteArrayToString(rowKeyRanges.get(1).getUpperRange()));
+    }
+
+
+    @Test
+    public void getRowKeyRangesSorted() {
+        int rowKeySchemaFields = 2;
+        RowKeySchema schema = buildSimpleRowKeySchema(rowKeySchemaFields);
+        int[] slotSpan = new int[rowKeySchemaFields];
+
+        String k1a = "A";
+        String k1b = "B";
+        String k1c = "C";
+
+        String k2x = "X";
+        String k2y = "Y";
+        String k2z = "Z";
+
+        KeyRange pointKey1Range1 = 
KeyRange.getKeyRange(stringToByteArray(k1a));
+        KeyRange pointKey1Range2 = 
KeyRange.getKeyRange(stringToByteArray(k1b));
+        KeyRange pointKey1Range3 = 
KeyRange.getKeyRange(stringToByteArray(k1c));
+        KeyRange pointKey2Range1 = 
KeyRange.getKeyRange(stringToByteArray(k2x));
+        KeyRange pointKey2Range2 = 
KeyRange.getKeyRange(stringToByteArray(k2y));
+        KeyRange pointKey2Range3 = 
KeyRange.getKeyRange(stringToByteArray(k2z));
+
+        List<List<KeyRange>> ranges = new ArrayList<>();
+        ranges.add(Lists.newArrayList(pointKey1Range1, pointKey1Range2, 
pointKey1Range3));
+        
ranges.add(Lists.newArrayList(pointKey2Range1,pointKey2Range2,pointKey2Range3));
+
+        ScanRanges scanRanges = ScanRanges.create(schema, ranges, slotSpan, 
null, true, -1);
+
+        List<KeyRange> rowKeyRanges = scanRanges.getRowKeyRanges();
+        assertEquals(9, rowKeyRanges.size());
+        assertEquals(singleKeyToScanRange("AX"), 
rowKeyRanges.get(0).toString());
+        assertEquals(singleKeyToScanRange("AY"), 
rowKeyRanges.get(1).toString());
+        assertEquals(singleKeyToScanRange("AZ"), 
rowKeyRanges.get(2).toString());
+
+        assertEquals(singleKeyToScanRange("BX"), 
rowKeyRanges.get(3).toString());
+        assertEquals(singleKeyToScanRange("BY"), 
rowKeyRanges.get(4).toString());
+        assertEquals(singleKeyToScanRange("BZ"), 
rowKeyRanges.get(5).toString());
+
+        assertEquals(singleKeyToScanRange("CX"), 
rowKeyRanges.get(6).toString());
+        assertEquals(singleKeyToScanRange("CY"), 
rowKeyRanges.get(7).toString());
+        assertEquals(singleKeyToScanRange("CZ"), 
rowKeyRanges.get(8).toString());
+
+    }
+
+    @Test
+    public void getRowKeyRangesAdjacentSubRanges() {
+        int rowKeySchemaFields = 2;
+        RowKeySchema schema = buildSimpleRowKeySchema(rowKeySchemaFields);
+        int[] slotSpan = new int[rowKeySchemaFields];
+
+        List<KeyRange> keyRanges = new ArrayList<>();
+        keyRanges.add(KeyRange.getKeyRange(stringToByteArray("A"), true,
+                stringToByteArray("C"), false));
+        keyRanges.add(KeyRange.getKeyRange(stringToByteArray("C"), true,
+                stringToByteArray("E"), false));
+        keyRanges.add(KeyRange.getKeyRange(stringToByteArray("E"), true,
+                stringToByteArray("G"), false));
+        keyRanges.add(KeyRange.getKeyRange(stringToByteArray("G"), true,
+                stringToByteArray("I"), false));
+
+        List<List<KeyRange>> ranges = new ArrayList<>();
+        ranges.add(keyRanges);
+
+        ScanRanges scanRanges = ScanRanges.create(schema, ranges, slotSpan, 
null, true, -1);
+
+        List<KeyRange> rowKeyRanges = scanRanges.getRowKeyRanges();
+        assertEquals(4, rowKeyRanges.size());
+        assertEquals("[A - C)", rowKeyRanges.get(0).toString());
+        assertEquals("[C - E)", rowKeyRanges.get(1).toString());
+        assertEquals("[E - G)", rowKeyRanges.get(2).toString());
+        assertEquals("[G - I)", rowKeyRanges.get(3).toString());
+    }
+
+    @Test
+    public void getRowKeyRangesAdjacentSubRangesUpperInclusive() {
+        int rowKeySchemaFields = 1;
+        RowKeySchema schema = buildSimpleRowKeySchema(rowKeySchemaFields);
+
+        int[] slotSpan = new int[rowKeySchemaFields];
+
+        List<KeyRange> keyRanges = new ArrayList<>();
+        keyRanges.add(KeyRange.getKeyRange(stringToByteArray("A"), false,
+                stringToByteArray("C"), true));
+        keyRanges.add(KeyRange.getKeyRange(stringToByteArray("C"), false,
+                stringToByteArray("E"), true));
+        keyRanges.add(KeyRange.getKeyRange(stringToByteArray("E"), false,
+                stringToByteArray("G"), true));
+        keyRanges.add(KeyRange.getKeyRange(stringToByteArray("G"), false,
+                stringToByteArray("I"), true));
+
+        List<List<KeyRange>> ranges = new ArrayList<>();
+        ranges.add(keyRanges);
+
+        ScanRanges scanRanges = ScanRanges.create(schema, ranges, slotSpan, 
null, true, -1);
+
+        List<KeyRange> rowKeyRanges = scanRanges.getRowKeyRanges();
+        assertEquals(4, rowKeyRanges.size());
+        assertEquals("[B - D)", rowKeyRanges.get(0).toString());
+        assertEquals("[D - F)", rowKeyRanges.get(1).toString());
+        assertEquals("[F - H)", rowKeyRanges.get(2).toString());
+        assertEquals("[H - J)", rowKeyRanges.get(3).toString());
+    }
+
+    /*
+     * range/single    boundary       bound      increment
+     *  range          inclusive      lower         no
+     *  range          inclusive      upper         yes, at the end if occurs 
at any slots.
+     *  range          exclusive      lower         yes
+     *  range          exclusive      upper         no
+     *  single         inclusive      lower         no
+     *  single         inclusive      upper         yes, at the end if it is 
the last slots.
+     */
+
+    @Test
+    public void getRangeKeyExclusiveLowerIncrementedUpperNotIncremented() {
+        int rowKeySchemaFields = 1;
+        RowKeySchema schema = buildSimpleRowKeySchema(rowKeySchemaFields);
+
+        int[] slotSpan = new int[rowKeySchemaFields];
+
+        String lowerKeyString = "E";
+        String upperKeyString = "O";
+        KeyRange rangeKeyRange = 
KeyRange.getKeyRange(stringToByteArray(lowerKeyString),false, 
stringToByteArray(upperKeyString), false);
+        List<List<KeyRange>> ranges = new ArrayList<>();
+        ranges.add(Lists.newArrayList(rangeKeyRange));
+
+        ScanRanges scanRanges = ScanRanges.create(schema, ranges, slotSpan, 
null, true, -1);
+
+        List<KeyRange> rowKeyRanges = scanRanges.getRowKeyRanges();
+        assertEquals(1, rowKeyRanges.size());
+        assertTrue(rowKeyRanges.get(0).isLowerInclusive());
+        assertFalse(rowKeyRanges.get(0).isUpperInclusive());
+        
assertArrayEquals(stringToByteArray(handleScanNextKey(lowerKeyString)),rowKeyRanges.get(0).getLowerRange());
+        
assertArrayEquals(stringToByteArray(upperKeyString),rowKeyRanges.get(0).getUpperRange());
+    }
+
+    @Test
+    public void getAdjacentKeysLowerNotIncrementedUpperIncrementedLastSlots() {
+        int rowKeySchemaFields = 2;
+        RowKeySchema schema = buildSimpleRowKeySchema(rowKeySchemaFields);
+
+        int[] slotSpan = new int[rowKeySchemaFields];
+
+        KeyRange keyRange1_1 = KeyRange.getKeyRange(stringToByteArray("A"));
+        KeyRange keyRange2_1 = KeyRange.getKeyRange(stringToByteArray("B"));
+
+        KeyRange keyRange1_2 = KeyRange.getKeyRange(stringToByteArray("C"));
+        KeyRange keyRange2_2 = KeyRange.getKeyRange(stringToByteArray("D"));
+        List<List<KeyRange>> ranges = new ArrayList<>();
+        ranges.add(Lists.newArrayList(keyRange1_1,keyRange2_1));
+        ranges.add(Lists.newArrayList(keyRange1_2,keyRange2_2));
+
+        ScanRanges scanRanges = ScanRanges.create(schema, ranges, slotSpan, 
null, true, -1);
+
+        List<KeyRange> rowKeyRanges = scanRanges.getRowKeyRanges();
+        assertEquals(4, rowKeyRanges.size());
+        
assertEquals(singleKeyToScanRange("AC"),rowKeyRanges.get(0).toString());
+        
assertEquals(singleKeyToScanRange("AD"),rowKeyRanges.get(1).toString());
+        
assertEquals(singleKeyToScanRange("BC"),rowKeyRanges.get(2).toString());
+        
assertEquals(singleKeyToScanRange("BD"),rowKeyRanges.get(3).toString());
+    }
+
+    private RowKeySchema buildSimpleRowKeySchema(int fields){
+        RowKeySchema.RowKeySchemaBuilder builder = new 
RowKeySchema.RowKeySchemaBuilder(fields);
+        for(int i = 0; i < fields; i++) {
+            builder.addField(SIMPLE_CHAR, SIMPLE_CHAR.isNullable(), 
SIMPLE_CHAR.getSortOrder());
+        }
+        return builder.build();
+    }
+
+    private String singleKeyToScanRange(String key){
+        return String.format("[%s - %s\\x00)",key,key);
+    }
 }
diff --git 
a/phoenix-core/src/test/java/org/apache/phoenix/query/KeyRangeMoreTest.java 
b/phoenix-core/src/test/java/org/apache/phoenix/query/KeyRangeMoreTest.java
index 6f0c4c7..3763541 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/query/KeyRangeMoreTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/query/KeyRangeMoreTest.java
@@ -24,6 +24,7 @@ import java.util.Collections;
 import java.util.List;
 
 import com.google.common.collect.Lists;
+import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.phoenix.schema.types.PInteger;
 import org.junit.Test;
@@ -171,6 +172,19 @@ public class KeyRangeMoreTest extends TestCase {
     }
 
     @Test
+    public void testHBaseConstants() {
+        byte[] key = {0,7};
+        KeyRange key1 = KeyRange.getKeyRange(HConstants.EMPTY_BYTE_ARRAY,key);
+        assertTrue(key1.lowerUnbound());
+
+        KeyRange key2 = KeyRange.getKeyRange(key,HConstants.EMPTY_BYTE_ARRAY);
+        assertTrue(key2.upperUnbound());
+
+        KeyRange key3 = 
KeyRange.getKeyRange(HConstants.EMPTY_BYTE_ARRAY,HConstants.EMPTY_BYTE_ARRAY);
+        assertEquals(KeyRange.EVERYTHING_RANGE, key3);
+    }
+
+    @Test
     public void testListIntersectForBoundary() throws Exception {
         List<KeyRange> rowKeyRanges1=Arrays.asList(KeyRange.EVERYTHING_RANGE);
         List<KeyRange> rowKeyRanges2=new ArrayList<KeyRange>();

Reply via email to