You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@arrow.apache.org by we...@apache.org on 2019/06/21 21:59:52 UTC
[arrow] branch master updated: ARROW-3676: [Go] implement
Decimal128 array
This is an automated email from the ASF dual-hosted git repository.
wesm pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/arrow.git
The following commit(s) were added to refs/heads/master by this push:
new f406721 ARROW-3676: [Go] implement Decimal128 array
f406721 is described below
commit f40672143cf9d0989083a654a33b7b7c0725ed75
Author: Sebastien Binet <bi...@cern.ch>
AuthorDate: Fri Jun 21 16:59:41 2019 -0500
ARROW-3676: [Go] implement Decimal128 array
Author: Sebastien Binet <bi...@cern.ch>
Closes #4633 from sbinet/issue-3676 and squashes the following commits:
eb62bc712 <Sebastien Binet> go/arrow/array: exercize more of Decimal128 API surface
0ea83919b <Sebastien Binet> go/arrow/array: use Decimal128.Value in Stringer
9b57f7425 <Sebastien Binet> go/arrow/decimal128: test hi/lo bits
7778a48a6 <Sebastien Binet> ARROW-3676: implement Decimal128 array
---
go/arrow/array/array.go | 2 +-
go/arrow/array/array_test.go | 11 +-
go/arrow/array/compare.go | 6 +
go/arrow/array/decimal128.go | 235 +++++++++++++++++++++++++++++++++
go/arrow/array/decimal128_test.go | 179 +++++++++++++++++++++++++
go/arrow/datatype_fixedwidth.go | 18 ++-
go/arrow/datatype_fixedwidth_test.go | 27 ++++
go/arrow/decimal128/decimal128.go | 73 ++++++++++
go/arrow/decimal128/decimal128_test.go | 94 +++++++++++++
go/arrow/type_traits_decimal128.go | 75 +++++++++++
go/arrow/type_traits_test.go | 45 +++++++
11 files changed, 759 insertions(+), 6 deletions(-)
diff --git a/go/arrow/array/array.go b/go/arrow/array/array.go
index 1912f3e..d8418f8 100644
--- a/go/arrow/array/array.go
+++ b/go/arrow/array/array.go
@@ -186,7 +186,7 @@ func init() {
arrow.TIME32: func(data *Data) Interface { return NewTime32Data(data) },
arrow.TIME64: func(data *Data) Interface { return NewTime64Data(data) },
arrow.INTERVAL: func(data *Data) Interface { return NewIntervalData(data) },
- arrow.DECIMAL: unsupportedArrayType,
+ arrow.DECIMAL: func(data *Data) Interface { return NewDecimal128Data(data) },
arrow.LIST: func(data *Data) Interface { return NewListData(data) },
arrow.STRUCT: func(data *Data) Interface { return NewStructData(data) },
arrow.UNION: unsupportedArrayType,
diff --git a/go/arrow/array/array_test.go b/go/arrow/array/array_test.go
index 724f3b4..ba3a961 100644
--- a/go/arrow/array/array_test.go
+++ b/go/arrow/array/array_test.go
@@ -43,9 +43,6 @@ func TestMakeFromData(t *testing.T) {
expPanic bool
expError string
}{
- // unsupported types
- {name: "map", d: &testDataType{arrow.MAP}, expPanic: true, expError: "unsupported data type: MAP"},
-
// supported types
{name: "null", d: &testDataType{arrow.NULL}},
{name: "bool", d: &testDataType{arrow.BOOL}},
@@ -59,11 +56,17 @@ func TestMakeFromData(t *testing.T) {
{name: "int64", d: &testDataType{arrow.INT64}},
{name: "float32", d: &testDataType{arrow.FLOAT32}},
{name: "float64", d: &testDataType{arrow.FLOAT64}},
+ {name: "string", d: &testDataType{arrow.STRING}, size: 3},
{name: "binary", d: &testDataType{arrow.BINARY}, size: 3},
+ {name: "fixed_size_binary", d: &testDataType{arrow.FIXED_SIZE_BINARY}},
+ {name: "date32", d: &testDataType{arrow.DATE32}},
+ {name: "date64", d: &testDataType{arrow.DATE64}},
{name: "timestamp", d: &testDataType{arrow.TIMESTAMP}},
{name: "time32", d: &testDataType{arrow.TIME32}},
{name: "time64", d: &testDataType{arrow.TIME64}},
- {name: "fixed_size_binary", d: &testDataType{arrow.FIXED_SIZE_BINARY}},
+ {name: "month_interval", d: arrow.FixedWidthTypes.MonthInterval},
+ {name: "day_time_interval", d: arrow.FixedWidthTypes.DayTimeInterval},
+ {name: "decimal", d: &testDataType{arrow.DECIMAL}},
{name: "list", d: &testDataType{arrow.LIST}, child: []*array.Data{
array.NewData(&testDataType{arrow.INT64}, 0, make([]*memory.Buffer, 4), nil, 0, 0),
diff --git a/go/arrow/array/compare.go b/go/arrow/array/compare.go
index c6665c9..17839c6 100644
--- a/go/arrow/array/compare.go
+++ b/go/arrow/array/compare.go
@@ -128,6 +128,9 @@ func ArrayEqual(left, right Interface) bool {
case *Float64:
r := right.(*Float64)
return arrayEqualFloat64(l, r)
+ case *Decimal128:
+ r := right.(*Decimal128)
+ return arrayEqualDecimal128(l, r)
case *Date32:
r := right.(*Date32)
return arrayEqualDate32(l, r)
@@ -314,6 +317,9 @@ func arrayApproxEqual(left, right Interface, opt equalOption) bool {
case *Float64:
r := right.(*Float64)
return arrayApproxEqualFloat64(l, r, opt)
+ case *Decimal128:
+ r := right.(*Decimal128)
+ return arrayEqualDecimal128(l, r)
case *Date32:
r := right.(*Date32)
return arrayEqualDate32(l, r)
diff --git a/go/arrow/array/decimal128.go b/go/arrow/array/decimal128.go
new file mode 100644
index 0000000..390d87e
--- /dev/null
+++ b/go/arrow/array/decimal128.go
@@ -0,0 +1,235 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package array // import "github.com/apache/arrow/go/arrow/array"
+
+import (
+ "fmt"
+ "strings"
+ "sync/atomic"
+
+ "github.com/apache/arrow/go/arrow"
+ "github.com/apache/arrow/go/arrow/decimal128"
+ "github.com/apache/arrow/go/arrow/internal/bitutil"
+ "github.com/apache/arrow/go/arrow/internal/debug"
+ "github.com/apache/arrow/go/arrow/memory"
+)
+
+// A type which represents an immutable sequence of 128-bit decimal values.
+type Decimal128 struct {
+ array
+
+ values []decimal128.Num
+}
+
+func NewDecimal128Data(data *Data) *Decimal128 {
+ a := &Decimal128{}
+ a.refCount = 1
+ a.setData(data)
+ return a
+}
+
+func (a *Decimal128) Value(i int) decimal128.Num { return a.values[i] }
+
+func (a *Decimal128) Values() []decimal128.Num { return a.values }
+
+func (a *Decimal128) String() string {
+ o := new(strings.Builder)
+ o.WriteString("[")
+ for i := 0; i < a.Len(); i++ {
+ if i > 0 {
+ fmt.Fprintf(o, " ")
+ }
+ switch {
+ case a.IsNull(i):
+ o.WriteString("(null)")
+ default:
+ fmt.Fprintf(o, "%v", a.Value(i))
+ }
+ }
+ o.WriteString("]")
+ return o.String()
+}
+
+func (a *Decimal128) setData(data *Data) {
+ a.array.setData(data)
+ vals := data.buffers[1]
+ if vals != nil {
+ a.values = arrow.Decimal128Traits.CastFromBytes(vals.Bytes())
+ beg := a.array.data.offset
+ end := beg + a.array.data.length
+ a.values = a.values[beg:end]
+ }
+}
+
+func arrayEqualDecimal128(left, right *Decimal128) bool {
+ for i := 0; i < left.Len(); i++ {
+ if left.IsNull(i) {
+ continue
+ }
+ if left.Value(i) != right.Value(i) {
+ return false
+ }
+ }
+ return true
+}
+
+type Decimal128Builder struct {
+ builder
+
+ dtype *arrow.Decimal128Type
+ data *memory.Buffer
+ rawData []decimal128.Num
+}
+
+func NewDecimal128Builder(mem memory.Allocator, dtype *arrow.Decimal128Type) *Decimal128Builder {
+ return &Decimal128Builder{
+ builder: builder{refCount: 1, mem: mem},
+ dtype: dtype,
+ }
+}
+
+// Release decreases the reference count by 1.
+// When the reference count goes to zero, the memory is freed.
+func (b *Decimal128Builder) Release() {
+ debug.Assert(atomic.LoadInt64(&b.refCount) > 0, "too many releases")
+
+ if atomic.AddInt64(&b.refCount, -1) == 0 {
+ if b.nullBitmap != nil {
+ b.nullBitmap.Release()
+ b.nullBitmap = nil
+ }
+ if b.data != nil {
+ b.data.Release()
+ b.data = nil
+ b.rawData = nil
+ }
+ }
+}
+
+func (b *Decimal128Builder) Append(v decimal128.Num) {
+ b.Reserve(1)
+ b.UnsafeAppend(v)
+}
+
+func (b *Decimal128Builder) UnsafeAppend(v decimal128.Num) {
+ bitutil.SetBit(b.nullBitmap.Bytes(), b.length)
+ b.rawData[b.length] = v
+ b.length++
+}
+
+func (b *Decimal128Builder) AppendNull() {
+ b.Reserve(1)
+ b.UnsafeAppendBoolToBitmap(false)
+}
+
+func (b *Decimal128Builder) UnsafeAppendBoolToBitmap(isValid bool) {
+ if isValid {
+ bitutil.SetBit(b.nullBitmap.Bytes(), b.length)
+ } else {
+ b.nulls++
+ }
+ b.length++
+}
+
+// AppendValues will append the values in the v slice. The valid slice determines which values
+// in v are valid (not null). The valid slice must either be empty or be equal in length to v. If empty,
+// all values in v are appended and considered valid.
+func (b *Decimal128Builder) AppendValues(v []decimal128.Num, valid []bool) {
+ if len(v) != len(valid) && len(valid) != 0 {
+ panic("len(v) != len(valid) && len(valid) != 0")
+ }
+
+ if len(v) == 0 {
+ return
+ }
+
+ b.Reserve(len(v))
+ if len(v) > 0 {
+ arrow.Decimal128Traits.Copy(b.rawData[b.length:], v)
+ }
+ b.builder.unsafeAppendBoolsToBitmap(valid, len(v))
+}
+
+func (b *Decimal128Builder) init(capacity int) {
+ b.builder.init(capacity)
+
+ b.data = memory.NewResizableBuffer(b.mem)
+ bytesN := arrow.Decimal128Traits.BytesRequired(capacity)
+ b.data.Resize(bytesN)
+ b.rawData = arrow.Decimal128Traits.CastFromBytes(b.data.Bytes())
+}
+
+// Reserve ensures there is enough space for appending n elements
+// by checking the capacity and calling Resize if necessary.
+func (b *Decimal128Builder) Reserve(n int) {
+ b.builder.reserve(n, b.Resize)
+}
+
+// Resize adjusts the space allocated by b to n elements. If n is greater than b.Cap(),
+// additional memory will be allocated. If n is smaller, the allocated memory may reduced.
+func (b *Decimal128Builder) Resize(n int) {
+ nBuilder := n
+ if n < minBuilderCapacity {
+ n = minBuilderCapacity
+ }
+
+ if b.capacity == 0 {
+ b.init(n)
+ } else {
+ b.builder.resize(nBuilder, b.init)
+ b.data.Resize(arrow.Decimal128Traits.BytesRequired(n))
+ b.rawData = arrow.Decimal128Traits.CastFromBytes(b.data.Bytes())
+ }
+}
+
+// NewArray creates a Decimal128 array from the memory buffers used by the builder and resets the Decimal128Builder
+// so it can be used to build a new array.
+func (b *Decimal128Builder) NewArray() Interface {
+ return b.NewDecimal128Array()
+}
+
+// NewDecimal128Array creates a Decimal128 array from the memory buffers used by the builder and resets the Decimal128Builder
+// so it can be used to build a new array.
+func (b *Decimal128Builder) NewDecimal128Array() (a *Decimal128) {
+ data := b.newData()
+ a = NewDecimal128Data(data)
+ data.Release()
+ return
+}
+
+func (b *Decimal128Builder) newData() (data *Data) {
+ bytesRequired := arrow.Decimal128Traits.BytesRequired(b.length)
+ if bytesRequired > 0 && bytesRequired < b.data.Len() {
+ // trim buffers
+ b.data.Resize(bytesRequired)
+ }
+ data = NewData(b.dtype, b.length, []*memory.Buffer{b.nullBitmap, b.data}, nil, b.nulls, 0)
+ b.reset()
+
+ if b.data != nil {
+ b.data.Release()
+ b.data = nil
+ b.rawData = nil
+ }
+
+ return
+}
+
+var (
+ _ Interface = (*Decimal128)(nil)
+ _ Builder = (*Decimal128Builder)(nil)
+)
diff --git a/go/arrow/array/decimal128_test.go b/go/arrow/array/decimal128_test.go
new file mode 100644
index 0000000..5a39d92
--- /dev/null
+++ b/go/arrow/array/decimal128_test.go
@@ -0,0 +1,179 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package array_test
+
+import (
+ "testing"
+
+ "github.com/apache/arrow/go/arrow"
+ "github.com/apache/arrow/go/arrow/array"
+ "github.com/apache/arrow/go/arrow/decimal128"
+ "github.com/apache/arrow/go/arrow/memory"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestNewDecimal128Builder(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
+ defer mem.AssertSize(t, 0)
+
+ ab := array.NewDecimal128Builder(mem, &arrow.Decimal128Type{Precision: 10, Scale: 1})
+ defer ab.Release()
+
+ ab.Retain()
+ ab.Release()
+
+ want := []decimal128.Num{
+ decimal128.New(1, 1),
+ decimal128.New(2, 2),
+ decimal128.New(3, 3),
+ {},
+ decimal128.FromI64(-5),
+ decimal128.FromI64(-6),
+ {},
+ decimal128.FromI64(8),
+ decimal128.FromI64(9),
+ decimal128.FromI64(10),
+ }
+ valids := []bool{true, true, true, false, true, true, false, true, true, true}
+
+ for i, valid := range valids {
+ switch {
+ case valid:
+ ab.Append(want[i])
+ default:
+ ab.AppendNull()
+ }
+ }
+
+ // check state of builder before NewDecimal128Array
+ assert.Equal(t, 10, ab.Len(), "unexpected Len()")
+ assert.Equal(t, 2, ab.NullN(), "unexpected NullN()")
+
+ a := ab.NewArray().(*array.Decimal128)
+ a.Retain()
+ a.Release()
+
+ // check state of builder after NewDecimal128Array
+ assert.Zero(t, ab.Len(), "unexpected ArrayBuilder.Len(), NewDecimal128Array did not reset state")
+ assert.Zero(t, ab.Cap(), "unexpected ArrayBuilder.Cap(), NewDecimal128Array did not reset state")
+ assert.Zero(t, ab.NullN(), "unexpected ArrayBuilder.NullN(), NewDecimal128Array did not reset state")
+
+ // check state of array
+ assert.Equal(t, 2, a.NullN(), "unexpected null count")
+
+ assert.Equal(t, want, a.Values(), "unexpected Decimal128Values")
+ assert.Equal(t, []byte{0xb7}, a.NullBitmapBytes()[:1]) // 4 bytes due to minBuilderCapacity
+ assert.Len(t, a.Values(), 10, "unexpected length of Decimal128Values")
+
+ a.Release()
+ ab.Append(decimal128.FromI64(7))
+ ab.Append(decimal128.FromI64(8))
+
+ a = ab.NewDecimal128Array()
+
+ assert.Equal(t, 0, a.NullN())
+ assert.Equal(t, []decimal128.Num{decimal128.FromI64(7), decimal128.FromI64(8)}, a.Values())
+ assert.Len(t, a.Values(), 2)
+
+ a.Release()
+}
+
+func TestDecimal128Builder_Empty(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
+ defer mem.AssertSize(t, 0)
+
+ ab := array.NewDecimal128Builder(mem, &arrow.Decimal128Type{Precision: 10, Scale: 1})
+ defer ab.Release()
+
+ want := []decimal128.Num{decimal128.FromI64(3), decimal128.FromI64(4)}
+
+ ab.AppendValues([]decimal128.Num{}, nil)
+ a := ab.NewDecimal128Array()
+ assert.Zero(t, a.Len())
+ a.Release()
+
+ ab.AppendValues(nil, nil)
+ a = ab.NewDecimal128Array()
+ assert.Zero(t, a.Len())
+ a.Release()
+
+ ab.AppendValues(want, nil)
+ a = ab.NewDecimal128Array()
+ assert.Equal(t, want, a.Values())
+ a.Release()
+
+ ab.AppendValues([]decimal128.Num{}, nil)
+ ab.AppendValues(want, nil)
+ a = ab.NewDecimal128Array()
+ assert.Equal(t, want, a.Values())
+ a.Release()
+
+ ab.AppendValues(want, nil)
+ ab.AppendValues([]decimal128.Num{}, nil)
+ a = ab.NewDecimal128Array()
+ assert.Equal(t, want, a.Values())
+ a.Release()
+}
+
+func TestDecimal128Slice(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
+ defer mem.AssertSize(t, 0)
+
+ dtype := &arrow.Decimal128Type{Precision: 10, Scale: 1}
+ b := array.NewDecimal128Builder(mem, dtype)
+ defer b.Release()
+
+ var data = []decimal128.Num{
+ decimal128.FromI64(-1),
+ decimal128.FromI64(+0),
+ decimal128.FromI64(+1),
+ decimal128.New(-4, 4),
+ }
+ b.AppendValues(data[:2], nil)
+ b.AppendNull()
+ b.Append(data[3])
+
+ arr := b.NewDecimal128Array()
+ defer arr.Release()
+
+ if got, want := arr.Len(), len(data); got != want {
+ t.Fatalf("invalid array length: got=%d, want=%d", got, want)
+ }
+
+ slice := array.NewSliceData(arr.Data(), 2, 4)
+ defer slice.Release()
+
+ sub1 := array.MakeFromData(slice)
+ defer sub1.Release()
+
+ v, ok := sub1.(*array.Decimal128)
+ if !ok {
+ t.Fatalf("could not type-assert to array.String")
+ }
+
+ if got, want := v.String(), `[(null) {4 -4}]`; got != want {
+ t.Fatalf("got=%q, want=%q", got, want)
+ }
+
+ if got, want := v.NullN(), 1; got != want {
+ t.Fatalf("got=%q, want=%q", got, want)
+ }
+
+ if got, want := v.Data().Offset(), 2; got != want {
+ t.Fatalf("invalid offset: got=%d, want=%d", got, want)
+ }
+}
diff --git a/go/arrow/datatype_fixedwidth.go b/go/arrow/datatype_fixedwidth.go
index 8dc9c81..5f63ea1 100644
--- a/go/arrow/datatype_fixedwidth.go
+++ b/go/arrow/datatype_fixedwidth.go
@@ -16,7 +16,10 @@
package arrow
-import "strconv"
+import (
+ "fmt"
+ "strconv"
+)
type BooleanType struct{}
@@ -114,6 +117,19 @@ func (t *Float16Type) String() string { return "float16" }
// BitWidth returns the number of bits required to store a single element of this data type in memory.
func (t *Float16Type) BitWidth() int { return 16 }
+// Decimal128Type represents a fixed-size 128-bit decimal type.
+type Decimal128Type struct {
+ Precision int32
+ Scale int32
+}
+
+func (*Decimal128Type) ID() Type { return DECIMAL }
+func (*Decimal128Type) Name() string { return "decimal" }
+func (*Decimal128Type) BitWidth() int { return 16 }
+func (t *Decimal128Type) String() string {
+ return fmt.Sprintf("%s(%d, %d)", t.Name(), t.Precision, t.Scale)
+}
+
// MonthInterval represents a number of months.
type MonthInterval int32
diff --git a/go/arrow/datatype_fixedwidth_test.go b/go/arrow/datatype_fixedwidth_test.go
index 865f0ae..5fb02e0 100644
--- a/go/arrow/datatype_fixedwidth_test.go
+++ b/go/arrow/datatype_fixedwidth_test.go
@@ -40,3 +40,30 @@ func TestTimeUnit_String(t *testing.T) {
})
}
}
+
+func TestDecimal128Type(t *testing.T) {
+ for _, tc := range []struct {
+ precision int32
+ scale int32
+ want string
+ }{
+ {1, 10, "decimal(1, 10)"},
+ {10, 10, "decimal(10, 10)"},
+ {10, 1, "decimal(10, 1)"},
+ } {
+ t.Run(tc.want, func(t *testing.T) {
+ dt := arrow.Decimal128Type{Precision: tc.precision, Scale: tc.scale}
+ if got, want := dt.BitWidth(), 16; got != want {
+ t.Fatalf("invalid bitwidth: got=%d, want=%d", got, want)
+ }
+
+ if got, want := dt.ID(), arrow.DECIMAL; got != want {
+ t.Fatalf("invalid type ID: got=%v, want=%v", got, want)
+ }
+
+ if got, want := dt.String(), tc.want; got != want {
+ t.Fatalf("invalid stringer: got=%q, want=%q", got, want)
+ }
+ })
+ }
+}
diff --git a/go/arrow/decimal128/decimal128.go b/go/arrow/decimal128/decimal128.go
new file mode 100644
index 0000000..2f1b181
--- /dev/null
+++ b/go/arrow/decimal128/decimal128.go
@@ -0,0 +1,73 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package decimal128 // import "github.com/apache/arrow/go/arrow/decimal128"
+
+var (
+ MaxDecimal128 = New(542101086242752217, 687399551400673280-1)
+)
+
+// Num represents a signed 128-bit integer in two's complement.
+// Calculations wrap around and overflow is ignored.
+//
+// For a discussion of the algorithms, look at Knuth's volume 2,
+// Semi-numerical Algorithms section 4.3.1.
+//
+// Adapted from the Apache ORC C++ implementation
+type Num struct {
+ lo uint64 // low bits
+ hi int64 // high bits
+}
+
+// New returns a new signed 128-bit integer value.
+func New(hi int64, lo uint64) Num {
+ return Num{lo: lo, hi: hi}
+}
+
+// FromU64 returns a new signed 128-bit integer value from the provided uint64 one.
+func FromU64(v uint64) Num {
+ return New(0, v)
+}
+
+// FromI64 returns a new signed 128-bit integer value from the provided int64 one.
+func FromI64(v int64) Num {
+ switch {
+ case v > 0:
+ return New(0, uint64(v))
+ case v < 0:
+ return New(-1, uint64(v))
+ default:
+ return Num{}
+ }
+}
+
+// LowBits returns the low bits of the two's complement representation of the number.
+func (n Num) LowBits() uint64 { return n.lo }
+
+// HighBits returns the high bits of the two's complement representation of the number.
+func (n Num) HighBits() int64 { return n.hi }
+
+// Sign returns:
+//
+// -1 if x < 0
+// 0 if x == 0
+// +1 if x > 0
+func (n Num) Sign() int {
+ if n == (Num{}) {
+ return 0
+ }
+ return int(1 | (n.hi >> 63))
+}
diff --git a/go/arrow/decimal128/decimal128_test.go b/go/arrow/decimal128/decimal128_test.go
new file mode 100644
index 0000000..cf4ebd4
--- /dev/null
+++ b/go/arrow/decimal128/decimal128_test.go
@@ -0,0 +1,94 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package decimal128 // import "github.com/apache/arrow/go/arrow/decimal128"
+
+import (
+ "fmt"
+ "math"
+ "math/big"
+ "testing"
+)
+
+func TestFromU64(t *testing.T) {
+ for _, tc := range []struct {
+ v uint64
+ want Num
+ sign int
+ }{
+ {0, Num{0, 0}, 0},
+ {1, Num{1, 0}, +1},
+ {2, Num{2, 0}, +1},
+ {math.MaxInt64, Num{math.MaxInt64, 0}, +1},
+ {math.MaxUint64, Num{math.MaxUint64, 0}, +1},
+ } {
+ t.Run(fmt.Sprintf("%+0#x", tc.v), func(t *testing.T) {
+ v := FromU64(tc.v)
+ ref := new(big.Int).SetUint64(tc.v)
+ if got, want := v, tc.want; got != want {
+ t.Fatalf("invalid value. got=%+0#x, want=%+0#x (big-int=%+0#x)", got, want, ref)
+ }
+ if got, want := v.Sign(), tc.sign; got != want {
+ t.Fatalf("invalid sign for %+0#x: got=%v, want=%v", v, got, want)
+ }
+ if got, want := v.Sign(), ref.Sign(); got != want {
+ t.Fatalf("invalid sign for %+0#x: got=%v, want=%v", v, got, want)
+ }
+ if got, want := v.LowBits(), tc.want.lo; got != want {
+ t.Fatalf("invalid low-bits: got=%+0#x, want=%+0#x", got, want)
+ }
+ if got, want := v.HighBits(), tc.want.hi; got != want {
+ t.Fatalf("invalid high-bits: got=%+0#x, want=%+0#x", got, want)
+ }
+ })
+ }
+}
+
+func TestFromI64(t *testing.T) {
+ for _, tc := range []struct {
+ v int64
+ want Num
+ sign int
+ }{
+ {0, Num{0, 0}, 0},
+ {1, Num{1, 0}, 1},
+ {2, Num{2, 0}, 1},
+ {math.MaxInt64, Num{math.MaxInt64, 0}, 1},
+ {math.MinInt64, Num{u64Cnv(math.MinInt64), -1}, -1},
+ } {
+ t.Run(fmt.Sprintf("%+0#x", tc.v), func(t *testing.T) {
+ v := FromI64(tc.v)
+ ref := big.NewInt(tc.v)
+ if got, want := v, tc.want; got != want {
+ t.Fatalf("invalid value. got=%+0#x, want=%+0#x (big-int=%+0#x)", got, want, ref)
+ }
+ if got, want := v.Sign(), tc.sign; got != want {
+ t.Fatalf("invalid sign for %+0#x: got=%v, want=%v", v, got, want)
+ }
+ if got, want := v.Sign(), ref.Sign(); got != want {
+ t.Fatalf("invalid sign for %+0#x: got=%v, want=%v", v, got, want)
+ }
+ if got, want := v.LowBits(), tc.want.lo; got != want {
+ t.Fatalf("invalid low-bits: got=%+0#x, want=%+0#x", got, want)
+ }
+ if got, want := v.HighBits(), tc.want.hi; got != want {
+ t.Fatalf("invalid high-bits: got=%+0#x, want=%+0#x", got, want)
+ }
+ })
+ }
+}
+
+func u64Cnv(i int64) uint64 { return uint64(i) }
diff --git a/go/arrow/type_traits_decimal128.go b/go/arrow/type_traits_decimal128.go
new file mode 100644
index 0000000..debe6c7
--- /dev/null
+++ b/go/arrow/type_traits_decimal128.go
@@ -0,0 +1,75 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package arrow
+
+import (
+ "encoding/binary"
+ "reflect"
+ "unsafe"
+
+ "github.com/apache/arrow/go/arrow/decimal128"
+)
+
+// Decimal128 traits
+var Decimal128Traits decimal128Traits
+
+const (
+ // Decimal128SizeBytes specifies the number of bytes required to store a single decimal128 in memory
+ Decimal128SizeBytes = int(unsafe.Sizeof(decimal128.Num{}))
+)
+
+type decimal128Traits struct{}
+
+// BytesRequired returns the number of bytes required to store n elements in memory.
+func (decimal128Traits) BytesRequired(n int) int { return Decimal128SizeBytes * n }
+
+// PutValue
+func (decimal128Traits) PutValue(b []byte, v decimal128.Num) {
+ binary.LittleEndian.PutUint64(b[:8], uint64(v.LowBits()))
+ binary.LittleEndian.PutUint64(b[8:], uint64(v.HighBits()))
+}
+
+// CastFromBytes reinterprets the slice b to a slice of type uint16.
+//
+// NOTE: len(b) must be a multiple of Uint16SizeBytes.
+func (decimal128Traits) CastFromBytes(b []byte) []decimal128.Num {
+ h := (*reflect.SliceHeader)(unsafe.Pointer(&b))
+
+ var res []decimal128.Num
+ s := (*reflect.SliceHeader)(unsafe.Pointer(&res))
+ s.Data = h.Data
+ s.Len = h.Len / Decimal128SizeBytes
+ s.Cap = h.Cap / Decimal128SizeBytes
+
+ return res
+}
+
+// CastToBytes reinterprets the slice b to a slice of bytes.
+func (decimal128Traits) CastToBytes(b []decimal128.Num) []byte {
+ h := (*reflect.SliceHeader)(unsafe.Pointer(&b))
+
+ var res []byte
+ s := (*reflect.SliceHeader)(unsafe.Pointer(&res))
+ s.Data = h.Data
+ s.Len = h.Len * Decimal128SizeBytes
+ s.Cap = h.Cap * Decimal128SizeBytes
+
+ return res
+}
+
+// Copy copies src to dst.
+func (decimal128Traits) Copy(dst, src []decimal128.Num) { copy(dst, src) }
diff --git a/go/arrow/type_traits_test.go b/go/arrow/type_traits_test.go
index f2f1d9a..59ad06e 100644
--- a/go/arrow/type_traits_test.go
+++ b/go/arrow/type_traits_test.go
@@ -22,6 +22,7 @@ import (
"testing"
"github.com/apache/arrow/go/arrow"
+ "github.com/apache/arrow/go/arrow/decimal128"
"github.com/apache/arrow/go/arrow/float16"
)
@@ -87,6 +88,50 @@ func TestFloat16Traits(t *testing.T) {
}
}
+func TestDecimal128Traits(t *testing.T) {
+ const N = 10
+ nbytes := arrow.Decimal128Traits.BytesRequired(N)
+ b1 := arrow.Decimal128Traits.CastToBytes([]decimal128.Num{
+ decimal128.New(0, 10),
+ decimal128.New(1, 10),
+ decimal128.New(2, 10),
+ decimal128.New(3, 10),
+ decimal128.New(4, 10),
+ decimal128.New(5, 10),
+ decimal128.New(6, 10),
+ decimal128.New(7, 10),
+ decimal128.New(8, 10),
+ decimal128.New(9, 10),
+ })
+
+ b2 := make([]byte, nbytes)
+ for i := 0; i < N; i++ {
+ beg := i * arrow.Decimal128SizeBytes
+ end := (i + 1) * arrow.Decimal128SizeBytes
+ arrow.Decimal128Traits.PutValue(b2[beg:end], decimal128.New(int64(i), 10))
+ }
+
+ if !reflect.DeepEqual(b1, b2) {
+ v1 := arrow.Decimal128Traits.CastFromBytes(b1)
+ v2 := arrow.Decimal128Traits.CastFromBytes(b2)
+ t.Fatalf("invalid values:\nb1=%v\nb2=%v\nv1=%v\nv2=%v\n", b1, b2, v1, v2)
+ }
+
+ v1 := arrow.Decimal128Traits.CastFromBytes(b1)
+ for i, v := range v1 {
+ if got, want := v, decimal128.New(int64(i), 10); got != want {
+ t.Fatalf("invalid value[%d]. got=%v, want=%v", i, got, want)
+ }
+ }
+
+ v2 := make([]decimal128.Num, N)
+ arrow.Decimal128Traits.Copy(v2, v1)
+
+ if !reflect.DeepEqual(v1, v2) {
+ t.Fatalf("invalid values:\nv1=%v\nv2=%v\n", v1, v2)
+ }
+}
+
func TestMonthIntervalTraits(t *testing.T) {
const N = 10
b1 := arrow.MonthIntervalTraits.CastToBytes([]arrow.MonthInterval{