[Enhancement] Fail fast when relevance functions cannot be pushed down - #5662
[Enhancement] Fail fast when relevance functions cannot be pushed down#5662RyanL1997 wants to merge 4 commits into
Conversation
Relevance search functions (match, match_phrase, query_string, ...) are Calcite marker UDFs with no executable implementation -- they exist only to be rewritten into an OpenSearch query during push down. Nothing guaranteed that rewrite happened, and the two script fallbacks in PredicateAnalyzer would serialize whatever they could not analyze into a Calcite script, including a relevance call. That script was then shipped to every data node, where code generation hit RelevanceQueryImplementor and failed, surfacing to the user as: QueryShardException[failed to create query: Failed to compile inline script [...]] -> 500, all shards failed Guard both fallbacks so a node containing a relevance function is never serialized into a script, and report it on the coordinator instead. Also: - Reject a non-indexed field or non-literal query operand in visitRelevanceFunc as PredicateAnalyzerException rather than letting an unchecked ClassCastException escape to the top-level Throwable catch, which was one route into the script fallback. - Give the last-resort UnsupportedOperationException an actionable message naming the function and the constraint, instead of only "only supported when they are pushed down". - Hoist the relevance-function detection out of RelevanceFunctionPushdownRule into UserDefinedFunctionUtils so the analyzer and the rule share one definition. Queries that legitimately push down are unaffected; the reported combination of match() with a like() filter keeps working, with the LIKE going down as a script and the relevance call as a native match query. Signed-off-by: Jialiang Liang <ryanleeang@gmail.com>
PR Reviewer Guide 🔍(Review updated until commit d91ad18)Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Latest suggestions up to d91ad18 Explore these optional code suggestions:
Previous suggestionsSuggestions up to commit 3bb6701
Suggestions up to commit b55674c
Suggestions up to commit 981dfb1
|
Follows the "reject early, not at runtime" proposal in opensearch-project#5491, which asks for a planning-time failure whose message names the column. The previous commit stopped the shard-side crash, but the message the user actually saw still came from RelevanceQueryImplementor and carried only positional references: Expression: match($t4, $t7) The message built in PredicateAnalyzer never reached the user at all, because CalciteLogicalIndexScan.pushDownFilter swallows the exception and returns null, leaving the filter in the plan for code generation to reject. Resolve the column against the pre-compilation plan in the planning error path instead, where the row type is still available, so the report names the column as the user wrote it: Relevance search function [match] cannot be applied to column [b2]. It must run against a field indexed by OpenSearch, and cannot be evaluated on a value the query computes (eval, parse, rex), on aggregated output (stats, top, rare), or after a row limit (head). Move the match filter so it directly follows `source=` and targets an indexed field, or use a non-relevance predicate such as `like` which can be evaluated per row. The error is also now coded UNSUPPORTED_OPERATION with the function and column exposed as context, and carries a suggestion. Extends the IT to assert the column name is present for each shape: b2 (eval), lvl (parse), body (top, and relevance in a projection). Signed-off-by: Jialiang Liang <ryanleeang@gmail.com>
|
Persistent review updated to latest commit b55674c |
Adds a "Usage constraints" section to the relevance function docs explaining that these functions are executed by the OpenSearch search engine against the inverted index built at write time, so they must be applied to an indexed field before any command that transforms rows. Each unsupported pattern is listed with the reason it cannot work, and the planning-time error the user will see. Every claim was verified against a running cluster rather than inferred: - stats on the grouping key followed by a relevance filter DOES work (the filter transposes below the aggregation), so it is documented as supported rather than listed as a limitation. - sort + head followed by a relevance filter fails at planning time. - top / rare followed by a relevance filter fails at planning time. Also documents the known issue where a relevance filter placed directly after `head` silently returns rows drawn from the whole index instead of from the intended sample, since that reordering is not yet guarded. Signed-off-by: Jialiang Liang <ryanleeang@gmail.com>
|
Persistent review updated to latest commit 3bb6701 |
Implements proposal 2 from opensearch-project#5491: explain that match / match_phrase on a keyword field perform whole-value term matching rather than substring or phrase matching, which is the most common reason a relevance query returns zero results without an error. Every case in the new section was verified against a running cluster. That check corrected the alternatives suggested in the original proposal: "query the .keyword subfield only with =/LIKE %X%" does not work in PPL, because a keyword subfield cannot be referenced directly -- match(body.keyword, 'ERROR') fails with "Field [body.keyword] not found". For a text field with a keyword subfield the engine already selects the right form automatically (match uses the analyzed text form, like uses the keyword subfield), so the documented alternatives are = for exact whole-value match, like for substring, and a text mapping for word or phrase matching. Signed-off-by: Jialiang Liang <ryanleeang@gmail.com>
|
Persistent review updated to latest commit d91ad18 |
Description
Relevance search functions (
match,match_phrase,query_string, ...) are Calcite marker UDFs with no executable implementation —RelevanceQueryFunction.RelevanceQueryImplementor.implementunconditionally throws. They only work when the planner rewrites them into an OpenSearch query during push down.Nothing guaranteed that rewrite happened. The two script fallbacks in
PredicateAnalyzerwould serialize whatever they could not analyze into a Calcite script — including a relevance call. That script was then shipped to every data node, where code generation hit the throwing implementor. The user got a 500 with all shards failing, for a query the coordinator could have rejected instantly.Before
The planner emits a script containing the relevance call:
and the shards reject it:
Every data node does work that cannot possibly succeed, and the message says nothing about the actual problem.
A second shape failed at planning time, but with a message that names no field and offers no remedy:
After
Both shapes now fail on the coordinator, before any shard is contacted, with a message that names the offending column (real output from the new IT, for
eval b2 = upper(body) | where match(b2, 'ERROR')):{ "error": { "details": "Relevance search function [match] cannot be applied to column [b2]. It must run against a field indexed by OpenSearch, and cannot be evaluated on a value the query computes (eval, parse, rex), on aggregated output (stats, top, rare), or after a row limit (head). Move the match filter so it directly follows `source=` and targets an indexed field, or use a non-relevance predicate such as `like` which can be evaluated per row.", "context": { "relevance_function": "match", "relevance_column": "b2" }, "suggestion": "apply match directly to an indexed field before any command that transforms rows", "code": "UNSUPPORTED_OPERATION" }, "status": 500 }No
Failed to compile inline script, noall shards failed, and the column is named as the user wrote it —b2foreval,lvlforparse,bodyfortopand for a relevance call in a projection.Queries that legitimately push down are unaffected — including a relevance filter combined with a
LIKEfilter on a puretextfield, where theLIKEstill goes down as a script alongside a nativematchquery:Related Issues
Relates to #5491 — implements @penghuo's proposal 1 from that issue
One deviation from proposal 2, found while verifying each case against a running cluster: a
keywordsubfield cannot be referenced directly in PPL.match(body.keyword, 'ERROR')fails withField [body.keyword] not found, so "query the.keywordsubfield" is not an available workaround. For atextfield with akeywordsubfield the engine already picks the right form automatically —matchuses the analyzedtextform,likeuses thekeywordsubfield. The documented alternatives are therefore=for exact whole-value match,likefor substring, and atextmapping for word or phrase matching.Check List
--signoffor-s.By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.