PHOENIX-2194 order by should not require all PK fields with = constraint

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

Branch: refs/heads/calcite
Commit: d18afe183525db7e1b9aba4e469ab39931204070
Parents: b3c05bf
Author: James Taylor <jamestay...@apache.org>
Authored: Tue Sep 8 15:41:41 2015 -0700
Committer: James Taylor <jamestay...@apache.org>
Committed: Tue Sep 8 15:41:41 2015 -0700

----------------------------------------------------------------------
 .../phoenix/end2end/RowValueConstructorIT.java  |  69 ++++++++
 .../org/apache/phoenix/end2end/SortOrderIT.java |  27 +++
 .../java/org/apache/phoenix/end2end/ViewIT.java |  24 +++
 .../phoenix/compile/OrderPreservingTracker.java |  21 ++-
 .../org/apache/phoenix/compile/ScanRanges.java  | 136 +++++++++++----
 .../apache/phoenix/compile/WhereOptimizer.java  |  72 ++++----
 .../coprocessor/MetaDataEndpointImpl.java       |   7 +-
 .../apache/phoenix/filter/SkipScanFilter.java   |  28 +--
 .../phoenix/index/PhoenixIndexBuilder.java      |   5 +-
 .../apache/phoenix/iterate/ExplainTable.java    |   7 +-
 .../apache/phoenix/optimize/QueryOptimizer.java |   6 +-
 .../java/org/apache/phoenix/util/ScanUtil.java  |  49 +++---
 .../phoenix/compile/QueryCompilerTest.java      |  18 +-
 .../compile/ScanRangesIntersectTest.java        |  37 +---
 .../apache/phoenix/compile/ScanRangesTest.java  |   3 +-
 .../TenantSpecificViewIndexCompileTest.java     | 172 +++++++++++++++++++
 .../phoenix/compile/ViewCompilerTest.java       |   1 -
 .../phoenix/compile/WhereOptimizerTest.java     |  22 ++-
 .../query/ParallelIteratorsSplitTest.java       |   3 +-
 .../org/apache/phoenix/query/QueryPlanTest.java |  10 +-
 .../org/apache/phoenix/util/ScanUtilTest.java   |  10 +-
 21 files changed, 537 insertions(+), 190 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/phoenix/blob/d18afe18/phoenix-core/src/it/java/org/apache/phoenix/end2end/RowValueConstructorIT.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/RowValueConstructorIT.java
 
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/RowValueConstructorIT.java
index 5bf0a1e..749da5d 100644
--- 
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/RowValueConstructorIT.java
+++ 
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/RowValueConstructorIT.java
@@ -1522,4 +1522,73 @@ public class RowValueConstructorIT extends 
BaseClientManagedTimeIT {
         assertTrue(rs.next());
         assertEquals(3, rs.getInt(1));
     }
