blob: 54cc3930efa4a3d57c7141affa973d0a83c54629 [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
import (
"slices"
"testing"
"github.com/apache/arrow-go/v18/arrow"
"github.com/apache/arrow-go/v18/arrow/memory"
"github.com/apache/arrow-go/v18/internal/utils/maphash"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDataReset(t *testing.T) {
var (
buffers1 = make([]*memory.Buffer, 0, 3)
buffers2 = make([]*memory.Buffer, 0, 3)
)
for i := 0; i < cap(buffers1); i++ {
buffers1 = append(buffers1, memory.NewBufferBytes([]byte("some-bytes1")))
buffers2 = append(buffers2, memory.NewBufferBytes([]byte("some-bytes2")))
}
data := NewData(&arrow.StringType{}, 10, buffers1, nil, 0, 0)
data.Reset(&arrow.Int64Type{}, 5, buffers2, nil, 1, 2)
for i := 0; i < 2; i++ {
assert.Equal(t, buffers2, data.Buffers())
assert.Equal(t, &arrow.Int64Type{}, data.DataType())
assert.Equal(t, 1, data.NullN())
assert.Equal(t, 2, data.Offset())
assert.Equal(t, 5, data.Len())
// Make sure it works when resetting the data with its own buffers (new buffers are retained
// before old ones are released.)
data.Reset(&arrow.Int64Type{}, 5, data.Buffers(), nil, 1, 2)
}
}
func TestNewSliceDataInvalidBounds(t *testing.T) {
data := NewData(&arrow.Int64Type{}, 3, nil, nil, 0, 0)
defer data.Release()
tests := []struct {
name string
i, j int64
}{
{name: "negative start", i: -1, j: 0},
{name: "negative end", i: -2, j: -1},
{name: "end beyond length", i: 0, j: 4},
{name: "start after end", i: 2, j: 1},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.PanicsWithValue(t, "arrow/array: index out of range", func() {
NewSliceData(data, tt.i, tt.j)
})
})
}
}
func TestDataResetClearsDictionary(t *testing.T) {
mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
values := memory.NewResizableBuffer(mem)
values.Resize(16)
dictData := NewData(arrow.PrimitiveTypes.Int32, 4, []*memory.Buffer{nil, values}, nil, 0, 0)
values.Release()
dictType := &arrow.DictionaryType{
IndexType: arrow.PrimitiveTypes.Int8,
ValueType: arrow.PrimitiveTypes.Int32,
}
data := NewDataWithDictionary(dictType, 0, []*memory.Buffer{nil, nil}, 0, 0, dictData)
defer data.Release()
assert.NotNil(t, data.Dictionary())
data.Reset(arrow.PrimitiveTypes.Int8, 0, []*memory.Buffer{nil, nil}, nil, 0, 0)
assert.Nil(t, data.Dictionary())
dictData.Release()
mem.AssertSize(t, 0)
}
func TestDataSetDictionaryWithSamePointerRetainsDictionary(t *testing.T) {
mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
defer mem.AssertSize(t, 0)
values := memory.NewResizableBuffer(mem)
values.Resize(4)
copy(values.Bytes(), arrow.Int32Traits.CastToBytes([]int32{42}))
dictData := NewData(arrow.PrimitiveTypes.Int32, 1, []*memory.Buffer{nil, values}, nil, 0, 0)
values.Release()
dictType := &arrow.DictionaryType{
IndexType: arrow.PrimitiveTypes.Int8,
ValueType: arrow.PrimitiveTypes.Int32,
}
data := NewDataWithDictionary(dictType, 1, []*memory.Buffer{nil, memory.NewBufferBytes([]byte{0})}, 0, 0, dictData)
defer data.Release()
dictData.Release()
data.SetDictionary(dictData)
require.Same(t, dictData, data.Dictionary())
require.NotNil(t, dictData.Buffers()[1])
valuesArray := NewInt32Data(dictData)
defer valuesArray.Release()
require.Equal(t, int32(42), valuesArray.Value(0))
}
func TestDataSetDictionaryNilClearsDictionary(t *testing.T) {
mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
defer mem.AssertSize(t, 0)
values := memory.NewResizableBuffer(mem)
values.Resize(4)
dictData := NewData(arrow.PrimitiveTypes.Int32, 1, []*memory.Buffer{nil, values}, nil, 0, 0)
values.Release()
dictType := &arrow.DictionaryType{
IndexType: arrow.PrimitiveTypes.Int8,
ValueType: arrow.PrimitiveTypes.Int32,
}
data := NewDataWithDictionary(dictType, 1, []*memory.Buffer{nil, memory.NewBufferBytes([]byte{0})}, 0, 0, dictData)
defer data.Release()
dictData.Release()
require.NotNil(t, data.Dictionary())
data.SetDictionary(nil)
require.Nil(t, data.Dictionary())
}
func TestHashIncludesDataMetadataAndBuffers(t *testing.T) {
seed := maphash.MakeSeed()
hash := func(data arrow.ArrayData) uint64 {
var h maphash.MapHash
h.SetSeed(seed)
Hash(&h, data)
return h.Sum64()
}
base := NewData(arrow.PrimitiveTypes.Int32, 2, []*memory.Buffer{
nil,
memory.NewBufferBytes([]byte{1, 0, 0, 0, 2, 0, 0, 0}),
}, nil, 0, 0)
defer base.Release()
baseHash := hash(base)
t.Run("value buffers", func(t *testing.T) {
other := NewData(arrow.PrimitiveTypes.Int32, 2, []*memory.Buffer{
nil,
memory.NewBufferBytes([]byte{3, 0, 0, 0, 4, 0, 0, 0}),
}, nil, 0, 0)
defer other.Release()
assert.NotEqual(t, baseHash, hash(other))
})
t.Run("data type", func(t *testing.T) {
other := NewData(arrow.PrimitiveTypes.Uint32, 2, base.Buffers(), nil, 0, 0)
defer other.Release()
assert.NotEqual(t, baseHash, hash(other))
})
t.Run("offset", func(t *testing.T) {
other := NewData(arrow.PrimitiveTypes.Int32, 2, base.Buffers(), nil, 0, 1)
defer other.Release()
assert.NotEqual(t, baseHash, hash(other))
})
t.Run("children", func(t *testing.T) {
childA := NewData(arrow.PrimitiveTypes.Int32, 1, []*memory.Buffer{
nil,
memory.NewBufferBytes([]byte{1, 0, 0, 0}),
}, nil, 0, 0)
defer childA.Release()
childB := NewData(arrow.PrimitiveTypes.Int32, 1, []*memory.Buffer{
nil,
memory.NewBufferBytes([]byte{2, 0, 0, 0}),
}, nil, 0, 0)
defer childB.Release()
offsets := memory.NewBufferBytes([]byte{0, 0, 0, 0, 1, 0, 0, 0})
left := NewData(arrow.ListOf(arrow.PrimitiveTypes.Int32), 1, []*memory.Buffer{nil, offsets}, []arrow.ArrayData{childA}, 0, 0)
defer left.Release()
right := NewData(arrow.ListOf(arrow.PrimitiveTypes.Int32), 1, []*memory.Buffer{nil, offsets}, []arrow.ArrayData{childB}, 0, 0)
defer right.Release()
assert.NotEqual(t, hash(left), hash(right))
})
t.Run("dictionary", func(t *testing.T) {
dictA := NewData(arrow.PrimitiveTypes.Int32, 1, []*memory.Buffer{
nil,
memory.NewBufferBytes([]byte{1, 0, 0, 0}),
}, nil, 0, 0)
defer dictA.Release()
dictB := NewData(arrow.PrimitiveTypes.Int32, 1, []*memory.Buffer{
nil,
memory.NewBufferBytes([]byte{2, 0, 0, 0}),
}, nil, 0, 0)
defer dictB.Release()
dt := &arrow.DictionaryType{IndexType: arrow.PrimitiveTypes.Uint8, ValueType: arrow.PrimitiveTypes.Int32}
indices := []*memory.Buffer{nil, memory.NewBufferBytes([]byte{0})}
left := NewDataWithDictionary(dt, 1, indices, 0, 0, dictA)
defer left.Release()
right := NewDataWithDictionary(dt, 1, indices, 0, 0, dictB)
defer right.Release()
assert.NotEqual(t, hash(left), hash(right))
})
}
func TestSizeInBytes(t *testing.T) {
var buffers1 = make([]*memory.Buffer, 0, 3)
for i := 0; i < cap(buffers1); i++ {
buffers1 = append(buffers1, memory.NewBufferBytes([]byte("15-bytes-buffer")))
}
data := NewData(&arrow.StringType{}, 10, buffers1, nil, 0, 0)
var arrayData arrow.ArrayData = data
dataWithChild := NewData(&arrow.StringType{}, 10, buffers1, []arrow.ArrayData{arrayData}, 0, 0)
buffers2 := slices.Clone(buffers1)
buffers2[0] = nil
dataWithNilBuffer := NewData(&arrow.StringType{}, 10, buffers2, nil, 0, 0)
t.Run("nil buffers", func(t *testing.T) {
expectedSize := uint64(30)
if actualSize := dataWithNilBuffer.SizeInBytes(); actualSize != expectedSize {
t.Errorf("expected size %d, got %d", expectedSize, actualSize)
}
})
t.Run("buffers only", func(t *testing.T) {
expectedSize := uint64(45)
if actualSize := data.SizeInBytes(); actualSize != expectedSize {
t.Errorf("expected size %d, got %d", expectedSize, actualSize)
}
})
t.Run("buffers and child data", func(t *testing.T) {
// 45 bytes in buffers, 45 bytes in child data
expectedSize := uint64(90)
if actualSize := dataWithChild.SizeInBytes(); actualSize != expectedSize {
t.Errorf("expected size %d, got %d", expectedSize, actualSize)
}
})
t.Run("buffers and nested child data", func(t *testing.T) {
var dataWithChildArrayData arrow.ArrayData = dataWithChild
var dataWithNestedChild arrow.ArrayData = NewData(&arrow.StringType{}, 10, buffers1, []arrow.ArrayData{dataWithChildArrayData}, 0, 0)
// 45 bytes in buffers, 90 bytes in nested child data
expectedSize := uint64(135)
if actualSize := dataWithNestedChild.SizeInBytes(); actualSize != expectedSize {
t.Errorf("expected size %d, got %d", expectedSize, actualSize)
}
})
t.Run("buffers and dictionary", func(t *testing.T) {
dictData := data
dataWithDict := NewDataWithDictionary(&arrow.StringType{}, 10, buffers1, 0, 0, dictData)
// 45 bytes in buffers, 45 bytes in dictionary
expectedSize := uint64(90)
if actualSize := dataWithDict.SizeInBytes(); actualSize != expectedSize {
t.Errorf("expected size %d, got %d", expectedSize, actualSize)
}
})
t.Run("sliced data", func(t *testing.T) {
sliceData := NewSliceData(arrayData, 3, 5)
// offset is not taken into account in SizeInBytes()
expectedSize := uint64(45)
if actualSize := sliceData.SizeInBytes(); actualSize != expectedSize {
t.Errorf("expected size %d, got %d", expectedSize, actualSize)
}
})
t.Run("sliced data with children", func(t *testing.T) {
var dataWithChildArrayData arrow.ArrayData = dataWithChild
sliceData := NewSliceData(dataWithChildArrayData, 3, 5)
// offset is not taken into account in SizeInBytes()
expectedSize := uint64(90)
if actualSize := sliceData.SizeInBytes(); actualSize != expectedSize {
t.Errorf("expected size %d, got %d", expectedSize, actualSize)
}
})
t.Run("buffers with children which are sliced data", func(t *testing.T) {
sliceData := NewSliceData(arrayData, 3, 5)
dataWithSlicedChildren := NewData(&arrow.StringType{}, 10, buffers1, []arrow.ArrayData{sliceData}, 0, 0)
// offset is not taken into account in SizeInBytes()
expectedSize := uint64(90)
if actualSize := dataWithSlicedChildren.SizeInBytes(); actualSize != expectedSize {
t.Errorf("expected size %d, got %d", expectedSize, actualSize)
}
})
}
func TestDataReleaseWithNilChildData(t *testing.T) {
mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
defer mem.AssertSize(t, 0)
// Create a Data object that simulates the state after a failed concatenation
// where childData slice is allocated but contains nil elements
buffers := []*memory.Buffer{memory.NewBufferBytes([]byte("test-buffer"))}
data := NewData(arrow.ListOf(arrow.PrimitiveTypes.Int32), 1, buffers, nil, 0, 0)
// Simulate the scenario where childData is allocated but elements remain nil
// This happens in concat.go when childData is allocated but concat() fails
data.childData = make([]arrow.ArrayData, 1)
// data.childData[0] remains nil (simulating failed concat)
assert.NotPanics(t, func() {
data.Release()
}, "Release() should not panic when childData contains nil elements")
}