Skip to content

Commit 3a52b87

Browse files
Unify ReplaceMissingFieldWithNull and ReplaceConstantKeyword in a single rule
1 parent 76bd083 commit 3a52b87

File tree

3 files changed

+25
-76
lines changed

3 files changed

+25
-76
lines changed

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@
1212
import org.elasticsearch.xpack.esql.optimizer.rules.logical.local.InferIsNotNull;
1313
import org.elasticsearch.xpack.esql.optimizer.rules.logical.local.InferNonNullAggConstraint;
1414
import org.elasticsearch.xpack.esql.optimizer.rules.logical.local.LocalPropagateEmptyRelation;
15-
import org.elasticsearch.xpack.esql.optimizer.rules.logical.local.ReplaceConstantKeywords;
16-
import org.elasticsearch.xpack.esql.optimizer.rules.logical.local.ReplaceMissingFieldWithNull;
15+
import org.elasticsearch.xpack.esql.optimizer.rules.logical.local.ReplaceFieldWithConstant;
1716
import org.elasticsearch.xpack.esql.optimizer.rules.logical.local.ReplaceTopNWithLimitAndSort;
1817
import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan;
1918
import org.elasticsearch.xpack.esql.rule.ParameterizedRuleExecutor;
@@ -39,9 +38,8 @@ public class LocalLogicalPlanOptimizer extends ParameterizedRuleExecutor<Logical
3938
new Batch<>(
4039
"Local rewrite",
4140
Limiter.ONCE,
42-
new ReplaceConstantKeywords(),
4341
new ReplaceTopNWithLimitAndSort(),
44-
new ReplaceMissingFieldWithNull(),
42+
new ReplaceFieldWithConstant(),
4543
new InferIsNotNull(),
4644
new InferNonNullAggConstraint()
4745
),

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/local/ReplaceConstantKeywords.java

Lines changed: 0 additions & 67 deletions
This file was deleted.
Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.elasticsearch.xpack.esql.core.expression.Alias;
1313
import org.elasticsearch.xpack.esql.core.expression.Attribute;
1414
import org.elasticsearch.xpack.esql.core.expression.AttributeSet;
15+
import org.elasticsearch.xpack.esql.core.expression.Expression;
1516
import org.elasticsearch.xpack.esql.core.expression.FieldAttribute;
1617
import org.elasticsearch.xpack.esql.core.expression.Literal;
1718
import org.elasticsearch.xpack.esql.core.expression.NamedExpression;
@@ -29,19 +30,21 @@
2930
import org.elasticsearch.xpack.esql.rule.ParameterizedRule;
3031

3132
import java.util.ArrayList;
33+
import java.util.HashMap;
3234
import java.util.List;
3335
import java.util.Map;
3436
import java.util.function.Predicate;
3537

3638
/**
37-
* Look for any fields used in the plan that are missing locally and replace them with null.
39+
* Look for any fields used in the plan that are missing or that are constant locally and replace them with null or with the value.
3840
* This should minimize the plan execution, in the best scenario skipping its execution all together.
3941
*/
40-
public class ReplaceMissingFieldWithNull extends ParameterizedRule<LogicalPlan, LogicalPlan, LocalLogicalOptimizerContext> {
42+
public class ReplaceFieldWithConstant extends ParameterizedRule<LogicalPlan, LogicalPlan, LocalLogicalOptimizerContext> {
4143

4244
@Override
4345
public LogicalPlan apply(LogicalPlan plan, LocalLogicalOptimizerContext localLogicalOptimizerContext) {
4446
var lookupFieldsBuilder = AttributeSet.builder();
47+
Map<Attribute, Expression> attrToValue = new HashMap<>();
4548
plan.forEachUp(EsRelation.class, esRelation -> {
4649
// Looking only for indices in LOOKUP mode is correct: during parsing, we assign the expected mode and even if a lookup index
4750
// is used in the FROM command, it will not be marked with LOOKUP mode there - but STANDARD.
@@ -52,6 +55,18 @@ public LogicalPlan apply(LogicalPlan plan, LocalLogicalOptimizerContext localLog
5255
if (esRelation.indexMode() == IndexMode.LOOKUP) {
5356
lookupFieldsBuilder.addAll(esRelation.output());
5457
}
58+
// find constant values only in the main indices
59+
else if (esRelation.indexMode() == IndexMode.STANDARD) {
60+
for (Attribute attribute : esRelation.output()) {
61+
if (attribute instanceof FieldAttribute fa) {
62+
// Do not use the attribute name, this can deviate from the field name for union types; use fieldName() instead.
63+
var val = localLogicalOptimizerContext.searchStats().constantValue(fa.fieldName());
64+
if (val != null) {
65+
attrToValue.put(attribute, Literal.of(attribute, val));
66+
}
67+
}
68+
}
69+
}
5570
});
5671
AttributeSet lookupFields = lookupFieldsBuilder.build();
5772

@@ -61,10 +76,10 @@ public LogicalPlan apply(LogicalPlan plan, LocalLogicalOptimizerContext localLog
6176
|| localLogicalOptimizerContext.searchStats().exists(f.fieldName())
6277
|| lookupFields.contains(f);
6378

64-
return plan.transformUp(p -> missingToNull(p, shouldBeRetained));
79+
return plan.transformUp(p -> missingToNull(p, shouldBeRetained, attrToValue));
6580
}
6681

67-
private LogicalPlan missingToNull(LogicalPlan plan, Predicate<FieldAttribute> shouldBeRetained) {
82+
private LogicalPlan missingToNull(LogicalPlan plan, Predicate<FieldAttribute> shouldBeRetained, Map<Attribute, Expression> constants) {
6883
if (plan instanceof EsRelation relation) {
6984
// For any missing field, place an Eval right after the EsRelation to assign null values to that attribute (using the same name
7085
// id!), thus avoiding that InsertFieldExtrations inserts a field extraction later.
@@ -118,7 +133,10 @@ private LogicalPlan missingToNull(LogicalPlan plan, Predicate<FieldAttribute> sh
118133
|| plan instanceof OrderBy
119134
|| plan instanceof RegexExtract
120135
|| plan instanceof TopN) {
121-
return plan.transformExpressionsOnlyUp(FieldAttribute.class, f -> shouldBeRetained.test(f) ? f : Literal.of(f, null));
136+
return plan.transformExpressionsOnlyUp(
137+
FieldAttribute.class,
138+
f -> constants.getOrDefault(f, shouldBeRetained.test(f) ? f : Literal.of(f, null))
139+
);
122140
}
123141

124142
return plan;

0 commit comments

Comments
 (0)