JingsongLi commented on PR #9909:
URL: https://github.com/apache/paimon/pull/9909#issuecomment-5709392022

   The direction looks right — with the index path lacking a final-read filter, 
an unevaluable predicate can neither be answered by an empty bitmap nor 
ignored, so returning `null` ("cannot decide") and routing those ranges through 
the raw search is the only sound fallback. Two implementation notes, both 
optional:
   
   **1. Fold the demote into the shared split step**
   
   Every entry point currently does `splitSearchSplits(...)` followed by 
`demoteUncoverableIndexSplits(...)` (local / batch / Spark / Flink), with the 
ordering contract carried in the javadoc ("Must run after splitSearchSplits and 
before the index splits are read"). That contract is convention only: a future 
reader that prepares splits elsewhere and forgets the second call silently 
regresses to the #9883 behavior. `splitSearchSplits` is already the single 
shared entry point — making it an instance method (or wrapping both steps in a 
`prepareSplits`) makes the ordering structural and removes the contract from 
the javadoc.
   
   **2. The `coveredByRawSplits` / `uncovered` bookkeeping can be dropped**
   
   Every raw read path merges ranges before reading: local/batch via 
`withRawSearch` → `rawRowRanges(...)` (`Range.sortAndMergeOverlap(..., true)`), 
and Spark/Flink's `readRawSplitsInSpark` / `readRawSplitsInFlink` call the same 
helper first. The demote also adds whole split ranges, not set differences. So 
"add only the uncovered ranges" and "add all of them" produce the identical 
merged union — a range already covered by a raw split disappears into that same 
range — and duplicate raw scoring is idempotent (same rowId, same score; 
`result.or(rawResult).topK(limit)` dedups). The method collapses to:
   
   ```java
   protected void prepareSplits(
           List<? extends VectorSearchSplit> splits,
           List<IndexVectorSearchSplit> indexSplits,
           List<RawVectorSearchSplit> rawSplits) {
       splitSearchSplits(splits, indexSplits, rawSplits);
       scalarPreFilter = null;
       if (filter == null || indexSplits.isEmpty()) {
           return;
       }
       RoaringNavigableMap64 matchedRows = scalarMatchedRows(indexSplits); // 
probe once
       if (matchedRows != null) {
           scalarPreFilter = matchedRows; // reused by preFilters
           return;
       }
       List<Range> ranges = new ArrayList<>();
       List<IndexFileMeta> files = new ArrayList<>();
       for (IndexVectorSearchSplit split : indexSplits) {
           ranges.add(new Range(split.rowRangeStart(), split.rowRangeEnd()));
           files.addAll(split.scalarIndexFiles());
       }
       rawSplits.add(new RawVectorSearchSplit(ranges, files, 
vectorIndexType(indexSplits)));
       indexSplits.clear();
   }
   ```
   
   `vectorIndexType(indexSplits)` needs to stay: `rawSearchIndexer` resolves 
the metric from the raw split's indexType. With both steps in one place, 
`scalarPreFilter` also becomes an internal detail of this method instead of a 
lifecycle documented on the class. The only behavioral delta is that the 
demoted split's `scalarIndexFiles` grows from "files of the uncovered splits" 
to "files of all splits"; they only feed `rawPreFilter`, which is a sound 
superset pre-filter with the exact final-read filter as backstop, so 
correctness is unaffected.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to