diff --git a/arrow/datatype_fixedwidth.go b/arrow/datatype_fixedwidth.go index c0595b51..efdfdbf0 100644 --- a/arrow/datatype_fixedwidth.go +++ b/arrow/datatype_fixedwidth.go @@ -25,7 +25,6 @@ import ( "time" "github.com/apache/arrow-go/v18/arrow/decimal" - "github.com/apache/arrow-go/v18/arrow/internal/debug" "github.com/apache/arrow-go/v18/internal/json" ) @@ -567,22 +566,32 @@ func NarrowestDecimalType(prec, scale int32) (DecimalType, error) { } func NewDecimalType(id Type, prec, scale int32) (DecimalType, error) { + var ( + dtype DecimalType + maxPrecision int32 + ) switch id { case DECIMAL32: - debug.Assert(prec <= int32(decimal.MaxPrecision[decimal.Decimal32]()), "invalid precision for decimal32") - return &Decimal32Type{Precision: prec, Scale: scale}, nil + dtype = &Decimal32Type{Precision: prec, Scale: scale} + maxPrecision = int32(decimal.MaxPrecision[decimal.Decimal32]()) case DECIMAL64: - debug.Assert(prec <= int32(decimal.MaxPrecision[decimal.Decimal64]()), "invalid precision for decimal64") - return &Decimal64Type{Precision: prec, Scale: scale}, nil + dtype = &Decimal64Type{Precision: prec, Scale: scale} + maxPrecision = int32(decimal.MaxPrecision[decimal.Decimal64]()) case DECIMAL128: - debug.Assert(prec <= int32(decimal.MaxPrecision[decimal.Decimal128]()), "invalid precision for decimal128") - return &Decimal128Type{Precision: prec, Scale: scale}, nil + dtype = &Decimal128Type{Precision: prec, Scale: scale} + maxPrecision = int32(decimal.MaxPrecision[decimal.Decimal128]()) case DECIMAL256: - debug.Assert(prec <= int32(decimal.MaxPrecision[decimal.Decimal256]()), "invalid precision for decimal256") - return &Decimal256Type{Precision: prec, Scale: scale}, nil + dtype = &Decimal256Type{Precision: prec, Scale: scale} + maxPrecision = int32(decimal.MaxPrecision[decimal.Decimal256]()) default: return nil, fmt.Errorf("%w: must use one of the DECIMAL IDs to create a DecimalType", ErrInvalid) } + + if prec <= 0 || prec > maxPrecision { + return nil, fmt.Errorf("%w: precision for %s must be between 1 and %d, got %d", + ErrInvalid, id, maxPrecision, prec) + } + return dtype, nil } // Decimal32Type represents a fixed-size 32-bit decimal type. diff --git a/arrow/datatype_fixedwidth_test.go b/arrow/datatype_fixedwidth_test.go index bc899f34..27d4e5e9 100644 --- a/arrow/datatype_fixedwidth_test.go +++ b/arrow/datatype_fixedwidth_test.go @@ -526,3 +526,32 @@ func TestNarrowestDecimalType(t *testing.T) { assert.Error(t, err) assert.ErrorIs(t, err, arrow.ErrInvalid) } + +func TestNewDecimalTypeValidatesPrecision(t *testing.T) { + tests := []struct { + id arrow.Type + maxPrecision int32 + }{ + {arrow.DECIMAL32, 9}, + {arrow.DECIMAL64, 18}, + {arrow.DECIMAL128, 38}, + {arrow.DECIMAL256, 76}, + } + + for _, tt := range tests { + t.Run(tt.id.String(), func(t *testing.T) { + for _, precision := range []int32{1, tt.maxPrecision} { + typ, err := arrow.NewDecimalType(tt.id, precision, 2) + require.NoError(t, err) + assert.Equal(t, precision, typ.GetPrecision()) + assert.Equal(t, int32(2), typ.GetScale()) + } + + for _, precision := range []int32{-1, 0, tt.maxPrecision + 1} { + typ, err := arrow.NewDecimalType(tt.id, precision, 2) + assert.Nil(t, typ) + assert.ErrorIs(t, err, arrow.ErrInvalid) + } + }) + } +}