PHOENIX-4658 IllegalStateException: requestSeek cannot be called on ReversedKeyValueH...
[phoenix.git] / phoenix-core / src / main / java / org / apache / phoenix / compile / OrderByCompiler.java
1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18 package org.apache.phoenix.compile;
19
20
21 import java.sql.SQLException;
22 import java.util.Collections;
23 import java.util.LinkedHashSet;
24 import java.util.List;
25
26 import org.apache.phoenix.compile.GroupByCompiler.GroupBy;
27 import org.apache.phoenix.compile.OrderPreservingTracker.Ordering;
28 import org.apache.phoenix.exception.SQLExceptionCode;
29 import org.apache.phoenix.exception.SQLExceptionInfo;
30 import org.apache.phoenix.execute.TupleProjector;
31 import org.apache.phoenix.expression.Expression;
32 import org.apache.phoenix.expression.OrderByExpression;
33 import org.apache.phoenix.parse.HintNode.Hint;
34 import org.apache.phoenix.parse.LiteralParseNode;
35 import org.apache.phoenix.parse.OrderByNode;
36 import org.apache.phoenix.parse.ParseNode;
37 import org.apache.phoenix.parse.SelectStatement;
38 import org.apache.phoenix.query.QueryServices;
39 import org.apache.phoenix.query.QueryServicesOptions;
40 import org.apache.phoenix.schema.PColumn;
41 import org.apache.phoenix.schema.PTableType;
42 import org.apache.phoenix.schema.SortOrder;
43 import org.apache.phoenix.schema.types.PInteger;
44
45 import com.google.common.collect.ImmutableList;
46 import com.google.common.collect.Lists;
47 import com.google.common.collect.Sets;
48
49 /**
50 * Validates ORDER BY clause and builds up a list of referenced columns.
51 *
52 *
53 * @since 0.1
54 */
55 public class OrderByCompiler {
56 public static class OrderBy {
57 public static final OrderBy EMPTY_ORDER_BY = new OrderBy(Collections.<OrderByExpression>emptyList());
58 /**
59 * Used to indicate that there was an ORDER BY, but it was optimized out because
60 * rows are already returned in this order.
61 */
62 public static final OrderBy FWD_ROW_KEY_ORDER_BY = new OrderBy(Collections.<OrderByExpression>emptyList());
63 public static final OrderBy REV_ROW_KEY_ORDER_BY = new OrderBy(Collections.<OrderByExpression>emptyList());
64
65 private final List<OrderByExpression> orderByExpressions;
66
67 private OrderBy(List<OrderByExpression> orderByExpressions) {
68 this.orderByExpressions = ImmutableList.copyOf(orderByExpressions);
69 }
70
71 public List<OrderByExpression> getOrderByExpressions() {
72 return orderByExpressions;
73 }
74 }
75 /**
76 * Gets a list of columns in the ORDER BY clause
77 * @param context the query context for tracking various states
78 * associated with the given select statement
79 * @param statement TODO
80 * @param groupBy the list of columns in the GROUP BY clause
81 * @param limit the row limit or null if no limit
82 * @return the compiled ORDER BY clause
83 * @throws SQLException
84 */
85 public static OrderBy compile(StatementContext context,
86 SelectStatement statement,
87 GroupBy groupBy, Integer limit,
88 Integer offset,
89 RowProjector rowProjector,
90 TupleProjector tupleProjector,
91 boolean isInRowKeyOrder) throws SQLException {
92 List<OrderByNode> orderByNodes = statement.getOrderBy();
93 if (orderByNodes.isEmpty()) {
94 return OrderBy.EMPTY_ORDER_BY;
95 }
96 // for ungroupedAggregates as GROUP BY expression, check against an empty group by
97 ExpressionCompiler compiler;
98 if (groupBy.isUngroupedAggregate()) {
99 compiler = new ExpressionCompiler(context, GroupBy.EMPTY_GROUP_BY) {
100 @Override
101 protected Expression addExpression(Expression expression) {return expression;}
102 @Override
103 protected void addColumn(PColumn column) {}
104 };
105 } else {
106 compiler = new ExpressionCompiler(context, groupBy);
107 }
108 // accumulate columns in ORDER BY
109 OrderPreservingTracker tracker =
110 new OrderPreservingTracker(context, groupBy, Ordering.ORDERED, orderByNodes.size(), tupleProjector);
111 LinkedHashSet<OrderByExpression> orderByExpressions = Sets.newLinkedHashSetWithExpectedSize(orderByNodes.size());
112 for (OrderByNode node : orderByNodes) {
113 ParseNode parseNode = node.getNode();
114 Expression expression = null;
115 if (parseNode instanceof LiteralParseNode && ((LiteralParseNode)parseNode).getType() == PInteger.INSTANCE){
116 Integer index = (Integer)((LiteralParseNode)parseNode).getValue();
117 int size = rowProjector.getColumnProjectors().size();
118 if (index > size || index <= 0 ) {
119 throw new SQLExceptionInfo.Builder(SQLExceptionCode.PARAM_INDEX_OUT_OF_BOUND)
120 .build().buildException();
121 }
122 expression = rowProjector.getColumnProjector(index-1).getExpression();
123 } else {
124 expression = node.getNode().accept(compiler);
125 // Detect mix of aggregate and non aggregates (i.e. ORDER BY txns, SUM(txns)
126 if (!expression.isStateless() && !compiler.isAggregate()) {
127 if (statement.isAggregate() || statement.isDistinct()) {
128 // Detect ORDER BY not in SELECT DISTINCT: SELECT DISTINCT count(*) FROM t ORDER BY x
129 if (statement.isDistinct()) {
130 throw new SQLExceptionInfo.Builder(SQLExceptionCode.ORDER_BY_NOT_IN_SELECT_DISTINCT)
131 .setMessage(expression.toString()).build().buildException();
132 }
133 ExpressionCompiler.throwNonAggExpressionInAggException(expression.toString());
134 }
135 }
136 }
137 if (!expression.isStateless()) {
138 boolean isAscending = node.isAscending();
139 boolean isNullsLast = node.isNullsLast();
140 tracker.track(expression, isAscending ? SortOrder.ASC : SortOrder.DESC, isNullsLast);
141 // If we have a schema where column A is DESC, reverse the sort order and nulls last
142 // since this is the order they actually are in.
143 if (expression.getSortOrder() == SortOrder.DESC) {
144 isAscending = !isAscending;
145 }
146 OrderByExpression orderByExpression = new OrderByExpression(expression, isNullsLast, isAscending);
147 orderByExpressions.add(orderByExpression);
148 }
149 compiler.reset();
150 }
151 // we can remove ORDER BY clauses in case of only COUNT(DISTINCT...) clauses
152 if (orderByExpressions.isEmpty() || groupBy.isUngroupedAggregate()) {
153 return OrderBy.EMPTY_ORDER_BY;
154 }
155 // If we're ordering by the order returned by the scan, we don't need an order by
156 if (isInRowKeyOrder && tracker.isOrderPreserving()) {
157 if (tracker.isReverse()) {
158 // Don't use reverse scan if:
159 // 1) we're using a skip scan, as our skip scan doesn't support this yet.
160 // 2) we have the FORWARD_SCAN hint set to choose to keep loading of column
161 // families on demand versus doing a reverse scan
162 // REV_ROW_KEY_ORDER_BY scan would not take effect for a projected table, so don't return it for such table types.
163 if (context.getConnection().getQueryServices().getProps().getBoolean(QueryServices.USE_REVERSE_SCAN_ATTRIB, QueryServicesOptions.DEFAULT_USE_REVERSE_SCAN)
164 && !context.getScanRanges().useSkipScanFilter()
165 && context.getCurrentTable().getTable().getType() != PTableType.PROJECTED
166 && context.getCurrentTable().getTable().getType() != PTableType.SUBQUERY
167 && !statement.getHint().hasHint(Hint.FORWARD_SCAN)) {
168 return OrderBy.REV_ROW_KEY_ORDER_BY;
169 }
170 } else {
171 return OrderBy.FWD_ROW_KEY_ORDER_BY;
172 }
173 }
174
175 return new OrderBy(Lists.newArrayList(orderByExpressions.iterator()));
176 }
177
178 private OrderByCompiler() {
179 }
180 }