Skip to content

Commit 2f08d7d

Browse files
authored
ESQL: Reorganize optimizer rules (#112338)
- Organize the optimizer rules consistently for all 4 optimizers (logical, physical, local logical, local physical). - Move helper methods meant for optimizer rules out of the optimizers into the relevant rules or into helper classes. - Consolidate the 2 nearly identical logical ParameterizedRules into one.
1 parent e54f46e commit 2f08d7d

File tree

93 files changed

+1862
-1613
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

93 files changed

+1862
-1613
lines changed

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Verifier.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
import static org.elasticsearch.xpack.esql.common.Failure.fail;
6262
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.FIRST;
6363
import static org.elasticsearch.xpack.esql.core.type.DataType.BOOLEAN;
64-
import static org.elasticsearch.xpack.esql.optimizer.LocalPhysicalPlanOptimizer.PushFiltersToSource.canPushToSource;
64+
import static org.elasticsearch.xpack.esql.optimizer.rules.physical.local.PushFiltersToSource.canPushToSource;
6565

6666
/**
6767
* This class is part of the planner. Responsible for failing impossible queries with a human-readable error message. In particular, this

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/LocalLogicalPlanOptimizer.java

Lines changed: 8 additions & 295 deletions
Large diffs are not rendered by default.

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/LocalPhysicalPlanOptimizer.java

Lines changed: 13 additions & 745 deletions
Large diffs are not rendered by default.

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizer.java

Lines changed: 54 additions & 298 deletions
Large diffs are not rendered by default.

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/LogicalVerifier.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@
99

1010
import org.elasticsearch.xpack.esql.capabilities.Validatable;
1111
import org.elasticsearch.xpack.esql.common.Failures;
12-
import org.elasticsearch.xpack.esql.optimizer.OptimizerRules.DependencyConsistency;
12+
import org.elasticsearch.xpack.esql.optimizer.rules.PlanConsistencyChecker;
1313
import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan;
1414

1515
public final class LogicalVerifier {
1616

17-
private static final DependencyConsistency<LogicalPlan> DEPENDENCY_CHECK = new DependencyConsistency<>();
17+
private static final PlanConsistencyChecker<LogicalPlan> DEPENDENCY_CHECK = new PlanConsistencyChecker<>();
1818
public static final LogicalVerifier INSTANCE = new LogicalVerifier();
1919

2020
private LogicalVerifier() {}

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/OptimizerRules.java

Lines changed: 0 additions & 53 deletions
This file was deleted.

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/PhysicalOptimizerRules.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import org.elasticsearch.xpack.esql.core.rule.ParameterizedRule;
1212
import org.elasticsearch.xpack.esql.core.rule.Rule;
1313
import org.elasticsearch.xpack.esql.core.util.ReflectionUtils;
14-
import org.elasticsearch.xpack.esql.optimizer.rules.OptimizerRules.TransformDirection;
14+
import org.elasticsearch.xpack.esql.optimizer.rules.logical.OptimizerRules.TransformDirection;
1515
import org.elasticsearch.xpack.esql.plan.physical.PhysicalPlan;
1616

1717
public class PhysicalOptimizerRules {

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizer.java

Lines changed: 1 addition & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -9,32 +9,16 @@
99

1010
import org.elasticsearch.xpack.esql.VerificationException;
1111
import org.elasticsearch.xpack.esql.common.Failure;
12-
import org.elasticsearch.xpack.esql.core.expression.Alias;
13-
import org.elasticsearch.xpack.esql.core.expression.Attribute;
14-
import org.elasticsearch.xpack.esql.core.expression.AttributeSet;
15-
import org.elasticsearch.xpack.esql.core.expression.Expressions;
16-
import org.elasticsearch.xpack.esql.core.expression.Literal;
1712
import org.elasticsearch.xpack.esql.core.rule.ParameterizedRuleExecutor;
18-
import org.elasticsearch.xpack.esql.core.rule.Rule;
1913
import org.elasticsearch.xpack.esql.core.rule.RuleExecutor;
20-
import org.elasticsearch.xpack.esql.core.tree.Source;
21-
import org.elasticsearch.xpack.esql.core.util.Holder;
22-
import org.elasticsearch.xpack.esql.plan.logical.Aggregate;
23-
import org.elasticsearch.xpack.esql.plan.logical.Eval;
24-
import org.elasticsearch.xpack.esql.plan.logical.Project;
25-
import org.elasticsearch.xpack.esql.plan.physical.ExchangeExec;
14+
import org.elasticsearch.xpack.esql.optimizer.rules.physical.ProjectAwayColumns;
2615
import org.elasticsearch.xpack.esql.plan.physical.FragmentExec;
2716
import org.elasticsearch.xpack.esql.plan.physical.PhysicalPlan;
28-
import org.elasticsearch.xpack.esql.plan.physical.UnaryExec;
2917

30-
import java.util.ArrayList;
3118
import java.util.Collection;
3219
import java.util.List;
3320

34-
import static java.lang.Boolean.FALSE;
35-
import static java.lang.Boolean.TRUE;
3621
import static java.util.Arrays.asList;
37-
import static java.util.Collections.singletonList;
3822

3923
/**
4024
* This class is part of the planner. Performs global (coordinator) optimization of the physical plan. Local (data-node) optimizations
@@ -70,64 +54,4 @@ static List<RuleExecutor.Batch<PhysicalPlan>> initializeRules(boolean isOptimize
7054
protected Iterable<RuleExecutor.Batch<PhysicalPlan>> batches() {
7155
return rules;
7256
}
73-
74-
/**
75-
* Adds an explicit project to minimize the amount of attributes sent from the local plan to the coordinator.
76-
* This is done here to localize the project close to the data source and simplify the upcoming field
77-
* extraction.
78-
*/
79-
static class ProjectAwayColumns extends Rule<PhysicalPlan, PhysicalPlan> {
80-
81-
@Override
82-
public PhysicalPlan apply(PhysicalPlan plan) {
83-
Holder<Boolean> keepTraversing = new Holder<>(TRUE);
84-
// Invariant: if we add a projection with these attributes after the current plan node, the plan remains valid
85-
// and the overall output will not change.
86-
Holder<AttributeSet> requiredAttributes = new Holder<>(plan.outputSet());
87-
88-
// This will require updating should we choose to have non-unary execution plans in the future.
89-
return plan.transformDown(UnaryExec.class, currentPlanNode -> {
90-
if (keepTraversing.get() == false) {
91-
return currentPlanNode;
92-
}
93-
if (currentPlanNode instanceof ExchangeExec exec) {
94-
keepTraversing.set(FALSE);
95-
var child = exec.child();
96-
// otherwise expect a Fragment
97-
if (child instanceof FragmentExec fragmentExec) {
98-
var logicalFragment = fragmentExec.fragment();
99-
100-
// no need for projection when dealing with aggs
101-
if (logicalFragment instanceof Aggregate == false) {
102-
List<Attribute> output = new ArrayList<>(requiredAttributes.get());
103-
// if all the fields are filtered out, it's only the count that matters
104-
// however until a proper fix (see https://github.com/elastic/elasticsearch/issues/98703)
105-
// add a synthetic field (so it doesn't clash with the user defined one) to return a constant
106-
// to avoid the block from being trimmed
107-
if (output.isEmpty()) {
108-
var alias = new Alias(logicalFragment.source(), "<all-fields-projected>", Literal.NULL, null, true);
109-
List<Alias> fields = singletonList(alias);
110-
logicalFragment = new Eval(logicalFragment.source(), logicalFragment, fields);
111-
output = Expressions.asAttributes(fields);
112-
}
113-
// add a logical projection (let the local replanning remove it if needed)
114-
FragmentExec newChild = new FragmentExec(
115-
Source.EMPTY,
116-
new Project(logicalFragment.source(), logicalFragment, output),
117-
fragmentExec.esFilter(),
118-
fragmentExec.estimatedRowSize(),
119-
fragmentExec.reducer()
120-
);
121-
return new ExchangeExec(exec.source(), output, exec.inBetweenAggs(), newChild);
122-
}
123-
}
124-
} else {
125-
AttributeSet childOutput = currentPlanNode.inputSet();
126-
AttributeSet addedAttributes = currentPlanNode.outputSet().subtract(childOutput);
127-
requiredAttributes.set(requiredAttributes.get().subtract(addedAttributes).combine(currentPlanNode.references()));
128-
}
129-
return currentPlanNode;
130-
});
131-
}
132-
}
13357
}

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/PhysicalVerifier.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import org.elasticsearch.xpack.esql.common.Failure;
1111
import org.elasticsearch.xpack.esql.core.expression.Attribute;
1212
import org.elasticsearch.xpack.esql.core.expression.Expressions;
13-
import org.elasticsearch.xpack.esql.optimizer.OptimizerRules.DependencyConsistency;
13+
import org.elasticsearch.xpack.esql.optimizer.rules.PlanConsistencyChecker;
1414
import org.elasticsearch.xpack.esql.plan.physical.FieldExtractExec;
1515
import org.elasticsearch.xpack.esql.plan.physical.PhysicalPlan;
1616

