diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/local/ReplaceMissingFieldWithNull.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/local/ReplaceMissingFieldWithNull.java index 5a99781b2afde..e6c5c0f1aa89f 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/local/ReplaceMissingFieldWithNull.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/local/ReplaceMissingFieldWithNull.java @@ -7,15 +7,10 @@ package org.elasticsearch.xpack.esql.optimizer.rules.logical.local; -import org.elasticsearch.common.util.Maps; import org.elasticsearch.index.IndexMode; -import org.elasticsearch.xpack.esql.core.expression.Alias; -import org.elasticsearch.xpack.esql.core.expression.Attribute; import org.elasticsearch.xpack.esql.core.expression.AttributeSet; import org.elasticsearch.xpack.esql.core.expression.FieldAttribute; import org.elasticsearch.xpack.esql.core.expression.Literal; -import org.elasticsearch.xpack.esql.core.expression.NamedExpression; -import org.elasticsearch.xpack.esql.core.type.DataType; import org.elasticsearch.xpack.esql.core.type.PotentiallyUnmappedKeywordEsField; import org.elasticsearch.xpack.esql.optimizer.LocalLogicalOptimizerContext; import org.elasticsearch.xpack.esql.plan.logical.EsRelation; @@ -23,19 +18,14 @@ import org.elasticsearch.xpack.esql.plan.logical.Filter; import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan; import org.elasticsearch.xpack.esql.plan.logical.OrderBy; -import org.elasticsearch.xpack.esql.plan.logical.Project; import org.elasticsearch.xpack.esql.plan.logical.RegexExtract; import org.elasticsearch.xpack.esql.plan.logical.TopN; import org.elasticsearch.xpack.esql.rule.ParameterizedRule; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; import java.util.function.Predicate; /** * Look for any fields used in the plan that are missing locally and replace them with null. - * This should minimize the plan execution, in the best scenario skipping its execution all together. */ public class ReplaceMissingFieldWithNull extends ParameterizedRule { @@ -65,54 +55,6 @@ public LogicalPlan apply(LogicalPlan plan, LocalLogicalOptimizerContext localLog } private LogicalPlan missingToNull(LogicalPlan plan, Predicate shouldBeRetained) { - if (plan instanceof EsRelation relation) { - // For any missing field, place an Eval right after the EsRelation to assign null values to that attribute (using the same name - // id!), thus avoiding that InsertFieldExtrations inserts a field extraction later. - // This means that an EsRelation[field1, field2, field3] where field1 and field 3 are missing will be replaced by - // Project[field1, field2, field3] <- keeps the ordering intact - // \_Eval[field1 = null, field3 = null] - // \_EsRelation[field1, field2, field3] - List relationOutput = relation.output(); - Map nullLiterals = Maps.newLinkedHashMapWithExpectedSize(DataType.types().size()); - List newProjections = new ArrayList<>(relationOutput.size()); - for (int i = 0, size = relationOutput.size(); i < size; i++) { - Attribute attr = relationOutput.get(i); - NamedExpression projection; - if (attr instanceof FieldAttribute f && shouldBeRetained.test(f) == false) { - DataType dt = f.dataType(); - Alias nullAlias = nullLiterals.get(dt); - // save the first field as null (per datatype) - if (nullAlias == null) { - // Keep the same id so downstream query plans don't need updating - // NOTE: THIS IS BRITTLE AND CAN LEAD TO BUGS. - // In case some optimizer rule or so inserts a plan node that requires the field BEFORE the Eval that we're adding - // on top of the EsRelation, this can trigger a field extraction in the physical optimizer phase, causing wrong - // layouts due to a duplicate name id. - // If someone reaches here AGAIN when debugging e.g. ClassCastExceptions NPEs from wrong layouts, we should probably - // give up on this approach and instead insert EvalExecs in InsertFieldExtraction. - Alias alias = new Alias(f.source(), f.name(), Literal.of(f, null), f.id()); - nullLiterals.put(dt, alias); - projection = alias.toAttribute(); - } - // otherwise point to it since this avoids creating field copies - else { - projection = new Alias(f.source(), f.name(), nullAlias.toAttribute(), f.id()); - } - } else { - projection = attr; - } - newProjections.add(projection); - } - - if (nullLiterals.size() == 0) { - return plan; - } - - Eval eval = new Eval(plan.source(), relation, new ArrayList<>(nullLiterals.values())); - // This projection is redundant if there's another projection downstream (and no commands depend on the order until we hit it). - return new Project(plan.source(), eval, newProjections); - } - if (plan instanceof Eval || plan instanceof Filter || plan instanceof OrderBy