+
+    @Test
+    public void testRVCRequiringExtractNodeClear() throws Exception {
+        Connection conn = nextConnection(getUrl());
+        String tableName = "testRVCWithTrailingGT";
+        String ddl = "CREATE TABLE  " + tableName + "  (k1 VARCHAR, k2 
VARCHAR, k3 VARCHAR, k4 VARCHAR, CONSTRAINT pk PRIMARY KEY (k1,k2,k3,k4))";
+        conn.createStatement().execute(ddl);
+        
+        conn = nextConnection(getUrl());
+        PreparedStatement stmt = conn.prepareStatement("UPSERT INTO  " + 
tableName + " VALUES('a','b','c','d')");
+        stmt.execute();
+        stmt = conn.prepareStatement("UPSERT INTO  " + tableName + " 
VALUES('b', 'b', 'c', 'e')");
+        stmt.execute();
+        stmt = conn.prepareStatement("UPSERT INTO  " + tableName + " 
VALUES('c', 'b','c','f')");
+        stmt.execute();
+        conn.commit();
+        
+        conn = nextConnection(getUrl());
+        ResultSet rs;
+        rs = conn.createStatement().executeQuery("SELECT k1 from  " + 
tableName + " WHERE k1 IN ('a','c') AND (k2,k3) IN (('b','c'),('f','g')) AND k4 
> 'c'");
+        assertTrue(rs.next());
+        assertEquals("a", rs.getString(1));
+        assertTrue(rs.next());
+        assertEquals("c", rs.getString(1));
+        assertFalse(rs.next());
+    }
+
+    @Test
+    public void testRVCRequiringNoSkipScan() throws Exception {
+        Connection conn = nextConnection(getUrl());
+        String tableName = "testRVCWithTrailingGT";
+        String ddl = "CREATE TABLE  " + tableName + "  (k1 VARCHAR, k2 
VARCHAR, k3 VARCHAR, k4 VARCHAR, CONSTRAINT pk PRIMARY KEY (k1,k2,k3,k4))";
+        conn.createStatement().execute(ddl);
+        
+        conn = nextConnection(getUrl());
+        PreparedStatement stmt = conn.prepareStatement("UPSERT INTO  " + 
tableName + " VALUES('','','a')");
+        stmt.execute();
+        stmt = conn.prepareStatement("UPSERT INTO  " + tableName + " 
VALUES('', '', 'a', 'a')");
+        stmt.execute();
+        stmt = conn.prepareStatement("UPSERT INTO  " + tableName + " 
VALUES('', '','b')");
+        stmt.execute();
+        stmt = conn.prepareStatement("UPSERT INTO  " + tableName + " 
VALUES('', '','b','a')");
+        stmt.execute();
+        stmt = conn.prepareStatement("UPSERT INTO  " + tableName + " 
VALUES('a', '','c')");
+        stmt.execute();
+        stmt = conn.prepareStatement("UPSERT INTO  " + tableName + " 
VALUES('a', '','c', 'a')");
+        stmt.execute();
+        conn.commit();
+        
+        conn = nextConnection(getUrl());
+        ResultSet rs;
+        rs = conn.createStatement().executeQuery("SELECT k1,k3,k4 from  " + 
tableName + " WHERE (k1,k2,k3) IN (('','','a'),('','','b'),('a','','c')) AND k4 
is not null");
+        assertTrue(rs.next());
+        assertEquals(null, rs.getString(1));
+        assertEquals("a", rs.getString(2));
+        assertEquals("a", rs.getString(3));
+        
+        assertTrue(rs.next());
+        assertEquals(null, rs.getString(1));
+        assertEquals("b", rs.getString(2));
+        assertEquals("a", rs.getString(3));
+        
+        assertTrue(rs.next());
+        assertEquals("a", rs.getString(1));
+        assertEquals("c", rs.getString(2));
+        assertEquals("a", rs.getString(3));
+        
+        assertFalse(rs.next());
+    }
 }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/d18afe18/phoenix-core/src/it/java/org/apache/phoenix/end2end/SortOrderIT.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/SortOrderIT.java 
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/SortOrderIT.java
index fdbd26d..b906ae2 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/SortOrderIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/SortOrderIT.java
@@ -402,6 +402,33 @@ public class SortOrderIT extends BaseHBaseManagedTimeIT {
                 new WhereCondition("k2", "<=", "'bb'"), null, null);
     }
         
+    @Test
+    public void varLengthAscLT() throws Exception {
+        String ddl = "CREATE TABLE " + TABLE + " (k1 VARCHAR NOT NULL, k2 
VARCHAR, CONSTRAINT pk PRIMARY KEY (k1, k2))";
+        Object[][] insertedRows = new Object[][]{{"a", ""}, {"b",""}, 
{"b","a"}};
+        Object[][] expectedRows = new Object[][]{{"a"}};
+        runQueryTest(ddl, upsert("k1", "k2"), select("k1"), insertedRows, 
expectedRows,
+                new WhereCondition("k1", "<", "'b'"), null, null);
+    }
+        
+    @Test
+    public void varLengthDescLT() throws Exception {
+        String ddl = "CREATE TABLE " + TABLE + " (k1 VARCHAR NOT NULL, k2 
VARCHAR, CONSTRAINT pk PRIMARY KEY (k1 desc, k2))";
+        Object[][] insertedRows = new Object[][]{{"a", ""}, {"b",""}, 
{"b","a"}};
+        Object[][] expectedRows = new Object[][]{{"a"}};
+        runQueryTest(ddl, upsert("k1", "k2"), select("k1"), insertedRows, 
expectedRows,
+                new WhereCondition("k1", "<", "'b'"), null, null);
+    }
+        
+    @Test
+    public void varLengthDescGT() throws Exception {
+        String ddl = "CREATE TABLE " + TABLE + " (k1 VARCHAR NOT NULL, k2 
VARCHAR, CONSTRAINT pk PRIMARY KEY (k1 desc, k2))";
+        Object[][] insertedRows = new Object[][]{{"a", ""}, {"b",""}, 
{"b","a"}, {"ba","a"}};
+        Object[][] expectedRows = new Object[][]{{"ba"}};
+        runQueryTest(ddl, upsert("k1", "k2"), select("k1"), insertedRows, 
expectedRows,
+                new WhereCondition("k1", ">", "'b'"), null, null);
+    }
+        
    @Test
     public void testNonPKCompare() throws Exception {
         List<Integer> expectedResults = Lists.newArrayList(2,3,4);

http://git-wip-us.apache.org/repos/asf/phoenix/blob/d18afe18/phoenix-core/src/it/java/org/apache/phoenix/end2end/ViewIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ViewIT.java 
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ViewIT.java
index 3254c2e..123fd85 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ViewIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ViewIT.java
@@ -30,15 +30,18 @@ import static org.junit.Assert.fail;
 
 import java.sql.Connection;
 import java.sql.DriverManager;
+import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.List;
 
+import org.apache.phoenix.compile.QueryPlan;
 import org.apache.phoenix.exception.SQLExceptionCode;
 import org.apache.phoenix.query.KeyRange;
 import org.apache.phoenix.schema.ColumnAlreadyExistsException;
 import org.apache.phoenix.schema.ReadOnlyTableException;
 import org.apache.phoenix.schema.TableNotFoundException;
+import org.apache.phoenix.util.PhoenixRuntime;
 import org.apache.phoenix.util.QueryUtil;
 import org.junit.Test;
 
@@ -572,6 +575,27 @@ public class ViewIT extends BaseViewIT {
         }
     }
     
+    @Test
+    public void testQueryViewStatementOptimization() throws Exception {
+        Connection conn = DriverManager.getConnection(getUrl());
+        String sql = "CREATE TABLE tp (k1 INTEGER NOT NULL, k2 INTEGER NOT 
NULL, v1 DECIMAL, CONSTRAINT pk PRIMARY KEY (k1, k2))";
+        conn.createStatement().execute(sql);
+        sql = "CREATE VIEW v1  AS SELECT * FROM tp";
+        conn.createStatement().execute(sql);
+        sql = "CREATE VIEW v2  AS SELECT * FROM tp WHERE k1 = 1.0";
+        conn.createStatement().execute(sql);
+        
+        sql = "SELECT * FROM v1 order by k1, k2";
+        PreparedStatement stmt = conn.prepareStatement(sql);
+        QueryPlan plan = PhoenixRuntime.getOptimizedQueryPlan(stmt);
+        assertEquals(0, plan.getOrderBy().getOrderByExpressions().size());
+        
+        sql = "SELECT * FROM v2 order by k1, k2";
+        stmt = conn.prepareStatement(sql);
+        plan = PhoenixRuntime.getOptimizedQueryPlan(stmt);
+        assertEquals(0, plan.getOrderBy().getOrderByExpressions().size());
+    }
+    
     private void assertPKs(ResultSet rs, String[] expectedPKs) throws 
SQLException {
         List<String> pkCols = newArrayListWithExpectedSize(expectedPKs.length);
         while (rs.next()) {

http://git-wip-us.apache.org/repos/asf/phoenix/blob/d18afe18/phoenix-core/src/main/java/org/apache/phoenix/compile/OrderPreservingTracker.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/compile/OrderPreservingTracker.java
 
b/phoenix-core/src/main/java/org/apache/phoenix/compile/OrderPreservingTracker.java
index 70ae231..65245f3 100644
--- 
a/phoenix-core/src/main/java/org/apache/phoenix/compile/OrderPreservingTracker.java
+++ 
b/phoenix-core/src/main/java/org/apache/phoenix/compile/OrderPreservingTracker.java
@@ -65,6 +65,7 @@ public class OrderPreservingTracker {
             this.orderPreserving = orderPreserving;
         }
     }
+    private final StatementContext context;
     private final TrackOrderPreservingExpressionVisitor visitor;
     private final GroupBy groupBy;
     private final Ordering ordering;
@@ -78,6 +79,7 @@ public class OrderPreservingTracker {
     }
     
     public OrderPreservingTracker(StatementContext context, GroupBy groupBy, 
Ordering ordering, int nNodes, TupleProjector projector) {
+        this.context = context;
         int pkPositionOffset = 0;
         PTable table = context.getResolver().getTables().get(0).getTable();
         isOrderPreserving = table.rowKeyOrderOptimizable();
@@ -156,7 +158,12 @@ public class OrderPreservingTracker {
             Collections.sort(orderPreservingInfos, new Comparator<Info>() {
                 @Override
                 public int compare(Info o1, Info o2) {
-                    return o1.pkPosition-o2.pkPosition;
+                    int cmp = o1.pkPosition-o2.pkPosition;
+                    if (cmp != 0) return cmp;
+                    // After pk position, sort on reverse OrderPreserving 
ordinal: NO, YES_IF_LAST, YES
+                    // In this way, if we have an ORDER BY over a YES_IF_LAST 
followed by a YES, we'll
+                    // allow subsequent columns to be ordered.
+                    return o2.orderPreserving.ordinal() - 
o1.orderPreserving.ordinal();
                 }
             });
         }
@@ -169,7 +176,7 @@ public class OrderPreservingTracker {
         for (int i = 0; i < orderPreservingInfos.size() && isOrderPreserving; 
i++) {
             Info entry = orderPreservingInfos.get(i);
             int pos = entry.pkPosition;
-            isOrderPreserving &= (entry.orderPreserving != OrderPreserving.NO) 
&& (pos == prevPos || ((pos - prevSlotSpan == prevPos) && (prevOrderPreserving 
== OrderPreserving.YES)));
+            isOrderPreserving &= entry.orderPreserving != OrderPreserving.NO 
&& prevOrderPreserving == OrderPreserving.YES && (pos == prevPos || pos - 
prevSlotSpan == prevPos  || hasEqualityConstraints(prevPos+prevSlotSpan, pos));
             prevPos = pos;
             prevSlotSpan = entry.slotSpan;
             prevOrderPreserving = entry.orderPreserving;
@@ -177,6 +184,16 @@ public class OrderPreservingTracker {
         return isOrderPreserving;
     }
     
+    private boolean hasEqualityConstraints(int startPos, int endPos) {
+        ScanRanges ranges = context.getScanRanges();
+        for (int pos = startPos; pos < endPos; pos++) {
+            if (!ranges.hasEqualityConstraint(pos)) {
+                return false;
+            }
+        }
+        return true;
+    }
+    
     public boolean isReverse() {
         return Boolean.TRUE.equals(isReverse);
     }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/d18afe18/phoenix-core/src/main/java/org/apache/phoenix/compile/ScanRanges.java
----------------------------------------------------------------------
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 298cd4e..2a032a3 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
@@ -52,18 +52,24 @@ public class ScanRanges {
     public static final ScanRanges NOTHING = new 
ScanRanges(null,ScanUtil.SINGLE_COLUMN_SLOT_SPAN,NOTHING_RANGES, 
KeyRange.EMPTY_RANGE, KeyRange.EMPTY_RANGE, false, false, null);
     private static final Scan HAS_INTERSECTION = new Scan();
 
-    public static ScanRanges create(RowKeySchema schema, List<List<KeyRange>> 
ranges, int[] slotSpan) {
-        return create(schema, ranges, slotSpan, KeyRange.EVERYTHING_RANGE, 
false, null);
+    public static ScanRanges createPointLookup(List<KeyRange> keys) {
+        return ScanRanges.create(SchemaUtil.VAR_BINARY_SCHEMA, 
Collections.singletonList(keys), ScanUtil.SINGLE_COLUMN_SLOT_SPAN, 
KeyRange.EVERYTHING_RANGE, null, true);
     }
     
-    public static ScanRanges create(RowKeySchema schema, List<List<KeyRange>> 
ranges, int[] slotSpan, KeyRange minMaxRange, boolean forceRangeScan, Integer 
nBuckets) {
+    // For testing
+    public static ScanRanges createSingleSpan(RowKeySchema schema, 
List<List<KeyRange>> ranges) {
+        return create(schema, ranges, 
ScanUtil.getDefaultSlotSpans(ranges.size()), KeyRange.EVERYTHING_RANGE, null, 
true);
+    }
+    
+    public static ScanRanges create(RowKeySchema schema, List<List<KeyRange>> 
ranges, int[] slotSpan, KeyRange minMaxRange, Integer nBuckets, boolean 
useSkipScan) {
         int offset = nBuckets == null ? 0 : SaltingUtil.NUM_SALTING_BYTES;
-        if (ranges.size() == offset && minMaxRange == 
KeyRange.EVERYTHING_RANGE) {
+        int nSlots = ranges.size();
+        if (nSlots == offset && minMaxRange == KeyRange.EVERYTHING_RANGE) {
             return EVERYTHING;
-        } else if (minMaxRange == KeyRange.EMPTY_RANGE || (ranges.size() == 1 
+ offset && ranges.get(offset).size() == 1 && ranges.get(offset).get(0) == 
KeyRange.EMPTY_RANGE)) {
+        } else if (minMaxRange == KeyRange.EMPTY_RANGE || (nSlots == 1 + 
offset && ranges.get(offset).size() == 1 && ranges.get(offset).get(0) == 
KeyRange.EMPTY_RANGE)) {
             return NOTHING;
         }
-        boolean isPointLookup = !forceRangeScan && 
ScanRanges.isPointLookup(schema, ranges, slotSpan);
+        boolean isPointLookup = isPointLookup(schema, ranges, slotSpan, 
useSkipScan);
         if (isPointLookup) {
             // TODO: consider keeping original to use for serialization as it 
would be smaller?
             List<byte[]> keys = ScanRanges.getPointKeys(ranges, slotSpan, 
schema, nBuckets);
@@ -86,6 +92,7 @@ public class ScanRanges {
                 }
             }
             ranges = Collections.singletonList(keyRanges);
+            useSkipScan = keyRanges.size() > 1;
             // Treat as binary if descending because we've got a separator 
byte at the end
             // which is not part of the value.
             if (keys.size() > 1 || 
SchemaUtil.getSeparatorByte(schema.rowKeyOrderOptimizable(), false, 
schema.getField(0)) == QueryConstants.DESC_SEPARATOR_BYTE) {
@@ -103,7 +110,6 @@ public class ScanRanges {
             Collections.sort(sorted, KeyRange.COMPARATOR);
             sortedRanges.add(ImmutableList.copyOf(sorted));
         }
-        boolean useSkipScanFilter = useSkipScanFilter(forceRangeScan, 
isPointLookup, sortedRanges);
         
         // Don't set minMaxRange for point lookup because it causes issues 
during intersect
         // by going across region boundaries
@@ -111,7 +117,7 @@ public class ScanRanges {
         // if (!isPointLookup && (nBuckets == null || !useSkipScanFilter)) {
         // if (! ( isPointLookup || (nBuckets != null && useSkipScanFilter) ) 
) {
         // if (nBuckets == null || (nBuckets != null && (!isPointLookup || 
!useSkipScanFilter))) {
-        if (nBuckets == null || !isPointLookup || !useSkipScanFilter) {
+        if (nBuckets == null || !isPointLookup || !useSkipScan) {
             byte[] minKey = ScanUtil.getMinKey(schema, sortedRanges, slotSpan);
             byte[] maxKey = ScanUtil.getMaxKey(schema, sortedRanges, slotSpan);
             // If the maxKey has crossed the salt byte boundary, then we do not
@@ -133,7 +139,7 @@ public class ScanRanges {
         if (scanRange == KeyRange.EMPTY_RANGE) {
             return NOTHING;
         }
-        return new ScanRanges(schema, slotSpan, sortedRanges, scanRange, 
minMaxRange, useSkipScanFilter, isPointLookup, nBuckets);
+        return new ScanRanges(schema, slotSpan, sortedRanges, scanRange, 
minMaxRange, useSkipScan, isPointLookup, nBuckets);
     }
 
     private SkipScanFilter filter;
@@ -163,8 +169,13 @@ public class ScanRanges {
         this.ranges = ImmutableList.copyOf(ranges);
         this.slotSpan = slotSpan;
         this.schema = schema;
-        if (schema != null && !ranges.isEmpty()) { // TODO: only create if 
useSkipScanFilter is true?
-            this.filter = new SkipScanFilter(this.ranges, slotSpan, schema);
+        if (schema != null && !ranges.isEmpty()) {
+            if (!this.useSkipScanFilter) {
+                int boundSlotCount = this.getBoundSlotCount();
+                ranges = ranges.subList(0, boundSlotCount);
+                slotSpan = Arrays.copyOf(slotSpan, boundSlotCount);
+            }
+            this.filter = new SkipScanFilter(ranges, slotSpan, this.schema);
         }
     }
     
@@ -405,12 +416,16 @@ public class ScanRanges {
         return ranges;
     }
 
+    public List<List<KeyRange>> getBoundRanges() {
+        return ranges.subList(0, getBoundSlotCount());
+    }
+
     public RowKeySchema getSchema() {
         return schema;
     }
 
     public boolean isEverything() {
-        return this == EVERYTHING;
+        return this == EVERYTHING || ranges.get(0).get(0) == 
KeyRange.EVERYTHING_RANGE;
     }
 
     public boolean isDegenerate() {
@@ -427,33 +442,48 @@ public class ScanRanges {
         return useSkipScanFilter;
     }
     
-    private static boolean useSkipScanFilter(boolean forceRangeScan, boolean 
isPointLookup, List<List<KeyRange>> ranges) {
-        if (forceRangeScan) {
-            return false;
-        }
-        if (isPointLookup) {
-            return getPointLookupCount(isPointLookup, ranges) > 1;
-        }
-        boolean hasRangeKey = false, useSkipScan = false;
-        for (List<KeyRange> orRanges : ranges) {
-            useSkipScan |= (orRanges.size() > 1 || hasRangeKey);
-            if (useSkipScan) {
-                return true;
-            }
+    /**
+     * Finds the total number of row keys spanned by this ranges / slotSpan 
pair.
+     * This accounts for slots in the ranges that may span more than on row 
key.
+     * @param ranges  the KeyRange slots paired with this slotSpan. 
corresponds to {@link ScanRanges#ranges}
+     * @param slotSpan  the extra span per skip scan slot. corresponds to 
{@link ScanRanges#slotSpan}
+     * @return  the total number of row keys spanned yb this ranges / slotSpan 
pair.
+     */
+    private static int getBoundPkSpan(List<List<KeyRange>> ranges, int[] 
slotSpan) {
+        int count = 0;
+        boolean hasUnbound = false;
+        int nRanges = ranges.size();
+
+        for(int i = 0; i < nRanges && !hasUnbound; i++) {
+            List<KeyRange> orRanges = ranges.get(i);
             for (KeyRange range : orRanges) {
-                hasRangeKey |= !range.isSingleKey();
+                if (range == KeyRange.EVERYTHING_RANGE) {
+                    return count;
+                }
+                if (range.isUnbound()) {
+                    hasUnbound = true;
+                }
             }
+            count += slotSpan[i] + 1;
         }
-        return false;
+
+        return count;
     }
 
-    private static boolean isPointLookup(RowKeySchema schema, 
List<List<KeyRange>> ranges, int[] slotSpan) {
-        if (ScanUtil.getTotalSpan(ranges, slotSpan) < schema.getMaxFields()) {
+    private static boolean isFullyQualified(RowKeySchema schema, 
List<List<KeyRange>> ranges, int[] slotSpan) {
+        return getBoundPkSpan(ranges, slotSpan) == schema.getMaxFields();
+    }
+    
+    private static boolean isPointLookup(RowKeySchema schema, 
List<List<KeyRange>> ranges, int[] slotSpan, boolean useSkipScan) {
+        if (!isFullyQualified(schema, ranges, slotSpan)) {
             return false;
         }
         int lastIndex = ranges.size()-1;
         for (int i = lastIndex; i >= 0; i--) {
             List<KeyRange> orRanges = ranges.get(i);
+            if (!useSkipScan && orRanges.size() > 1) {
+                return false;
+            }
             for (KeyRange keyRange : orRanges) {
                 // Special case for single trailing IS NULL. We cannot 
consider this as a point key because
                 // we strip trailing nulls when we form the key.
@@ -519,8 +549,29 @@ public class ScanRanges {
         return isPointLookup ? ranges.get(0).iterator() : 
Iterators.<KeyRange>emptyIterator();
     }
 
-    public int getPkColumnSpan() {
-        return this == ScanRanges.NOTHING ? 0 : ScanUtil.getTotalSpan(ranges, 
slotSpan);
+    public int getBoundPkColumnCount() {
+        return this.useSkipScanFilter ? ScanUtil.getRowKeyPosition(slotSpan, 
ranges.size()) : getBoundPkSpan(ranges, slotSpan);
+    }
+
+    public int getBoundSlotCount() {
+        int count = 0;
+        boolean hasUnbound = false;
+        int nRanges = ranges.size();
+
+        for(int i = 0; i < nRanges && !hasUnbound; i++) {
+            List<KeyRange> orRanges = ranges.get(i);
+            for (KeyRange range : orRanges) {
+                if (range == KeyRange.EVERYTHING_RANGE) {
+                    return count;
+                }
+                if (range.isUnbound()) {
+                    hasUnbound = true;
+                }
+            }
+            count++;
+        }
+
+        return count;
     }
 
     @Override
@@ -528,4 +579,27 @@ public class ScanRanges {
         return "ScanRanges[" + ranges.toString() + "]";
     }
 
+    public int[] getSlotSpans() {
+        return slotSpan;
+    }
+
+    public boolean hasEqualityConstraint(int pkPosition) {
+        if (isPointLookup) {
+            return true;
+        }
+        
+        int pkOffset = 0;
+        int nRanges = ranges.size();
+
+        for(int i = 0; i < nRanges; i++) {
+            if (pkOffset + slotSpan[i] >= pkPosition) {
+                List<KeyRange> range = ranges.get(i);
+                return range.size() == 1 && range.get(0).isSingleKey();
+            }
+            pkOffset += slotSpan[i] + 1;
+        }
+
+        return false;
+        
+    }
 }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/d18afe18/phoenix-core/src/main/java/org/apache/phoenix/compile/WhereOptimizer.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/compile/WhereOptimizer.java 
b/phoenix-core/src/main/java/org/apache/phoenix/compile/WhereOptimizer.java
index 95cea83..2f607cc 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/WhereOptimizer.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/WhereOptimizer.java
@@ -157,7 +157,6 @@ public class WhereOptimizer {
         int pkPos = 0;
         int nPKColumns = table.getPKColumns().size();
         int[] slotSpan = new int[nPKColumns];
-        List<Expression> removeFromExtractNodes = null;
         List<List<KeyRange>> cnf = 
Lists.newArrayListWithExpectedSize(schema.getMaxFields());
         KeyRange minMaxRange = keySlots.getMinMaxRange();
         if (minMaxRange == null) {
@@ -225,9 +224,10 @@ public class WhereOptimizer {
         boolean forcedRangeScan = statement.getHint().hasHint(Hint.RANGE_SCAN);
         boolean hasUnboundedRange = false;
         boolean hasMultiRanges = false;
-        boolean hasMultiColumnSpan = false;
-        boolean hasNonPointKey = false;
+        boolean hasRangeKey = false;
         boolean stopExtracting = false;
+        boolean useSkipScan = false;
+        //boolean useSkipScan = !forcedRangeScan && nBuckets != null;
         // Concat byte arrays of literals to form scan start key
         while (iterator.hasNext()) {
             KeyExpressionVisitor.KeySlot slot = iterator.next();
@@ -235,11 +235,14 @@ public class WhereOptimizer {
             // then we have to handle in the next phase through a key filter.
             // If the slot is null this means we have no entry for this pk 
position.
             if (slot == null || slot.getKeyRanges().isEmpty())  {
-                if (!forcedSkipScan || hasMultiColumnSpan) break;
                 continue;
             }
             if (slot.getPKPosition() != pkPos) {
-                if (!forcedSkipScan || hasMultiColumnSpan) break;
+                if (!forcedSkipScan) {
+                    stopExtracting = true;
+                } else {
+                    useSkipScan |= !stopExtracting && !forcedRangeScan && 
forcedSkipScan;
+                }
                 for (int i=pkPos; i < slot.getPKPosition(); i++) {
                     
cnf.add(Collections.singletonList(KeyRange.EVERYTHING_RANGE));
                 }
@@ -247,29 +250,36 @@ public class WhereOptimizer {
             KeyPart keyPart = slot.getKeyPart();
             slotSpan[cnf.size()] = slot.getPKSpan() - 1;
             pkPos = slot.getPKPosition() + slot.getPKSpan();
-            hasMultiColumnSpan |= slot.getPKSpan() > 1;
             // Skip span-1 slots as we skip one at the top of the loop
             for (int i = 1; i < slot.getPKSpan() && iterator.hasNext(); i++) {
                 iterator.next();
             }
             List<KeyRange> keyRanges = slot.getKeyRanges();
-            for (int i = 0; (!hasUnboundedRange || !hasNonPointKey) && i < 
keyRanges.size(); i++) {
+            cnf.add(keyRanges);
+            
+            // TODO: when stats are available, we may want to use a skip scan 
if the
+            // cardinality of this slot is low.
+            /*
+             *  Stop extracting nodes once we encounter:
+             *  1) An unbound range unless we're forcing a skip scan and 
havn't encountered
+             *     a multi-column span. Even if we're trying to force a skip 
scan, we can't
+             *     execute it over a multi-column span.
+             *  2) A non range key as we can extract the first one, but 
further ones need
+             *     to be evaluated in a filter.
+             */
+            stopExtracting |= (hasUnboundedRange && !forcedSkipScan) || 
(hasRangeKey && forcedRangeScan);
+            useSkipScan |= !stopExtracting && !forcedRangeScan && 
(keyRanges.size() > 1 || hasRangeKey);
+            
+            for (int i = 0; (!hasUnboundedRange || !hasRangeKey) && i < 
keyRanges.size(); i++) {
                 KeyRange range  = keyRanges.get(i);
                 if (range.isUnbound()) {
-                    hasUnboundedRange = hasNonPointKey = true;
+                    hasUnboundedRange = hasRangeKey = true;
                 } else if (!range.isSingleKey()) {
-                    hasNonPointKey = true;
+                    hasRangeKey = true;
                 }
             }
             
             hasMultiRanges |= keyRanges.size() > 1;
-            // Force a range scan if we've encountered a multi-span slot (i.e. 
RVC)
-            // and a non point key, as our skip scan only handles fully 
qualified
-            // RVC in our skip scan. This will force us to not extract nodes 
any
-            // longer as well.
-            // TODO: consider ending loop here if true.
-            forcedRangeScan |= (hasMultiColumnSpan && hasNonPointKey);
-            cnf.add(keyRanges);
             
             // We cannot extract if we have multiple ranges and are forcing a 
range scan.
             stopExtracting |= forcedRangeScan && hasMultiRanges;
@@ -282,36 +292,14 @@ public class WhereOptimizer {
             // that, so must filter on the remaining conditions (see issue 
#467).
             if (!stopExtracting) {
                 List<Expression> nodesToExtract = keyPart.getExtractNodes();
-                // Detect case of a RVC used in a range. We do not want to
-                // remove these from the extract nodes.
-                if (hasMultiColumnSpan && !hasUnboundedRange) {
-                    if (removeFromExtractNodes == null) {
-                        removeFromExtractNodes = 
Lists.newArrayListWithExpectedSize(nodesToExtract.size() + 
table.getPKColumns().size() - pkPos);
-                    }
-                    removeFromExtractNodes.addAll(nodesToExtract);
-                }
                 extractNodes.addAll(nodesToExtract);
             }
-            /*
-             *  Stop building start/stop key once we encounter An unbound 
range unless we're
-             *  forcing a skip scan and havn't encountered a multi-column 
span. Even if we're
-             *  trying to force a skip scan, we can't execute it over a 
multi-column span.
-             */
-            if (hasUnboundedRange && (!forcedSkipScan || hasMultiColumnSpan)) {
-                // TODO: when stats are available, we may want to continue 
this loop if the
-                // cardinality of this slot is low. We could potentially even 
continue this
-                // loop in the absence of a range for a key slot.
-                break;
-            }
-            // Set stopExtracting if we're forcing a range scan and have 
anything other than
-            // an equality constraint. We can extract the first one, but no 
future ones.
-            stopExtracting |= forcedRangeScan && hasNonPointKey;
         }
         // If we have fully qualified point keys with multi-column spans (i.e. 
RVC),
         // we can still use our skip scan. The ScanRanges.create() call will 
explode
         // out the keys.
         slotSpan = Arrays.copyOf(slotSpan, cnf.size());
-        ScanRanges scanRanges = ScanRanges.create(schema, cnf, slotSpan, 
minMaxRange, forcedRangeScan, nBuckets);
+        ScanRanges scanRanges = ScanRanges.create(schema, cnf, slotSpan, 
minMaxRange, nBuckets, useSkipScan);
         context.setScanRanges(scanRanges);
         if (whereClause == null) {
             return null;
@@ -332,8 +320,9 @@ public class WhereOptimizer {
     public static boolean getKeyExpressionCombination(List<Expression> result, 
StatementContext context, FilterableStatement statement, List<Expression> 
expressions) throws SQLException {
         List<Integer> candidateIndexes = Lists.newArrayList();
         final List<Integer> pkPositions = Lists.newArrayList();
+        PTable table = context.getCurrentTable().getTable();
         for (int i = 0; i < expressions.size(); i++) {
-            KeyExpressionVisitor visitor = new KeyExpressionVisitor(context, 
context.getCurrentTable().getTable());
+            KeyExpressionVisitor visitor = new KeyExpressionVisitor(context, 
table);
             KeyExpressionVisitor.KeySlots keySlots = 
expressions.get(i).accept(visitor);
             int minPkPos = Integer.MAX_VALUE; 
             if (keySlots != null) {
@@ -376,6 +365,7 @@ public class WhereOptimizer {
         }
         
         int count = 0;
+        int offset = table.getBucketNum() == null ? 0 : 
SaltingUtil.NUM_SALTING_BYTES;
         int maxPkSpan = 0;
         Expression remaining = null;
         while (count < candidates.size()) {
@@ -388,7 +378,7 @@ public class WhereOptimizer {
                 count++;
                 break; // found the best match
             }
-            int pkSpan = context.getScanRanges().getPkColumnSpan();
+            int pkSpan = context.getScanRanges().getBoundPkColumnCount() - 
offset;
             if (pkSpan <= maxPkSpan) {
                 break;
             }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/d18afe18/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/MetaDataEndpointImpl.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/MetaDataEndpointImpl.java
 
b/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/MetaDataEndpointImpl.java
index 13788e1..001615b 100644
--- 
a/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/MetaDataEndpointImpl.java
+++ 
b/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/MetaDataEndpointImpl.java
@@ -182,15 +182,12 @@ import org.apache.phoenix.util.IndexUtil;
 import org.apache.phoenix.util.KeyValueUtil;
 import org.apache.phoenix.util.MetaDataUtil;
 import org.apache.phoenix.util.QueryUtil;
-import org.apache.phoenix.util.ScanUtil;
 import org.apache.phoenix.util.SchemaUtil;
 import org.apache.phoenix.util.ServerUtil;
 import org.apache.phoenix.util.UpgradeUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.base.Objects;
-import com.google.common.base.Preconditions;
 import com.google.common.cache.Cache;
 import com.google.common.collect.Lists;
 import com.google.protobuf.ByteString;
@@ -498,9 +495,7 @@ public class MetaDataEndpointImpl extends MetaDataProtocol 
implements Coprocesso
         }
         Scan scan = new Scan();
         scan.setTimeRange(MIN_TABLE_TIMESTAMP, clientTimeStamp);
-        ScanRanges scanRanges =
-                ScanRanges.create(SchemaUtil.VAR_BINARY_SCHEMA,
-                    Collections.singletonList(keyRanges), 
ScanUtil.SINGLE_COLUMN_SLOT_SPAN);
+        ScanRanges scanRanges = ScanRanges.createPointLookup(keyRanges);
         scanRanges.initializeScan(scan);
         scan.setFilter(scanRanges.getSkipScanFilter());
 

http://git-wip-us.apache.org/repos/asf/phoenix/blob/d18afe18/phoenix-core/src/main/java/org/apache/phoenix/filter/SkipScanFilter.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/filter/SkipScanFilter.java 
b/phoenix-core/src/main/java/org/apache/phoenix/filter/SkipScanFilter.java
index ff58a18..d1c8532 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/filter/SkipScanFilter.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/filter/SkipScanFilter.java
@@ -42,6 +42,8 @@ import org.apache.phoenix.util.ByteUtil;
 import org.apache.phoenix.util.ScanUtil;
 import org.apache.phoenix.util.ScanUtil.BytesComparator;
 import org.apache.phoenix.util.SchemaUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Objects;
 import com.google.common.collect.Lists;
@@ -60,6 +62,8 @@ import com.google.common.hash.Hashing;
  * @since 0.1
  */
 public class SkipScanFilter extends FilterBase implements Writable {
+    private static final Logger logger = 
LoggerFactory.getLogger(SkipScanFilter.class);
+
     private enum Terminate {AT, AFTER};
     // Conjunctive normal form of or-ed ranges or point lookups
     private List<List<KeyRange>> slots;
@@ -151,11 +155,15 @@ public class SkipScanFilter extends FilterBase implements 
Writable {
         }
         Cell previousCellHint = nextCellHintMap.put(family, nextCellHint);
         // we should either have no previous hint, or the next hint should 
always come after the previous hint
-        assert previousCellHint == null
+        boolean isHintAfterPrevious = previousCellHint == null
                 || Bytes.compareTo(nextCellHint.getRowArray(), 
nextCellHint.getRowOffset(),
-                    nextCellHint.getRowLength(), 
previousCellHint.getRowArray(), previousCellHint
-                            .getRowOffset(), previousCellHint.getRowLength()) 
> 0 : "next hint must come after previous hint (prev="
-                + previousCellHint + ", next=" + nextCellHint + ", kv=" + kv + 
")";
+                        nextCellHint.getRowLength(), 
previousCellHint.getRowArray(), previousCellHint
+                                .getRowOffset(), 
previousCellHint.getRowLength()) > 0;
+        if (!isHintAfterPrevious) {
+            String msg = "The next hint must come after previous hint (prev=" 
+ previousCellHint + ", next=" + nextCellHint + ", kv=" + kv + ")";
+            assert isHintAfterPrevious : msg;
+            logger.warn(msg);
+        }
     }
     
     @Override
@@ -431,7 +439,7 @@ public class SkipScanFilter extends FilterBase implements 
Writable {
                     setStartKey();
                     schema.reposition(ptr, 
ScanUtil.getRowKeyPosition(slotSpan, i), ScanUtil.getRowKeyPosition(slotSpan, 
j), minOffset, maxOffset, slotSpan[j]);
                 } else {
-                    int currentLength = setStartKey(ptr, minOffset, j+1);
+                    int currentLength = setStartKey(ptr, minOffset, j+1, 
nSlots);
                     // From here on, we use startKey as our buffer (resetting 
minOffset and maxOffset)
                     // We've copied the part of the current key above that we 
need into startKey
                     // Reinitialize the iterator to be positioned at previous 
slot position
@@ -446,7 +454,7 @@ public class SkipScanFilter extends FilterBase implements 
Writable {
             } else if 
(slots.get(i).get(position[i]).compareLowerToUpperBound(ptr, comparator) > 0) {
                 // Our current key is less than the lower range of the current 
position in the current slot.
                 // Seek to the lower range, since it's bigger than the current 
key
-                setStartKey(ptr, minOffset, i);
+                setStartKey(ptr, minOffset, i, nSlots);
                 return ReturnCode.SEEK_NEXT_USING_HINT;
             } else { // We're in range, check the next slot
                 if (!slots.get(i).get(position[i]).isSingleKey() && i < 
earliestRangeIndex) {
@@ -469,7 +477,7 @@ public class SkipScanFilter extends FilterBase implements 
Writable {
                         break;
                     }
                     // Otherwise we seek to the next start key because we're 
before it now
-                    setStartKey(ptr, minOffset, i);
+                    setStartKey(ptr, minOffset, i, nSlots);
                     return ReturnCode.SEEK_NEXT_USING_HINT;
                 }
             }
@@ -513,12 +521,12 @@ public class SkipScanFilter extends FilterBase implements 
Writable {
         startKeyLength = setKey(Bound.LOWER, startKey, 0, 0);
     }
 
-    private int setStartKey(ImmutableBytesWritable ptr, int offset, int i) {
+    private int setStartKey(ImmutableBytesWritable ptr, int offset, int i, int 
nSlots) {
         int length = ptr.getOffset() - offset;
         startKey = copyKey(startKey, length + this.maxKeyLength, ptr.get(), 
offset, length);
         startKeyLength = length;
-        // Add separator byte if we're at the end of the buffer, since 
trailing separator bytes are stripped
-        if (ptr.getOffset() + ptr.getLength() == offset + length && i-1 > 0 && 
!schema.getField(i-1).getDataType().isFixedWidth()) {
+        // Add separator byte if we're at end of the key, since trailing 
separator bytes are stripped
+        if (ptr.getLength() == 0 && i > 0 && i-1 < nSlots && 
!schema.getField(i-1).getDataType().isFixedWidth()) {
             startKey[startKeyLength++] = 
SchemaUtil.getSeparatorByte(schema.rowKeyOrderOptimizable(), 
ptr.getLength()==0, schema.getField(i-1));
         }
         startKeyLength += setKey(Bound.LOWER, startKey, startKeyLength, i);

http://git-wip-us.apache.org/repos/asf/phoenix/blob/d18afe18/phoenix-core/src/main/java/org/apache/phoenix/index/PhoenixIndexBuilder.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/index/PhoenixIndexBuilder.java 
b/phoenix-core/src/main/java/org/apache/phoenix/index/PhoenixIndexBuilder.java
index 7a45e21..3ef01fe 100644
--- 
a/phoenix-core/src/main/java/org/apache/phoenix/index/PhoenixIndexBuilder.java
+++ 
b/phoenix-core/src/main/java/org/apache/phoenix/index/PhoenixIndexBuilder.java
@@ -19,7 +19,6 @@ package org.apache.phoenix.index;
 
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -36,8 +35,6 @@ import 
org.apache.phoenix.hbase.index.covered.CoveredColumnsIndexBuilder;
 import org.apache.phoenix.hbase.index.util.IndexManagementUtil;
 import org.apache.phoenix.query.KeyRange;
 import org.apache.phoenix.schema.types.PVarbinary;
-import org.apache.phoenix.util.ScanUtil;
-import org.apache.phoenix.util.SchemaUtil;
 
 import com.google.common.collect.Lists;
 
@@ -70,7 +67,7 @@ public class PhoenixIndexBuilder extends 
CoveredColumnsIndexBuilder {
         }
         if (maintainers.isEmpty()) return;
         Scan scan = IndexManagementUtil.newLocalStateScan(new 
ArrayList<IndexMaintainer>(maintainers.values()));
-        ScanRanges scanRanges = 
ScanRanges.create(SchemaUtil.VAR_BINARY_SCHEMA, 
Collections.singletonList(keys), ScanUtil.SINGLE_COLUMN_SLOT_SPAN);
+        ScanRanges scanRanges = ScanRanges.createPointLookup(keys);
         scanRanges.initializeScan(scan);
         scan.setFilter(scanRanges.getSkipScanFilter());
         Region region = this.env.getRegion();

http://git-wip-us.apache.org/repos/asf/phoenix/blob/d18afe18/phoenix-core/src/main/java/org/apache/phoenix/iterate/ExplainTable.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/iterate/ExplainTable.java 
b/phoenix-core/src/main/java/org/apache/phoenix/iterate/ExplainTable.java
index cf19a6c..da81dd2 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/ExplainTable.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/ExplainTable.java
@@ -82,7 +82,9 @@ public abstract class ExplainTable {
             buf.append("SKIP SCAN ");
             int count = 1;
             boolean hasRanges = false;
-            for (List<KeyRange> ranges : scanRanges.getRanges()) {
+            int nSlots = scanRanges.getBoundSlotCount();
+            for (int i = 0; i < nSlots; i++) {
+                List<KeyRange> ranges = scanRanges.getRanges().get(i);
                 count *= ranges.size();
                 for (KeyRange range : ranges) {
                     hasRanges |= !range.isSingleKey();
@@ -242,7 +244,8 @@ public abstract class ExplainTable {
                 minMaxIterator = new RowKeyValueIterator(schema, 
minMaxRange.getRange(bound));
             }
         }
-        int nRanges = scanRanges.getRanges().size();
+        boolean forceSkipScan = this.hint.hasHint(Hint.SKIP_SCAN);
+        int nRanges = forceSkipScan ? scanRanges.getRanges().size() : 
scanRanges.getBoundSlotCount();
         for (int i = 0, minPos = 0; minPos < nRanges || 
minMaxIterator.hasNext(); i++) {
             List<KeyRange> ranges = minPos >= nRanges ? EVERYTHING :  
scanRanges.getRanges().get(minPos++);
             KeyRange range = bound == Bound.LOWER ? ranges.get(0) : 
ranges.get(ranges.size()-1);

http://git-wip-us.apache.org/repos/asf/phoenix/blob/d18afe18/phoenix-core/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java 
b/phoenix-core/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java
index 99ca46e..bc318d2 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java
@@ -40,9 +40,9 @@ import org.apache.phoenix.parse.AliasedNode;
 import org.apache.phoenix.parse.AndParseNode;
 import org.apache.phoenix.parse.BooleanParseNodeVisitor;
 import org.apache.phoenix.parse.ColumnParseNode;
-import org.apache.phoenix.parse.IndexExpressionParseNodeRewriter;
 import org.apache.phoenix.parse.HintNode;
 import org.apache.phoenix.parse.HintNode.Hint;
+import org.apache.phoenix.parse.IndexExpressionParseNodeRewriter;
 import org.apache.phoenix.parse.ParseNode;
 import org.apache.phoenix.parse.ParseNodeFactory;
 import org.apache.phoenix.parse.ParseNodeRewriter;
@@ -384,8 +384,8 @@ public class QueryOptimizer {
                 // For shared indexes (i.e. indexes on views and local 
indexes),
                 // a) add back any view constants as these won't be in the 
index, and
                 // b) ignore the viewIndexId which will be part of the row key 
columns.
-                int c = (plan2.getContext().getScanRanges().getRanges().size() 
+ (table2.getViewIndexId() == null ? 0 : (boundRanges - 1))) - 
-                        (plan1.getContext().getScanRanges().getRanges().size() 
+ (table1.getViewIndexId() == null ? 0 : (boundRanges - 1)));
+                int c = 
(plan2.getContext().getScanRanges().getBoundPkColumnCount() + 
(table2.getViewIndexId() == null ? 0 : (boundRanges - 1))) - 
+                        
(plan1.getContext().getScanRanges().getBoundPkColumnCount() + 
(table1.getViewIndexId() == null ? 0 : (boundRanges - 1)));
                 if (c != 0) return c;
                 if (plan1.getGroupBy()!=null && plan2.getGroupBy()!=null) {
                     if (plan1.getGroupBy().isOrderPreserving() != 
plan2.getGroupBy().isOrderPreserving()) {

http://git-wip-us.apache.org/repos/asf/phoenix/blob/d18afe18/phoenix-core/src/main/java/org/apache/phoenix/util/ScanUtil.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/util/ScanUtil.java 
b/phoenix-core/src/main/java/org/apache/phoenix/util/ScanUtil.java
index 2a73e25..7b76a2b 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/util/ScanUtil.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/util/ScanUtil.java
@@ -25,7 +25,6 @@ import java.io.IOException;
 import java.sql.SQLException;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -48,7 +47,8 @@ import 
org.apache.phoenix.coprocessor.BaseScannerRegionObserver;
 import org.apache.phoenix.coprocessor.MetaDataProtocol;
 import org.apache.phoenix.exception.SQLExceptionCode;
 import org.apache.phoenix.exception.SQLExceptionInfo;
-import org.apache.phoenix.execute.DescVarLengthFastByteComparisons;import 
org.apache.phoenix.filter.BooleanExpressionFilter;
+import org.apache.phoenix.execute.DescVarLengthFastByteComparisons;
+import org.apache.phoenix.filter.BooleanExpressionFilter;
 import org.apache.phoenix.filter.SkipScanFilter;
 import org.apache.phoenix.query.KeyRange;
 import org.apache.phoenix.query.KeyRange.Bound;
@@ -57,7 +57,6 @@ import org.apache.phoenix.query.QueryServices;
 import org.apache.phoenix.query.QueryServicesOptions;
 import org.apache.phoenix.schema.IllegalDataException;
 import org.apache.phoenix.schema.PName;
-import org.apache.phoenix.schema.PNameFactory;
 import org.apache.phoenix.schema.RowKeySchema;
 import org.apache.phoenix.schema.SortOrder;
 import org.apache.phoenix.schema.ValueSchema.Field;
@@ -335,6 +334,7 @@ public class ScanUtil {
         int offset = byteOffset;
         boolean lastInclusiveUpperSingleKey = false;
         boolean anyInclusiveUpperRangeKey = false;
+        boolean lastUnboundUpper = false;
         // The index used for slots should be incremented by 1,
         // but the index for the field it represents in the schema
         // should be incremented by 1 + value in the current slotSpan index
@@ -348,7 +348,6 @@ public class ScanUtil {
             // Use last slot in a multi-span column to determine if fixed width
             field = schema.getField(fieldIndex + slotSpan[i]);
             boolean isFixedWidth = field.getDataType().isFixedWidth();
-            fieldIndex += slotSpan[i] + 1;
             /*
              * If the current slot is unbound then stop if:
              * 1) setting the upper bound. There's no value in
@@ -357,21 +356,27 @@ public class ScanUtil {
              *    for the same reason. However, if the type is variable width
              *    continue building the key because null values will be 
filtered
              *    since our separator byte will be appended and incremented.
+             * 3) if the range includes everything as we cannot add any more 
useful
+             *    information to the key after that.
              */
+            lastUnboundUpper = false;
             if (  range.isUnbound(bound) &&
-                ( bound == Bound.UPPER || isFixedWidth) ){
+                ( bound == Bound.UPPER || isFixedWidth || range == 
KeyRange.EVERYTHING_RANGE) ){
+                lastUnboundUpper = (bound == Bound.UPPER);
                 break;
             }
             byte[] bytes = range.getRange(bound);
             System.arraycopy(bytes, 0, key, offset, bytes.length);
             offset += bytes.length;
+            
             /*
              * We must add a terminator to a variable length key even for the 
last PK column if
              * the lower key is non inclusive or the upper key is inclusive. 
Otherwise, we'd be
              * incrementing the key value itself, and thus bumping it up too 
much.
              */
-            boolean inclusiveUpper = range.isInclusive(bound) && bound == 
Bound.UPPER;
-            boolean exclusiveLower = !range.isInclusive(bound) && bound == 
Bound.LOWER;
+            boolean inclusiveUpper = range.isUpperInclusive() && bound == 
Bound.UPPER;
+            boolean exclusiveLower = !range.isLowerInclusive() && bound == 
Bound.LOWER;
+            boolean exclusiveUpper = !range.isUpperInclusive() && bound == 
Bound.UPPER;
             // If we are setting the upper bound of using inclusive single 
key, we remember 
             // to increment the key if we exit the loop after this iteration.
             // 
@@ -387,12 +392,20 @@ public class ScanUtil {
             // A null or empty byte array is always represented as a zero byte
             byte sepByte = 
SchemaUtil.getSeparatorByte(schema.rowKeyOrderOptimizable(), bytes.length == 0, 
field);
             
-            if (!isFixedWidth && ( fieldIndex < schema.getMaxFields() || 
inclusiveUpper || exclusiveLower || sepByte == 
QueryConstants.DESC_SEPARATOR_BYTE)) {
+            if ( !isFixedWidth && ( sepByte == 
QueryConstants.DESC_SEPARATOR_BYTE 
+                                    || ( !exclusiveUpper 
+                                         && (fieldIndex < 
schema.getMaxFields() || inclusiveUpper || exclusiveLower) ) ) ) {
                 key[offset++] = sepByte;
                 // Set lastInclusiveUpperSingleKey back to false if this is 
the last pk column
                 // as we don't want to increment the null byte in this case
                 lastInclusiveUpperSingleKey &= i < schema.getMaxFields()-1;
             }
+            if (exclusiveUpper) {
+                // Cannot include anything else on the key, as otherwise
+                // keys that match the upper range will be included. For 
example WHERE k1 < 2 and k2 = 3
+                // would match k1 = 2, k2 = 3 which is wrong.
+                break;
+            }
             // If we are setting the lower bound with an exclusive range key, 
we need to bump the
             // slot up for each key part. For an upper bound, we bump up an 
inclusive key, but
             // only after the last key part.
@@ -414,8 +427,10 @@ public class ScanUtil {
                     key[offset++] = QueryConstants.DESC_SEPARATOR_BYTE;
                 }
             }
+            
+            fieldIndex += slotSpan[i] + 1;
         }
-        if (lastInclusiveUpperSingleKey || anyInclusiveUpperRangeKey) {
+        if (lastInclusiveUpperSingleKey || anyInclusiveUpperRangeKey || 
lastUnboundUpper) {
             if (!ByteUtil.nextKey(key, offset)) {
                 // Special case for not being able to increment.
                 // In this case we return a negative byteOffset to
@@ -501,7 +516,7 @@ public class ScanUtil {
         for (Mutation m : mutations) {
             keys.add(PVarbinary.INSTANCE.getKeyRange(m.getRow()));
         }
-        ScanRanges keyRanges = ScanRanges.create(SchemaUtil.VAR_BINARY_SCHEMA, 
Collections.singletonList(keys), ScanUtil.SINGLE_COLUMN_SLOT_SPAN);
+        ScanRanges keyRanges = ScanRanges.createPointLookup(keys);
         return keyRanges;
     }
 
@@ -627,19 +642,6 @@ public class ScanUtil {
     }
 
     /**
-     * Finds the total number of row keys spanned by this ranges / slotSpan 
pair.
-     * This accounts for slots in the ranges that may span more than on row 
key.
-     * @param ranges  the KeyRange slots paired with this slotSpan. 
corresponds to {@link ScanRanges#ranges}
-     * @param slotSpan  the extra span per skip scan slot. corresponds to 
{@link ScanRanges#slotSpan}
-     * @return  the total number of row keys spanned yb this ranges / slotSpan 
pair.
-     * @see #getRowKeyPosition(int[], int)
-     */
-    public static int getTotalSpan(List<List<KeyRange>> ranges, int[] 
slotSpan) {
-        // finds the position at the "end" of the ranges, which is also the 
total span
-        return getRowKeyPosition(slotSpan, ranges.size());
-    }
-
-    /**
      * Finds the position in the row key schema for a given position in the 
scan slots.
      * For example, with a slotSpan of {0, 1, 0}, the slot at index 1 spans an 
extra column in the row key. This means
      * that the slot at index 2 has a slot index of 2 but a row key index of 3.
@@ -648,7 +650,6 @@ public class ScanUtil {
      * @param slotSpan  the extra span per skip scan slot. corresponds to 
{@link ScanRanges#slotSpan}
      * @param slotPosition  the index of a slot in the SkipScan slots list.
      * @return  the equivalent row key position in the RowKeySchema
-     * @see #getTotalSpan(java.util.List, int[])
      */
     public static int getRowKeyPosition(int[] slotSpan, int slotPosition) {
         int offset = 0;

http://git-wip-us.apache.org/repos/asf/phoenix/blob/d18afe18/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryCompilerTest.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryCompilerTest.java 
b/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryCompilerTest.java
index 6225c6b..076b49e 100644
--- 
a/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryCompilerTest.java
+++ 
b/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryCompilerTest.java
@@ -469,7 +469,7 @@ public class QueryCompilerTest extends 
BaseConnectionlessQueryTest {
         List<Object> binds = Collections.emptyList();
         for (String query : queries) {
             QueryPlan plan = getQueryPlan(query, binds);
-            assertEquals(plan.getGroupBy().getScanAttribName(), 
BaseScannerRegionObserver.KEY_ORDERED_GROUP_BY_EXPRESSIONS);
+            assertEquals(query, 
BaseScannerRegionObserver.KEY_ORDERED_GROUP_BY_EXPRESSIONS, 
plan.getGroupBy().getScanAttribName());
         }
     }
 
@@ -864,15 +864,15 @@ public class QueryCompilerTest extends 
BaseConnectionlessQueryTest {
         String query = "SELECT host FROM ptsdb WHERE regexp_substr(inst, 
'[a-zA-Z]+') = 'abc'";
         List<Object> binds = Collections.emptyList();
         Scan scan = compileQuery(query, binds);
-        assertArrayEquals(ByteUtil.concat(Bytes.toBytes("abc")), 
scan.getStartRow());
-        
assertArrayEquals(ByteUtil.concat(ByteUtil.nextKey(Bytes.toBytes("abc")), 
QueryConstants.SEPARATOR_BYTE_ARRAY),scan.getStopRow());
+        assertArrayEquals(Bytes.toBytes("abc"), scan.getStartRow());
+        
assertArrayEquals(ByteUtil.nextKey(Bytes.toBytes("abc")),scan.getStopRow());
         assertTrue(scan.getFilter() != null);
 
         query = "SELECT host FROM ptsdb WHERE regexp_substr(inst, '[a-zA-Z]+', 
0) = 'abc'";
         binds = Collections.emptyList();
         scan = compileQuery(query, binds);
-        assertArrayEquals(ByteUtil.concat(Bytes.toBytes("abc")), 
scan.getStartRow());
-        
assertArrayEquals(ByteUtil.concat(ByteUtil.nextKey(Bytes.toBytes("abc")),QueryConstants.SEPARATOR_BYTE_ARRAY),
 scan.getStopRow());
+        assertArrayEquals(Bytes.toBytes("abc"), scan.getStartRow());
+        assertArrayEquals(ByteUtil.nextKey(Bytes.toBytes("abc")), 
scan.getStopRow());
         assertTrue(scan.getFilter() != null);
 
         // Test scan keys are not set when the offset is not 0 or 1.
@@ -980,7 +980,7 @@ public class QueryCompilerTest extends 
BaseConnectionlessQueryTest {
         List<Object> binds = Collections.emptyList();
         Scan scan = compileQuery(query, binds);
         assertArrayEquals(Bytes.toBytes("abc"), scan.getStartRow());
-        
assertArrayEquals(ByteUtil.concat(ByteUtil.nextKey(Bytes.toBytes("abc")), 
QueryConstants.SEPARATOR_BYTE_ARRAY), scan.getStopRow());
+        assertArrayEquals(ByteUtil.nextKey(Bytes.toBytes("abc")), 
scan.getStopRow());
         assertTrue(scan.getFilter() == null); // Extracted.
     }
 
@@ -989,8 +989,8 @@ public class QueryCompilerTest extends 
BaseConnectionlessQueryTest {
         String query = "SELECT inst FROM ptsdb WHERE rtrim(inst) = 'abc'";
         List<Object> binds = Collections.emptyList();
         Scan scan = compileQuery(query, binds);
-        assertArrayEquals(ByteUtil.concat(Bytes.toBytes("abc")), 
scan.getStartRow());
-        assertArrayEquals(ByteUtil.concat(ByteUtil.nextKey(Bytes.toBytes("abc 
")), QueryConstants.SEPARATOR_BYTE_ARRAY), scan.getStopRow());
+        assertArrayEquals(Bytes.toBytes("abc"), scan.getStartRow());
+        assertArrayEquals(ByteUtil.nextKey(Bytes.toBytes("abc ")), 
scan.getStopRow());
         assertNotNull(scan.getFilter());
     }
     
@@ -1732,6 +1732,7 @@ public class QueryCompilerTest extends 
BaseConnectionlessQueryTest {
         Connection conn = DriverManager.getConnection(getUrl());
         conn.createStatement().execute("CREATE TABLE t (k1 date not null, k2 
date not null, k3 varchar, v varchar, constraint pk primary key(k1,k2,k3))");
         String[] queries = {
+                "SELECT * FROM T WHERE k2=CURRENT_DATE() ORDER BY k1, k3",
                 "SELECT * FROM T ORDER BY (k1,k2), k3",
                 "SELECT * FROM T ORDER BY k1,k2,k3 NULLS FIRST",
                 "SELECT * FROM T ORDER BY k1,k2,k3",
@@ -1741,6 +1742,7 @@ public class QueryCompilerTest extends 
BaseConnectionlessQueryTest {
                 "SELECT * FROM T ORDER BY (k1,k2,k3)",
                 "SELECT * FROM T ORDER BY TRUNC(k1, 'DAY'), CEIL(k2, 'HOUR')",
                 "SELECT * FROM T ORDER BY INVERT(k1) DESC",
+                "SELECT * FROM T WHERE k1=CURRENT_DATE() ORDER BY k2",
                 };
         String query;
         for (int i = 0; i < queries.length; i++) {

http://git-wip-us.apache.org/repos/asf/phoenix/blob/d18afe18/phoenix-core/src/test/java/org/apache/phoenix/compile/ScanRangesIntersectTest.java
----------------------------------------------------------------------
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 e5a9878..c2bbc06 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
@@ -29,13 +29,7 @@ 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.types.PDataType;
-import org.apache.phoenix.schema.PDatum;
 import org.apache.phoenix.schema.types.PVarchar;
-import org.apache.phoenix.schema.RowKeySchema;
-import org.apache.phoenix.schema.RowKeySchema.RowKeySchemaBuilder;
-import org.apache.phoenix.schema.SortOrder;
-import org.apache.phoenix.util.ScanUtil;
 import org.junit.Test;
 
 import com.google.common.collect.Lists;
@@ -44,10 +38,8 @@ public class ScanRangesIntersectTest {
 
     @Test
     public void testPointLookupIntersect() throws Exception {
-        RowKeySchema schema = schema();
-        int[] slotSpan = ScanUtil.SINGLE_COLUMN_SLOT_SPAN;
         List<KeyRange> keys = points("a","j","m","z");
-        ScanRanges ranges = ScanRanges.create(schema, 
Collections.singletonList(keys), slotSpan);
+        ScanRanges ranges = ScanRanges.createPointLookup(keys);
         assertIntersect(ranges, "b", "l", "j");
         
     }
@@ -76,31 +68,4 @@ public class ScanRangesIntersectTest {
         }
         return keys;
     }
-    
-    private static RowKeySchema schema() {
-        RowKeySchemaBuilder builder = new RowKeySchemaBuilder(1);
-        builder.addField(new PDatum() {
-            @Override
-            public boolean isNullable() {
-                return false;
-            }
-            @Override
-            public PDataType getDataType() {
-                return PVarchar.INSTANCE;
-            }
-            @Override
-            public Integer getMaxLength() {
-                return null;
-            }
-            @Override
-            public Integer getScale() {
-                return null;
-            }
-            @Override
-            public SortOrder getSortOrder() {
-                return SortOrder.getDefault();
-            }
-        }, false, SortOrder.getDefault());
-        return builder.build();
-    }
 }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/d18afe18/phoenix-core/src/test/java/org/apache/phoenix/compile/ScanRangesTest.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/test/java/org/apache/phoenix/compile/ScanRangesTest.java 
b/phoenix-core/src/test/java/org/apache/phoenix/compile/ScanRangesTest.java
index b5d20ab..0292244 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/compile/ScanRangesTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/compile/ScanRangesTest.java
@@ -31,7 +31,6 @@ import org.apache.phoenix.schema.types.PVarchar;
 import org.apache.phoenix.schema.RowKeySchema.RowKeySchemaBuilder;
 import org.apache.phoenix.schema.SortOrder;
 import org.apache.phoenix.util.ByteUtil;
-import org.apache.phoenix.util.ScanUtil;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -525,7 +524,7 @@ public class ScanRangesTest {
                 }, false, SortOrder.getDefault());
             }
         }
-        ScanRanges scanRanges = ScanRanges.create(builder.build(), slots, 
ScanUtil.getDefaultSlotSpans(slots.size()));
+        ScanRanges scanRanges = ScanRanges.createSingleSpan(builder.build(), 
slots);
         return foreach(scanRanges, widths, keyRange, expectedResult);
     }
 

http://git-wip-us.apache.org/repos/asf/phoenix/blob/d18afe18/phoenix-core/src/test/java/org/apache/phoenix/compile/TenantSpecificViewIndexCompileTest.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/test/java/org/apache/phoenix/compile/TenantSpecificViewIndexCompileTest.java
 
b/phoenix-core/src/test/java/org/apache/phoenix/compile/TenantSpecificViewIndexCompileTest.java
index 6d735f9..63e513b 100644
--- 
a/phoenix-core/src/test/java/org/apache/phoenix/compile/TenantSpecificViewIndexCompileTest.java
+++ 
b/phoenix-core/src/test/java/org/apache/phoenix/compile/TenantSpecificViewIndexCompileTest.java
@@ -21,10 +21,15 @@ import static org.junit.Assert.assertEquals;
 
 import java.sql.Connection;
 import java.sql.DriverManager;
+import java.sql.PreparedStatement;
 import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Calendar;
 import java.util.Properties;
+import java.util.TimeZone;
 
 import org.apache.phoenix.query.BaseConnectionlessQueryTest;
+import org.apache.phoenix.util.DateUtil;
 import org.apache.phoenix.util.PhoenixRuntime;
 import org.apache.phoenix.util.QueryUtil;
 import org.junit.Test;
@@ -50,6 +55,132 @@ public class TenantSpecificViewIndexCompileTest extends 
BaseConnectionlessQueryT
     }
 
     @Test
+    public void testOrderByOptimizedOutWithoutPredicateInView() throws 
Exception {
+
+        Connection conn = DriverManager.getConnection(getUrl());
+        conn.createStatement().execute("CREATE TABLE t(t_id CHAR(15) NOT NULL, 
k1 CHAR(3) NOT NULL, k2 CHAR(15) NOT NULL, k3 DATE NOT NULL, v1 VARCHAR," +
+                " CONSTRAINT pk PRIMARY KEY(t_id, k1, k2, k3)) 
multi_tenant=true");
+        conn.createStatement().execute("CREATE VIEW v1  AS SELECT * FROM t");
+
+        conn = createTenantSpecificConnection();
+        
+        // Query without predicate ordered by full row key
+        String sql = "SELECT * FROM v1 ORDER BY k1, k2, k3";
+        String expectedExplainOutput = "CLIENT PARALLEL 1-WAY RANGE SCAN OVER 
T ['tenant123456789']"; 
+        assertExplainPlanIsCorrect(conn, sql, expectedExplainOutput);
+        assertOrderByHasBeenOptimizedOut(conn, sql);
+
+        // Predicate with valid partial PK
+        sql = "SELECT * FROM v1 WHERE k1 = 'xyz' ORDER BY k1, k2, k3";
+        expectedExplainOutput = "CLIENT PARALLEL 1-WAY RANGE SCAN OVER T 
['tenant123456789','xyz']";
+        assertExplainPlanIsCorrect(conn, sql, expectedExplainOutput);
+        assertOrderByHasBeenOptimizedOut(conn, sql);
+        
+        sql = "SELECT * FROM v1 WHERE k1 > 'xyz' ORDER BY k1, k2, k3";
+        expectedExplainOutput = "CLIENT PARALLEL 1-WAY RANGE SCAN OVER T 
['tenant123456789','xy{'] - ['tenant123456789',*]";
+        assertExplainPlanIsCorrect(conn, sql, expectedExplainOutput);
+        assertOrderByHasBeenOptimizedOut(conn, sql);
+
+        String datePredicate = createStaticDate();
+        sql = "SELECT * FROM v1 WHERE k1 = 'xyz' AND k2 = '123456789012345' 
AND k3 < TO_DATE('" + datePredicate + "') ORDER BY k1, k2, k3";
+        expectedExplainOutput = "CLIENT PARALLEL 1-WAY RANGE SCAN OVER T 
['tenant123456789','xyz','123456789012345',*] - 
['tenant123456789','xyz','123456789012345','2015-01-01 08:00:00.000']";
+        assertExplainPlanIsCorrect(conn, sql, expectedExplainOutput);
+        assertOrderByHasBeenOptimizedOut(conn, sql);
+
+        
+        // Predicate without valid partial PK
+        sql = "SELECT * FROM v1 WHERE k2 < 'abcde1234567890' ORDER BY k1, k2, 
k3";
+        expectedExplainOutput = "CLIENT PARALLEL 1-WAY RANGE SCAN OVER T 
['tenant123456789']\n" +
+                "    SERVER FILTER BY K2 < 'abcde1234567890'";
+        assertExplainPlanIsCorrect(conn, sql, expectedExplainOutput);
+        assertOrderByHasBeenOptimizedOut(conn, sql);
+    }
+    
+    @Test
+    public void testOrderByOptimizedOutWithPredicateInView() throws Exception {
+        // Arrange
+        Connection conn = DriverManager.getConnection(getUrl());
+        conn.createStatement().execute("CREATE TABLE t(t_id CHAR(15) NOT NULL, 
k1 CHAR(3) NOT NULL, k2 CHAR(15) NOT NULL, k3 DATE NOT NULL, v1 VARCHAR," +
+                " CONSTRAINT pk PRIMARY KEY(t_id, k1, k2, k3)) 
multi_tenant=true");
+        conn.createStatement().execute("CREATE VIEW v1  AS SELECT * FROM t 
WHERE k1 = 'xyz'");
+        conn = createTenantSpecificConnection();
+
+        // Query without predicate ordered by full row key
+        String sql = "SELECT * FROM v1 ORDER BY k2, k3";
+        String expectedExplainOutput = "CLIENT PARALLEL 1-WAY RANGE SCAN OVER 
T ['tenant123456789','xyz']"; 
+        assertExplainPlanIsCorrect(conn, sql, expectedExplainOutput);
+        assertOrderByHasBeenOptimizedOut(conn, sql);
+        
+        // Query without predicate ordered by full row key, but without column 
view predicate
+        sql = "SELECT * FROM v1 ORDER BY k2, k3";
+        expectedExplainOutput = "CLIENT PARALLEL 1-WAY RANGE SCAN OVER T 
['tenant123456789','xyz']"; 
+        assertExplainPlanIsCorrect(conn, sql, expectedExplainOutput);
+        assertOrderByHasBeenOptimizedOut(conn, sql);
+        
+        // Predicate with valid partial PK
+        sql = "SELECT * FROM v1 WHERE k1 = 'xyz' ORDER BY k2, k3";
+        expectedExplainOutput = "CLIENT PARALLEL 1-WAY RANGE SCAN OVER T 
['tenant123456789','xyz']";
+        assertExplainPlanIsCorrect(conn, sql, expectedExplainOutput);
+        assertOrderByHasBeenOptimizedOut(conn, sql);
+
+        sql = "SELECT * FROM v1 WHERE k2 < 'abcde1234567890' ORDER BY k2, k3";
+        expectedExplainOutput = "CLIENT PARALLEL 1-WAY RANGE SCAN OVER T 
['tenant123456789','xyz',*] - ['tenant123456789','xyz','abcde1234567890']";
+        assertExplainPlanIsCorrect(conn, sql, expectedExplainOutput);
+        assertOrderByHasBeenOptimizedOut(conn, sql);
+
+        // Predicate with full PK
+        String datePredicate = createStaticDate();
+        sql = "SELECT * FROM v1 WHERE k2 = '123456789012345' AND k3 < 
TO_DATE('" + datePredicate + "') ORDER BY k2, k3";
+        expectedExplainOutput = "CLIENT PARALLEL 1-WAY RANGE SCAN OVER T 
['tenant123456789','xyz','123456789012345',*] - 
['tenant123456789','xyz','123456789012345','2015-01-01 08:00:00.000']";
+        assertExplainPlanIsCorrect(conn, sql, expectedExplainOutput);
+        assertOrderByHasBeenOptimizedOut(conn, sql);
+
+        
+        // Predicate with valid partial PK
+        sql = "SELECT * FROM v1 WHERE k3 < TO_DATE('" + datePredicate + "') 
ORDER BY k2, k3";
+        expectedExplainOutput = "CLIENT PARALLEL 1-WAY RANGE SCAN OVER T 
['tenant123456789','xyz']\n" +
+                "    SERVER FILTER BY K3 < DATE '" + datePredicate + "'";
+        assertExplainPlanIsCorrect(conn, sql, expectedExplainOutput);
+        assertOrderByHasBeenOptimizedOut(conn, sql);
+    }
+
+    @Test
+    public void testOrderByOptimizedOutWithMultiplePredicatesInView() throws 
Exception {
+        // Arrange
+        Connection conn = DriverManager.getConnection(getUrl());
+        conn.createStatement().execute("CREATE TABLE t(t_id CHAR(15) NOT NULL, 
k1 CHAR(3) NOT NULL, k2 CHAR(5) NOT NULL, k3 DATE NOT NULL, v1 VARCHAR," +
+                " CONSTRAINT pk PRIMARY KEY(t_id, k1, k2, k3 DESC)) 
multi_tenant=true");
+        conn.createStatement().execute("CREATE VIEW v1  AS SELECT * FROM t 
WHERE k1 = 'xyz' AND k2='abcde'");
+        conn = createTenantSpecificConnection();
+
+        // Query without predicate ordered by full row key
+        String sql = "SELECT * FROM v1 ORDER BY k3 DESC";
+        String expectedExplainOutput = "CLIENT PARALLEL 1-WAY RANGE SCAN OVER 
T ['tenant123456789','xyz','abcde']"; 
+        assertExplainPlanIsCorrect(conn, sql, expectedExplainOutput);
+        assertOrderByHasBeenOptimizedOut(conn, sql);
+        
+        // Query without predicate ordered by full row key, but without column 
view predicate
+        sql = "SELECT * FROM v1 ORDER BY k3 DESC";
+        expectedExplainOutput = "CLIENT PARALLEL 1-WAY RANGE SCAN OVER T 
['tenant123456789','xyz','abcde']"; 
+        assertExplainPlanIsCorrect(conn, sql, expectedExplainOutput);
+        assertOrderByHasBeenOptimizedOut(conn, sql);
+
+        // Query with predicate ordered by full row key
+        sql = "SELECT * FROM v1 WHERE k3 < TO_DATE('" + createStaticDate() + 
"') ORDER BY k3 DESC";
+        expectedExplainOutput = "CLIENT PARALLEL 1-WAY RANGE SCAN OVER T 
['tenant123456789','xyz','abcde',~'2015-01-01 07:59:59.999'] - 
['tenant123456789','xyz','abcde',*]";
+        assertExplainPlanIsCorrect(conn, sql, expectedExplainOutput);
+        assertOrderByHasBeenOptimizedOut(conn, sql);
+
+        // Query with predicate ordered by full row key with date in reverse 
order
+        sql = "SELECT * FROM v1 WHERE k3 < TO_DATE('" + createStaticDate() + 
"') ORDER BY k3";
+        expectedExplainOutput = "CLIENT PARALLEL 1-WAY REVERSE RANGE SCAN OVER 
T ['tenant123456789','xyz','abcde',~'2015-01-01 07:59:59.999'] - 
['tenant123456789','xyz','abcde',*]";
+        assertExplainPlanIsCorrect(conn, sql, expectedExplainOutput);
+        assertOrderByHasBeenOptimizedOut(conn, sql);
+
+    }
+
+
+    @Test
     public void testViewConstantsOptimizedOut() throws Exception {
         Properties props = new Properties();
         Connection conn = DriverManager.getConnection(getUrl());
@@ -99,4 +230,45 @@ public class TenantSpecificViewIndexCompileTest extends 
BaseConnectionlessQueryT
         assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER _IDX_T 
['me',-32767,'a'] - ['me',-32767,*]",
                 QueryUtil.getExplainPlan(rs));
     }
+    
+    //-----------------------------------------------------------------
+    // Private Helper Methods
+    //-----------------------------------------------------------------
+    private Connection createTenantSpecificConnection() throws SQLException {
+        Connection conn;
+        Properties props = new Properties();
+        String tenantId = "tenant123456789";
+        props.setProperty(PhoenixRuntime.TENANT_ID_ATTRIB, tenantId); // 
connection is tenant-specific
+        conn = DriverManager.getConnection(getUrl(), props);
+        return conn;
+    }
+    
+    
+    private void assertExplainPlanIsCorrect(Connection conn, String sql,
+            String expectedExplainOutput) throws SQLException {
+        ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + sql);
+        assertEquals(expectedExplainOutput, QueryUtil.getExplainPlan(rs));
+    }
+
+    private void assertOrderByHasBeenOptimizedOut(Connection conn, String sql) 
throws SQLException {
+        PreparedStatement stmt = conn.prepareStatement(sql);
+        QueryPlan plan = PhoenixRuntime.getOptimizedQueryPlan(stmt);
+        assertEquals(0, plan.getOrderBy().getOrderByExpressions().size());
+    }
+    
+    /**
+     * Returns the default String representation of 1/1/2015 00:00:00
+     */
+    private String createStaticDate() {
+        Calendar cal = Calendar.getInstance();
+        cal.set(Calendar.DAY_OF_YEAR, 1);
+        cal.set(Calendar.YEAR, 2015);
+        cal.set(Calendar.HOUR_OF_DAY, 0);
+        cal.set(Calendar.MINUTE, 0);
+        cal.set(Calendar.SECOND, 0);
+        cal.set(Calendar.MILLISECOND, 0);
+        cal.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
+        return DateUtil.DEFAULT_DATE_FORMATTER.format(cal.getTime());
+    }
+    
 }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/d18afe18/phoenix-core/src/test/java/org/apache/phoenix/compile/ViewCompilerTest.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/test/java/org/apache/phoenix/compile/ViewCompilerTest.java 
b/phoenix-core/src/test/java/org/apache/phoenix/compile/ViewCompilerTest.java
index 7a0bac6..1d95058 100644
--- 
a/phoenix-core/src/test/java/org/apache/phoenix/compile/ViewCompilerTest.java
+++ 
b/phoenix-core/src/test/java/org/apache/phoenix/compile/ViewCompilerTest.java
@@ -85,7 +85,6 @@ public class ViewCompilerTest extends 
BaseConnectionlessQueryTest {
         conn.createStatement().execute("CREATE VIEW s2.v3 AS SELECT * FROM 
s1.t WHERE v = 'bar'");
         
         // TODO: should it be an error to remove columns from a VIEW that 
we're defined there?
-        // TOOD: should we require an ALTER VIEW instead of ALTER TABLE?
         conn.createStatement().execute("ALTER VIEW s2.v3 DROP COLUMN v");
         try {
             conn.createStatement().executeQuery("SELECT * FROM s2.v3");

http://git-wip-us.apache.org/repos/asf/phoenix/blob/d18afe18/phoenix-core/src/test/java/org/apache/phoenix/compile/WhereOptimizerTest.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/test/java/org/apache/phoenix/compile/WhereOptimizerTest.java 
b/phoenix-core/src/test/java/org/apache/phoenix/compile/WhereOptimizerTest.java
index 2413013..c5c4125 100644
--- 
a/phoenix-core/src/test/java/org/apache/phoenix/compile/WhereOptimizerTest.java
+++ 
b/phoenix-core/src/test/java/org/apache/phoenix/compile/WhereOptimizerTest.java
@@ -388,7 +388,7 @@ public class WhereOptimizerTest extends 
BaseConnectionlessQueryTest {
 
         assertNull(scan.getFilter());
         byte[] startRow = 
ByteUtil.concat(PVarchar.INSTANCE.toBytes(inst),QueryConstants.SEPARATOR_BYTE_ARRAY,
-                
PVarchar.INSTANCE.toBytes(host),QueryConstants.SEPARATOR_BYTE_ARRAY);
+                
PVarchar.INSTANCE.toBytes(host)/*,QueryConstants.SEPARATOR_BYTE_ARRAY*/);
         assertArrayEquals(startRow, scan.getStartRow());
         byte[] stopRow = 
ByteUtil.concat(PVarchar.INSTANCE.toBytes(inst),QueryConstants.SEPARATOR_BYTE_ARRAY,
                 
PVarchar.INSTANCE.toBytes(host),QueryConstants.SEPARATOR_BYTE_ARRAY,
@@ -408,7 +408,7 @@ public class WhereOptimizerTest extends 
BaseConnectionlessQueryTest {
         assertNull(scan.getFilter());
 
         byte[] startRow = 
ByteUtil.concat(PVarchar.INSTANCE.toBytes(inst),QueryConstants.SEPARATOR_BYTE_ARRAY,
-                
PVarchar.INSTANCE.toBytes(host),QueryConstants.SEPARATOR_BYTE_ARRAY);
+                
PVarchar.INSTANCE.toBytes(host)/*,QueryConstants.SEPARATOR_BYTE_ARRAY*/);
         assertArrayEquals(startRow, scan.getStartRow());
         byte[] stopRow = 
ByteUtil.concat(PVarchar.INSTANCE.toBytes(inst),QueryConstants.SEPARATOR_BYTE_ARRAY,
                 
PVarchar.INSTANCE.toBytes(host),QueryConstants.SEPARATOR_BYTE_ARRAY,
@@ -428,7 +428,7 @@ public class WhereOptimizerTest extends 
BaseConnectionlessQueryTest {
         assertNull(scan.getFilter());
 
         byte[] startRow = 
ByteUtil.concat(PVarchar.INSTANCE.toBytes(inst),QueryConstants.SEPARATOR_BYTE_ARRAY,
-                
PVarchar.INSTANCE.toBytes(host),QueryConstants.SEPARATOR_BYTE_ARRAY);
+                
PVarchar.INSTANCE.toBytes(host)/*,QueryConstants.SEPARATOR_BYTE_ARRAY*/);
         assertArrayEquals(startRow, scan.getStartRow());
         byte[] stopRow = 
ByteUtil.concat(PVarchar.INSTANCE.toBytes(inst),QueryConstants.SEPARATOR_BYTE_ARRAY,
                 
PVarchar.INSTANCE.toBytes(host),QueryConstants.SEPARATOR_BYTE_ARRAY,
@@ -448,7 +448,7 @@ public class WhereOptimizerTest extends 
BaseConnectionlessQueryTest {
 
         assertNull(scan.getFilter());
         byte[] startRow = 
ByteUtil.concat(PVarchar.INSTANCE.toBytes(inst),QueryConstants.SEPARATOR_BYTE_ARRAY,
-                
PVarchar.INSTANCE.toBytes(host),QueryConstants.SEPARATOR_BYTE_ARRAY);
+                
PVarchar.INSTANCE.toBytes(host)/*,QueryConstants.SEPARATOR_BYTE_ARRAY*/);
         assertArrayEquals(startRow, scan.getStartRow());
         byte[] stopRow = 
ByteUtil.concat(PVarchar.INSTANCE.toBytes(inst),QueryConstants.SEPARATOR_BYTE_ARRAY,
                 
PVarchar.INSTANCE.toBytes(host),QueryConstants.SEPARATOR_BYTE_ARRAY,
@@ -486,8 +486,7 @@ public class WhereOptimizerTest extends 
BaseConnectionlessQueryTest {
         // loop during skip scan. We could end up having a first slot just 
under the upper
         // limit of slot one and a value equal to the value in slot two and we 
need this to
         // be less than the upper range that would get formed.
-        byte[] stopRow = 
ByteUtil.concat(StringUtil.padChar(ByteUtil.nextKey(PVarchar.INSTANCE.toBytes(tenantId.substring(0,3))),15),ByteUtil.nextKey(
-            PVarchar.INSTANCE.toBytes(entityId)));
+        byte[] stopRow = 
ByteUtil.concat(StringUtil.padChar(ByteUtil.nextKey(PVarchar.INSTANCE.toBytes(tenantId.substring(0,3))),15));
         assertArrayEquals(stopRow, scan.getStopRow());
     }
 
@@ -701,7 +700,14 @@ public class WhereOptimizerTest extends 
BaseConnectionlessQueryTest {
         Scan scan = context.getScan();
 
         assertNotNull(scan.getFilter());
-        assertArrayEquals(PVarchar.INSTANCE.toBytes(tenantId), 
scan.getStartRow());
+//        assertArrayEquals(PVarchar.INSTANCE.toBytes(tenantId), 
scan.getStartRow());
+        assertArrayEquals(
+                ByteUtil.concat(PChar.INSTANCE.toBytes(tenantId),
+                                PChar.INSTANCE.toBytes(
+                                        PChar.INSTANCE.pad(
+                                                
PChar.INSTANCE.toObject(ByteUtil.nextKey(PChar.INSTANCE.toBytes(keyPrefix))),
+                                                15))),
+                                                        scan.getStartRow());
         assertArrayEquals(HConstants.EMPTY_END_ROW, scan.getStopRow());
     }
 
@@ -1267,7 +1273,7 @@ public class WhereOptimizerTest extends 
BaseConnectionlessQueryTest {
             PVarchar.INSTANCE.toBytes(tenantId), 
StringUtil.padChar(PVarchar.INSTANCE.toBytes(keyPrefix),15), 
PDate.INSTANCE.toBytes(startTime));
         assertArrayEquals(expectedStartRow, scan.getStartRow());
         byte[] expectedStopRow = ByteUtil.concat(
-            PVarchar.INSTANCE.toBytes(tenantId), 
StringUtil.padChar(ByteUtil.nextKey(PVarchar.INSTANCE.toBytes(keyPrefix)),15), 
PDate.INSTANCE.toBytes(stopTime));
+            PVarchar.INSTANCE.toBytes(tenantId), 
StringUtil.padChar(ByteUtil.nextKey(PVarchar.INSTANCE.toBytes(keyPrefix)),15));
         assertArrayEquals(expectedStopRow, scan.getStopRow());
     }
 

http://git-wip-us.apache.org/repos/asf/phoenix/blob/d18afe18/phoenix-core/src/test/java/org/apache/phoenix/query/ParallelIteratorsSplitTest.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/test/java/org/apache/phoenix/query/ParallelIteratorsSplitTest.java
 
b/phoenix-core/src/test/java/org/apache/phoenix/query/ParallelIteratorsSplitTest.java
index ad65373..ed1f3e4 100644
--- 
a/phoenix-core/src/test/java/org/apache/phoenix/query/ParallelIteratorsSplitTest.java
+++ 
b/phoenix-core/src/test/java/org/apache/phoenix/query/ParallelIteratorsSplitTest.java
@@ -65,7 +65,6 @@ import org.apache.phoenix.schema.SortOrder;
 import org.apache.phoenix.schema.TableRef;
 import org.apache.phoenix.util.ByteUtil;
 import org.apache.phoenix.util.PropertiesUtil;
-import org.apache.phoenix.util.ScanUtil;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -297,7 +296,7 @@ public class ParallelIteratorsSplitTest extends 
BaseConnectionlessQueryTest {
         // Always set start and stop key to max to verify we are using the 
information in skipscan
         // filter over the scan's KMIN and KMAX.
         Scan scan = new Scan().setFilter(filter);
-        ScanRanges scanRanges = ScanRanges.create(schema, slots, 
ScanUtil.getDefaultSlotSpans(ranges.length));
+        ScanRanges scanRanges = ScanRanges.createSingleSpan(schema, slots);
         List<Object> ret = Lists.newArrayList();
         ret.add(new Object[] {scan, scanRanges, 
Arrays.<KeyRange>asList(expectedSplits)});
         return ret;

http://git-wip-us.apache.org/repos/asf/phoenix/blob/d18afe18/phoenix-core/src/test/java/org/apache/phoenix/query/QueryPlanTest.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/test/java/org/apache/phoenix/query/QueryPlanTest.java 
b/phoenix-core/src/test/java/org/apache/phoenix/query/QueryPlanTest.java
index 2f8088d..fdae749 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/query/QueryPlanTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/query/QueryPlanTest.java
@@ -35,6 +35,10 @@ public class QueryPlanTest extends 
BaseConnectionlessQueryTest {
     public void testExplainPlan() throws Exception {
         String[] queryPlans = new String[] {
 
+                "SELECT host FROM PTSDB WHERE inst IS NULL AND host IS NOT 
NULL AND date >= to_date('2013-01-01')",
+                "CLIENT PARALLEL 1-WAY RANGE SCAN OVER PTSDB [null,not 
null]\n" + 
+                "    SERVER FILTER BY FIRST KEY ONLY AND DATE >= DATE 
'2013-01-01 00:00:00.000'",
+
                 "SELECT a_string,b_string FROM atable WHERE organization_id = 
'000000000000001' AND entity_id > '000000000000002' AND entity_id < 
'000000000000008' AND (organization_id,entity_id) >= 
('000000000000001','000000000000005') ",
                 "CLIENT PARALLEL 1-WAY RANGE SCAN OVER ATABLE 
['000000000000001','000000000000005'] - ['000000000000001','000000000000008']",
 
@@ -50,10 +54,6 @@ public class QueryPlanTest extends 
BaseConnectionlessQueryTest {
                 "CLIENT PARALLEL 1-WAY REVERSE RANGE SCAN OVER PTSDB2 
['na1']\n" +
                 "    SERVER FILTER BY FIRST KEY ONLY",
 
-                "SELECT host FROM PTSDB WHERE inst IS NULL AND host IS NOT 
NULL AND date >= to_date('2013-01-01')",
-                "CLIENT PARALLEL 1-WAY RANGE SCAN OVER PTSDB [null,not 
null]\n" + 
-                "    SERVER FILTER BY FIRST KEY ONLY AND DATE >= DATE 
'2013-01-01 00:00:00.000'",
-
                 // Since inst IS NOT NULL is unbounded, we won't continue 
optimizing
                 "SELECT host FROM PTSDB WHERE inst IS NOT NULL AND host IS 
NULL AND date >= to_date('2013-01-01')",
                 "CLIENT PARALLEL 1-WAY RANGE SCAN OVER PTSDB [not null]\n" + 
@@ -149,7 +149,7 @@ public class QueryPlanTest extends 
BaseConnectionlessQueryTest {
                 "CLIENT PARALLEL 1-WAY RANGE SCAN OVER ATABLE 
['000000000000001']\n" + 
                 "    SERVER AGGREGATE INTO DISTINCT ROWS BY [ORGANIZATION_ID, 
ENTITY_ID, ROUND(A_DATE)]\n" + 
                 "CLIENT MERGE SORT\n" + 
-                "CLIENT TOP 10 ROWS SORTED BY [ENTITY_ID NULLS LAST]",
+                "CLIENT 10 ROW LIMIT",
 
                 "SELECT a_string,b_string FROM atable WHERE organization_id = 
'000000000000001' ORDER BY a_string DESC NULLS LAST LIMIT 10",
                 "CLIENT PARALLEL 1-WAY RANGE SCAN OVER ATABLE 
['000000000000001']\n" + 

Reply via email to