Skip to content

Commit bd6b731

Browse files
Address some of the code review comments
1 parent 68a4262 commit bd6b731

File tree

4 files changed

+73
-1
lines changed

4 files changed

+73
-1
lines changed

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
@@ -88,7 +88,7 @@ public LogicalPlan localOptimize(LogicalPlan plan) {
8888
LogicalPlan optimized = execute(plan);
8989
Failures failures = verifier.verify(optimized);
9090
if (failures.hasFailures()) {
91-
throw new VerificationException("Plan after optimize not valid: " + failures + " for plan: " + plan.nodeString());
91+
throw new VerificationException(failures);
9292
}
9393
return optimized;
9494
}

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,16 @@
2323
import org.elasticsearch.xpack.esql.core.expression.FieldAttribute;
2424
import org.elasticsearch.xpack.esql.core.expression.FoldContext;
2525
import org.elasticsearch.xpack.esql.core.expression.Literal;
26+
import org.elasticsearch.xpack.esql.core.expression.NamedExpression;
2627
import org.elasticsearch.xpack.esql.core.expression.ReferenceAttribute;
2728
import org.elasticsearch.xpack.esql.core.tree.NodeInfo;
2829
import org.elasticsearch.xpack.esql.core.tree.Source;
2930
import org.elasticsearch.xpack.esql.core.type.DataType;
3031
import org.elasticsearch.xpack.esql.core.type.EsField;
3132
import org.elasticsearch.xpack.esql.core.type.InvalidMappedField;
33+
import org.elasticsearch.xpack.esql.expression.Order;
3234
import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry;
35+
import org.elasticsearch.xpack.esql.expression.function.aggregate.Min;
3336
import org.elasticsearch.xpack.esql.expression.function.scalar.conditional.Case;
3437
import org.elasticsearch.xpack.esql.expression.function.scalar.nulls.Coalesce;
3538
import org.elasticsearch.xpack.esql.expression.function.scalar.string.StartsWith;
@@ -49,6 +52,7 @@
4952
import org.elasticsearch.xpack.esql.plan.logical.Limit;
5053
import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan;
5154
import org.elasticsearch.xpack.esql.plan.logical.MvExpand;
55+
import org.elasticsearch.xpack.esql.plan.logical.OrderBy;
5256
import org.elasticsearch.xpack.esql.plan.logical.Project;
5357
import org.elasticsearch.xpack.esql.plan.logical.Row;
5458
import org.elasticsearch.xpack.esql.plan.logical.UnaryPlan;
@@ -64,6 +68,7 @@
6468
import java.util.Map;
6569
import java.util.Set;
6670

71+
import static java.util.Arrays.asList;
6772
import static java.util.Collections.emptyMap;
6873
import static org.elasticsearch.xpack.esql.EsqlTestUtils.L;
6974
import static org.elasticsearch.xpack.esql.EsqlTestUtils.ONE;
@@ -84,6 +89,7 @@
8489
import static org.elasticsearch.xpack.esql.EsqlTestUtils.withDefaultLimitWarning;
8590
import static org.elasticsearch.xpack.esql.core.tree.Source.EMPTY;
8691
import static org.hamcrest.Matchers.contains;
92+
import static org.hamcrest.Matchers.containsString;
8793
import static org.hamcrest.Matchers.equalTo;
8894
import static org.hamcrest.Matchers.hasSize;
8995
import static org.hamcrest.Matchers.is;
@@ -774,6 +780,32 @@ public void testGroupingByMissingFields() {
774780
as(eval.child(), EsRelation.class);
775781
}
776782

