Repository navigation
branch-4.2: [fix](be) Support BM25 scoring on keyword SEARCH indexes #69051 - #69318
Merged
Merged
Conversation
…pache#69051 ### What problem does this PR solve? Related PR: apache#69051 Problem Summary: Backport keyword SEARCH BM25 scoring to branch-4.2. Use unit frequency and encoded norm one when keyword postings omit frequencies or norms. Preserve keyword case and whitespace, physical SNII statistics, and the target branch's restrictions for analyzed indexes without scoring metadata. Keep keyword MATCH filters out of score collection and score docs-only terms individually in SNII conjunctions. Adapt the source interfaces to branch-4.2, which does not have master's normalizer-only scoring exception. Pass CLucene norm presence through posting partitions so that leaf bounds and partition switches preserve unit length. Retain the target compile-check wrappers. Historical Variant parser-option edits are not part of the landed source change and are not included. ### Release note SEARCH with score() supports keyword inverted indexes, including conjunctions, TopN ordering and WAND execution. ### Check List (For Author) - Test: The ASAN BE/FE build and 369 targeted ASAN unit tests passed. Three pre-existing tests are disabled. Both source regression suites and their expected results are preserved; execution evidence is included in the PR validation report. - Behavior changed: Yes. Keyword SEARCH returns valid BM25 scores instead of dropping matches or reporting unsupported metadata. - Does this need documentation: No new syntax is introduced. (cherry picked from commit 4016f20) Conflicts: be/src/storage/index/index_reader_helper.h be/src/storage/index/inverted/clucene/spi/clucene_postings_cursor.h be/src/storage/index/inverted/reader/collection_statistics.cpp be/src/storage/index/inverted/reader/collection_statistics.h be/test/storage/index/inverted/query/engine/boolean_postings_bench_test.cpp be/test/storage/index/inverted/query/engine/multi_segment_collector_test.cpp be/test/storage/index/inverted/reader/collection_statistics_test.cpp
Contributor
|
Thank you for your contribution to Apache Doris. Please clearly describe your PR:
|
Member
Author
|
run buildall |
Contributor
BE Regression && UT Coverage ReportIncrement line coverage Increment coverage report
|
yiguolei
approved these changes
Oct 21, 2026
Contributor
|
PR approved by anyone and no changes requested. |
Contributor
|
PR approved by at least one committer and no changes requested. |
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.
What problem does this PR solve?
Issue Number: None
Related PR: #69051
Problem Summary: Backport the merged keyword SEARCH BM25 fix to branch-4.2. Projecting
score()on keyword indexes can drop matching rows or reject missing scoring metadata. Keyword MATCH filters can also request statistics that were not collected, and scored SNII conjunctions can incorrectly require positions from docs-only terms.Use unit frequency and encoded norm 1 for keyword postings, preserve whole keyword values, and score docs-only terms through the existing individual term scorer. Legacy indexes without norms use average length 1; SNII retains its physical collection statistics. The BM25 formula and the restrictions for analyzed indexes remain unchanged.
Source commit:
4016f207bfe7eff77f56f4d98d23629888539462.Target branch adaptations
reader->hasNorms()through posting partitions and restore it on partition switches, including WAND bounds.9b356cb594f60b802de117ff9b5915b60500d424into the landing parent reproduces the exact landed tree57f5e5d63fafe808a83bc393e36ea59a6e367ca0.Release note
SEARCH with
score()supports keyword inverted indexes, including conjunctions, TopN ordering, WAND execution, and keyword MATCH filters.Check List (For Author)
Backport validation
source ./custom_env.sh && bash build.sh --be --fe -j 24(ASAN BE, FE and packaged output; 45m 19s).bash run-be-ut.sh -j 48, defaultGLIBC_COMPATIBILITY=ON; no local CMake test-source selection.bash run-be-ut.sh --run --filter="BooleanPostingsBench.*:CluceneIndexSourceTest.*:CollectionStatisticsDetailedTest.*:CollectionStatisticsSniiScoringTest.*:CollectionStatisticsTest.*:CursorChainedPostings.*:DocSetCollectorTest.*:ExpansionLeafTest.*:FunctionSearchNullTest.*:FunctionSearchTest.*:IndexReaderHelperTest.*:ListedTermsTest.*:ListedTermsWalkTest.*:MultiSegmentCollectorTest.*:PhraseQueryV2Test.*:PhraseScorerStreamTest.*:PrefixQueryV2Test.*:SegmentPostingsTest.*" -j 48; 369 passed, 3 existing disabled benchmarks, zero failures or sanitizer reports. Coverage includes postings/norms, collection statistics, MATCH eligibility, conjunctions, prefix/phrase queries, cursor chaining, and SEARCH execution/NULL handling.bash run-regression-test.sh --run -d search -s test_search_score_raw_index,test_search_score_keyword_filters -parallel 1on an isolated ASAN FE/BE instance. Both suites passed with zero failed/fatal/skipped scripts. All 114 tags ran exactly once; source expected files are byte-identical. Coverage includes V2/V3/SNII, WAND on/off, whole keyword values, TopN/ascending order, internal AND, external equality/MATCH filters, and implicit/explicitparser=none.809eb7498a0fdfce905bdde7a350f3ba207947bdagainst latest branch-4.2bcaaa5f293674f1adba0dfa52c8f8f57fb5e9549; complete committed PR English check (17 source files, zero exceptions).62cb1df3a7f8124ad338e8788e4faaf71f030b07. The subsequent commit-message amendment preserved the exact tested code tree. The newer target commit changes JSON_SEARCH path escaping and has no changed-file overlap with this pick.NOLINTENDinbe/src/core/types.h:578. The same diagnostic was reproduced with all modified existing production files restored to target-base contents through a VFS overlay; the offending header is byte-identical to base62cb1df3a7f8124ad338e8788e4faaf71f030b07. No unrelated baseline fix is included.Source hunk audit
be/src/storage/index/index_reader_helper.cpp@@ -0,0 +1,54 @@be/src/storage/index/index_reader_helper.h@@ -103,36 +103,12 @@ public:be/src/storage/index/inverted/clucene/spi/clucene_postings_cursor.h@@ -155,7 +155,7 @@ public:be/src/storage/index/inverted/clucene/spi/clucene_postings_cursor.h@@ -305,6 +305,8 @@ public:be/src/storage/index/inverted/clucene/spi/clucene_postings_cursor.h@@ -314,8 +316,8 @@ private:be/src/storage/index/inverted/query/engine/boolean_query/operator_boolean_weight.hcomplete postimagebe/src/storage/index/inverted/query/scoring/bm25_similarity.h@@ -24,14 +24,8 @@be/src/storage/index/inverted/reader/collection_statistics.cpp@@ -44,9 +44,11 @@ namespace collection_statistics_detail {be/src/storage/index/inverted/reader/collection_statistics.cpp@@ -264,10 +266,11 @@ Status CollectionStatistics::process_segment(const RowsetSharedPtr& rowset,be/src/storage/index/inverted/reader/collection_statistics.cpp@@ -337,12 +340,11 @@ Status CollectionStatistics::process_segment(const RowsetSharedPtr& rowset,be/src/storage/index/inverted/reader/collection_statistics.cpp@@ -351,10 +353,8 @@ Status CollectionStatistics::process_segment(const RowsetSharedPtr& rowset,be/src/storage/index/inverted/reader/collection_statistics.cpp@@ -375,11 +375,12 @@ Status CollectionStatistics::process_segment(const RowsetSharedPtr& rowset,be/src/storage/index/inverted/reader/collection_statistics.h@@ -86,8 +86,9 @@ private:be/src/storage/index/inverted/reader/collection_statistics.h@@ -121,11 +122,12 @@ struct SniiScoringSegmentStats {be/test/storage/index/inverted/query/engine/boolean_postings_bench_test.cppcomplete postimagebe/test/storage/index/inverted/query/engine/boolean_query/listed_terms_test.cppcomplete postimagebe/test/storage/index/inverted/query/engine/multi_segment_collector_test.cppcomplete postimagebe/test/storage/index/inverted/query/engine/segment_postings_test.cppcomplete postimagebe/test/storage/index/inverted/query/spi/fake_index_source.hcomplete postimagebe/test/storage/index/inverted/reader/collection_statistics_test.cpp@@ -667,6 +667,7 @@ protected:be/test/storage/index/inverted/reader/collection_statistics_test.cpp@@ -676,8 +677,8 @@ protected:be/test/storage/index/inverted/reader/collection_statistics_test.cpp@@ -975,26 +976,45 @@ TEST_F(CollectionStatisticsTest, LegacyV3RejectsSegmentWrittenWithoutNorms) {be/test/storage/index/inverted/reader/collection_statistics_test.cpp@@ -1230,9 +1250,10 @@ TEST(CollectionStatisticsSniiScoringTest, ResolveRejectsSegmentWithoutPositions)be/test/storage/index/inverted/reader/collection_statistics_test.cpp@@ -1939,7 +1960,9 @@ TEST_F(CollectionStatisticsTest, CollectSkipsIndexWithoutAnalyzer) {be/test/storage/index/inverted/reader/collection_statistics_test.cpp@@ -2377,7 +2400,7 @@ TEST_F(CollectionStatisticsTest, SearchArrayStringSelectsFulltextLeafIndex) {be/test/storage/index/inverted/reader/collection_statistics_test.cpp@@ -2397,8 +2420,8 @@ TEST_F(CollectionStatisticsTest, SearchExactIgnoresAnalyzedBindingHint) {be/test/storage/segment/index_reader_helper_test.cpp@@ -22,6 +22,7 @@be/test/storage/segment/index_reader_helper_test.cpp@@ -380,6 +381,7 @@ TEST_F(IndexReaderHelperTest, IsNeedSimilarityScoreWithInvertedIndexQueryTypeTesbe/test/storage/segment/index_reader_helper_test.cpp@@ -448,6 +450,7 @@ TEST_F(IndexReaderHelperTest, IsNeedSimilarityScoreWithTExprOpcodeTest) {be/test/storage/segment/index_reader_helper_test.cpp@@ -460,3 +463,51 @@ TEST_F(IndexReaderHelperTest, IsNeedSimilarityScoreWithTExprOpcodeTest) {regression-test/data/search/test_search_score_keyword_filters.outcomplete postimageregression-test/data/search/test_search_score_raw_index.outcomplete postimageregression-test/suites/search/test_search_score_keyword_filters.groovycomplete postimageregression-test/suites/search/test_search_score_raw_index.groovycomplete postimagebe/src/storage/index/inverted/clucene/spi/clucene_index_source.cppadditional target adaptationbe/test/core/data_type_serde/data_type_serde_arrow_test.cpp@@ -957,9 +957,8 @@ TEST(DataTypeSerDeArrowTest, PlainArrowConverterDoesNotInferIcebergUuid) {be/test/core/data_type_serde/data_type_serde_arrow_test.cpp@@ -1054,9 +1053,8 @@ TEST(DataTypeSerDeArrowTest, IcebergVariantExtensionAndParquetSchema) {be/test/core/data_type_serde/data_type_variant_v2_serde_jsonb_test.cpp@@ -112,9 +112,8 @@ std::string json_at(const DataTypeVariantV2SerDe& serde, const IColumn& column,be/test/exprs/variant_v2_execution_test.cpp@@ -49,9 +49,8 @@ TEST(VariantV2ExecutionTest, TypePropertiesDoNotAffectEquality) {Range-diff review
Compared the landed source commit against the target pick with
git range-diff 4016f207bfe^..4016f207bfe HEAD^..HEAD.Representative differences:
## be/src/storage/index/index_reader_helper.cpp (new) ## @@ @@ be/src/storage/index/index_reader_helper.cpp (new) + // parser=none can inherit support_phrase=true from FE defaults. MATCH statistics skip + // these keyword indexes, so their filters must not request a scoring context either. + return inverted_index::InvertedIndexAnalyzer::should_analyzer(properties) && -+ (get_parser_phrase_support_string_from_properties(properties) == -+ INVERTED_INDEX_PARSER_PHRASE_SUPPORT_YES || -+ IndexReaderHelper::is_normalizer_only(properties)); ++ get_parser_phrase_support_string_from_properties(properties) == ++ INVERTED_INDEX_PARSER_PHRASE_SUPPORT_YES; +} + +} // namespace @@ be/src/storage/index/index_reader_helper.h: public: return iter->get_reader(InvertedIndexReaderType::STRING_TYPE) != nullptr; } -+ // MATCH filters score analyzed phrase indexes and single-term normalizers. ++ // MATCH filters score analyzed indexes with phrase support. static bool is_need_similarity_score(InvertedIndexQueryType query_type, - const TabletIndex* index_meta) { - if (query_type == InvertedIndexQueryType::MATCH_ANY_QUERY || + ## be/src/storage/index/inverted/clucene/spi/clucene_index_source.cpp ## +@@ be/src/storage/index/inverted/clucene/spi/clucene_index_source.cpp: ClucenePostingsCursor::Partition open_postings(lucene::index::IndexReader* reade + lucene::index::Term* term, bool positions, + bool scoring, const io::IOContext* io_ctx) { + ClucenePostingsCursor::Partition result; ++ result.has_norms = reader->hasNorms(term->field()); + if (positions) { + auto iter = make_term_positions_ptr(reader, scoring, io_ctx); + result.positions = iter.get(); ## be/src/storage/index/inverted/reader/collection_statistics.cpp ## @@ be/src/storage/index/inverted/reader/collection_statistics.cpp: namespace collection_statistics_detail { + Result<SniiScoringSegmentStats> resolve_snii_scoring_segment(uint64_t index_doc_count, uint64_t sum_total_term_freq, - bool has_positions, bool has_norms, -+ bool is_analyzed, - bool single_term_normalizer) { -- // Lowercase-normalized docs-only postings use unit frequency and length for scoring. -- if ((!has_positions || !has_norms) && !single_term_normalizer) { -+ // Keywords and lowercase-only normalizers can omit both sections: a matched value -+ // contributes one term with unit frequency and length. Other analyzed sources need both. -+ if (is_analyzed && !single_term_normalizer && (!has_positions || !has_norms)) { +- bool has_positions, bool has_norms) { +- if (!has_positions || !has_norms) { ++ bool has_positions, bool has_norms, ++ bool is_analyzed) { ++ // Keyword terms use unit frequency and length without positions or norms. ++ // Analyzed sources still require both sections. ++ if (is_analyzed && (!has_positions || !has_norms)) { return ResultError(Status::Error<ErrorCode::INVERTED_INDEX_NOT_SUPPORTED, false>( "SNII scoring requires positions and norms; this segment was written without " "positions or without norms. Norms are left out when the index sets \"norms\" = "The first and third groups omit the master-only normalizer exception and preserve branch-4.2's existing analyzed-index restrictions. The second group supplies the older target's missing norm-presence plumbing; the landed source parent already has it. Other differences are the corresponding helper signatures and tests, retained compile-check context, and backport commit metadata. The source hunk that updates the normalizer-only test is inapplicable because that feature and test do not exist on branch-4.2. The complete audit above accounts for all source hunks and the extra target plumbing file.
Check List (For Reviewer who merge this PR)