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 55f6fd07 fix(arrow/array): use scale-aware ValueStr in decimal array 
String() (#849)
55f6fd07 is described below

commit 55f6fd07654248e7eea1bf90981b0c358aeb630e
Author: Matt Topol <[email protected]>
AuthorDate: Wed Jun 10 15:57:12 2026 -0400

    fix(arrow/array): use scale-aware ValueStr in decimal array String() (#849)
    
    ### Rationale for this change
    
    Decimal arrays stringify the raw `decimal.Num` struct instead of the
    scaled logical value. `fmt.Println` on a decimal array — or on a
    `Record`/`RecordBatch` containing one — prints e.g. `{1999 0}` instead
    of `19.99`, even though `ValueStr` already formats the value correctly
    using the type's scale. This is inconsistent with `ValueStr`/JSON output
    and with other Arrow implementations (e.g. PyArrow prints `19.99`).
    
    ### What changes are included in this PR?
    
    - `baseDecimal[T].String()` now formats each non-null value via
    `ValueStr` (which applies the type's scale through `GetOneForMarshal`)
    instead of `fmt.Sprintf("%v", Value(i))`. Because `baseDecimal` is
    generic, this corrects `Decimal32/64/128/256` at once, and `RecordBatch`
    printing inherits the fix through the column `String()` path
    (`record.go`).
    - Updated two existing slice-test assertions in `decimal128_test.go` /
    `decimal256_test.go` that pinned the old raw-struct output (they were
    already inconsistent with the adjacent `ValueStr` expectations on the
    next lines).
    - Added `TestDecimal128StringScaled`, reproducing the issue
    (`decimal128(5, 2)` = `19.99`) and asserting both the array `String()`
    and the `RecordBatch` output.
    
    ### Are these changes tested?
    
    Yes. `go test ./arrow/array/` passes, including the new regression test
    and the updated slice tests. `go build ./...` and `go vet`/golangci-lint
    (via pre-commit) pass.
    
    ### Are there any user-facing changes?
    
    Yes — the textual output of `String()` on decimal arrays, and of any
    `Record`/`RecordBatch` containing decimal columns, now shows scaled
    decimal values (e.g. `19.99`) instead of the internal struct (e.g.
    `{1999 0}`). This only affects human-readable stringification;
    binary/IPC/JSON representations are unchanged.
    
    Closes #848
---
 arrow/array/decimal.go         |  2 +-
 arrow/array/decimal128_test.go | 38 +++++++++++++++++++++++++++++++++++++-
 arrow/array/decimal256_test.go |  2 +-
 3 files changed, 39 insertions(+), 3 deletions(-)

diff --git a/arrow/array/decimal.go b/arrow/array/decimal.go
index 84689c95..704b1d93 100644
--- a/arrow/array/decimal.go
+++ b/arrow/array/decimal.go
@@ -71,7 +71,7 @@ func (a *baseDecimal[T]) String() string {
                case a.IsNull(i):
                        o.WriteString(NullValueStr)
                default:
-                       fmt.Fprintf(o, "%v", a.Value(i))
+                       fmt.Fprintf(o, "%v", a.ValueStr(i))
                }
        }
        o.WriteString("]")
diff --git a/arrow/array/decimal128_test.go b/arrow/array/decimal128_test.go
index 4d48a97a..e642d037 100644
--- a/arrow/array/decimal128_test.go
+++ b/arrow/array/decimal128_test.go
@@ -17,6 +17,7 @@
 package array_test
 
 import (
+       "fmt"
        "testing"
 
        "github.com/apache/arrow-go/v18/arrow"
@@ -168,7 +169,7 @@ func TestDecimal128Slice(t *testing.T) {
                t.Fatalf("could not type-assert to array.String")
        }
 
-       if got, want := v.String(), `[(null) {4 -4}]`; got != want {
+       if got, want := v.String(), `[(null) -7.378697629e+18]`; got != want {
                t.Fatalf("got=%q, want=%q", got, want)
        }
        assert.Equal(t, array.NullValueStr, v.ValueStr(0))
@@ -281,3 +282,38 @@ func TestDecimal128GetOneForMarshal(t *testing.T) {
                assert.Equalf(t, cases[i].want, arr.GetOneForMarshal(i), 
"unexpected value at index %d", i)
        }
 }
+
+// TestDecimal128StringScaled is a regression test for apache/arrow-go#848:
+// Array.String() (and RecordBatch printing) must use the type's scale like
+// ValueStr, not the raw unscaled decimal128.Num struct.
+func TestDecimal128StringScaled(t *testing.T) {
+       mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
+       defer mem.AssertSize(t, 0)
+
+       typ := &arrow.Decimal128Type{Precision: 5, Scale: 2}
+
+       b := array.NewDecimal128Builder(mem, typ)
+       defer b.Release()
+
+       value, err := decimal128.FromString("19.99", typ.Precision, typ.Scale)
+       if err != nil {
+               t.Fatal(err)
+       }
+       b.Append(value)
+       b.AppendNull()
+
+       arr := b.NewDecimal128Array()
+       defer arr.Release()
+
+       assert.Equal(t, "19.99", arr.ValueStr(0))
+       assert.Equal(t, "[19.99 (null)]", arr.String())
+
+       schema := arrow.NewSchema([]arrow.Field{{Name: "price", Type: typ}}, 
nil)
+       rec := array.NewRecordBatch(schema, []arrow.Array{arr}, 
int64(arr.Len()))
+       defer rec.Release()
+
+       out := fmt.Sprintf("%v", rec)
+       assert.Contains(t, out, "[19.99 (null)]")
+       // 1999 is the raw unscaled value that must never leak into output.
+       assert.NotContains(t, out, "1999")
+}
diff --git a/arrow/array/decimal256_test.go b/arrow/array/decimal256_test.go
index 025f3bd3..b5674253 100644
--- a/arrow/array/decimal256_test.go
+++ b/arrow/array/decimal256_test.go
@@ -169,7 +169,7 @@ func TestDecimal256Slice(t *testing.T) {
                t.Fatalf("could not type-assert to array.String")
        }
 
-       if got, want := v.String(), `[(null) {[4 4 4 4]}]`; got != want {
+       if got, want := v.String(), `[(null) 2.510840694e+57]`; got != want {
                t.Fatalf("got=%q, want=%q", got, want)
        }
        assert.Equal(t, array.NullValueStr, v.ValueStr(0))

Reply via email to