|
10 | 10 | import org.elasticsearch.common.lucene.BytesRefs; |
11 | 11 | import org.elasticsearch.compute.lucene.LuceneQueryExpressionEvaluator; |
12 | 12 | import org.elasticsearch.compute.lucene.LuceneQueryExpressionEvaluator.ShardConfig; |
| 13 | +import org.elasticsearch.compute.lucene.LuceneQueryScoreEvaluator; |
13 | 14 | import org.elasticsearch.compute.operator.EvalOperator; |
| 15 | +import org.elasticsearch.compute.operator.ScoreOperator; |
14 | 16 | import org.elasticsearch.index.query.QueryBuilder; |
15 | 17 | import org.elasticsearch.xpack.esql.capabilities.PostAnalysisPlanVerificationAware; |
16 | 18 | import org.elasticsearch.xpack.esql.capabilities.TranslationAware; |
17 | 19 | import org.elasticsearch.xpack.esql.common.Failures; |
18 | 20 | import org.elasticsearch.xpack.esql.core.expression.Expression; |
19 | 21 | import org.elasticsearch.xpack.esql.core.expression.FoldContext; |
20 | | -import org.elasticsearch.xpack.esql.core.expression.MetadataAttribute; |
21 | 22 | import org.elasticsearch.xpack.esql.core.expression.Nullability; |
22 | 23 | import org.elasticsearch.xpack.esql.core.expression.TypeResolutions; |
23 | 24 | import org.elasticsearch.xpack.esql.core.expression.function.Function; |
24 | 25 | import org.elasticsearch.xpack.esql.core.querydsl.query.Query; |
25 | 26 | import org.elasticsearch.xpack.esql.core.tree.Source; |
26 | 27 | import org.elasticsearch.xpack.esql.core.type.DataType; |
27 | | -import org.elasticsearch.xpack.esql.core.util.Holder; |
28 | 28 | import org.elasticsearch.xpack.esql.evaluator.mapper.EvaluatorMapper; |
29 | 29 | import org.elasticsearch.xpack.esql.expression.predicate.logical.BinaryLogic; |
30 | 30 | import org.elasticsearch.xpack.esql.expression.predicate.logical.Not; |
31 | | -import org.elasticsearch.xpack.esql.expression.predicate.logical.Or; |
32 | 31 | import org.elasticsearch.xpack.esql.optimizer.rules.physical.local.LucenePushdownPredicates; |
33 | 32 | import org.elasticsearch.xpack.esql.plan.logical.Aggregate; |
34 | 33 | import org.elasticsearch.xpack.esql.plan.logical.EsRelation; |
|
39 | 38 | import org.elasticsearch.xpack.esql.planner.EsPhysicalOperationProviders; |
40 | 39 | import org.elasticsearch.xpack.esql.planner.TranslatorHandler; |
41 | 40 | import org.elasticsearch.xpack.esql.querydsl.query.TranslationAwareExpressionQuery; |
| 41 | +import org.elasticsearch.xpack.esql.score.ExpressionScoreMapper; |
42 | 42 |
|
43 | 43 | import java.util.List; |
44 | 44 | import java.util.Locale; |
|
56 | 56 | * These functions needs to be pushed down to Lucene queries to be executed - there's no Evaluator for them, but depend on |
57 | 57 | * {@link org.elasticsearch.xpack.esql.optimizer.LocalPhysicalPlanOptimizer} to rewrite them into Lucene queries. |
58 | 58 | */ |
59 | | -public abstract class FullTextFunction extends Function implements TranslationAware, PostAnalysisPlanVerificationAware, EvaluatorMapper { |
| 59 | +public abstract class FullTextFunction extends Function |
| 60 | + implements |
| 61 | + TranslationAware, |
| 62 | + PostAnalysisPlanVerificationAware, |
| 63 | + EvaluatorMapper, |
| 64 | + ExpressionScoreMapper { |
60 | 65 |
|
61 | 66 | private final Expression query; |
62 | 67 | private final QueryBuilder queryBuilder; |
@@ -204,79 +209,13 @@ private static void checkFullTextQueryFunctions(LogicalPlan plan, Failures failu |
204 | 209 | failures |
205 | 210 | ); |
206 | 211 | checkFullTextFunctionsParents(condition, failures); |
207 | | - |
208 | | - boolean usesScore = plan.output() |
209 | | - .stream() |
210 | | - .anyMatch(attr -> attr instanceof MetadataAttribute ma && ma.name().equals(MetadataAttribute.SCORE)); |
211 | | - if (usesScore) { |
212 | | - checkFullTextSearchDisjunctions(condition, failures); |
213 | | - } |
214 | 212 | } else { |
215 | 213 | plan.forEachExpression(FullTextFunction.class, ftf -> { |
216 | 214 | failures.add(fail(ftf, "[{}] {} is only supported in WHERE commands", ftf.functionName(), ftf.functionType())); |
217 | 215 | }); |
218 | 216 | } |
219 | 217 | } |
220 | 218 |
|
221 | | - /** |
222 | | - * Checks whether a condition contains a disjunction with a full text search. |
223 | | - * If it does, check that every element of the disjunction is a full text search or combinations (AND, OR, NOT) of them. |
224 | | - * If not, add a failure to the failures collection. |
225 | | - * |
226 | | - * @param condition condition to check for disjunctions of full text searches |
227 | | - * @param failures failures collection to add to |
228 | | - */ |
229 | | - private static void checkFullTextSearchDisjunctions(Expression condition, Failures failures) { |
230 | | - Holder<Boolean> isInvalid = new Holder<>(false); |
231 | | - condition.forEachDown(Or.class, or -> { |
232 | | - if (isInvalid.get()) { |
233 | | - // Exit early if we already have a failures |
234 | | - return; |
235 | | - } |
236 | | - if (checkDisjunctionPushable(or) == false) { |
237 | | - isInvalid.set(true); |
238 | | - failures.add( |
239 | | - fail( |
240 | | - or, |
241 | | - "Invalid condition when using METADATA _score [{}]. Full text functions can be used in an OR condition, " |
242 | | - + "but only if just full text functions are used in the OR condition", |
243 | | - or.sourceText() |
244 | | - ) |
245 | | - ); |
246 | | - } |
247 | | - }); |
248 | | - } |
249 | | - |
250 | | - /** |
251 | | - * Checks if a disjunction is pushable from the point of view of FullTextFunctions. Either it has no FullTextFunctions or |
252 | | - * all it contains are FullTextFunctions. |
253 | | - * |
254 | | - * @param or disjunction to check |
255 | | - * @return true if the disjunction is pushable, false otherwise |
256 | | - */ |
257 | | - private static boolean checkDisjunctionPushable(Or or) { |
258 | | - boolean hasFullText = or.anyMatch(FullTextFunction.class::isInstance); |
259 | | - return hasFullText == false || onlyFullTextFunctionsInExpression(or); |
260 | | - } |
261 | | - |
262 | | - /** |
263 | | - * Checks whether an expression contains just full text functions or negations (NOT) and combinations (AND, OR) of full text functions |
264 | | - * |
265 | | - * @param expression expression to check |
266 | | - * @return true if all children are full text functions or negations of full text functions, false otherwise |
267 | | - */ |
268 | | - private static boolean onlyFullTextFunctionsInExpression(Expression expression) { |
269 | | - if (expression instanceof FullTextFunction) { |
270 | | - return true; |
271 | | - } else if (expression instanceof Not) { |
272 | | - return onlyFullTextFunctionsInExpression(expression.children().get(0)); |
273 | | - } else if (expression instanceof BinaryLogic binaryLogic) { |
274 | | - return onlyFullTextFunctionsInExpression(binaryLogic.left()) && onlyFullTextFunctionsInExpression(binaryLogic.right()); |
275 | | - } |
276 | | - |
277 | | - return false; |
278 | | - } |
279 | | - |
280 | 219 | /** |
281 | 220 | * Checks all commands that exist before a specific type satisfy conditions. |
282 | 221 | * |
@@ -365,4 +304,15 @@ public EvalOperator.ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvalua |
365 | 304 | } |
366 | 305 | return new LuceneQueryExpressionEvaluator.Factory(shardConfigs); |
367 | 306 | } |
| 307 | + |
| 308 | + @Override |
| 309 | + public ScoreOperator.ExpressionScorer.Factory toScorer(ToScorer toScorer) { |
| 310 | + List<EsPhysicalOperationProviders.ShardContext> shardContexts = toScorer.shardContexts(); |
| 311 | + ShardConfig[] shardConfigs = new ShardConfig[shardContexts.size()]; |
| 312 | + int i = 0; |
| 313 | + for (EsPhysicalOperationProviders.ShardContext shardContext : shardContexts) { |
| 314 | + shardConfigs[i++] = new ShardConfig(shardContext.toQuery(queryBuilder()), shardContext.searcher()); |
| 315 | + } |
| 316 | + return new LuceneQueryScoreEvaluator.Factory(shardConfigs); |
| 317 | + } |
368 | 318 | } |
0 commit comments