Skip to content

fix(writer): narrow and cascade decimal_byte_parts' value child, as Rust does - #647

Merged
dfa1 merged 1 commit into
mainfrom
fix/644-decimal-byte-parts-cascade
Nov 2, 2026
Merged

dfa1 merged 1 commit into
mainfrom
fix/644-decimal-byte-parts-cascade

Conversation

@dfa1

@dfa1 dfa1 commented Nov 2, 2026

Copy link
Copy Markdown
Owner

Closes #644.

Rust's DecimalScheme narrows a decimal to the smallest signed integer type holding its values (narrowed_decimal) and compresses the most significant part with compress_child, so frame of reference and bit-packing take it. DecimalBytePartsEncodingEncoder had no encodeCascade and always wrote the part as a plain i64 vortex.primitive.

Change

  • narrow the part to i8/i16/i32/i64 (signed only, by the values, as Rust's try_downcast), record that type in the metadata, and return it as an open cascade child
  • a chunk of fewer than two values is declined (stays vortex.decimal), as Rust leaves it un-narrowed

Before → after (TPC-H from tpchgen-cli, vortex-java Parquet import; Rust = vortex Python bindings):

table Rust Java before Java after
sf10 lineitem 1,825 MB 3,378 MB 1,796 MB
sf10 orders 512 MB 519 MB 448 MB
sf10 partsupp 263 MB 311 MB 264 MB
sf10 customer 95 MB 105 MB 97 MB
sf10 part 56 MB 64 MB 52 MB

Same at sf1. Import time unchanged (lineitem sf10: 19.4 → 19.6 s).

Tests

  • DecimalBytePartsInteropIntegrationTest: for five TPC-H-shaped decimal columns the data column's encoding tree equals vortex-jni's (fails without this change), and vortex-jni reads the Java file in full, filtered and row-range scans.
  • Unit tests: signed narrowing boundaries, the open child, the <2-value decline.
  • writer suite (2507) and full routine integration suite (3227) pass.

Docs: docs/compatibility.md row and the VortexWriter comment updated.

Reviewed independently (vortex-reviewer agent): reader side and zone stats verified; its should-fix items (the <2-value parity case, stale docs/comment, test FQNs and a weak assertion) are in this commit. Its nullable-decimal finding is #646 (a nullable decimal goes through vortex.masked, which is also why the stats tables still differ). Found while measuring: #645 (Snappy Parquet unreadable).

🤖 Generated with Claude Code

https://claude.ai/code/session_011fRbYryTxfcu5j2CXwkSzd

…ust does

Rust's DecimalScheme narrows a decimal to the smallest signed integer type
holding its values (narrowed_decimal) and compresses the most significant
part with compress_child, so frame of reference and bit-packing take it.
DecimalBytePartsEncodingEncoder had no encodeCascade and always wrote the
part as a plain i64 vortex.primitive.

The encoder now narrows the part to i8/i16/i32/i64 (signed only, as Rust),
records that type in the metadata, and returns the part as an open cascade
child.

TPC-H from tpchgen-cli, vortex-java before -> after (Rust):
  sf10 lineitem  3,378 -> 1,796 MB  (1,825)
  sf10 orders      519 ->   448 MB    (512)
  sf10 partsupp    311 ->   264 MB    (263)
  sf10 customer    105 ->    97 MB     (95)
  sf10 part         64 ->    52 MB     (56)
Import time unchanged (lineitem sf10: 19.4 -> 19.6 s).

DecimalBytePartsInteropIntegrationTest: for five TPC-H-shaped decimal
columns the data column's encoding tree equals vortex-jni's (it fails
without this change), and vortex-jni reads the Java file in full, filtered
and row-range scans.

Closes #644

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011fRbYryTxfcu5j2CXwkSzd
@dfa1
dfa1 merged commit b87a1c4 into main Nov 2, 2026
6 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.

Writer: decimal_byte_parts stores its value child as plain primitive; Rust bit-packs it (2.5–16x larger decimal columns)

1 participant