Skip to content

fix: decimal dtype bounds and FoR unsigned reference, as Rust - #571

Merged
dfa1 merged 3 commits into
mainfrom
fix/decimal-and-for-parity
Oct 19, 2026
Merged

dfa1 merged 3 commits into
mainfrom
fix/decimal-and-for-parity

Conversation

@dfa1

@dfa1 dfa1 commented Oct 19, 2026

Copy link
Copy Markdown
Owner

Follow-up review of the fuzz round (#493), looking for more bugs of the kinds it found.

  • fix(core): refuse a decimal dtype Rust refuses. DType.decimal(80, 0) or (10, 12) wrote files that vortex-jni and our reader refuse; a vortex.variant decimal was never checked, and its proto u32/i32 fields were truncated by a (byte) cast (300 → a valid 44) where Rust errors. The rule (Rust's DecimalDType::try_new) now lives in DType.Decimal, like DType.Map's key rule. Breaking.
  • fix(writer): FoR reference is an unsigned min. For u64 values spanning 2^63, the signed min left values at full width where Rust's Stat::Min packs them in a few bits. Compression parity only; files were readable.
  • test(fuzz): the vortex-jni differential target now writes decimal (i32/i64/i128 widths) and binary columns: 116,767 inputs in 5 minutes, no mismatch.

Checked:

🤖 Generated with Claude Code

https://claude.ai/code/session_01WtuVGQ5h5Lk6Zn4JnkxF1K

dfa1 and others added 3 commits October 19, 2026 07:08
Rust's DecimalDType::try_new wants a precision in 1..=76 and a scale no
greater than it. Only the FlatBuffer dtype parser checked that, so
DType.decimal(80, 0) or (10, 12) wrote a file that vortex-jni and our
own reader both refuse, and a vortex.variant decimal (parsed from proto)
was never checked, with its u32/i32 fields truncated by a (byte) cast
where Rust's try_into errors (300 became the valid precision 44).

The rule moves into DType.Decimal itself, as DType.Map already does for
its key: the compact constructor checks the byte components and
Decimal.of checks wide values before narrowing. It throws
VortexException, because the file parsers construct through it. The
parser's own copy is gone.

Breaking: such a dtype used to be accepted.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WtuVGQ5h5Lk6Zn4JnkxF1K
Rust's FoRData::encode subtracts Stat::Min, an unsigned minimum for a
u64 column. computeRef took a signed one for every ptype, so for u64
values on both sides of 2^63 it picked 2^63 (Long.MIN_VALUE) over a
smaller value below it, leaving that value a full-width residual where
Rust's are a few bits. The cascade's estimate already used the unsigned
min (ArrayStats), so estimate and encode disagreed too.

The files were readable (decode is a wrapping add); this is compression
parity. Widened U8..U32 values are zero-extended, so the U64 order
serves every unsigned width.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WtuVGQ5h5Lk6Zn4JnkxF1K
…ni targets

The differential target found every writer interop bug of the fuzz
round, but it wrote only ints, floats, bools and utf8. Decimals at each
physical width Rust picks (i32, i64, i128) and arbitrary-byte binary
columns now join them, with the same value shapes. 116,767 inputs over
five minutes: Java and vortex-jni agree.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WtuVGQ5h5Lk6Zn4JnkxF1K
@dfa1
dfa1 merged commit 02accc8 into main Oct 19, 2026
9 checks passed
@dfa1
dfa1 deleted the fix/decimal-and-for-parity branch October 19, 2026 05:11
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.

1 participant