-
Notifications
You must be signed in to change notification settings - Fork 25.2k
ESQL: Push down filter passed lookup join #118410
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e8ffd92
5010855
11a53ce
659e090
e8cbe94
b6ab3fb
ce4dfd0
642464b
bd9a510
e9bab98
f03ff19
62dee1b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
pr: 118410 | ||
summary: Push down filter passed lookup join | ||
area: ES|QL | ||
type: enhancement | ||
issues: [] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,7 @@ | |
import org.elasticsearch.xpack.esql.core.expression.Expressions; | ||
import org.elasticsearch.xpack.esql.core.expression.ReferenceAttribute; | ||
import org.elasticsearch.xpack.esql.core.expression.predicate.Predicates; | ||
import org.elasticsearch.xpack.esql.core.util.CollectionUtils; | ||
import org.elasticsearch.xpack.esql.plan.logical.Enrich; | ||
import org.elasticsearch.xpack.esql.plan.logical.Eval; | ||
import org.elasticsearch.xpack.esql.plan.logical.Filter; | ||
|
@@ -23,6 +24,8 @@ | |
import org.elasticsearch.xpack.esql.plan.logical.Project; | ||
import org.elasticsearch.xpack.esql.plan.logical.RegexExtract; | ||
import org.elasticsearch.xpack.esql.plan.logical.UnaryPlan; | ||
import org.elasticsearch.xpack.esql.plan.logical.join.Join; | ||
import org.elasticsearch.xpack.esql.plan.logical.join.JoinTypes; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
@@ -76,11 +79,63 @@ protected LogicalPlan rule(Filter filter) { | |
} else if (child instanceof OrderBy orderBy) { | ||
// swap the filter with its child | ||
plan = orderBy.replaceChild(filter.with(orderBy.child(), condition)); | ||
} else if (child instanceof Join join) { | ||
return pushDownPastJoin(filter, join); | ||
} | ||
// cannot push past a Limit, this could change the tailing result set returned | ||
return plan; | ||
} | ||
|
||
private record ScopedFilter(List<Expression> commonFilters, List<Expression> leftFilters, List<Expression> rightFilters) {} | ||
|
||
// split the filter condition in 3 parts: | ||
// 1. filter scoped to the left | ||
// 2. filter scoped to the right | ||
// 3. filter that requires both sides to be evaluated | ||
private static ScopedFilter scopeFilter(List<Expression> filters, LogicalPlan left, LogicalPlan right) { | ||
List<Expression> rest = new ArrayList<>(filters); | ||
List<Expression> leftFilters = new ArrayList<>(); | ||
List<Expression> rightFilters = new ArrayList<>(); | ||
|
||
AttributeSet leftOutput = left.outputSet(); | ||
AttributeSet rightOutput = right.outputSet(); | ||
|
||
// first remove things that are left scoped only | ||
rest.removeIf(f -> f.references().subsetOf(leftOutput) && leftFilters.add(f)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Funky use of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ➕ |
||
// followed by right scoped only | ||
rest.removeIf(f -> f.references().subsetOf(rightOutput) && rightFilters.add(f)); | ||
return new ScopedFilter(rest, leftFilters, rightFilters); | ||
} | ||
|
||
private static LogicalPlan pushDownPastJoin(Filter filter, Join join) { | ||
LogicalPlan plan = filter; | ||
// pushdown only through LEFT joins | ||
// TODO: generalize this for other join types | ||
if (join.config().type() == JoinTypes.LEFT) { | ||
LogicalPlan left = join.left(); | ||
LogicalPlan right = join.right(); | ||
|
||
// split the filter condition in 3 parts: | ||
// 1. filter scoped to the left | ||
// 2. filter scoped to the right | ||
// 3. filter that requires both sides to be evaluated | ||
Comment on lines
+118
to
+121
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: I don't think this duplicated comment here is necessary, since the method itself has the same javadoc. |
||
ScopedFilter scoped = scopeFilter(Predicates.splitAnd(filter.condition()), left, right); | ||
// push the left scoped filter down to the left child, keep the rest intact | ||
if (scoped.leftFilters.size() > 0) { | ||
// push the filter down to the left child | ||
left = new Filter(left.source(), left, Predicates.combineAnd(scoped.leftFilters)); | ||
// update the join with the new left child | ||
join = (Join) join.replaceLeft(left); | ||
|
||
// keep the remaining filters in place, otherwise return the new join; | ||
Expression remainingFilter = Predicates.combineAnd(CollectionUtils.combine(scoped.commonFilters, scoped.rightFilters)); | ||
plan = remainingFilter != null ? filter.with(join, remainingFilter) : join; | ||
} | ||
} | ||
// ignore the rest of the join | ||
return plan; | ||
} | ||
|
||
private static Function<Expression, Expression> NO_OP = expression -> expression; | ||
|
||
private static LogicalPlan maybePushDownPastUnary( | ||
|
Uh oh!
There was an error while loading. Please reload this page.