Jackie-Jiang commented on code in PR #20035:
URL: https://github.com/apache/pinot/pull/20035#discussion_r4404338521
##########
pinot-core/src/main/java/org/apache/pinot/core/plan/maker/InstancePlanMakerImplV2.java:
##########
@@ -657,86 +661,125 @@ private static boolean
fitsByColumnUnionCardinalities(List<IndexSegment> segment
return true;
}
- /// In-place rewrite QueryContext based on the information from local
IndexSegment.
- ///
- /// @param queryContext
- /// @param indexSegment
+ /// Rewrites the shared context before fetch planning, using only columns
available on every selected segment.
+ /// A partially reloaded timestamp index must leave the original expression
executable on all segments.
+ /// Rewriting per segment would mutate the expressions shared by already
constructed segment plans.
@VisibleForTesting
- public static void rewriteQueryContextWithHints(QueryContext queryContext,
IndexSegment indexSegment) {
- Map<ExpressionContext, ExpressionContext> expressionOverrideHints =
queryContext.getExpressionOverrideHints();
- if (MapUtils.isEmpty(expressionOverrideHints)) {
+ static void rewriteQueryContextWithHints(QueryContext queryContext,
List<SegmentContext> segmentContexts) {
+ Map<ExpressionContext, ExpressionContext> hints =
queryContext.getExpressionOverrideHints();
+ if (MapUtils.isEmpty(hints) || segmentContexts.isEmpty()) {
return;
}
+ Map<ExpressionContext, ExpressionContext> applicableHints =
getApplicableHints(hints, segmentContexts);
+ if (!applicableHints.isEmpty()) {
+ rewriteQueryContextWithHints(queryContext, applicableHints);
+ }
+ }
- List<ExpressionContext> selectExpressions =
queryContext.getSelectExpressions();
- selectExpressions.replaceAll(
- expression -> overrideWithExpressionHints(expression, indexSegment,
expressionOverrideHints));
+ /// Makes a private copy so segment availability checks never mutate the
broker-provided hint map.
+ private static Map<ExpressionContext, ExpressionContext> getApplicableHints(
+ Map<ExpressionContext, ExpressionContext> hints, List<SegmentContext>
segmentContexts) {
+ Map<ExpressionContext, ExpressionContext> applicableHints = new
HashMap<>(hints);
+ applicableHints.values().removeIf(expression -> expression.getType() !=
ExpressionContext.Type.IDENTIFIER);
+ for (SegmentContext segmentContext : segmentContexts) {
+ Set<String> columns = segmentContext.getIndexSegment().getColumnNames();
+ applicableHints.values().removeIf(expression ->
!columns.contains(expression.getIdentifier()));
+ }
+ return applicableHints;
+ }
- List<Pair<AggregationFunction, FilterContext>> filtAggrFuns =
queryContext.getFilteredAggregationFunctions();
- if (filtAggrFuns != null) {
- for (Pair<AggregationFunction, FilterContext>
filteredAggregationFunction : filtAggrFuns) {
- FilterContext right = filteredAggregationFunction.getRight();
- if (right != null) {
- overrideWithExpressionHints(right, indexSegment,
expressionOverrideHints);
+ private static void rewriteQueryContextWithHints(QueryContext queryContext,
+ Map<ExpressionContext, ExpressionContext> hints) {
+ Set<FunctionContext> changedFunctions = Collections.newSetFromMap(new
IdentityHashMap<>());
+ // FILTER predicates have separate contexts; aggregation inputs are
visited through SELECT/HAVING/ORDER-BY.
+ List<Pair<AggregationFunction, FilterContext>> filteredAggregations =
+ queryContext.getFilteredAggregationFunctions();
+ if (filteredAggregations != null) {
+ for (Pair<AggregationFunction, FilterContext> aggregation :
filteredAggregations) {
+ if (aggregation.getRight() != null) {
+ overrideWithExpressionHints(aggregation.getRight(), hints,
changedFunctions);
}
}
}
-
+ queryContext.getSelectExpressions().replaceAll(
+ expression -> overrideWithExpressionHints(expression, hints,
changedFunctions));
List<ExpressionContext> groupByExpressions =
queryContext.getGroupByExpressions();
if (CollectionUtils.isNotEmpty(groupByExpressions)) {
groupByExpressions.replaceAll(
- expression -> overrideWithExpressionHints(expression, indexSegment,
expressionOverrideHints));
+ expression -> overrideWithExpressionHints(expression, hints,
changedFunctions));
}
-
List<OrderByExpressionContext> orderByExpressions =
queryContext.getOrderByExpressions();
if (CollectionUtils.isNotEmpty(orderByExpressions)) {
- orderByExpressions.replaceAll(expression -> new OrderByExpressionContext(
- overrideWithExpressionHints(expression.getExpression(),
indexSegment, expressionOverrideHints),
- expression.isAsc()));
+ orderByExpressions.replaceAll(orderBy -> {
+ ExpressionContext expression = orderBy.getExpression();
+ ExpressionContext rewritten = overrideWithExpressionHints(expression,
hints, changedFunctions);
+ return rewritten == expression ? orderBy
+ : new OrderByExpressionContext(rewritten, orderBy.isAsc(),
orderBy.isNullsLast());
+ });
}
-
- // In-place override
- FilterContext filter = queryContext.getFilter();
- if (filter != null) {
- overrideWithExpressionHints(filter, indexSegment,
expressionOverrideHints);
+ if (queryContext.getFilter() != null) {
+ overrideWithExpressionHints(queryContext.getFilter(), hints,
changedFunctions);
}
-
- // In-place override
- FilterContext havingFilter = queryContext.getHavingFilter();
- if (havingFilter != null) {
- overrideWithExpressionHints(havingFilter, indexSegment,
expressionOverrideHints);
+ if (queryContext.getHavingFilter() != null) {
+ overrideWithExpressionHints(queryContext.getHavingFilter(), hints,
changedFunctions);
+ }
+ if (!changedFunctions.isEmpty()) {
+ queryContext.prepareForExecution(changedFunctions);
}
}
@VisibleForTesting
public static void overrideWithExpressionHints(FilterContext filter,
IndexSegment indexSegment,
Map<ExpressionContext, ExpressionContext> expressionOverrideHints) {
+ overrideWithExpressionHints(filter,
+ getApplicableHints(expressionOverrideHints, List.of(new
SegmentContext(indexSegment))),
+ Collections.newSetFromMap(new IdentityHashMap<>()));
+ }
+
+ private static void overrideWithExpressionHints(FilterContext filter,
+ Map<ExpressionContext, ExpressionContext> expressionOverrideHints,
Set<FunctionContext> changedFunctions) {
+ if (filter.isConstant()) {
+ return;
+ }
if (filter.getChildren() != null) {
- // AND, OR, NOT
for (FilterContext child : filter.getChildren()) {
- overrideWithExpressionHints(child, indexSegment,
expressionOverrideHints);
+ overrideWithExpressionHints(child, expressionOverrideHints,
changedFunctions);
}
} else {
- // PREDICATE
Predicate predicate = filter.getPredicate();
- predicate.setLhs(overrideWithExpressionHints(predicate.getLhs(),
indexSegment, expressionOverrideHints));
+ predicate.setLhs(
+ overrideWithExpressionHints(predicate.getLhs(),
expressionOverrideHints, changedFunctions));
}
}
@VisibleForTesting
public static ExpressionContext
overrideWithExpressionHints(ExpressionContext expression, IndexSegment
indexSegment,
Map<ExpressionContext, ExpressionContext> expressionOverrideHints) {
+ return overrideWithExpressionHints(expression,
+ getApplicableHints(expressionOverrideHints, List.of(new
SegmentContext(indexSegment))),
+ Collections.newSetFromMap(new IdentityHashMap<>()));
+ }
+
+ private static ExpressionContext
overrideWithExpressionHints(ExpressionContext expression,
+ Map<ExpressionContext, ExpressionContext> expressionOverrideHints,
Set<FunctionContext> changedFunctions) {
if (expression.getType() != ExpressionContext.Type.FUNCTION) {
return expression;
}
ExpressionContext overrideExpression =
expressionOverrideHints.get(expression);
Review Comment:
A further optimization is to gate this structural lookup by candidate
function name/arity derived from the hint keys. `FunctionContext.hashCode()`
recursively hashes the argument tree, and the visitor then descends and hashes
each child subtree again. For a deep function chain this can produce quadratic
hashing work; ordinary enclosing SUM/ADD expressions cannot match a DATETRUNC
hint but still pay the lookup cost.
This pattern predates the PR, and moving rewriting to instance planning
reduces its frequency. A cheap candidate-signature check would avoid hashing
unrelated subtrees while preserving recursive traversal. Do not cache
structural hashes on these mutable expression contexts.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]