Skip to content

Commit c97aeb7

Browse files
committed
Moves validation to Or class
1 parent 51aa10e commit c97aeb7

File tree

3 files changed

+61
-5
lines changed

3 files changed

+61
-5
lines changed

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/fulltext/FullTextFunction.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,20 @@
2525
import org.elasticsearch.xpack.esql.core.tree.Source;
2626
import org.elasticsearch.xpack.esql.core.type.DataType;
2727
import org.elasticsearch.xpack.esql.core.util.Holder;
28+
import org.elasticsearch.xpack.esql.evaluator.mapper.EvaluatorMapper;
2829
import org.elasticsearch.xpack.esql.expression.predicate.logical.BinaryLogic;
2930
import org.elasticsearch.xpack.esql.expression.predicate.logical.Not;
3031
import org.elasticsearch.xpack.esql.expression.predicate.logical.Or;
3132
import org.elasticsearch.xpack.esql.optimizer.rules.physical.local.LucenePushdownPredicates;
32-
import org.elasticsearch.xpack.esql.evaluator.mapper.EvaluatorMapper;
3333
import org.elasticsearch.xpack.esql.plan.logical.Aggregate;
3434
import org.elasticsearch.xpack.esql.plan.logical.EsRelation;
3535
import org.elasticsearch.xpack.esql.plan.logical.Filter;
3636
import org.elasticsearch.xpack.esql.plan.logical.Limit;
3737
import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan;
3838
import org.elasticsearch.xpack.esql.plan.logical.OrderBy;
39+
import org.elasticsearch.xpack.esql.planner.EsPhysicalOperationProviders;
3940
import org.elasticsearch.xpack.esql.planner.TranslatorHandler;
4041
import org.elasticsearch.xpack.esql.querydsl.query.TranslationAwareExpressionQuery;
41-
import org.elasticsearch.xpack.esql.planner.EsPhysicalOperationProviders;
4242

4343
import java.util.List;
4444
import java.util.Locale;
@@ -56,7 +56,7 @@
5656
* These functions needs to be pushed down to Lucene queries to be executed - there's no Evaluator for them, but depend on
5757
* {@link org.elasticsearch.xpack.esql.optimizer.LocalPhysicalPlanOptimizer} to rewrite them into Lucene queries.
5858
*/
59-
public abstract class FullTextFunction extends Function implements TranslationAware, PostAnalysisPlanVerificationAware, EvaluatorMappe {
59+
public abstract class FullTextFunction extends Function implements TranslationAware, PostAnalysisPlanVerificationAware, EvaluatorMapper {
6060

6161
private final Expression query;
6262
private final QueryBuilder queryBuilder;
@@ -165,6 +165,7 @@ public boolean equals(Object obj) {
165165

166166
@Override
167167
public boolean translatable(LucenePushdownPredicates pushdownPredicates) {
168+
// In isolation, full text functions are pushable to source. We check if there are no disjunctions in Or conditions
168169
return true;
169170
}
170171

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/predicate/logical/Or.java

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,24 @@
88

99
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
1010
import org.elasticsearch.common.io.stream.StreamInput;
11+
import org.elasticsearch.xpack.esql.capabilities.PostAnalysisPlanVerificationAware;
12+
import org.elasticsearch.xpack.esql.common.Failures;
1113
import org.elasticsearch.xpack.esql.core.expression.Expression;
14+
import org.elasticsearch.xpack.esql.core.expression.MetadataAttribute;
1215
import org.elasticsearch.xpack.esql.core.expression.predicate.Negatable;
1316
import org.elasticsearch.xpack.esql.core.tree.NodeInfo;
1417
import org.elasticsearch.xpack.esql.core.tree.Source;
18+
import org.elasticsearch.xpack.esql.expression.function.fulltext.FullTextFunction;
1519
import org.elasticsearch.xpack.esql.expression.predicate.Predicates;
20+
import org.elasticsearch.xpack.esql.optimizer.rules.physical.local.LucenePushdownPredicates;
21+
import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan;
1622

1723
import java.io.IOException;
24+
import java.util.function.BiConsumer;
1825

19-
public class Or extends BinaryLogic implements Negatable<BinaryLogic> {
26+
import static org.elasticsearch.xpack.esql.common.Failure.fail;
27+
28+
public class Or extends BinaryLogic implements Negatable<BinaryLogic>, PostAnalysisPlanVerificationAware {
2029
public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "Or", Or::new);
2130

2231
public Or(Source source, Expression left, Expression right) {
@@ -57,4 +66,51 @@ protected Expression canonicalize() {
5766
// NB: this add a circular dependency between Predicates / Logical package
5867
return Predicates.combineOr(Predicates.splitOr(super.canonicalize()));
5968
}
69+
70+
@Override
71+
public boolean translatable(LucenePushdownPredicates pushdownPredicates) {
72+
return super.translatable(pushdownPredicates) && checkPushableFullTextSearchFunctions();
73+
}
74+
75+
@Override
76+
public BiConsumer<LogicalPlan, Failures> postAnalysisPlanVerification() {
77+
return (plan, failures) -> {
78+
boolean usesScore = plan.output()
79+
.stream()
80+
.anyMatch(attr -> attr instanceof MetadataAttribute ma && ma.name().equals(MetadataAttribute.SCORE));
81+
if (usesScore && checkPushableFullTextSearchFunctions() == false) {
82+
failures.add(
83+
fail(
84+
this,
85+
"Invalid condition when using METADATA _score [{}]. Full text functions can be used in an OR condition, "
86+
+ "but only if just full text functions are used in the OR condition",
87+
sourceText()
88+
)
89+
);
90+
}
91+
};
92+
}
93+
94+
private boolean checkPushableFullTextSearchFunctions() {
95+
boolean hasFullText = anyMatch(FullTextFunction.class::isInstance);
96+
return hasFullText == false || onlyFullTextFunctionsInExpression(this);
97+
}
98+
99+
/**
100+
* Checks whether an expression contains just full text functions or negations (NOT) and combinations (AND, OR) of full text functions
101+
*
102+
* @param expression expression to check
103+
* @return true if all children are full text functions or negations of full text functions, false otherwise
104+
*/
105+
private static boolean onlyFullTextFunctionsInExpression(Expression expression) {
106+
if (expression instanceof FullTextFunction) {
107+
return true;
108+
} else if (expression instanceof Not) {
109+
return onlyFullTextFunctionsInExpression(expression.children().get(0));
110+
} else if (expression instanceof BinaryLogic binaryLogic) {
111+
return onlyFullTextFunctionsInExpression(binaryLogic.left()) && onlyFullTextFunctionsInExpression(binaryLogic.right());
112+
}
113+
114+
return false;
115+
}
60116
}

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
package org.elasticsearch.xpack.esql.optimizer.rules.physical.local;
99

1010
import org.elasticsearch.index.query.QueryBuilder;
11-
import org.elasticsearch.xpack.esql.common.Failures;
1211
import org.elasticsearch.xpack.esql.capabilities.TranslationAware;
1312
import org.elasticsearch.xpack.esql.core.expression.Alias;
1413
import org.elasticsearch.xpack.esql.core.expression.Attribute;

0 commit comments

Comments
 (0)