This is an automated email from the ASF dual-hosted git repository.

zeroshade pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-go.git


The following commit(s) were added to refs/heads/main by this push:
     new 2950f2ed fix(arrow): validate precision in NewDecimalType (#1162)
2950f2ed is described below

commit 2950f2edac8f1e783ed143b7f827be5402195bd3
Author: Minh Vu <[email protected]>
AuthorDate: Wed Aug 26 21:42:32 2026 +0200

    fix(arrow): validate precision in NewDecimalType (#1162)
    
    ### Rationale for this change
    
    NewDecimalType only checks maximum precision with debug assertions and
    does not reject zero precision. Normal builds can therefore create
    invalid decimal types.
    
    ### What changes are included in this PR?
    
    Validate that precision is between 1 and the maximum supported by the
    requested decimal type, and return arrow.ErrInvalid otherwise.
    
    ### Are these changes tested?
    
    Yes. The tests cover the lower and upper valid boundaries plus zero and
    overflow precision for all four decimal widths. The full arrow package
    suite passes.
    
    ### Are there any user-facing changes?
    
    NewDecimalType now returns an error for invalid precision instead of
    constructing an invalid type.
---
 arrow/avro/schema.go              |  6 +++++-
 arrow/avro/schema_test.go         | 23 +++++++++++++++++++++++
 arrow/datatype_fixedwidth.go      | 27 ++++++++++++++++++---------
 arrow/datatype_fixedwidth_test.go | 29 +++++++++++++++++++++++++++++
 4 files changed, 75 insertions(+), 10 deletions(-)

diff --git a/arrow/avro/schema.go b/arrow/avro/schema.go
index 39053a4c..96508df6 100644
--- a/arrow/avro/schema.go
+++ b/arrow/avro/schema.go
@@ -472,7 +472,11 @@ func avroLogicalToArrowField(n *schemaNode) {
                if n.node.Precision > decimal128.MaxPrecision {
                        id = arrow.DECIMAL256
                }
-               dt, _ = arrow.NewDecimalType(id, int32(n.node.Precision), 
int32(n.node.Scale))
+               var err error
+               dt, err = arrow.NewDecimalType(id, int32(n.node.Precision), 
int32(n.node.Scale))
+               if err != nil {
+                       panic(err)
+               }
 
        // The uuid logical type represents a random generated universally 
unique identifier (UUID).
        // A uuid logical type annotates an Avro string. The string has to 
conform with RFC-4122
diff --git a/arrow/avro/schema_test.go b/arrow/avro/schema_test.go
index fe5dea36..8b208a34 100644
--- a/arrow/avro/schema_test.go
+++ b/arrow/avro/schema_test.go
@@ -17,6 +17,7 @@
 package avro
 
 import (
+       "errors"
        "fmt"
        "strings"
        "testing"
@@ -268,6 +269,28 @@ func TestArrowSchemaFromAvroJSON_RejectsInvalidRoot(t 
*testing.T) {
        }
 }
 
+func TestArrowSchemaFromAvroJSON_RejectsInvalidDecimalPrecision(t *testing.T) {
+       const schemaJSON = `{
+               "type": "record",
+               "name": "Root",
+               "fields": [{
+                       "name": "value",
+                       "type": {"type": "bytes", "logicalType": "decimal", 
"precision": 77, "scale": 2}
+               }]
+       }`
+
+       _, err := ArrowSchemaFromAvroJSON(schemaJSON)
+       if err == nil {
+               t.Fatal("expected invalid decimal precision error")
+       }
+       if !errors.Is(err, arrow.ErrInvalid) {
+               t.Fatalf("expected errors.Is(err, arrow.ErrInvalid), got %v", 
err)
+       }
+       if !strings.Contains(err.Error(), "precision") {
+               t.Fatalf("expected precision in error, got %v", err)
+       }
+}
+
 // A named record referenced again by name resolves back to the same 
definition.
 func TestArrowSchemaFromAvroJSON_ReusedNamedReference(t *testing.T) {
        const schemaJSON = `{"type":"record","name":"Root","fields":[
diff --git a/arrow/datatype_fixedwidth.go b/arrow/datatype_fixedwidth.go
index 663f3474..5cdca735 100644
--- a/arrow/datatype_fixedwidth.go
+++ b/arrow/datatype_fixedwidth.go
@@ -26,7 +26,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"
 )
 
@@ -590,22 +589,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 05afa334..0dc9a844 100644
--- a/arrow/datatype_fixedwidth_test.go
+++ b/arrow/datatype_fixedwidth_test.go
@@ -604,3 +604,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)
+                       }
+               })
+       }
+}

Reply via email to