Skip to content

[fix](function) Preserve NULL rows in IP range index results - #68051

Open
Mryange wants to merge 2 commits into
apache:masterfrom
Mryange:fix-ip-range-null-bitmap
Open

Mryange wants to merge 2 commits into
apache:masterfrom
Mryange:fix-ip-range-null-bitmap

Conversation

@Mryange

@Mryange Mryange commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Inverted-index evaluation for is_ip_address_in_range discarded the source column's NULL bitmap. A NOT predicate could therefore treat NULL rows as ordinary non-matching rows and return them incorrectly. The evaluator now reads and propagates the index NULL bitmap so bitmap complement evaluation preserves SQL three-valued logic.

Release note

None

Check List (For Author)

  • Test

    • Regression test
    • Unit Test
    • Manual test (add detailed scripts or steps below)
    • No need to test or manual test. Explain why:
      • This is a refactor/code format and no logic has been changed.
      • Previous test can cover this change.
      • No code files have been changed.
      • Other reason
  • Behavior changed:

    • No.
    • Yes.
  • Does this need documentation?

    • No.
    • Yes.

Check List (For Reviewer who merge this PR)

  • Confirm the release note
  • Confirm test cases
  • Confirm document
  • Add branch pick label

### What problem does this PR solve?

Issue Number: N/A

Problem Summary: Inverted-index evaluation for is_ip_address_in_range discarded the source column's NULL bitmap. A NOT predicate therefore treated NULL rows as ordinary non-matching rows and could return them incorrectly. Read and propagate the index NULL bitmap so bitmap complement evaluation preserves SQL three-valued logic.

### Release note

None

### Check List (For Author)

- Test: Regression test

    - Regression test: inverted_index_p0/test_ip_cidr_not_null

- Behavior changed: No

- Does this need documentation: No
@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?

@Mryange

Mryange commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

/review

@github-actions github-actions Bot 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.

Requesting changes for one data-correctness issue and two implementation/test issues.

Critical checkpoint conclusions:

  • Goal and correctness: propagating the NULL bitmap fixes the direct NOT is_ip_address_in_range(...) case and the checked-in result 3 is correct, but a reachable nested NOT (IP predicate AND FALSE) still returns wrong rows because indexed AND short-circuits while UNKNOWN rows remain.
  • Scope and focus: the diff is small and focused, with no unrelated code or build-surface changes; completing it requires correcting the downstream compound-AND terminal condition.
  • Concurrency and lifecycle: no new threads, locks, or static lifetime are introduced. Cache handles and InvertedIndexResultBitmap deep copies keep cache-owned bitmaps alive and prevent cross-query mutation.
  • Configuration, compatibility, persistence, and writes: no production config, FE/BE protocol, ABI, storage-format, EditLog, transaction, visibility, or write-path change applies.
  • Parallel paths and conditions: IPv4 and IPv6 share the new code, but the Result<bool> condition is not unwrapped and the cold NULL fetch is ordered before the BKD query, causing an avoidable second directory open. Compound AND/NOT ordering also needs the three-valued fix described inline.
  • Tests and results: ordering and expected output are deterministic and follow suite conventions, but the enabled queries can pass through row fallback without executing the changed evaluator, and the nested Boolean counterexample is untested.
  • Observability: existing debug points and cache/index counters are sufficient; the regression should use the existing ip.inverted_index_filtered oracle.
  • Performance and error handling: range and explicit NULL-read statuses are otherwise checked, but has_null() value/error handling and the cold-cache double-open must be corrected.

No additional user focus was supplied. This was a static-only review as required; no builds or tests were run. Three bounded review rounds completed, both final-round passes returned NO_NEW_VALUABLE_FINDINGS, and every candidate was accepted, deduplicated, or dismissed with code evidence.

Comment thread be/src/exprs/function/function_ip.h Outdated
}
// apply for inverted index
std::shared_ptr<roaring::Roaring> null_bitmap = std::make_shared<roaring::Roaring>();
if (iter->has_null()) {

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.

[P2] Unwrap has_null() and avoid the cold double-open

has_null() returns Result<bool>, but using it directly as the condition tests only whether the result has a value (expected::operator bool() returns has_value); a successful false still enters this branch, while an error is silently treated as false. This standalone fetch also runs before the first BKD lookup: on cold NULL/searcher caches it opens and closes the index directory, then the range query opens it again even though handle_searcher_cache() intentionally populates the NULL cache through its already-open directory. Please propagate/unpack the result and acquire NULLs through or after the first selected BKD query (for example by attaching a NULL-bitmap handle to the min query), so the cold path uses one directory open.

Comment thread be/src/exprs/function/function_ip.h Outdated
if (iter->has_null()) {
segment_v2::InvertedIndexQueryCacheHandle null_bitmap_cache_handle;
RETURN_IF_ERROR(iter->read_null_bitmap(&null_bitmap_cache_handle));
null_bitmap = null_bitmap_cache_handle.get_bitmap();

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.

[P1] Preserve UNKNOWN rows through indexed AND before outer NOT

A reachable case is:

NOT
  AND
    is_ip_address_in_range(address, cidr)  -- address is NULL
    flag = 1                               -- flag is 0 (FALSE)

SQL evaluates NULL AND FALSE to FALSE, then outer NOT to TRUE. With this new NULL bitmap, the first child is (data={}, null={row}), but VCompoundPred::evaluate_inverted_index breaks the AND loop solely because the data bitmap is empty. It never evaluates the FALSE child that would clear the row from the NULL bitmap, and outer op_not() subtracts that stale NULL row, incorrectly filtering it out. Please make the AND shortcut continue while UNKNOWN rows can still be resolved (the safe empty terminal state requires both TRUE/data and NULL to be empty), and add an index-forced regression for this shape.

ORDER BY id
"""

sql "SET enable_inverted_index_query = true"

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.

[P2] Make the regression prove the index evaluator ran

These assertions compare only final rows, so a planner miss or an allowed inverted-index bypass falls back to row evaluation and still returns 3; the test can therefore pass without executing the production lines changed by this PR. The existing IP inverted-index suite enables ip.inverted_index_filtered around supported queries: the index evaluator handles that point, while row execute_impl() fails, making fallback visible. Please use the same oracle around both enabled queries (and explicitly pin/instrument the query cache if the repeat is intended to prove a cache hit).

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants