Skip to content

Commit 9aa9d78

Browse files
committed
Check disjunctions for pushing down to source
1 parent e1ca189 commit 9aa9d78

File tree

2 files changed

+51
-82
lines changed

2 files changed

+51
-82
lines changed

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

Lines changed: 0 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,12 @@
2222
import org.elasticsearch.xpack.esql.core.expression.function.Function;
2323
import org.elasticsearch.xpack.esql.core.expression.predicate.logical.BinaryLogic;
2424
import org.elasticsearch.xpack.esql.core.expression.predicate.logical.Not;
25-
import org.elasticsearch.xpack.esql.core.expression.predicate.logical.Or;
2625
import org.elasticsearch.xpack.esql.core.planner.ExpressionTranslator;
2726
import org.elasticsearch.xpack.esql.core.planner.TranslatorHandler;
2827
import org.elasticsearch.xpack.esql.core.querydsl.query.Query;
2928
import org.elasticsearch.xpack.esql.core.querydsl.query.TranslationAwareExpressionQuery;
3029
import org.elasticsearch.xpack.esql.core.tree.Source;
3130
import org.elasticsearch.xpack.esql.core.type.DataType;
32-
import org.elasticsearch.xpack.esql.core.util.Holder;
3331
import org.elasticsearch.xpack.esql.evaluator.mapper.EvaluatorMapper;
3432
import org.elasticsearch.xpack.esql.plan.logical.Aggregate;
3533
import org.elasticsearch.xpack.esql.plan.logical.EsRelation;
@@ -233,82 +231,6 @@ private static void checkFullTextQueryFunctions(LogicalPlan plan, Failures failu
233231
}
234232
}
235233

