Skip to content

fix(go): error on NUMBER values that overflow int64 with use_high_precision=false#161

Open
zeroshade wants to merge 1 commit into
mainfrom
fix/number-int64-overflow
Open

fix(go): error on NUMBER values that overflow int64 with use_high_precision=false#161
zeroshade wants to merge 1 commit into
mainfrom
fix/number-int64-overflow

Conversation

@zeroshade

Copy link
Copy Markdown
Contributor

Summary

With use_high_precision=false, Snowflake NUMBER(n,0) columns are returned as Arrow int64. Because Snowflake stores integer types as NUMBER(38,0) (decimal128-equivalent), high-precision columns arrive as a Decimal128 array, and the previous compute.UnsafeCastOptions cast silently discarded the upper bits for values that do not fit in int64 — returning a corrupted low-64-bit result with no error or warning.

Closes #129.

Changes

  • record_reader.go — scale-0 Decimal128/Decimal256 columns are now converted via a new decimalScale0ToInt64 transformer that range-checks each value and returns an adbc.StatusInvalidData error when it falls outside [MinInt64, MaxInt64], instead of truncating. The bounds are checked manually (not via compute.SafeCastOptions) because arrow-go's safe decimal→int kernel tests value >= MaxInt64 and therefore rejects MaxInt64 itself.
  • record_reader.go — preserve a transformer's adbc.Error status code through the record-transform loop, so the overflow surfaces as InvalidData rather than being re-wrapped as Internal.
  • Tests — new TestDecimalScale0ToInt64 unit test covering in-range values, the int64 min/max boundaries, and overflow; updated TestIntDecimalLowPrecision to expect a StatusInvalidData reader error for out-of-range NUMBER(p,0) values (precision ≥ 19).
  • Validation — updated the numeric_low_precision caveat to document the new error-on-overflow behavior (it previously documented the silent truncation).

Behavior change

Queries that previously returned a silently-truncated value for out-of-range NUMBER now return an error. Users who need the full value can set use_high_precision=true to read the column losslessly as decimal128.

Testing

  • go test ./... passes (unit tests; Snowflake integration tests skip without credentials).
  • go vet ./... clean.

…cision=false

With use_high_precision=false, Snowflake NUMBER(n,0) columns are returned as int64. High-precision NUMBER arrives as Decimal128, and the previous compute.UnsafeCastOptions cast silently discarded the upper bits for values that do not fit in int64, returning a corrupted low-64-bit result.

Convert scale-0 decimal columns with an explicit inclusive int64 bounds check and return a StatusInvalidData error when a value is out of range instead of truncating it. The bounds are checked manually because arrow-go's safe decimal->int cast rejects math.MaxInt64 itself. Also preserve a transformer's adbc.Error status code through the record transform loop so the error surfaces as InvalidData rather than Internal.

Closes #129
Comment thread go/record_reader.go
Comment on lines +578 to +580
// The range is checked manually rather than with compute.SafeCastOptions because
// arrow-go's safe decimal->int kernel tests `value >= MaxInt64` and so rejects
// math.MaxInt64 itself; a manual check accepts both int64 bounds inclusively.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Er, should we fix this upstream?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

good point. don't know why i didn't just do that when i saw this...

zeroshade added a commit to apache/arrow-go that referenced this pull request Jun 23, 2026
…#862)

### Rationale for this change

The safe `decimal128`/`decimal256` → integer cast kernel range-checks
each value against the output type's bounds before truncating to the low
64 bits. The upper-bound check used `value >= max`, where `max` is the
*inclusive* maximum of the output integer type (e.g. `math.MaxInt64` for
`int64`, computed via `MaxOf[T]()`). As a result a value exactly equal
to the type maximum was incorrectly rejected as "integer value out of
bounds", even though it is a valid in-range value. This affects every
integer output type (`int8`/`int16`/`int32`/`int64` and the unsigned
variants), not just `int64`.

This was noticed while working around the behavior downstream in the
ADBC Snowflake driver (adbc-drivers/snowflake#161), where it was
suggested to fix it upstream here.

### What changes are included in this PR?

- In `decimalToIntImpl`
(`arrow/compute/internal/kernels/numeric_cast.go`), change the
upper-bound overflow check from `value >= max` to `value > max` so the
inclusive maximum is accepted, matching the already-inclusive
lower-bound check (`value < min`). The `decimal[T]` helper interface now
requires `Greater` instead of `GreaterEqual`; both `decimal128.Num` and
`decimal256.Num` already implement it.

### Are these changes tested?

Yes. Added an `int64 bounds inclusive` regression subtest to
`TestDecimal128ToInt` and `TestDecimal256ToInt` that assert, with
`AllowIntOverflow=false`:
- `math.MaxInt64` (`9223372036854775807`) and `math.MinInt64`
(`-9223372036854775808`) cast successfully, and
- values one beyond either bound still fail as overflow.

`go test ./arrow/compute/...` passes and `go vet` is clean.

### Are there any user-facing changes?

Yes — a bug fix. A safe (non-`AllowIntOverflow`) decimal→integer cast of
a value equal to the destination type's maximum (e.g. `math.MaxInt64`)
now succeeds instead of returning an `ErrInvalid` "integer value out of
bounds" error. Values genuinely outside the range still error as before.
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.

[Go] NUMBER(n, 0) values get truncated with use_high_precision=false

2 participants