You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@arrow.apache.org by ze...@apache.org on 2026/08/26 19:42:38 UTC

(arrow-go) branch main updated: fix(arrow): validate precision in NewDecimalType (#1162)

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 <vu...@gmail.com>
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)
+			}
+		})
+	}
+}