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

chenglei pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/phoenix.git


The following commit(s) were added to refs/heads/master by this push:
     new 37756efc11 PHOENIX-7352 Improve OrderPreservingTracker to support 
extracting par… (#1926)
37756efc11 is described below

commit 37756efc1183911f72347ee65f344ddd6bcaeb86
Author: chenglei <[email protected]>
AuthorDate: Tue Jul 23 15:52:39 2024 +0800

    PHOENIX-7352 Improve OrderPreservingTracker to support extracting par… 
(#1926)
---
 .../apache/phoenix/compile/GroupByCompiler.java    |   5 +
 .../phoenix/compile/OrderPreservingTracker.java    | 246 ++++++++++-------
 .../apache/phoenix/execute/SortMergeJoinPlan.java  |  15 +-
 .../phoenix/execute/TupleProjectionPlan.java       |  21 +-
 .../java/org/apache/phoenix/end2end/OrderByIT.java | 137 +++++++++
 .../apache/phoenix/compile/QueryCompilerTest.java  | 305 +++++++++++++++++++++
 6 files changed, 618 insertions(+), 111 deletions(-)

diff --git 
a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/GroupByCompiler.java
 
b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/GroupByCompiler.java
index 695ba0d8e2..d348eafcc2 100644
--- 
a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/GroupByCompiler.java
+++ 
b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/GroupByCompiler.java
@@ -155,6 +155,11 @@ public class GroupByCompiler {
             return isUngroupedAggregate;
         }
 
+        /**
+         * This value represents the row key column count corresponding to 
longest continuous
+         * ordering columns returned by {@link 
GroupBy#getOrderPreservingTrackInfos}, it may
+         * not equal to the size of {@link 
GroupBy#getOrderPreservingTrackInfos}.
+         */
         public int getOrderPreservingColumnCount() {
             return orderPreservingColumnCount;
         }
diff --git 
a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/OrderPreservingTracker.java
 
b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/OrderPreservingTracker.java
index 2701a48304..03c864bbde 100644
--- 
a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/OrderPreservingTracker.java
+++ 
b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/OrderPreservingTracker.java
@@ -53,25 +53,28 @@ import 
org.apache.phoenix.thirdparty.com.google.common.collect.Lists;
  * for each group will be contiguous. For ORDER BY, we can drop the ORDER BY 
statement if
  * the order is preserved.
  * 
- * There are mainly three changes for refactoring this class in PHOENIX-5148:
- * 1.added a {@link #getInputOrderBys} method to determine the input OrderBys 
by the combination
- *   of innerQueryPlan's output OrderBys , GroupBy of current QueryPlan and 
the rowKeyColumns of current table.
+ * There are mainly four changes for refactoring this class after PHOENIX-5148:
+ * 1.Add a {@link #getInputOrderBys} method to determine the input OrderBys 
through
+ *   innerQueryPlan's output OrderBys, GroupBy of current QueryPlan or the 
rowKeyColumns of
+ *   current table.
  *
- * 2.because the innerQueryPlan may have multiple output OrderBys(for {@link 
SortMergeJoinPlan}),
- *   so I extracted many stateful member variables (such as 
orderPreservingTrackInfos, isOrderPreserving
- *   and isReverse etc.) to a new inner class {@link TrackOrderByContext}, 
{@link TrackOrderByContext}
+ * 2.Because the innerQueryPlan may have multiple output OrderBys(eg. {@link 
SortMergeJoinPlan}),
+ *   so many stateful member variables (such as orderPreservingTrackInfos, 
isOrderPreserving
+ *   and isReverse etc.) are extracted to a new inner class {@link 
TrackOrderByContext}, {@link TrackOrderByContext}
  *   is used to track if a single Input OrderBy matches the target 
OrderByExpressions in {@link #isOrderPreserving()}
  *   method, and once we found a  {@link TrackOrderByContext} satisfied {@link 
#isOrderPreserving()},
- *   {@link #selectedTrackOrderByContext} member variable is set to this 
{@link TrackOrderByContext},
- *   then we can use this {@link #selectedTrackOrderByContext} to implement 
the {@link #getOrderPreservingTrackInfos()}
+ *   {@link OrderPreservingTracker#selectedTrackOrderByContext} member 
variable is set to this {@link TrackOrderByContext},
+ *   then we can use this {@link #selectedTrackOrderByContext} to serve the 
{@link OrderPreservingTracker#getOrderPreservingTrackInfos}
  *   and {@link #isReverse()} methods etc.
  *   BTW. at most only one {@link TrackOrderByContext} can meet {@link 
#isOrderPreserving()} is true.
  *
- * 3.added ascending and nullsLast to the inner class {@link Info} , and 
extracted complete ordering
+ * 3.Add ascending and nullsLast to the inner class {@link Info} , and 
extracted complete ordering
  *   information in {@link Info} class by the inner {@link 
TrackOrderPreservingExpressionVisitor} class,
  *   so we can inferring alignment between the target OrderByExpressions and 
the input OrderBys based on
- *   {@link Info} in {@link TrackOrderByContext#doTrack} method, not on the 
row keys like the original
- *   {@link #track} method does.
+ *   {@link Info}, not on the row keys like the original method does.
+ *
+ * 4.{@link OrderPreservingTracker#getOrderPreservingTrackInfos} could extract 
partial continuous ordering columns which start from the
+ *   first column even if {@link OrderPreservingTracker#isOrderPreserving} is 
false.
  * </pre>
  */
 public class OrderPreservingTracker {
@@ -83,7 +86,7 @@ public class OrderPreservingTracker {
         private final int slotSpan;
         private final boolean ascending;
         private final boolean nullsLast;
-        private Expression expression;
+        private TrackingOrderByExpression trackingOrderByExpression;
 
         public Info(int pkPosition, boolean ascending, boolean nullsLast) {
             this.pkPosition = pkPosition;
@@ -104,13 +107,13 @@ public class OrderPreservingTracker {
         public static List<Expression> extractExpressions(List<Info> 
orderPreservingTrackInfos) {
             List<Expression> newExpressions = new 
ArrayList<Expression>(orderPreservingTrackInfos.size());
             for(Info trackInfo : orderPreservingTrackInfos) {
-                newExpressions.add(trackInfo.expression);
+                newExpressions.add(trackInfo.getExpression());
             }
             return newExpressions;
         }
 
         public Expression getExpression() {
-            return expression;
+            return this.trackingOrderByExpression.expression;
         }
 
         public boolean isAscending() {
@@ -124,12 +127,13 @@ public class OrderPreservingTracker {
     private final StatementContext context;
     private final GroupBy groupBy;
     private final Ordering ordering;
-    private int pkPositionOffset = 0;
+    private final int pkPositionOffset;
     private Expression whereExpression;
-    private List<TrackOrderByCell> trackOrderByCells = new 
LinkedList<TrackOrderByCell>();
-    private List<TrackOrderByContext> trackOrderByContexts = 
Collections.<TrackOrderByContext> emptyList();
+    private List<TrackingOrderByExpression> trackingOrderByExpressions =
+            new LinkedList<TrackingOrderByExpression>();
+    private final List<TrackOrderByContext> trackOrderByContexts;
     private TrackOrderByContext selectedTrackOrderByContext = null;
-    private List<OrderBy> inputOrderBys = Collections.<OrderBy> emptyList();
+    private final List<OrderBy> inputOrderBys;
 
     public OrderPreservingTracker(StatementContext context, GroupBy groupBy, 
Ordering ordering, int nNodes) throws SQLException {
         this(context, groupBy, ordering, nNodes, null, null, null);
@@ -145,94 +149,102 @@ public class OrderPreservingTracker {
             Expression whereExpression) throws SQLException {
 
         this.context = context;
-        boolean isOrderPreserving = false;
-        if (groupBy.isEmpty() && inputOrderBys == null) {
-            PTable table = context.getResolver().getTables().get(0).getTable();
-            isOrderPreserving = table.rowKeyOrderOptimizable();
-        } else {
-            isOrderPreserving = true;
-        }
         this.groupBy = groupBy;
         this.ordering = ordering;
         this.whereExpression = whereExpression;
-        if(inputOrderBys != null) {
+        if (inputOrderBys != null) {
             this.inputOrderBys = inputOrderBys;
+            this.pkPositionOffset = 0;
         } else {
-            this.getInputOrderBys(innerQueryPlan, groupBy, context);
+            Pair<List<OrderBy>, Integer> orderBysAndRowKeyColumnOffset =
+                    getInputOrderBys(innerQueryPlan, groupBy, context);
+            this.inputOrderBys = orderBysAndRowKeyColumnOffset.getFirst();
+            this.pkPositionOffset = orderBysAndRowKeyColumnOffset.getSecond();
         }
-        if(this.inputOrderBys.isEmpty()) {
+
+        if (this.inputOrderBys.isEmpty()) {
+            this.trackOrderByContexts = Collections.emptyList();
             return;
         }
+
         this.trackOrderByContexts = new 
ArrayList<TrackOrderByContext>(this.inputOrderBys.size());
         for(OrderBy inputOrderBy : this.inputOrderBys) {
             this.trackOrderByContexts.add(
-                    new TrackOrderByContext(isOrderPreserving, nNodes, 
inputOrderBy));
+                    new TrackOrderByContext(nNodes, inputOrderBy));
         }
     }
 
     /**
      * Infer input OrderBys, if the innerQueryPlan is null, we make the 
OrderBys from the pk columns of {@link PTable}.
-     * @param innerQueryPlan
-     * @param groupBy
-     * @param statementContext
-     * @throws SQLException
      */
-    private void getInputOrderBys(QueryPlan innerQueryPlan, GroupBy groupBy, 
StatementContext statementContext) throws SQLException {
-        if(!groupBy.isEmpty()) {
-            this.inputOrderBys = 
Collections.singletonList(ExpressionUtil.convertGroupByToOrderBy(groupBy, 
false));
-            return;
+    private static Pair<List<OrderBy>, Integer> getInputOrderBys(
+            QueryPlan innerQueryPlan,
+            GroupBy groupBy,
+            StatementContext statementContext) throws SQLException {
+        if (!groupBy.isEmpty()) {
+            return Pair.newPair(
+                    Collections.singletonList(
+                            ExpressionUtil.convertGroupByToOrderBy(groupBy, 
false)), 0);
         }
-        if(innerQueryPlan != null) {
-            this.inputOrderBys = innerQueryPlan.getOutputOrderBys();
-            return;
+        if (innerQueryPlan != null) {
+            return Pair.newPair(innerQueryPlan.getOutputOrderBys(), 0);
         }
-        this.inputOrderBys = Collections.<OrderBy> emptyList();
+
         TableRef tableRef = statementContext.getResolver().getTables().get(0);
+        if (!tableRef.getTable().rowKeyOrderOptimizable()) {
+            return Pair.newPair(Collections.<OrderBy>emptyList(), 0);
+        }
         PhoenixConnection phoenixConnection = statementContext.getConnection();
-        Pair<OrderBy,Integer> orderByAndRowKeyColumnOffset =
+        Pair<OrderBy, Integer> orderByAndRowKeyColumnOffset =
                 ExpressionUtil.getOrderByFromTable(tableRef, 
phoenixConnection, false);
         OrderBy orderBy = orderByAndRowKeyColumnOffset.getFirst();
-        this.pkPositionOffset = orderByAndRowKeyColumnOffset.getSecond();
-        if(orderBy != OrderBy.EMPTY_ORDER_BY) {
-            this.inputOrderBys = Collections.singletonList(orderBy);
-        }
+        Integer rowKeyColumnOffset = orderByAndRowKeyColumnOffset.getSecond();
+        return Pair.newPair(
+                    orderBy != OrderBy.EMPTY_ORDER_BY
+                    ? Collections.singletonList(orderBy)
+                    : Collections.<OrderBy>emptyList(),
+                    rowKeyColumnOffset);
     }
 
     private class TrackOrderByContext {
-        private List<Info> orderPreservingTrackInfos;
+        private final List<Info> orderPreservingTrackedInfos;
         private boolean isOrderPreserving = true;
         private Boolean isReverse = null;
         private int orderPreservingColumnCount = 0;
+        private int orderedTrackedInfosCount = 0;
         private final TrackOrderPreservingExpressionVisitor 
trackOrderPreservingExpressionVisitor;
-        private OrderBy inputOrderBy = null;
+        private final OrderBy inputOrderBy;
+        private int trackingOrderByExpressionCount = 0;
+        private boolean isOrderPreservingCalled = false;
 
-        public TrackOrderByContext(boolean isOrderPreserving, int 
orderByNodeCount, OrderBy inputOrderBy) {
-            this.isOrderPreserving = isOrderPreserving;
+        TrackOrderByContext(int orderByNodeCount, OrderBy inputOrderBy) {
             this.trackOrderPreservingExpressionVisitor = new 
TrackOrderPreservingExpressionVisitor(inputOrderBy);
-            this.orderPreservingTrackInfos = 
Lists.newArrayListWithExpectedSize(orderByNodeCount);
+            this.orderPreservingTrackedInfos = 
Lists.newArrayListWithExpectedSize(orderByNodeCount);
             this.inputOrderBy = inputOrderBy;
         }
 
-        public void track(List<TrackOrderByCell> trackOrderByCells) {
-            for(TrackOrderByCell trackOrderByCell : trackOrderByCells) {
-                doTrack(trackOrderByCell.expression,
-                        trackOrderByCell.isAscending,
-                        trackOrderByCell.isNullsLast);
-            }
+        public void track(List<TrackingOrderByExpression> 
trackingOrderByExpressions) {
+            this.trackingOrderByExpressionCount = 
trackingOrderByExpressions.size();
+            trackingOrderByExpressions.forEach(trackingOrderByExpression -> {
+                Expression expression = trackingOrderByExpression.expression;
+                Info trackedInfo = 
expression.accept(trackOrderPreservingExpressionVisitor);
+                if (trackedInfo != null) {
+                    trackedInfo.trackingOrderByExpression = 
trackingOrderByExpression;
+                    orderPreservingTrackedInfos.add(trackedInfo);
+                }
+            });
         }
 
-        private void doTrack(Expression expression, Boolean isAscending, 
Boolean isNullsLast) {
-            if (!isOrderPreserving) {
-               return;
-            }
-            Info trackInfo = 
expression.accept(trackOrderPreservingExpressionVisitor);
-            if (trackInfo == null) {
-                isOrderPreserving = false;
-                return;
-            }
+        private void checkAscendingAndNullsLast(Info trackedInfo) {
+            TrackingOrderByExpression trackingOrderByExpression =
+                    trackedInfo.trackingOrderByExpression;
+            Expression expression = trackingOrderByExpression.expression;
+            Boolean isAscending = trackingOrderByExpression.isAscending;
+            Boolean isNullsLast = trackingOrderByExpression.isNullsLast;
+
             // If the expression is sorted in a different order than the 
specified sort order
             // then the expressions are not order preserving.
-            if (isAscending != null && trackInfo.ascending != 
isAscending.booleanValue()) {
+            if (isAscending != null && trackedInfo.ascending != 
isAscending.booleanValue()) {
                 if (isReverse == null) {
                     isReverse = true;
                 } else if (!isReverse){
@@ -250,22 +262,31 @@ public class OrderPreservingTracker {
                 }
             }
 
-            if (isNullsLast!=null && expression.isNullable()) {
-                if ((trackInfo.nullsLast == isNullsLast.booleanValue()) && 
isReverse.booleanValue() ||
-                    (trackInfo.nullsLast != isNullsLast.booleanValue()) && 
!isReverse.booleanValue()) {
+            assert isReverse != null;
+            if (isNullsLast != null && expression.isNullable()) {
+                if (trackedInfo.nullsLast == isNullsLast.booleanValue()
+                        && isReverse.booleanValue()
+                    || trackedInfo.nullsLast != isNullsLast.booleanValue()
+                        && !isReverse.booleanValue()) {
                     isOrderPreserving = false;
                     isReverse = false;
                     return;
                 }
             }
-            trackInfo.expression = expression;
-            orderPreservingTrackInfos.add(trackInfo);
         }
 
-        /*
-         * Only valid AFTER call to isOrderPreserving
+        /**
+         * Only valid AFTER call to isOrderPreserving.
+         * This value represents the input column count of {@link 
TrackOrderByContext#inputOrderBy}
+         * corresponding to longest continuous ordering columns returned by
+         * {@link TrackOrderByContext#getOrderPreservingTrackInfos}, it may 
not equal to the size
+         * of {@link TrackOrderByContext#getOrderPreservingTrackInfos}.
          */
         public int getOrderPreservingColumnCount() {
+            if (!isOrderPreservingCalled) {
+                throw new IllegalStateException(
+                        "getOrderPreservingColumnCount must be called after 
isOrderPreserving is called!");
+            }
             return orderPreservingColumnCount;
         }
 
@@ -273,26 +294,32 @@ public class OrderPreservingTracker {
          * Only valid AFTER call to isOrderPreserving
          */
         public List<Info> getOrderPreservingTrackInfos() {
-            if(this.isOrderPreserving) {
-                return ImmutableList.copyOf(this.orderPreservingTrackInfos);
+            if (!isOrderPreservingCalled) {
+                throw new IllegalStateException(
+                        "getOrderPreservingTrackInfos must be called after 
isOrderPreserving is called!");
+            }
+            if (this.isOrderPreserving) {
+                return ImmutableList.copyOf(this.orderPreservingTrackedInfos);
             }
-            int orderPreservingColumnCountToUse = 
this.orderPreservingColumnCount - pkPositionOffset;
-            if(orderPreservingColumnCountToUse <= 0) {
+            if (this.orderedTrackedInfosCount <= 0) {
                 return Collections.<Info> emptyList();
             }
-            return 
ImmutableList.copyOf(this.orderPreservingTrackInfos.subList(0, 
orderPreservingColumnCountToUse));
+            return ImmutableList.copyOf(
+                    this.orderPreservingTrackedInfos.subList(
+                            0, this.orderedTrackedInfosCount));
         }
 
         public boolean isOrderPreserving() {
-            if (!isOrderPreserving) {
-                return false;
+            if (this.isOrderPreservingCalled) {
+                return isOrderPreserving;
             }
+
             if (ordering == Ordering.UNORDERED) {
                 // Sort by position
-                Collections.sort(orderPreservingTrackInfos, new 
Comparator<Info>() {
+                Collections.sort(orderPreservingTrackedInfos, new 
Comparator<Info>() {
                     @Override
                     public int compare(Info o1, Info o2) {
-                        int cmp = 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
@@ -307,23 +334,30 @@ public class OrderPreservingTracker {
             int prevSlotSpan = 1;
             int prevPos =  -1;
             OrderPreserving prevOrderPreserving = OrderPreserving.YES;
-            for (int i = 0; i < orderPreservingTrackInfos.size(); i++) {
-                Info entry = orderPreservingTrackInfos.get(i);
+            this.orderedTrackedInfosCount = 0;
+            for (int i = 0; i < orderPreservingTrackedInfos.size(); i++) {
+                Info entry = orderPreservingTrackedInfos.get(i);
                 int pos = entry.pkPosition;
+                this.checkAscendingAndNullsLast(entry);
                 isOrderPreserving = isOrderPreserving &&
                         entry.orderPreserving != OrderPreserving.NO &&
                         prevOrderPreserving == OrderPreserving.YES &&
                         (pos == prevPos ||
                          pos - prevSlotSpan == prevPos  ||
-                         hasEqualityConstraints(prevPos+prevSlotSpan, pos));
-                if(!isOrderPreserving) {
+                         hasEqualityConstraints(prevPos + prevSlotSpan, pos));
+                if (!isOrderPreserving) {
                     break;
                 }
+                this.orderedTrackedInfosCount++;
                 prevPos = pos;
                 prevSlotSpan = entry.slotSpan;
                 prevOrderPreserving = entry.orderPreserving;
             }
+            isOrderPreserving = isOrderPreserving
+                    && this.orderPreservingTrackedInfos.size()
+                    == this.trackingOrderByExpressionCount;
             orderPreservingColumnCount = prevPos + prevSlotSpan + 
pkPositionOffset;
+            this.isOrderPreservingCalled = true;
             return isOrderPreserving;
         }
 
@@ -345,6 +379,14 @@ public class OrderPreservingTracker {
         }
 
         public boolean isReverse() {
+            if (!isOrderPreservingCalled) {
+                throw new IllegalStateException(
+                        "isReverse must be called after isOrderPreserving is 
called!");
+            }
+            if (!isOrderPreserving) {
+                throw new IllegalStateException(
+                        "isReverse should only be called when 
isOrderPreserving is true!");
+            }
             return Boolean.TRUE.equals(isReverse);
         }
 
@@ -360,13 +402,14 @@ public class OrderPreservingTracker {
         }
     }
 
-    private static class TrackOrderByCell
+    private static class TrackingOrderByExpression
     {
         private Expression expression;
         private Boolean isAscending;
         private Boolean isNullsLast;
 
-        public TrackOrderByCell(Expression expression,Boolean isAscending, 
Boolean isNullsLast) {
+        TrackingOrderByExpression(
+                Expression expression, Boolean isAscending, Boolean 
isNullsLast) {
             this.expression = expression;
             this.isAscending = isAscending;
             this.isNullsLast = isNullsLast;
@@ -378,13 +421,16 @@ public class OrderPreservingTracker {
     }
 
     public void track(Expression expression, Boolean isAscending, Boolean 
isNullsLast) {
-        TrackOrderByCell trackOrderByContext =
-                new TrackOrderByCell(expression, isAscending, isNullsLast);
-        this.trackOrderByCells.add(trackOrderByContext);
+        TrackingOrderByExpression trackingOrderByExpression =
+                new TrackingOrderByExpression(expression, isAscending, 
isNullsLast);
+        this.trackingOrderByExpressions.add(trackingOrderByExpression);
     }
 
-    /*
-     * Only valid AFTER call to isOrderPreserving
+    /**
+     * Only valid AFTER call to isOrderPreserving.
+     * This value represents the input column count corresponding to longest 
continuous ordering
+     * columns returned by {@link 
OrderPreservingTracker#getOrderPreservingTrackInfos}, it may not
+     * equal to the size of {@link 
OrderPreservingTracker#getOrderPreservingTrackInfos}.
      */
     public int getOrderPreservingColumnCount() {
         if(this.selectedTrackOrderByContext == null) {
@@ -404,15 +450,15 @@ public class OrderPreservingTracker {
     }
 
     public boolean isOrderPreserving() {
-        if(this.selectedTrackOrderByContext != null) {
-            throw new IllegalStateException("isOrderPreserving should be 
called only once");
+        if (this.selectedTrackOrderByContext != null) {
+            return this.selectedTrackOrderByContext.isOrderPreserving();
         }
 
-        if(this.trackOrderByContexts.isEmpty()) {
+        if (this.trackOrderByContexts.isEmpty()) {
            return false;
         }
 
-        if(this.trackOrderByCells.isEmpty()) {
+        if (this.trackingOrderByExpressions.isEmpty()) {
             return false;
         }
 
@@ -420,7 +466,7 @@ public class OrderPreservingTracker {
          * at most only one TrackOrderByContext can meet isOrderPreserving is 
true
          */
         for(TrackOrderByContext trackOrderByContext : 
this.trackOrderByContexts) {
-            trackOrderByContext.track(trackOrderByCells);
+            trackOrderByContext.track(trackingOrderByExpressions);
             if(trackOrderByContext.isOrderPreserving()) {
                this.selectedTrackOrderByContext = trackOrderByContext;
                break;
@@ -430,7 +476,7 @@ public class OrderPreservingTracker {
                 this.selectedTrackOrderByContext = trackOrderByContext;
             }
         }
-        return this.selectedTrackOrderByContext.isOrderPreserving;
+        return this.selectedTrackOrderByContext.isOrderPreserving();
     }
 
     public boolean isReverse() {
diff --git 
a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/SortMergeJoinPlan.java
 
b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/SortMergeJoinPlan.java
index 7a3dcb84a4..c22ac0ff18 100644
--- 
a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/SortMergeJoinPlan.java
+++ 
b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/SortMergeJoinPlan.java
@@ -940,13 +940,13 @@ public class SortMergeJoinPlan implements QueryPlan {
 
         List<OrderBy> orderBys = new ArrayList<OrderBy>(2);
         List<OrderByExpression> lhsOrderByExpressions =
-                compileOrderByNodes(lhsOrderByNodes, statementContext);
+                compileOutputOrderByExpressions(lhsOrderByNodes, 
statementContext);
         if(!lhsOrderByExpressions.isEmpty()) {
             orderBys.add(new OrderBy(lhsOrderByExpressions));
         }
 
         List<OrderByExpression> rhsOrderByExpressions =
-                compileOrderByNodes(rhsOrderByNodes, statementContext);
+                compileOutputOrderByExpressions(rhsOrderByNodes, 
statementContext);
         if(!rhsOrderByExpressions.isEmpty()) {
             orderBys.add(new OrderBy(rhsOrderByExpressions));
         }
@@ -956,7 +956,9 @@ public class SortMergeJoinPlan implements QueryPlan {
         return orderBys;
     }
 
-    private static List<OrderByExpression> 
compileOrderByNodes(List<OrderByNode> orderByNodes, StatementContext 
statementContext) throws SQLException {
+    private static List<OrderByExpression> compileOutputOrderByExpressions(
+            List<OrderByNode> orderByNodes,
+            StatementContext statementContext) throws SQLException {
         /**
          * If there is TableNotFoundException or ColumnNotFoundException, it 
means that the orderByNodes is not referenced by other parts of the sql,
          * so could be ignored.
@@ -976,6 +978,13 @@ public class SortMergeJoinPlan implements QueryPlan {
                 return orderByExpressions;
             }
             assert expression != null;
+            // Note here we don't like OrderByCompiler#compile method to 
reverse the
+            // OrderByExpression#isAscending if expression#sortOrder is 
SortOrder.DESC. That's
+            // because we compile it for QueryPlan#getOutputOrderBys and the 
compiled
+            // OrderByExpression is used for OrderPreservingTracker, not used 
in
+            // OrderedResultIterator to compare based on binary representation.
+            // TODO: We should make a explicit distinction between 
OrderByExpression for
+            // OrderPreservingTracker and OrderByExpression for 
OrderedResultIterator computation.
             orderByExpressions.add(
                     OrderByExpression.createByCheckIfOrderByReverse(
                             expression,
diff --git 
a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/TupleProjectionPlan.java
 
b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/TupleProjectionPlan.java
index 1a3bac2d47..a932446936 100644
--- 
a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/TupleProjectionPlan.java
+++ 
b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/TupleProjectionPlan.java
@@ -54,9 +54,8 @@ import 
org.apache.phoenix.thirdparty.com.google.common.collect.Lists;
 public class TupleProjectionPlan extends DelegateQueryPlan {
     private final TupleProjector tupleProjector;
     private final Expression postFilter;
-    private final StatementContext statementContext;
-    private ColumnResolver columnResolver = null;
-    private List<OrderBy> actualOutputOrderBys = Collections.<OrderBy> 
emptyList();
+    private final ColumnResolver columnResolver;
+    private final List<OrderBy> actualOutputOrderBys;
 
     public TupleProjectionPlan(
             QueryPlan plan,
@@ -64,13 +63,17 @@ public class TupleProjectionPlan extends DelegateQueryPlan {
             StatementContext statementContext,
             Expression postFilter) throws SQLException {
         super(plan);
-        if (tupleProjector == null) throw new 
IllegalArgumentException("tupleProjector is null");
+        if (tupleProjector == null) {
+            throw new IllegalArgumentException("tupleProjector is null");
+        }
         this.tupleProjector = tupleProjector;
-        this.statementContext = statementContext;
         this.postFilter = postFilter;
-        if(this.statementContext != null) {
+        if (statementContext != null) {
             this.columnResolver = statementContext.getResolver();
             this.actualOutputOrderBys = this.convertInputOrderBys(plan);
+        } else {
+            this.columnResolver = null;
+            this.actualOutputOrderBys = Collections.<OrderBy>emptyList();
         }
     }
 
@@ -95,6 +98,7 @@ public class TupleProjectionPlan extends DelegateQueryPlan {
         List<OrderBy> newOrderBys = new 
ArrayList<OrderBy>(inputOrderBys.size());
         for(OrderBy inputOrderBy : inputOrderBys) {
             OrderBy newOrderBy = this.convertSingleInputOrderBy(
+                    targetQueryPlan,
                     selectColumnExpressionToIndex,
                     selectColumnExpressions,
                     inputOrderBy);
@@ -109,12 +113,13 @@ public class TupleProjectionPlan extends 
DelegateQueryPlan {
     }
 
     private OrderBy convertSingleInputOrderBy(
+            QueryPlan targetQueryPlan,
             Map<Expression,Integer> selectColumnExpressionToIndex,
             Expression[] selectColumnExpressions,
             OrderBy inputOrderBy) throws SQLException {
-
+        //Here we track targetQueryPlan's output so we use targetQueryPlan's 
StatementContext
         OrderPreservingTracker orderPreservingTracker = new 
OrderPreservingTracker(
-                this.statementContext,
+                targetQueryPlan.getContext(),
                 GroupBy.EMPTY_GROUP_BY,
                 Ordering.UNORDERED,
                 selectColumnExpressions.length,
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/OrderByIT.java 
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/OrderByIT.java
index 6539565cd1..490dd45895 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/OrderByIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/OrderByIT.java
@@ -17,9 +17,146 @@
  */
 package org.apache.phoenix.end2end;
 
+import static org.apache.phoenix.util.TestUtil.assertResultSet;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import org.junit.Test;
 import org.junit.experimental.categories.Category;
 
+
 @Category(ParallelStatsDisabledTest.class)
 public class OrderByIT extends BaseOrderByIT {
 
+    @Test
+    public void testPartialOrderForTupleProjectionPlanBug7352() throws 
Exception {
+        doTestPartialOrderForTupleProjectionPlanBug7352(false, false);
+        doTestPartialOrderForTupleProjectionPlanBug7352(false, true);
+        doTestPartialOrderForTupleProjectionPlanBug7352(true, false);
+        doTestPartialOrderForTupleProjectionPlanBug7352(true, true);
+    }
+
+    private void doTestPartialOrderForTupleProjectionPlanBug7352(
+            boolean desc, boolean salted) throws Exception {
+        Connection conn = null;
+        try {
+            conn = DriverManager.getConnection(getUrl(), props);
+            String tableName = generateUniqueName();
+            String sql = "create table " + tableName + "( "+
+                    " pk1 char(20) not null , " +
+                    " pk2 char(20) not null, " +
+                    " pk3 char(20) not null," +
+                    " v1 varchar, " +
+                    " v2 varchar, " +
+                    " v3 varchar, " +
+                    " CONSTRAINT TEST_PK PRIMARY KEY ( "+
+                    "pk1 "+(desc ? "desc" : "")+", "+
+                    "pk2 "+(desc ? "desc" : "")+", "+
+                    "pk3 "+(desc ? "desc" : "")+
+                    " )) "+(salted ? "SALT_BUCKETS =4" : "split on('b')");
+            conn.createStatement().execute(sql);
+
+            conn.createStatement().execute("UPSERT INTO "+tableName+" VALUES 
('a11','a12','a13','a14','a15','a16')");
+            conn.createStatement().execute("UPSERT INTO "+tableName+" VALUES 
('a21','a22','a23','a24','a25','a26')");
+            conn.createStatement().execute("UPSERT INTO "+tableName+" VALUES 
('a31','a32','a33','a34','a35','a36')");
+            conn.createStatement().execute("UPSERT INTO "+tableName+" VALUES 
('b11','b12','b13','b14','b15','b16')");
+            conn.createStatement().execute("UPSERT INTO "+tableName+" VALUES 
('b21','b22','b23','b24','b25', 'b26')");
+            conn.createStatement().execute("UPSERT INTO "+tableName+" VALUES 
('b31','b32','b33','b34','b35', 'b36')");
+            conn.commit();
+
+            sql = "select pk3,v1,v2 from (select v1,v2,pk3 from " + tableName 
+ " t where pk1 > 'a10' order by t.v2,t.v1 limit 10) a order by v2";
+            ResultSet rs = conn.prepareStatement(sql).executeQuery();
+            assertResultSet(rs, new 
Object[][]{{"a13"},{"a23"},{"a33"},{"b13"},{"b23"},{"b33"}});
+
+            sql = "select pk3,v1,v2 from (select v1,v2,pk3 from " + tableName
+                    + " t where pk1 > 'a10' order by t.v2 desc,t.v1 desc limit 
10) a order by v2 desc";
+            rs = conn.prepareStatement(sql).executeQuery();
+            assertResultSet(rs, new 
Object[][]{{"b33"},{"b23"},{"b13"},{"a33"},{"a23"},{"a13"}});
+
+            sql = "select pk3,v1,v2 from (select v1,v2,pk3 from " + tableName
+                    + " t where pk1 > 'a10' order by t.v2 desc,t.v1 desc, t.v3 
desc limit 10) a order by v2 desc";
+            rs = conn.prepareStatement(sql).executeQuery();
+            assertResultSet(rs, new 
Object[][]{{"b33"},{"b23"},{"b13"},{"a33"},{"a23"},{"a13"}});
+
+            sql = "select pk3,v1,v2 from (select v1,v2,pk3 from " + tableName
+                    + " t where pk1 > 'a10' order by t.v2 desc,t.v1 desc, t.v3 
asc limit 10) a order by v2 desc";
+            rs = conn.prepareStatement(sql).executeQuery();
+            assertResultSet(rs, new 
Object[][]{{"b33"},{"b23"},{"b13"},{"a33"},{"a23"},{"a13"}});
+
+            sql = "select v2,cnt from (select count(pk3) cnt,v1,v2 from " + 
tableName
+                    + " t where pk1 > 'a10' group by t.v1, t.v2, t.v3 limit 
10) a order by v1";
+            rs = conn.prepareStatement(sql).executeQuery();
+            assertResultSet(rs, new 
Object[][]{{"a15"},{"a25"},{"a35"},{"b15"},{"b25"},{"b35"}});
+
+            sql = "select sub, pk2Cnt from (select substr(v2,0,3) sub, cast 
(count(pk3) as bigint) cnt, count(pk2) pk2Cnt from "
+                    + tableName
+                    + " t where pk1 > 'a10' group by t.v1 ,t.v2, t.v3 "
+                    + " order by count(pk3) desc,t.v2 desc,t.v3 desc limit 10) 
a order by cnt desc ,sub desc";
+            rs = conn.prepareStatement(sql).executeQuery();
+            assertResultSet(rs, new 
Object[][]{{"b35"},{"b25"},{"b15"},{"a35"},{"a25"},{"a15"}});
+
+            sql = "select sub, pk2Cnt from (select substr(v2,0,3) sub,cast 
(count(pk3) as bigint) cnt, count(pk2) pk2Cnt from "
+                    + tableName
+                    + " t where pk1 > 'a10' group by t.v1 ,t.v2, t.v3 "
+                    + " order by count(pk3) desc,t.v2 desc,t.v3 asc limit 10) 
a order by cnt desc ,sub desc";
+            rs = conn.prepareStatement(sql).executeQuery();
+            assertResultSet(rs, new 
Object[][]{{"b35"},{"b25"},{"b15"},{"a35"},{"a25"},{"a15"}});
+
+            sql = "select sub, pk2Cnt from (select substr(v2,0,3) sub,cast 
(count(pk3) as bigint) cnt, count(pk2) pk2Cnt from "
+                    + tableName
+                    + " t where pk1 > 'a10' group by t.v1 ,t.v2, t.v3 "
+                    + " order by t.v2 desc, count(pk3) desc, t.v3 desc limit 
10) a order by sub desc, cnt desc";
+            rs = conn.prepareStatement(sql).executeQuery();
+            assertResultSet(rs, new 
Object[][]{{"b35"},{"b25"},{"b15"},{"a35"},{"a25"},{"a15"}});
+
+            sql = "select sub, pk2Cnt from (select substr(v2,0,3) sub,cast 
(count(pk3) as bigint) cnt, count(pk2) pk2Cnt from "
+                    + tableName
+                    + " t where pk1 > 'a10' group by t.v1 ,t.v2, t.v3 "
+                    + " order by t.v2 desc, count(pk3) desc, t.v3 asc limit 
10) a order by sub desc, cnt desc";
+            rs = conn.prepareStatement(sql).executeQuery();
+            assertResultSet(rs, new 
Object[][]{{"b35"},{"b25"},{"b15"},{"a35"},{"a25"},{"a15"}});
+
+            sql = "select v1, pk3, v2 from (select v1,v2,pk3 from " + tableName
+                    + " t where pk1 > 'a10' order by t.v2,t.v1, t.v3 limit 10) 
a order by v1";
+            rs = conn.prepareStatement(sql).executeQuery();
+            assertResultSet(rs, new 
Object[][]{{"a14"},{"a24"},{"a34"},{"b14"},{"b24"},{"b34"}});
+
+            sql = "select pk3,pk1,pk2 from (select pk1,pk2,pk3 from " + 
tableName
+                    + " t where pk1 > 'a10' order by t.v2, t.v1, t.v3 limit 
10) a order by pk3";
+            rs = conn.prepareStatement(sql).executeQuery();
+            assertResultSet(rs, new 
Object[][]{{"a13"},{"a23"},{"a33"},{"b13"},{"b23"},{"b33"}});
+
+            conn.createStatement().execute("UPSERT INTO "+tableName+" VALUES 
('b31','a12','a13','a14','a15','a16')");
+            conn.createStatement().execute("UPSERT INTO "+tableName+" VALUES 
('b31','a22','a23','a24','a25','a26')");
+            conn.createStatement().execute("UPSERT INTO "+tableName+" VALUES 
('b31','a32','a33','a34','a35','a36')");
+            conn.createStatement().execute("UPSERT INTO "+tableName+" VALUES 
('b31','b12','b13','b14','b15','b16')");
+            conn.createStatement().execute("UPSERT INTO "+tableName+" VALUES 
('b31','b22','b23','b24','b25', 'b26')");
+            conn.commit();
+
+            sql = "select sub, v1 from (select substr(pk3,0,3) sub, pk2, v1 
from "
+                    + tableName + " t where pk1 = 'b31' order by pk2, pk3 
limit 10) a order by pk2 desc ,sub desc";
+            rs = conn.prepareStatement(sql).executeQuery();
+            assertResultSet(rs, new 
Object[][]{{"b33"},{"b23"},{"b13"},{"a33"},{"a23"},{"a13"}});
+
+            sql = "select sub, v1 from (select substr(pk3,0,3) sub, pk2, v1 
from "
+                    + tableName + " t where pk1 = 'b31' order by pk2 desc, pk3 
desc limit 10) a order by pk2 desc ,sub desc";
+            rs = conn.prepareStatement(sql).executeQuery();
+            assertResultSet(rs, new 
Object[][]{{"b33"},{"b23"},{"b13"},{"a33"},{"a23"},{"a13"}});
+
+            sql = "select sub, v1 from (select substr(pk2,0,3) sub, pk3, v1 
from "
+                    + tableName + " t where pk1 = 'b31' order by pk2, pk3 
limit 10) a order by sub desc ,pk3 desc";
+            rs = conn.prepareStatement(sql).executeQuery();
+            assertResultSet(rs, new 
Object[][]{{"b32"},{"b22"},{"b12"},{"a32"},{"a22"},{"a12"}});
+
+            sql = "select sub, v1 from (select substr(pk2,0,3) sub, pk3, v1 
from "
+                    + tableName + " t where pk1 = 'b31' order by pk2 desc, pk3 
desc limit 10) a order by sub desc, pk3 desc";
+            rs = conn.prepareStatement(sql).executeQuery();
+            assertResultSet(rs, new 
Object[][]{{"b32"},{"b22"},{"b12"},{"a32"},{"a22"},{"a12"}});
+        } finally {
+            if(conn != null) {
+                conn.close();
+            }
+        }
+    }
+
 }
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 9d3f962521..0e32be630f 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
@@ -74,6 +74,7 @@ import org.apache.phoenix.execute.UnnestArrayPlan;
 import org.apache.phoenix.execute.visitor.QueryPlanVisitor;
 import org.apache.phoenix.expression.Expression;
 import org.apache.phoenix.expression.LiteralExpression;
+import org.apache.phoenix.expression.OrderByExpression;
 import org.apache.phoenix.expression.aggregator.Aggregator;
 import org.apache.phoenix.expression.aggregator.CountAggregator;
 import org.apache.phoenix.expression.aggregator.ServerAggregators;
@@ -7105,4 +7106,308 @@ public class QueryCompilerTest extends 
BaseConnectionlessQueryTest {
                     explainPlan);
         }
     }
+
+    @Test
+    public void testPartialOrderForTupleProjectionWithJoinBug7352() throws 
Exception {
+        try (Connection conn = DriverManager.getConnection(getUrl())) {
+            String cpc_pv_dumper = generateUniqueName();
+            String sql = "create table " + cpc_pv_dumper + " ( "
+                    + " aid BIGINT not null,"
+                    + " k BIGINT  not null,"
+                    + " cm BIGINT, "
+                    + " CONSTRAINT TEST_PK PRIMARY KEY (aid, k))";
+            conn.createStatement().execute(sql);
+
+            String group_temp = generateUniqueName();
+            sql = "create table " + group_temp + " ("
+                    + "  aid BIGINT not null,"
+                    + "  gid TINYINT not null,"
+                    + " CONSTRAINT TEST_PK PRIMARY KEY (aid, gid))";
+            conn.createStatement().execute(sql);
+
+            sql = "select a_key, sum(groupCost) from ( "
+                    + " select t1.k as a_key, sum(t1.cm) as groupCost "
+                    + " from " + cpc_pv_dumper + " as t1 join " + group_temp + 
" as t2 on t1.aid = t2.aid group by t1.k, t2.gid" +
+                    ") group by a_key having count(1) >= 2 order by 
sum(groupCost) desc limit 100";
+            QueryPlan plan =  TestUtil.getOptimizeQueryPlanNoIterator(conn, 
sql);
+            assertTrue(plan.getGroupBy().isOrderPreserving());
+
+            sql = "select a_key, sum(groupCost) from ( "
+                    + " select t1.k as a_key, t2.gid as b_gid, sum(t1.cm) as 
groupCost "
+                    + " from " + cpc_pv_dumper + " as t1 join " + group_temp + 
" as t2 on t1.aid = t2.aid group by t1.k, t2.gid" +
+                    ") group by a_key having count(1) >= 2 order by 
sum(groupCost) desc limit 100";
+            plan =  TestUtil.getOptimizeQueryPlanNoIterator(conn, sql);
+            assertTrue(plan.getGroupBy().isOrderPreserving());
+
+            sql = "select b_gid, sum(groupCost) from ( "
+                    + " select t1.k as a_key, t2.gid as b_gid, sum(t1.cm) as 
groupCost "
+                    + " from " + cpc_pv_dumper + " as t1 join " + group_temp + 
" as t2 on t1.aid = t2.aid group by t1.k, t2.gid" +
+                    ") group by b_gid having count(1) >= 2 order by 
sum(groupCost) desc limit 100";
+            plan =  TestUtil.getOptimizeQueryPlanNoIterator(conn, sql);
+            assertFalse(plan.getGroupBy().isOrderPreserving());
+
+            sql = "select b_gid, a_key, groupCost from ( "
+                    + " select t1.k as a_key, t2.gid as b_gid, cast(sum(t1.cm) 
as bigint) as groupCost "
+                    + " from " + cpc_pv_dumper + " as t1 join " + group_temp
+                    + " as t2 on t1.aid = t2.aid group by t1.k, t2.gid, t2.aid 
order by sum(t1.cm), a_key, t2.aid desc limit 20" +
+                    ") order by groupCost";
+            ClientScanPlan clientScanPlan =  
(ClientScanPlan)TestUtil.getOptimizeQueryPlanNoIterator(conn, sql);
+            assertTrue(clientScanPlan.getOrderBy() == 
OrderBy.FWD_ROW_KEY_ORDER_BY);
+            List<OrderBy> outputOrderBys = 
((TupleProjectionPlan)(clientScanPlan.getDelegate())).getOutputOrderBys();
+            assertTrue(outputOrderBys.size() == 1);
+            OrderBy outputOrderBy = outputOrderBys.get(0);
+            assertTrue(outputOrderBy.getOrderByExpressions().size() == 2);
+            
assertTrue(outputOrderBy.getOrderByExpressions().get(0).toString().equals("GROUPCOST"));
+            
assertTrue(outputOrderBy.getOrderByExpressions().get(1).toString().equals("A_KEY"));
+
+            sql = "select b_gid, a_key, groupCost from ( "
+                    + " select t1.k as a_key, t2.gid as b_gid, cast(sum(t1.cm) 
as bigint) as groupCost "
+                    + " from " + cpc_pv_dumper + " as t1 join " + group_temp
+                    + " as t2 on t1.aid = t2.aid group by t1.k, t2.gid order 
by sum(t1.cm) desc, a_key asc limit 20" +
+                    ") order by groupCost desc";
+            clientScanPlan =  
(ClientScanPlan)TestUtil.getOptimizeQueryPlanNoIterator(conn, sql);
+            assertTrue(clientScanPlan.getOrderBy() == 
OrderBy.FWD_ROW_KEY_ORDER_BY);
+            outputOrderBys = 
((TupleProjectionPlan)(clientScanPlan.getDelegate())).getOutputOrderBys();
+            assertTrue(outputOrderBys.size() == 1);
+            outputOrderBy = outputOrderBys.get(0);
+            assertTrue(outputOrderBy.getOrderByExpressions().size() == 2);
+            
assertTrue(outputOrderBy.getOrderByExpressions().get(0).toString().equals("GROUPCOST
 DESC"));
+            
assertTrue(outputOrderBy.getOrderByExpressions().get(1).toString().equals("A_KEY"));
+
+            sql = "select b_gid, groupCost from ( "
+                    + " select t2.gid as b_gid, cast(sum(t1.cm) as bigint) as 
groupCost "
+                    + " from " + cpc_pv_dumper + " as t1 join " + group_temp
+                    + " as t2 on t1.aid = t2.aid group by t1.k, t2.gid order 
by sum(t1.cm), t1.k limit 20" +
+                    ") order by groupCost";
+            clientScanPlan =  
(ClientScanPlan)TestUtil.getOptimizeQueryPlanNoIterator(conn, sql);
+            assertTrue(clientScanPlan.getOrderBy() == 
OrderBy.FWD_ROW_KEY_ORDER_BY);
+            outputOrderBys = 
((TupleProjectionPlan)(clientScanPlan.getDelegate())).getOutputOrderBys();
+            assertTrue(outputOrderBys.size() == 1);
+            outputOrderBy = outputOrderBys.get(0);
+            assertTrue(outputOrderBy.getOrderByExpressions().size() == 1);
+            
assertTrue(outputOrderBy.getOrderByExpressions().get(0).toString().equals("GROUPCOST"));
+        }
+    }
+
+    @Test
+    public void testPartialOrderForTupleProjectionPlanBug7352() throws 
Exception {
+        doTestPartialOrderForTupleProjectionPlanBug7352(false, false);
+        doTestPartialOrderForTupleProjectionPlanBug7352(false, true);
+        doTestPartialOrderForTupleProjectionPlanBug7352(true, false);
+        doTestPartialOrderForTupleProjectionPlanBug7352(true, true);
+    }
+
+    private void doTestPartialOrderForTupleProjectionPlanBug7352(boolean desc, 
boolean salted) throws Exception {
+        try (Connection conn = DriverManager.getConnection(getUrl())) {
+            String tableName = generateUniqueName();
+            String sql = "create table " + tableName + "( "+
+                    " pk1 char(20) not null , " +
+                    " pk2 char(20) not null, " +
+                    " pk3 char(20) not null," +
+                    " v1 varchar, " +
+                    " v2 varchar, " +
+                    " v3 varchar, " +
+                    " CONSTRAINT TEST_PK PRIMARY KEY ( " +
+                    " pk1 " + (desc ? "desc" : "")+", "+
+                    " pk2 " + (desc ? "desc" : "")+", "+
+                    " pk3 " + (desc ? "desc" : "")+
+                    " )) " + (salted ? "SALT_BUCKETS =4" : "");
+            conn.createStatement().execute(sql);
+
+            sql = "select pk3, v1, v2 from (select v1,v2,pk3 from " + tableName
+                    + " t where pk1 = '6' order by t.v2,t.v1 limit 10) a order 
by v2";
+            ClientScanPlan plan =  
(ClientScanPlan)TestUtil.getOptimizeQueryPlan(conn, sql);
+            assertTrue(plan.getOrderBy() == OrderBy.FWD_ROW_KEY_ORDER_BY);
+            List<OrderBy> outputOrderBys = 
((TupleProjectionPlan)(plan.getDelegate())).getOutputOrderBys();
+            assertTrue(outputOrderBys.size() == 1);
+            OrderBy outputOrderBy = outputOrderBys.get(0);
+            assertTrue(outputOrderBy.getOrderByExpressions().size() == 2);
+            
assertTrue(outputOrderBy.getOrderByExpressions().get(0).toString().equals("V2"));
+            
assertTrue(outputOrderBy.getOrderByExpressions().get(1).toString().equals("V1"));
+
+            sql = "select pk3, v1, v2 from (select v1,v2,pk3 from " + tableName
+                    + " t where pk1 = '6' order by t.v2 desc,t.v1 desc limit 
10) a order by v2 desc";
+            plan =  (ClientScanPlan)TestUtil.getOptimizeQueryPlan(conn, sql);
+            assertTrue(plan.getOrderBy() == OrderBy.FWD_ROW_KEY_ORDER_BY);
+            outputOrderBys = 
((TupleProjectionPlan)(plan.getDelegate())).getOutputOrderBys();
+            assertTrue(outputOrderBys.size() == 1);
+            outputOrderBy = outputOrderBys.get(0);
+            assertTrue(outputOrderBy.getOrderByExpressions().size() == 2);
+            
assertTrue(outputOrderBy.getOrderByExpressions().get(0).toString().equals("V2 
DESC"));
+            
assertTrue(outputOrderBy.getOrderByExpressions().get(1).toString().equals("V1 
DESC"));
+
+            sql = "select pk3, v1, v2 from (select v1,v2,pk3 from " + tableName
+                    + " t where pk1 = '6' order by t.v2 desc,t.v1 desc, t.v3 
desc limit 10) a order by v2 desc";
+            plan =  (ClientScanPlan)TestUtil.getOptimizeQueryPlan(conn, sql);
+            assertTrue(plan.getOrderBy() == OrderBy.FWD_ROW_KEY_ORDER_BY);
+            outputOrderBys = 
((TupleProjectionPlan)(plan.getDelegate())).getOutputOrderBys();
+            assertTrue(outputOrderBys.size() == 1);
+            outputOrderBy = outputOrderBys.get(0);
+            assertTrue(outputOrderBy.getOrderByExpressions().size() == 2);
+            
assertTrue(outputOrderBy.getOrderByExpressions().get(0).toString().equals("V2 
DESC"));
+            
assertTrue(outputOrderBy.getOrderByExpressions().get(1).toString().equals("V1 
DESC"));
+
+            sql = "select pk3, v1, v2 from (select v1,v2,pk3 from " + tableName
+                    + " t where pk1 = '6' order by t.v2 desc,t.v1 desc, t.v3 
asc limit 10) a order by v2 desc";
+            plan =  (ClientScanPlan)TestUtil.getOptimizeQueryPlan(conn, sql);
+            assertTrue(plan.getOrderBy() == OrderBy.FWD_ROW_KEY_ORDER_BY);
+            outputOrderBys = 
((TupleProjectionPlan)(plan.getDelegate())).getOutputOrderBys();
+            assertTrue(outputOrderBys.size() == 1);
+            outputOrderBy = outputOrderBys.get(0);
+            assertTrue(outputOrderBy.getOrderByExpressions().size() == 2);
+            
assertTrue(outputOrderBy.getOrderByExpressions().get(0).toString().equals("V2 
DESC"));
+            
assertTrue(outputOrderBy.getOrderByExpressions().get(1).toString().equals("V1 
DESC"));
+
+            sql = "select v2,cnt from (select count(pk3) cnt,v1,v2 from " + 
tableName
+                    + " t where pk1 = '6' group by t.v1,t.v2,t.v3 limit 10) a 
order by v1";
+            plan = (ClientScanPlan)TestUtil.getOptimizeQueryPlan(conn, sql);
+            assertTrue(plan.getOrderBy() == OrderBy.FWD_ROW_KEY_ORDER_BY);
+            outputOrderBys = 
((TupleProjectionPlan)(plan.getDelegate())).getOutputOrderBys();
+            assertTrue(outputOrderBys.size() == 1);
+            outputOrderBy = outputOrderBys.get(0);
+            assertTrue(outputOrderBy.getOrderByExpressions().size() == 2);
+            
assertTrue(outputOrderBy.getOrderByExpressions().get(0).toString().equals("V1"));
+            
assertTrue(outputOrderBy.getOrderByExpressions().get(1).toString().equals("V2"));
+
+            sql = "select sub, pk2Cnt from (select substr(v2,0,2) sub,cast 
(count(pk3) as bigint) cnt, count(pk2) pk2Cnt from "
+                    + tableName
+                    + " t where pk1 = '6' group by t.v1 ,t.v2, t.v3 "
+                    + " order by count(pk3) desc,t.v2 desc,t.v3 desc limit 10) 
a order by cnt desc ,sub desc";
+            plan =   (ClientScanPlan)TestUtil.getOptimizeQueryPlan(conn, sql);
+            assertTrue(plan.getOrderBy() == OrderBy.FWD_ROW_KEY_ORDER_BY);
+            outputOrderBys = 
((TupleProjectionPlan)(plan.getDelegate())).getOutputOrderBys();
+            assertTrue(outputOrderBys.size() == 1);
+            outputOrderBy = outputOrderBys.get(0);
+            assertTrue(outputOrderBy.getOrderByExpressions().size() == 2);
+            
assertTrue(outputOrderBy.getOrderByExpressions().get(0).toString().equals("CNT 
DESC"));
+            
assertTrue(outputOrderBy.getOrderByExpressions().get(1).toString().equals("SUB 
DESC"));
+
+            sql = "select sub, pk2Cnt from (select substr(v2,0,2) sub,cast 
(count(pk3) as bigint) cnt, count(pk2) pk2Cnt from "
+                    + tableName
+                    + " t where pk1 = '6' group by t.v1 ,t.v2, t.v3 "
+                    + " order by count(pk3) desc,t.v2 desc,t.v3 asc limit 10) 
a order by cnt desc ,sub desc";
+            plan =   (ClientScanPlan)TestUtil.getOptimizeQueryPlan(conn, sql);
+            assertTrue(plan.getOrderBy() == OrderBy.FWD_ROW_KEY_ORDER_BY);
+            outputOrderBys = 
((TupleProjectionPlan)(plan.getDelegate())).getOutputOrderBys();
+            assertTrue(outputOrderBys.size() == 1);
+            outputOrderBy = outputOrderBys.get(0);
+            assertTrue(outputOrderBy.getOrderByExpressions().size() == 2);
+            
assertTrue(outputOrderBy.getOrderByExpressions().get(0).toString().equals("CNT 
DESC"));
+            
assertTrue(outputOrderBy.getOrderByExpressions().get(1).toString().equals("SUB 
DESC"));
+
+            sql = "select sub, pk2Cnt from (select substr(v2,0,2) sub,cast 
(count(pk3) as bigint) cnt, count(pk2) pk2Cnt from "
+                    + tableName
+                    + " t where pk1 = '6' group by t.v1 ,t.v2, t.v3 "
+                    + " order by t.v2 desc, count(pk3) desc, t.v3 desc limit 
10) a order by sub desc, cnt desc";
+            plan =   (ClientScanPlan)TestUtil.getOptimizeQueryPlan(conn, sql);
+            assertTrue(plan.getOrderBy().getOrderByExpressions().size() == 2);
+            
assertTrue(plan.getOrderBy().getOrderByExpressions().get(0).toString().equals("SUB
 DESC"));
+            
assertTrue(plan.getOrderBy().getOrderByExpressions().get(1).toString().equals("CNT
 DESC"));
+            outputOrderBys = 
((TupleProjectionPlan)(plan.getDelegate())).getOutputOrderBys();
+            assertTrue(outputOrderBys.size() == 1);
+            outputOrderBy = outputOrderBys.get(0);
+            assertTrue(outputOrderBy.getOrderByExpressions().size() == 1);
+            
assertTrue(outputOrderBy.getOrderByExpressions().get(0).toString().equals("SUB 
DESC"));
+
+            sql = "select sub, pk2Cnt from (select substr(v2,0,2) sub,cast 
(count(pk3) as bigint) cnt, count(pk2) pk2Cnt from "
+                    + tableName
+                    + " t where pk1 = '6' group by v1 ,v2, v3 "
+                    + " order by t.v2 desc, count(pk3) desc, t.v3 asc limit 
10) a order by sub desc, cnt desc";
+            plan =   (ClientScanPlan)TestUtil.getOptimizeQueryPlan(conn, sql);
+            assertTrue(plan.getOrderBy().getOrderByExpressions().size() == 2);
+            
assertTrue(plan.getOrderBy().getOrderByExpressions().get(0).toString().equals("SUB
 DESC"));
+            
assertTrue(plan.getOrderBy().getOrderByExpressions().get(1).toString().equals("CNT
 DESC"));
+            outputOrderBys = 
((TupleProjectionPlan)(plan.getDelegate())).getOutputOrderBys();
+            assertTrue(outputOrderBys.size() == 1);
+            outputOrderBy = outputOrderBys.get(0);
+            assertTrue(outputOrderBy.getOrderByExpressions().size() == 1);
+            
assertTrue(outputOrderBy.getOrderByExpressions().get(0).toString().equals("SUB 
DESC"));
+
+            sql = "select v1, pk3, v2 from (select v1,v2,pk3 from " + tableName
+                    + " t where pk1 = '6' order by t.v2, t.v1, t.v3 limit 10) 
a order by v1";
+            plan =  (ClientScanPlan)TestUtil.getOptimizeQueryPlan(conn, sql);
+            assertTrue(plan.getOrderBy().getOrderByExpressions().size() == 1);
+            
assertTrue(plan.getOrderBy().getOrderByExpressions().get(0).toString().equals("V1"));
+
+            sql = "select pk3, pk1, pk2 from (select pk1,pk2,pk3 from " + 
tableName
+                    + " t where pk1 = '6' order by t.v2, t.v1, t.v3 limit 10) 
a order by pk3";
+            plan =  (ClientScanPlan)TestUtil.getOptimizeQueryPlan(conn, sql);
+            assertTrue(plan.getOrderBy().getOrderByExpressions().size() == 1);
+            
assertTrue(plan.getOrderBy().getOrderByExpressions().get(0).toString().equals("PK3"));
+            outputOrderBys = 
((TupleProjectionPlan)(plan.getDelegate())).getOutputOrderBys();
+            assertTrue(outputOrderBys.size() == 0);
+
+            sql = "select sub, v1 from (select substr(pk3,0,2) sub, pk2, v1 
from "
+                    + tableName + " t where pk1 = '6' order by pk2, pk3 limit 
10) a order by pk2 desc ,sub desc";
+            plan =   (ClientScanPlan)TestUtil.getOptimizeQueryPlan(conn, sql);
+            //Here because for subquery, there is no OrderBy 
REV_ROW_KEY_ORDER_BY
+            assertTrue(plan.getOrderBy().getOrderByExpressions().size() > 0);
+            outputOrderBys = 
((TupleProjectionPlan)(plan.getDelegate())).getOutputOrderBys();
+            assertTrue(outputOrderBys.size() == 1);
+            outputOrderBy = outputOrderBys.get(0);
+            assertTrue(outputOrderBy.getOrderByExpressions().size() == 2);
+            if (desc) {
+                
assertOrderByForDescExpression(outputOrderBy.getOrderByExpressions().get(0), 
"PK2", true, true);
+                
assertOrderByForDescExpression(outputOrderBy.getOrderByExpressions().get(1), 
"SUB", true, true);
+            } else {
+                
assertTrue(outputOrderBy.getOrderByExpressions().get(0).toString().equals("PK2"));
+                
assertTrue(outputOrderBy.getOrderByExpressions().get(1).toString().equals("SUB"));
+            }
+
+            sql = "select sub, v1 from (select substr(pk3,0,2) sub, pk2, v1 
from "
+                    + tableName + " t where pk1 = '6' order by pk2 desc, pk3 
desc limit 10) a order by pk2 desc ,sub desc";
+            plan =   (ClientScanPlan)TestUtil.getOptimizeQueryPlan(conn, sql);
+            assertTrue(plan.getOrderBy() == OrderBy.FWD_ROW_KEY_ORDER_BY);
+            outputOrderBys = 
((TupleProjectionPlan)(plan.getDelegate())).getOutputOrderBys();
+            assertTrue(outputOrderBys.size() == 1);
+            outputOrderBy = outputOrderBys.get(0);
+            assertTrue(outputOrderBy.getOrderByExpressions().size() == 2);
+            if (desc) {
+                
assertOrderByForDescExpression(outputOrderBy.getOrderByExpressions().get(0), 
"PK2", false, false);
+                
assertOrderByForDescExpression(outputOrderBy.getOrderByExpressions().get(1), 
"SUB", false, false);
+            } else {
+                
assertTrue(outputOrderBy.getOrderByExpressions().get(0).toString().equals("PK2 
DESC NULLS LAST"));
+                
assertTrue(outputOrderBy.getOrderByExpressions().get(1).toString().equals("SUB 
DESC NULLS LAST"));
+            }
+
+            sql = "select sub, v1 from (select substr(pk2,0,2) sub, pk3, v1 
from "
+                    + tableName + " t where pk1 = '6' order by pk2, pk3 limit 
10) a order by sub desc ,pk3 desc";
+            plan =   (ClientScanPlan)TestUtil.getOptimizeQueryPlan(conn, sql);
+            //Here because for subquery, there is no OrderBy 
REV_ROW_KEY_ORDER_BY
+            assertTrue(plan.getOrderBy().getOrderByExpressions().size() > 0);
+            outputOrderBys = 
((TupleProjectionPlan)(plan.getDelegate())).getOutputOrderBys();
+            assertTrue(outputOrderBys.size() == 1);
+            outputOrderBy = outputOrderBys.get(0);
+            assertTrue(outputOrderBy.getOrderByExpressions().size() == 1);
+            if (desc) {
+                
assertOrderByForDescExpression(outputOrderBy.getOrderByExpressions().get(0), 
"SUB", true, true);
+            } else {
+                
assertTrue(outputOrderBy.getOrderByExpressions().get(0).toString().equals("SUB"));
+            }
+
+            sql = "select sub, v1 from (select substr(pk2,0,2) sub, pk3, v1 
from "
+                    + tableName + " t where pk1 = '6' order by pk2 desc, pk3 
desc limit 10) a order by sub desc,pk3 desc";
+            plan =   (ClientScanPlan)TestUtil.getOptimizeQueryPlan(conn, sql);
+            assertTrue(plan.getOrderBy().getOrderByExpressions().size() > 0);
+            outputOrderBys = 
((TupleProjectionPlan)(plan.getDelegate())).getOutputOrderBys();
+            assertTrue(outputOrderBys.size() == 1);
+            outputOrderBy = outputOrderBys.get(0);
+            assertTrue(outputOrderBy.getOrderByExpressions().size() == 1);
+            if (desc) {
+                
assertOrderByForDescExpression(outputOrderBy.getOrderByExpressions().get(0), 
"SUB", false, false);
+            } else {
+                
assertTrue(outputOrderBy.getOrderByExpressions().get(0).toString().equals("SUB 
DESC NULLS LAST"));
+            }
+        }
+    }
+
+    private static void assertOrderByForDescExpression(
+            OrderByExpression orderByExpression,
+            String strExpression,
+            boolean isNullsLast,
+            boolean isAscending) {
+        assertEquals(strExpression, 
orderByExpression.getExpression().toString());
+        assertEquals(isNullsLast, orderByExpression.isNullsLast());
+        assertEquals(isAscending, orderByExpression.isAscending());
+    }
 }

Reply via email to