@@ -24,7 +24,7 @@
2424
public final class PhysicalVerifier {
2525

2626
public static final PhysicalVerifier INSTANCE = new PhysicalVerifier();
27-
private static final DependencyConsistency<PhysicalPlan> DEPENDENCY_CHECK = new DependencyConsistency<>();
27+
private static final PlanConsistencyChecker<PhysicalPlan> DEPENDENCY_CHECK = new PlanConsistencyChecker<>();
2828

2929
private PhysicalVerifier() {}
3030

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.esql.optimizer.rules;
9+
10+
import org.elasticsearch.xpack.esql.common.Failures;
11+
import org.elasticsearch.xpack.esql.core.expression.Attribute;
12+
import org.elasticsearch.xpack.esql.core.expression.AttributeSet;
13+
import org.elasticsearch.xpack.esql.core.expression.NameId;
14+
import org.elasticsearch.xpack.esql.plan.QueryPlan;
15+
16+
import java.util.HashSet;
17+
import java.util.Set;
18+
19+
import static org.elasticsearch.xpack.esql.common.Failure.fail;
20+
21+
public class PlanConsistencyChecker<P extends QueryPlan<P>> {
22+
23+
/**
24+
* Check whether a single {@link QueryPlan} produces no duplicate attributes and its children provide all of its required
25+
* {@link QueryPlan#references() references}. Otherwise, add
26+
* {@link org.elasticsearch.xpack.esql.common.Failure Failure}s to the {@link Failures} object.
27+
*/
28+
public void checkPlan(P p, Failures failures) {
29+
AttributeSet refs = p.references();
30+
AttributeSet input = p.inputSet();
31+
AttributeSet missing = refs.subtract(input);
32+
// TODO: for Joins, we should probably check if the required fields from the left child are actually in the left child, not
33+
// just any child (and analogously for the right child).
34+
if (missing.isEmpty() == false) {
35+
failures.add(fail(p, "Plan [{}] optimized incorrectly due to missing references {}", p.nodeString(), missing));
36+
}
37+
38+
Set<String> outputAttributeNames = new HashSet<>();
39+
Set<NameId> outputAttributeIds = new HashSet<>();
40+
for (Attribute outputAttr : p.output()) {
41+
if (outputAttributeNames.add(outputAttr.name()) == false || outputAttributeIds.add(outputAttr.id()) == false) {
42+
failures.add(
43+
fail(p, "Plan [{}] optimized incorrectly due to duplicate output attribute {}", p.nodeString(), outputAttr.toString())
44+
);
45+
}
46+
}
47+
}
48+
}

0 commit comments

Comments
 (0)