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 168951e2 fix(array): retain dictionary before replacement (#926)
168951e2 is described below
commit 168951e287b98456611fac4dc1de021bc52e95f8
Author: Minh Vu <[email protected]>
AuthorDate: Mon Jul 13 21:09:35 2026 +0200
fix(array): retain dictionary before replacement (#926)
Data.SetDictionary released the existing dictionary before retaining the
replacement. Replacing a dictionary with the same pointer while the parent held
the final reference could therefore clear the dictionary before resurrecting
its reference.
This retains the validated replacement first, swaps it into the Data
object, then releases the previous dictionary. It also handles nil input and
reports an explicit panic for an invalid dictionary data implementation.
Tests:
- go test ./arrow/array -run
'TestData(SetDictionaryWithSamePointerRetainsDictionary|ResetClearsDictionary)'
- go test ./arrow/array
- go test ./arrow/... -run '^$'
---
arrow/array/data.go | 21 +++++++++++++------
arrow/array/data_test.go | 52 ++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 67 insertions(+), 6 deletions(-)
diff --git a/arrow/array/data.go b/arrow/array/data.go
index 47d83f56..5de367d5 100644
--- a/arrow/array/data.go
+++ b/arrow/array/data.go
@@ -187,13 +187,22 @@ func (d *Data) Dictionary() arrow.ArrayData { return
d.dictionary }
// SetDictionary allows replacing the dictionary for this particular Data
object
func (d *Data) SetDictionary(dict arrow.ArrayData) {
- if d.dictionary != nil {
- d.dictionary.Release()
- d.dictionary = nil
+ var newDict *Data
+ if dict != nil {
+ var ok bool
+ newDict, ok = dict.(*Data)
+ if !ok {
+ panic("arrow/array: dictionary data must be
*array.Data")
+ }
}
- if dict.(*Data) != nil {
- dict.Retain()
- d.dictionary = dict.(*Data)
+
+ if newDict != nil {
+ newDict.Retain()
+ }
+ oldDict := d.dictionary
+ d.dictionary = newDict
+ if oldDict != nil {
+ oldDict.Release()
}
}
diff --git a/arrow/array/data_test.go b/arrow/array/data_test.go
index 95eb3848..54cc3930 100644
--- a/arrow/array/data_test.go
+++ b/arrow/array/data_test.go
@@ -24,6 +24,7 @@ import (
"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) {
@@ -101,6 +102,57 @@ func TestDataResetClearsDictionary(t *testing.T) {
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 {