SteNicholas opened a new pull request, #485:
URL: https://github.com/apache/paimon-cpp/pull/485

   <!-- PR titles must follow Conventional Commits: <type>(<optional-scope>): 
<description> -->
   
   ### Purpose
   
   <!-- Linking this pull request to the issue -->
   Linked issue: close #405
   
   <!-- What is the purpose of the change -->
   A full-text search in Paimon C++ could not carry a row filter, so a caller 
had to filter after
   ranking and got fewer than `limit` rows. This adds 
`FullTextSearchBuilder::WithFilter()`, aligned
   with Java `FullTextSearchBuilder#withFilter` (apache/paimon#9855): only the 
matching rows are
   ranked, so the result is the top k among them, and they reach the existing 
index as include row
   ids, so BM25 statistics stay those of the full corpus.
   
   - **Predicate split**: as Java 
`PartitionPredicate#splitPartitionPredicatesAndDataPredicates` does,
     conjuncts over partition fields alone are ANDed into the partition filter, 
and the rest become the
     data filter. A conjunct over both partition and data fields stays in the 
data filter and also
     restricts the partitions it implies, projected as Java 
`PredicateProjectionConverter` does, so
     `(p = 1 AND a > 0) OR (p = 2 AND a > 0)` also searches only `p = 1 OR p = 
2`. Repeated calls are
     ANDed.
   - **`DataEvolutionFullTextScan`**: also keeps the index files that index a 
filter field, and
     attaches to each split the scalar pre-filter files that intersect its 
shard: files that are not
     full-text indexes, or full-text indexes with extra fields. Unlike in Java, 
the latter rule holds
     whichever column the index serves, as `lucene-fts` answers every scalar 
predicate with all of its
     rows. A scalar index file whose row range is negative, reversed or 
overflows int64 fails the plan.
   - **`IndexFullTextSearchSplit`**: version 2 of the encoding appends the 
scalar index files, as
     version 2 of Java `IndexFullTextSearchSplit` does; version 1 splits are 
still read.
   - **`DataEvolutionFullTextRead#matchedRows`**: resolves the data filter 
before ranking with the
     scalar indexes of the splits, read per field as one union 
(`GlobalIndexScanImpl::
     CreateForScalarFilters` and `ScanWithCoverage`, as Java 
`DataEvolutionGlobalIndexScanner`).
     - Rows that no scalar index covers are excluded, as in the `fast` 
`scalar-index.search-mode`. If
       no index can evaluate the filter at all, nothing is searched and a 
warning is logged.
     - If the indexes return only a superset, the candidates are excluded with 
a warning by default,
       so the search never returns a non-matching row. With the new
       `global-index.filter.refine-from-data` option (default `false`, Java 
description), they are
       verified against the data instead.
     - Each split searches with include row ids `searchRowRanges ∧ liveRows ∧ 
matchedRows`.
   - **`GlobalIndexEvaluatorImpl`**: reports the fields that decided a result
     (`EvaluateWithContributingFields`, as Java 
`GlobalIndexEvaluator.Evaluation`), so that a superset
     can be told from an exact answer: not every filter field contributed, or 
there is a `Contains`,
     `EndsWith` or `Like` leaf. Unlike in Java, a field with a conjunct that no 
index could evaluate
     does not count even if another of its conjuncts was evaluated, as the 
result is then only a
     superset for that field.
   - **`FilteredRowIdReader`**: as in Java, reads only the filter columns and 
`_ROW_ID` of the
     candidates, pinned to the planned snapshot and with the schema that the 
search resolved, so a
     column renamed or replaced since then cannot point the filter at another 
field. Unlike in Java,
     once the planned snapshot has expired, a search of `scan.tag-name` reads 
the tag instead, which
     must still refer to the planned snapshot.
   - **`StaticFromTagStartingScanner`**: reads tags from the branch of the 
scan, as in Java, instead of
     always from the main branch.
   - **Options**: `scalar-index.search-mode` falls back on 
`global-index.search-mode`, as Java
     `CoreOptions#scalarIndexSearchMode` does. Only `fast` is supported so far; 
a row filter in `full`
     or `detail` fails with `NotImplemented`, as does a filter with a full-text 
match.
   
   Not part of this change:
   
   - The `full` and `detail` scalar search modes (#406), which decide the rows 
that no scalar index
     covers by reading their filter columns.
   - Row filters on a primary-key full-text index (#410), which is not 
searchable yet.
   
   ### Tests
   
   <!-- List UT and IT cases to verify this change -->
   UT:
   
   - `src/paimon/core/table/source/data_evolution_full_text_read_test.cpp`, 
with fake scalar indexers:
     `TestFilterRanksOnlyMatchedRows`, 
`TestFilterExcludesRowsThatIndexesCannotDecide` (no scalar
     file, a declining or multi-field one, a partly indexed conjunction, 
`Contains`/`EndsWith`/`Like`,
     rows that no scalar file covers), 
`TestFilterUnionsIndexesOfDifferentTypes`, and filter validation in
     `TestBuilderValidatesSettings` (unknown field, `_ROW_ID`, wrong type, 
full-text match, a scalar
     search mode other than `fast`, null filter).
   - `src/paimon/core/table/source/data_evolution_full_text_scan_test.cpp`: 
`TestScalarPreFilterFiles`
     and `TestIntersectingFiles`, including invalid row ranges.
   - `src/paimon/core/table/source/full_text_search_split_test.cpp`: 
`TestSerializeRoundTrip` (the
     version 2 bytes are pinned and version 1 bytes still read), 
`TestSerializeScalarIndexFiles`, and
     invalid scalar index files in `TestDeserializeRejectsInvalidBytes`,
     `TestDeserializeRejectsCorruptEntries` and `TestCreateValidatesArguments`.
   - `src/paimon/core/table/source/filtered_row_id_reader_test.cpp`:
     `TestExactWhenEveryFilterFieldContributed` and 
`TestCandidateOnlyLeavesAreNotExact`.
   - `src/paimon/core/global_index/global_index_evaluator_impl_test.cpp`: 
`ReportsContributingFields`,
     `AndWithEmptyResultShortCircuits` and `CollectFieldIds`.
   - `src/paimon/core/core_options_test.cpp`: `TestParseIndexSearchMode` for 
the full-text and scalar
     families, and `global-index.filter.refine-from-data` in `TestDefaultValue` 
and `TestFromMap`.
   
   IT (`test/inte/global_index_test.cpp`), aligned with Java 
`NativeFullTextRowFilterTest` and
   `FullTextSearchBuilderTest`:
   
   - `TestFullTextTableFullTextSearchWithFilter` and 
`TestLuceneTableFullTextSearchWithFilter`:
     equality, range, `IN`, `NotEqual`, `OR` across fields, no and all matching 
rows, equality on a
     BTree of the text column, accumulated filters, the top row among matching 
rows with `limit` 1,
     a column without an index or with only a full-text index, a partly indexed 
conjunction and
     `Contains`/`EndsWith`/`Like` with refine on and off, scalar index files 
attached only for filtered
     columns, serialized splits, and refine with `scan.snapshot-id`, 
`scan.tag-name` and
     `scan.timestamp-millis`.
   - `TestFullTextTableFullTextSearchWithPartitionAndDataFilter` and
     `TestLuceneTableFullTextSearchWithPartitionAndDataFilter`: partition and 
data predicates combined,
     with `SetPartitionFilter()` and refine, and the partitions that a mixed 
conjunct implies.
   - `TestFullTextSearchSkipsRowsDeletedByDeletionVectors` and
     `TestLuceneSearchSkipsRowsDeletedByDeletionVectors`: a filter with 
deletion vectors, a scalar index
     file per shard, and refine across shards.
   - `TestFilteredRowIdReader`: deletion vectors, a pinned older snapshot, a 
tag moved to another
     snapshot, the tag fallback once the snapshot has expired (also with an 
empty plan), a branch tag,
     and a column renamed or replaced after planning.
   
   ### API and Format
   
   <!-- Does this change affect API in include dir or storage format or 
protocol -->
   New public API: `FullTextSearchBuilder::WithFilter()`, and 
`Options::SCALAR_INDEX_SEARCH_MODE` and
   `Options::GLOBAL_INDEX_FILTER_REFINE_FROM_DATA`. The serialized form of 
full-text search splits,
   which is specific to Paimon C++, moves to version 2 and still reads version 
1. No storage format or
   protocol change.
   
   ### Documentation
   
   <!-- Does this change introduce a new feature -->
   Yes. `docs/source/user_guide/global_index.rst` adds a "Row Filters" section 
covering the predicate
   split, implied partitions, the `fast` mode, excluded candidates and
   `global-index.filter.refine-from-data`, and the docs of 
`FullTextSearchBuilder`, `FullTextScan`,
   `FullTextRead` and the new options describe the filter.
   
   ### Generative AI tooling
   
   <!--
   If generative AI tooling has been used in the process of authoring this 
patch, please include the
   phrase: 'Generated-by: ' followed by the name of the tool and its version.
   If no, write 'No'.
   Please refer to the [ASF Generative Tooling 
Guidance](https://www.apache.org/legal/generative-tooling.html) for details.
   -->
   Generated-by: Claude Code 2.1.311 (Claude Opus 5.5)
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)
   


-- 
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