Skip to content

fix(core): align evolution stats and aggregation flush with Java - #839

Merged
JingsongLi merged 1 commit into
apache:mainfrom
JingsongLi:codex/fix-java-scan-write-parity
Sep 15, 2026
Merged

JingsongLi merged 1 commit into
apache:mainfrom
JingsongLi:codex/fix-java-scan-write-parity

Conversation

@JingsongLi

Copy link
Copy Markdown
Contributor

DE stats pruning currently interprets historical files using the current schema layout. Dropping a column can make a predicate on the next column read the wrong statistics and discard matching rows. Aggregation writes also retain duplicate keys within a single data file, violating the merge-tree reader assumption shared with Java and Python.

This aligns both paths with Java:

  • Resolve DE file schemas and match fields by ID, including dense statistics, renames, reordering and re-added columns. Changed types or unavailable schemas keep the affected data conservatively.
  • Use DE statistics only when the latest field provider is unique and covers the entire row-id group; reject inconsistent statistics.
  • Apply the existing aggregate merge function during write-buffer flush, preserving user-sequence order, partition primary keys, original input changelog rows and output sequence metadata. The merge reader and writer share the aggregation implementation.

Regression tests cover schema changes with full/dense stats, missing schemas, provider ties and partial overlap, invalid stats, physical Parquet rows, null/default aggregators, sequence ties, partition keys, changelog preservation, retract rejection and aggregation across commits.

Validation:

  • cargo test --locked -p paimon --lib table:: — 1,278 passed, 1 ignored.
  • cargo clippy --locked -p paimon --all-targets -- -D warnings — passed.
  • cargo fmt --all --check — passed.
  • Built the Python wheel and verified real DE schema-evolution reads through Python/native planning. A Rust-written aggregation file containing inputs 10 and 20 now has one physical row with value 30, read identically by both Python planning paths and the Rust reader.

Java references: DE evolution stats, write-buffer merge, output sequence metadata.

@leaves12138

Copy link
Copy Markdown

Non-blocking, pre-existing follow-up found during the cross-reader validation: the Rust aggregation reader can still aggregate a partition primary-key column.

Reproduction: create a table with columns (k BIGINT, p INT, amount BIGINT), primary key (k, p), partition key p, bucket=1, merge-engine=aggregation, and fields.default-aggregate-function=sum. Commit (1, 7, 10) and (1, 7, 20) separately, with exactly one row in each data file. The current Rust SQL reader returns (1, 14, 30), while both Java and PyPaimon return (1, 7, 30).

The write side in this PR correctly passes full primary keys. However, crates/paimon/src/table/table_read.rs still populates KeyValueReadConfig.primary_keys with trimmed_primary_keys(), and kv_file_reader.rs::new_merge_function passes those names to AggregateMergeFunction::new. Consequently p is treated as a value column and summed when files are merged. The read-side aggregation constructor needs the full key names while physical-key comparison should continue using the trimmed keys.

I also reproduced the same result with the pre-fix binding from #838, and the relevant read-side production wiring is unchanged by this PR. This is therefore a separate existing issue, not a regression or a blocker for these DE/flush fixes. Please cover a partitioned aggregation table across at least two commits in the follow-up; a single-file physical-row assertion does not exercise this reader path.

@leaves12138 leaves12138 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.

LGTM at 362a408d780931ee64ad96d6e92a2fcf9e4ccf33. No blocking issues found in the changes under review.

Checked the DE schema/field-ID mapping, dense statistics, conservative handling of changed or missing schemas, provider ties and incomplete row-range coverage, and statistics validity against Java. Also checked flush ordering, full primary-key exclusion, input changelog preservation, and emitted sequence bounds against the Java merge/write paths.

Independent validation:

  • cargo test --locked -p paimon --lib table::: 1,278 passed, 1 ignored; cargo fmt --all -- --check passed.
  • Built the Python wheel from this commit. Seventeen focused interop cases passed, covering Java- and Python-written DE files after drop/reorder/rename/type widening/drop-and-readd, full/projected statistics, physical aggregation output for fixed/dynamic buckets, sequence ties, NULL/default aggregators, original input changelog rows, and cross-commit aggregate values. Eight of these cases failed with the pre-fix #838 binding.
  • Expanded related Python/native regression suite: 358 passed, with 168 tracked native plans; three Vortex tests were skipped because the local interpreter is Python 3.10.

A separate pre-existing read-side partition-PK aggregation issue is documented in my follow-up comment. The write-side fix passes, but this approval should not be read as claiming that the older reader issue is fixed.

CI is not fully green: the Lumina job failed while linking blob_descriptor_functions, with rust-lld terminated by signal 7 / Bus error. The log shows a linker crash rather than a test assertion failure; the other current checks succeeded. These local tests do not replace the full CI matrix.

@JingsongLi
JingsongLi merged commit baac87c into apache:main Sep 15, 2026
13 of 14 checks passed
JeonDaehong added a commit to JeonDaehong/paimon-rust that referenced this pull request Oct 22, 2026
A file written under aggregation could hold several versions of a key
(before apache#839), and ALTER can switch the table to deduplicate afterwards.
Skip the sort merge only when every file was written under deduplicate or
first-row.
JeonDaehong added a commit to JeonDaehong/paimon-rust that referenced this pull request Oct 28, 2026
A file written under aggregation could hold several versions of a key
(before apache#839), and ALTER can switch the table to deduplicate afterwards.
Skip the sort merge only when every file was written under deduplicate or
first-row.
JeonDaehong added a commit to JeonDaehong/paimon-rust that referenced this pull request Oct 28, 2026
…ting schemas

Neither the stored nor the read schema proves which engine wrote a file:
before apache#839 copy_with_options could switch the writer to aggregation under
the same schema ID, and that writer kept every version of a key. Drop the
stored-schema lookup. The unmerged path now holds one batch back and checks
adjacent keys; at the first repeated key it hands the held batch and the
rest of the run to the sort merge.
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