Skip to content

Iceberg nullable minmax pruning - #2657

Draft
ianton-ru wants to merge 5 commits into
antalya-26.8from
feature/antalya-26.8/iceberg-nullable-minmax-pruning
Draft

ianton-ru wants to merge 5 commits into
antalya-26.8from
feature/antalya-26.8/iceberg-nullable-minmax-pruning

Conversation

@ianton-ru

Copy link
Copy Markdown

Changelog category (leave one):

  • Performance Improvement

Changelog entry (a user-readable short description of the changes that goes to CHANGELOG.md):

Iceberg file min/max pruning and Parquet row-group and page min/max pruning can now skip a nullable column when its non-null range misses the predicate and SQL NULL cannot make the predicate true. IS NULL, assumeNotNull, and IN / NOT IN with transform_null_in still read a chunk that contains NULL.

Documentation entry for user-facing changes

No new setting or syntax. Existing Iceberg manifest min/max pruning and Parquet filter pushdown (input_format_parquet_filter_push_down, input_format_parquet_page_filter_push_down) apply the predicate to the non-null bounds of a nullable column.

CI/CD Options

Exclude tests:

  • Fast test
  • Integration Tests
  • Stateless tests
  • Stateful tests
  • Unit tests
  • Performance tests
  • Aarch64 tests
  • All with ASAN
  • All with TSAN
  • All with MSAN
  • All with UBSAN
  • All with Coverage
  • All Regression
  • Disable CI Cache

Regression jobs to run:

  • Fast suites (mostly <1h)
  • Aggregate Functions (2h)
  • Alter (1.5h)
  • Benchmark (30m)
  • CAS (content-addressed storage; Antalya only)
  • ClickHouse Keeper (1h)
  • Iceberg (2h)
  • LDAP (1h)
  • OAuth (5m)
  • Parquet (1.5h)
  • RBAC (1.5h)
  • SSL Server (1h)
  • S3 (2h)
  • S3 Export (2h)
  • Swarms (30m)
  • Tiered Storage (2h)

Motivation

Unity liquid clustering on Iceberg is a sort order plus per-file lower_bounds / upper_bounds. It is not a partition spec. For a nullable clustering column, ClickHouse already had those bounds, but it refused to use them unless null_value_counts was present and exactly 0. Parquet did the same for row groups and pages: a nullable column with any null widened the min/max to infinity, so a predicate such as partner_id = 'sg2o27m' could not skip a chunk whose other values missed the probe.

A null does not satisfy that comparison. The chunk can be skipped. IS NULL must still read it.

Related: #2395 reads Delta delta.clustering only. It does not change the Iceberg or Parquet readers.

Approach

KeyCondition::mayBeTrueOnNull walks the predicate and reports whether it can be true when one key column is SQL NULL. A bare comparison is unknown on NULL, so WHERE does not keep that row. IS NULL is true. IS NOT NULL is false.

Iceberg manifest pruning skips a file when the non-null range misses and either the manifest says the column has no nulls or mayBeTrueOnNull is false. Parquet row-group and page stats keep the finite non-null range in that same case. An all-null row group has no finite min/max and is dropped only when NULL cannot match.

Two predicates are true for NULL and must not be pruned away:

  • assumeNotNull replaces NULL with the nested value, so a monotonic wrapper is treated as able to match.
  • nullIn / notNullIn, which transform_null_in rewrites IN / NOT IN into, can match NULL. The RPN stores both the normal and the null-comparing forms as the same set atom, so a chunk that contains NULL is kept for every IN / NOT IN.

A null-preserving wrapper such as toDate is also kept when the chunk contains NULL. The rows are unchanged. The chunk is not pruned from that column's bounds.

Tests

  • 05386_iceberg_nullable_minmax_prunes_null_rejecting_predicate: an Iceberg file whose non-null range misses partner_id = '...' or NOT partner_id = '...' is pruned even if it contains NULL. IS NULL keeps it.
  • 05387_parquet_nullable_minmax_null_rejecting: the same rule for Parquet row groups and pages, including an all-null row group, assumeNotNull, and IN / NOT IN with transform_null_in = 1.

ianton-ru and others added 3 commits October 22, 2026 17:17
…ch NULL.

A clustering column can contain nulls while its manifest bounds still describe the other values. `partner_id = '...'` cannot match those nulls, so the file can be skipped. `IS NULL` still keeps a file that may contain nulls.

Co-authored-by: Cursor <cursoragent@cursor.com>
…cate cannot match NULL.

`KeyCondition::mayBeTrueOnNull` is the shared check for Iceberg files and Parquet chunks. An equality that rejects NULL still skips a chunk whose other values miss, including an all-null row group. `IS NULL` keeps it.

Co-authored-by: Cursor <cursoragent@cursor.com>
…in` matches NULL.

`mayBeTrueOnNull` treated every comparison and `IN` as unknown on NULL, so a chunk whose other values missed the probe was skipped even though the NULL rows matched.

Co-authored-by: Cursor <cursoragent@cursor.com>
@ianton-ru

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-10-26T18:13:47.911219Z
We couldn’t complete this review. Comment @codex review to try again.
3be5d18 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Workflow [PR], commit [4086c26]

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9732cac9ee

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/Storages/MergeTree/KeyCondition.cpp Outdated
Comment on lines +6945 to +6951
case RPNElement::FUNCTION_IN_RANGE:
case RPNElement::FUNCTION_NOT_IN_RANGE:
case RPNElement::FUNCTION_ARGS_IN_HYPERRECTANGLE:
case RPNElement::FUNCTION_POINT_IN_POLYGON:
/// A bare comparison is unknown on NULL. `assumeNotNull` and other wrappers can make it true.
stack.push_back(uses_this_column && element.monotonic_functions_chain.empty()
? TruthOnNull::isUnknown() : TruthOnNull::any());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve null-safe comparison semantics under NOT

When the filter is NOT isNotDistinctFrom(nullable_col, c), KeyCondition represents the inner null-safe equality as FUNCTION_IN_RANGE, but this branch classifies its result on NULL as unknown; FUNCTION_NOT therefore leaves it unknown even though the actual result is true. For a Parquet row group/page or Iceberg file containing NULLs whose non-null values are all c, the finite range makes the negated predicate appear impossible and the new pruning path drops the chunk, losing every NULL row that should match. Preserve the atom's null-safe origin/null truth value in the RPN, or conservatively keep nullable chunks when that distinction is unavailable.

Useful? React with 👍 / 👎.

@ianton-ru

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting “@codex review”.

Failed to set up container
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ianton-ru

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting “@codex review”.

Failed to set up container
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ianton-ru ianton-ru changed the title iceberg nullable minmax pruning Iceberg nullable minmax pruning Oct 26, 2026
@ianton-ru

Copy link
Copy Markdown
Author

@codex review

@ianton-ru ianton-ru added antalya port-antalya PRs to be ported to all new Antalya releases antalya-26.8 Session label (releasy session config) labels Oct 26, 2026
@ianton-ru

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting “@codex review”.

Failed to set up container
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ianton-ru

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting “@codex review”.

Failed to set up container
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ianton-ru

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting “@codex review”.

Failed to set up container
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

This branch has not been deployed

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

Labels

antalya antalya-26.8 Session label (releasy session config) port-antalya PRs to be ported to all new Antalya releases

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant