Skip to content

Commit 84d2dcb

Browse files
Address code review comments, add UTs
1 parent 66c126f commit 84d2dcb

File tree

2 files changed

+275
-68
lines changed

2 files changed

+275
-68
lines changed

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PushDownAndCombineFilters.java

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
import org.elasticsearch.xpack.esql.core.expression.FoldContext;
1717
import org.elasticsearch.xpack.esql.core.expression.Literal;
1818
import org.elasticsearch.xpack.esql.core.expression.ReferenceAttribute;
19-
import org.elasticsearch.xpack.esql.core.type.DataType;
2019
import org.elasticsearch.xpack.esql.core.util.CollectionUtils;
2120
import org.elasticsearch.xpack.esql.expression.predicate.Predicates;
21+
import org.elasticsearch.xpack.esql.optimizer.LogicalOptimizerContext;
2222
import org.elasticsearch.xpack.esql.plan.logical.Enrich;
2323
import org.elasticsearch.xpack.esql.plan.logical.Eval;
2424
import org.elasticsearch.xpack.esql.plan.logical.Filter;
@@ -48,9 +48,14 @@
4848
*
4949
* Also combines adjacent filters using a logical {@code AND}.
5050
*/
51-
public final class PushDownAndCombineFilters extends OptimizerRules.OptimizerRule<Filter> {
51+
public final class PushDownAndCombineFilters extends OptimizerRules.ParameterizedOptimizerRule<Filter, LogicalOptimizerContext> {
52+
53+
public PushDownAndCombineFilters() {
54+
super(OptimizerRules.TransformDirection.DOWN);
55+
}
56+
5257
@Override
53-
protected LogicalPlan rule(Filter filter) {
58+
protected LogicalPlan rule(Filter filter, LogicalOptimizerContext ctx) {
5459
LogicalPlan plan = filter;
5560
LogicalPlan child = filter.child();
5661
Expression condition = filter.condition();
@@ -103,7 +108,7 @@ protected LogicalPlan rule(Filter filter) {
103108
// TODO: could we do better here about pushing down filters for inlinestats?
104109
// See also https://github.com/elastic/elasticsearch/issues/127497
105110
// Push down past INLINESTATS if the condition is on the groupings
106-
return pushDownPastJoin(filter, join);
111+
return pushDownPastJoin(filter, join, ctx.foldCtx());
107112
}
108113
// cannot push past a Limit, this could change the tailing result set returned
109114
return plan;
@@ -130,7 +135,7 @@ private static ScopedFilter scopeFilter(List<Expression> filters, LogicalPlan le
130135
return new ScopedFilter(rest, leftFilters, rightFilters);
131136
}
132137

133-
private static LogicalPlan pushDownPastJoin(Filter filter, Join join) {
138+
private static LogicalPlan pushDownPastJoin(Filter filter, Join join, FoldContext foldCtx) {
134139
LogicalPlan plan = filter;
135140
// pushdown only through LEFT joins
136141
// TODO: generalize this for other join types
@@ -155,8 +160,10 @@ private static LogicalPlan pushDownPastJoin(Filter filter, Join join) {
155160
optimizationApplied = true;
156161
}
157162
// push the right scoped filter down to the right child
158-
if (scoped.rightFilters().isEmpty() == false && (join.right() instanceof Filter == false)) {
159-
List<Expression> rightPushableFilters = buildRightPushableFilters(scoped.rightFilters());
163+
// We don't want to execute this rule more than once per join, so we check if we
164+
// already pushed some filters to the right, and we only apply the rule if join.candidateRightHandFilters() is not empty
165+
if (scoped.rightFilters().isEmpty() == false && join.candidateRightHandFilters().isEmpty()) {
166+
List<Expression> rightPushableFilters = buildRightPushableFilters(scoped.rightFilters(), foldCtx);
160167
if (rightPushableFilters.isEmpty() == false) {
161168
join = join.withCandidateRightHandFilters(rightPushableFilters);
162169
optimizationApplied = true;
@@ -195,24 +202,24 @@ private static LogicalPlan pushDownPastJoin(Filter filter, Join join) {
195202
/**
196203
* Builds the right pushable filters for the given expressions.
197204
*/
198-
private static List<Expression> buildRightPushableFilters(List<Expression> expressions) {
199-
return expressions.stream().filter(x -> isRightPushableFilter(x)).toList();
205+
private static List<Expression> buildRightPushableFilters(List<Expression> expressions, FoldContext foldCtx) {
206+
return expressions.stream().filter(x -> isRightPushableFilter(x, foldCtx)).toList();
200207
}
201208

202209
/**
203210
* Determines if the given expression can be pushed down to the right side of a join.
204211
* A filter is right pushable if the filter's predicate evaluates to false or null when all fields are set to null
205212
*/
206-
private static boolean isRightPushableFilter(Expression filter) {
213+
private static boolean isRightPushableFilter(Expression filter, FoldContext foldCtx) {
207214
// traverse the filter tree
208215
// replace any reference to an attribute with a null literal
209-
Expression nullifiedFilter = filter.transformUp(Attribute.class, r -> new Literal(r.source(), null, DataType.NULL));
216+
Expression nullifiedFilter = filter.transformUp(Attribute.class, r -> new Literal(r.source(), null, r.dataType()));
210217
// try to fold the filter
211218
// check if the folded filter evaluates to false or null, if yes return true
212219
// pushable WHERE field > 1 (evaluates to null), WHERE field is NOT NULL (evaluates to false)
213-
// not pushable WHERE field is NULL (evaluates to true), WHERE coalesce(field, 10) = 10 (evaluates to true)
220+
// not pushable WHERE field is NULL (evaluates to true), WHERE coalesce(field, 10) == 10 (evaluates to true)
214221
if (nullifiedFilter.foldable()) {
215-
Object folded = nullifiedFilter.fold(FoldContext.small());
222+
Object folded = nullifiedFilter.fold(foldCtx);
216223
return folded == null || Boolean.FALSE.equals(folded);
217224
}
218225
return false;

0 commit comments

Comments
 (0)