Skip to content

Commit 2f10f4f

Browse files
[backport] Fix ClassCastException with MV_EXPAND on missing field (#110096) (#110264)
1 parent 6253c58 commit 2f10f4f

File tree

2 files changed

+40
-0
lines changed

2 files changed

+40
-0
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.elasticsearch.xpack.esql.expression.function.scalar.nulls.Coalesce;
1717
import org.elasticsearch.xpack.esql.optimizer.LogicalPlanOptimizer.PropagateEmptyRelation;
1818
import org.elasticsearch.xpack.esql.plan.logical.Eval;
19+
import org.elasticsearch.xpack.esql.plan.logical.MvExpand;
1920
import org.elasticsearch.xpack.esql.plan.logical.TopN;
2021
import org.elasticsearch.xpack.esql.planner.AbstractPhysicalOperationProviders;
2122
import org.elasticsearch.xpack.esql.planner.PlannerUtils;
@@ -158,6 +159,12 @@ else if (plan instanceof Project project) {
158159
plan = new Eval(project.source(), project.child(), new ArrayList<>(nullLiteral.values()));
159160
plan = new Project(project.source(), plan, newProjections);
160161
}
162+
} else if (plan instanceof MvExpand) {
163+
// We cannot replace the target (NamedExpression) with a Literal
164+
// https://github.com/elastic/elasticsearch/issues/109974
165+
// Unfortunately we cannot remove the MvExpand right away, or we'll lose the output field (layout problems)
166+
// TODO but this could be a follow-up optimization
167+
return plan;
161168
}
162169
// otherwise transform fields in place
163170
else {

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.Add;
1818
import org.elasticsearch.xpack.esql.parser.EsqlParser;
1919
import org.elasticsearch.xpack.esql.plan.logical.Eval;
20+
import org.elasticsearch.xpack.esql.plan.logical.MvExpand;
21+
import org.elasticsearch.xpack.esql.plan.logical.local.EsqlProject;
2022
import org.elasticsearch.xpack.esql.plan.logical.local.LocalRelation;
2123
import org.elasticsearch.xpack.esql.plan.logical.local.LocalSupplier;
2224
import org.elasticsearch.xpack.esql.stats.SearchStats;
@@ -181,6 +183,37 @@ public void testMissingFieldInSort() {
181183
var source = as(limit.child(), EsRelation.class);
182184
}
183185

186+
/**
187+
* Expects
188+
* EsqlProject[[first_name{f}#6]]
189+
* \_Limit[1000[INTEGER]]
190+
* \_MvExpand[last_name{f}#9,last_name{r}#15]
191+
* \_Limit[1000[INTEGER]]
192+
* \_EsRelation[test][_meta_field{f}#11, emp_no{f}#5, first_name{f}#6, ge..]
193+
*/
194+
public void testMissingFieldInMvExpand() {
195+
var plan = plan("""
196+
from test
197+
| mv_expand last_name
198+
| keep first_name, last_name
199+
""");
200+
201+
var testStats = statsForMissingField("last_name");
202+
var localPlan = localPlan(plan, testStats);
203+
204+
var project = as(localPlan, EsqlProject.class);
205+
var projections = project.projections();
206+
assertThat(Expressions.names(projections), contains("first_name", "last_name"));
207+
208+
var limit = as(project.child(), Limit.class);
209+
// MvExpand cannot be optimized (yet) because the target NamedExpression cannot be replaced with a NULL literal
210+
// https://github.com/elastic/elasticsearch/issues/109974
211+
// See LocalLogicalPlanOptimizer.ReplaceMissingFieldWithNull
212+
var mvExpand = as(limit.child(), MvExpand.class);
213+
var limit2 = as(mvExpand.child(), Limit.class);
214+
as(limit2.child(), EsRelation.class);
215+
}
216+
184217
/**
185218
* Expects
186219
* EsqlProject[[x{r}#3]]

0 commit comments

Comments
 (0)