diff --git a/arrow/compute/cast_test.go b/arrow/compute/cast_test.go index 674200989..74d66541a 100644 --- a/arrow/compute/cast_test.go +++ b/arrow/compute/cast_test.go @@ -721,6 +721,22 @@ func (c *CastSuite) TestDecimal128ToInt() { opts.AllowDecimalTruncate = true c.checkCastArr(negScale, arrow.PrimitiveTypes.Int64, `[1234567890000, -120000]`, *opts) }) + + c.Run("int64 bounds inclusive", func() { + opts.AllowIntOverflow = false + opts.AllowDecimalTruncate = false + + atBounds, _, _ := array.FromJSON(c.mem, &arrow.Decimal128Type{Precision: 38, Scale: 0}, + strings.NewReader(`["9223372036854775807", "-9223372036854775808", null]`)) + defer atBounds.Release() + c.checkCastArr(atBounds, arrow.PrimitiveTypes.Int64, + `[9223372036854775807, -9223372036854775808, null]`, *opts) + + beyondBounds, _, _ := array.FromJSON(c.mem, &arrow.Decimal128Type{Precision: 38, Scale: 0}, + strings.NewReader(`["9223372036854775808", "-9223372036854775809"]`)) + defer beyondBounds.Release() + checkCastFails(c.T(), beyondBounds, *opts) + }) } func (c *CastSuite) TestDecimal256ToInt() { @@ -828,6 +844,22 @@ func (c *CastSuite) TestDecimal256ToInt() { opts.AllowDecimalTruncate = true c.checkCastArr(negScale, arrow.PrimitiveTypes.Int64, `[1234567890000, -120000]`, *opts) }) + + c.Run("int64 bounds inclusive", func() { + opts.AllowIntOverflow = false + opts.AllowDecimalTruncate = false + + atBounds, _, _ := array.FromJSON(c.mem, &arrow.Decimal256Type{Precision: 40, Scale: 0}, + strings.NewReader(`["9223372036854775807", "-9223372036854775808", null]`)) + defer atBounds.Release() + c.checkCastArr(atBounds, arrow.PrimitiveTypes.Int64, + `[9223372036854775807, -9223372036854775808, null]`, *opts) + + beyondBounds, _, _ := array.FromJSON(c.mem, &arrow.Decimal256Type{Precision: 40, Scale: 0}, + strings.NewReader(`["9223372036854775808", "-9223372036854775809"]`)) + defer beyondBounds.Release() + checkCastFails(c.T(), beyondBounds, *opts) + }) } func (c *CastSuite) TestIntegerToDecimal() { diff --git a/arrow/compute/internal/kernels/numeric_cast.go b/arrow/compute/internal/kernels/numeric_cast.go index 7681b02e2..9ce451c7f 100644 --- a/arrow/compute/internal/kernels/numeric_cast.go +++ b/arrow/compute/internal/kernels/numeric_cast.go @@ -72,12 +72,15 @@ func CastIntegerToFloating(ctx *exec.KernelCtx, batch *exec.ExecSpan, out *exec. type decimal[T decimal128.Num | decimal256.Num] interface { Less(T) bool - GreaterEqual(T) bool + Greater(T) bool LowBits() uint64 } func decimalToIntImpl[InT decimal128.Num | decimal256.Num, OutT arrow.IntType | arrow.UintType](allowOverflow bool, min, max InT, v decimal[InT], err *error) OutT { - if !allowOverflow && (v.Less(min) || v.GreaterEqual(max)) { + // min and max are the inclusive bounds of the output integer type, so a + // value equal to max (e.g. math.MaxInt64) is in range. Use a strict + // greater-than check rather than >= so the endpoints are not rejected. + if !allowOverflow && (v.Less(min) || v.Greater(max)) { debug.Log("integer value out of bounds from decimal") *err = fmt.Errorf("%w: integer value out of bounds", arrow.ErrInvalid) return OutT(0)