Add Support for Relevance functions - #21562
Conversation
Signed-off-by: Suresh N S <nssuresh@amazon.com>
PR Reviewer Guide 🔍(Review updated until commit 6ab7900)Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Latest suggestions up to 6ab7900 Explore these optional code suggestions:
Previous suggestionsSuggestions up to commit e384326
Suggestions up to commit c7590ae
Suggestions up to commit 1f28400
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #21562 +/- ##
============================================
- Coverage 73.50% 73.40% -0.10%
+ Complexity 74644 74559 -85
============================================
Files 5980 5980
Lines 338777 338825 +48
Branches 48848 48857 +9
============================================
- Hits 249011 248728 -283
- Misses 69946 70328 +382
+ Partials 19820 19769 -51 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Thanks for getting this started @nssuresh2007
We can also look at running the Integ tests in SQL/PPL plugin to see the improvement coverage and identify any gaps early.
./gradlew :integ-test:integTestRemote \
-Dtests.rest.cluster=localhost:9200 \
-Dtests.cluster=localhost:9300 \
-Dtests.clustername=runTask \
--tests "org.opensearch.sql.calcite.remote.CalcitePPLAggregationIT"
Signed-off-by: Suresh N S <nssuresh@amazon.com>
|
Persistent review updated to latest commit c7590ae |
Signed-off-by: Suresh N S <nssuresh@amazon.com>
|
Persistent review updated to latest commit e384326 |
|
❌ Gradle check result for e384326: FAILURE Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change? |
Signed-off-by: Suresh N S <nssuresh@amazon.com>
Signed-off-by: Suresh N S <nssuresh@amazon.com>
Signed-off-by: Suresh N S <nssuresh@amazon.com>
Signed-off-by: Suresh N S <nssuresh@amazon.com>
Updated code to remove the assumption on the ordering of the elements within the MAP structure Signed-off-by: Suresh N S <nssuresh@amazon.com>
|
Persistent review updated to latest commit 6ab7900 |
|
❌ Gradle check result for 6ab7900: FAILURE Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change? |
expani
left a comment
There was a problem hiding this comment.
Thanks for sticking to good design patterns in the age of LLM coding :)
LGTM 🚀
|
Persistent review updated to latest commit 6ab7900 |
2733336
into
opensearch-project:main
PR opensearch-project#21562 ("Add Support for Relevance functions") introduced a reference to `FieldType.TEXT` at OpenSearchFilterRule.java:158 inside the new FULL_TEXT branch of `resolveViableBackends`, but didn't add the corresponding `import org.opensearch.analytics.spi.FieldType;`. That breaks `:sandbox:plugins:analytics-engine:compileJava` on upstream/main, blocking any downstream branch (including this one) from compiling cleanly. Fix: add the import. Single-line change with no functional impact. Included in this PR purely to keep the branch buildable; should be landed in a standalone follow-up PR if a maintainer prefers to keep this PR scoped to SPAN only. Signed-off-by: Kai Huang <huangkaics@gmail.com> Signed-off-by: Kai Huang <ahkcs@amazon.com>
* Adding support for Relevance Functions Signed-off-by: Suresh N S <nssuresh@amazon.com> * Addressing comments Signed-off-by: Suresh N S <nssuresh@amazon.com> * Removing the unintentional checkin of md file Signed-off-by: Suresh N S <nssuresh@amazon.com> * Adding support for Relevance Functions Signed-off-by: Suresh N S <nssuresh@amazon.com> * Addressing comments Signed-off-by: Suresh N S <nssuresh@amazon.com> * Removing the unintentional checkin of md file Signed-off-by: Suresh N S <nssuresh@amazon.com> * Addressing comments from 2nd iteration Signed-off-by: Suresh N S <nssuresh@amazon.com> * Fixing a bug where MAP ordering can be random Updated code to remove the assumption on the ordering of the elements within the MAP structure Signed-off-by: Suresh N S <nssuresh@amazon.com> --------- Signed-off-by: Suresh N S <nssuresh@amazon.com>
Description
Adds support for the remaining OpenSearch relevance (full-text search) functions in the analytics engine's Lucene backend serialization layer:
Changes
and RexInputRef fallback)
rather than RexInputRef) can resolve backend viability correctly
Tests
Check List
Testing
1. match() on text field
1a. Single term match on description
Result: ✅ PASS
{"columns":["age","city","description","name","score"],"rows":[[30,"Seattle","Software engineer working on search engines and distributed systems","Alice",85.5],[35,"Seattle","Senior developer building search infrastructure and query optimization","Charlie",91.0],[42,"New York","Principal engineer leading distributed search platform development","Eve",95.2],[45,"New York","Project manager overseeing search and analytics platform teams","Henry",62.4],[29,"Seattle","Search engineer improving relevance scoring and query processing","Jack",83.6]]}Expected: 5 docs (Alice, Charlie, Eve, Henry, Jack) — all contain "search" in description
Actual: 5 docs ✅
1b. Multi-term match (OR semantics)
Result: ✅ PASS
{"columns":["age","city","description","name","score"],"rows":[[30,"Seattle","Software engineer working on search engines and distributed systems","Alice",85.5],[35,"Seattle","Senior developer building search infrastructure and query optimization","Charlie",91.0],[42,"New York","Principal engineer leading distributed search platform development","Eve",95.2],[31,"Seattle","Backend developer focused on high performance computing and optimization","Frank",77.8],[45,"New York","Project manager overseeing search and analytics platform teams","Henry",62.4],[29,"Seattle","Search engineer improving relevance scoring and query processing","Jack",83.6]]}Expected: 6 docs — those with "search" OR "optimization"
Actual: 6 docs (Alice, Charlie, Eve, Frank, Henry, Jack) ✅
1c. match on keyword field (city)
Result: ✅ PASS
{"columns":["age","city","description","name","score"],"rows":[[30,"Seattle","Software engineer working on search engines and distributed systems","Alice",85.5],[35,"Seattle","Senior developer building search infrastructure and query optimization","Charlie",91.0],[31,"Seattle","Backend developer focused on high performance computing and optimization","Frank",77.8],[29,"Seattle","Search engineer improving relevance scoring and query processing","Jack",83.6]]}Expected: 4 docs in Seattle
Actual: 4 docs (Alice, Charlie, Frank, Jack) ✅
2. match_phrase()
2a. Phrase that exists (adjacent terms)
Result: ✅ PASS
{"columns":["age","city","description","name","score"],"rows":[[30,"Seattle","Software engineer working on search engines and distributed systems","Alice",85.5]]}Expected: 1 doc (Alice) — only one has "search engines" as adjacent terms
Actual: 1 doc ✅
2b. Phrase that does NOT exist (non-adjacent terms)
Result: ✅ PASS
{"columns":["age","city","description","name","score"],"rows":[]}Expected: 0 docs — "search" and "distributed" are not adjacent in any doc
Actual: 0 docs ✅
3. multi_match()
3a. multi_match across text fields
Result: ✅ PASS
{"columns":["age","city","description","name","score"],"rows":[[30,"Seattle","Software engineer working on search engines and distributed systems","Alice",85.5],[35,"Seattle","Senior developer building search infrastructure and query optimization","Charlie",91.0],[42,"New York","Principal engineer leading distributed search platform development","Eve",95.2],[45,"New York","Project manager overseeing search and analytics platform teams","Henry",62.4],[29,"Seattle","Search engineer improving relevance scoring and query processing","Jack",83.6]]}Expected: 5 docs matching "search" in description (name is keyword, won't match analyzed "search")
Actual: 5 docs ✅
3b. multi_match across text + keyword fields
Result: ✅ PASS
{"columns":["age","city","description","name","score"],"rows":[[30,"seattle","This is seattle city","alice",95.5],[35,"seattle","This is seattle city","carol",92.3],[32,"seattle","This is seattle city","eve",91.0],[30,"Seattle","Software engineer working on search engines and distributed systems","Alice",85.5],[35,"Seattle","Senior developer building search infrastructure and query optimization","Charlie",91.0],[31,"Seattle","Backend developer focused on high performance computing and optimization","Frank",77.8],[29,"Seattle","Search engineer improving relevance scoring and query processing","Jack",83.6]]}Expected: Docs matching "Seattle" in city (keyword exact) or description (text analyzed)
Actual: 7 docs (includes pre-existing lowercase docs + our 4 Seattle docs) ✅
4. query_string()
4a. query_string with AND operator
Result: ✅ PASS
{"columns":["age","city","description","name","score"],"rows":[[35,"Seattle","Senior developer building search infrastructure and query optimization","Charlie",91.0]]}Expected: 1 doc (Charlie) — only one has both "search" AND "optimization"
Actual: 1 doc ✅
4b. query_string with OR operator
Result: ✅ PASS
{"columns":["age","city","description","name","score"],"rows":[[30,"Seattle","Software engineer working on search engines and distributed systems","Alice",85.5],[25,"Portland","Data scientist specializing in machine learning and natural language processing","Bob",72.3],[35,"Seattle","Senior developer building search infrastructure and query optimization","Charlie",91.0],[42,"New York","Principal engineer leading distributed search platform development","Eve",95.2],[27,"Portland","Machine learning engineer working on natural language understanding","Grace",88.1],[45,"New York","Project manager overseeing search and analytics platform teams","Henry",62.4],[29,"Seattle","Search engineer improving relevance scoring and query processing","Jack",83.6]]}Expected: 7 docs — those with "search" OR "machine"
Actual: 7 docs ✅
4c. query_string with wildcard
Result: ✅ PASS
{"columns":["age","city","description","name","score"],"rows":[[30,"Seattle","Software engineer working on search engines and distributed systems","Alice",85.5],[35,"Seattle","Senior developer building search infrastructure and query optimization","Charlie",91.0],[42,"New York","Principal engineer leading distributed search platform development","Eve",95.2],[45,"New York","Project manager overseeing search and analytics platform teams","Henry",62.4],[29,"Seattle","Search engineer improving relevance scoring and query processing","Jack",83.6]]}Expected: 5 docs with words starting with "search"
Actual: 5 docs ✅
5. simple_query_string()
5a. simple_query_string with + (required) operator
Result: ✅ PASS
{"columns":["age","city","description","name","score"],"rows":[[35,"Seattle","Senior developer building search infrastructure and query optimization","Charlie",91.0]]}Expected: 1 doc — must have "optimization", optionally "search"
Actual: 1 doc (Charlie) ✅
6. match_phrase_prefix()
6a. Prefix completion of phrase
Result: ✅ PASS
{"columns":["age","city","description","name","score"],"rows":[[30,"Seattle","Software engineer working on search engines and distributed systems","Alice",85.5],[29,"Seattle","Search engineer improving relevance scoring and query processing","Jack",83.6]]}Expected: 2 docs — "search eng*" matches "search engines" (Alice) and "Search engineer" (Jack)
Actual: 2 docs ✅
7. match_bool_prefix()
7a. Bool prefix matching
Result: ✅ PASS
{"columns":["age","city","description","name","score"],"rows":[[30,"Seattle","Software engineer working on search engines and distributed systems","Alice",85.5],[35,"Seattle","Senior developer building search infrastructure and query optimization","Charlie",91.0],[42,"New York","Principal engineer leading distributed search platform development","Eve",95.2],[45,"New York","Project manager overseeing search and analytics platform teams","Henry",62.4],[29,"Seattle","Search engineer improving relevance scoring and query processing","Jack",83.6]]}Expected: Docs matching "search" (as full term) AND/OR "plat*" (as prefix) — match_bool_prefix creates term queries for all but last term, prefix query for last
Actual: 5 docs — all have "search"; "plat" prefix matches "platform" in Eve and Henry ✅
8. Term/Terms on keyword fields
8a. Exact term match on keyword (name)
Result: ✅ PASS
{"columns":["age","city","description","name","score"],"rows":[[30,"Seattle","Software engineer working on search engines and distributed systems","Alice",85.5]]}Expected: 1 doc (Alice)
Actual: 1 doc ✅
8b. IN clause (terms equivalent) on keyword (city)
Result: ✅ PASS
{"columns":["age","city","description","name","score"],"rows":[[30,"Seattle","Software engineer working on search engines and distributed systems","Alice",85.5],[25,"Portland","Data scientist specializing in machine learning and natural language processing","Bob",72.3],[35,"Seattle","Senior developer building search infrastructure and query optimization","Charlie",91.0],[31,"Seattle","Backend developer focused on high performance computing and optimization","Frank",77.8],[27,"Portland","Machine learning engineer working on natural language understanding","Grace",88.1],[29,"Seattle","Search engineer improving relevance scoring and query processing","Jack",83.6]]}Expected: 6 docs (4 Seattle + 2 Portland)
Actual: 6 docs ✅
9. Range queries on numeric fields
9a. Range on integer (age > 35)
Result: ✅ PASS
{"columns":["age","city","description","name","score"],"rows":[[42,"New York","Principal engineer leading distributed search platform development","Eve",95.2],[45,"New York","Project manager overseeing search and analytics platform teams","Henry",62.4]]}Expected: 2 docs (Eve age=42, Henry age=45)
Actual: 2 docs ✅
9b. Range on double (score between 85 and 95)
Result: ✅ PASS
{"columns":["age","city","description","name","score"],"rows":[[30,"Seattle","Software engineer working on search engines and distributed systems","Alice",85.5],[35,"Seattle","Senior developer building search infrastructure and query optimization","Charlie",91.0],[27,"Portland","Machine learning engineer working on natural language understanding","Grace",88.1],[25,"portland","This is portland","bob",88.0],[35,"seattle","This is seattle city","carol",92.3],[32,"seattle","This is seattle city","eve",91.0]]}Expected: Docs with score in [85.0, 95.0] — includes pre-existing docs
Actual: 6 docs (3 from our set + 3 pre-existing) ✅
10. Bool compound queries
10a. match OR match (two FTS functions combined)
Result: ✅ PASS
{"columns":["age","city","description","name","score"],"rows":[[30,"Seattle","Software engineer working on search engines and distributed systems","Alice",85.5],[25,"Portland","Data scientist specializing in machine learning and natural language processing","Bob",72.3],[35,"Seattle","Senior developer building search infrastructure and query optimization","Charlie",91.0],[42,"New York","Principal engineer leading distributed search platform development","Eve",95.2],[27,"Portland","Machine learning engineer working on natural language understanding","Grace",88.1],[45,"New York","Project manager overseeing search and analytics platform teams","Henry",62.4],[29,"Seattle","Search engineer improving relevance scoring and query processing","Jack",83.6]]}Expected: 7 docs — union of "search" (5) and "machine" (2+)
Actual: 7 docs ✅
10b. match AND match (two FTS functions intersected)
Result: ✅ PASS
{"columns":["age","city","description","name","score"],"rows":[[30,"Seattle","Software engineer working on search engines and distributed systems","Alice",85.5],[42,"New York","Principal engineer leading distributed search platform development","Eve",95.2]]}Expected: 2 docs — those with both "search" AND "distributed" (Alice, Eve)
Actual: 2 docs ✅
10c. match AND range filter (FTS + numeric)
Result: ❌ FAIL
{"error":{"root_cause":[{"type":"runtime_exception","reason":"Stage 0 failed"}],"type":"runtime_exception","reason":"Stage 0 failed","caused_by":{"type":"stream_exception","reason":"java.lang.RuntimeException: Execution error: Execution error: Panic: primitive array","content-type":"application/grpc","raw-header":"RVMAAAA2AAAAAAAAABMBCC7YkwAAACUBHF9zeXN0ZW1faW5kZXhfYWNjZXNzX2FsbG93ZWQFZmFsc2UAAAAAAA==","correlation-id":"1864987403856379924"}},"status":500}Expected: 3 docs (Charlie age=35, Eve age=42, Henry age=45 — all have "search" and age>30)
Actual: Runtime panic
[Clarified that this issue is not related to this PR and will be handled separately]
10d. match AND keyword filter (FTS + keyword equality)
Result: ❌ FAIL
{"error":{"root_cause":[{"type":"runtime_exception","reason":"Stage 0 failed"}],"type":"runtime_exception","reason":"Stage 0 failed","caused_by":{"type":"stream_exception","reason":"java.lang.RuntimeException: Execution error: Execution error: Panic: primitive array","content-type":"application/grpc","raw-header":"RVMAAAA2AAAAAAAAABQBCC7YkwAAACUBHF9zeXN0ZW1faW5kZXhfYWNjZXNzX2FsbG93ZWQFZmFsc2UAAAAAAA==","correlation-id":"1864987403856379925"}},"status":500}Expected: 3 docs (Alice, Charlie, Jack — have "search" and city=Seattle)
Actual: Runtime panic
[Clarified that this issue is not related to this PR and will be handled separately]
11. Negative scenarios
11a. match on non-existent field
Result: ✅ PASS (proper error)
{"error":{"root_cause":[{"type":"illegal_state_exception","reason":"Failed to plan query"}],"type":"illegal_state_exception","reason":"Failed to plan query","caused_by":{"type":"error_report","reason":"Field [nonexistent_field] not found.","caused_by":{"type":"illegal_argument_exception","reason":"Field [nonexistent_field] not found."}}},"status":500}Expected: Error indicating field not found
Actual: Clear error message ✅
11b. match on integer field (unsupported type for FTS)
Result: ✅ PASS (proper error)
{"error":{"root_cause":[{"type":"illegal_state_exception","reason":"No backend can evaluate filter predicate [OTHER_FUNCTION] on fields [age:integer]"}],"type":"illegal_state_exception","reason":"No backend can evaluate filter predicate [OTHER_FUNCTION] on fields [age:integer]"},"status":500}Expected: Error — match not supported on integer fields
Actual: Clear error about no backend for integer field ✅
11c. match on double field (unsupported type for FTS)
Result: ✅ PASS (proper error)
{"error":{"root_cause":[{"type":"illegal_state_exception","reason":"No backend can evaluate filter predicate [OTHER_FUNCTION] on fields [score:double]"}],"type":"illegal_state_exception","reason":"No backend can evaluate filter predicate [OTHER_FUNCTION] on fields [score:double]"},"status":500}Expected: Error — match not supported on double fields
Actual: Clear error ✅
11d. match_phrase on keyword field (should work — keyword supports phrase)
Result: ✅ PASS
{"columns":["age","city","description","name","score"],"rows":[[30,"Seattle","Software engineer working on search engines and distributed systems","Alice",85.5]]}Expected: 1 doc — keyword field supports exact phrase match
Actual: 1 doc ✅
11e. match with empty string
Result: ✅ PASS
{"columns":["age","city","description","name","score"],"rows":[]}Expected: 0 docs — empty query matches nothing
Actual: 0 docs ✅
11f. match with non-matching term
Result: ✅ PASS
{"columns":["age","city","description","name","score"],"rows":[]}Expected: 0 docs — term doesn't exist in any document
Actual: 0 docs ✅
11g. query_string with invalid syntax (all operators, no terms)
Result: ✅ PASS (error returned)
{"error":{"root_cause":[{"type":"runtime_exception","reason":"Stage 0 failed"}],"type":"runtime_exception","reason":"Stage 0 failed","caused_by":{"type":"stream_exception","reason":"Failed to start streaming fragment on [parquet_test][0]"}},"status":500}Expected: Error — invalid query syntax
Actual: Error returned ✅
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.