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());
+ }
}