|
16 | 16 | import org.elasticsearch.xpack.esql.common.Failures; |
17 | 17 | import org.elasticsearch.xpack.esql.core.expression.Expression; |
18 | 18 | import org.elasticsearch.xpack.esql.core.expression.FoldContext; |
| 19 | +import org.elasticsearch.xpack.esql.core.expression.MetadataAttribute; |
19 | 20 | import org.elasticsearch.xpack.esql.core.expression.Nullability; |
20 | 21 | import org.elasticsearch.xpack.esql.core.expression.TranslationAware; |
21 | 22 | import org.elasticsearch.xpack.esql.core.expression.TypeResolutions; |
22 | 23 | import org.elasticsearch.xpack.esql.core.expression.function.Function; |
23 | 24 | import org.elasticsearch.xpack.esql.core.expression.predicate.logical.BinaryLogic; |
24 | 25 | import org.elasticsearch.xpack.esql.core.expression.predicate.logical.Not; |
| 26 | +import org.elasticsearch.xpack.esql.core.expression.predicate.logical.Or; |
25 | 27 | import org.elasticsearch.xpack.esql.core.planner.ExpressionTranslator; |
26 | 28 | import org.elasticsearch.xpack.esql.core.planner.TranslatorHandler; |
27 | 29 | import org.elasticsearch.xpack.esql.core.querydsl.query.Query; |
28 | 30 | import org.elasticsearch.xpack.esql.core.querydsl.query.TranslationAwareExpressionQuery; |
29 | 31 | import org.elasticsearch.xpack.esql.core.tree.Source; |
30 | 32 | import org.elasticsearch.xpack.esql.core.type.DataType; |
| 33 | +import org.elasticsearch.xpack.esql.core.util.Holder; |
31 | 34 | import org.elasticsearch.xpack.esql.evaluator.mapper.EvaluatorMapper; |
32 | 35 | import org.elasticsearch.xpack.esql.plan.logical.Aggregate; |
33 | 36 | import org.elasticsearch.xpack.esql.plan.logical.EsRelation; |
@@ -222,15 +225,72 @@ private static void checkFullTextQueryFunctions(LogicalPlan plan, Failures failu |
222 | 225 | m -> "[" + m.functionName() + "] " + m.functionType(), |
223 | 226 | failures |
224 | 227 | ); |
225 | | - // checkFullTextSearchDisjunctions(condition, ftf -> "[" + ftf.functionName() + "] " + ftf.functionType(), failures); |
226 | 228 | checkFullTextFunctionsParents(condition, failures); |
| 229 | + |
| 230 | + boolean usesScore = plan.output() |
| 231 | + .stream() |
| 232 | + .anyMatch(attr -> attr instanceof MetadataAttribute ma && ma.name().equals(MetadataAttribute.SCORE)); |
| 233 | + if (usesScore) { |
| 234 | + checkFullTextSearchDisjunctions(condition, failures); |
| 235 | + } |
227 | 236 | } else { |
228 | 237 | plan.forEachExpression(FullTextFunction.class, ftf -> { |
229 | 238 | failures.add(fail(ftf, "[{}] {} is only supported in WHERE commands", ftf.functionName(), ftf.functionType())); |
230 | 239 | }); |
231 | 240 | } |
232 | 241 | } |
233 | 242 |
|
| 243 | + /** |
| 244 | + * Checks whether a condition contains a disjunction with a full text search. |
| 245 | + * If it does, check that every element of the disjunction is a full text search or combinations (AND, OR, NOT) of them. |
| 246 | + * If not, add a failure to the failures collection. |
| 247 | + * |
| 248 | + * @param condition condition to check for disjunctions of full text searches |
| 249 | + * @param failures failures collection to add to |
| 250 | + */ |
| 251 | + public static void checkFullTextSearchDisjunctions(Expression condition, Failures failures) { |
| 252 | + Holder<Boolean> isInvalid = new Holder<>(false); |
| 253 | + condition.forEachDown(Or.class, or -> { |
| 254 | + if (isInvalid.get()) { |
| 255 | + // Exit early if we already have a failures |
| 256 | + return; |
| 257 | + } |
| 258 | + boolean hasFullText = or.anyMatch(FullTextFunction.class::isInstance); |
| 259 | + if (hasFullText) { |
| 260 | + boolean hasOnlyFullText = onlyFullTextFunctionsInExpression(or); |
| 261 | + if (hasOnlyFullText == false) { |
| 262 | + isInvalid.set(true); |
| 263 | + failures.add( |
| 264 | + fail( |
| 265 | + or, |
| 266 | + "Invalid condition when using METADATA _score [{}]. Full text functions can be used in an OR condition, " |
| 267 | + + "but only if just full text functions are used in the OR condition", |
| 268 | + or.sourceText() |
| 269 | + ) |
| 270 | + ); |
| 271 | + } |
| 272 | + } |
| 273 | + }); |
| 274 | + } |
| 275 | + |
| 276 | + /** |
| 277 | + * Checks whether an expression contains just full text functions or negations (NOT) and combinations (AND, OR) of full text functions |
| 278 | + * |
| 279 | + * @param expression expression to check |
| 280 | + * @return true if all children are full text functions or negations of full text functions, false otherwise |
| 281 | + */ |
| 282 | + private static boolean onlyFullTextFunctionsInExpression(Expression expression) { |
| 283 | + if (expression instanceof FullTextFunction) { |
| 284 | + return true; |
| 285 | + } else if (expression instanceof Not) { |
| 286 | + return onlyFullTextFunctionsInExpression(expression.children().get(0)); |
| 287 | + } else if (expression instanceof BinaryLogic binaryLogic) { |
| 288 | + return onlyFullTextFunctionsInExpression(binaryLogic.left()) && onlyFullTextFunctionsInExpression(binaryLogic.right()); |
| 289 | + } |
| 290 | + |
| 291 | + return false; |
| 292 | + } |
| 293 | + |
234 | 294 | /** |
235 | 295 | * Checks all commands that exist before a specific type satisfy conditions. |
236 | 296 | * |
|
0 commit comments