Skip to content

branch-4.2: [fix](be) Score regexp and wildcard patterns with a constant like Elasticsearch #69815 - #69922

Merged
yiguolei merged 1 commit into
branch-4.2from
auto-pick-69815-branch-4.2
Nov 2, 2026
Merged

yiguolei merged 1 commit into
branch-4.2from
auto-pick-69815-branch-4.2

Conversation

@github-actions

@github-actions github-actions Bot commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

Cherry-picked from #69815

…sticsearch (#69815)

### What problem does this PR solve?

Issue Number: None

Related PR: #69052

Problem Summary: score() of pattern queries disagrees with Elasticsearch
and with itself:

    -- 'Inverted indexes are suitable for full text search',
    -- 'The parser splits text into multiple terms', ...
SELECT score() FROM document WHERE SEARCH('content:/.*ext/'); -- BM25
sum
SELECT score() FROM document WHERE SEARCH('content:*ext'); -- BM25 sum
    SELECT score() FROM document WHERE SEARCH('content:te*');      -- 1
    SELECT score() FROM document WHERE content MATCH_REGEXP 'ext'; -- 0

Elasticsearch scores a query_string /regexp/, wildcard or prefix, and a
regexp, wildcard or prefix query, with a constant 1 per matching row
(default rewrite constant_score_blended); measured on 9.5.5 with the
rows above: 1.0 for both matching rows. Only an explicit
"rewrite": "scoring_boolean" sums the BM25 of the expanded terms. Next
to a scored clause in a bool must, a regexp adds exactly 1.

#69052 made SEARCH regexp and wildcard sum the BM25 of every expanded
term, i.e. scoring_boolean always on, while prefix stayed constant. That
sum also takes idf and avgdl from the current segment, not from the
tablet statistics MATCH uses: two inserts of the same text scored
0.2876821 and 1.3862942 for identical 'apple' rows, while MATCH 'apple'
scored 0.6931471 for both. MATCH_REGEXP never requested a scoring
context, so it scored 0 like a filter.

Fix:
1. Revert the scoring part of #69052: ExpandWeight scores every pattern
   with the constant scorer again. The test fixture extensions that
   #69051 built on are kept.
2. MATCH_REGEXP requests scoring on every index; the regexp expansion
   then publishes the constant 1. The MATCH statistics collector skips
   it, since a constant reads no statistics and the pattern is not text
   to analyze.
@github-actions
github-actions Bot requested a review from yiguolei as a code owner November 1, 2026 03:21
@hello-stephen

Copy link
Copy Markdown
Contributor

Thank you for your contribution to Apache Doris.
Don't know what should be done next? See How to process your PR.

Please clearly describe your PR:

  1. What problem was fixed (it's best to include specific error reporting information). How it was fixed.
  2. Which behaviors were modified. What was the previous behavior, what is it now, why was it modified, and what possible impacts might there be.
  3. What features were added. Why was this function added?
  4. Which code was refactored and why was this part of the code refactored?
  5. Which functions were optimized and what is the difference before and after the optimization?

@hello-stephen

Copy link
Copy Markdown
Contributor

run buildall

@hello-stephen

Copy link
Copy Markdown
Contributor

BE UT Coverage Report

Increment line coverage 100.00% (13/13) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 64.68% (30242/46758)
Line Coverage 49.53% (319914/645867)
Region Coverage 45.35% (258001/568946)
Branch Coverage 46.99% (121844/259293)

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 100.00% (13/13) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 76.15% (34580/45411)
Line Coverage 61.00% (390664/640483)
Region Coverage 57.83% (329519/569832)
Branch Coverage 58.80% (152293/258999)

@yiguolei
yiguolei merged commit b2eb3c9 into branch-4.2 Nov 2, 2026
35 of 36 checks passed
@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor Author

PR approved by anyone and no changes requested.

@github-actions github-actions Bot added the approved Indicates a PR has been approved by one committer. label Nov 2, 2026
@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor Author

PR approved by at least one committer and no changes requested.

yiguolei pushed a commit that referenced this pull request Nov 5, 2026
…ARCH #69790 (#70008)

Backport of #69790 to branch-4.2.

### What problem does this PR solve?

Issue Number: None

Related PR: #69790, #69051

Problem Summary: score() of a MATCH on an untokenized (keyword) inverted
index is always 0, while SEARCH on the same field scores BM25:

    INDEX idx_redirect (redirect) USING INVERTED
    -- one row ('BP')
    SELECT score() FROM t WHERE redirect MATCH 'BP';  -- 0
    SELECT score() FROM t WHERE SEARCH('redirect:BP'); -- 0.2876821

Elasticsearch scores match, term and match_phrase on a keyword field
with BM25 in query context, so the score is the idf of the whole value.
#69051 (already on 4.2) made the engine score keyword postings but kept
MATCH out. This makes a keyword index eligible for MATCH scoring and
collects its whole value as one term, the one the reader looks up. It
also carries the follow-up fixes from #69790's review:
- A keyword MATCH_PHRASE drops its ` ~N` slop before collecting.
- A MATCH on a non-string leaf such as `CAST(n AS STRING)` collects
nothing.
- A VARIANT path that a segment indexed as BKD, or does not hold, adds
no statistics. This covers the BKD searcher coming from the searcher
cache, and SNII.
- A SNII index kind is looked up without copying the directory.

### Branch adaptations

- `can_score_match` keeps 4.2's analyzed rule (support_phrase only);
branch-4.2 has no lowercase-normalizer clause.
- `test_omit_norms` does not exist on 4.2, so its `.out` change is
dropped. The branch was cut before #69922 (the 4.2 backport of #69815)
and then merged with branch-4.2 to resolve its conflict in
`index_reader_helper_test.cpp`: both tests are kept, as on master.
- `test_match_keyword_score` inserts its VARIANT rows as JSON strings:
`parse_to_variant` in `VALUES` fails on 4.2 with a max-subcolumns
conversion error. The expected output is master's file, unchanged.
- **#69901 (BM25 statistics per index) is not on 4.2.** A MATCH on a
keyword index and a MATCH on an analyzed index of the same column in one
`score()` query (`name MATCH 'apple' USING ANALYZER english AND name
MATCH 'apple' USING ANALYZER none`) now fails with "Scoring predicates
for one field select different inverted indexes". Master behaved the
same before #69901. Before this change, that query ran with the keyword
clause scoring 0. Each clause alone scores fine. Two analyzed indexes on
one column already fail this way on 4.2. Backporting #69901 lifts this.

### Validation

On this branch merged with branch-4.2 c2aaed9, ASAN build:
- BE UT: 1611 tests in `IndexReaderHelperTest`, `CollectionStatistics*`
and `*Snii*` pass.
- Regression: `test_match_keyword_score` passes against master's `.out`
on V2/V3/SNII. The following also pass:
`test_match_phrase_prefix_score`, `test_bm25_score`,
`test_score_filter_matrix`, `test_score_filter_usage`,
`test_search_score_topn_predicates`, `test_multi_analyzer_query`,
`test_match_using_analyzer`, `test_single_column_multi_index` (with
debug points on), `test_search_score_keyword_filters`,
`test_search_score_raw_index`, `test_search_score_pattern_constant`.
- Local note: the local thirdparty predates the lance-c upgrade, so
`lance_reader.cpp`'s `lance_scanner_set_scalar_index_auto_selection`
call was stubbed for the local build only. It is not part of this PR.

### Release note

score() of MATCH / MATCH_ALL / MATCH_PHRASE on a keyword (untokenized)
inverted index now returns the BM25 score of the value, the same as
SEARCH, instead of 0.

### Check List (For Author)

- Test: Regression test / Unit Test (see Validation)
- Behavior changed: Yes. Keyword MATCH contributes its BM25 score to
score() instead of 0.
- Does this need documentation: No

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by one committer. reviewed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants