phoenix-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jamestay...@apache.org
Subject [1/2] git commit: Fix for optimizer not choosing point lookup and optimized out order
Date Tue, 04 Feb 2014 10:01:13 GMT
Updated Branches:
  refs/heads/master d40c5066e -> bc21d8e41


Fix for optimizer not choosing point lookup and optimized out order


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

Branch: refs/heads/master
Commit: 0b469d47e76fe62ecc60592dcb8340803d79888e
Parents: fc96ccb
Author: James Taylor <jamestaylor@apache.org>
Authored: Tue Feb 4 02:00:27 2014 -0800
Committer: James Taylor <jamestaylor@apache.org>
Committed: Tue Feb 4 02:00:27 2014 -0800

----------------------------------------------------------------------
 .../apache/phoenix/compile/DeleteCompiler.java  | 18 +++--
 .../org/apache/phoenix/compile/ScanRanges.java  | 76 ++++++++++++++++----
 .../phoenix/compile/StatementContext.java       |  6 --
 .../apache/phoenix/compile/WhereOptimizer.java  | 45 +++++-------
 .../iterate/MergeSortTopNResultIterator.java    |  3 +-
 .../apache/phoenix/optimize/QueryOptimizer.java | 52 +++++++++-----
 .../org/apache/phoenix/schema/SaltingUtil.java  | 45 +-----------
 .../java/org/apache/phoenix/util/ScanUtil.java  | 10 ++-
 .../phoenix/compile/QueryOptimizerTest.java     | 31 ++++++--
 .../phoenix/compile/WhereClauseCompileTest.java | 14 ++--
 .../compile/WhereClauseOptimizerTest.java       |  8 +--
 .../end2end/index/ImmutableIndexTest.java       | 36 ++++------
 .../end2end/index/MutableSaltedIndexTest.java   | 30 ++++----
 13 files changed, 197 insertions(+), 177 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/0b469d47/phoenix-core/src/main/java/org/apache/phoenix/compile/DeleteCompiler.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/DeleteCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/DeleteCompiler.java
index a73c111..b71a836 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/DeleteCompiler.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/DeleteCompiler.java
@@ -29,6 +29,7 @@ import java.util.Map;
 
 import org.apache.hadoop.hbase.KeyValue;
 import org.apache.hadoop.hbase.client.Scan;
+import org.apache.hadoop.hbase.filter.FilterList;
 import org.apache.hadoop.hbase.index.util.ImmutableBytesPtr;
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
 import org.apache.phoenix.cache.ServerCacheClient.ServerCache;
