blob: 5d40398df80f4efa420f61637a9b709352689fc4 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.pinot.core.plan;
import java.util.ArrayList;
import java.util.List;
import org.apache.pinot.common.request.context.ExpressionContext;
import org.apache.pinot.common.request.context.OrderByExpressionContext;
import org.apache.pinot.core.common.Operator;
import org.apache.pinot.core.operator.blocks.results.SelectionResultsBlock;
import org.apache.pinot.core.operator.query.EmptySelectionOperator;
import org.apache.pinot.core.operator.query.SelectionOnlyOperator;
import org.apache.pinot.core.operator.query.SelectionOrderByOperator;
import org.apache.pinot.core.operator.transform.TransformOperator;
import org.apache.pinot.core.query.request.context.QueryContext;
import org.apache.pinot.core.query.selection.SelectionOperatorUtils;
import org.apache.pinot.segment.spi.IndexSegment;
/**
* The <code>SelectionPlanNode</code> class provides the execution plan for selection query on a single segment.
*/
public class SelectionPlanNode implements PlanNode {
private final IndexSegment _indexSegment;
private final QueryContext _queryContext;
public SelectionPlanNode(IndexSegment indexSegment, QueryContext queryContext) {
_indexSegment = indexSegment;
_queryContext = queryContext;
}
@Override
public Operator<SelectionResultsBlock> run() {
List<ExpressionContext> expressions = SelectionOperatorUtils.extractExpressions(_queryContext, _indexSegment);
int limit = _queryContext.getLimit();
if (limit > 0) {
List<OrderByExpressionContext> orderByExpressions = _queryContext.getOrderByExpressions();
if (orderByExpressions == null) {
// Selection only
TransformOperator transformOperator = new TransformPlanNode(_indexSegment, _queryContext, expressions,
Math.min(limit, DocIdSetPlanNode.MAX_DOC_PER_CALL)).run();
return new SelectionOnlyOperator(_indexSegment, _queryContext, expressions, transformOperator);
} else {
// Selection order-by
if (isAllOrderByColumnsSorted(orderByExpressions)) {
// All order-by columns are sorted, no need to sort the records
TransformOperator transformOperator = new TransformPlanNode(_indexSegment, _queryContext, expressions,
Math.min(limit + _queryContext.getOffset(), DocIdSetPlanNode.MAX_DOC_PER_CALL)).run();
return new SelectionOrderByOperator(_indexSegment, _queryContext, expressions, transformOperator, true);
} else if (orderByExpressions.size() == expressions.size()) {
// All output expressions are ordered
TransformOperator transformOperator =
new TransformPlanNode(_indexSegment, _queryContext, expressions, DocIdSetPlanNode.MAX_DOC_PER_CALL).run();
return new SelectionOrderByOperator(_indexSegment, _queryContext, expressions, transformOperator, false);
} else {
// Not all output expressions are ordered, only fetch the order-by expressions and docId to avoid the
// unnecessary data fetch
List<ExpressionContext> expressionsToTransform = new ArrayList<>(orderByExpressions.size());
for (OrderByExpressionContext orderByExpression : orderByExpressions) {
expressionsToTransform.add(orderByExpression.getExpression());
}
TransformOperator transformOperator =
new TransformPlanNode(_indexSegment, _queryContext, expressionsToTransform,
DocIdSetPlanNode.MAX_DOC_PER_CALL).run();
return new SelectionOrderByOperator(_indexSegment, _queryContext, expressions, transformOperator, false);
}
}
} else {
// Empty selection (LIMIT 0)
TransformOperator transformOperator = new TransformPlanNode(_indexSegment, _queryContext, expressions, 0).run();
return new EmptySelectionOperator(_indexSegment, expressions, transformOperator);
}
}
/**
* This function checks whether all columns in order by clause are pre-sorted.
* This is used to optimize order by limit clauses.
* For eg:
* A query like "select * from table order by col1, col2 limit 10"
* will take all the n matching rows and add it to a priority queue of size 10.
* This is nlogk operation which can be quite expensive for a large n.
* In the above example, if the docs in the segment are already sorted by col1 and col2 then there is no need for
* sorting at all (only limit is needed).
* @return true is all columns in order by clause are sorted . False otherwise
*/
private boolean isAllOrderByColumnsSorted(List<OrderByExpressionContext> orderByExpressions) {
for (OrderByExpressionContext orderByExpression : orderByExpressions) {
if (!(orderByExpression.getExpression().getType() == ExpressionContext.Type.IDENTIFIER)
|| !orderByExpression.isAsc()) {
return false;
}
String column = orderByExpression.getExpression().getIdentifier();
if (!_indexSegment.getDataSource(column).getDataSourceMetadata().isSorted()) {
return false;
}
}
return true;
}
}