blob: 087da9feb7c6e4136a49e4c6c335d35d155dbfce [file]
// 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 (
"bytes"
"reflect"
"testing"
"github.com/apache/arrow-go/v18/arrow"
"github.com/apache/arrow-go/v18/arrow/array"
"github.com/apache/arrow-go/v18/arrow/bitutil"
"github.com/apache/arrow-go/v18/arrow/memory"
"github.com/apache/arrow-go/v18/internal/json"
"github.com/stretchr/testify/assert"
)
func TestStringArray(t *testing.T) {
mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
defer mem.AssertSize(t, 0)
var (
want = []string{"hello", "世界", "", "bye"}
valids = []bool{true, true, false, true}
offsets = []int32{0, 5, 11, 11, 14}
)
sb := array.NewStringBuilder(mem)
defer sb.Release()
sb.Retain()
sb.Release()
assert.NoError(t, sb.AppendValueFromString(want[0]))
sb.AppendValues(want[1:2], nil)
sb.AppendNull()
sb.Append(want[3])
if got, want := sb.Len(), len(want); got != want {
t.Fatalf("invalid len: got=%d, want=%d", got, want)
}
if got, want := sb.NullN(), 1; got != want {
t.Fatalf("invalid nulls: got=%d, want=%d", got, want)
}
arr := sb.NewStringArray()
defer arr.Release()
arr.Retain()
arr.Release()
assert.Equal(t, "hello", arr.ValueStr(0))
if got, want := arr.Len(), len(want); got != want {
t.Fatalf("invalid len: got=%d, want=%d", got, want)
}
if got, want := arr.NullN(), 1; got != want {
t.Fatalf("invalid nulls: got=%d, want=%d", got, want)
}
for i := range want {
if arr.IsNull(i) != !valids[i] {
t.Fatalf("arr[%d]-validity: got=%v want=%v", i, !arr.IsNull(i), valids[i])
}
switch {
case arr.IsNull(i):
default:
got := arr.Value(i)
if got != want[i] {
t.Fatalf("arr[%d]: got=%q, want=%q", i, got, want[i])
}
}
if got, want := arr.ValueOffset(i), int(offsets[i]); got != want {
t.Fatalf("arr-offset-beg[%d]: got=%d, want=%d", i, got, want)
}
}
if !reflect.DeepEqual(offsets, arr.ValueOffsets()) {
t.Fatalf("ValueOffsets got=%v, want=%v", arr.ValueOffsets(), offsets)
}
sub := array.MakeFromData(arr.Data())
defer sub.Release()
if sub.DataType().ID() != arrow.STRING {
t.Fatalf("invalid type: got=%q, want=string", sub.DataType().Name())
}
if _, ok := sub.(*array.String); !ok {
t.Fatalf("could not type-assert to array.String")
}
if got, want := arr.String(), `["hello" "世界" (null) "bye"]`; got != want {
t.Fatalf("got=%q, want=%q", got, want)
}
if !bytes.Equal([]byte(`hello世界bye`), arr.ValueBytes()) {
t.Fatalf("got=%q, want=%q", string(arr.ValueBytes()), `hello世界bye`)
}
slice := array.NewSliceData(arr.Data(), 2, 4)
defer slice.Release()
sub1 := array.MakeFromData(slice)
defer sub1.Release()
v, ok := sub1.(*array.String)
if !ok {
t.Fatalf("could not type-assert to array.String")
}
if got, want := v.String(), `[(null) "bye"]`; got != want {
t.Fatalf("got=%q, want=%q", got, want)
}
if !bytes.Equal(v.ValueBytes(), []byte("bye")) {
t.Fatalf("got=%q, want=%q", string(v.ValueBytes()), "bye")
}
for i := 0; i < v.Len(); i++ {
if got, want := v.ValueOffset(0), int(offsets[i+slice.Offset()]); got != want {
t.Fatalf("val-offset-with-offset[%d]: got=%q, want=%q", i, got, want)
}
}
if !reflect.DeepEqual(offsets[2:5], v.ValueOffsets()) {
t.Fatalf("ValueOffsets got=%v, want=%v", v.ValueOffsets(), offsets[2:5])
}
}
func TestStringSliceOutOfBounds(t *testing.T) {
mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
defer mem.AssertSize(t, 0)
values := []string{"a", "bc", "def", "g", "hijk", "lm", "n", "opq", "rs", "tu"}
b := array.NewStringBuilder(mem)
defer b.Release()
for _, v := range values {
b.AppendString(v)
}
arr := b.NewArray().(*array.String)
defer arr.Release()
slice := array.NewSlice(arr, 3, 8).(*array.String)
defer slice.Release()
tests := []struct {
index int
panic bool
}{
{
index: -1,
panic: true,
},
{
index: 5,
panic: true,
},
{
index: 0,
panic: false,
},
{
index: 4,
panic: false,
},
}
for _, tc := range tests {
t.Run("", func(t *testing.T) {
var val string
if tc.panic {
defer func() {
e := recover()
if e == nil {
t.Fatalf("this should have panicked, but did not; slice value %q", val)
}
if got, want := e.(string), "arrow/array: index out of range"; got != want {
t.Fatalf("invalid error. got=%q, want=%q", got, want)
}
}()
} else {
defer func() {
if e := recover(); e != nil {
t.Fatalf("unexpected panic: %v", e)
}
}()
}
val = slice.Value(tc.index)
})
}
}
func TestLargeStringSliceOutOfBounds(t *testing.T) {
mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
defer mem.AssertSize(t, 0)
values := []string{"a", "bc", "def", "g", "hijk", "lm", "n", "opq", "rs", "tu"}
b := array.NewLargeStringBuilder(mem)
defer b.Release()
for _, v := range values {
b.Append(v)
}
arr := b.NewArray().(*array.LargeString)
defer arr.Release()
slice := array.NewSlice(arr, 3, 8).(*array.LargeString)
defer slice.Release()
tests := []struct {
index int
panic bool
}{
{
index: -1,
panic: true,
},
{
index: 5,
panic: true,
},
{
index: 0,
panic: false,
},
{
index: 4,
panic: false,
},
}
for _, tc := range tests {
t.Run("", func(t *testing.T) {
var val string
if tc.panic {
defer func() {
e := recover()
if e == nil {
t.Fatalf("this should have panicked, but did not; slice value %q", val)
}
if got, want := e.(string), "arrow/array: index out of range"; got != want {
t.Fatalf("invalid error. got=%q, want=%q", got, want)
}
}()
} else {
defer func() {
if e := recover(); e != nil {
t.Fatalf("unexpected panic: %v", e)
}
}()
}
val = slice.Value(tc.index)
})
}
}
func TestStringBuilder_Empty(t *testing.T) {
mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
defer mem.AssertSize(t, 0)
want := []string{"hello", "世界", "", "bye"}
ab := array.NewStringBuilder(mem)
defer ab.Release()
stringValues := func(a *array.String) []string {
vs := make([]string, a.Len())
for i := range vs {
vs[i] = a.Value(i)
}
return vs
}
ab.AppendValues([]string{}, nil)
a := ab.NewStringArray()
assert.Zero(t, a.Len())
a.Release()
ab.AppendValues(nil, nil)
a = ab.NewStringArray()
assert.Zero(t, a.Len())
a.Release()
ab.AppendValues([]string{}, nil)
ab.AppendValues(want, nil)
a = ab.NewStringArray()
assert.Equal(t, want, stringValues(a))
a.Release()
ab.AppendValues(want, nil)
ab.AppendValues([]string{}, nil)
a = ab.NewStringArray()
assert.Equal(t, want, stringValues(a))
a.Release()
}
// TestStringReset tests the Reset() method on the String type by creating two different Strings and then
// resetting the contents of string2 with the values from string1.
func TestStringReset(t *testing.T) {
mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
sb1 := array.NewStringBuilder(mem)
sb2 := array.NewStringBuilder(mem)
defer sb1.Release()
defer sb2.Release()
sb1.Append("string1")
sb1.AppendNull()
var (
string1 = sb1.NewStringArray()
string2 = sb2.NewStringArray()
string1Data = string1.Data()
)
string2.Reset(string1Data)
assert.Equal(t, "string1", string2.Value(0))
}
func TestStringInvalidOffsets(t *testing.T) {
const expectedPanic = "arrow/array: string offsets out of bounds of data buffer"
makeBuffers := func(valids []bool, offsets []int32, data string) []*memory.Buffer {
offsetBuf := memory.NewBufferBytes(arrow.Int32Traits.CastToBytes(offsets))
var nullBufBytes []byte
var nullBuf *memory.Buffer
if valids != nil {
nullBufBytes = make([]byte, bitutil.BytesForBits(int64(len(valids))))
for i, v := range valids {
bitutil.SetBitTo(nullBufBytes, i, v)
}
nullBuf = memory.NewBufferBytes(nullBufBytes)
}
return []*memory.Buffer{nullBuf, offsetBuf, memory.NewBufferBytes([]byte(data))}
}
assert.NotPanics(t, func() {
buffers := makeBuffers(nil, []int32{}, "")
array.NewStringData(array.NewData(arrow.BinaryTypes.String, 0, buffers, nil, 0, 0))
}, "empty array with no offsets")
assert.NotPanics(t, func() {
buffers := makeBuffers(nil, []int32{0, 5}, "")
array.NewStringData(array.NewData(arrow.BinaryTypes.String, 0, buffers, nil, 0, 0))
}, "empty array, offsets ignored")
assert.NotPanics(t, func() {
buffers := makeBuffers(nil, []int32{0, 3, 4, 9}, "oooabcdef")
array.NewStringData(array.NewData(arrow.BinaryTypes.String, 1, buffers, nil, 0, 2))
}, "data has offset and value offsets are valid")
assert.NotPanics(t, func() {
buffers := makeBuffers(nil, []int32{0, 3, 6, 9, 9}, "012345678")
arr := array.NewStringData(array.NewData(arrow.BinaryTypes.String, 4, buffers, nil, 0, 0))
if assert.Equal(t, 4, arr.Len()) && assert.Zero(t, arr.NullN()) {
assert.Equal(t, "012", arr.Value(0))
assert.Equal(t, "345", arr.Value(1))
assert.Equal(t, "678", arr.Value(2))
assert.Equal(t, "", arr.Value(3), "trailing empty string value will have offset past end")
}
}, "simple valid case")
assert.NotPanics(t, func() {
buffers := makeBuffers([]bool{true, false, true, false}, []int32{0, 3, 4, 9, 9}, "oooabcdef")
arr := array.NewStringData(array.NewData(arrow.BinaryTypes.String, 4, buffers, nil, 2, 0))
if assert.Equal(t, 4, arr.Len()) && assert.Equal(t, 2, arr.NullN()) {
assert.Equal(t, "ooo", arr.Value(0))
assert.True(t, arr.IsNull(1))
assert.Equal(t, "bcdef", arr.Value(2))
assert.True(t, arr.IsNull(3))
}
}, "simple valid case with nulls")
assert.PanicsWithValue(t, expectedPanic, func() {
buffers := makeBuffers(nil, []int32{0, 5}, "abc")
array.NewStringData(array.NewData(arrow.BinaryTypes.String, 1, buffers, nil, 0, 0))
}, "last offset is overflowing")
assert.PanicsWithError(t, "arrow/array: string offset buffer must have at least 2 values", func() {
buffers := makeBuffers(nil, []int32{0}, "abc")
array.NewStringData(array.NewData(arrow.BinaryTypes.String, 1, buffers, nil, 0, 0))
}, "last offset is missing")
assert.PanicsWithValue(t, expectedPanic, func() {
buffers := makeBuffers(nil, []int32{0, 3, 10, 15}, "oooabcdef")
array.NewStringData(array.NewData(arrow.BinaryTypes.String, 1, buffers, nil, 0, 2))
}, "data has offset and value offset is overflowing")
}
func TestStringStringRoundTrip(t *testing.T) {
// 1. create array
mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
defer mem.AssertSize(t, 0)
var (
values = []string{"hello", "世界", "", "bye"}
valid = []bool{true, true, false, true}
)
b := array.NewStringBuilder(mem)
defer b.Release()
b.AppendValues(values, valid)
arr := b.NewArray().(*array.String)
defer arr.Release()
// 2. create array via AppendValueFromString
b1 := array.NewStringBuilder(mem)
defer b1.Release()
for i := 0; i < arr.Len(); i++ {
assert.NoError(t, b1.AppendValueFromString(arr.ValueStr(i)))
}
arr1 := b1.NewArray().(*array.String)
defer arr1.Release()
assert.True(t, array.Equal(arr, arr1))
}
func TestLargeStringArray(t *testing.T) {
mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
defer mem.AssertSize(t, 0)
var (
want = []string{"hello", "世界", "", "bye"}
valids = []bool{true, true, false, true}
offsets = []int64{0, 5, 11, 11, 14}
)
sb := array.NewLargeStringBuilder(mem)
defer sb.Release()
sb.Retain()
sb.Release()
sb.AppendValues(want[:2], nil)
sb.AppendNull()
sb.Append(want[3])
if got, want := sb.Len(), len(want); got != want {
t.Fatalf("invalid len: got=%d, want=%d", got, want)
}
if got, want := sb.NullN(), 1; got != want {
t.Fatalf("invalid nulls: got=%d, want=%d", got, want)
}
arr := sb.NewLargeStringArray()
defer arr.Release()
arr.Retain()
arr.Release()
if got, want := arr.Len(), len(want); got != want {
t.Fatalf("invalid len: got=%d, want=%d", got, want)
}
if got, want := arr.NullN(), 1; got != want {
t.Fatalf("invalid nulls: got=%d, want=%d", got, want)
}
for i := range want {
if arr.IsNull(i) != !valids[i] {
t.Fatalf("arr[%d]-validity: got=%v want=%v", i, !arr.IsNull(i), valids[i])
}
switch {
case arr.IsNull(i):
default:
got := arr.Value(i)
if got != want[i] {
t.Fatalf("arr[%d]: got=%q, want=%q", i, got, want[i])
}
}
if got, want := arr.ValueOffset(i), offsets[i]; got != want {
t.Fatalf("arr-offset-beg[%d]: got=%d, want=%d", i, got, want)
}
}
if !reflect.DeepEqual(offsets, arr.ValueOffsets()) {
t.Fatalf("ValueOffsets got=%v, want=%v", arr.ValueOffsets(), offsets)
}
sub := array.MakeFromData(arr.Data())
defer sub.Release()
if sub.DataType().ID() != arrow.LARGE_STRING {
t.Fatalf("invalid type: got=%q, want=large_string", sub.DataType().Name())
}
if _, ok := sub.(*array.LargeString); !ok {
t.Fatalf("could not type-assert to array.LargeString")
}
if got, want := arr.String(), `["hello" "世界" (null) "bye"]`; got != want {
t.Fatalf("got=%q, want=%q", got, want)
}
if !bytes.Equal([]byte(`hello世界bye`), arr.ValueBytes()) {
t.Fatalf("got=%q, want=%q", string(arr.ValueBytes()), `hello世界bye`)
}
slice := array.NewSliceData(arr.Data(), 2, 4)
defer slice.Release()
sub1 := array.MakeFromData(slice)
defer sub1.Release()
v, ok := sub1.(*array.LargeString)
if !ok {
t.Fatalf("could not type-assert to array.LargeString")
}
if got, want := v.String(), `[(null) "bye"]`; got != want {
t.Fatalf("got=%q, want=%q", got, want)
}
if !bytes.Equal(v.ValueBytes(), []byte("bye")) {
t.Fatalf("got=%q, want=%q", string(v.ValueBytes()), "bye")
}
for i := 0; i < v.Len(); i++ {
if got, want := v.ValueOffset(0), offsets[i+slice.Offset()]; got != want {
t.Fatalf("val-offset-with-offset[%d]: got=%q, want=%q", i, got, want)
}
}
if !reflect.DeepEqual(offsets[2:5], v.ValueOffsets()) {
t.Fatalf("ValueOffsets got=%v, want=%v", v.ValueOffsets(), offsets[2:5])
}
}
func TestLargeStringBuilder_Empty(t *testing.T) {
mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
defer mem.AssertSize(t, 0)
want := []string{"hello", "世界", "", "bye"}
ab := array.NewLargeStringBuilder(mem)
defer ab.Release()
stringValues := func(a *array.LargeString) []string {
vs := make([]string, a.Len())
for i := range vs {
vs[i] = a.Value(i)
}
return vs
}
ab.AppendValues([]string{}, nil)
a := ab.NewLargeStringArray()
assert.Zero(t, a.Len())
a.Release()
ab.AppendValues(nil, nil)
a = ab.NewLargeStringArray()
assert.Zero(t, a.Len())
a.Release()
ab.AppendValues([]string{}, nil)
ab.AppendValues(want, nil)
a = ab.NewLargeStringArray()
assert.Equal(t, want, stringValues(a))
a.Release()
ab.AppendValues(want, nil)
ab.AppendValues([]string{}, nil)
a = ab.NewLargeStringArray()
assert.Equal(t, want, stringValues(a))
a.Release()
}
// TestStringReset tests the Reset() method on the String type by creating two different Strings and then
// resetting the contents of string2 with the values from string1.
func TestLargeStringReset(t *testing.T) {
mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
sb1 := array.NewLargeStringBuilder(mem)
sb2 := array.NewLargeStringBuilder(mem)
defer sb1.Release()
defer sb2.Release()
sb1.Append("string1")
sb1.AppendNull()
var (
string1 = sb1.NewLargeStringArray()
string2 = sb2.NewLargeStringArray()
string1Data = string1.Data()
)
string2.Reset(string1Data)
assert.Equal(t, "string1", string2.Value(0))
}
func TestLargeStringInvalidOffsets(t *testing.T) {
const expectedPanic = "arrow/array: string offsets out of bounds of data buffer"
makeBuffers := func(valids []bool, offsets []int64, data string) []*memory.Buffer {
offsetBuf := memory.NewBufferBytes(arrow.Int64Traits.CastToBytes(offsets))
var nullBufBytes []byte
var nullBuf *memory.Buffer
if valids != nil {
nullBufBytes = make([]byte, bitutil.BytesForBits(int64(len(valids))))
for i, v := range valids {
bitutil.SetBitTo(nullBufBytes, i, v)
}
nullBuf = memory.NewBufferBytes(nullBufBytes)
}
return []*memory.Buffer{nullBuf, offsetBuf, memory.NewBufferBytes([]byte(data))}
}
assert.NotPanics(t, func() {
buffers := makeBuffers(nil, []int64{}, "")
array.NewLargeStringData(array.NewData(arrow.BinaryTypes.LargeString, 0, buffers, nil, 0, 0))
}, "empty array with no offsets")
assert.NotPanics(t, func() {
buffers := makeBuffers(nil, []int64{0, 5}, "")
array.NewLargeStringData(array.NewData(arrow.BinaryTypes.LargeString, 0, buffers, nil, 0, 0))
}, "empty array, offsets ignored")
assert.NotPanics(t, func() {
buffers := makeBuffers(nil, []int64{0, 3, 4, 9}, "oooabcdef")
array.NewLargeStringData(array.NewData(arrow.BinaryTypes.LargeString, 1, buffers, nil, 0, 2))
}, "data has offset and value offsets are valid")
assert.NotPanics(t, func() {
buffers := makeBuffers(nil, []int64{0, 3, 6, 9, 9}, "012345678")
arr := array.NewLargeStringData(array.NewData(arrow.BinaryTypes.LargeString, 4, buffers, nil, 0, 0))
if assert.Equal(t, 4, arr.Len()) && assert.Zero(t, arr.NullN()) {
assert.Equal(t, "012", arr.Value(0))
assert.Equal(t, "345", arr.Value(1))
assert.Equal(t, "678", arr.Value(2))
assert.Equal(t, "", arr.Value(3), "trailing empty string value will have offset past end")
}
}, "simple valid case")
assert.NotPanics(t, func() {
buffers := makeBuffers([]bool{true, false, true, false}, []int64{0, 3, 4, 9, 9}, "oooabcdef")
arr := array.NewLargeStringData(array.NewData(arrow.BinaryTypes.LargeString, 4, buffers, nil, 2, 0))
if assert.Equal(t, 4, arr.Len()) && assert.Equal(t, 2, arr.NullN()) {
assert.Equal(t, "ooo", arr.Value(0))
assert.True(t, arr.IsNull(1))
assert.Equal(t, "bcdef", arr.Value(2))
assert.True(t, arr.IsNull(3))
}
}, "simple valid case with nulls")
assert.PanicsWithValue(t, expectedPanic, func() {
buffers := makeBuffers(nil, []int64{0, 5}, "abc")
array.NewLargeStringData(array.NewData(arrow.BinaryTypes.LargeString, 1, buffers, nil, 0, 0))
}, "last offset is overflowing")
assert.PanicsWithError(t, "arrow/array: string offset buffer must have at least 2 values", func() {
buffers := makeBuffers(nil, []int64{0}, "abc")
array.NewLargeStringData(array.NewData(arrow.BinaryTypes.LargeString, 1, buffers, nil, 0, 0))
}, "last offset is missing")
assert.PanicsWithValue(t, expectedPanic, func() {
buffers := makeBuffers(nil, []int64{0, 3, 10, 15}, "oooabcdef")
array.NewLargeStringData(array.NewData(arrow.BinaryTypes.LargeString, 1, buffers, nil, 0, 2))
}, "data has offset and value offset is overflowing")
}
func TestLargeStringStringRoundTrip(t *testing.T) {
// 1. create array
mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
defer mem.AssertSize(t, 0)
var (
values = []string{"hello", "世界", "", "bye"}
valid = []bool{true, true, false, true}
)
b := array.NewLargeStringBuilder(mem)
defer b.Release()
b.AppendValues(values, valid)
arr := b.NewArray().(*array.LargeString)
defer arr.Release()
// 2. create array via AppendValueFromString
b1 := array.NewLargeStringBuilder(mem)
defer b1.Release()
for i := 0; i < arr.Len(); i++ {
assert.NoError(t, b1.AppendValueFromString(arr.ValueStr(i)))
}
arr1 := b1.NewArray().(*array.LargeString)
defer arr1.Release()
assert.True(t, array.Equal(arr, arr1))
}
func TestStringValueLen(t *testing.T) {
mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
defer mem.AssertSize(t, 0)
values := []string{"a", "bc", "", "", "hijk", "lm", "", "opq", "", "tu"}
valids := []bool{true, true, false, false, true, true, true, true, false, true}
b := array.NewStringBuilder(mem)
defer b.Release()
b.AppendStringValues(values, valids)
arr := b.NewArray().(*array.String)
defer arr.Release()
slice := array.NewSlice(arr, 2, 9).(*array.String)
defer slice.Release()
vs := values[2:9]
for i, v := range vs {
assert.Equal(t, len(v), slice.ValueLen(i))
}
}
func TestStringViewArray(t *testing.T) {
mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
defer mem.AssertSize(t, 0)
var (
// only the last string is long enough to not get inlined
want = []string{"hello", "世界", "", "say goodbye daffy"}
valids = []bool{true, true, false, true}
)
sb := array.NewStringViewBuilder(mem)
defer sb.Release()
sb.Retain()
sb.Release()
assert.NoError(t, sb.AppendValueFromString(want[0]))
sb.AppendValues(want[1:2], nil)
sb.AppendNull()
sb.Append(want[3])
if got, want := sb.Len(), len(want); got != want {
t.Fatalf("invalid len: got=%d, want=%d", got, want)
}
if got, want := sb.NullN(), 1; got != want {
t.Fatalf("invalid nulls: got=%d, want=%d", got, want)
}
arr := sb.NewStringViewArray()
defer arr.Release()
arr.Retain()
arr.Release()
assert.Equal(t, "hello", arr.ValueStr(0))
if got, want := arr.Len(), len(want); got != want {
t.Fatalf("invalid len: got=%d, want=%d", got, want)
}
if got, want := arr.NullN(), 1; got != want {
t.Fatalf("invalid nulls: got=%d, want=%d", got, want)
}
for i := range want {
if arr.IsNull(i) != !valids[i] {
t.Fatalf("arr[%d]-validity: got=%v want=%v", i, !arr.IsNull(i), valids[i])
}
switch {
case arr.IsNull(i):
default:
got := arr.Value(i)
if got != want[i] {
t.Fatalf("arr[%d]: got=%q, want=%q", i, got, want[i])
}
}
}
sub := array.MakeFromData(arr.Data())
defer sub.Release()
if sub.DataType().ID() != arrow.STRING_VIEW {
t.Fatalf("invalid type: got=%q, want=string view", sub.DataType().Name())
}
if _, ok := sub.(*array.StringView); !ok {
t.Fatalf("could not type-assert to array.String")
}
if got, want := arr.String(), `["hello" "世界" (null) "say goodbye daffy"]`; got != want {
t.Fatalf("got=%q, want=%q", got, want)
}
// only the last string gets stuck into a buffer the rest are inlined
// in the headers.
if !bytes.Equal([]byte(`say goodbye daffy`), arr.Data().Buffers()[2].Bytes()) {
t.Fatalf("got=%q, want=%q", string(arr.Data().Buffers()[2].Bytes()), `say goodbye daffy`)
}
// check the prefix for the non-inlined value
if [4]byte{'s', 'a', 'y', ' '} != arr.ValueHeader(3).Prefix() {
t.Fatalf("got=%q, want=%q", arr.ValueHeader(3).Prefix(), `say `)
}
slice := array.NewSliceData(arr.Data(), 2, 4)
defer slice.Release()
sub1 := array.MakeFromData(slice)
defer sub1.Release()
v, ok := sub1.(*array.StringView)
if !ok {
t.Fatalf("could not type-assert to array.StringView")
}
if got, want := v.String(), `[(null) "say goodbye daffy"]`; got != want {
t.Fatalf("got=%q, want=%q", got, want)
}
if !bytes.Equal([]byte(`say goodbye daffy`), v.Data().Buffers()[2].Bytes()) {
t.Fatalf("got=%q, want=%q", string(v.Data().Buffers()[2].Bytes()), `say goodbye daffy`)
}
// check the prefix for the non-inlined value
if [4]byte{'s', 'a', 'y', ' '} != v.ValueHeader(1).Prefix() {
t.Fatalf("got=%q, want=%q", v.ValueHeader(1).Prefix(), `say `)
}
}
func TestStringViewBuilder_Empty(t *testing.T) {
mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
defer mem.AssertSize(t, 0)
want := []string{"hello", "世界", "", "say goodbye daffy"}
ab := array.NewStringViewBuilder(mem)
defer ab.Release()
stringValues := func(a *array.StringView) []string {
vs := make([]string, a.Len())
for i := range vs {
vs[i] = a.Value(i)
}
return vs
}
ab.AppendValues([]string{}, nil)
a := ab.NewStringViewArray()
assert.Zero(t, a.Len())
a.Release()
ab.AppendValues(nil, nil)
a = ab.NewStringViewArray()
assert.Zero(t, a.Len())
a.Release()
ab.AppendValues([]string{}, nil)
ab.AppendValues(want, nil)
a = ab.NewStringViewArray()
assert.Equal(t, want, stringValues(a))
a.Release()
ab.AppendValues(want, nil)
ab.AppendValues([]string{}, nil)
a = ab.NewStringViewArray()
assert.Equal(t, want, stringValues(a))
a.Release()
}
func TestStringViewBuilderUnmarshalOneWrongType(t *testing.T) {
mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
defer mem.AssertSize(t, 0)
bldr := array.NewStringViewBuilder(mem)
defer bldr.Release()
dec := json.NewDecoder(bytes.NewReader([]byte(`1`)))
err := bldr.UnmarshalOne(dec)
assert.Error(t, err)
var ute *json.UnmarshalTypeError
assert.ErrorAs(t, err, &ute)
if assert.NotNil(t, ute) {
assert.Equal(t, reflect.TypeOf(""), ute.Type)
}
}
// TestStringReset tests the Reset() method on the String type by creating two different Strings and then
// resetting the contents of string2 with the values from string1.
func TestStringViewReset(t *testing.T) {
mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
sb1 := array.NewStringViewBuilder(mem)
sb2 := array.NewStringViewBuilder(mem)
defer sb1.Release()
defer sb2.Release()
sb1.Append("string1")
sb1.AppendNull()
var (
string1 = sb1.NewStringViewArray()
string2 = sb2.NewStringViewArray()
string1Data = string1.Data()
)
string2.Reset(string1Data)
assert.Equal(t, "string1", string2.Value(0))
}