From 25066184a7fbfbebceedb287eaa1c45914504207 Mon Sep 17 00:00:00 2001 From: chenglei Date: Sat, 15 Dec 2018 15:45:17 +0800 Subject: [PATCH] PHOENIX 4820 --- .../apache/phoenix/end2end/AggregateIT.java | 104 ++++++ .../phoenix/compile/GroupByCompiler.java | 8 +- .../phoenix/compile/OrderByCompiler.java | 18 +- .../compile/OrderPreservingTracker.java | 49 +-- .../apache/phoenix/compile/QueryCompiler.java | 12 +- .../compile/SequenceValueExpression.java | 7 +- .../phoenix/expression/BaseExpression.java | 5 + .../expression/DelegateExpression.java | 5 + .../apache/phoenix/expression/Expression.java | 6 + .../function/AggregateFunction.java | 5 + .../expression/function/RandomFunction.java | 5 + .../apache/phoenix/util/ExpressionUtil.java | 148 ++++++++ .../phoenix/compile/QueryCompilerTest.java | 324 ++++++++++++++++++ 13 files changed, 666 insertions(+), 30 deletions(-) diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/AggregateIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/AggregateIT.java index 8916d4d8c81..d52025e7fa8 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/AggregateIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/AggregateIT.java @@ -227,5 +227,109 @@ protected void testCountNullInNonEmptyKeyValueCF(int columnEncodedBytes) throws assertEquals(4, rs.getLong(1)); } } + + @Test + public void testOrderByOptimizeForClientAggregatePlanBug4820() throws Exception { + doTestOrderByOptimizeForClientAggregatePlanBug4820(false,false); + doTestOrderByOptimizeForClientAggregatePlanBug4820(false,true); + doTestOrderByOptimizeForClientAggregatePlanBug4820(true,false); + doTestOrderByOptimizeForClientAggregatePlanBug4820(true,true); + } + + private void doTestOrderByOptimizeForClientAggregatePlanBug4820(boolean desc ,boolean salted) throws Exception { + Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); + Connection conn = null; + try { + conn = DriverManager.getConnection(getUrl(), props); + String tableName = generateUniqueName(); + String sql = "create table " + tableName + "( "+ + " pk1 varchar not null , " + + " pk2 varchar not null, " + + " pk3 varchar not null," + + " v1 varchar, " + + " v2 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')"); + conn.createStatement().execute("UPSERT INTO "+tableName+" VALUES ('a21','a22','a23','a24','a25')"); + conn.createStatement().execute("UPSERT INTO "+tableName+" VALUES ('a31','a32','a33','a34','a35')"); + conn.createStatement().execute("UPSERT INTO "+tableName+" VALUES ('b11','b12','b13','b14','b15')"); + conn.createStatement().execute("UPSERT INTO "+tableName+" VALUES ('b21','b22','b23','b24','b25')"); + conn.createStatement().execute("UPSERT INTO "+tableName+" VALUES ('b31','b32','b33','b34','b35')"); + conn.commit(); + + sql = "select a.ak3 "+ + "from (select pk1 ak1,pk2 ak2,pk3 ak3, substr(v1,1,1) av1,substr(v2,1,1) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "group by a.ak3,a.av1 order by a.ak3 desc,a.av1"; + ResultSet rs = conn.prepareStatement(sql).executeQuery(); + assertResultSet(rs, new Object[][]{{"b33"},{"b23"},{"b13"},{"a33"},{"a23"},{"a13"}}); + + sql = "select a.ak3 "+ + "from (select pk1 ak1,pk2 ak2,pk3 ak3, substr(v1,1,1) av1,substr(v2,1,1) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "group by a.ak3,a.av1 order by a.ak3,a.av1"; + rs = conn.prepareStatement(sql).executeQuery(); + assertResultSet(rs, new Object[][]{{"a13"},{"a23"},{"a33"},{"b13"},{"b23"},{"b33"}}); + + sql = "select a.ak3 "+ + "from (select pk1 ak1,pk2 ak2,pk3 ak3,substr(v1,1,1) av1,substr(v2,1,1) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where a.av1 = 'a' group by a.av1,a.ak3 order by a.ak3 desc"; + rs = conn.prepareStatement(sql).executeQuery(); + assertResultSet(rs, new Object[][]{{"a33"},{"a23"},{"a13"}}); + + sql = "select a.ak3 "+ + "from (select pk1 ak1,pk2 ak2,pk3 ak3,substr(v1,1,1) av1,substr(v2,1,1) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where a.av1 = 'a' group by a.av1,a.ak3 order by a.ak3"; + rs = conn.prepareStatement(sql).executeQuery(); + assertResultSet(rs, new Object[][]{{"a13"},{"a23"},{"a33"}}); + + sql = "select a.ak3 "+ + "from (select pk1 ak1,pk2 ak2,pk3 ak3,substr(v1,1,1) av1,substr(v2,1,1) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where a.av1 = 'b' and a.av2= 'b' group by CASE WHEN a.av1 > a.av2 THEN a.av1 ELSE a.av2 END,a.ak3,a.ak2 order by a.ak3 desc,a.ak2 desc"; + rs = conn.prepareStatement(sql).executeQuery(); + assertResultSet(rs, new Object[][]{{"b33"},{"b23"},{"b13"}}); + + sql = "select a.ak3 "+ + "from (select pk1 ak1,pk2 ak2,pk3 ak3,substr(v1,1,1) av1,substr(v2,1,1) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where a.av1 = 'b' and a.av2= 'b' group by CASE WHEN a.av1 > a.av2 THEN a.av1 ELSE a.av2 END,a.ak3,a.ak2 order by a.ak3,a.ak2 desc"; + rs = conn.prepareStatement(sql).executeQuery(); + assertResultSet(rs, new Object[][]{{"b13"},{"b23"},{"b33"}}); + + tableName = generateUniqueName(); + sql = "create table " + tableName + "( "+ + " pk1 double not null , " + + " pk2 double not null, " + + " pk3 double not null," + + " v1 varchar, " + + " CONSTRAINT TEST_PK PRIMARY KEY ( "+ + "pk1 "+(desc ? "desc" : "")+", "+ + "pk2 "+(desc ? "desc" : "")+", "+ + "pk3 "+(desc ? "desc" : "")+ + " )) "+(salted ? "SALT_BUCKETS =4" : "split on(2.3)"); + conn.createStatement().execute(sql); + conn.createStatement().execute("UPSERT INTO "+tableName+" VALUES (2.1,2.11,2.12,'e')"); + conn.createStatement().execute("UPSERT INTO "+tableName+" VALUES (2.2,2.21,2.23,'d')"); + conn.createStatement().execute("UPSERT INTO "+tableName+" VALUES (2.3,2.31,2.32,'c')"); + conn.createStatement().execute("UPSERT INTO "+tableName+" VALUES (2.4,2.41,2.42,'b')"); + conn.createStatement().execute("UPSERT INTO "+tableName+" VALUES (2.5,2.51,2.52,'a')"); + conn.commit(); + + sql = "select a.av1 "+ + "from (select pk1 ak1,pk2 ak2,pk3 ak3, substr(v1,1,1) av1 from "+tableName+" order by pk1,pk2 limit 10) a "+ + "where cast(a.ak1 as integer)=2 group by a.ak1,a.av1 order by a.av1"; + rs = conn.prepareStatement(sql).executeQuery(); + assertResultSet(rs, new Object[][]{{"a"},{"b"},{"c"},{"d"},{"e"}}); + + } finally { + if(conn != null) { + conn.close(); + } + } + } + } diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/GroupByCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/GroupByCompiler.java index 4777c296ceb..2bdea9aed58 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/compile/GroupByCompiler.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/GroupByCompiler.java @@ -143,7 +143,13 @@ public GroupBy compile(StatementContext context, TupleProjector tupleProjector) boolean isOrderPreserving = this.isOrderPreserving; int orderPreservingColumnCount = 0; if (isOrderPreserving) { - OrderPreservingTracker tracker = new OrderPreservingTracker(context, GroupBy.EMPTY_GROUP_BY, Ordering.UNORDERED, expressions.size(), tupleProjector); + OrderPreservingTracker tracker = new OrderPreservingTracker( + context, + GroupBy.EMPTY_GROUP_BY, + Ordering.UNORDERED, + expressions.size(), + tupleProjector, + null); for (int i = 0; i < expressions.size(); i++) { Expression expression = expressions.get(i); tracker.track(expression); diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/OrderByCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/OrderByCompiler.java index b83c7a86284..3c3f429198b 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/compile/OrderByCompiler.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/OrderByCompiler.java @@ -88,7 +88,8 @@ public static OrderBy compile(StatementContext context, Integer offset, RowProjector rowProjector, TupleProjector tupleProjector, - boolean isInRowKeyOrder) throws SQLException { + boolean isInRowKeyOrder, + Expression whereExpression) throws SQLException { List orderByNodes = statement.getOrderBy(); if (orderByNodes.isEmpty()) { return OrderBy.EMPTY_ORDER_BY; @@ -105,9 +106,22 @@ protected void addColumn(PColumn column) {} } else { compiler = new ExpressionCompiler(context, groupBy); } + + if(groupBy != GroupBy.EMPTY_GROUP_BY) { + //if there is groupBy,the groupBy.expressions are viewed as new rowKey columns,so + //tupleProjector and isInRowKeyOrder is cleared + tupleProjector = null; + isInRowKeyOrder = true; + } // accumulate columns in ORDER BY OrderPreservingTracker tracker = - new OrderPreservingTracker(context, groupBy, Ordering.ORDERED, orderByNodes.size(), tupleProjector); + new OrderPreservingTracker( + context, + groupBy, + Ordering.ORDERED, + orderByNodes.size(), + tupleProjector, + whereExpression); LinkedHashSet orderByExpressions = Sets.newLinkedHashSetWithExpectedSize(orderByNodes.size()); for (OrderByNode node : orderByNodes) { ParseNode parseNode = node.getNode(); diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/OrderPreservingTracker.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/OrderPreservingTracker.java index d1175f6e3f7..43841a74b46 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/compile/OrderPreservingTracker.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/OrderPreservingTracker.java @@ -20,6 +20,7 @@ import org.apache.phoenix.expression.CoerceExpression; import org.apache.phoenix.expression.Determinism; import org.apache.phoenix.expression.Expression; +import org.apache.phoenix.expression.KeyValueColumnExpression; import org.apache.phoenix.expression.LiteralExpression; import org.apache.phoenix.expression.ProjectedColumnExpression; import org.apache.phoenix.expression.RowKeyColumnExpression; @@ -30,6 +31,7 @@ import org.apache.phoenix.expression.visitor.StatelessTraverseNoExpressionVisitor; import org.apache.phoenix.schema.PTable; import org.apache.phoenix.schema.SortOrder; +import org.apache.phoenix.util.ExpressionUtil; import com.google.common.collect.Iterators; import com.google.common.collect.Lists; @@ -76,16 +78,22 @@ public Info(Info info, int slotSpan, OrderPreserving orderPreserving) { private final Ordering ordering; private final int pkPositionOffset; private final List orderPreservingInfos; - private final TupleProjector projector; private boolean isOrderPreserving = true; private Boolean isReverse = null; private int orderPreservingColumnCount = 0; + private Expression whereExpression; public OrderPreservingTracker(StatementContext context, GroupBy groupBy, Ordering ordering, int nNodes) { - this(context, groupBy, ordering, nNodes, null); + this(context, groupBy, ordering, nNodes, null, null); } - public OrderPreservingTracker(StatementContext context, GroupBy groupBy, Ordering ordering, int nNodes, TupleProjector projector) { + public OrderPreservingTracker( + StatementContext context, + GroupBy groupBy, + Ordering ordering, + int nNodes, + TupleProjector projector, + Expression whereExpression) { this.context = context; if (groupBy.isEmpty()) { PTable table = context.getResolver().getTables().get(0).getTable(); @@ -103,7 +111,7 @@ public OrderPreservingTracker(StatementContext context, GroupBy groupBy, Orderin this.visitor = new TrackOrderPreservingExpressionVisitor(projector); this.orderPreservingInfos = Lists.newArrayListWithExpectedSize(nNodes); this.ordering = ordering; - this.projector = projector; + this.whereExpression = whereExpression; } public void track(Expression node) { @@ -213,15 +221,12 @@ private boolean hasEqualityConstraints(int startPos, int endPos) { // be held constant based on the WHERE clause. if (!groupBy.isEmpty()) { for (int pos = startPos; pos < endPos; pos++) { - IsConstantVisitor visitor = new IsConstantVisitor(this.projector, ranges); + IsConstantVisitor visitor = new IsConstantVisitor(ranges, whereExpression); List groupByExpressions = groupBy.getExpressions(); if (pos >= groupByExpressions.size()) { // sanity check - shouldn't be necessary return false; } Expression groupByExpression = groupByExpressions.get(pos); - if ( groupByExpression.getDeterminism().ordinal() > Determinism.PER_STATEMENT.ordinal() ) { - return false; - } Boolean isConstant = groupByExpression.accept(visitor); if (!Boolean.TRUE.equals(isConstant)) { return false; @@ -248,17 +253,17 @@ public boolean isReverse() { * */ private static class IsConstantVisitor extends StatelessTraverseAllExpressionVisitor { - private final TupleProjector projector; private final ScanRanges scanRanges; + private final Expression whereExpression; - public IsConstantVisitor(TupleProjector projector, ScanRanges scanRanges) { - this.projector = projector; - this.scanRanges = scanRanges; - } + public IsConstantVisitor(ScanRanges scanRanges, Expression whereExpression) { + this.scanRanges = scanRanges; + this.whereExpression = whereExpression; + } @Override public Boolean defaultReturn(Expression node, List returnValues) { - if (node.getDeterminism().ordinal() > Determinism.PER_STATEMENT.ordinal() || + if (!node.isConstantIfChildrenAllConstant() || returnValues.size() < node.getChildren().size()) { return Boolean.FALSE; } @@ -281,16 +286,12 @@ public Boolean visit(LiteralExpression node) { } @Override - public Boolean visit(ProjectedColumnExpression node) { - if (projector == null) { - return super.visit(node); - } - Expression expression = projector.getExpressions()[node.getPosition()]; - // Only look one level down the projection. - if (expression instanceof ProjectedColumnExpression) { - return super.visit(node); - } - return expression.accept(this); + public Boolean visit(KeyValueColumnExpression keyValueColumnExpression) { + return ExpressionUtil.isColumnExpressionConstant(keyValueColumnExpression, whereExpression); + } + @Override + public Boolean visit(ProjectedColumnExpression projectedColumnExpression) { + return ExpressionUtil.isColumnExpressionConstant(projectedColumnExpression, whereExpression); } } /** diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/QueryCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/QueryCompiler.java index 603da0b5edd..6e3615803a4 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/compile/QueryCompiler.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/QueryCompiler.java @@ -562,8 +562,16 @@ protected QueryPlan compileSingleFlatQuery(StatementContext context, SelectState groupBy = groupBy.compile(context, innerPlanTupleProjector); context.setResolver(resolver); // recover resolver RowProjector projector = ProjectionCompiler.compile(context, select, groupBy, asSubquery ? Collections.emptyList() : targetColumns, where); - OrderBy orderBy = OrderByCompiler.compile(context, select, groupBy, limit, offset, projector, - groupBy == GroupBy.EMPTY_GROUP_BY ? innerPlanTupleProjector : null, isInRowKeyOrder); + OrderBy orderBy = OrderByCompiler.compile( + context, + select, + groupBy, + limit, + offset, + projector, + innerPlanTupleProjector, + isInRowKeyOrder, + where); context.getAggregationManager().compile(context, groupBy); // Final step is to build the query plan if (!asSubquery) { diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/SequenceValueExpression.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/SequenceValueExpression.java index 71e2d026d19..e0c5700cd4e 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/compile/SequenceValueExpression.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/SequenceValueExpression.java @@ -75,7 +75,12 @@ public boolean isNullable() { public Determinism getDeterminism() { return Determinism.PER_ROW; } - + + @Override + public boolean isConstantIfChildrenAllConstant() { + return false; + } + @Override public boolean isStateless() { return true; diff --git a/phoenix-core/src/main/java/org/apache/phoenix/expression/BaseExpression.java b/phoenix-core/src/main/java/org/apache/phoenix/expression/BaseExpression.java index efdceacc67f..b509b86881c 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/expression/BaseExpression.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/expression/BaseExpression.java @@ -244,6 +244,11 @@ protected final List acceptChildren(ExpressionVisitor visitor, Iterato public Determinism getDeterminism() { return Determinism.ALWAYS; } + + @Override + public boolean isConstantIfChildrenAllConstant() { + return true; + } @Override public boolean isStateless() { diff --git a/phoenix-core/src/main/java/org/apache/phoenix/expression/DelegateExpression.java b/phoenix-core/src/main/java/org/apache/phoenix/expression/DelegateExpression.java index 3ca93ddc77f..bd76f465560 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/expression/DelegateExpression.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/expression/DelegateExpression.java @@ -105,4 +105,9 @@ public boolean requiresFinalEvaluation() { return delegate.requiresFinalEvaluation(); } + @Override + public boolean isConstantIfChildrenAllConstant() { + return delegate.isConstantIfChildrenAllConstant(); + } + } diff --git a/phoenix-core/src/main/java/org/apache/phoenix/expression/Expression.java b/phoenix-core/src/main/java/org/apache/phoenix/expression/Expression.java index aeea0c8ce18..9c9ed0641d1 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/expression/Expression.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/expression/Expression.java @@ -88,4 +88,10 @@ public interface Expression extends PDatum, Writable { * @return */ boolean requiresFinalEvaluation(); + + /** + * Determines if expression is constant if all children of it are constants. + * @return + */ + boolean isConstantIfChildrenAllConstant(); } diff --git a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/AggregateFunction.java b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/AggregateFunction.java index 32cae195233..f721eb620c9 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/AggregateFunction.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/AggregateFunction.java @@ -50,4 +50,9 @@ public boolean isStateless() { public Determinism getDeterminism() { return Determinism.PER_ROW; } + + @Override + public boolean isConstantIfChildrenAllConstant() { + return false; + } } diff --git a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/RandomFunction.java b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/RandomFunction.java index 01a4eed8e81..9d765334d19 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/RandomFunction.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/RandomFunction.java @@ -117,6 +117,11 @@ public Determinism getDeterminism() { return hasSeed ? Determinism.PER_ROW : Determinism.PER_INVOCATION; } + @Override + public boolean isConstantIfChildrenAllConstant() { + return false; + } + @Override public boolean isStateless() { return true; diff --git a/phoenix-core/src/main/java/org/apache/phoenix/util/ExpressionUtil.java b/phoenix-core/src/main/java/org/apache/phoenix/util/ExpressionUtil.java index 881b0e1a413..9e315d5167b 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/util/ExpressionUtil.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/util/ExpressionUtil.java @@ -10,12 +10,20 @@ package org.apache.phoenix.util; import java.sql.SQLException; +import java.util.Iterator; import java.util.List; +import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp; import org.apache.hadoop.hbase.io.ImmutableBytesWritable; +import org.apache.phoenix.expression.AndExpression; +import org.apache.phoenix.expression.ColumnExpression; +import org.apache.phoenix.expression.ComparisonExpression; import org.apache.phoenix.expression.Determinism; import org.apache.phoenix.expression.Expression; +import org.apache.phoenix.expression.IsNullExpression; import org.apache.phoenix.expression.LiteralExpression; +import org.apache.phoenix.expression.visitor.StatelessTraverseAllExpressionVisitor; +import org.apache.phoenix.expression.visitor.StatelessTraverseNoExpressionVisitor; import org.apache.phoenix.schema.ColumnRef; import org.apache.phoenix.schema.PColumn; import org.apache.phoenix.schema.TableRef; @@ -68,4 +76,144 @@ public static boolean isPkPositionChanging(TableRef tableRef, List p return false; } + /** + * check the whereExpression to see if the columnExpression is constant. + * eg. for "where a =3 and b > 9", a is constant,but b is not. + * @param columnExpression + * @param whereExpression + * @return + */ + public static boolean isColumnExpressionConstant(ColumnExpression columnExpression, Expression whereExpression) { + if(whereExpression == null) { + return false; + } + IsColumnConstantExpressionVisitor isColumnConstantExpressionVisitor = + new IsColumnConstantExpressionVisitor(columnExpression); + whereExpression.accept(isColumnConstantExpressionVisitor); + return isColumnConstantExpressionVisitor.isConstant(); + } + + private static class IsColumnConstantExpressionVisitor extends StatelessTraverseNoExpressionVisitor { + private final Expression columnExpression ; + private Expression firstRhsConstantExpression = null; + private int rhsConstantCount = 0; + private boolean isNullExpressionVisited = false; + + public IsColumnConstantExpressionVisitor(Expression columnExpression) { + this.columnExpression = columnExpression; + } + /** + * only conside and,for "where a = 3 or b = 9", neither a or b is constant. + */ + @Override + public Iterator visitEnter(AndExpression andExpression) { + if(rhsConstantCount > 1) { + return null; + } + return andExpression.getChildren().iterator(); + } + /** + *
+         * We just conside {@link ComparisonExpression} because:
+         * 1.for {@link InListExpression} as "a in ('2')", the {@link InListExpression} is rewritten to
+         *  {@link ComparisonExpression} in {@link InListExpression#create}
+         * 2.for {@link RowValueConstructorExpression} as "(a,b)=(1,2)",{@link RowValueConstructorExpression}
+         *   is rewritten to {@link ComparisonExpression} in {@link ComparisonExpression#create}
+         * 3.not conside {@link CoerceExpression}, because for "where cast(a as integer)=2", when a is double,
+         *   a is not constant.
+         * 
+ */ + @Override + public Iterator visitEnter(ComparisonExpression comparisonExpression) { + if(rhsConstantCount > 1) { + return null; + } + if(comparisonExpression.getFilterOp() != CompareOp.EQUAL) { + return null; + } + Expression lhsExpresssion = comparisonExpression.getChildren().get(0); + if(!this.columnExpression.equals(lhsExpresssion)) { + return null; + } + Expression rhsExpression = comparisonExpression.getChildren().get(1); + if(rhsExpression == null) { + return null; + } + Boolean isConstant = rhsExpression.accept(new IsExpressionConstantExpressionVisitor()); + if(isConstant != null && isConstant.booleanValue()) { + checkConstantValue(rhsExpression); + } + return null; + } + + public boolean isConstant() { + return this.rhsConstantCount == 1; + } + + @Override + public Iterator visitEnter(IsNullExpression isNullExpression) { + if(rhsConstantCount > 1) { + return null; + } + if(isNullExpression.isNegate()) { + return null; + } + Expression lhsExpresssion = isNullExpression.getChildren().get(0); + if(!this.columnExpression.equals(lhsExpresssion)) { + return null; + } + this.checkConstantValue(null); + return null; + } + + private void checkConstantValue(Expression rhsExpression) { + if(!this.isNullExpressionVisited && this.firstRhsConstantExpression == null) { + this.firstRhsConstantExpression = rhsExpression; + rhsConstantCount++; + if(rhsExpression == null) { + this.isNullExpressionVisited = true; + } + return; + } + + if(!isExpressionEquals(this.isNullExpressionVisited ? null : this.firstRhsConstantExpression, rhsExpression)) { + rhsConstantCount++; + return; + } + } + + private static boolean isExpressionEquals(Expression oldExpression,Expression newExpression) { + if(oldExpression == null) { + if(newExpression == null) { + return true; + } + return ExpressionUtil.isNull(newExpression, new ImmutableBytesWritable()); + } + if(newExpression == null) { + return ExpressionUtil.isNull(oldExpression, new ImmutableBytesWritable()); + } + return oldExpression.equals(newExpression); + } + } + + private static class IsExpressionConstantExpressionVisitor extends StatelessTraverseAllExpressionVisitor { + @Override + public Boolean defaultReturn(Expression expression, List childResultValues) { + if (!expression.isConstantIfChildrenAllConstant() || + childResultValues.size() < expression.getChildren().size()) { + return Boolean.FALSE; + } + for (Boolean childResultValue : childResultValues) { + if (!childResultValue) { + return Boolean.FALSE; + } + } + return Boolean.TRUE; + } + @Override + public Boolean visit(LiteralExpression literalExpression) { + return Boolean.TRUE; + } + } + } 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 154dd7a6d5d..68954b87886 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 @@ -2951,6 +2951,129 @@ public void testOrderPreservingGroupBy() throws Exception { } } } + + @Test + public void testOrderPreservingGroupByForNotPkColumns() throws Exception { + try (Connection conn= DriverManager.getConnection(getUrl())) { + conn.createStatement().execute("CREATE TABLE test (\n" + + " pk1 varchar, \n" + + " pk2 varchar, \n" + + " pk3 varchar, \n" + + " pk4 varchar, \n" + + " v1 varchar, \n" + + " v2 varchar,\n" + + " CONSTRAINT pk PRIMARY KEY (\n" + + " pk1,\n" + + " pk2,\n" + + " pk3,\n" + + " pk4\n" + + " )\n" + + " )"); + String[] queries = new String[] { + "SELECT pk3 FROM test WHERE v2 = 'a' GROUP BY substr(v2,0,1),pk3 ORDER BY pk3", + "SELECT pk3 FROM test WHERE pk1 = 'c' and v2 = substr('abc',1,1) GROUP BY v2,pk3 ORDER BY pk3", + "SELECT pk3 FROM test WHERE v1 = 'a' and v2 = 'b' GROUP BY length(v1)+length(v2),pk3 ORDER BY pk3", + "SELECT pk3 FROM test WHERE pk1 = 'a' and v2 = 'b' GROUP BY length(pk1)+length(v2),pk3 ORDER BY pk3", + "SELECT pk3 FROM test WHERE v1 = 'a' and v2 = substr('abc',2,1) GROUP BY pk4,CASE WHEN v1 > v2 THEN v1 ELSE v2 END,pk3 ORDER BY pk4,pk3", + "SELECT pk3 FROM test WHERE pk1 = 'a' and v2 = substr('abc',2,1) GROUP BY pk4,CASE WHEN pk1 > v2 THEN pk1 ELSE v2 END,pk3 ORDER BY pk4,pk3", + "SELECT pk3 FROM test WHERE pk1 = 'a' and pk2 = 'b' and v1 = 'c' GROUP BY CASE WHEN pk1 > pk2 THEN v1 WHEN pk1 = pk2 THEN pk1 ELSE pk2 END,pk3 ORDER BY pk3" + }; + int index = 0; + for (String query : queries) { + QueryPlan plan = getQueryPlan(conn, query); + assertTrue((index + 1) + ") " + queries[index], plan.getOrderBy().getOrderByExpressions().isEmpty()); + index++; + } + } + } + + @Test + public void testOrderPreservingGroupByForClientAggregatePlan() throws Exception { + Connection conn = null; + try { + conn = DriverManager.getConnection(getUrl()); + String tableName = "test_table"; + String sql = "create table " + tableName + "( "+ + " pk1 varchar not null , " + + " pk2 varchar not null, " + + " pk3 varchar not null," + + " v1 varchar, " + + " v2 varchar, " + + " CONSTRAINT TEST_PK PRIMARY KEY ( "+ + "pk1,"+ + "pk2,"+ + "pk3 ))"; + conn.createStatement().execute(sql); + + String[] queries = new String[] { + "select a.ak3 "+ + "from (select substr(pk1,1,1) ak1,substr(pk2,1,1) ak2,substr(pk3,1,1) ak3,substr(v1,1,1) av1,substr(v2,1,1) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "group by a.ak3,a.av1 order by a.ak3,a.av1", + + "select a.ak3 "+ + "from (select substr(pk1,1,1) ak1,substr(pk2,1,1) ak2,substr(pk3,1,1) ak3,substr(v1,1,1) av1,substr(v2,1,1) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where a.av2 = 'a' GROUP BY substr(a.av2,0,1),ak3 ORDER BY ak3", + + //for InListExpression + "select a.ak3 "+ + "from (select substr(pk1,1,1) ak1,substr(pk2,1,1) ak2,substr(pk3,1,1) ak3,substr(v1,1,1) av1,substr(v2,1,1) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where a.av2 in('a') GROUP BY substr(a.av2,0,1),ak3 ORDER BY ak3", + + "select a.ak3 "+ + "from (select substr(pk1,1,1) ak1,substr(pk2,1,1) ak2,substr(pk3,1,1) ak3,substr(v1,1,1) av1,substr(v2,1,1) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where a.ak1 = 'c' and a.av2 = substr('abc',1,1) GROUP BY a.av2,a.ak3 ORDER BY a.ak3", + + //for RVC + "select a.ak3 "+ + "from (select substr(pk1,1,1) ak1,substr(pk2,1,1) ak2,substr(pk3,1,1) ak3,substr(v1,1,1) av1,substr(v2,1,1) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where (a.ak1,a.av2) = ('c', substr('abc',1,1)) GROUP BY a.av2,a.ak3 ORDER BY a.ak3", + + "select a.ak3 "+ + "from (select substr(pk1,1,1) ak1,substr(pk2,1,1) ak2,substr(pk3,1,1) ak3,substr(v1,1,1) av1,substr(v2,1,1) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where a.av1 = 'a' and a.av2 = 'b' GROUP BY length(a.av1)+length(a.av2),a.ak3 ORDER BY a.ak3", + + "select a.ak3 "+ + "from (select substr(pk1,1,1) ak1,substr(pk2,1,1) ak2,substr(pk3,1,1) ak3,substr(v1,1,1) av1,substr(v2,1,1) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where a.ak1 = 'a' and a.av2 = 'b' GROUP BY length(a.ak1)+length(a.av2),a.ak3 ORDER BY a.ak3", + + "select a.ak3 "+ + "from (select substr(pk1,1,1) ak1,substr(pk2,1,1) ak2,substr(pk3,1,1) ak3, coalesce(pk3,'1') ak4, substr(v1,1,1) av1,substr(v2,1,1) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where a.av1 = 'a' and a.av2 = substr('abc',2,1) GROUP BY a.ak4,CASE WHEN a.av1 > a.av2 THEN a.av1 ELSE a.av2 END,a.ak3 ORDER BY a.ak4,a.ak3", + + "select a.ak3 "+ + "from (select rand() ak1,length(pk2) ak2,length(pk3) ak3,length(v1) av1,length(v2) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where a.ak1 = 0.0 and a.av2 = (5+3*2) GROUP BY a.ak3,CASE WHEN a.ak1 > a.av2 THEN a.ak1 ELSE a.av2 END,a.av1 ORDER BY a.ak3,a.av1", + + "select a.ak3 "+ + "from (select rand() ak1,length(pk2) ak2,length(pk3) ak3,length(v1) av1,length(v2) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where a.ak1 = 0.0 and a.av2 = length(substr('abc',1,1)) GROUP BY a.ak3,CASE WHEN a.ak1 > a.av2 THEN a.ak1 ELSE a.av2 END,a.av1 ORDER BY a.ak3,a.av1", + + "select a.ak3 "+ + "from (select rand() ak1,length(pk2) ak2,length(pk3) ak3,length(v1) av1,length(v2) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where a.ak1 = 0.0 and a.av2 = length(substr('abc',1,1)) GROUP BY a.ak3,CASE WHEN coalesce(a.ak1,1) > coalesce(a.av2,2) THEN coalesce(a.ak1,1) ELSE coalesce(a.av2,2) END,a.av1 ORDER BY a.ak3,a.av1", + + //for IS NULL + "select a.ak3 "+ + "from (select rand() ak1,length(pk2) ak2,length(pk3) ak3,length(v1) av1,length(v2) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where a.ak1 is null and a.av2 = length(substr('abc',1,1)) GROUP BY a.ak3,CASE WHEN coalesce(a.ak1,1) > coalesce(a.av2,2) THEN coalesce(a.ak1,1) ELSE coalesce(a.av2,2) END,a.av1 ORDER BY a.ak3,a.av1", + + "select a.ak3 "+ + "from (select rand() ak1,length(pk2) ak2,length(pk3) ak3,length(v1) av1,length(v2) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where a.ak1 = 0.0 and a.av2 is null GROUP BY a.ak3,CASE WHEN coalesce(a.ak1,1) > coalesce(a.av2,2) THEN coalesce(a.ak1,1) ELSE coalesce(a.av2,2) END,a.av1 ORDER BY a.ak3,a.av1", + }; + int index = 0; + for (String query : queries) { + QueryPlan plan = TestUtil.getOptimizeQueryPlan(conn, query); + assertTrue((index + 1) + ") " + queries[index], plan.getOrderBy()== OrderBy.FWD_ROW_KEY_ORDER_BY); + index++; + } + } + finally { + if(conn != null) { + conn.close(); + } + } + } @Test public void testNotOrderPreservingGroupBy() throws Exception { @@ -2987,6 +3110,207 @@ public void testNotOrderPreservingGroupBy() throws Exception { } } + @Test + public void testNotOrderPreservingGroupByForNotPkColumns() throws Exception { + try (Connection conn= DriverManager.getConnection(getUrl())) { + conn.createStatement().execute("CREATE TABLE test (\n" + + " pk1 varchar,\n" + + " pk2 varchar,\n" + + " pk3 varchar,\n" + + " pk4 varchar,\n" + + " v1 varchar,\n" + + " v2 varchar,\n" + + " CONSTRAINT pk PRIMARY KEY (\n" + + " pk1,\n" + + " pk2,\n" + + " pk3,\n" + + " pk4\n" + + " )\n" + + " )"); + String[] queries = new String[] { + "SELECT pk3 FROM test WHERE (pk1 = 'a' and pk2 = 'b') or v1 ='c' GROUP BY pk4,CASE WHEN pk1 > pk2 THEN coalesce(v1,'1') ELSE pk2 END,pk3 ORDER BY pk4,pk3", + "SELECT pk3 FROM test WHERE pk1 = 'a' or pk2 = 'b' GROUP BY CASE WHEN pk1 > pk2 THEN v1 WHEN pk1 = pk2 THEN pk1 ELSE pk2 END,pk3 ORDER BY pk3", + "SELECT pk3 FROM test WHERE pk1 = 'a' and (pk2 = 'b' or v1 = 'c') GROUP BY CASE WHEN pk1 > pk2 THEN v1 WHEN pk1 = pk2 THEN pk1 ELSE pk2 END,pk3 ORDER BY pk3", + "SELECT v2 FROM test GROUP BY v1,v2 ORDER BY v2", + "SELECT pk3 FROM test WHERE v1 = 'a' GROUP BY v1,v2,pk3 ORDER BY pk3", + "SELECT length(pk3) FROM test WHERE v1 = 'a' GROUP BY RAND()+length(v1),length(v2),length(pk3) ORDER BY length(v2),length(pk3)", + "SELECT length(pk3) FROM test WHERE v1 = 'a' and v2 = 'b' GROUP BY CASE WHEN v1 > v2 THEN length(v1) ELSE RAND(1) END,length(pk3) ORDER BY length(pk3)", + }; + int index = 0; + for (String query : queries) { + QueryPlan plan = getQueryPlan(conn, query); + assertFalse((index + 1) + ") " + queries[index], plan.getOrderBy().getOrderByExpressions().isEmpty()); + index++; + } + } + } + + @Test + public void testNotOrderPreservingGroupByForClientAggregatePlan() throws Exception { + Connection conn = null; + try { + conn = DriverManager.getConnection(getUrl()); + String tableName = "table_test"; + String sql = "create table " + tableName + "( "+ + " pk1 varchar not null , " + + " pk2 varchar not null, " + + " pk3 varchar not null," + + " v1 varchar, " + + " v2 varchar, " + + " CONSTRAINT TEST_PK PRIMARY KEY ( "+ + "pk1,"+ + "pk2,"+ + "pk3 ))"; + conn.createStatement().execute(sql); + + String[] queries = new String[] { + "select a.ak3 "+ + "from (select substr(pk1,1,1) ak1,substr(pk2,1,1) ak2,substr(pk3,1,1) ak3,coalesce(pk3,'1') ak4, substr(v1,1,1) av1,substr(v2,1,1) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where (a.ak1 = 'a' and a.ak2 = 'b') or a.av1 ='c' GROUP BY a.ak4,CASE WHEN a.ak1 > a.ak2 THEN coalesce(a.av1,'1') ELSE a.ak2 END,a.ak3 ORDER BY a.ak4,a.ak3", + + "select a.ak3 "+ + "from (select substr(pk1,1,1) ak1,substr(pk2,1,1) ak2,substr(pk3,1,1) ak3,coalesce(pk3,'1') ak4, substr(v1,1,1) av1,substr(v2,1,1) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where a.ak1 = 'a' or a.ak2 = 'b' GROUP BY CASE WHEN a.ak1 > a.ak2 THEN a.av1 WHEN a.ak1 = a.ak2 THEN a.ak1 ELSE a.ak2 END,a.ak3 ORDER BY a.ak3", + + //for in + "select a.ak3 "+ + "from (select substr(pk1,1,1) ak1,substr(pk2,1,1) ak2,substr(pk3,1,1) ak3,coalesce(pk3,'1') ak4, substr(v1,1,1) av1,substr(v2,1,1) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where a.ak1 in ( 'a','b') GROUP BY CASE WHEN a.ak1 > a.ak2 THEN a.av1 WHEN a.ak1 = a.ak2 THEN a.ak1 ELSE a.ak2 END,a.ak3 ORDER BY a.ak3", + + "select a.ak3 "+ + "from (select substr(pk1,1,1) ak1,substr(pk2,1,1) ak2,substr(pk3,1,1) ak3,coalesce(pk3,'1') ak4, substr(v1,1,1) av1,substr(v2,1,1) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where a.ak1 = 'a' and (a.ak2 = 'b' or a.av1 = 'c') GROUP BY CASE WHEN a.ak1 > a.ak2 THEN a.av1 WHEN a.ak1 = a.ak2 THEN a.ak1 ELSE a.ak2 END,a.ak3 ORDER BY a.ak3", + + "select a.av2 "+ + "from (select substr(pk1,1,1) ak1,substr(pk2,1,1) ak2,substr(pk3,1,1) ak3,coalesce(pk3,'1') ak4, substr(v1,1,1) av1,substr(v2,1,1) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "GROUP BY a.av1,a.av2 ORDER BY a.av2", + + "select a.ak3 "+ + "from (select substr(pk1,1,1) ak1,substr(pk2,1,1) ak2,substr(pk3,1,1) ak3,coalesce(pk3,'1') ak4, substr(v1,1,1) av1,substr(v2,1,1) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where a.av1 = 'a' GROUP BY a.av1,a.av2,a.ak3 ORDER BY a.ak3", + + "select length(a.ak3) "+ + "from (select substr(pk1,1,1) ak1,substr(pk2,1,1) ak2,substr(pk3,1,1) ak3,coalesce(pk3,'1') ak4, substr(v1,1,1) av1,substr(v2,1,1) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where a.av1 = 'a' GROUP BY RAND()+length(a.av1),length(a.av2),length(a.ak3) ORDER BY length(a.av2),length(a.ak3)", + + "select length(a.ak3) "+ + "from (select substr(pk1,1,1) ak1,substr(pk2,1,1) ak2,substr(pk3,1,1) ak3,coalesce(pk3,'1') ak4, substr(v1,1,1) av1,substr(v2,1,1) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where a.av1 = 'a' and a.av2 = 'b' GROUP BY CASE WHEN a.av1 > a.av2 THEN length(a.av1) ELSE RAND(1) END,length(a.ak3) ORDER BY length(a.ak3)", + + "select a.ak3 "+ + "from (select rand() ak1,length(pk2) ak2,length(pk3) ak3,length(v1) av1,length(v2) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where a.ak1 > 0.0 and a.av2 = (5+3*2) GROUP BY a.ak3,CASE WHEN a.ak1 > a.av2 THEN a.ak1 ELSE a.av2 END,a.av1 ORDER BY a.ak3,a.av1", + + //for CoerceExpression + "select a.ak3 "+ + "from (select rand() ak1,length(pk2) ak2,length(pk3) ak3,length(v1) av1,length(v2) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where CAST(a.ak1 AS INTEGER) = 0 and a.av2 = (5+3*2) GROUP BY a.ak3,a.ak1,a.av1 ORDER BY a.ak3,a.av1", + + "select a.ak3 "+ + "from (select rand() ak1,length(pk2) ak2,length(pk3) ak3,length(v1) av1,length(v2) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where a.ak1 = 0.0 or a.av2 = length(substr('abc',1,1)) GROUP BY a.ak3,CASE WHEN coalesce(a.ak1,1) > coalesce(a.av2,2) THEN coalesce(a.ak1,1) ELSE coalesce(a.av2,2) END,a.av1 ORDER BY a.ak3,a.av1", + + //for IS NULL + "select a.ak3 "+ + "from (select rand() ak1,length(pk2) ak2,length(pk3) ak3,length(v1) av1,length(v2) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where a.ak1 is not null and a.av2 = length(substr('abc',1,1)) GROUP BY a.ak3,CASE WHEN coalesce(a.ak1,1) > coalesce(a.av2,2) THEN coalesce(a.ak1,1) ELSE coalesce(a.av2,2) END,a.av1 ORDER BY a.ak3,a.av1", + + "select a.ak3 "+ + "from (select rand() ak1,length(pk2) ak2,length(pk3) ak3,length(v1) av1,length(v2) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where a.ak1 is null or a.av2 = length(substr('abc',1,1)) GROUP BY a.ak3,CASE WHEN coalesce(a.ak1,1) > coalesce(a.av2,2) THEN coalesce(a.ak1,1) ELSE coalesce(a.av2,2) END,a.av1 ORDER BY a.ak3,a.av1", + + "select a.ak3 "+ + "from (select rand() ak1,length(pk2) ak2,length(pk3) ak3,length(v1) av1,length(v2) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where a.ak1 is null and a.av2 = length(substr('abc',1,1)) and a.ak1 = 0.0 GROUP BY a.ak3,CASE WHEN coalesce(a.ak1,1) > coalesce(a.av2,2) THEN coalesce(a.ak1,1) ELSE coalesce(a.av2,2) END,a.av1 ORDER BY a.ak3,a.av1", + + "select a.ak3 "+ + "from (select rand() ak1,length(pk2) ak2,length(pk3) ak3,length(v1) av1,length(v2) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where a.ak1 is null and a.av2 = length(substr('abc',1,1)) or a.ak1 = 0.0 GROUP BY a.ak3,CASE WHEN coalesce(a.ak1,1) > coalesce(a.av2,2) THEN coalesce(a.ak1,1) ELSE coalesce(a.av2,2) END,a.av1 ORDER BY a.ak3,a.av1", + }; + int index = 0; + for (String query : queries) { + QueryPlan plan = TestUtil.getOptimizeQueryPlan(conn, query); + assertTrue((index + 1) + ") " + queries[index], plan.getOrderBy().getOrderByExpressions().size() > 0); + index++; + } + } + finally { + if(conn != null) { + conn.close(); + } + } + } + + @Test + public void testOrderByOptimizeForClientAggregatePlanAndDesc() throws Exception { + Connection conn = null; + try { + conn = DriverManager.getConnection(getUrl()); + String tableName = "test_table"; + String sql = "create table " + tableName + "( "+ + " pk1 varchar not null, " + + " pk2 varchar not null, " + + " pk3 varchar not null, " + + " v1 varchar, " + + " v2 varchar, " + + " CONSTRAINT TEST_PK PRIMARY KEY ( "+ + "pk1 desc,"+ + "pk2 desc,"+ + "pk3 desc))"; + conn.createStatement().execute(sql); + + String[] queries = new String[] { + "select a.ak3 "+ + "from (select pk1 ak1,pk2 ak2,pk3 ak3, substr(v1,1,1) av1,substr(v2,1,1) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "group by a.ak3,a.av1 order by a.ak3 desc,a.av1", + + "select a.ak3 "+ + "from (select pk1 ak1,pk2 ak2,pk3 ak3,substr(v1,1,1) av1,substr(v2,1,1) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where a.av1 = 'a' group by a.av1,a.ak3 order by a.ak3 desc", + + "select a.ak3 "+ + "from (select pk1 ak1,pk2 ak2,pk3 ak3,substr(v1,1,1) av1,substr(v2,1,1) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where a.av1 = 'a' and a.av2= 'b' group by CASE WHEN a.av1 > a.av2 THEN a.av1 ELSE a.av2 END,a.ak3,a.ak2 order by a.ak3 desc,a.ak2 desc" + }; + + int index = 0; + for (String query : queries) { + QueryPlan plan = TestUtil.getOptimizeQueryPlan(conn, query); + assertTrue((index + 1) + ") " + queries[index], plan.getOrderBy()== OrderBy.FWD_ROW_KEY_ORDER_BY); + index++; + } + + queries = new String[] { + "select a.ak3 "+ + "from (select pk1 ak1,pk2 ak2,pk3 ak3,substr(v1,1,1) av1,substr(v2,1,1) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "group by a.ak3,a.av1 order by a.ak3,a.av1", + + "select a.ak3 "+ + "from (select pk1 ak1,pk2 ak2,pk3 ak3,substr(v1,1,1) av1,substr(v2,1,1) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where a.av1 = 'a' group by a.av1,a.ak3 order by a.ak3", + + "select a.ak3 "+ + "from (select pk1 ak1,pk2 ak2,pk3 ak3,substr(v1,1,1) av1,substr(v2,1,1) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where a.av1 = 'a' and a.av2= 'b' group by CASE WHEN a.av1 > a.av2 THEN a.av1 ELSE a.av2 END,a.ak3,a.ak2 order by a.ak3,a.ak2", + + "select a.ak3 "+ + "from (select pk1 ak1,pk2 ak2,pk3 ak3,substr(v1,1,1) av1,substr(v2,1,1) av2 from "+tableName+" order by pk2,pk3 limit 10) a "+ + "where a.av1 = 'a' and a.av2= 'b' group by CASE WHEN a.av1 > a.av2 THEN a.av1 ELSE a.av2 END,a.ak3,a.ak2 order by a.ak3 asc,a.ak2 desc" + }; + index = 0; + for (String query : queries) { + QueryPlan plan = TestUtil.getOptimizeQueryPlan(conn, query); + assertTrue((index + 1) + ") " + queries[index], plan.getOrderBy().getOrderByExpressions().size() > 0); + index++; + } + } + finally { + if(conn != null) { + conn.close(); + } + } + } + @Test public void testGroupByDescColumnBug3451() throws Exception {