236-
/**
237-
* Checks whether a condition contains a disjunction with a full text search.
238-
* If it does, check that every element of the disjunction is a full text search or combinations (AND, OR, NOT) of them.
239-
* If not, add a failure to the failures collection.
240-
*
241-
* @param condition condition to check for disjunctions of full text searches
242-
* @param typeNameProvider provider for the type name to add in the failure message
243-
* @param failures failures collection to add to
244-
*/
245-
private static void checkFullTextSearchDisjunctions(
246-
Expression condition,
247-
java.util.function.Function<FullTextFunction, String> typeNameProvider,
248-
Failures failures
249-
) {
250-
Holder<Boolean> isInvalid = new Holder<>(false);
251-
condition.forEachDown(Or.class, or -> {
252-
if (isInvalid.get()) {
253-
// Exit early if we already have a failures
254-
return;
255-
}
256-
boolean hasFullText = or.anyMatch(FullTextFunction.class::isInstance);
257-
if (hasFullText) {
258-
boolean hasOnlyFullText = onlyFullTextFunctionsInExpression(or);
259-
if (hasOnlyFullText == false) {
260-
isInvalid.set(true);
261-
failures.add(
262-
fail(
263-
or,
264-
"Invalid condition [{}]. Full text functions can be used in an OR condition, "
265-
+ "but only if just full text functions are used in the OR condition",
266-
or.sourceText()
267-
)
268-
);
269-
}
270-
}
271-
});
272-
}
273-
274-
/**
275-
* Checks whether an expression contains just full text functions or negations (NOT) and combinations (AND, OR) of full text functions
276-
*
277-
* @param expression expression to check
278-
* @return true if all children are full text functions or negations of full text functions, false otherwise
279-
*/
280-
private static boolean onlyFullTextFunctionsInExpression(Expression expression) {
281-
if (expression instanceof FullTextFunction) {
282-
return true;
283-
} else if (expression instanceof Not) {
284-
return onlyFullTextFunctionsInExpression(expression.children().get(0));
285-
} else if (expression instanceof BinaryLogic binaryLogic) {
286-
return onlyFullTextFunctionsInExpression(binaryLogic.left()) && onlyFullTextFunctionsInExpression(binaryLogic.right());
287-
}
288-
289-
return false;
290-
}
291-
292-
/**
293-
* Checks whether an expression contains a full text function as part of it
294-
*
295-
* @param expression expression to check
296-
* @return true if the expression or any of its children is a full text function, false otherwise
297-
*/
298-
private static boolean anyFullTextFunctionsInExpression(Expression expression) {
299-
if (expression instanceof FullTextFunction) {
300-
return true;
301-
}
302-
303-
for (Expression child : expression.children()) {
304-
if (anyFullTextFunctionsInExpression(child)) {
305-
return true;
306-
}
307-
}
308-
309-
return false;
310-
}
311-
312234
/**
313235
* Checks all commands that exist before a specific type satisfy conditions.
314236
*
@@ -387,7 +309,6 @@ private static FullTextFunction forEachFullTextFunctionParent(Expression conditi
387309
return null;
388310
}
389311

390-
391312
@Override
392313
public EvalOperator.ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvaluator) {
393314
List<EsPhysicalOperationProviders.ShardContext> shardContexts = toEvaluator.shardContexts();

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

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
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;
1112
import org.elasticsearch.xpack.esql.core.expression.Alias;
1213
import org.elasticsearch.xpack.esql.core.expression.Attribute;
1314
import org.elasticsearch.xpack.esql.core.expression.AttributeMap;
@@ -21,6 +22,7 @@
2122
import org.elasticsearch.xpack.esql.core.expression.predicate.Range;
2223
import org.elasticsearch.xpack.esql.core.expression.predicate.logical.BinaryLogic;
2324
import org.elasticsearch.xpack.esql.core.expression.predicate.logical.Not;
25+
import org.elasticsearch.xpack.esql.core.expression.predicate.logical.Or;
2426
import org.elasticsearch.xpack.esql.core.expression.predicate.nulls.IsNotNull;
2527
import org.elasticsearch.xpack.esql.core.expression.predicate.nulls.IsNull;
2628
import org.elasticsearch.xpack.esql.core.expression.predicate.operator.comparison.BinaryComparison;
@@ -29,6 +31,7 @@
2931
import org.elasticsearch.xpack.esql.core.querydsl.query.Query;
3032
import org.elasticsearch.xpack.esql.core.type.DataType;
3133
import org.elasticsearch.xpack.esql.core.util.CollectionUtils;
34+
import org.elasticsearch.xpack.esql.core.util.Holder;
3235
import org.elasticsearch.xpack.esql.core.util.Queries;
3336
import org.elasticsearch.xpack.esql.expression.function.fulltext.FullTextFunction;
3437
import org.elasticsearch.xpack.esql.expression.function.scalar.ip.CIDRMatch;
@@ -232,7 +235,9 @@ static boolean canPushToSource(Expression exp, LucenePushdownPredicates lucenePu
232235
} else if (exp instanceof InsensitiveBinaryComparison bc) {
233236
return isAttributePushable(bc.left(), bc, lucenePushdownPredicates) && bc.right().foldable();
234237
} else if (exp instanceof BinaryLogic bl) {
235-
return canPushToSource(bl.left(), lucenePushdownPredicates) && canPushToSource(bl.right(), lucenePushdownPredicates);
238+
return canPushToSource(bl.left(), lucenePushdownPredicates)
239+
&& canPushToSource(bl.right(), lucenePushdownPredicates)
240+
&& checkPushableFullTextSearchFunctions(exp);
236241
} else if (exp instanceof In in) {
237242
return isAttributePushable(in.value(), null, lucenePushdownPredicates) && Expressions.foldable(in.list());
238243
} else if (exp instanceof Not not) {
@@ -252,8 +257,8 @@ static boolean canPushToSource(Expression exp, LucenePushdownPredicates lucenePu
252257
} else if (exp instanceof SpatialRelatesFunction spatial) {
253258
return canPushSpatialFunctionToSource(spatial, lucenePushdownPredicates);
254259
} else if (exp instanceof FullTextFunction) {
255-
// TODO check for disjunctions
256-
return false;
260+
// In isolation, full text functions are pushable to source. We check if there are no disjunctions on the binary logic check
261+
return true;
257262
}
258263
return false;
259264
}
@@ -269,6 +274,49 @@ public static boolean canPushSpatialFunctionToSource(BinarySpatialFunction s, Lu
269274
|| isPushableSpatialAttribute(s.right(), lucenePushdownPredicates) && s.left().foldable();
270275
}
271276

277+
/**
278+
* Checks whether a condition contains a full text function that can't be pushed down to source.
279+
* Full text functions can be pushed down as long as they are the only functions in the expression, or there are no disjunctions with
280+
* other non-full text functions conditions.
281+
*
282+
* @param condition condition to check for disjunctions of full text searches
283+
*/
284+
private static boolean checkPushableFullTextSearchFunctions(Expression condition) {
285+
Holder<Boolean> isPushable = new Holder<>(true);
286+
condition.forEachDown(Or.class, or -> {
287+
if (isPushable.get() == false) {
288+
// Exit early if we already have a failures
289+
return;
290+
}
291+
boolean hasFullText = or.anyMatch(FullTextFunction.class::isInstance);
292+
if (hasFullText) {
293+
boolean hasOnlyFullText = onlyFullTextFunctionsInExpression(or);
294+
if (hasOnlyFullText == false) {
295+
isPushable.set(false);
296+
}
297+
}
298+
});
299+
return isPushable.get();
300+
}
301+
302+
/**
303+
* Checks whether an expression contains just full text functions or negations (NOT) and combinations (AND, OR) of full text functions
304+
*
305+
* @param expression expression to check
306+
* @return true if all children are full text functions or negations of full text functions, false otherwise
307+
*/
308+
private static boolean onlyFullTextFunctionsInExpression(Expression expression) {
309+
if (expression instanceof FullTextFunction) {
310+
return true;
311+
} else if (expression instanceof Not) {
312+
return onlyFullTextFunctionsInExpression(expression.children().get(0));
313+
} else if (expression instanceof BinaryLogic binaryLogic) {
314+
return onlyFullTextFunctionsInExpression(binaryLogic.left()) && onlyFullTextFunctionsInExpression(binaryLogic.right());
315+
}
316+
317+
return false;
318+
}
319+
272320
private static boolean isPushableSpatialAttribute(Expression exp, LucenePushdownPredicates p) {
273321
return exp instanceof FieldAttribute fa && DataType.isSpatial(fa.dataType()) && fa.getExactInfo().hasExact() && p.isIndexed(fa);
274322
}

0 commit comments

Comments
 (0)