Skip to content

[core] Support logical Arrow keys and selected upsert values - #1147

Merged
JingsongLi merged 5 commits into
apache:mainfrom
JingsongLi:codex/native-upsert-selected-values
Oct 17, 2026
Merged

JingsongLi merged 5 commits into
apache:mainfrom
JingsongLi:codex/native-upsert-selected-values

Conversation

@JingsongLi

@JingsongLi JingsongLi commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Purpose

Expand Native row-level updates for Arrow input shapes already supported by PyPaimon. This covers action-specific upsert payload conversion, lossless logical key matching, and Dictionary keys in MERGE. Matching and conversion stay in Rust core; the Python bridge only qualifies supported inputs and transports batches.

Brief change log

  • Select surviving upsert rows before converting payloads. Reuse row-ID update conversions for matched rows, ignore unwritten columns, and retain ordinary write validation for appended rows.
  • Normalize upsert keys independently of their original payload arrays. Support exact integer widths, decimal scales, date/time/timestamp units, NULL-typed arrays, and Dictionary encodings. Reject overflow, lost precision, and timezone-awareness changes.
  • Preserve original key arrays for action-specific write validation: a key conversion accepted for matching must not implicitly relax append validation.
  • Decode MERGE keys before row encoding and SQL NULL filtering. Different dictionaries in separate batches cannot change key identity. Retain cardinality validation before conditions and the unconditional DELETE exception.
  • Reuse existing row-ID conversions for matched Binary/LargeBinary values, including nested MAP/ROW and constructor-shaped inputs. No upsert-specific conversion policy or writer flag.
  • Reuse existing nested field projection and core conversion helpers. Upsert, MERGE and Blob map key layout normalization share the internal normalize_match_key helper; logical type and NULL matching rules remain operation-specific. No new public API or dependencies.

Tests

  • cargo +1.98.0 test --locked -p paimon --lib key_normalization: 5 passed.
  • cargo +1.98.0 test --locked -p paimon --lib upsert: 10 passed.
  • cargo +1.98.0 test --locked -p paimon --lib table_merge_into_tests: 17 passed.
  • cargo +1.98.0 test --locked -p paimon --lib table::update_input: 29 passed.
  • cargo +1.98.0 test --locked -p paimon --lib table::data_evolution_writer: 19 passed.
  • cargo +1.98.0 test --locked -p paimon --lib table::write_batch_normalize: 24 passed.
  • cargo +1.98.0 clippy --locked -p paimon --lib --tests -- -D warnings.
  • cargo +1.98.0 fmt --all -- --check and git diff --check.
  • Built the Native extension from this branch and ran the companion PyPaimon regressions with Native plan/read/write/commit enabled: 1322 passed, 2 skipped. Exercised 1887 Native plans, 2236 reads, 809 writes, and 257 commits.

Coverage includes batch/stream upserts, last-write-wins across chunks, fan-out, partition isolation, NULL indices and NULL dictionary values, discarded invalid payloads, strict append validation, precision/overflow failures, Binary/LargeBinary conversion parity, Dictionary MERGE update/insert/delete/cardinality, and Pandas categorical input.

The companion apache/paimon#10629 also enables existing core Parquet vector/BLOB update support across row-ID, incremental, predicate, upsert, MERGE, and shard operations, with Python/Native parity coverage. Its Native CI requires this PR on Rust main.

API and Format

No public API or storage format change. Existing core row-ID conversion rules and Java-compatible write/file metadata semantics are reused.

Documentation

Clarified TableUpdate::upsert_by_arrow_with_key matched conversion and append-validation contracts and documented logical key normalization internally.

Follow-up review fixes

  • Remove the upsert-specific Binary/LargeBinary restriction and its recursive policy plumbing. Matched values now directly use the established shared row-ID conversion implementation; ordinary append validation keeps its existing rules. Positive Python/Native batch and stream tests cover arbitrary bytes, NULLs, nested MAP/ROW values and constructor inputs, with unchanged row identities.
  • Preserve Blob map transport and Java key serialization independently of value conversion: Dictionary keys and Dictionary payloads, exact integer widths, DECIMAL HALF_UP and TIME millisecond normalization. Normalization-induced duplicate keys still fail, and append types remain strict.
  • Handle exact decimal zero/NULL keys across extreme scale differences without Arrow power-table errors or i8 scale arithmetic overflow. Nonzero overflow and precision loss remain errors.
  • Add regression cases for those failures, NULL containers, mixed Blob map transport and unchanged row identities. Several independent review passes found no remaining confirmed issue after repairs.

@JingsongLi JingsongLi changed the title [core] Normalize upsert payloads after matching [core] Support logical Arrow keys and selected upsert values Oct 17, 2026

@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

@JingsongLi
JingsongLi merged commit b72adae into apache:main Oct 17, 2026
16 checks passed
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