Skip to content

[FEATURE] PPL vector search foundation: VECTOR type, KNN_SCORE plan and k resolution - #5893

Open
LantaoJin wants to merge 3 commits into
opensearch-project:mainfrom
LantaoJin:feature/ppl-vector-search
Open

LantaoJin wants to merge 3 commits into
opensearch-project:mainfrom
LantaoJin:feature/ppl-vector-search

Conversation

@LantaoJin

Copy link
Copy Markdown
Member

Description

First of three pull requests for task 1 of the PPL vector search RFC (#5875), the sub-task tracked in #5876. It targets the feature branch feature/ppl-vector-search (to be created from main), which one final pull request merges into main, as the SQL vectorSearch() feature was delivered (#5394). This step adds the type, the canonical plan and the rules that the knn() function (step 2, with the k-NN query pushdown) and the knn command (step 3) will lower to. It adds no grammar, so no user query can reach the k-NN plan yet; the tests build the AST directly.

Vector type (Calcite engine; SQL and the V2 engine keep their current type names, values, schemas and error texts)

  • knn_vector fields map to a new ExprCoreType.VECTOR and, on Calcite, to ExprVectorType (REAL or TINYINT elements) carrying the dimension, data type and space type from the mapping; PPL with Calcite reports the type as vector (describe included).
  • Values are read from _source and converted at the Calcite boundary, in scans and in pushed-down scripts: vectors inside objects and nested fields, aliases, and base64-encoded vectors (k-NN 3.8+), decoded with the data type of the index each hit comes from.
  • Multi-index mappings merge as before. Indices that disagree on dimension, data type or the space type k-NN resolves (including k-NN 2.x remote indices) are recorded on the type, and a k-NN step over such a field fails with vector mapping mismatch.
  • Vectors are not comparable: comparisons, sorting, grouping, dedup keys, keyless joins on a shared vector and similar uses fail with a 400.

Plan and k

  • A KNN_SCORE operator and one plan builder producing Sort(fetch=k, _score DESC) ← Project(…, _score := KNN_SCORE(field, vector, options)) ← [Filter(pre-filter)] ← Scan, with field, dimension, placement and pre-filter checks.
  • k is explicit (k=) or derived from the first following head through fields, eval, rename and sort - _score; it is resolved once per statement on the AST and stays the same when a statement is lowered twice.
  • In a k-NN result, and in rows combined with it through join, lookup, append, appendcol, appendpipe, multisearch or union, _score is the k-NN score: no command may replace it, rename another column to _score0, or bring a second _score.
  • Every error is a 400 that never falls back to V2; a statement with a k-NN step skips the V2 attempt. A k-NN step on the analytics-engine route is rejected in the planner, and the SQL transpiler refuses KNN_SCORE.

Aggregation responses and projections. first, last, earliest, latest and dedup pushdowns read vectors whole from _source, dedup rows give every column the value and type of the field it reads, and projections of objects that hold vectors name the vector paths in the _source includes (needed on k-NN derived-source indices). These also fix pre-existing defects for non-vector fields; the same fixes are proposed to main as three separate pull requests (to be linked), and this branch will be rebased onto them once they merge.

Related Issues

Resolves #5876

Check List

  • New functionality includes testing.
  • New functionality has been documented.
  • New functionality has javadoc added.
  • New functionality has a user manual doc added.
  • New PPL command checklist all confirmed.
  • API changes companion pull request created.
  • Commits are signed per the DCO using --signoff or -s.
  • Public documentation issue/PR created.

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.

Step 1 of the PPL vector search sub-task (opensearch-project#5876, RFC opensearch-project#5875) on the
Calcite engine. It adds the type, the canonical plan and the rules that
the knn() function and the knn command will lower to; the grammar and
the k-NN query pushdown come in the next steps. SQL and the V2 engine
keep their current behaviour.

Vector type
- knn_vector fields map to the new ExprCoreType.VECTOR and, on Calcite,
  to ExprVectorType (REAL or TINYINT elements) carrying the dimension,
  data type and space type from the mapping. PPL with Calcite reports
  the type as vector; SQL and V2 type names, values, schemas and error
  texts are unchanged.
- Values are read from _source and converted at the Calcite boundary,
  in scans and in pushed-down scripts, including vectors inside object
  and nested columns, aliases, and base64-encoded vectors (k-NN 3.8+),
  decoded with the data type of the index each hit comes from.
- Multi-index mappings merge as before; indices that disagree on
  dimension, data type or the space type k-NN resolves (including
  k-NN 2.x remote indices) are recorded on the type.
- Vectors are not comparable: comparisons, sorting, grouping, dedup
  keys, keyless joins on a shared vector, graphlookup keys and similar
  uses fail with a 400.

Plan and k
- KNN_SCORE operator and a shared plan builder producing
  Sort(fetch=k, _score DESC) <- Project(..., _score := KNN_SCORE(...))
  <- [Filter(pre-filter)] <- Scan, with field, dimension, placement
  and pre-filter checks.
- k is explicit (k=) or derived from the first following head through
  fields, eval, rename and sort - _score; it is resolved once per
  statement on the AST and is stable when a statement is lowered twice.
- In a k-NN result, and in rows combined with it, _score is the k-NN
  score: no command may replace it, rename another column to _score0,
  or bring a second _score; inputs without k-NN lose only the scan's
  metadata _score.
- Errors are 400s that never fall back to V2; a statement with a k-NN
  step skips the V2 attempt. k-NN steps on the analytics-engine route
  are rejected in the planner, and the SQL transpiler refuses KNN_SCORE.

Aggregation responses
- first, last, earliest, latest and dedup pushdowns read vectors whole
  from _source, and dedup rows give every column the value and type of
  the field it reads (aliases, copies, swaps, script columns, multi-
  valued fields, object fields). These also fix pre-existing defects
  for non-vector fields; the same fixes are proposed separately.
- Projections of objects that hold vectors name the vector paths in the
  _source includes, so derived-source indices return them.

Tests: unit tests for every component, including a test over every PPL
command after a k-NN step, and opt-in integration tests against the
k-NN plugin (-Pknn.integ=true).

Signed-off-by: Lantao Jin <ltjin@amazon.com>
@github-actions

Copy link
Copy Markdown
Contributor

PR Code Analyzer ❗

AI-powered 'Code-Diff-Analyzer' found issues on commit 09974dd.

'Diff too large, requires skip by maintainers after manual review'


Pull Requests Author(s): Please update your Pull Request according to the report above.

Repository Maintainer(s): You can bypass diff analyzer by adding label skip-diff-analyzer after reviewing the changes carefully, then re-run failed actions. To re-enable the analyzer, remove the label, then re-run all actions.


⚠️ Note: The Code-Diff-Analyzer helps protect against potentially harmful code patterns. Please ensure you have thoroughly reviewed the changes beforehand.

Thanks.

@LantaoJin LantaoJin added skip-diff-analyzer Maintainer to skip code-diff-analyzer check, after reviewing issues in AI analysis. skip-diff-reviewer Maintainer to skip code-diff-reviewer check, after reviewing issues in AI analysis. PPL Piped processing language feature labels Oct 16, 2026
@codecov

codecov Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 77.63713% with 424 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.56%. Comparing base (44b1a76) to head (8ad5b18).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
.../opensearch/sql/calcite/CalciteRelNodeVisitor.java 55.38% 100 Missing and 16 partials ⚠️
...in/java/org/opensearch/sql/calcite/knn/KnnAst.java 54.91% 75 Missing and 26 partials ⚠️
...org/opensearch/sql/calcite/knn/KnnPlanBuilder.java 73.45% 41 Missing and 15 partials ⚠️
...search/storage/scan/OpenSearchIndexEnumerator.java 88.29% 2 Missing and 9 partials ⚠️
...rg/opensearch/sql/calcite/type/ExprVectorType.java 91.59% 3 Missing and 7 partials ⚠️
...earch/sql/calcite/utils/OpenSearchTypeFactory.java 84.61% 6 Missing and 4 partials ⚠️
...arch/sql/opensearch/request/AggregateAnalyzer.java 91.66% 3 Missing and 7 partials ⚠️
...rch/sql/opensearch/response/agg/TopHitsParser.java 85.91% 2 Missing and 8 partials ⚠️
...earch/storage/scan/CalciteEnumerableIndexScan.java 0.00% 10 Missing ⚠️
...java/org/opensearch/sql/calcite/SchemaUnifier.java 30.76% 8 Missing and 1 partial ⚠️
... and 24 more

❌ Your project check has failed because the head coverage (66.56%) is below the target coverage (99.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files
@@             Coverage Diff              @@
##               main    #5893      +/-   ##
============================================
+ Coverage     63.87%   66.56%   +2.68%     
- Complexity     9091    10045     +954     
============================================
  Files           962      977      +15     
  Lines         41033    42799    +1766     
  Branches       4668     5029     +361     
============================================
+ Hits          26211    28489    +2278     
+ Misses        13944    13171     -773     
- Partials        878     1139     +261     
Flag Coverage Δ
sql-engine 66.56% <77.63%> (+2.68%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

RelOptUtil.toString ends each line with System.lineSeparator(), so on
Windows the plans of KnnLoweringTest and KnnPlanBuilderTest ended lines
with \r\n against expected plans written with \n, and 19 tests failed.
KnnTestSchema.explain now gives the plan with \n line ends, as Calcite's
Matchers.hasTree compares plans for the other Calcite tests.

CalcitePPLKnnTest lowers each query twice in fresh contexts and expects
the same plan. timewrap writes the query start time, in seconds, into
the plan, so when the two contexts were created in different seconds
the plans differed (seen once on Windows with JDK 25). Both contexts are
now created in the same second.

Signed-off-by: Lantao Jin <ltjin@amazon.com>
…VersionUID

The V2 scripts in these plans are Java-serialized and embed the
serialVersionUID of OpenSearchDataType, which declares none. It changed
from 7128bdc1452f35d3 to 3b91ac532260aa52 because the class gained
public methods (describeTypeName and asAliasTarget). Only those eight
bytes of the script blobs change.

Signed-off-by: Lantao Jin <ltjin@amazon.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature PPL Piped processing language skip-diff-analyzer Maintainer to skip code-diff-analyzer check, after reviewing issues in AI analysis. skip-diff-reviewer Maintainer to skip code-diff-reviewer check, after reviewing issues in AI analysis.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] PPL vector search foundation: VECTOR type, knn() / knn command, k-NN query pushdown (task 1 of #5875)

1 participant