Skip to content

Fix stale docId attribution in ExpressionScanDocIdIterator under look-ahead projection operators - #19245

Open
noob-se7en wants to merge 1 commit into
apache:masterfrom
noob-se7en:fix-expression-scan-stale-docid-attribution
Open

Fix stale docId attribution in ExpressionScanDocIdIterator under look-ahead projection operators#19245
noob-se7en wants to merge 1 commit into
apache:masterfrom
noob-se7en:fix-expression-scan-stale-docid-attribution

Conversation

@noob-se7en

Copy link
Copy Markdown
Contributor

Problem

ExpressionScanDocIdIterator resolves match positions through a scratch docId buffer shared with the doc-id source operators it creates: every nextBlock() call on the source overwrites the buffer, and processProjectionBlock emitted matches via matchingDocIds.add(_docIdBuffer[i]).

This is only correct while blocks are pulled and processed in lock-step — which the default ProjectionOperator happens to guarantee, but no contract documents. Since #11291 the projection operator is pluggable via ProjectionOperatorUtils; an implementation that pulls several blocks from the doc-id source ahead of consumption (e.g. to overlap remote storage reads with evaluation) leaves the buffer holding the last pulled batch while earlier batches are evaluated. Values and predicate verdicts stay correct (each block fetches values through its own docId copy), but matches get attributed to the wrong docIds — silently wrong results (phantom rows, undercounts from collisions) for any expression predicate AND-ed with an index-based filter.

Fix

  • Resolve match positions through projectionBlock.getDocIds(), which is position-aligned with the fetched values by construction. With the default operator the block's docIds alias the scratch buffer, so behavior and performance there are unchanged.
  • Document the buffer-reuse contract on DocIdSetBlock, BitmapDocIdSetOperator, and DocIdSetOperator: block contents may be invalidated by the next nextBlock() call; consumers holding blocks across pulls must copy.
  • Regression test: a minimal pull-ahead ProjectionOperator registered via ProjectionOperatorUtils; fails before the fix with matches attributed to the last batch's docIds, passes after. Covers the plain, null-handling-enabled, and PredicateEvaluationResult.NULL emission paths, each under both the default and the pull-ahead operator.

Impact

  • Default deployments: no behavior change — the emission reads the same array through a different reference; no added allocation or per-row cost.
  • Deployments plugging in look-ahead projection operators: expression-filter results under AND are now correct regardless of look-ahead depth.

…-ahead projection operators

ExpressionScanDocIdIterator resolves match positions through a scratch buffer shared with its
doc-id source; every nextBlock() call on the source overwrites it. A pluggable ProjectionOperator
(ProjectionOperatorUtils) that pulls multiple blocks ahead invalidates the buffer before earlier
blocks are evaluated, silently attributing matches to the wrong docIds. Emit from the projection
block's own docIds instead, document the buffer-reuse contract, and add a regression test.
@noob-se7en
noob-se7en marked this pull request as draft August 13, 2026 08:46
@noob-se7en noob-se7en added bug Something is not working as expected query Related to query processing tiered-storage Related to tiered storage support labels Aug 13, 2026
@noob-se7en
noob-se7en marked this pull request as ready for review August 13, 2026 08:54
@codecov-commenter

codecov-commenter commented Aug 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 40.00000% with 15 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.96%. Comparing base (5c0c74d) to head (f45c6a0).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
...or/dociditerators/ExpressionScanDocIdIterator.java 40.00% 14 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master   #19245      +/-   ##
============================================
+ Coverage     66.94%   66.96%   +0.01%     
  Complexity     1423     1423              
============================================
  Files          3452     3453       +1     
  Lines        218619   218860     +241     
  Branches      34742    34788      +46     
============================================
+ Hits         146363   146552     +189     
- Misses        60564    60597      +33     
- Partials      11692    11711      +19     
Flag Coverage Δ
integration 100.00% <ø> (ø)
integration1 100.00% <ø> (ø)
integration2 0.00% <ø> (ø)
java-25 66.96% <40.00%> (+0.01%) ⬆️
lane-a 100.00% <ø> (ø)
lane-b 0.00% <ø> (ø)
temurin 66.96% <40.00%> (+0.01%) ⬆️
unittests 66.95% <40.00%> (+0.01%) ⬆️
unittests1 57.70% <40.00%> (+0.03%) ⬆️
unittests2 39.02% <0.00%> (-0.05%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Jackie-Jiang
Jackie-Jiang requested review from yashmayya and a balanced review from Copilot August 13, 2026 18:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes stale document-ID attribution when pluggable projection operators pull blocks ahead of processing.

Changes:

  • Uses projection-block document IDs when emitting predicate matches.
  • Documents scratch-buffer reuse requirements.
  • Adds regression coverage for look-ahead projection and null handling.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
ExpressionScanDocIdIterator.java Attributes matches using block-aligned document IDs.
DocIdSetBlock.java Documents scratch-state lifetime.
DocIdSetOperator.java Documents thread-local buffer reuse.
BitmapDocIdSetOperator.java Documents buffer invalidation behavior.
ExpressionScanDocIdIteratorTest.java Tests default and pull-ahead projection paths.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

/// every `nextBlock()` call on the source overwrites it. If the iterator resolves match positions through that shared
/// buffer instead of the docIds carried by the projection block being processed, all blocks except the last one in a
/// look-ahead window get their matches attributed to the last block's docIds, silently returning wrong results.
public class ExpressionScanDocIdIteratorTest {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add one case for an MV type?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something is not working as expected query Related to query processing tiered-storage Related to tiered storage support

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants