Skip to content

Export partition - allow non matching partition expressions iff monotonicity is proved and data is not repartitioned - #2074

Open
arthurpassos wants to merge 34 commits into
antalya-26.3from
feature/antalya-26.3/export-partition-monotonic-compat
Open

Export partition - allow non matching partition expressions iff monotonicity is proved and data is not repartitioned#2074
arthurpassos wants to merge 34 commits into
antalya-26.3from
feature/antalya-26.3/export-partition-monotonic-compat

Conversation

@arthurpassos

@arthurpassos arthurpassos commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Changelog category (leave one):

  • Improvement

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

Allow export partition through different partition expressions as long as the destination expression does not repartition the data. This is asserted through a destination expression monotonicity check on the source minmax values. Destination expression columns must be a subset of the source.

Documentation entry for user-facing changes

...

CI/CD Options

Exclude tests:

  • Fast test
  • Integration Tests
  • Stateless tests
  • Stateful 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)
  • 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)

@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown

Workflow [PR], commit [b4cca45]

@arthurpassos

Copy link
Copy Markdown
Collaborator Author

@codex review

@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: 3f3af7c4fc

ℹ️ 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".

castColumn({std::move(values_column), source_type, column}, destination_type), destination_type, column};

const auto dest_transform_with_tz = Iceberg::parseTransformAndArgument(dest_transform, partition_timezone);
if (!destinationTransformIsConstant(*dest_transform_with_tz, cast_values, context))

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 Persist the partition value proven for these parts

When this dynamic check succeeds in the replicated EXPORT PARTITION path, the proof is only for the scheduling-time parts; the manifest still persists just partition_id, and commit later calls getPartitionSourceBlockForIcebergCommit, which reads the current active part for that partition. If a coarser source partition is accepted because its current rows map to one destination partition (for example toYear(ts) into Iceberg day(ts)) and, before commit, another day is inserted and merged with the exported parts, the committed manifest can tag the already exported files with the merged/current part's representative value instead of the exported rows' value. Persist/use representative values from the validated parts, or keep these cases structural-only for replicated exports.

Useful? React with 👍 / 👎.

{
if (const auto * id = arg->as<ASTIdentifier>())
column = id->name();
else if (const auto * lit = arg->as<ASTLiteral>(); lit && lit->value.getType() != Field::Types::String)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Include timezone in structural transform matching

This drops string literals from source partition terms, so toRelativeDayNum(ts, 'UTC') is treated as the same structural transform as the destination Iceberg day(ts) transform. With iceberg_partition_timezone set differently for the export (for example America/Los_Angeles), one UTC-day source partition can contain rows from two destination days; the structural fast path then skips the dynamic min/max proof and commits them as a single Iceberg partition. Carry/compare the timezone literal, or force timezone-qualified source terms through the dynamic proof.

Useful? React with 👍 / 👎.

@ilejn

ilejn commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Consider mentioning 'export' in the title and/or description

@arthurpassos arthurpassos changed the title Allow non matching partition expressions iff monotonicity is proved and data is not repartitioned Export partition - allow non matching partition expressions iff monotonicity is proved and data is not repartitioned Jul 21, 2026
@arthurpassos

Copy link
Copy Markdown
Collaborator Author

Consider mentioning 'export' in the title and/or description

Done, I'll soon add the description

@arthurpassos

Copy link
Copy Markdown
Collaborator Author

@codex review

@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: ca43cf1f6d

ℹ️ 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/ExportPartitionUtils.cpp
@arthurpassos
arthurpassos marked this pull request as ready for review July 27, 2026 22:41

@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: e055b1f46d

ℹ️ 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 on lines +697 to +698
const auto slot_it = std::find(minmax_column_names.begin(), minmax_column_names.end(), column);
if (slot_it == minmax_column_names.end())

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 Validate the source column actually written by position

When source and destination schemas contain reordered same-typed columns, this name-based lookup validates a different value from the one exported because ExportPartTask::addExportConvertingActions converts columns with MatchColumnsMode::Position. For example, with source columns (x, ts) partitioned monthly by ts and destination columns (ts, x) partitioned daily by ts, a part whose source ts values fall on one day passes this proof even when source x spans several days; the destination ts actually receives x, while the Hive path or Iceberg metadata is derived from the source column named ts. Map each destination partition column to its positional source column before checking min/max and constructing the representative block.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I think we shall fix this in a separate PR

@Selfeer

Selfeer commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Hey @arthurpassos — while writing regression tests for this PR I ran into a CREATE TABLE failure that I want to check with you before I file it separately, because I'm not sure whether it's in-scope for #2074 or a pre-existing IcebergS3 engine bug we should track on its own.

What I tried

A single CREATE TABLE on the IcebergS3 destination with a timezone literal inside a temporal transform in PARTITION BY:

CREATE TABLE bug_repro
(
    id Int64,
    event_time DateTime('UTC')
)
ENGINE = IcebergS3(
    'http://minio:9000/warehouse/data/bug_repro/',
    '<minio_root_user>',
    '<minio_root_password>'
)
PARTITION BY toRelativeDayNum(event_time, 'UTC')
SETTINGS s3_retry_attempts = 1;

With allow_experimental_insert_into_iceberg = 1. No ALTER TABLE ... EXPORT PARTITION, no source table, nothing else — just this one statement.

What happens

Code: 170. DB::Exception: Bad get: has String, requested Int64. (BAD_GET)

Why I'm asking here rather than filing separately

  1. The PR's own integration tests use the same shape — e.g. toRelativeDayNum(event_time, 'Asia/Tokyo') in test_export_replicated_mt_partition_to_iceberg/test.py — so it looks like the PR expects this to work on the IcebergS3 engine.
  2. The PR diff of ExportPartitionUtils.cpp explicitly comments "Integer literal is the bucket/truncate width; a string literal is a date transform's timezone" and adds a branch that skips String literals when scanning a transform's arg list. That's the same class of fix that the CREATE TABLE path would need.
  3. But the failure fires at CREATE TABLE — before any of the PR's new gate/commit code runs. It looks like it comes from the Iceberg engine's partition-spec construction (around Iceberg::getPartitionField in Iceberg/Utils.cpp), which the PR doesn't touch.

What works (narrows it)

  • Same PARTITION BY toRelativeDayNum(event_time, 'UTC') on plain MergeTree / ReplicatedMergeTree — succeeds.
  • IcebergS3 with the bare form + the setting — succeeds:
    PARTITION BY toRelativeDayNum(event_time)
    SETTINGS iceberg_partition_timezone = 'UTC';

So the fault is specifically IcebergS3 engine + String literal inside the transform arg list at CREATE TABLE time.

Questions

  1. Is the transform-literal form (toRelativeDayNum(col, 'UTC')) considered supported on IcebergS3 destinations by this PR, or is iceberg_partition_timezone the only intended way? If it's the latter, happy to drop this from the PR-scoped test file and file it as a standalone IcebergS3 bug instead.
  2. If it is considered supported, would you want the same String-literal skip you added in ExportPartitionUtils.cpp mirrored into the Iceberg engine's partition-spec construction so the CREATE TABLE succeeds? That's a one-line change and would let Export partition - allow non matching partition expressions iff monotonicity is proved and data is not repartitioned #2074 close out this behavior end-to-end.
  3. Either way, I have a failing regression test ready — I'll wire it under whichever bucket you want (PR-scoped regression guard here, or a separate issue).

@arthurpassos

Copy link
Copy Markdown
Collaborator Author

@codex review

@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: 38481944eb

ℹ️ 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".

/// The written value is transform(cast(source)). A value-preserving cast is order-preserving, so
/// the endpoints bound every interior row; a lossy cast may wrap, so require it monotonic over the
/// partition's actual range, otherwise the endpoints prove nothing about the interior.
bool cast_is_monotonic = canBeSafelyCast(source_type, destination_type);

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 Check monotonicity for value-preserving string casts

When an integer partition column is converted to String, canBeSafelyCast returns true because the cast preserves values, but it is not order-preserving: for a source PARTITION BY intDiv(k, 100) containing k = 2, 10, 29 and an Iceberg destination with k String PARTITION BY icebergTruncate(1, k), the endpoints cast to "2" and "29" and both truncate to "2", while the interior value 10 truncates to "1". The gate therefore accepts a source partition spanning multiple destination partitions, and the commit records the representative endpoint's partition for every exported file, producing incorrect Iceberg metadata and potentially wrong partition-pruned results. Value preservation should not bypass the cast monotonicity check.

Useful? React with 👍 / 👎.

@arthurpassos

Copy link
Copy Markdown
Collaborator Author

@Selfeer I also wondered about this very same timezone case. As far as I could understand from apache iceberg docs and articles, the day transform is always UTC. Therefore, it does not make sense to parse the timezone argument in those functions.

This is not crystal clear, tho. The docs are not very explicit. For this reason, I think @ianton-ru introduced the iceberg_partition_timezone setting, which afaik modifies the timezone which values are written to/ read from.

Export partition must respect it because it exists, even if it is non compliant. That's why I respect iceberg_partition_timezone.

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.

4 participants