@@ -176,7 +177,7 @@ public class DeleteCompiler {
         final ConnectionQueryServices services = connection.getQueryServices();
         final ColumnResolver resolver = FromCompiler.getResolver(delete, connection);
         final TableRef tableRef = resolver.getTables().get(0);
-        PTable table = tableRef.getTable();
+        final PTable table = tableRef.getTable();
         if (table.getType() == PTableType.VIEW && table.getViewType().isReadOnly())
{
             throw new ReadOnlyTableException(table.getSchemaName().getString(),table.getTableName().getString());
         }
@@ -217,11 +218,11 @@ public class DeleteCompiler {
         }
         
         final StatementContext context = plan.getContext();
-        // If we're doing a query for a single row with no where clause, then we don't need
to contact the server at all.
+        // If we're doing a query for a set of rows with no where clause, then we don't need
to contact the server at all.
         // A simple check of the none existence of a where clause in the parse node is not
sufficient, as the where clause
-        // may have been optimized out.
-        if (noQueryReqd && context.isSingleRowScan()) {
-            final ImmutableBytesPtr key = new ImmutableBytesPtr(context.getScan().getStartRow());
+        // may have been optimized out. Instead, we check that there's a single filter which
must be the SkipScanFilter
+        // in this case.
+        if (noQueryReqd && ! (context.getScan().getFilter() instanceof FilterList)
&& context.getScanRanges().isPointLookup()) {
             return new MutationPlan() {
 
                 @Override
@@ -231,8 +232,11 @@ public class DeleteCompiler {
 
                 @Override
                 public MutationState execute() {
-                    Map<ImmutableBytesPtr,Map<PColumn,byte[]>> mutation = Maps.newHashMapWithExpectedSize(1);
-                    mutation.put(key, PRow.DELETE_MARKER);
+                    List<byte[]> keys = context.getScanRanges().getPointKeys(table.getBucketNum());
+                    Map<ImmutableBytesPtr,Map<PColumn,byte[]>> mutation = Maps.newHashMapWithExpectedSize(keys.size());
+                    for (byte[] key : keys) {
+                        mutation.put(new ImmutableBytesPtr(key), PRow.DELETE_MARKER);
+                    }
                     return new MutationState(tableRef, mutation, 0, maxSize, connection);
                 }
 

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/0b469d47/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 44f4473..73edaf8 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
@@ -19,18 +19,22 @@
  */
 package org.apache.phoenix.compile;
 
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
 import org.apache.hadoop.hbase.client.Scan;
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
 import org.apache.phoenix.filter.SkipScanFilter;
 import org.apache.phoenix.query.KeyRange;
+import org.apache.phoenix.query.KeyRange.Bound;
 import org.apache.phoenix.schema.RowKeySchema;
+import org.apache.phoenix.schema.SaltingUtil;
 import org.apache.phoenix.util.ScanUtil;
+import org.apache.phoenix.util.SchemaUtil;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
 
 
 public class ScanRanges {
@@ -115,21 +119,67 @@ public class ScanRanges {
         return false;
     }
 
-    /**
-     * @return true if this represents the full key to a single row
-     */
-    public boolean isSingleRowScan() {
-        if (schema == null || ranges.size() < schema.getMaxFields()) {
+    public static boolean isPointLookup(RowKeySchema schema, List<List<KeyRange>>
ranges) {
+        if (ranges.size() < schema.getMaxFields()) {
             return false;
         }
-        boolean isSingleKey = true;
         for (List<KeyRange> orRanges : ranges) {
-            if (orRanges.size() > 1) {
-                return false;
+            for (KeyRange keyRange : orRanges) {
+                if (!keyRange.isSingleKey()) {
+                    return false;
+                }
             }
-            isSingleKey &= orRanges.get(0).isSingleKey();
         }
-        return isSingleKey;
+        return true;
+    }
+    
+    
+    private static boolean incrementKey(List<List<KeyRange>> slots, int[] position)
{
+        int idx = slots.size() - 1;
+        while (idx >= 0 && (position[idx] = (position[idx] + 1) % slots.get(idx).size())
== 0) {
+            idx--;
+        }
+        return idx >= 0;
+    }
+
+    /**
+     * @return true if this represents a set of complete keys
+     */
+    public List<byte[]> getPointKeys(Integer bucketNum) {
+        return getPointKeys(this.getRanges(), this.getSchema(), bucketNum);
+    }
+    
+    public static List<byte[]> getPointKeys(List<List<KeyRange>> ranges,
RowKeySchema schema, Integer bucketNum) {
+        if (ranges == null || ranges.isEmpty()) {
+            return Collections.emptyList();
+        }
+        boolean isSalted = bucketNum != null;
+        int count = 1;
+        int offset = isSalted ? 1 : 0;
+        // Skip salt byte range in the first position if salted
+        for (int i = offset; i < ranges.size(); i++) {
+            count *= ranges.get(i).size();
+        }
+        List<byte[]> keys = Lists.newArrayListWithExpectedSize(count);
+        int[] position = new int[ranges.size()];
+        int maxKeyLength = SchemaUtil.getMaxKeyLength(schema, ranges);
+        int length;
+        byte[] key = new byte[maxKeyLength];
+        do {
+            length = ScanUtil.setKey(schema, ranges, position, Bound.LOWER, key, offset,
offset, ranges.size(), offset);
+            if (isSalted) {
+                key[0] = SaltingUtil.getSaltingByte(key, offset, length, bucketNum);
+            }
+            keys.add(Arrays.copyOf(key, length + offset));
+        } while (incrementKey(ranges, position));
+        return keys;
+    }
+
+    /**
+     * @return true if this represents a set of complete keys
+     */
+    public boolean isPointLookup() {
+        return schema != null && isPointLookup(schema, ranges);
     }
 
     public void setScanStartStopRow(Scan scan) {

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/0b469d47/phoenix-core/src/main/java/org/apache/phoenix/compile/StatementContext.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/StatementContext.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/StatementContext.java
index 5736536..c702985 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/StatementContext.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/StatementContext.java
@@ -24,9 +24,7 @@ import java.text.Format;
 import java.util.List;
 
 import org.apache.hadoop.hbase.client.Scan;
-import org.apache.hadoop.hbase.filter.FilterList;
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
-
 import org.apache.phoenix.jdbc.PhoenixConnection;
 import org.apache.phoenix.jdbc.PhoenixStatement;
 import org.apache.phoenix.query.KeyRange;
@@ -217,10 +215,6 @@ public class StatementContext {
         return minMaxRange;
     }
     
-    public boolean isSingleRowScan() {
-        return this.getScanRanges().isSingleRowScan() && ! (this.getScan().getFilter()
instanceof FilterList);
-    }
-    
     public SequenceManager getSequenceManager(){
         return sequences;
     }

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/0b469d47/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 9bfbca3..bc868b1 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
@@ -32,6 +32,7 @@ import java.util.Set;
 
 import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
+import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.phoenix.expression.AndExpression;
 import org.apache.phoenix.expression.BaseTerminalExpression;
 import org.apache.phoenix.expression.CoerceExpression;
@@ -78,6 +79,7 @@ import com.google.common.collect.Lists;
  */
 public class WhereOptimizer {
     private static final List<KeyRange> SALT_PLACEHOLDER = Collections.singletonList(PDataType.CHAR.getKeyRange(QueryConstants.SEPARATOR_BYTE_ARRAY));
+    
     private WhereOptimizer() {
     }
 
@@ -204,12 +206,21 @@ public class WhereOptimizer {
                 // If we have all single keys, we can optimize by adding the salt byte up
front
                 if (schema == SchemaUtil.VAR_BINARY_SCHEMA) {
                     ranges = SaltingUtil.setSaltByte(ranges, table.getBucketNum());
-                } else if (isAllSingleRowScan(cnf, table)) {
-                    cnf.addFirst(SALT_PLACEHOLDER);
-                    ranges = SaltingUtil.flattenRanges(cnf, table.getRowKeySchema(), table.getBucketNum());
-                    schema = SchemaUtil.VAR_BINARY_SCHEMA;
                 } else {
-                    cnf.addFirst(SaltingUtil.generateAllSaltingRanges(table.getBucketNum()));
+                    List<KeyRange> saltRanges = SALT_PLACEHOLDER;
+                    cnf.addFirst(saltRanges);
+                    if (ScanRanges.isPointLookup(schema, cnf)) {
+                        List<byte[]> keys = ScanRanges.getPointKeys(cnf, schema, table.getBucketNum());
+                        Collections.sort(keys, Bytes.BYTES_COMPARATOR);
+                        List<KeyRange> keyRanges = Lists.newArrayListWithExpectedSize(keys.size());
+                        for (byte[] key : keys) {
+                            keyRanges.add(KeyRange.getKeyRange(key));
+                        }
+                        ranges = Collections.singletonList(keyRanges);
+                        schema = SchemaUtil.VAR_BINARY_SCHEMA;
+                    } else {
+                        cnf.set(0, SaltingUtil.generateAllSaltingRanges(table.getBucketNum()));
+                    }
                 }
             }
         }
@@ -223,30 +234,6 @@ public class WhereOptimizer {
         }
     }
 
-    /**
-     * Calculate whether or not the list of ranges represents the full primary key
-     * of one or more rows
-     * @param ranges the list of ranges WITHOUT the salt KeyRange inserted yet
-     * @param table
-     * @return true if the list of ranges represents the full primary key of one or
-     * more ranges and false otherwise.
-     */
-    private static boolean isAllSingleRowScan(List<List<KeyRange>> ranges, PTable
table) {
-        RowKeySchema schema = table.getRowKeySchema();
-        if (ranges.size() + ( table.getBucketNum() == null ? 0 : 1) < schema.getMaxFields())
{
-            return false;
-        }
-        for (int i = 0; i < ranges.size(); i++) {
-            List<KeyRange> orRanges = ranges.get(i);
-            for (KeyRange range: orRanges) {
-                if (!range.isSingleKey()) {
-                    return false;
-                }
-            }
-        }
-        return true;
-    }
-
     private static class RemoveExtractedNodesVisitor extends TraverseNoExpressionVisitor<Expression>
{
         private final Set<Expression> nodesToRemove;
 

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/0b469d47/phoenix-core/src/main/java/org/apache/phoenix/iterate/MergeSortTopNResultIterator.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/MergeSortTopNResultIterator.java
b/phoenix-core/src/main/java/org/apache/phoenix/iterate/MergeSortTopNResultIterator.java
index 77d1c62..f611fc8 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/MergeSortTopNResultIterator.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/MergeSortTopNResultIterator.java
@@ -23,7 +23,6 @@ import java.sql.SQLException;
 import java.util.List;
 
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
-
 import org.apache.phoenix.expression.Expression;
 import org.apache.phoenix.expression.OrderByExpression;
 import org.apache.phoenix.schema.tuple.Tuple;
@@ -94,7 +93,7 @@ public class MergeSortTopNResultIterator extends MergeSortResultIterator
{
     @Override
     public void explain(List<String> planSteps) {
         resultIterators.explain(planSteps);
-        planSteps.add("    SERVER TOP " + limit + " ROW" + (limit == 1 ? "" : "S") + " SORTED
BY " + orderByColumns.toString());
+        planSteps.add("    SERVER" + (limit == -1 ? "" : " TOP " + limit + " ROW" + (limit
== 1 ? "" : "S")) + " SORTED BY " + orderByColumns.toString());
         planSteps.add("CLIENT MERGE SORT");
     }
 }

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/0b469d47/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 b60f742..7dc6b04 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
@@ -5,7 +5,6 @@ import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 
-import com.google.common.collect.Lists;
 import org.apache.phoenix.compile.ColumnProjector;
 import org.apache.phoenix.compile.IndexStatementRewriter;
 import org.apache.phoenix.compile.QueryCompiler;
@@ -26,6 +25,8 @@ import org.apache.phoenix.schema.PIndexState;
 import org.apache.phoenix.schema.PTable;
 import org.apache.phoenix.schema.PTableType;
 
+import com.google.common.collect.Lists;
+
 public class QueryOptimizer {
     private static final ParseNodeFactory FACTORY = new ParseNodeFactory();
 
@@ -170,9 +171,10 @@ public class QueryOptimizer {
     /**
      * Choose the best plan among all the possible ones.
      * Since we don't keep stats yet, we use the following simple algorithm:
-     * 1) If the query has an ORDER BY and a LIMIT, choose the plan that has all the ORDER
BY expression
+     * 1) If the query is a point lookup (i.e. we have a set of exact row keys), choose among
those.
+     * 2) If the query has an ORDER BY and a LIMIT, choose the plan that has all the ORDER
BY expression
      * in the same order as the row key columns.
-     * 2) If there are more than one plan that meets (1), choose the plan with:
+     * 3) If there are more than one plan that meets (1&2), choose the plan with:
      *    a) the most row key columns that may be used to form the start/stop scan key.
      *    b) the plan that preserves ordering for a group by.
      *    c) the data table plan
@@ -185,22 +187,41 @@ public class QueryOptimizer {
             return firstPlan;
         }
         
+        /**
+         * If we have a plan(s) that are just point lookups (i.e. fully qualified row
+         * keys), then favor those first.
+         */
         List<QueryPlan> candidates = Lists.newArrayListWithExpectedSize(plans.size());
-        if (firstPlan.getLimit() == null) {
-            candidates.addAll(plans);
-        } else {
-            for (QueryPlan plan : plans) {
-                // If ORDER BY optimized out (or not present at all)
-                if (plan.getOrderBy().getOrderByExpressions().isEmpty()) {
-                    candidates.add(plan);
-                }
+        for (QueryPlan plan : plans) {
+            if (plan.getContext().getScanRanges().isPointLookup()) {
+                candidates.add(plan);
             }
-            if (candidates.isEmpty()) {
-                candidates.addAll(plans);
+        }
+        /**
+         * If we have a plan(s) that removes the order by, choose from among these,
+         * as this is typically the most expensive operation. Once we have stats, if
+         * there's a limit on the query, we might choose a different plan. For example
+         * if the limit was a very large number and the combination of applying other 
+         * filters on the row key are estimated to choose fewer rows, we'd choose that
+         * one.
+         */
+        List<QueryPlan> stillCandidates = plans;
+        List<QueryPlan> bestCandidates = candidates;
+        if (!candidates.isEmpty()) {
+            stillCandidates = candidates;
+            bestCandidates = Lists.<QueryPlan>newArrayListWithExpectedSize(candidates.size());
+        }
+        for (QueryPlan plan : stillCandidates) {
+            // If ORDER BY optimized out (or not present at all)
+            if (plan.getOrderBy().getOrderByExpressions().isEmpty()) {
+                bestCandidates.add(plan);
             }
         }
+        if (bestCandidates.isEmpty()) {
+            bestCandidates.addAll(stillCandidates);
+        }
         final int comparisonOfDataVersusIndexTable = select.getHint().hasHint(Hint.USE_DATA_OVER_INDEX_TABLE)
? -1 : 1;
-        Collections.sort(candidates, new Comparator<QueryPlan>() {
+        Collections.sort(bestCandidates, new Comparator<QueryPlan>() {
 
             @Override
             public int compare(QueryPlan plan1, QueryPlan plan2) {
@@ -217,8 +238,7 @@ public class QueryOptimizer {
                 c = (table1.getColumns().size() - table1.getPKColumns().size()) - (table2.getColumns().size()
- table2.getPKColumns().size());
                 if (c != 0) return c;
                 
-                // All things being equal, just use the index table
-                // TODO: have hint that drives this
+                // All things being equal, just use the table based on the Hint.USE_DATA_OVER_INDEX_TABLE
                 if (plan1.getTableRef().getTable().getType() == PTableType.INDEX) {
                     return comparisonOfDataVersusIndexTable;
                 }

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/0b469d47/phoenix-core/src/main/java/org/apache/phoenix/schema/SaltingUtil.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/SaltingUtil.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/SaltingUtil.java
index 12ab4e7..53a6ac0 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/schema/SaltingUtil.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/SaltingUtil.java
@@ -19,16 +19,12 @@
  */
 package org.apache.phoenix.schema;
 
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
 import org.apache.phoenix.compile.ScanRanges;
 import org.apache.phoenix.query.KeyRange;
-import org.apache.phoenix.query.KeyRange.Bound;
-import org.apache.phoenix.util.ScanUtil;
-import org.apache.phoenix.util.SchemaUtil;
 
 import com.google.common.collect.Lists;
 
@@ -45,7 +41,7 @@ public class SaltingUtil {
             PNameFactory.newName(SALTING_COLUMN_NAME), null, PDataType.BINARY, 1, 0, false,
0, null, 0);
 
     public static List<KeyRange> generateAllSaltingRanges(int bucketNum) {
-        List<KeyRange> allRanges = Lists.<KeyRange>newArrayListWithExpectedSize(bucketNum);
+        List<KeyRange> allRanges = Lists.newArrayListWithExpectedSize(bucketNum);
         for (int i=0; i<bucketNum; i++) {
             byte[] saltByte = new byte[] {(byte) i};
             allRanges.add(SALTING_COLUMN.getDataType().getKeyRange(
@@ -114,45 +110,6 @@ public class SaltingUtil {
         return Collections.singletonList(newRanges);
     }
     
-    public static List<List<KeyRange>> flattenRanges(List<List<KeyRange>>
ranges, RowKeySchema schema, int bucketNum) {
-        if (ranges == null || ranges.isEmpty()) {
-            return ScanRanges.NOTHING.getRanges();
-        }
-        int count = 1;
-        // Skip salt byte range in the first position
-        for (int i = 1; i < ranges.size(); i++) {
-            count *= ranges.get(i).size();
-        }
-        KeyRange[] expandedRanges = new KeyRange[count];
-        int[] position = new int[ranges.size()];
-        int maxKeyLength = SchemaUtil.getMaxKeyLength(schema, ranges);
-        int idx = 0, length;
-        byte saltByte;
-        byte[] key = new byte[maxKeyLength];
-        do {
-            length = ScanUtil.setKey(schema, ranges, position, Bound.LOWER, key, NUM_SALTING_BYTES,
1, ranges.size(), 1);
-            saltByte = SaltingUtil.getSaltingByte(key, 1, length, bucketNum);
-            key[0] = saltByte;
-            byte[] saltedStartKey = Arrays.copyOf(key, length + 1);
-            length = ScanUtil.setKey(schema, ranges, position, Bound.UPPER, key, NUM_SALTING_BYTES,
1, ranges.size(), 1);
-            byte[] saltedEndKey = Arrays.copyOf(key, length + 1);
-            KeyRange range = PDataType.VARBINARY.getKeyRange(saltedStartKey, true, saltedEndKey,
false);
-            expandedRanges[idx++] = range;
-        } while (incrementKey(ranges, position));
-        // The comparator is imperfect, but sufficient for all single keys.
-        Arrays.sort(expandedRanges, KeyRange.COMPARATOR);
-        List<KeyRange> expandedRangesList = Arrays.asList(expandedRanges);
-        return Collections.singletonList(expandedRangesList);
-    }
-
-    private static boolean incrementKey(List<List<KeyRange>> slots, int[] position)
{
-        int idx = slots.size() - 1;
-        while (idx >= 0 && (position[idx] = (position[idx] + 1) % slots.get(idx).size())
== 0) {
-            idx--;
-        }
-        return idx >= 0;
-    }
-
     public static KeyRange addSaltByte(byte[] startKey, KeyRange minMaxRange) {
         byte saltByte = startKey.length == 0 ? 0 : startKey[0];
         byte[] lowerRange = minMaxRange.getLowerRange();

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/0b469d47/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 3531306..528eda3 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
@@ -283,9 +283,6 @@ public class ScanUtil {
              */
             boolean inclusiveUpper = range.isInclusive(bound) && bound == Bound.UPPER;
             boolean exclusiveLower = !range.isInclusive(bound) && bound == Bound.LOWER;
-            if (!isFixedWidth && ( i < schema.getMaxFields()-1 || inclusiveUpper
|| exclusiveLower)) {
-                key[offset++] = QueryConstants.SEPARATOR_BYTE;
-            }
             // 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.
             // 
@@ -298,6 +295,13 @@ public class ScanUtil {
             // key slots would cause the flag to become true.
             lastInclusiveUpperSingleKey = range.isSingleKey() && inclusiveUpper;
             anyInclusiveUpperRangeKey |= !range.isSingleKey() && inclusiveUpper;
+            
+            if (!isFixedWidth && ( i < schema.getMaxFields()-1 || inclusiveUpper
|| exclusiveLower)) {
+                key[offset++] = QueryConstants.SEPARATOR_BYTE;
+                // 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 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.

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/0b469d47/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryOptimizerTest.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryOptimizerTest.java
b/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryOptimizerTest.java
index f3359da..8eaa4f2 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryOptimizerTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryOptimizerTest.java
@@ -25,12 +25,11 @@ import static org.junit.Assert.assertFalse;
 import java.sql.Connection;
 import java.sql.DriverManager;
 
-import org.junit.Test;
-
 import org.apache.phoenix.compile.OrderByCompiler.OrderBy;
 import org.apache.phoenix.jdbc.PhoenixStatement;
 import org.apache.phoenix.query.BaseConnectionlessQueryTest;
 import org.apache.phoenix.util.SchemaUtil;
+import org.junit.Test;
 
 public class QueryOptimizerTest extends BaseConnectionlessQueryTest {
     
@@ -156,17 +155,27 @@ public class QueryOptimizerTest extends BaseConnectionlessQueryTest
{
         conn.createStatement().execute("CREATE TABLE t (k INTEGER NOT NULL PRIMARY KEY, v1
VARCHAR, v2 VARCHAR) IMMUTABLE_ROWS=true");
         conn.createStatement().execute("CREATE INDEX idx ON t(v1)");
         PhoenixStatement stmt = conn.createStatement().unwrap(PhoenixStatement.class);
-        QueryPlan plan = stmt.optimizeQuery("SELECT k FROM t WHERE k = 30 ORDER BY v1 LIMIT
5");
+        QueryPlan plan = stmt.optimizeQuery("SELECT k FROM t WHERE k > 30 ORDER BY v1
LIMIT 5");
         assertEquals("IDX", plan.getTableRef().getTable().getTableName().getString());
     }
     
     @Test
+    public void testChoosePointLookupOverOrderByRemoval() throws Exception {
+        Connection conn = DriverManager.getConnection(getUrl());
+        conn.createStatement().execute("CREATE TABLE t (k INTEGER NOT NULL PRIMARY KEY, v1
VARCHAR, v2 VARCHAR) IMMUTABLE_ROWS=true");
+        conn.createStatement().execute("CREATE INDEX idx ON t(v1)");
+        PhoenixStatement stmt = conn.createStatement().unwrap(PhoenixStatement.class);
+        QueryPlan plan = stmt.optimizeQuery("SELECT k FROM t WHERE k = 30 ORDER BY v1 LIMIT
5"); // Prefer 
+        assertEquals("T", plan.getTableRef().getTable().getTableName().getString());
+    }
+    
+    @Test
     public void testChooseIndexFromOrderByDesc() throws Exception {
         Connection conn = DriverManager.getConnection(getUrl());
         conn.createStatement().execute("CREATE TABLE t (k INTEGER NOT NULL PRIMARY KEY DESC,
v1 VARCHAR, v2 VARCHAR) IMMUTABLE_ROWS=true");
         conn.createStatement().execute("CREATE INDEX idx ON t(v1)");
         PhoenixStatement stmt = conn.createStatement().unwrap(PhoenixStatement.class);
-        QueryPlan plan = stmt.optimizeQuery("SELECT k FROM t WHERE k = 30 ORDER BY v1, k
DESC LIMIT 5");
+        QueryPlan plan = stmt.optimizeQuery("SELECT k FROM t WHERE k > 30 ORDER BY v1,
k DESC LIMIT 5");
         assertEquals("IDX", plan.getTableRef().getTable().getTableName().getString());
     }
     
@@ -176,7 +185,7 @@ public class QueryOptimizerTest extends BaseConnectionlessQueryTest {
         conn.createStatement().execute("CREATE TABLE t (k INTEGER NOT NULL PRIMARY KEY DESC,
v1 VARCHAR, v2 VARCHAR) IMMUTABLE_ROWS=true");
         conn.createStatement().execute("CREATE INDEX idx ON t(v1)");
         PhoenixStatement stmt = conn.createStatement().unwrap(PhoenixStatement.class);
-        QueryPlan plan = stmt.optimizeQuery("SELECT k FROM t WHERE k = 30 ORDER BY v1, k
LIMIT 5");
+        QueryPlan plan = stmt.optimizeQuery("SELECT k FROM t WHERE k > 30 ORDER BY v1,
k LIMIT 5");
         assertEquals("T", plan.getTableRef().getTable().getTableName().getString());
     }
     
@@ -186,10 +195,20 @@ public class QueryOptimizerTest extends BaseConnectionlessQueryTest
{
         conn.createStatement().execute("CREATE TABLE t (k INTEGER NOT NULL PRIMARY KEY DESC,
v1 VARCHAR, v2 VARCHAR) IMMUTABLE_ROWS=true");
         conn.createStatement().execute("CREATE INDEX idx ON t(v1, k)");
         PhoenixStatement stmt = conn.createStatement().unwrap(PhoenixStatement.class);
-        QueryPlan plan = stmt.optimizeQuery("SELECT k FROM t WHERE k = 30 ORDER BY v1, k
LIMIT 5");
+        QueryPlan plan = stmt.optimizeQuery("SELECT k FROM t WHERE k > 30 ORDER BY v1,
k LIMIT 5");
         assertEquals("IDX", plan.getTableRef().getTable().getTableName().getString());
     }
     
+    @Test
+    public void testChoosePointLookupOverOrderByDesc() throws Exception {
+        Connection conn = DriverManager.getConnection(getUrl());
+        conn.createStatement().execute("CREATE TABLE t (k INTEGER NOT NULL PRIMARY KEY DESC,
v1 VARCHAR, v2 VARCHAR) IMMUTABLE_ROWS=true");
+        conn.createStatement().execute("CREATE INDEX idx ON t(v1, k)");
+        PhoenixStatement stmt = conn.createStatement().unwrap(PhoenixStatement.class);
+        QueryPlan plan = stmt.optimizeQuery("SELECT k FROM t WHERE k = 30 ORDER BY v1, k
LIMIT 5");
+        assertEquals("T", plan.getTableRef().getTable().getTableName().getString());
+    }
+    
 
     @Test
     public void testChooseIndexWithLongestRowKey() throws Exception {

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/0b469d47/phoenix-core/src/test/java/org/apache/phoenix/compile/WhereClauseCompileTest.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/compile/WhereClauseCompileTest.java
b/phoenix-core/src/test/java/org/apache/phoenix/compile/WhereClauseCompileTest.java
index cfa063f..fb9dffe 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/compile/WhereClauseCompileTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/compile/WhereClauseCompileTest.java
@@ -107,7 +107,7 @@ public class WhereClauseCompileTest extends BaseConnectionlessQueryTest
{
         PDataType.LONG.toBytes(1L, key, 1);
         key[0] = SaltingUtil.getSaltingByte(key, 1, PDataType.LONG.getByteSize(), 20);
         byte[] expectedStartKey = key;
-        byte[] expectedEndKey = ByteUtil.nextKey(key);
+        byte[] expectedEndKey = ByteUtil.concat(key, QueryConstants.SEPARATOR_BYTE_ARRAY);
         byte[] startKey = scan.getStartRow();
         byte[] stopKey = scan.getStopRow();
         assertTrue(Bytes.compareTo(expectedStartKey, startKey) == 0);
@@ -128,7 +128,7 @@ public class WhereClauseCompileTest extends BaseConnectionlessQueryTest
{
         PDataType.VARCHAR.toBytes("a", key, 1);
         key[0] = SaltingUtil.getSaltingByte(key, 1, 1, 20);
         byte[] expectedStartKey = key;
-        byte[] expectedEndKey = ByteUtil.concat(key, ByteUtil.nextKey(QueryConstants.SEPARATOR_BYTE_ARRAY));
+        byte[] expectedEndKey = ByteUtil.concat(key, QueryConstants.SEPARATOR_BYTE_ARRAY);
         byte[] startKey = scan.getStartRow();
         byte[] stopKey = scan.getStopRow();
         assertTrue(Bytes.compareTo(expectedStartKey, startKey) == 0);
@@ -148,13 +148,11 @@ public class WhereClauseCompileTest extends BaseConnectionlessQueryTest
{
         PDataType.LONG.toBytes(1L, key, 1);
         key[0] = SaltingUtil.getSaltingByte(key, 1, PDataType.LONG.getByteSize(), 20);
         byte[] startKey1 = key;
-        byte[] endKey1 = ByteUtil.nextKey(key);
         
         key = new byte[PDataType.LONG.getByteSize() + 1];
         PDataType.LONG.toBytes(3L, key, 1);
         key[0] = SaltingUtil.getSaltingByte(key, 1, PDataType.LONG.getByteSize(), 20);
         byte[] startKey2 = key;
-        byte[] endKey2 = ByteUtil.nextKey(key);
         
         byte[] startKey = scan.getStartRow();
         byte[] stopKey = scan.getStopRow();
@@ -163,15 +161,15 @@ public class WhereClauseCompileTest extends BaseConnectionlessQueryTest
{
         byte[] expectedStartKey;
         byte[] expectedEndKey;
         List<List<KeyRange>> expectedRanges = Collections.singletonList(
-                Arrays.asList(KeyRange.getKeyRange(startKey1, true, endKey1, false),
-                              KeyRange.getKeyRange(startKey2, true, endKey2, false)));
+                Arrays.asList(KeyRange.getKeyRange(startKey1),
+                              KeyRange.getKeyRange(startKey2)));
         if (Bytes.compareTo(startKey1, startKey2) > 0) {
             expectedStartKey = startKey2;
-            expectedEndKey = endKey1;
+            expectedEndKey = ByteUtil.concat(startKey1, QueryConstants.SEPARATOR_BYTE_ARRAY);
             Collections.reverse(expectedRanges.get(0));
         } else {
             expectedStartKey = startKey1;
-            expectedEndKey = endKey2;
+            expectedEndKey = ByteUtil.concat(startKey2, QueryConstants.SEPARATOR_BYTE_ARRAY);;
         }
         assertTrue(Bytes.compareTo(expectedStartKey, startKey) == 0);
         assertTrue(Bytes.compareTo(expectedEndKey, stopKey) == 0);

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/0b469d47/phoenix-core/src/test/java/org/apache/phoenix/compile/WhereClauseOptimizerTest.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/compile/WhereClauseOptimizerTest.java
b/phoenix-core/src/test/java/org/apache/phoenix/compile/WhereClauseOptimizerTest.java
index 9a3755c..1de13fd 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/compile/WhereClauseOptimizerTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/compile/WhereClauseOptimizerTest.java
@@ -42,9 +42,6 @@ import java.util.Set;
 import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.client.Scan;
 import org.apache.hadoop.hbase.filter.Filter;
-import org.junit.Test;
-
-import com.google.common.collect.Sets;
 import org.apache.phoenix.compile.GroupByCompiler.GroupBy;
 import org.apache.phoenix.expression.Expression;
 import org.apache.phoenix.expression.OrExpression;
@@ -62,6 +59,9 @@ import org.apache.phoenix.schema.PDataType;
 import org.apache.phoenix.util.ByteUtil;
 import org.apache.phoenix.util.DateUtil;
 import org.apache.phoenix.util.TestUtil;
+import org.junit.Test;
+
+import com.google.common.collect.Sets;
 
 
 
@@ -1615,6 +1615,6 @@ public class WhereClauseOptimizerTest extends BaseConnectionlessQueryTest
{
                 KeyRange.getKeyRange(ByteUtil.concat(PDataType.CHAR.toBytes(secondOrgId),
PDataType.CHAR.toBytes(secondParentId)))));
         assertEquals(skipScanRanges, context.getScanRanges().getRanges());
         assertArrayEquals(ByteUtil.concat(PDataType.CHAR.toBytes(firstOrgId), PDataType.CHAR.toBytes(firstParentId)),
scan.getStartRow());
-        assertArrayEquals(ByteUtil.nextKey(ByteUtil.concat(PDataType.CHAR.toBytes(secondOrgId),
PDataType.CHAR.toBytes(secondParentId), QueryConstants.SEPARATOR_BYTE_ARRAY)), scan.getStopRow());
+        assertArrayEquals(ByteUtil.concat(PDataType.CHAR.toBytes(secondOrgId), PDataType.CHAR.toBytes(secondParentId),
QueryConstants.SEPARATOR_BYTE_ARRAY), scan.getStopRow());
     }
 }

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/0b469d47/phoenix-core/src/test/java/org/apache/phoenix/end2end/index/ImmutableIndexTest.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/end2end/index/ImmutableIndexTest.java
b/phoenix-core/src/test/java/org/apache/phoenix/end2end/index/ImmutableIndexTest.java
index ff87b40..0bf0d63 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/end2end/index/ImmutableIndexTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/end2end/index/ImmutableIndexTest.java
@@ -36,10 +36,6 @@ import java.sql.SQLException;
 import java.util.Map;
 import java.util.Properties;
 
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-import com.google.common.collect.Maps;
 import org.apache.phoenix.end2end.BaseHBaseManagedTimeTest;
 import org.apache.phoenix.exception.SQLExceptionCode;
 import org.apache.phoenix.jdbc.PhoenixConnection;
@@ -47,6 +43,10 @@ import org.apache.phoenix.query.QueryConstants;
 import org.apache.phoenix.query.QueryServices;
 import org.apache.phoenix.util.QueryUtil;
 import org.apache.phoenix.util.ReadOnlyProps;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.google.common.collect.Maps;
 
 
 public class ImmutableIndexTest extends BaseHBaseManagedTimeTest{
@@ -214,31 +214,25 @@ public class ImmutableIndexTest extends BaseHBaseManagedTimeTest{
              "CLIENT MERGE SORT");
         assertEquals(expectedPlan,QueryUtil.getExplainPlan(rs));
         
-        // Will still use index, since there's no LIMIT clause
-        query = "SELECT k,v FROM t WHERE v >= 'x' ORDER BY k";
+        // Use data table, since point lookup trumps order by
+        query = "SELECT k,v FROM t WHERE k = 'a' ORDER BY v";
         rs = conn.createStatement().executeQuery(query);
         assertTrue(rs.next());
         assertEquals("a",rs.getString(1));
         assertEquals("x",rs.getString(2));
-        assertTrue(rs.next());
-        assertEquals("b",rs.getString(1));
-        assertEquals("y",rs.getString(2));
         assertFalse(rs.next());
         rs = conn.createStatement().executeQuery("EXPLAIN " + query);
-        // Turns into an ORDER BY, which could be bad if lots of data is
-        // being returned. Without stats we don't know. The alternative
-        // would be a full table scan.
-        expectedPlan = indexSaltBuckets == null ? 
-            ("CLIENT PARALLEL 1-WAY RANGE SCAN OVER I [*] - [~'x']\n" + 
-             "    SERVER TOP -1 ROWS SORTED BY [K]\n" + 
-             "CLIENT MERGE SORT") :
-            ("CLIENT PARALLEL 4-WAY SKIP SCAN ON 4 RANGES OVER I [0,*] - [3,~'x']\n" + 
-             "    SERVER TOP -1 ROWS SORTED BY [K]\n" + 
-             "CLIENT MERGE SORT");
+        expectedPlan = tableSaltBuckets == null ? 
+                ("CLIENT PARALLEL 1-WAY RANGE SCAN OVER T ['a']\n" + 
+                     "    SERVER SORTED BY [V]\n" + 
+                     "CLIENT MERGE SORT") :
+                ("CLIENT PARALLEL 1-WAY RANGE SCAN OVER T [[2,97]]\n" + 
+                        "    SERVER SORTED BY [V]\n" + 
+                        "CLIENT MERGE SORT");
         assertEquals(expectedPlan,QueryUtil.getExplainPlan(rs));
         
-        // Will use data table now, since there's a LIMIT clause and
-        // we're able to optimize out the ORDER BY.
+        // Will use data table now, since there's an ORDER BY which can
+        // be optimized out for the data table, but not the index table.
         query = "SELECT k,v FROM t WHERE v >= 'x' ORDER BY k LIMIT 2";
         rs = conn.createStatement().executeQuery(query);
         assertTrue(rs.next());

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/0b469d47/phoenix-core/src/test/java/org/apache/phoenix/end2end/index/MutableSaltedIndexTest.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/end2end/index/MutableSaltedIndexTest.java
b/phoenix-core/src/test/java/org/apache/phoenix/end2end/index/MutableSaltedIndexTest.java
index 5c5ffd6..f6369f7 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/end2end/index/MutableSaltedIndexTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/end2end/index/MutableSaltedIndexTest.java
@@ -31,13 +31,13 @@ import java.sql.ResultSet;
 import java.util.Map;
 import java.util.Properties;
 
+import org.apache.phoenix.query.QueryServices;
+import org.apache.phoenix.util.QueryUtil;
+import org.apache.phoenix.util.ReadOnlyProps;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
 import com.google.common.collect.Maps;
-import org.apache.phoenix.query.QueryServices;
-import org.apache.phoenix.util.QueryUtil;
-import org.apache.phoenix.util.ReadOnlyProps;
 
 
 public class MutableSaltedIndexTest extends BaseMutableIndexTest{
@@ -138,27 +138,21 @@ public class MutableSaltedIndexTest extends BaseMutableIndexTest{
              "CLIENT MERGE SORT");
         assertEquals(expectedPlan,QueryUtil.getExplainPlan(rs));
         
-        // Will still use index, since there's no LIMIT clause
-        query = "SELECT k,v FROM " + DATA_TABLE_FULL_NAME + " WHERE v >= 'x' ORDER BY
k";
+        // Use data table, since point lookup trumps order by
+        query = "SELECT k,v FROM " + DATA_TABLE_FULL_NAME + " WHERE k = 'a' ORDER BY v";
         rs = conn.createStatement().executeQuery(query);
         assertTrue(rs.next());
         assertEquals("a",rs.getString(1));
         assertEquals("x",rs.getString(2));
-        assertTrue(rs.next());
-        assertEquals("b",rs.getString(1));
-        assertEquals("y",rs.getString(2));
         assertFalse(rs.next());
         rs = conn.createStatement().executeQuery("EXPLAIN " + query);
-        // Turns into an ORDER BY, which could be bad if lots of data is
-        // being returned. Without stats we don't know. The alternative
-        // would be a full table scan.
-        expectedPlan = indexSaltBuckets == null ? 
-            ("CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + INDEX_TABLE_FULL_NAME + " [*] - [~'x']\n"
+ 
-             "    SERVER TOP -1 ROWS SORTED BY [K]\n" + 
-             "CLIENT MERGE SORT") :
-            ("CLIENT PARALLEL 4-WAY SKIP SCAN ON 4 RANGES OVER " + INDEX_TABLE_FULL_NAME
+ " [0,*] - [3,~'x']\n" + 
-             "    SERVER TOP -1 ROWS SORTED BY [K]\n" + 
-             "CLIENT MERGE SORT");
+        expectedPlan = tableSaltBuckets == null ? 
+                "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + DATA_TABLE_FULL_NAME + " ['a']\n"
+
+                "    SERVER SORTED BY [V]\n" + 
+                "CLIENT MERGE SORT" :
+                    "CLIENT PARALLEL 1-WAY RANGE SCAN OVER T [[2,97]]\n" + 
+                    "    SERVER SORTED BY [V]\n" + 
+                    "CLIENT MERGE SORT";
         assertEquals(expectedPlan,QueryUtil.getExplainPlan(rs));
         
         // Will use data table now, since there's a LIMIT clause and


Mime
View raw message