This is an automated email from the ASF dual-hosted git repository.
Jackie-Jiang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git
The following commit(s) were added to refs/heads/master by this push:
new af3c64f7eb7 Make sorted ORDER BY late materialization automatic
(#20003)
af3c64f7eb7 is described below
commit af3c64f7eb7d85488900818ccf7b5c6909a266fc
Author: Xiaotian (Jackie) Jiang <[email protected]>
AuthorDate: Thu Oct 22 16:47:51 2026 -0700
Make sorted ORDER BY late materialization automatic (#20003)
---
.../common/utils/config/QueryOptionsUtils.java | 8 --
.../query/LinearSelectionOrderByOperator.java | 154 +++++++--------------
.../SelectionPartiallyOrderedByDescOperation.java | 16 +--
.../SelectionPartiallyOrderedByLinearOperator.java | 10 +-
.../apache/pinot/core/plan/SelectionPlanNode.java | 57 ++------
...nnerSegmentSelectionSingleValueQueriesTest.java | 113 +++++----------
.../queries/NullHandlingEnabledQueriesTest.java | 71 +++++++---
.../queries/TextMatchTransformFunctionTest.java | 12 +-
.../apache/pinot/spi/utils/CommonConstants.java | 10 --
9 files changed, 155 insertions(+), 296 deletions(-)
diff --git
a/pinot-common/src/main/java/org/apache/pinot/common/utils/config/QueryOptionsUtils.java
b/pinot-common/src/main/java/org/apache/pinot/common/utils/config/QueryOptionsUtils.java
index 491417d7315..936c968aaf0 100644
---
a/pinot-common/src/main/java/org/apache/pinot/common/utils/config/QueryOptionsUtils.java
+++
b/pinot-common/src/main/java/org/apache/pinot/common/utils/config/QueryOptionsUtils.java
@@ -964,14 +964,6 @@ public class QueryOptionsUtils {
return queryOptions.getOrDefault(QueryOptionKey.WORKLOAD_NAME,
CommonConstants.Accounting.DEFAULT_WORKLOAD_NAME);
}
- public static boolean isLateMaterializeSortedOrderByEnabled(Map<String,
String> queryOptions) {
- String value =
queryOptions.get(QueryOptionKey.LATE_MATERIALIZE_SORTED_ORDER_BY);
- if (value == null) {
- return QueryOptionKey.DEFAULT_LATE_MATERIALIZE_SORTED_ORDER_BY;
- }
- return Boolean.parseBoolean(value);
- }
-
public static boolean isReverseOrderAllowed(Map<String, String>
queryOptions) {
String value = queryOptions.get(QueryOptionKey.ALLOW_REVERSE_ORDER);
if (value == null) {
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/LinearSelectionOrderByOperator.java
b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/LinearSelectionOrderByOperator.java
index b5a16551b25..4e87193899e 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/LinearSelectionOrderByOperator.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/LinearSelectionOrderByOperator.java
@@ -38,6 +38,7 @@ import javax.annotation.Nullable;
import org.apache.pinot.common.request.context.ExpressionContext;
import org.apache.pinot.common.request.context.OrderByExpressionContext;
import org.apache.pinot.common.utils.DataSchema;
+import org.apache.pinot.common.utils.DataSchema.ColumnDataType;
import org.apache.pinot.core.common.BlockValSet;
import org.apache.pinot.core.common.RowBasedBlockValueFetcher;
import org.apache.pinot.core.operator.BaseOperator;
@@ -67,11 +68,11 @@ import org.roaringbitmap.RoaringBitmap;
/// or: SELECT ... FROM Table WHERE predicates ORDER BY sorted_column,
not_sorted LIMIT 10 OFFSET 5
/// but not SELECT ... FROM Table WHERE predicates ORDER BY not_sorted,
sorted_column LIMIT 10 OFFSET 5
///
-/// Operators that derives from this class are going to have an almost linear
cost instead of the usual NlogN when
-/// actual sorting must be done, where N is the number of rows in the segment.
There is a degraded scenario when the
-/// cost is actually NlogL (where L is the limit of the query) when all the
first L rows have the exact same value for
-/// the prefix of the sorted columns. Even in that case, L should be quite
smaller than N, so this implementation is
-/// algorithmically better than the normal solution.
+/// Only order-by expressions are fetched during selection. Any remaining
output expressions are fetched for the
+/// surviving rows after selection, as in [SelectionOrderByOperator].
+///
+/// These operators reuse the segment's sorted prefix. Rows tied on that
prefix are ordered by the remaining
+/// expressions.
public abstract class LinearSelectionOrderByOperator extends
BaseOperator<SelectionResultsBlock> {
protected final IndexSegment _indexSegment;
protected final QueryContext _queryContext;
@@ -82,72 +83,49 @@ public abstract class LinearSelectionOrderByOperator
extends BaseOperator<Select
protected final List<ExpressionContext> _toSort;
protected final BaseProjectOperator<?> _projectOperator;
- protected final List<OrderByExpressionContext> _orderByExpressions;
protected final ColumnContext[] _columnContexts;
- protected final int _numRowsToKeep;
protected final Comparator<Object[]> _comparator;
protected final Supplier<ListBuilder> _listBuilderSupplier;
protected final int _numOrderByExpressions;
- /// When true the project operator only produces the order-by expressions,
and the remaining output expressions are
- /// fetched for the rows that survive the limit. Rows carry their doc id at
index [#_numOrderByExpressions] until
- /// that fetch overwrites it.
- protected final boolean _lateMaterialize;
private long _numEntriesScannedPostFilterLate;
/// @param expressions Order-by expressions must be at the head of
the list.
/// @param numSortedExpressions Number of expressions in the order-by
expressions that are sorted.
public LinearSelectionOrderByOperator(IndexSegment indexSegment,
QueryContext queryContext,
List<ExpressionContext> expressions, BaseProjectOperator<?>
projectOperator, int numSortedExpressions) {
- this(indexSegment, queryContext, expressions, projectOperator,
numSortedExpressions, false);
- }
-
- /// @param lateMaterialize whether `projectOperator` produces only the
order-by expressions, leaving the remaining
- /// output expressions to be fetched once the limit
has been applied.
- public LinearSelectionOrderByOperator(IndexSegment indexSegment,
QueryContext queryContext,
- List<ExpressionContext> expressions, BaseProjectOperator<?>
projectOperator, int numSortedExpressions,
- boolean lateMaterialize) {
_indexSegment = indexSegment;
_queryContext = queryContext;
_nullHandlingEnabled = queryContext.isNullHandlingEnabled();
_expressions = expressions;
_projectOperator = projectOperator;
- _orderByExpressions = queryContext.getOrderByExpressions();
- assert _orderByExpressions != null;
- int numOrderByExpressions = _orderByExpressions.size();
-
- _numOrderByExpressions = numOrderByExpressions;
- _lateMaterialize = lateMaterialize;
- // Rows carry the doc id at index numOrderByExpressions, so there must be
at least one expression after the
- // order-by ones for that slot to exist (and something worth deferring).
- Preconditions.checkArgument(!lateMaterialize || expressions.size() >
numOrderByExpressions,
- "Late materialization requires output expressions beyond the %s
order-by ones, got %s",
- numOrderByExpressions, expressions.size());
+ List<OrderByExpressionContext> orderByExpressions =
queryContext.getOrderByExpressions();
+ assert orderByExpressions != null;
+ _numOrderByExpressions = orderByExpressions.size();
_alreadySorted = expressions.subList(0, numSortedExpressions);
- _toSort = expressions.subList(numSortedExpressions, numOrderByExpressions);
+ _toSort = expressions.subList(numSortedExpressions,
_numOrderByExpressions);
- // Only the projected expressions can be resolved against the project
operator. When materialising late it holds
- // the order-by expressions alone, which is also all the comparator needs.
- _columnContexts = new ColumnContext[lateMaterialize ?
numOrderByExpressions : _expressions.size()];
+ // Only order-by expressions are projected during selection; the remaining
expressions are fetched afterwards.
+ _columnContexts = new ColumnContext[_numOrderByExpressions];
for (int i = 0; i < _columnContexts.length; i++) {
ExpressionContext expression = _expressions.get(i);
_columnContexts[i] = _projectOperator.getResultColumnContext(expression);
}
- _numRowsToKeep = queryContext.getOffset() + queryContext.getLimit();
- _comparator = OrderByComparatorFactory.getComparator(_orderByExpressions,
_columnContexts, _nullHandlingEnabled);
+ int numRowsToKeep = queryContext.getOffset() + queryContext.getLimit();
+ _comparator = OrderByComparatorFactory.getComparator(orderByExpressions,
_columnContexts, _nullHandlingEnabled);
if (_toSort.isEmpty()) {
- _listBuilderSupplier = () -> new
TotallySortedListBuilder(_numRowsToKeep);
+ _listBuilderSupplier = () -> new TotallySortedListBuilder(numRowsToKeep);
} else {
Comparator<Object[]> sortedComparator =
- OrderByComparatorFactory.getComparator(_orderByExpressions,
_columnContexts, _nullHandlingEnabled, 0,
+ OrderByComparatorFactory.getComparator(orderByExpressions,
_columnContexts, _nullHandlingEnabled, 0,
numSortedExpressions);
Comparator<Object[]> unsortedComparator =
- OrderByComparatorFactory.getComparator(_orderByExpressions,
_columnContexts, _nullHandlingEnabled,
- numSortedExpressions, numOrderByExpressions);
- _listBuilderSupplier = () -> new
PartiallySortedListBuilder(_numRowsToKeep, sortedComparator,
unsortedComparator);
+ OrderByComparatorFactory.getComparator(orderByExpressions,
_columnContexts, _nullHandlingEnabled,
+ numSortedExpressions, _numOrderByExpressions);
+ _listBuilderSupplier = () -> new
PartiallySortedListBuilder(numRowsToKeep, sortedComparator, unsortedComparator);
}
}
@@ -167,16 +145,8 @@ public abstract class LinearSelectionOrderByOperator
extends BaseOperator<Select
numTotalDocs);
}
- /// Number of expressions the project operator actually produces: all of
them, or just the order-by prefix when
- /// materialising late.
- protected int getNumProjectedExpressions() {
- return _columnContexts.length;
- }
-
protected IntFunction<Object[]> fetchBlock(ValueBlock valueBlock,
BlockValSet[] blockValSets) {
- int numProjected = getNumProjectedExpressions();
-
- for (int i = 0; i < numProjected; i++) {
+ for (int i = 0; i < _numOrderByExpressions; i++) {
ExpressionContext expression = _expressions.get(i);
blockValSets[i] = valueBlock.getBlockValueSet(expression);
}
@@ -184,21 +154,21 @@ public abstract class LinearSelectionOrderByOperator
extends BaseOperator<Select
RoaringBitmap[] nullBitmaps;
if (_nullHandlingEnabled) {
- nullBitmaps = new RoaringBitmap[numProjected];
- for (int i = 0; i < numProjected; i++) {
+ nullBitmaps = new RoaringBitmap[_numOrderByExpressions];
+ for (int i = 0; i < _numOrderByExpressions; i++) {
nullBitmaps[i] = blockValSets[i].getNullBitmap();
}
} else {
nullBitmaps = null;
}
- if (!_lateMaterialize) {
+ if (_expressions.size() == _numOrderByExpressions) {
if (nullBitmaps == null) {
return blockValueFetcher::getRow;
}
return (rowIdInBlock) -> {
Object[] row = blockValueFetcher.getRow(rowIdInBlock);
- applyNulls(row, nullBitmaps, rowIdInBlock);
+ applyNulls(row, nullBitmaps, rowIdInBlock, 0);
return row;
};
}
@@ -211,17 +181,17 @@ public abstract class LinearSelectionOrderByOperator
extends BaseOperator<Select
Object[] row = new Object[numExpressions];
blockValueFetcher.getRow(rowIdInBlock, row, 0);
if (nullBitmaps != null) {
- applyNulls(row, nullBitmaps, rowIdInBlock);
+ applyNulls(row, nullBitmaps, rowIdInBlock, 0);
}
row[_numOrderByExpressions] = docIds[rowIdInBlock];
return row;
};
}
- private static void applyNulls(Object[] row, RoaringBitmap[] nullBitmaps,
int rowIdInBlock) {
+ private static void applyNulls(Object[] row, RoaringBitmap[] nullBitmaps,
int rowIdInBlock, int columnOffset) {
for (int colId = 0; colId < nullBitmaps.length; colId++) {
if (nullBitmaps[colId] != null &&
nullBitmaps[colId].contains(rowIdInBlock)) {
- row[colId] = null;
+ row[columnOffset + colId] = null;
}
}
}
@@ -257,7 +227,7 @@ public abstract class LinearSelectionOrderByOperator
extends BaseOperator<Select
return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL,
getUpperCaseExplainName());
}
- // remember this ist he single-stage explain string
+ // Used by the single-stage explain string.
@Override
public String toExplainString() {
StringBuilder sb = new StringBuilder(getUpperCaseExplainName());
@@ -267,12 +237,9 @@ public abstract class LinearSelectionOrderByOperator
extends BaseOperator<Select
sb.append(", unsortedList: ");
concatList(sb, _toSort);
- if (_lateMaterialize) {
- sb.append(", lateMaterialized: true");
- }
sb.append(", rest: ");
- concatList(sb, _expressions.subList(_alreadySorted.size() +
_toSort.size(), _expressions.size()));
+ concatList(sb, _expressions.subList(_numOrderByExpressions,
_expressions.size()));
sb.append(')');
return sb.toString();
@@ -281,15 +248,12 @@ public abstract class LinearSelectionOrderByOperator
extends BaseOperator<Select
@Override
protected void explainAttributes(ExplainAttributeBuilder attributeBuilder) {
super.explainAttributes(attributeBuilder);
- List<ExpressionContext> rest = _expressions.subList(_alreadySorted.size()
+ _toSort.size(), _expressions.size());
+ List<ExpressionContext> rest =
_expressions.subList(_numOrderByExpressions, _expressions.size());
List<String> sortedList =
_alreadySorted.stream().map(ExpressionContext::toString).collect(Collectors.toList());
List<String> toSort =
_toSort.stream().map(ExpressionContext::toString).collect(Collectors.toList());
List<String> restStr =
rest.stream().map(ExpressionContext::toString).collect(Collectors.toList());
- if (_lateMaterialize) {
- attributeBuilder.putString("lateMaterialized", "true");
- }
attributeBuilder.putStringList("sortedList", sortedList)
.putStringList("unsortedList", toSort)
.putStringList("rest", restStr);
@@ -310,8 +274,8 @@ public abstract class LinearSelectionOrderByOperator
extends BaseOperator<Select
@Override
protected SelectionResultsBlock getNextBlock() {
List<Object[]> rows = fetch(_listBuilderSupplier);
- if (!_lateMaterialize) {
- return new SelectionResultsBlock(createDataSchema(), rows, _comparator,
_queryContext);
+ if (_expressions.size() == _numOrderByExpressions) {
+ return new SelectionResultsBlock(createDataSchema(null), rows,
_comparator, _queryContext);
}
return fetchRemainingExpressions(rows);
}
@@ -321,8 +285,7 @@ public abstract class LinearSelectionOrderByOperator
extends BaseOperator<Select
/// The rows are visited in doc-id order because that is the order
[BitmapDocIdSetOperator#ascending] returns, while
/// `rows` keeps the result order. Both views hold the same row arrays, so
filling one fills the other.
private SelectionResultsBlock fetchRemainingExpressions(List<Object[]> rows)
{
- int numExpressions = _expressions.size();
- List<ExpressionContext> nonOrderByExpressions =
_expressions.subList(_numOrderByExpressions, numExpressions);
+ List<ExpressionContext> nonOrderByExpressions =
_expressions.subList(_numOrderByExpressions, _expressions.size());
int numNonOrderByExpressions = nonOrderByExpressions.size();
int numRows = rows.size();
@@ -332,14 +295,10 @@ public abstract class LinearSelectionOrderByOperator
extends BaseOperator<Select
for (Object[] row : rowsByDocId) {
docIds.add((int) row[_numOrderByExpressions]);
}
- // rowsByDocId is filled by walking the bitmap in ascending order, so the
two must line up one for one.
- // They do only because surviving rows have distinct doc ids. If that ever
stopped holding, the bitmap would
- // dedupe and the trailing rows would keep their unfilled values instead
of failing -- wrong data returned,
- // not an error. Checked rather than asserted because this build never
runs with -ea, and the cost is one
- // cardinality read per query against a projection pass over every
surviving row.
- Preconditions.checkState(docIds.getCardinality() == numRows,
- "Expected one distinct doc id per surviving row, got %s doc ids for %s
rows", docIds.getCardinality(),
- numRows);
+ // Surviving doc ids must be unique so the bitmap and rowsByDocId stay
aligned.
+ int numDocIds = docIds.getCardinality();
+ Preconditions.checkState(numDocIds == numRows,
+ "Expected one distinct doc id per surviving row, got %s doc ids for %s
rows", numDocIds, numRows);
Set<String> columns = new HashSet<>();
for (ExpressionContext expression : nonOrderByExpressions) {
@@ -347,13 +306,9 @@ public abstract class LinearSelectionOrderByOperator
extends BaseOperator<Select
}
Map<String, DataSource> dataSourceMap = new HashMap<>();
for (String column : columns) {
- // Same resolution the eager path uses: ProjectPlanNode resolves every
selected column through this exact
- // call, including the ones fetched here. The schema is only
dereferenced for a column the segment does not
- // physically have, and it is set for every server query in
ServerQueryExecutorV1Impl.
dataSourceMap.put(column, _indexSegment.getDataSource(column,
_queryContext.getSchema()));
}
- // Distinct physical columns. A column that is both an order-by column and
referenced by a non-order-by
- // expression is read again here and counted again, which slightly
over-reports entries scanned.
+ // The scan count includes columns reread from the order-by pass.
int numColumns = columns.size();
BitmapDocIdSetOperator docIdSetOperator =
BitmapDocIdSetOperator.ascending(docIds, numRows);
@@ -380,12 +335,7 @@ public abstract class LinearSelectionOrderByOperator
extends BaseOperator<Select
nullBitmaps[i] = blockValSets[i].getNullBitmap();
}
for (int i = 0; i < numDocsFetched; i++) {
- Object[] row = rowsByDocId[rowBaseId + i];
- for (int colId = 0; colId < numNonOrderByExpressions; colId++) {
- if (nullBitmaps[colId] != null &&
nullBitmaps[colId].contains(i)) {
- row[_numOrderByExpressions + colId] = null;
- }
- }
+ applyNulls(rowsByDocId[rowBaseId + i], nullBitmaps, i,
_numOrderByExpressions);
}
}
long entriesScanned = (long) numDocsFetched * numColumns;
@@ -396,35 +346,25 @@ public abstract class LinearSelectionOrderByOperator
extends BaseOperator<Select
}
rowBaseId += numDocsFetched;
}
- return new SelectionResultsBlock(createDataSchema(transformOperator,
nonOrderByExpressions), rows, _comparator,
- _queryContext);
+ return new SelectionResultsBlock(createDataSchema(transformOperator),
rows, _comparator, _queryContext);
}
}
- /// Data schema covering both cases. Types for the expressions the scan
projected come from the project
- /// operator's column contexts; types for any expressions fetched late come
from the transform operator built
- /// for that fetch. `lateExpressions` is empty when nothing was materialised
late, which leaves the first
- /// branch covering every expression.
- private DataSchema createDataSchema(@Nullable TransformOperator
lateTransformOperator,
- List<ExpressionContext> lateExpressions) {
+ /// Resolves order-by types from the initial projection and remaining types
from the late transform operator.
+ private DataSchema createDataSchema(@Nullable TransformOperator
lateTransformOperator) {
int numExpressions = _expressions.size();
- int numProjected = numExpressions - lateExpressions.size();
String[] columnNames = new String[numExpressions];
- DataSchema.ColumnDataType[] columnDataTypes = new
DataSchema.ColumnDataType[numExpressions];
+ ColumnDataType[] columnDataTypes = new ColumnDataType[numExpressions];
for (int i = 0; i < numExpressions; i++) {
columnNames[i] = _expressions.get(i).toString();
- ColumnContext columnContext = i < numProjected ? _columnContexts[i]
- : lateTransformOperator.getResultColumnContext(lateExpressions.get(i
- numProjected));
- columnDataTypes[i] =
- DataSchema.ColumnDataType.fromDataType(columnContext.getDataType(),
columnContext.isSingleValue());
+ ColumnContext columnContext = i < _numOrderByExpressions
+ ? _columnContexts[i]
+ : lateTransformOperator.getResultColumnContext(_expressions.get(i));
+ columnDataTypes[i] =
ColumnDataType.fromDataType(columnContext.getDataType(),
columnContext.isSingleValue());
}
return new DataSchema(columnNames, columnDataTypes);
}
- protected DataSchema createDataSchema() {
- return createDataSchema(null, List.of());
- }
-
/// A private class used to build a partially sorted list by adding
partially sorted data.
///
/// Specifically, this class has been designed to receive successive calls
to [#add(Object[])] follow by a
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/SelectionPartiallyOrderedByDescOperation.java
b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/SelectionPartiallyOrderedByDescOperation.java
index 396bd01ab02..90ae5fc5cf3 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/SelectionPartiallyOrderedByDescOperation.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/SelectionPartiallyOrderedByDescOperation.java
@@ -44,13 +44,7 @@ public class SelectionPartiallyOrderedByDescOperation
extends LinearSelectionOrd
public SelectionPartiallyOrderedByDescOperation(IndexSegment indexSegment,
QueryContext queryContext,
List<ExpressionContext> expressions, BaseProjectOperator<?>
projectOperator, int numSortedExpressions) {
- this(indexSegment, queryContext, expressions, projectOperator,
numSortedExpressions, false);
- }
-
- public SelectionPartiallyOrderedByDescOperation(IndexSegment indexSegment,
QueryContext queryContext,
- List<ExpressionContext> expressions, BaseProjectOperator<?>
projectOperator, int numSortedExpressions,
- boolean lateMaterialize) {
- super(indexSegment, queryContext, expressions, projectOperator,
numSortedExpressions, lateMaterialize);
+ super(indexSegment, queryContext, expressions, projectOperator,
numSortedExpressions);
assert queryContext.getOrderByExpressions() != null;
Preconditions.checkArgument(!queryContext.getOrderByExpressions().stream()
.filter(expr -> expr.getExpression().getType() ==
ExpressionContext.Type.IDENTIFIER)
@@ -63,12 +57,10 @@ public class SelectionPartiallyOrderedByDescOperation
extends LinearSelectionOrd
@Override
protected List<Object[]> fetch(Supplier<ListBuilder> listBuilderSupplier) {
- // Ideally we would use a descending cursor, but we don't actually have
them
- // Alternatively, we could store all blocks in a list and iterate them in
reverse order, but ProjectionBlocks share
- // the same DataBlockCache, so they may be ephemeral and being overridden
by the next block.
- // The only alternative we have right now is to retrieve the last LIMIT
elements from each block
+ // This path handles projections that cannot iterate in descending order.
Keep the best rows from each block
+ // because the blocks share a mutable DataBlockCache and cannot be
buffered for reverse iteration.
- BlockValSet[] blockValSets = new BlockValSet[getNumProjectedExpressions()];
+ BlockValSet[] blockValSets = new BlockValSet[_numOrderByExpressions];
List<Object[]> localBestRows = new ArrayList<>();
ValueBlock valueBlock;
while ((valueBlock = _projectOperator.nextBlock()) != null) {
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/SelectionPartiallyOrderedByLinearOperator.java
b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/SelectionPartiallyOrderedByLinearOperator.java
index 33a43a144c8..845d65e0181 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/SelectionPartiallyOrderedByLinearOperator.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/SelectionPartiallyOrderedByLinearOperator.java
@@ -46,13 +46,7 @@ public class SelectionPartiallyOrderedByLinearOperator
extends LinearSelectionOr
public SelectionPartiallyOrderedByLinearOperator(IndexSegment indexSegment,
QueryContext queryContext,
List<ExpressionContext> expressions, BaseProjectOperator<?>
projectOperator, int numSortedExpressions) {
- this(indexSegment, queryContext, expressions, projectOperator,
numSortedExpressions, false);
- }
-
- public SelectionPartiallyOrderedByLinearOperator(IndexSegment indexSegment,
QueryContext queryContext,
- List<ExpressionContext> expressions, BaseProjectOperator<?>
projectOperator, int numSortedExpressions,
- boolean lateMaterialize) {
- super(indexSegment, queryContext, expressions, projectOperator,
numSortedExpressions, lateMaterialize);
+ super(indexSegment, queryContext, expressions, projectOperator,
numSortedExpressions);
boolean firstColIsAsc = queryContext.getOrderByExpressions().stream()
.filter(expr -> expr.getExpression().getType() ==
ExpressionContext.Type.IDENTIFIER)
.findFirst()
@@ -67,7 +61,7 @@ public class SelectionPartiallyOrderedByLinearOperator
extends LinearSelectionOr
@Override
protected List<Object[]> fetch(Supplier<ListBuilder> listBuilderSupplier) {
- BlockValSet[] blockValSets = new BlockValSet[getNumProjectedExpressions()];
+ BlockValSet[] blockValSets = new BlockValSet[_numOrderByExpressions];
ListBuilder listBuilder = listBuilderSupplier.get();
ValueBlock valueBlock;
while ((valueBlock = _projectOperator.nextBlock()) != null) {
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/plan/SelectionPlanNode.java
b/pinot-core/src/main/java/org/apache/pinot/core/plan/SelectionPlanNode.java
index 04b11cec49b..3e366d54140 100644
--- a/pinot-core/src/main/java/org/apache/pinot/core/plan/SelectionPlanNode.java
+++ b/pinot-core/src/main/java/org/apache/pinot/core/plan/SelectionPlanNode.java
@@ -18,7 +18,6 @@
*/
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;
@@ -74,8 +73,8 @@ public class SelectionPlanNode implements PlanNode {
return new SelectionOnlyOperator(_indexSegment, _queryContext,
expressions, projectOperator);
}
int numOrderByExpressions = orderByExpressions.size();
- // Although it is a break of abstraction, some code, specially merging,
assumes that if there is an order by
- // expression the operator will return a block whose selection result is a
priority queue.
+ // Order-by expressions come first; any remaining output expressions are
fetched after selection.
+ List<ExpressionContext> expressionsToProject = expressions.subList(0,
numOrderByExpressions);
int sortedColumnsPrefixSize = getSortedColumnsPrefix(orderByExpressions,
_queryContext.isNullHandlingEnabled());
if (sortedColumnsPrefixSize > 0) {
int maxDocsPerCall = DocIdSetPlanNode.MAX_DOC_PER_CALL;
@@ -84,59 +83,24 @@ public class SelectionPlanNode implements PlanNode {
// or: SELECT ... FROM Table WHERE predicates ORDER BY sorted_column,
not_sorted LIMIT 10 OFFSET 5
// but not SELECT ... FROM Table WHERE predicates ORDER BY not_sorted,
sorted_column LIMIT 10 OFFSET 5
- if (sortedColumnsPrefixSize == orderByExpressions.size()) {
+ if (sortedColumnsPrefixSize == numOrderByExpressions) {
maxDocsPerCall = Math.min(limit + _queryContext.getOffset(),
DocIdSetPlanNode.MAX_DOC_PER_CALL);
}
- // Whether the scan can iterate in query order, and so stop once the
list builder is full. Ascending always
- // can; descending only when reverse iteration is permitted, because
DocIdSetPlanNode only produces an
- // ascending DocIdSetOperator. getSortedByProject below performs the
actual switch and decides for real; this
- // is a conservative prediction, and being wrong only costs the
optimization, never correctness.
- boolean asc = orderByExpressions.get(0).isAsc();
- boolean canIterateInQueryOrder =
- asc ||
QueryOptionsUtils.isReverseOrderAllowed(_queryContext.getQueryOptions());
-
- // Projecting only the order-by expressions and fetching the rest for
the rows that survive the limit pays off
- // only when the scan cannot stop at the limit. The linear operator
stops as soon as the list builder is full,
- // which with a fully sorted order-by prefix is after limit+offset
documents, so there is nothing to save
- // there. The descending operator has to read the whole match set
because there is no descending cursor.
- boolean scanBoundedByLimit = canIterateInQueryOrder &&
sortedColumnsPrefixSize == orderByExpressions.size();
- boolean lateMaterialize = expressions.size() > numOrderByExpressions &&
!scanBoundedByLimit
- &&
QueryOptionsUtils.isLateMaterializeSortedOrderByEnabled(_queryContext.getQueryOptions());
-
- // Build the project operator once. Constructing one is not free -- it
resolves a DataSource and a
- // ColumnContext per selected column, and some implementations start
prefetching those columns -- so a
- // build-and-discard probe would read exactly the columns this is trying
to avoid reading.
- List<ExpressionContext> expressionsToProject =
- lateMaterialize ? expressions.subList(0, numOrderByExpressions) :
expressions;
BaseProjectOperator<?> projectOperator =
getSortedByProject(expressionsToProject, maxDocsPerCall,
orderByExpressions);
- // Remember that we cannot use asc == projectOperator.isAscending()
because empty operators are considered
- // both ascending and descending
- DocIdOrderedOperator.DocIdOrder queryOrder =
DocIdOrderedOperator.DocIdOrder.fromAsc(asc);
+ // Empty operators support both directions, so check compatibility with
the query order.
+ DocIdOrderedOperator.DocIdOrder queryOrder =
+
DocIdOrderedOperator.DocIdOrder.fromAsc(orderByExpressions.get(0).isAsc());
if (projectOperator.isCompatibleWith(queryOrder)) {
return new SelectionPartiallyOrderedByLinearOperator(_indexSegment,
_queryContext, expressions, projectOperator,
- sortedColumnsPrefixSize, lateMaterialize);
+ sortedColumnsPrefixSize);
} else {
return new SelectionPartiallyOrderedByDescOperation(_indexSegment,
_queryContext, expressions, projectOperator,
- sortedColumnsPrefixSize, lateMaterialize);
+ sortedColumnsPrefixSize);
}
}
- if (numOrderByExpressions == expressions.size()) {
- // All output expressions are ordered
- // ie: SELECT not_sorted1, not_sorted2 FROM Table WHERE ... ORDER BY
not_sorted1, not_sorted2 LIMIT 10 OFFSET 5
- BaseProjectOperator<?> projectOperator =
- new ProjectPlanNode(_segmentContext, _queryContext, expressions,
DocIdSetPlanNode.MAX_DOC_PER_CALL).run();
- return new SelectionOrderByOperator(_indexSegment, _queryContext,
expressions, projectOperator);
- }
- // Not all output expressions are ordered, only fetch the order-by
expressions and docId to avoid the
- // unnecessary data fetch
- // ie: SELECT ... FROM Table WHERE ... ORDER BY not_sorted1, not_sorted2
LIMIT 10
- List<ExpressionContext> expressionsToTransform = new
ArrayList<>(numOrderByExpressions);
- for (OrderByExpressionContext orderByExpression : orderByExpressions) {
- expressionsToTransform.add(orderByExpression.getExpression());
- }
- BaseProjectOperator<?> projectOperator = new
ProjectPlanNode(_segmentContext, _queryContext, expressionsToTransform,
+ BaseProjectOperator<?> projectOperator = new
ProjectPlanNode(_segmentContext, _queryContext, expressionsToProject,
DocIdSetPlanNode.MAX_DOC_PER_CALL).run();
return new SelectionOrderByOperator(_indexSegment, _queryContext,
expressions, projectOperator);
}
@@ -153,8 +117,7 @@ public class SelectionPlanNode implements PlanNode {
try {
return projectOperator.withOrder(DocIdOrderedOperator.DocIdOrder.DESC);
} catch (IllegalArgumentException | UnsupportedOperationException e) {
- // This happens when the operator cannot provide the required order
between blocks
- // Fallback to SelectionOrderByOperator
+ // Keep the ascending project operator for
SelectionPartiallyOrderedByDescOperation.
return projectOperator;
}
}
diff --git
a/pinot-core/src/test/java/org/apache/pinot/queries/InnerSegmentSelectionSingleValueQueriesTest.java
b/pinot-core/src/test/java/org/apache/pinot/queries/InnerSegmentSelectionSingleValueQueriesTest.java
index 1a35f440ef9..b333f786e9d 100644
---
a/pinot-core/src/test/java/org/apache/pinot/queries/InnerSegmentSelectionSingleValueQueriesTest.java
+++
b/pinot-core/src/test/java/org/apache/pinot/queries/InnerSegmentSelectionSingleValueQueriesTest.java
@@ -290,7 +290,7 @@ public class InnerSegmentSelectionSingleValueQueriesTest
extends BaseSingleValue
public void testSelectionOrderBySingleSortedColumnDescDidOrder() {
String orderBy = " ORDER BY column5 DESC";
BaseOperator<SelectionResultsBlock> selectionOrderByOperator = getOperator(
- "SET allowReverseOrder = false; SET lateMaterializeSortedOrderBy =
true; " + SELECTION_QUERY + orderBy);
+ "SET allowReverseOrder = false; " + SELECTION_QUERY + orderBy);
assertTrue(selectionOrderByOperator instanceof
SelectionPartiallyOrderedByDescOperation, "Expected: "
+ SelectionPartiallyOrderedByLinearOperator.class + ", found: " +
selectionOrderByOperator.getClass());
SelectionResultsBlock resultsBlock = selectionOrderByOperator.nextBlock();
@@ -312,17 +312,6 @@ public class InnerSegmentSelectionSingleValueQueriesTest
extends BaseSingleValue
assertEquals(lastRow[0], "gFuH");
}
- /// The optimization is opt-in, so the same query without the option must
still read every selected column
- /// for the whole match set. Pins the default; delete or invert this when
the default flips.
- @Test
- public void testSelectionOrderBySingleSortedColumnDescIsEagerByDefault() {
- BaseOperator<SelectionResultsBlock> operator =
- getOperator("SET allowReverseOrder = false; " + SELECTION_QUERY + "
ORDER BY column5 DESC");
- operator.nextBlock();
- // 30000 docs * 3 selected columns, as before late materialisation existed.
-
assertEquals(operator.getExecutionStatistics().getNumEntriesScannedPostFilter(),
90000L);
- }
-
@Test
public void testSelectionOrderBySingleSortedColumnDescReverseOrder() {
String orderBy = " ORDER BY column5 DESC";
@@ -738,79 +727,45 @@ public class InnerSegmentSelectionSingleValueQueriesTest
extends BaseSingleValue
assertEquals(brokerResponse.getRealtimeThreadMemAllocatedBytes(), 0);
}
- /// Runs `query` with late materialisation on and off and asserts the
answers are identical. column5 is the sorted
- /// column in this fixture.
- ///
- /// @param expectFewerEntries whether late materialisation should read fewer
entries post-filter. It should not when
- /// the scan already stops at the limit, which is
the case for the linear operator with a
- /// fully sorted order-by prefix.
- private void assertSameAnswerWithAndWithoutLateMaterialization(String query,
boolean expectFewerEntries) {
- BaseOperator<SelectionResultsBlock> lateOperator = getOperator("SET
lateMaterializeSortedOrderBy = true; " + query);
- SelectionResultsBlock lateBlock = lateOperator.nextBlock();
- ExecutionStatistics lateStats = lateOperator.getExecutionStatistics();
-
- BaseOperator<SelectionResultsBlock> eagerOperator =
- getOperator("SET lateMaterializeSortedOrderBy = false; " + query);
- SelectionResultsBlock eagerBlock = eagerOperator.nextBlock();
- ExecutionStatistics eagerStats = eagerOperator.getExecutionStatistics();
-
- DataSchema lateSchema = lateBlock.getDataSchema();
- DataSchema eagerSchema = eagerBlock.getDataSchema();
- assertEquals(lateSchema.getColumnNames(), eagerSchema.getColumnNames());
- assertEquals(lateSchema.getColumnDataTypes(),
eagerSchema.getColumnDataTypes());
-
- List<Object[]> lateRows = lateBlock.getRows();
- List<Object[]> eagerRows = eagerBlock.getRows();
- assertEquals(lateRows.size(), eagerRows.size());
- for (int i = 0; i < lateRows.size(); i++) {
- assertEquals(lateRows.get(i), eagerRows.get(i), "row " + i + " differs
for query: " + query);
- }
- assertEquals(lateStats.getNumDocsScanned(),
eagerStats.getNumDocsScanned());
-
- long late = lateStats.getNumEntriesScannedPostFilter();
- long eager = eagerStats.getNumEntriesScannedPostFilter();
- if (expectFewerEntries) {
- assertTrue(late < eager, "expected fewer entries post-filter, but read "
+ late + " vs " + eager
- + " for query: " + query);
- } else {
- assertEquals(late, eager, "expected the same entries post-filter for
query: " + query);
- }
- }
-
- /// The linear operator stops at the limit, so there is nothing to defer and
the plan keeps projecting everything.
- @Test
- public void testLateMaterializationSkippedWhenScanStopsAtLimit() {
- assertSameAnswerWithAndWithoutLateMaterialization(SELECTION_QUERY + "
ORDER BY column5", false);
- assertSameAnswerWithAndWithoutLateMaterialization(
- "SET allowReverseOrder = true; " + SELECTION_QUERY + " ORDER BY
column5 DESC", false);
- }
-
- /// Descending without a reverse cursor reads the whole match set, so
deferring the other columns is a real saving.
- @Test
- public void testLateMaterializationSortedColumnDesc() {
- assertSameAnswerWithAndWithoutLateMaterialization(
- "SET allowReverseOrder = false; " + SELECTION_QUERY + " ORDER BY
column5 DESC", true);
- }
-
- @Test
- public void testLateMaterializationWithOffsetAndLimit() {
- assertSameAnswerWithAndWithoutLateMaterialization(
- "SET allowReverseOrder = false; " + SELECTION_QUERY + " ORDER BY
column5 DESC LIMIT 5 OFFSET 3", true);
- }
-
- /// An unsorted tail in the order-by removes the limit bound on the scan, so
the linear operator benefits too. The
- /// unsorted order-by column is still projected during the scan; only the
remaining output columns are deferred.
@Test
public void testLateMaterializationWithUnsortedOrderBySuffix() {
- assertSameAnswerWithAndWithoutLateMaterialization(SELECTION_QUERY + "
ORDER BY column5, column6", true);
+ BaseOperator<SelectionResultsBlock> operator = getOperator(
+ "SELECT column5, column1, ADD(column1, 1) FROM testTable ORDER BY
column5, column6, column1");
+ assertTrue(operator instanceof SelectionPartiallyOrderedByLinearOperator);
+ SelectionResultsBlock block = operator.nextBlock();
+ assertEquals(block.getDataSchema().getColumnNames(),
+ new String[]{"column5", "column6", "column1", "add(column1,'1')"});
+ assertEquals(block.getDataSchema().getColumnDataTypes(),
+ new ColumnDataType[]{ColumnDataType.STRING, ColumnDataType.INT,
ColumnDataType.INT, ColumnDataType.DOUBLE});
+ List<Object[]> rows = block.getRows();
+ assertEquals(rows.size(), 10);
+ for (Object[] row : rows) {
+ assertEquals(row[3], ((Integer) row[2]).doubleValue() + 1);
+ }
+ assertEquals(rows.get(9), new Object[]{"gFuH", 6043515, 10542595,
10542596.0});
+ assertEquals(operator.getExecutionStatistics().getNumDocsScanned(),
30000L);
+ // The transform rereads column1 for the selected rows after scanning the
three order-by columns.
+
assertEquals(operator.getExecutionStatistics().getNumEntriesScannedPostFilter(),
90010L);
}
- /// The late fetch builds its own TransformOperator, so a non-order-by
expression that is a function must resolve,
- /// both for its values and for its data type in the schema.
@Test
public void testLateMaterializationWithTransformProjection() {
- assertSameAnswerWithAndWithoutLateMaterialization(
+ BaseOperator<SelectionResultsBlock> operator = getOperator(
"SET allowReverseOrder = false; SELECT column5, ADD(column1, 1),
column11 FROM testTable "
- + "ORDER BY column5 DESC", true);
+ + "ORDER BY column5 DESC, column1, column11 LIMIT 5 OFFSET 3");
+ assertTrue(operator instanceof SelectionPartiallyOrderedByDescOperation);
+ SelectionResultsBlock block = operator.nextBlock();
+ assertEquals(block.getDataSchema().getColumnNames(),
+ new String[]{"column5", "column1", "column11", "add(column1,'1')"});
+ assertEquals(block.getDataSchema().getColumnDataTypes(),
+ new ColumnDataType[]{ColumnDataType.STRING, ColumnDataType.INT,
ColumnDataType.STRING, ColumnDataType.DOUBLE});
+ List<Object[]> rows = block.getRows();
+ // Segment operators retain limit + offset rows; the broker applies the
offset.
+ assertEquals(rows.size(), 8);
+ for (Object[] row : rows) {
+ assertEquals(row[3], ((Integer) row[1]).doubleValue() + 1);
+ }
+ assertEquals(operator.getExecutionStatistics().getNumDocsScanned(),
30000L);
+
assertEquals(operator.getExecutionStatistics().getNumEntriesScannedPostFilter(),
90008L);
}
}
diff --git
a/pinot-core/src/test/java/org/apache/pinot/queries/NullHandlingEnabledQueriesTest.java
b/pinot-core/src/test/java/org/apache/pinot/queries/NullHandlingEnabledQueriesTest.java
index 61faccf6d37..c4a15643494 100644
---
a/pinot-core/src/test/java/org/apache/pinot/queries/NullHandlingEnabledQueriesTest.java
+++
b/pinot-core/src/test/java/org/apache/pinot/queries/NullHandlingEnabledQueriesTest.java
@@ -27,6 +27,10 @@ import java.util.Set;
import javax.annotation.Nullable;
import org.apache.commons.io.FileUtils;
import org.apache.pinot.common.response.broker.ResultTable;
+import org.apache.pinot.common.utils.DataSchema.ColumnDataType;
+import org.apache.pinot.core.operator.BaseOperator;
+import org.apache.pinot.core.operator.blocks.results.SelectionResultsBlock;
+import
org.apache.pinot.core.operator.query.SelectionPartiallyOrderedByDescOperation;
import org.apache.pinot.core.plan.DocIdSetPlanNode;
import
org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentLoader;
import org.apache.pinot.segment.local.indexsegment.mutable.MutableSegmentImpl;
@@ -2103,11 +2107,13 @@ public class NullHandlingEnabledQueriesTest extends
BaseQueriesTest {
_indexSegment.destroy();
}
- /// Late materialisation fetches the non-order-by columns in a second pass,
so their null bitmaps come from a
- /// different block than the order-by column's. An off-by-one there would
silently drop or invent nulls, and the
- /// answer must not depend on whether the optimization ran.
- @Test
- public void testLateMaterializationPreservesNullsInDeferredColumns()
+ @DataProvider
+ public Object[][] sortedOrderByDirections() {
+ return new Object[][]{{true, false}, {false, false}, {false, true}};
+ }
+
+ @Test(dataProvider = "sortedOrderByDirections")
+ public void testLateMaterializationPreservesNullsInDeferredColumns(boolean
ascending, boolean allowReverseOrder)
throws Exception {
initializeRows();
insertRowWithTwoColumns(1, null);
@@ -2118,22 +2124,51 @@ public class NullHandlingEnabledQueriesTest extends
BaseQueriesTest {
.addSingleValueDimension(COLUMN1, DataType.INT)
.addSingleValueDimension(COLUMN2, DataType.INT)
.build();
- // column1 is sorted and has no nulls, so the sorted-prefix path is used;
column2 is the deferred column
setUpSegments(TABLE_CONFIG_WITH_SORTED_COLUMN, schema);
- String query =
- String.format("SELECT %s, %s FROM testTable ORDER BY %s DESC LIMIT 8",
COLUMN1, COLUMN2, COLUMN1);
- List<Object[]> lateRows = getRows("SET lateMaterializeSortedOrderBy =
true; " + query);
- List<Object[]> eagerRows = getRows("SET lateMaterializeSortedOrderBy =
false; " + query);
+ String direction = ascending ? "ASC" : "DESC";
+ String query = String.format(
+ "SET allowReverseOrder = %s; SELECT %s, %s FROM testTable ORDER BY %s
%s LIMIT 8 OFFSET 4",
+ allowReverseOrder, COLUMN1, COLUMN2, COLUMN1, direction);
+ List<Object[]> rows = getRows(query);
+ assertEquals(rows.size(), 8);
+ for (int i = 0; i < rows.size(); i++) {
+ int value = ascending ? 2 + i / NUM_OF_SEGMENT_COPIES : 3 - i /
NUM_OF_SEGMENT_COPIES;
+ assertEquals(rows.get(i), new Object[]{value, value == 2 ? 20 : null});
+ }
+ }
+
+ @Test
+ public void testLateMaterializationAcrossBlocksWithSparseDocIds()
+ throws Exception {
+ initializeRows();
+ for (int i = 0; i < 22010; i++) {
+ insertRowWithTwoColumns(i, i % 3 == 0 ? null : i * 10);
+ }
+ Schema schema = schemaBuilder()
+ .addSingleValueDimension(COLUMN1, DataType.INT)
+ .addSingleValueDimension(COLUMN2, DataType.INT)
+ .build();
+ setUpSegments(TABLE_CONFIG_WITH_SORTED_COLUMN, schema);
- assertEquals(lateRows.size(), eagerRows.size());
- for (int i = 0; i < lateRows.size(); i++) {
- assertEquals(lateRows.get(i), eagerRows.get(i), "row " + i + " differs");
+ BaseOperator<SelectionResultsBlock> operator = getOperator(
+ "SET enableNullHandling = true; SET allowReverseOrder = false; "
+ + "SELECT column1, column2, ADD(column2, 1) FROM testTable WHERE
MOD(column1, 2) = 0 "
+ + "ORDER BY column1 DESC LIMIT 10001 OFFSET 3");
+ assertTrue(operator instanceof SelectionPartiallyOrderedByDescOperation);
+ SelectionResultsBlock block = operator.nextBlock();
+ assertEquals(block.getDataSchema().getColumnNames(), new
String[]{"column1", "column2", "add(column2,'1')"});
+ assertEquals(block.getDataSchema().getColumnDataTypes(),
+ new ColumnDataType[]{ColumnDataType.INT, ColumnDataType.INT,
ColumnDataType.DOUBLE});
+ List<Object[]> rows = block.getRows();
+ // Both the initial scan and the deferred fetch span the 10000-document
block boundary.
+ assertEquals(rows.size(), 10004);
+ for (int i = 0; i < rows.size(); i++) {
+ int value = 22008 - 2 * i;
+ assertEquals(rows.get(i),
+ new Object[]{value, value % 3 == 0 ? null : value * 10, value % 3 ==
0 ? null : value * 10.0 + 1});
}
- // The top rows are column1=4 (column2=40) across the segment copies, then
column1=3, whose column2 is null
- assertEquals(lateRows.get(0)[0], 4);
- assertEquals(lateRows.get(0)[1], 40);
- assertEquals(lateRows.get(lateRows.size() - 1)[0], 3);
- assertNull(lateRows.get(lateRows.size() - 1)[1], "the deferred column must
keep its null");
+ assertEquals(operator.getExecutionStatistics().getNumDocsScanned(),
11005L);
+
assertEquals(operator.getExecutionStatistics().getNumEntriesScannedPostFilter(),
11005L + 10004L);
}
}
diff --git
a/pinot-core/src/test/java/org/apache/pinot/queries/TextMatchTransformFunctionTest.java
b/pinot-core/src/test/java/org/apache/pinot/queries/TextMatchTransformFunctionTest.java
index 18c59ef2254..c237cafced8 100644
---
a/pinot-core/src/test/java/org/apache/pinot/queries/TextMatchTransformFunctionTest.java
+++
b/pinot-core/src/test/java/org/apache/pinot/queries/TextMatchTransformFunctionTest.java
@@ -351,14 +351,12 @@ public class TextMatchTransformFunctionTest {
+ "SELECT_PARTIAL_ORDER_BY_LINEAR("
+ "sortedList: (startTime), unsortedList: (), rest:
(case(is_null(agent),"
+
"'N/A',and(text_match(part,'_zz_'),is_not_null(part)),agent,''))) | 3 | 2\n"
- +
"TRANSFORM(case(is_null(agent),'N/A',and(text_match(part,'_zz_'),is_not_null(part)),agent,''),
"
- + "startTime) | 4 | 3\n"
- + "PROJECT(agent, startTime, part) | 5 | 4\n"
- + "DOC_ID_SET | 6 | 5\n"
- + "FILTER_AND | 7 | 6\n"
- +
"FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:customerId
= 'XYZ') | 8 | 7\n"
+ + "PROJECT(startTime) | 4 | 3\n"
+ + "DOC_ID_SET | 5 | 4\n"
+ + "FILTER_AND | 6 | 5\n"
+ +
"FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:customerId
= 'XYZ') | 7 | 6\n"
+
"FILTER_RANGE_INDEX(indexLookUp:range_index,operator:RANGE,predicate:startTime
BETWEEN '0' AND "
- + "'1000000') | 9 | 7"
+ + "'1000000') | 8 | 6"
).whenQuery(query)
.thenResultTextIs("val[STRING]\n"
+ "N/A\n"
diff --git
a/pinot-spi/src/main/java/org/apache/pinot/spi/utils/CommonConstants.java
b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/CommonConstants.java
index 6e935bb3cec..363490d95b6 100644
--- a/pinot-spi/src/main/java/org/apache/pinot/spi/utils/CommonConstants.java
+++ b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/CommonConstants.java
@@ -1058,16 +1058,6 @@ public class CommonConstants {
public static final String ALLOW_REVERSE_ORDER = "allowReverseOrder";
public static final boolean DEFAULT_ALLOW_REVERSE_ORDER = false;
- /// Selection with an order-by prefix on a sorted column: project only
the order-by expressions during the
- /// scan and fetch the remaining output expressions for the rows that
survive the limit. Enabling it
- /// avoids reading every selected column for the whole match set.
- ///
- /// Off by default while the optimization bakes, following
`allowReverseOrder` and
- /// `usePhysicalOptimizer`. It also changes the observable
`numEntriesScannedPostFilter` on the queries it
- /// applies to, so turning it on is a deliberate choice. Note the win
shrinks as `limit + offset`
- /// approaches the match-set size, where the second pass reads what
the scan would have read anyway.
- public static final String LATE_MATERIALIZE_SORTED_ORDER_BY =
"lateMaterializeSortedOrderBy";
- public static final boolean DEFAULT_LATE_MATERIALIZE_SORTED_ORDER_BY =
false;
public static final String MULTI_STAGE_LEAF_LIMIT =
"multiStageLeafLimit";
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]