[spark] Add spark.paimon.search.residual-filter to select search TVF residual behavior - #9964
Draft
LuciferYang wants to merge 2 commits into
Draft
LuciferYang wants to merge 2 commits into
LuciferYang wants to merge 2 commits into
Conversation
LuciferYang
force-pushed
the
fix/spark-vector-search-residual-fail-fast
branch
from
September 18, 2026 11:21
a7bfcfb to
89551ae
Compare
Open
2 tasks
LuciferYang
marked this pull request as draft
September 18, 2026 12:18
Contributor
Author
|
Marking this draft. The fail-fast approach here overlaps with #9855 (merged after this branch was cut), which intentionally applies a non-pushable residual after full-text search and documents the "may be short" result. As a result this PR currently fails #9855's full-text test. I have raised the conflict on #9931 with a proposal to unify all three search TVFs under one option ( |
…residual behavior A vector / hybrid / full-text search truncates to the top-K. A WHERE conjunct on searched-table columns that Spark cannot push into Paimon (a UDF, a column-to-column comparison, an unresolvable cast) stays a Spark residual applied above that top-K, so post-filtering it can only drop rows and may return fewer than K: a row that satisfies the predicate but ranks just outside the returned K is never considered. apache#9855 documented this post-filter behavior for full-text search. This adds spark.paimon.search.residual-filter to select it explicitly instead of letting predicate translatability decide it silently: - post-filter (default): apply the residual above the top-K; the result is a subset of the top-K and may be short. Matches apache#9855. - fail: reject the query with a clear message, matching the Flink vector_search procedure and the strict kNN contract. The same code path covers the static (CheckUnpushableSearchFilter) and lateral (PushDownLateralVectorSearchFilter) forms of all three TVFs. Under fail, rejection fires only for a genuinely non-pushable residual (a conjunct referencing a searched-table column that SparkV2FilterConverter cannot convert), so a Spark-side recheck of an already-pushed predicate, being convertible, is never rejected.
LuciferYang
force-pushed
the
fix/spark-vector-search-residual-fail-fast
branch
from
September 20, 2026 06:21
89551ae to
16c3115
Compare
…on read - Add the search.residual-filter row to the generated Spark connector docs (ConfigOptionsDocsCompletenessITCase requires every option be documented). - Read the option only once a search TVF actually carries a non-pushable residual, so a misconfigured value fails just that query instead of every query in the session. - Use withSparkSQLConf in the new tests, matching the rest of the file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Purpose
close #9931
A vector / hybrid / full-text search truncates to the top-K. A WHERE conjunct on searched-table columns that Spark cannot push into Paimon (a UDF, a column-to-column comparison,
id % 2 = 1, an unresolvable cast) stays a Spark residual applied above that top-K. Post-filtering the top-K can only drop rows, so the result is a subset of the top-K and may be short: a row that satisfies the predicate but ranks just outside the returned K is never considered. This is a silent recall loss.#9855 documented this as the accepted behavior for full-text search (
filtered.subsetOf(unfiltered), may be short). This PR first landed the opposite default, failing the query fast, which broke that test, so #9931 and #9855 had two opposite defaults for the same case. After coordinating on #9931, this unifies the three search TVFs under one option that selects the residual behavior instead of letting "did the optimizer translate the predicate" decide it silently.spark.paimon.search.residual-filter:post-filter(default): apply the residual above the top-K. The result is a subset of the top-K and may be shorter than K. This is the [core][spark][docs] Support row filters in full-text search #9855 behavior.fail: reject the query with a clear message, matching the Flinkvector_searchprocedure and the strict kNN contract.The same code path covers the static form (
CheckUnpushableSearchFilter) and the lateral form (PushDownLateralVectorSearchFilter) of all three TVFs. Underfail, the rejection fires only for a genuinely non-pushable residual: a conjunct that references a searched-table column and thatSparkV2FilterConvertercannot convert. A predicate Paimon accepted is convertible by the same converter the pushdown path uses, so a Spark-side recheck of an already-pushed predicate is never rejected.Tests
PrimaryKeyVectorSearchTest:vector search fail mode rejects a non-convertible residual filtersetsresidual-filter=failand asserts anid > thresholdcolumn-to-column residual is rejected.vector search post-filters a non-convertible residual filter by defaultruns the same query under the default and asserts the result is a subset of the top-K (here empty) rather than an error.vector search fail mode keeps a pushable filterasserts a convertiblethreshold = 100is not rejected underfailand returns the top-K, pinning that a pushed-and-rechecked predicate never trips the check.FullTextSearchTest's existing "predicate that cannot be pushed down is applied after the search" keeps itssubsetOfassertion, now exercising the defaultpost-filtermode.