783+
public void testPlanSanityCheck() throws Exception {
784+
var plan = localPlan("""
785+
from test
786+
| stats a = min(salary) by emp_no
787+
""");
788+
789+
var limit = as(plan, Limit.class);
790+
var aggregate = as(limit.child(), Aggregate.class);
791+
var min = as(Alias.unwrap(aggregate.aggregates().get(0)), Min.class);
792+
var salary = as(min.field(), NamedExpression.class);
793+
assertThat(salary.name(), is("salary"));
794+
// emulate a rule that adds an invalid field
795+
var invalidPlan = new OrderBy(
796+
limit.source(),
797+
limit,
798+
asList(new Order(limit.source(), salary, Order.OrderDirection.ASC, Order.NullsPosition.FIRST))
799+
);
800+
801+
var localContext = new LocalLogicalOptimizerContext(EsqlTestUtils.TEST_CFG, FoldContext.small(), TEST_SEARCH_STATS);
802+
LocalLogicalPlanOptimizer localLogicalPlanOptimizer = new LocalLogicalPlanOptimizer(localContext);
803+
804+
IllegalStateException e = expectThrows(IllegalStateException.class, () -> localLogicalPlanOptimizer.localOptimize(invalidPlan));
805+
assertThat(e.getMessage(), containsString("Plan [OrderBy[[Order[salary"));
806+
assertThat(e.getMessage(), containsString(" optimized incorrectly due to missing references [salary"));
807+
}
808+
777809
private IsNotNull isNotNull(Expression field) {
778810
return new IsNotNull(EMPTY, field);
779811
}

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,11 @@
5252
import org.elasticsearch.xpack.esql.core.type.MultiTypeEsField;
5353
import org.elasticsearch.xpack.esql.core.util.Holder;
5454
import org.elasticsearch.xpack.esql.enrich.ResolvedEnrichPolicy;
55+
import org.elasticsearch.xpack.esql.expression.Order;
5556
import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry;
5657
import org.elasticsearch.xpack.esql.expression.function.UnsupportedAttribute;
5758
import org.elasticsearch.xpack.esql.expression.function.aggregate.Count;
59+
import org.elasticsearch.xpack.esql.expression.function.aggregate.Min;
5860
import org.elasticsearch.xpack.esql.expression.function.fulltext.FullTextFunction;
5961
import org.elasticsearch.xpack.esql.expression.function.fulltext.Kql;
6062
import org.elasticsearch.xpack.esql.expression.function.fulltext.Match;
@@ -131,8 +133,12 @@
131133
import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.indexWithDateDateNanosUnionType;
132134
import static org.elasticsearch.xpack.esql.core.querydsl.query.Query.unscore;
133135
import static org.elasticsearch.xpack.esql.core.type.DataType.DATE_NANOS;
136+
import static org.elasticsearch.xpack.esql.core.type.DataType.INTEGER;
137+
import static org.elasticsearch.xpack.esql.core.util.TestUtils.getFieldAttribute;
138+
import static org.elasticsearch.xpack.esql.plan.physical.AbstractPhysicalPlanSerializationTests.randomEstimatedRowSize;
134139
import static org.elasticsearch.xpack.esql.plan.physical.EsStatsQueryExec.StatsType;
135140
import static org.hamcrest.Matchers.contains;
141+
import static org.hamcrest.Matchers.containsString;
136142
import static org.hamcrest.Matchers.equalTo;
137143
import static org.hamcrest.Matchers.hasSize;
138144
import static org.hamcrest.Matchers.instanceOf;
@@ -2056,6 +2062,27 @@ public void testToDateNanosPushDown() {
20562062
assertThat(expected.toString(), is(esQuery.query().toString()));
20572063
}
20582064

2065+
public void testVerifierOnMissingReferencesWithBinaryPlans() throws Exception {
2066+
2067+
PhysicalPlan plan = plannerOptimizer.plan("""
2068+
from test
2069+
| stats a = min(salary) by emp_no
2070+
""");
2071+
2072+
var limit = as(plan, LimitExec.class);
2073+
var aggregate = as(limit.child(), AggregateExec.class);
2074+
var min = as(Alias.unwrap(aggregate.aggregates().get(0)), Min.class);
2075+
var salary = as(min.field(), NamedExpression.class);
2076+
assertThat(salary.name(), is("salary"));
2077+
// emulate a rule that adds a missing attribute
2078+
FieldAttribute missingAttr = getFieldAttribute("missing attr");
2079+
List<Order> orders = List.of(new Order(plan.source(), missingAttr, Order.OrderDirection.ASC, Order.NullsPosition.FIRST));
2080+
TopNExec topNExec = new TopNExec(plan.source(), plan, orders, new Literal(Source.EMPTY, limit, INTEGER), randomEstimatedRowSize());
2081+
2082+
Exception e = expectThrows(IllegalStateException.class, () -> plannerOptimizer.localPhysicalVerify(topNExec));
2083+
assertThat(e.getMessage(), containsString(" optimized incorrectly due to missing references [missing attr"));
2084+
}
2085+
20592086
private boolean isMultiTypeEsField(Expression e) {
20602087
return e instanceof FieldAttribute fa && fa.field() instanceof MultiTypeEsField;
20612088
}

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ public class TestPlannerOptimizer {
2323
private final Analyzer analyzer;
2424
private final LogicalPlanOptimizer logicalOptimizer;
2525
private final PhysicalPlanOptimizer physicalPlanOptimizer;
26+
private final LocalPhysicalPlanOptimizer localPhysicalPlanOptimizer;
27+
2628
private final Mapper mapper;
2729
private final Configuration config;
2830

@@ -38,6 +40,9 @@ public TestPlannerOptimizer(Configuration config, Analyzer analyzer, LogicalPlan
3840
parser = new EsqlParser();
3941
physicalPlanOptimizer = new PhysicalPlanOptimizer(new PhysicalOptimizerContext(config));
4042
mapper = new Mapper();
43+
localPhysicalPlanOptimizer = new LocalPhysicalPlanOptimizer(
44+
new LocalPhysicalOptimizerContext(config, FoldContext.small(), SearchStats.EMPTY)
45+
);
4146

4247
}
4348

@@ -84,4 +89,12 @@ private PhysicalPlan physicalPlan(String query, Analyzer analyzer) {
8489
var physical = mapper.map(logical);
8590
return physical;
8691
}
92+
93+
public PhysicalPlan localPhysicalOptimize(PhysicalPlan plan) {
94+
return localPhysicalPlanOptimizer.localOptimize(plan);
95+
}
96+
97+
public PhysicalPlan localPhysicalVerify(PhysicalPlan plan) {
98+
return localPhysicalPlanOptimizer.verify(plan);
99+
}
87100
}

0 commit comments

Comments
 (0)