Skip to content

Commit 4d69b79

Browse files
Verify the layout is not changed during optimizations
1 parent 8bcb0c4 commit 4d69b79

File tree

10 files changed

+62
-27
lines changed

10 files changed

+62
-27
lines changed

x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/Attribute.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,4 +134,19 @@ public String nodeString() {
134134
}
135135

136136
protected abstract String label();
137+
138+
/**
139+
* Compares the size and datatypes of two lists of attributes for equality.
140+
*/
141+
public static boolean datatypeEquals(List<Attribute> left, List<Attribute> right) {
142+
if (left.size() != right.size()) {
143+
return false;
144+
}
145+
for (int i = 0; i < left.size(); i++) {
146+
if (left.get(i).dataType().equals(right.get(i).dataType()) == false) {
147+
return false;
148+
}
149+
}
150+
return true;
151+
}
137152
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ private static Batch<LogicalPlan> localOperators() {
8686

8787
public LogicalPlan localOptimize(LogicalPlan plan) {
8888
LogicalPlan optimized = execute(plan);
89-
Failures failures = verifier.verify(optimized, true);
89+
Failures failures = verifier.verify(optimized, true, plan);
9090
if (failures.hasFailures()) {
9191
throw new VerificationException(failures);
9292
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,15 @@ public LocalPhysicalPlanOptimizer(LocalPhysicalOptimizerContext context) {
4242
}
4343

4444
public PhysicalPlan localOptimize(PhysicalPlan plan) {
45-
return verify(execute(plan));
45+
return verify(execute(plan), plan);
4646
}
4747

48-
PhysicalPlan verify(PhysicalPlan plan) {
49-
Failures failures = verifier.verify(plan, true);
48+
PhysicalPlan verify(PhysicalPlan planAfter, PhysicalPlan planBefore) {
49+
Failures failures = verifier.verify(planAfter, true, planBefore);
5050
if (failures.hasFailures()) {
5151
throw new VerificationException(failures);
5252
}
53-
return plan;
53+
return planAfter;
5454
}
5555

5656
@Override

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ public LogicalPlanOptimizer(LogicalOptimizerContext optimizerContext) {
112112
public LogicalPlan optimize(LogicalPlan verified) {
113113
var optimized = execute(verified);
114114

115-
Failures failures = verifier.verify(optimized, false);
115+
Failures failures = verifier.verify(optimized, false, verified);
116116
if (failures.hasFailures()) {
117117
throw new VerificationException(failures);
118118
}

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,29 @@
1313
import org.elasticsearch.xpack.esql.plan.logical.Enrich;
1414
import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan;
1515

16+
import static org.elasticsearch.xpack.esql.common.Failure.fail;
17+
import static org.elasticsearch.xpack.esql.core.expression.Attribute.datatypeEquals;
18+
1619
public final class LogicalVerifier {
1720

1821
public static final LogicalVerifier INSTANCE = new LogicalVerifier();
1922

2023
private LogicalVerifier() {}
2124

2225
/** Verifies the optimized logical plan. */
23-
public Failures verify(LogicalPlan plan, boolean skipRemoteEnrichVerification) {
26+
public Failures verify(LogicalPlan planAfter, boolean skipRemoteEnrichVerification, LogicalPlan planBefore) {
2427
Failures failures = new Failures();
2528
Failures dependencyFailures = new Failures();
2629

2730
if (skipRemoteEnrichVerification) {
2831
// AwaitsFix https://github.com/elastic/elasticsearch/issues/118531
29-
var enriches = plan.collectFirstChildren(Enrich.class::isInstance);
32+
var enriches = planAfter.collectFirstChildren(Enrich.class::isInstance);
3033
if (enriches.isEmpty() == false && ((Enrich) enriches.get(0)).mode() == Enrich.Mode.REMOTE) {
3134
return failures;
3235
}
3336
}
3437

35-
plan.forEachUp(p -> {
38+
planAfter.forEachUp(p -> {
3639
PlanConsistencyChecker.checkPlan(p, dependencyFailures);
3740

3841
if (failures.hasFailures() == false) {
@@ -47,6 +50,12 @@ public Failures verify(LogicalPlan plan, boolean skipRemoteEnrichVerification) {
4750
}
4851
});
4952

53+
if (datatypeEquals(planBefore.output(), planAfter.output()) == false) {
54+
failures.add(
55+
fail(planAfter, "Layout has changed from [{}] to [{}]. ", planBefore.output().toString(), planAfter.output().toString())
56+
);
57+
}
58+
5059
if (dependencyFailures.hasFailures()) {
5160
throw new IllegalStateException(dependencyFailures.toString());
5261
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,15 @@ public PhysicalPlanOptimizer(PhysicalOptimizerContext context) {
3434
}
3535

3636
public PhysicalPlan optimize(PhysicalPlan plan) {
37-
return verify(execute(plan));
37+
return verify(execute(plan), plan);
3838
}
3939

40-
PhysicalPlan verify(PhysicalPlan plan) {
41-
Failures failures = verifier.verify(plan, false);
40+
PhysicalPlan verify(PhysicalPlan planAfter, PhysicalPlan planBefore) {
41+
Failures failures = verifier.verify(planAfter, false, planBefore);
4242
if (failures.hasFailures()) {
4343
throw new VerificationException(failures);
4444
}
45-
return plan;
45+
return planAfter;
4646
}
4747

4848
@Override

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@
1414
import org.elasticsearch.xpack.esql.optimizer.rules.PlanConsistencyChecker;
1515
import org.elasticsearch.xpack.esql.plan.logical.Enrich;
1616
import org.elasticsearch.xpack.esql.plan.physical.EnrichExec;
17+
import org.elasticsearch.xpack.esql.plan.physical.EsQueryExec;
1718
import org.elasticsearch.xpack.esql.plan.physical.FieldExtractExec;
1819
import org.elasticsearch.xpack.esql.plan.physical.PhysicalPlan;
1920

21+
import static org.elasticsearch.index.IndexMode.LOOKUP;
2022
import static org.elasticsearch.xpack.esql.common.Failure.fail;
23+
import static org.elasticsearch.xpack.esql.core.expression.Attribute.datatypeEquals;
2124

2225
/** Physical plan verifier. */
2326
public final class PhysicalVerifier {
@@ -27,19 +30,19 @@ public final class PhysicalVerifier {
2730
private PhysicalVerifier() {}
2831

2932
/** Verifies the physical plan. */
30-
public Failures verify(PhysicalPlan plan, boolean skipRemoteEnrichVerification) {
33+
public Failures verify(PhysicalPlan planAfter, boolean skipRemoteEnrichVerification, PhysicalPlan planBefore) {
3134
Failures failures = new Failures();
3235
Failures depFailures = new Failures();
3336

3437
if (skipRemoteEnrichVerification) {
3538
// AwaitsFix https://github.com/elastic/elasticsearch/issues/118531
36-
var enriches = plan.collectFirstChildren(EnrichExec.class::isInstance);
39+
var enriches = planAfter.collectFirstChildren(EnrichExec.class::isInstance);
3740
if (enriches.isEmpty() == false && ((EnrichExec) enriches.get(0)).mode() == Enrich.Mode.REMOTE) {
3841
return failures;
3942
}
4043
}
4144

42-
plan.forEachDown(p -> {
45+
planAfter.forEachDown(p -> {
4346
if (p instanceof FieldExtractExec fieldExtractExec) {
4447
Attribute sourceAttribute = fieldExtractExec.sourceAttribute();
4548
if (sourceAttribute == null) {
@@ -67,6 +70,14 @@ public Failures verify(PhysicalPlan plan, boolean skipRemoteEnrichVerification)
6770
}
6871
});
6972

73+
if (datatypeEquals(planBefore.output(), planAfter.output()) == false) {
74+
if ((planAfter instanceof EsQueryExec esQueryExec && esQueryExec.indexMode() == LOOKUP) == false) {
75+
failures.add(
76+
fail(planAfter, "Layout has changed from [{}] to [{}]. ", planBefore.output().toString(), planAfter.output().toString())
77+
);
78+
}
79+
}
80+
7081
if (depFailures.hasFailures()) {
7182
throw new IllegalStateException(depFailures.toString());
7283
}

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/rule/RuleExecutor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ protected final ExecutionInfo executeWithInfo(TreeType plan) {
175175
if (tf.hasChanged()) {
176176
hasChanged = true;
177177
if (log.isTraceEnabled()) {
178-
log.trace("Rule {} applied\n{}", rule, NodeUtils.diffString(tf.before, tf.after));
178+
log.trace("Rule {} applied with change\n{}", rule, NodeUtils.diffString(tf.before, tf.after));
179179
}
180180
} else {
181181
if (log.isTraceEnabled()) {

x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5560,7 +5560,7 @@ public void testPushShadowingGeneratingPlanPastProject() {
55605560
List<Attribute> initialGeneratedExprs = ((GeneratingPlan) initialPlan).generatedAttributes();
55615561
LogicalPlan optimizedPlan = testCase.rule.apply(initialPlan);
55625562

5563-
Failures inconsistencies = LogicalVerifier.INSTANCE.verify(optimizedPlan, false);
5563+
Failures inconsistencies = LogicalVerifier.INSTANCE.verify(optimizedPlan, false, initialPlan);
55645564
assertFalse(inconsistencies.hasFailures());
55655565

55665566
Project project = as(optimizedPlan, Project.class);
@@ -5611,7 +5611,7 @@ public void testPushShadowingGeneratingPlanPastRenamingProject() {
56115611
List<Attribute> initialGeneratedExprs = ((GeneratingPlan) initialPlan).generatedAttributes();
56125612
LogicalPlan optimizedPlan = testCase.rule.apply(initialPlan);
56135613

5614-
Failures inconsistencies = LogicalVerifier.INSTANCE.verify(optimizedPlan, false);
5614+
Failures inconsistencies = LogicalVerifier.INSTANCE.verify(optimizedPlan, false, initialPlan);
56155615
assertFalse(inconsistencies.hasFailures());
56165616

56175617
Project project = as(optimizedPlan, Project.class);
@@ -5667,7 +5667,7 @@ public void testPushShadowingGeneratingPlanPastRenamingProjectWithResolution() {
56675667

56685668
// This ensures that our generating plan doesn't use invalid references, resp. that any rename from the Project has
56695669
// been propagated into the generating plan.
5670-
Failures inconsistencies = LogicalVerifier.INSTANCE.verify(optimizedPlan, false);
5670+
Failures inconsistencies = LogicalVerifier.INSTANCE.verify(optimizedPlan, false, initialPlan);
56715671
assertFalse(inconsistencies.hasFailures());
56725672

56735673
Project project = as(optimizedPlan, Project.class);

x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2866,7 +2866,7 @@ public void testFieldExtractWithoutSourceAttributes() {
28662866
)
28672867
);
28682868

2869-
var e = expectThrows(VerificationException.class, () -> physicalPlanOptimizer.verify(badPlan));
2869+
var e = expectThrows(VerificationException.class, () -> physicalPlanOptimizer.verify(badPlan, verifiedPlan));
28702870
assertThat(
28712871
e.getMessage(),
28722872
containsString(
@@ -2881,7 +2881,7 @@ public void testVerifierOnMissingReferences() {
28812881
| stats s = sum(salary) by emp_no
28822882
| where emp_no > 10
28832883
""");
2884-
2884+
final var planBeforeModification = plan;
28852885
plan = plan.transformUp(
28862886
AggregateExec.class,
28872887
a -> new AggregateExec(
@@ -2895,7 +2895,7 @@ public void testVerifierOnMissingReferences() {
28952895
)
28962896
);
28972897
final var finalPlan = plan;
2898-
var e = expectThrows(IllegalStateException.class, () -> physicalPlanOptimizer.verify(finalPlan));
2898+
var e = expectThrows(IllegalStateException.class, () -> physicalPlanOptimizer.verify(finalPlan, planBeforeModification));
28992899
assertThat(e.getMessage(), containsString(" > 10[INTEGER]]] optimized incorrectly due to missing references [emp_no{f}#"));
29002900
}
29012901

@@ -2913,7 +2913,7 @@ public void testVerifierOnMissingReferencesWithBinaryPlans() throws Exception {
29132913

29142914
var planWithInvalidJoinLeftSide = plan.transformUp(LookupJoinExec.class, join -> join.replaceChildren(join.right(), join.right()));
29152915

2916-
var e = expectThrows(IllegalStateException.class, () -> physicalPlanOptimizer.verify(planWithInvalidJoinLeftSide));
2916+
var e = expectThrows(IllegalStateException.class, () -> physicalPlanOptimizer.verify(planWithInvalidJoinLeftSide, plan));
29172917
assertThat(e.getMessage(), containsString(" optimized incorrectly due to missing references from left hand side [languages"));
29182918

29192919
var planWithInvalidJoinRightSide = plan.transformUp(
@@ -2930,7 +2930,7 @@ public void testVerifierOnMissingReferencesWithBinaryPlans() throws Exception {
29302930
)
29312931
);
29322932

2933-
e = expectThrows(IllegalStateException.class, () -> physicalPlanOptimizer.verify(planWithInvalidJoinRightSide));
2933+
e = expectThrows(IllegalStateException.class, () -> physicalPlanOptimizer.verify(planWithInvalidJoinRightSide, plan));
29342934
assertThat(e.getMessage(), containsString(" optimized incorrectly due to missing references from right hand side [language_code"));
29352935
}
29362936

@@ -2940,7 +2940,7 @@ public void testVerifierOnDuplicateOutputAttributes() {
29402940
| stats s = sum(salary) by emp_no
29412941
| where emp_no > 10
29422942
""");
2943-
2943+
final var planBeforeModification = plan;
29442944
plan = plan.transformUp(AggregateExec.class, a -> {
29452945
List<Attribute> intermediates = new ArrayList<>(a.intermediateAttributes());
29462946
intermediates.add(intermediates.get(0));
@@ -2955,7 +2955,7 @@ public void testVerifierOnDuplicateOutputAttributes() {
29552955
);
29562956
});
29572957
final var finalPlan = plan;
2958-
var e = expectThrows(IllegalStateException.class, () -> physicalPlanOptimizer.verify(finalPlan));
2958+
var e = expectThrows(IllegalStateException.class, () -> physicalPlanOptimizer.verify(finalPlan, planBeforeModification));
29592959
assertThat(
29602960
e.getMessage(),
29612961
containsString("Plan [LimitExec[1000[INTEGER],null]] optimized incorrectly due to duplicate output attribute emp_no{f}#")

0 commit comments

Comments
 (0)