Skip to content

Commit 8599fa0

Browse files
authored
fix: Preserve int64 precision when rebuilding nested values from strings (#2553)
arrow-go v18.7.0 made `array.FromJSON` decode int64/uint64 exactly, but `AppendValueFromString` still routes nested values through float64, so list/struct round-trips silently lose precision (`-8717895732742165505` becomes `-8717895732742165504`) and every destination plugin's write test suite fails. Route nested types through `UnmarshalOne` with `UseNumber` instead.
1 parent 2db4291 commit 8599fa0

4 files changed

Lines changed: 63 additions & 3 deletions

File tree

‎plugin/append_from_string.go‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package plugin
2+
3+
import (
4+
"strings"
5+
6+
"github.com/apache/arrow-go/v18/arrow"
7+
"github.com/apache/arrow-go/v18/arrow/array"
8+
"github.com/goccy/go-json"
9+
)
10+
11+
// appendFromString appends a value from its string representation. Nested types are
12+
// JSON-encoded, so they are decoded with UseNumber to keep int64/uint64 values that
13+
// exceed float64 precision intact.
14+
func appendFromString(b array.Builder, s string) error {
15+
switch b.Type().ID() {
16+
case arrow.LIST, arrow.LARGE_LIST, arrow.LIST_VIEW, arrow.LARGE_LIST_VIEW, arrow.FIXED_SIZE_LIST, arrow.MAP, arrow.STRUCT:
17+
dec := json.NewDecoder(strings.NewReader(s))
18+
dec.UseNumber()
19+
return b.UnmarshalOne(dec)
20+
default:
21+
return b.AppendValueFromString(s)
22+
}
23+
}

‎plugin/append_from_string_test.go‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package plugin
2+
3+
import (
4+
"testing"
5+
6+
"github.com/apache/arrow-go/v18/arrow"
7+
"github.com/apache/arrow-go/v18/arrow/array"
8+
"github.com/apache/arrow-go/v18/arrow/memory"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestAppendFromStringPreservesInt64Precision(t *testing.T) {
13+
for _, tc := range []struct {
14+
name string
15+
dt arrow.DataType
16+
str string
17+
}{
18+
{name: "int64", dt: arrow.PrimitiveTypes.Int64, str: "-8717895732742165505"},
19+
{name: "list_of_int64", dt: arrow.ListOf(arrow.PrimitiveTypes.Int64), str: "[-8717895732742165505]"},
20+
{name: "large_list_of_int64", dt: arrow.LargeListOf(arrow.PrimitiveTypes.Int64), str: "[-8717895732742165505]"},
21+
{name: "struct_with_int64", dt: arrow.StructOf(arrow.Field{Name: "v", Type: arrow.PrimitiveTypes.Int64, Nullable: true}), str: `{"v":-8717895732742165505}`},
22+
{name: "uint64", dt: arrow.PrimitiveTypes.Uint64, str: "18428615660272232523"},
23+
{name: "list_of_uint64", dt: arrow.ListOf(arrow.PrimitiveTypes.Uint64), str: "[18428615660272232523]"},
24+
} {
25+
t.Run(tc.name, func(t *testing.T) {
26+
bldr := array.NewBuilder(memory.DefaultAllocator, tc.dt)
27+
defer bldr.Release()
28+
29+
require.NoError(t, appendFromString(bldr, tc.str))
30+
31+
arr := bldr.NewArray()
32+
defer arr.Release()
33+
34+
require.Equal(t, tc.str, arr.ValueStr(0))
35+
})
36+
}
37+
}

‎plugin/nulls.go‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ func stripNullsFromLists(list array.ListLike) array.ListLike {
2222
if slc.IsNull(k) {
2323
continue
2424
}
25-
err := vBldr.AppendValueFromString(slc.ValueStr(k))
25+
err := appendFromString(vBldr, slc.ValueStr(k))
2626
if err != nil {
2727
panic(err)
2828
}
@@ -47,7 +47,7 @@ func (s *WriterTestSuite) replaceNullsByEmpty(arr arrow.Array) arrow.Array {
4747
continue
4848
}
4949

50-
if err := builder.AppendValueFromString(arr.ValueStr(j)); err != nil {
50+
if err := appendFromString(builder, arr.ValueStr(j)); err != nil {
5151
panic(err)
5252
}
5353
}

‎plugin/testing_write_upsert.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ func extractLastRowFromRecord(table *schema.Table, existingRecord arrow.RecordBa
208208
for i, c := range table.Columns {
209209
col := existingRecord.Column(i)
210210
lastRow := int(existingRecord.NumRows()) - 1
211-
err := bldr.Field(i).AppendValueFromString(col.ValueStr(lastRow))
211+
err := appendFromString(bldr.Field(i), col.ValueStr(lastRow))
212212
if err != nil {
213213
return nil, fmt.Errorf("failed to unmarshal json `%v` for column %v: %v", col.ValueStr(lastRow), c.Name, err)
214214
}

0 commit comments

Comments
 (0)