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 a8f60003 feat(compute): add list_element (#1138)
a8f60003 is described below
commit a8f600039d59a2de7e7232bef1216896d3c970b2
Author: Minh Vu <[email protected]>
AuthorDate: Mon Aug 31 18:25:11 2026 +0200
feat(compute): add list_element (#1138)
### Rationale for this change
Arrow-Go supports the list, large-list, list-view, and fixed-size-list
array types, but callers currently need to read offsets and child arrays
themselves to select one element from every row. A list_element kernel
provides the common nested-data operation directly through compute.
### What changes are included in this PR?
- Add list_element for List, LargeList, ListView, LargeListView, and
FixedSizeList.
- Accept signed or unsigned integer scalar indexes. A one-element
integer array is accepted for matching one-row input.
- Return an array of the list element type while preserving parent nulls
and child nulls.
- Return errors for null or negative indexes, empty lists, and indexes
outside the length of a valid list.
- Use the existing take kernels for primitive, binary, and fixed-width
child values to avoid one array allocation per input row. Keep the
generic concatenate path for nested child values.
- Handle list-view offsets and sizes without requiring the input to be
materialized first.
- Reject StringView, BinaryView, and dictionary-backed extension
children, including nested children, before execution.
- Fix null-array construction and run-end encoded span metadata needed
by the nested fallback.
### Are these changes tested?
- go test ./arrow/compute/... -count=1
- Added coverage for sliced List and FixedSizeList inputs,
non-contiguous ListView offsets, unsigned indexes, string and
nested-list children, parent and child nulls, and invalid indexes.
- Added regression coverage for dictionary-backed extension rejection
and null run-end encoded values with nested dictionaries.
- Added BenchmarkListElement cases for 1K, 100K, and 1M rows with
allocation reporting.
### Are there any user-facing changes?
Yes. This adds the public `compute.ListElement` function. It also fixes
`array.MakeArrayOfNull` for list views, unions, and run-end encoded
arrays, keeps run-end encoded `ArraySpan` buffer metadata consistent,
and preserves storage validity when extracting extension scalars.
---
arrow/array/util.go | 87 +-
arrow/array/util_test.go | 129 ++
arrow/compute/exec/span.go | 8 +-
arrow/compute/exec/span_test.go | 56 +
arrow/compute/internal/kernels/scalar_nested.go | 602 ++++++
arrow/compute/registry.go | 1 +
arrow/compute/scalar_nested.go | 263 +++
arrow/compute/scalar_nested_test.go | 2489 +++++++++++++++++++++++
arrow/scalar/scalar.go | 4 +-
9 files changed, 3604 insertions(+), 35 deletions(-)
diff --git a/arrow/array/util.go b/arrow/array/util.go
index afd0f39b..ca692911 100644
--- a/arrow/array/util.go
+++ b/arrow/array/util.go
@@ -463,12 +463,18 @@ func getMaxBufferLen(dt arrow.DataType, length int) int {
bufferLen = maxOf(getMaxBufferLen(f.Type, 1))
}
return bufferLen
+ case *arrow.ListViewType:
+ return maxOf(dt.OffsetTypeTraits().BytesRequired(length))
+ case *arrow.LargeListViewType:
+ return maxOf(dt.OffsetTypeTraits().BytesRequired(length))
case arrow.OffsetsDataType:
return maxOf(dt.OffsetTypeTraits().BytesRequired(length + 1))
case arrow.BinaryViewDataType:
return maxOf(arrow.ViewHeaderSizeBytes * length)
case *arrow.FixedSizeListType:
return maxOf(getMaxBufferLen(dt.Elem(), int(dt.Len())*length))
+ case *arrow.RunEndEncodedType:
+ return bufferLen
case arrow.ExtensionType:
return maxOf(getMaxBufferLen(dt.StorageType(), length))
default:
@@ -497,7 +503,11 @@ func (n *nullArrayFactory) create() *Data {
childData []arrow.ArrayData
dictData arrow.ArrayData
)
- defer bufs[0].Release()
+ defer func() {
+ if bufs[0] != nil {
+ bufs[0].Release()
+ }
+ }()
if ex, ok := dt.(arrow.ExtensionType); ok {
dt = ex.StorageType()
@@ -520,41 +530,12 @@ func (n *nullArrayFactory) create() *Data {
bufs = append(bufs, n.buf)
case arrow.BinaryDataType:
bufs = append(bufs, n.buf, n.buf)
- case arrow.OffsetsDataType:
- bufs = append(bufs, n.buf)
- childData[0] = n.createChild(dt, 0, 0)
- defer childData[0].Release()
- case *arrow.FixedSizeListType:
- childData[0] = n.createChild(dt, 0, n.len*int(dt.Len()))
- defer childData[0].Release()
- case *arrow.StructType:
- for i := range dt.Fields() {
- childData[i] = n.createChild(dt, i, n.len)
- defer childData[i].Release()
- }
- case *arrow.RunEndEncodedType:
- bldr := NewBuilder(n.mem, dt.RunEnds())
- defer bldr.Release()
-
- switch b := bldr.(type) {
- case *Int16Builder:
- b.Append(int16(n.len))
- case *Int32Builder:
- b.Append(int32(n.len))
- case *Int64Builder:
- b.Append(int64(n.len))
- }
-
- childData[0] = bldr.newData()
- defer childData[0].Release()
- childData[1] = n.createChild(dt.Encoded(), 1, 1)
- defer childData[1].Release()
case arrow.UnionType:
bufs[0].Release()
bufs[0] = nil
bufs = append(bufs, n.buf)
// buffer is zeroed, but 0 may not be a valid type code
- if dt.TypeCodes()[0] != 0 {
+ if n.len > 0 && dt.TypeCodes()[0] != 0 {
bufs[1] = memory.NewResizableBuffer(n.mem)
bufs[1].Resize(n.len)
defer bufs[1].Release()
@@ -573,9 +554,51 @@ func (n *nullArrayFactory) create() *Data {
childData[i] = n.createChild(dt, i, childLen)
defer childData[i].Release()
}
+ case *arrow.ListViewType:
+ bufs = append(bufs, n.buf, n.buf)
+ childData[0] = n.createChild(dt, 0, 0)
+ defer childData[0].Release()
+ case *arrow.LargeListViewType:
+ bufs = append(bufs, n.buf, n.buf)
+ childData[0] = n.createChild(dt, 0, 0)
+ defer childData[0].Release()
+ case arrow.OffsetsDataType:
+ bufs = append(bufs, n.buf)
+ childData[0] = n.createChild(dt, 0, 0)
+ defer childData[0].Release()
+ case *arrow.FixedSizeListType:
+ childData[0] = n.createChild(dt, 0, n.len*int(dt.Len()))
+ defer childData[0].Release()
+ case *arrow.StructType:
+ for i := range dt.Fields() {
+ childData[i] = n.createChild(dt, i, n.len)
+ defer childData[i].Release()
+ }
+ case *arrow.RunEndEncodedType:
+ // Build the run ends separately from encoded types without
builders.
+ bldr := NewRunEndEncodedBuilder(n.mem, dt.RunEnds(), arrow.Null)
+ defer bldr.Release()
+
+ if n.len > 0 {
+ bldr.AppendNull()
+ bldr.ContinueRun(uint64(n.len - 1))
+ }
+
+ arr := bldr.NewArray()
+ defer arr.Release()
+ values := MakeArrayOfNull(n.mem, dt.Encoded(),
arr.Data().Children()[1].Len())
+ defer values.Release()
+ childData[0] = arr.Data().Children()[0]
+ childData[1] = values.Data()
+ bufs[0].Release()
+ bufs[0] = nil
}
- out := NewData(n.dt, n.len, bufs, childData, n.len, 0)
+ nulls := n.len
+ if dt.ID() == arrow.RUN_END_ENCODED || arrow.IsUnion(dt.ID()) {
+ nulls = 0
+ }
+ out := NewData(n.dt, n.len, bufs, childData, nulls, 0)
if dictData != nil {
out.SetDictionary(dictData)
}
diff --git a/arrow/array/util_test.go b/arrow/array/util_test.go
index 6870cf8b..e8e496c8 100644
--- a/arrow/array/util_test.go
+++ b/arrow/array/util_test.go
@@ -63,6 +63,135 @@ func TestGetDictArrayDataNullInSuffix(t *testing.T) {
assert.True(t, dict.IsNull(1))
}
+func TestMakeArrayOfNullListViews(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ for _, typ := range []arrow.DataType{
+ arrow.ListViewOf(arrow.PrimitiveTypes.Int32),
+ arrow.LargeListViewOf(arrow.PrimitiveTypes.Int32),
+ } {
+ t.Run(typ.String(), func(t *testing.T) {
+ for _, length := range []int{0, 1} {
+ arr := array.MakeArrayOfNull(mem, typ, length)
+ require.Equal(t, length, arr.Len())
+ require.Equal(t, length, arr.NullN())
+ require.NoError(t, array.ValidateFull(arr))
+ arr.Release()
+ }
+ })
+ }
+}
+
+func TestMakeArrayOfNullRunEndEncoded(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ typ := arrow.RunEndEncodedOf(arrow.PrimitiveTypes.Int32,
arrow.PrimitiveTypes.Int32)
+ for _, length := range []int{0, 1, 4} {
+ t.Run(fmt.Sprintf("length-%d", length), func(t *testing.T) {
+ arr := array.MakeArrayOfNull(mem, typ, length)
+ defer arr.Release()
+
+ require.Equal(t, length, arr.Len())
+ require.Equal(t, 0, arr.NullN())
+ require.NoError(t, array.ValidateFull(arr))
+
+ rle := arr.(*array.RunEndEncoded)
+ if length == 0 {
+ require.Zero(t, rle.RunEndsArr().Len())
+ require.Zero(t, rle.Values().Len())
+ } else {
+ require.Equal(t, 1, rle.RunEndsArr().Len())
+ require.Equal(t, 1, rle.Values().Len())
+ require.True(t, rle.Values().IsNull(0))
+ }
+ })
+ }
+}
+
+func TestMakeArrayOfNullRunEndEncodedNestedDictionary(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ dictType := &arrow.DictionaryType{
+ IndexType: arrow.PrimitiveTypes.Int8,
+ ValueType: arrow.ListOf(arrow.PrimitiveTypes.Int32),
+ }
+ for _, runEndType := range []arrow.DataType{
+ arrow.PrimitiveTypes.Int16, arrow.PrimitiveTypes.Int32,
arrow.PrimitiveTypes.Int64,
+ } {
+ t.Run(runEndType.String(), func(t *testing.T) {
+ typ := arrow.RunEndEncodedOf(runEndType, dictType)
+ for _, length := range []int{0, 1, 4} {
+ t.Run(fmt.Sprintf("length-%d", length), func(t
*testing.T) {
+ arr := array.MakeArrayOfNull(mem, typ,
length)
+ defer arr.Release()
+ require.Equal(t, length, arr.Len())
+ require.NoError(t,
array.ValidateFull(arr))
+ encoded := arr.(*array.RunEndEncoded)
+ values :=
encoded.Values().(*array.Dictionary)
+ require.Empty(t,
values.Dictionary().(*array.List).ListValues().(*array.Int32).Int32Values())
+ if length == 0 {
+ require.Zero(t,
encoded.RunEndsArr().Len())
+ require.Zero(t, values.Len())
+ } else {
+ require.Equal(t, 1,
encoded.RunEndsArr().Len())
+ require.Equal(t, 1,
values.Len())
+ require.True(t,
values.IsNull(0))
+ require.Equal(t,
fmt.Sprint(length), encoded.RunEndsArr().ValueStr(0))
+ }
+ })
+ }
+ })
+ }
+}
+
+func TestMakeArrayOfNullEmptyUnions(t *testing.T) {
+ for _, typ := range []arrow.DataType{arrow.SparseUnionOf(nil, nil),
arrow.DenseUnionOf(nil, nil)} {
+ t.Run(typ.ID().String(), func(t *testing.T) {
+ mem :=
memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+ values := array.MakeArrayOfNull(mem, typ, 0)
+ defer values.Release()
+ require.Zero(t, values.Len())
+ require.NoError(t, array.ValidateFull(values))
+ })
+ }
+}
+
+func TestMakeArrayOfNullUnions(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ types := []arrow.DataType{
+ arrow.SparseUnionOf(
+ []arrow.Field{
+ {Name: "number", Type:
arrow.PrimitiveTypes.Int32, Nullable: true},
+ {Name: "text", Type: arrow.BinaryTypes.String,
Nullable: true},
+ },
+ []arrow.UnionTypeCode{5, 42},
+ ),
+ arrow.DenseUnionOf(
+ []arrow.Field{
+ {Name: "number", Type:
arrow.PrimitiveTypes.Int32, Nullable: true},
+ {Name: "text", Type: arrow.BinaryTypes.String,
Nullable: true},
+ },
+ []arrow.UnionTypeCode{5, 42},
+ ),
+ }
+
+ for _, typ := range types {
+ t.Run(typ.String(), func(t *testing.T) {
+ arr := array.MakeArrayOfNull(mem, typ, 1)
+ defer arr.Release()
+
+ require.Equal(t, 0, arr.NullN())
+ require.NoError(t, array.ValidateFull(arr))
+ })
+ }
+}
+
var typemap = map[arrow.DataType]reflect.Type{
arrow.PrimitiveTypes.Int8: reflect.TypeOf(int8(0)),
arrow.PrimitiveTypes.Uint8: reflect.TypeOf(uint8(0)),
diff --git a/arrow/compute/exec/span.go b/arrow/compute/exec/span.go
index c989cfe7..49350eeb 100644
--- a/arrow/compute/exec/span.go
+++ b/arrow/compute/exec/span.go
@@ -183,6 +183,9 @@ func (a *ArraySpan) MakeData() arrow.ArrayData {
} else if dt.ID() == arrow.DENSE_UNION || dt.ID() == arrow.SPARSE_UNION
{
bufs[0] = nil
nulls = 0
+ } else if dt.ID() == arrow.RUN_END_ENCODED {
+ bufs[0] = nil
+ nulls = 0
}
if len(a.Children) > 0 {
@@ -578,10 +581,11 @@ type ExecSpan struct {
func getNumBuffers(dt arrow.DataType) int {
switch dt.ID() {
case arrow.RUN_END_ENCODED:
- return 0
+ return 1
case arrow.NULL, arrow.STRUCT, arrow.FIXED_SIZE_LIST:
return 1
- case arrow.BINARY, arrow.LARGE_BINARY, arrow.STRING,
arrow.LARGE_STRING, arrow.DENSE_UNION:
+ case arrow.BINARY, arrow.LARGE_BINARY, arrow.STRING, arrow.LARGE_STRING,
+ arrow.DENSE_UNION:
return 3
case arrow.BINARY_VIEW, arrow.STRING_VIEW:
// bitmap + view-header buffer + a single overflow data buffer.
diff --git a/arrow/compute/exec/span_test.go b/arrow/compute/exec/span_test.go
index feff91e3..90d4c93e 100644
--- a/arrow/compute/exec/span_test.go
+++ b/arrow/compute/exec/span_test.go
@@ -34,6 +34,7 @@ import (
"github.com/apache/arrow-go/v18/arrow/scalar"
"github.com/apache/arrow-go/v18/internal/types"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestBufferSpan_SetBuffer(t *testing.T) {
@@ -205,6 +206,7 @@ func TestArraySpan_NumBuffers(t *testing.T) {
{"null", fields{Type: arrow.Null}, 1},
{"struct", fields{Type: arrow.StructOf()}, 1},
{"fixed size list", fields{Type: arrow.FixedSizeListOf(4,
arrow.PrimitiveTypes.Int32)}, 1},
+ {"run end encoded", fields{Type:
arrow.RunEndEncodedOf(arrow.PrimitiveTypes.Int32, arrow.PrimitiveTypes.Int32)},
1},
{"binary", fields{Type: arrow.BinaryTypes.Binary}, 3},
{"large binary", fields{Type: arrow.BinaryTypes.LargeBinary},
3},
{"string", fields{Type: arrow.BinaryTypes.String}, 3},
@@ -232,6 +234,60 @@ func TestArraySpan_NumBuffers(t *testing.T) {
}
}
+func TestArraySpanFillZeroLengthRunEndEncoded(t *testing.T) {
+ typ := arrow.RunEndEncodedOf(arrow.PrimitiveTypes.Int32,
arrow.PrimitiveTypes.Int32)
+ var span exec.ArraySpan
+ exec.FillZeroLength(typ, &span)
+
+ actual := span.MakeArray()
+ defer actual.Release()
+ require.NoError(t, array.ValidateFull(actual))
+}
+
+func TestArraySpan_MakeArrayPreservesListViewBuffers(t *testing.T) {
+ tests := []struct {
+ name string
+ build func(memory.Allocator) arrow.Array
+ }{
+ {"list view", func(mem memory.Allocator) arrow.Array {
+ builder := array.NewListViewBuilder(mem,
arrow.PrimitiveTypes.Int32)
+ values := builder.ValueBuilder().(*array.Int32Builder)
+ values.AppendValues([]int32{10, 11, 12}, nil)
+ builder.AppendDimensions(1, 2)
+ result := builder.NewArray()
+ builder.Release()
+ return result
+ }},
+ {"large list view", func(mem memory.Allocator) arrow.Array {
+ builder := array.NewLargeListViewBuilder(mem,
arrow.PrimitiveTypes.Int32)
+ values := builder.ValueBuilder().(*array.Int32Builder)
+ values.AppendValues([]int32{10, 11, 12}, nil)
+ builder.AppendDimensions(1, 2)
+ result := builder.NewArray()
+ builder.Release()
+ return result
+ }},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ mem :=
memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ input := tt.build(mem)
+ defer input.Release()
+
+ var span exec.ArraySpan
+ span.SetMembers(input.Data())
+ actual := span.MakeArray()
+ defer actual.Release()
+
+ assert.NoError(t, array.ValidateFull(actual))
+ assert.True(t, array.Equal(input, actual), "expected:
%s\ngot: %s", input, actual)
+ })
+ }
+}
+
func TestArraySpan_MakeData(t *testing.T) {
type fields struct {
Type arrow.DataType
diff --git a/arrow/compute/internal/kernels/scalar_nested.go
b/arrow/compute/internal/kernels/scalar_nested.go
new file mode 100644
index 00000000..dbf09527
--- /dev/null
+++ b/arrow/compute/internal/kernels/scalar_nested.go
@@ -0,0 +1,602 @@
+// 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.
+
+//go:build go1.18
+
+package kernels
+
+import (
+ "fmt"
+ "math"
+
+ "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/compute/exec"
+ "github.com/apache/arrow-go/v18/arrow/memory"
+ "github.com/apache/arrow-go/v18/arrow/scalar"
+)
+
+func listElementOutputType(_ *exec.KernelCtx, inputTypes []arrow.DataType)
(arrow.DataType, error) {
+ listType, ok := inputTypes[0].(arrow.ListLikeType)
+ if !ok {
+ return nil, fmt.Errorf("%w: list_element requires a list-like
input", arrow.ErrType)
+ }
+ return listType.Elem(), nil
+}
+
+func getListElementIndex(value *exec.ExecValue) (uint64, error) {
+ if value.IsScalar() {
+ return listElementScalarIndex(value.Scalar)
+ }
+
+ if value.Array.Len == 0 {
+ return 0, fmt.Errorf("%w: list_element index array is empty",
arrow.ErrInvalid)
+ }
+ if value.Array.Len > 1 {
+ return 0, fmt.Errorf("%w: list_element does not support arrays
of list indices", arrow.ErrNotImplemented)
+ }
+ if value.Array.UpdateNullCount() != 0 {
+ return 0, fmt.Errorf("%w: list_element index must not contain
nulls", arrow.ErrInvalid)
+ }
+
+ switch value.Array.Type.ID() {
+ case arrow.INT8:
+ return unsignedIndex(exec.GetSpanValues[int8](&value.Array,
1)[0])
+ case arrow.INT16:
+ return unsignedIndex(exec.GetSpanValues[int16](&value.Array,
1)[0])
+ case arrow.INT32:
+ return unsignedIndex(exec.GetSpanValues[int32](&value.Array,
1)[0])
+ case arrow.INT64:
+ return unsignedIndex(exec.GetSpanValues[int64](&value.Array,
1)[0])
+ case arrow.UINT8:
+ return uint64(exec.GetSpanValues[uint8](&value.Array, 1)[0]),
nil
+ case arrow.UINT16:
+ return uint64(exec.GetSpanValues[uint16](&value.Array, 1)[0]),
nil
+ case arrow.UINT32:
+ return uint64(exec.GetSpanValues[uint32](&value.Array, 1)[0]),
nil
+ case arrow.UINT64:
+ return exec.GetSpanValues[uint64](&value.Array, 1)[0], nil
+ default:
+ return 0, fmt.Errorf("%w: invalid list_element index type %s",
arrow.ErrType, value.Array.Type)
+ }
+}
+
+func ValidateListElementScalarIndex(value scalar.Scalar) error {
+ _, err := listElementScalarIndex(value)
+ return err
+}
+
+func ListElementScalarIndex(value scalar.Scalar) (uint64, error) {
+ return listElementScalarIndex(value)
+}
+
+func listElementScalarIndex(value scalar.Scalar) (uint64, error) {
+ if !value.IsValid() {
+ return 0, fmt.Errorf("%w: list_element index must not be null",
arrow.ErrInvalid)
+ }
+ return scalarIndex(value)
+}
+
+func scalarIndex(value scalar.Scalar) (uint64, error) {
+ switch value := value.(type) {
+ case *scalar.Int8:
+ return unsignedIndex(value.Value)
+ case *scalar.Int16:
+ return unsignedIndex(value.Value)
+ case *scalar.Int32:
+ return unsignedIndex(value.Value)
+ case *scalar.Int64:
+ return unsignedIndex(value.Value)
+ case *scalar.Uint8:
+ return uint64(value.Value), nil
+ case *scalar.Uint16:
+ return uint64(value.Value), nil
+ case *scalar.Uint32:
+ return uint64(value.Value), nil
+ case *scalar.Uint64:
+ return value.Value, nil
+ default:
+ return 0, fmt.Errorf("%w: invalid list_element index type %s",
arrow.ErrType, value.DataType())
+ }
+}
+
+func unsignedIndex[T arrow.IntType](value T) (uint64, error) {
+ if value < 0 {
+ return 0, fmt.Errorf("%w: list_element index %d is out of
bounds: should be greater than or equal to 0", arrow.ErrInvalid, value)
+ }
+ return uint64(value), nil
+}
+
+func listElementValueOffsets(list *exec.ArraySpan, i int64) (int64, int64,
error) {
+ check := func(start, end int64) (int64, int64, error) {
+ if start < 0 || end < start || end > list.Children[0].Len {
+ return 0, 0, fmt.Errorf("%w: list_element input has
invalid value offsets", arrow.ErrInvalid)
+ }
+ return start, end, nil
+ }
+
+ switch list.Type.ID() {
+ case arrow.LIST:
+ offsets := exec.GetSpanOffsets[int32](list, 1)
+ return check(int64(offsets[i]), int64(offsets[i+1]))
+ case arrow.LARGE_LIST:
+ offsets := exec.GetSpanOffsets[int64](list, 1)
+ return check(offsets[i], offsets[i+1])
+ case arrow.LIST_VIEW:
+ offsets := exec.GetSpanValues[int32](list, 1)
+ sizes := exec.GetSpanValues[int32](list, 2)
+ start := int64(offsets[i])
+ size := int64(sizes[i])
+ if size < 0 {
+ return 0, 0, fmt.Errorf("%w: list_element input has
invalid value offsets", arrow.ErrInvalid)
+ }
+ return check(start, start+size)
+ case arrow.LARGE_LIST_VIEW:
+ offsets := exec.GetSpanValues[int64](list, 1)
+ sizes := exec.GetSpanValues[int64](list, 2)
+ start := offsets[i]
+ size := sizes[i]
+ if start < 0 || size < 0 || size > math.MaxInt64-start {
+ return 0, 0, fmt.Errorf("%w: list_element input has
invalid value offsets", arrow.ErrInvalid)
+ }
+ return check(start, start+size)
+ case arrow.FIXED_SIZE_LIST:
+ size := int64(list.Type.(*arrow.FixedSizeListType).Len())
+ if list.Offset < 0 || i < 0 || i > math.MaxInt64-list.Offset {
+ return 0, 0, fmt.Errorf("%w: list_element input has
invalid value offsets", arrow.ErrInvalid)
+ }
+ position := list.Offset + i
+ if size < 0 || (size > 0 && position > math.MaxInt64/size) {
+ return 0, 0, fmt.Errorf("%w: list_element input has
invalid value offsets", arrow.ErrInvalid)
+ }
+ start := position * size
+ if size > math.MaxInt64-start {
+ return 0, 0, fmt.Errorf("%w: list_element input has
invalid value offsets", arrow.ErrInvalid)
+ }
+ return check(start, start+size)
+ default:
+ return 0, 0, fmt.Errorf("%w: unsupported list_element input
type %s", arrow.ErrType, list.Type)
+ }
+}
+
+func listElementExec(ctx *exec.KernelCtx, batch *exec.ExecSpan, out
*exec.ExecResult) error {
+ var listSpan exec.ArraySpan
+ if batch.Values[0].IsScalar() {
+ listSpan.FillFromScalar(batch.Values[0].Scalar)
+ } else {
+ listSpan = batch.Values[0].Array
+ }
+ list := &listSpan
+ if len(list.Children) == 0 {
+ return fmt.Errorf("%w: list_element input has no values child",
arrow.ErrInvalid)
+ }
+
+ index, err := getListElementIndex(&batch.Values[1])
+ if err != nil {
+ return err
+ }
+
+ elemType := list.Type.(arrow.ListLikeType).Elem()
+ if !ListElementOutputTypeSupported(elemType) {
+ return fmt.Errorf("%w: list_element output type %s is not
supported", arrow.ErrNotImplemented, elemType)
+ }
+ if list.Len == 0 {
+ values := list.Children[0].MakeArray()
+ defer values.Release()
+ empty := array.NewSlice(values, 0, 0)
+ defer empty.Release()
+ out.TakeOwnership(empty.Data())
+ return nil
+ }
+ if !listElementTakeSupported(elemType) {
+ return listElementConcat(ctx, list, index, elemType, out)
+ }
+
+ indexBuilder := array.NewInt64Builder(exec.GetAllocator(ctx.Ctx))
+ defer indexBuilder.Release()
+ indexBuilder.Reserve(int(list.Len))
+ for i := int64(0); i < list.Len; i++ {
+ if len(list.Buffers[0].Buf) != 0 &&
bitutil.BitIsNotSet(list.Buffers[0].Buf, int(list.Offset+i)) {
+ indexBuilder.AppendNull()
+ continue
+ }
+
+ start, end, err := listElementValueOffsets(list, i)
+ if err != nil {
+ return err
+ }
+ if end < start {
+ return fmt.Errorf("%w: list_element input has invalid
value offsets", arrow.ErrInvalid)
+ }
+ length := uint64(end - start)
+ if index >= length {
+ return fmt.Errorf("%w: list_element index %d is out of
bounds: should be in [0, %d)", arrow.ErrInvalid, index, length)
+ }
+ indexBuilder.Append(start + int64(index))
+ }
+
+ indices := indexBuilder.NewArray()
+ defer indices.Release()
+ return listElementTakeOrFallback(ctx, &list.Children[0], indices, out)
+}
+
+func ListElementOutputTypeSupported(typ arrow.DataType) bool {
+ switch typ.ID() {
+ case arrow.BINARY_VIEW, arrow.STRING_VIEW:
+ return false
+ case arrow.SPARSE_UNION, arrow.DENSE_UNION:
+ if typ.(arrow.UnionType).NumFields() == 0 {
+ return false
+ }
+ case arrow.EXTENSION:
+ storageType := typ.(arrow.ExtensionType).StorageType()
+ // ArraySpan does not preserve a dictionary stored under an
extension.
+ return storageType.ID() != arrow.DICTIONARY &&
ListElementOutputTypeSupported(storageType)
+ case arrow.DICTIONARY:
+ return
ListElementOutputTypeSupported(typ.(*arrow.DictionaryType).ValueType)
+ }
+
+ nested, ok := typ.(arrow.NestedType)
+ if !ok {
+ return true
+ }
+ for _, field := range nested.Fields() {
+ if !ListElementOutputTypeSupported(field.Type) {
+ return false
+ }
+ }
+ return true
+}
+
+func listElementTakeSupported(typ arrow.DataType) bool {
+ id := typ.ID()
+ if id == arrow.NULL || arrow.IsBinaryLike(id) ||
arrow.IsLargeBinaryLike(id) ||
+ arrow.IsFixedSizeBinary(id) || id == arrow.SPARSE_UNION || id
== arrow.DENSE_UNION ||
+ id == arrow.EXTENSION {
+ return true
+ }
+ if !arrow.IsPrimitive(id) {
+ return false
+ }
+
+ // PrimitiveTake has specialized implementations for these widths only.
+ // In particular, INTERVAL_MONTH_DAY_NANO is a primitive 128-bit type
and
+ // must use the generic concatenation fallback below.
+ fixed, ok := typ.(arrow.FixedWidthDataType)
+ if !ok {
+ return false
+ }
+ switch fixed.BitWidth() {
+ case 1, 8, 16, 32, 64:
+ return true
+ default:
+ return false
+ }
+}
+
+func listElementConcat(ctx *exec.KernelCtx, list *exec.ArraySpan, index
uint64, elemType arrow.DataType, out *exec.ExecResult) error {
+ values := list.Children[0].MakeArray()
+ defer values.Release()
+ pieces := make([]arrow.Array, 0, int(list.Len))
+ defer func() {
+ for _, piece := range pieces {
+ piece.Release()
+ }
+ }()
+
+ for i := int64(0); i < list.Len; i++ {
+ if len(list.Buffers[0].Buf) != 0 &&
bitutil.BitIsNotSet(list.Buffers[0].Buf, int(list.Offset+i)) {
+ pieces = append(pieces, listElementMakeNullLike(ctx,
values, elemType))
+ continue
+ }
+
+ start, end, err := listElementValueOffsets(list, i)
+ if err != nil {
+ return err
+ }
+ if end < start {
+ return fmt.Errorf("%w: list_element input has invalid
value offsets", arrow.ErrInvalid)
+ }
+ length := uint64(end - start)
+ if index >= length {
+ return fmt.Errorf("%w: list_element index %d is out of
bounds: should be in [0, %d)", arrow.ErrInvalid, index, length)
+ }
+ selected := start + int64(index)
+ pieces = append(pieces, array.NewSlice(values, selected,
selected+1))
+ }
+
+ result, err := array.Concatenate(pieces, exec.GetAllocator(ctx.Ctx))
+ if err != nil {
+ return err
+ }
+ defer result.Release()
+ out.TakeOwnership(result.Data())
+ return nil
+}
+
+func listElementMakeNullLike(ctx *exec.KernelCtx, values arrow.Array, elemType
arrow.DataType) arrow.Array {
+ mem := exec.GetAllocator(ctx.Ctx)
+ if extType, ok := elemType.(arrow.ExtensionType); ok {
+ storageValues := values
+ if extValues, ok := values.(array.ExtensionArray); ok {
+ storageValues = extValues.Storage()
+ }
+ storage := listElementMakeNullLike(ctx, storageValues,
extType.StorageType())
+ result := array.NewExtensionArrayWithStorage(extType, storage)
+ storage.Release()
+ return result
+ }
+ if elemType.ID() == arrow.RUN_END_ENCODED {
+ runEndType := elemType.(*arrow.RunEndEncodedType)
+ // Build only the run ends; nested encoded values may not have
a builder.
+ builder := array.NewRunEndEncodedBuilder(mem,
runEndType.RunEnds(), arrow.Null)
+ defer builder.Release()
+ builder.AppendNull()
+ runEnds := builder.NewRunEndEncodedArray()
+ defer runEnds.Release()
+ nulls := listElementMakeNullLike(ctx,
values.(*array.RunEndEncoded).Values(), runEndType.Encoded())
+ defer nulls.Release()
+ return array.NewRunEndEncodedArrayWithType(runEndType,
runEnds.RunEndsArr(), nulls, 1, 0)
+ }
+ if values.Len() == 0 || len(values.Data().Buffers()) == 0 ||
+ elemType.ID() == arrow.NULL || arrow.IsUnion(elemType.ID()) {
+ return array.MakeArrayOfNull(mem, elemType, 1)
+ }
+
+ source := array.NewSlice(values, 0, 1)
+ defer source.Release()
+
+ sourceData := source.Data()
+ validity := memory.NewResizableBuffer(mem)
+ validity.Resize(int(bitutil.BytesForBits(int64(sourceData.Offset() +
1))))
+ memory.Set(validity.Bytes(), 0)
+ defer validity.Release()
+
+ buffers := append([]*memory.Buffer(nil), sourceData.Buffers()...)
+ buffers[0] = validity
+ data := array.NewData(sourceData.DataType(), 1, buffers,
sourceData.Children(), 1, sourceData.Offset())
+ if dictionary := sourceData.Dictionary(); dictionary != nil {
+ data.SetDictionary(dictionary)
+ }
+ defer data.Release()
+ return array.MakeFromData(data)
+}
+
+func listElementTakeFallback(ctx *exec.KernelCtx, values *exec.ArraySpan,
indices arrow.Array, out *exec.ExecResult) error {
+ elemType := values.Type
+ valuesArray := values.MakeArray()
+ defer valuesArray.Release()
+ if indices.Len() == 0 {
+ empty := array.NewSlice(valuesArray, 0, 0)
+ defer empty.Release()
+ out.TakeOwnership(empty.Data())
+ return nil
+ }
+
+ pieces := make([]arrow.Array, 0, indices.Len())
+ defer func() {
+ for _, piece := range pieces {
+ piece.Release()
+ }
+ }()
+
+ for i := 0; i < indices.Len(); i++ {
+ if indices.IsNull(i) {
+ pieces = append(pieces, listElementMakeNullLike(ctx,
valuesArray, elemType))
+ continue
+ }
+ selected, err := listElementTakeIndex(indices, i)
+ if err != nil {
+ return err
+ }
+ pieces = append(pieces, array.NewSlice(valuesArray, selected,
selected+1))
+ }
+
+ result, err := array.Concatenate(pieces, exec.GetAllocator(ctx.Ctx))
+ if err != nil {
+ return err
+ }
+ defer result.Release()
+ out.TakeOwnership(result.Data())
+ return nil
+}
+
+func listElementTakeIndex(indices arrow.Array, i int) (int64, error) {
+ switch indexArray := indices.(type) {
+ case *array.Int32:
+ return int64(indexArray.Value(i)), nil
+ case *array.Int64:
+ return indexArray.Value(i), nil
+ default:
+ return 0, fmt.Errorf("%w: list_element fallback received
unsupported index type %s", arrow.ErrType, indices.DataType())
+ }
+}
+
+func listElementTakeOrFallback(ctx *exec.KernelCtx, values *exec.ArraySpan,
indices arrow.Array, out *exec.ExecResult) error {
+ if indices.Len() == 0 {
+ return listElementTakeFallback(ctx, values, indices, out)
+ }
+ if handled, err := listElementTake(ctx, values, indices, out); handled {
+ return err
+ }
+ return listElementTakeFallback(ctx, values, indices, out)
+}
+
+func listElementDenseUnionTake(ctx *exec.KernelCtx, values *exec.ArraySpan,
indices arrow.Array, out *exec.ExecResult) error {
+ var indexSpan exec.ArraySpan
+ indexSpan.SetMembers(indices.Data())
+ batch := &exec.ExecSpan{
+ Len: int64(indices.Len()),
+ Values: []exec.ExecValue{
+ {Array: *values},
+ {Array: indexSpan},
+ },
+ }
+ takeCtx := *ctx
+ takeCtx.State = TakeOptions{BoundsCheck: false}
+ if err := TakeExec(DenseUnionImpl)(&takeCtx, batch, out); err != nil {
+ return err
+ }
+
+ for i := range out.Children {
+ childIndices := out.Children[i].MakeArray()
+ out.Children[i] = exec.ArraySpan{}
+ childOut := &exec.ExecResult{Type: values.Children[i].Type}
+ err := listElementTakeOrFallback(ctx, &values.Children[i],
childIndices, childOut)
+ childIndices.Release()
+ if err != nil {
+ childOut.Release()
+ return err
+ }
+
+ childData := childOut.MakeData()
+ out.Children[i].TakeOwnership(childData)
+ childData.Release()
+ }
+ return nil
+}
+
+func listElementSparseUnionTake(ctx *exec.KernelCtx, values *exec.ArraySpan,
indices arrow.Array, out *exec.ExecResult) error {
+ valuesArray := values.MakeArray()
+ defer valuesArray.Release()
+
+ union := valuesArray.(*array.SparseUnion)
+ unionType := values.Type.(*arrow.SparseUnionType)
+ typeIDBuilder := array.NewInt8Builder(exec.GetAllocator(ctx.Ctx))
+ defer typeIDBuilder.Release()
+ typeIDBuilder.Reserve(indices.Len())
+
+ for i := 0; i < indices.Len(); i++ {
+ if indices.IsNull(i) {
+ typeIDBuilder.Append(unionType.TypeCodes()[0])
+ } else {
+ selected, err := listElementTakeIndex(indices, i)
+ if err != nil {
+ return err
+ }
+ typeIDBuilder.Append(union.TypeCode(int(selected)))
+ }
+ }
+
+ typeIDs := typeIDBuilder.NewArray()
+ defer typeIDs.Release()
+
+ children := make([]arrow.Array, len(values.Children))
+ defer func() {
+ for _, child := range children {
+ if child != nil {
+ child.Release()
+ }
+ }
+ }()
+ for i := range values.Children {
+ field := union.Field(i)
+ var childSpan exec.ArraySpan
+ childSpan.SetMembers(field.Data())
+ childOut := &exec.ExecResult{Type: childSpan.Type}
+ if err := listElementTakeOrFallback(ctx, &childSpan, indices,
childOut); err != nil {
+ childOut.Release()
+ return err
+ }
+ children[i] = childOut.MakeArray()
+ }
+
+ childData := make([]arrow.ArrayData, len(children))
+ for i, child := range children {
+ childData[i] = child.Data()
+ }
+ data := array.NewData(unionType, indices.Len(),
+ []*memory.Buffer{nil, typeIDs.Data().Buffers()[1]}, childData,
0, 0)
+ result := array.NewSparseUnionData(data)
+ data.Release()
+ defer result.Release()
+ out.TakeOwnership(result.Data())
+ return nil
+}
+
+func listElementTake(ctx *exec.KernelCtx, values *exec.ArraySpan, indices
arrow.Array, out *exec.ExecResult) (bool, error) {
+ if !listElementTakeSupported(values.Type) {
+ return false, nil
+ }
+
+ var indexSpan exec.ArraySpan
+ indexSpan.SetMembers(indices.Data())
+ batch := &exec.ExecSpan{
+ Len: int64(indices.Len()),
+ Values: []exec.ExecValue{
+ {Array: *values},
+ {Array: indexSpan},
+ },
+ }
+ takeCtx := *ctx
+ takeCtx.State = TakeOptions{BoundsCheck: false}
+
+ switch id := values.Type.ID(); {
+ case id == arrow.NULL:
+ return true, NullTake(&takeCtx, batch, out)
+ case arrow.IsPrimitive(id):
+ return true, PrimitiveTake(&takeCtx, batch, out)
+ case arrow.IsBinaryLike(id):
+ return true, TakeExec(VarBinaryImpl[int32])(&takeCtx, batch,
out)
+ case arrow.IsLargeBinaryLike(id):
+ return true, TakeExec(VarBinaryImpl[int64])(&takeCtx, batch,
out)
+ case arrow.IsFixedSizeBinary(id):
+ return true, TakeExec(FSBImpl)(&takeCtx, batch, out)
+ case id == arrow.SPARSE_UNION:
+ return true, listElementSparseUnionTake(ctx, values, indices,
out)
+ case id == arrow.DENSE_UNION:
+ return true, listElementDenseUnionTake(ctx, values, indices,
out)
+ case id == arrow.EXTENSION:
+ extType := values.Type.(arrow.ExtensionType)
+ storage := *values
+ storage.Type = extType.StorageType()
+ handled, err := listElementTake(ctx, &storage, indices, out)
+ if handled {
+ // The storage take produces the physical buffers and
children. Restore
+ // the logical extension type so ArraySpan.MakeData
reconstructs an
+ // ExtensionArray around them.
+ out.Type = values.Type
+ }
+ return handled, err
+ default:
+ return false, nil
+ }
+}
+
+func GetListElementKernels() []exec.ScalarKernel {
+ kernels := make([]exec.ScalarKernel, 0, 5)
+ for _, listID := range []arrow.Type{
+ arrow.LIST,
+ arrow.LARGE_LIST,
+ arrow.LIST_VIEW,
+ arrow.LARGE_LIST_VIEW,
+ arrow.FIXED_SIZE_LIST,
+ } {
+ kernel := exec.NewScalarKernel(
+ []exec.InputType{
+ exec.NewIDInput(listID),
+ exec.NewMatchedInput(exec.Integer()),
+ },
+ exec.NewComputedOutputType(listElementOutputType),
+ listElementExec,
+ nil)
+ kernel.NullHandling = exec.NullComputedNoPrealloc
+ kernel.MemAlloc = exec.MemNoPrealloc
+ kernels = append(kernels, kernel)
+ }
+ return kernels
+}
diff --git a/arrow/compute/registry.go b/arrow/compute/registry.go
index bea37025..cccf58ef 100644
--- a/arrow/compute/registry.go
+++ b/arrow/compute/registry.go
@@ -49,6 +49,7 @@ func GetFunctionRegistry() FunctionRegistry {
registry = NewRegistry()
RegisterScalarCast(registry)
RegisterVectorSelection(registry)
+ RegisterScalarNested(registry)
RegisterVectorSort(registry)
RegisterScalarBoolean(registry)
RegisterScalarArithmetic(registry)
diff --git a/arrow/compute/scalar_nested.go b/arrow/compute/scalar_nested.go
new file mode 100644
index 00000000..23d1c56d
--- /dev/null
+++ b/arrow/compute/scalar_nested.go
@@ -0,0 +1,263 @@
+// 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.
+
+//go:build go1.18
+
+package compute
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/apache/arrow-go/v18/arrow"
+ "github.com/apache/arrow-go/v18/arrow/array"
+ "github.com/apache/arrow-go/v18/arrow/compute/internal/kernels"
+ "github.com/apache/arrow-go/v18/arrow/scalar"
+)
+
+var listElementDoc = FunctionDoc{
+ Summary: "Compute elements using nested list values and an index",
+ Description: "For each list value, return the element at the requested
index.\n" +
+ "Use an integral scalar for broadcast selection. A one-element
array-like\n" +
+ "index is accepted only for a matching one-element execution
and is not\n" +
+ "broadcast over longer input. Supported inputs are List,
LargeList,\n" +
+ "ListView, LargeListView, and FixedSizeList; Map is not
supported.\n" +
+ "Null lists and null child values produce null results. A null
or negative\n" +
+ "index is invalid, as is an index outside any non-null list's
length\n" +
+ "(including an empty list). StringView, BinaryView, and
dictionary-backed\n" +
+ "extension children are not supported, including when nested
inside\n" +
+ "another child type.",
+ ArgNames: []string{"lists", "index"},
+}
+
+func validateListElementIndex(index Datum) error {
+ switch index.Kind() {
+ case KindScalar:
+ value, ok := index.(*ScalarDatum)
+ if !ok || value.Value == nil {
+ return fmt.Errorf("%w: list_element requires a scalar
index datum", arrow.ErrType)
+ }
+ return kernels.ValidateListElementScalarIndex(value.Value)
+ case KindArray, KindChunked:
+ if index.Len() == 0 {
+ return fmt.Errorf("%w: list_element index array is
empty", arrow.ErrInvalid)
+ }
+ if index.Len() > 1 {
+ return fmt.Errorf("%w: list_element does not support
arrays of list indices", arrow.ErrNotImplemented)
+ }
+ }
+ return nil
+}
+
+func validateListElementOutputType(lists Datum) error {
+ var typ arrow.DataType
+ switch lists.Kind() {
+ case KindScalar:
+ value, ok := lists.(*ScalarDatum)
+ if !ok || value.Value == nil {
+ return nil
+ }
+ typ = value.Value.DataType()
+ case KindArray, KindChunked:
+ value, ok := lists.(ArrayLikeDatum)
+ if !ok {
+ return nil
+ }
+ typ = value.Type()
+ default:
+ return nil
+ }
+
+ listType, ok := typ.(arrow.ListLikeType)
+ if !ok || kernels.ListElementOutputTypeSupported(listType.Elem()) {
+ return nil
+ }
+ return fmt.Errorf("%w: list_element output type %s is not supported",
arrow.ErrNotImplemented, listType.Elem())
+}
+
+type listElementFunction struct {
+ ScalarFunction
+}
+
+func listElementScalarResultSupported(typ arrow.DataType, nullResult bool)
bool {
+ switch typ.ID() {
+ case arrow.BINARY_VIEW, arrow.STRING_VIEW, arrow.LIST_VIEW,
arrow.LARGE_LIST_VIEW,
+ arrow.DECIMAL32, arrow.DECIMAL64:
+ return false
+ case arrow.EXTENSION:
+ return
listElementScalarResultSupported(typ.(arrow.ExtensionType).StorageType(),
nullResult)
+ case arrow.STRUCT:
+ for _, field := range typ.(*arrow.StructType).Fields() {
+ if !listElementScalarResultSupported(field.Type,
nullResult) {
+ return false
+ }
+ }
+ case arrow.SPARSE_UNION:
+ if typ.(arrow.UnionType).NumFields() == 0 {
+ return false
+ }
+ for _, field := range typ.(arrow.UnionType).Fields() {
+ if !listElementScalarResultSupported(field.Type,
nullResult) {
+ return false
+ }
+ }
+ case arrow.DENSE_UNION:
+ if !nullResult {
+ return true
+ }
+ fields := typ.(arrow.UnionType).Fields()
+ return len(fields) != 0 &&
listElementScalarResultSupported(fields[0].Type, true)
+ case arrow.RUN_END_ENCODED:
+ return
listElementScalarResultSupported(typ.(*arrow.RunEndEncodedType).Encoded(),
nullResult)
+ }
+ return true
+}
+
+func listElementScalarArrayValueSupported(values arrow.Array, index int) bool {
+ if values.IsNull(index) {
+ return listElementScalarResultSupported(values.DataType(), true)
+ }
+
+ switch values := values.(type) {
+ case *array.BinaryView, *array.StringView, *array.ListView,
*array.LargeListView,
+ *array.Decimal32, *array.Decimal64:
+ return false
+ case array.ExtensionArray:
+ return listElementScalarArrayValueSupported(values.Storage(),
index)
+ case *array.Struct:
+ for i := 0; i < values.NumField(); i++ {
+ if
!listElementScalarArrayValueSupported(values.Field(i), index) {
+ return false
+ }
+ }
+ case *array.SparseUnion:
+ for i := 0; i < values.NumFields(); i++ {
+ if
!listElementScalarArrayValueSupported(values.Field(i), index) {
+ return false
+ }
+ }
+ case *array.DenseUnion:
+ child := values.Field(values.ChildID(index))
+ if child == nil {
+ return false
+ }
+ offset := values.ValueOffset(index)
+ if offset < 0 || int64(offset) >= int64(child.Len()) {
+ return false
+ }
+ return listElementScalarArrayValueSupported(child, int(offset))
+ case *array.RunEndEncoded:
+ return listElementScalarArrayValueSupported(values.Values(),
values.GetPhysicalIndex(index))
+ }
+ return true
+}
+
+// Validate the index before scalar execution splits arguments into spans. The
+// index contract is defined by the original Datum, not by the length of an
+// execution span.
+func (fn *listElementFunction) Execute(ctx context.Context, opts
FunctionOptions, args ...Datum) (Datum, error) {
+ if err := fn.checkArity(len(args)); err != nil {
+ return nil, err
+ }
+ if err := checkOptions(fn, opts); err != nil {
+ return nil, err
+ }
+
+ if err := validateListElementIndex(args[1]); err != nil {
+ return nil, err
+ }
+ if err := validateListElementOutputType(args[0]); err != nil {
+ return nil, err
+ }
+ if args[0].Kind() == KindScalar && args[1].Kind() == KindScalar {
+ indexDatum, ok := args[1].(*ScalarDatum)
+ if !ok {
+ return nil, fmt.Errorf("%w: list_element requires a
scalar index datum", arrow.ErrType)
+ }
+ listDatum, ok := args[0].(*ScalarDatum)
+ if !ok {
+ return nil, fmt.Errorf("%w: list_element requires a
list-like input", arrow.ErrType)
+ }
+ if listDatum.Value == nil {
+ return nil, fmt.Errorf("%w: list_element requires a
list-like scalar input", arrow.ErrType)
+ }
+
+ listType, ok := listDatum.Type().(arrow.ListLikeType)
+ if !ok {
+ return nil, fmt.Errorf("%w: list_element requires a
list-like input", arrow.ErrType)
+ }
+ listValue, ok := listDatum.Value.(scalar.ListScalar)
+ if !ok {
+ return nil, fmt.Errorf("%w: list_element requires a
list-like scalar input", arrow.ErrType)
+ }
+ if !listElementScalarResultSupported(listType.Elem(),
!listValue.IsValid()) {
+ return nil, fmt.Errorf("%w: list_element scalar output
type %s is not supported", arrow.ErrNotImplemented, listType.Elem())
+ }
+ if !listValue.IsValid() {
+ if _, err := fn.DispatchExact(listDatum.Type(),
indexDatum.Type()); err != nil {
+ return nil, err
+ }
+ if err := context.Cause(ctx); err != nil {
+ return nil, err
+ }
+ // Null scalar construction does not need array
concatenation or
+ // unboxing, which do not support every nested scalar
type.
+ return &ScalarDatum{Value:
scalar.MakeNullScalar(listType.Elem())}, nil
+ }
+ if listValue.GetList() != nil {
+ index, err :=
kernels.ListElementScalarIndex(indexDatum.Value)
+ if err != nil {
+ return nil, err
+ }
+ values := listValue.GetList()
+ if index < uint64(values.Len()) {
+ if
!listElementScalarArrayValueSupported(values, int(index)) {
+ return nil, fmt.Errorf("%w:
list_element scalar output value %s is not supported", arrow.ErrNotImplemented,
values.DataType())
+ }
+ if values.IsNull(int(index)) {
+ if _, err :=
fn.DispatchExact(listDatum.Type(), indexDatum.Type()); err != nil {
+ return nil, err
+ }
+ if err := context.Cause(ctx); err !=
nil {
+ return nil, err
+ }
+ value, err := scalar.GetScalar(values,
int(index))
+ if err != nil {
+ return nil, err
+ }
+ return &ScalarDatum{Value: value}, nil
+ }
+ }
+ }
+ }
+
+ return fn.ScalarFunction.Execute(ctx, opts, args...)
+}
+
+func RegisterScalarNested(reg FunctionRegistry) {
+ fn := &listElementFunction{ScalarFunction:
*NewScalarFunction("list_element", Binary(), listElementDoc)}
+ for _, kernel := range kernels.GetListElementKernels() {
+ if err := fn.AddKernel(kernel); err != nil {
+ panic(err)
+ }
+ }
+
+ reg.AddFunction(fn, false)
+}
+
+func ListElement(ctx context.Context, lists, index Datum) (Datum, error) {
+ return CallFunction(ctx, "list_element", nil, lists, index)
+}
diff --git a/arrow/compute/scalar_nested_test.go
b/arrow/compute/scalar_nested_test.go
new file mode 100644
index 00000000..4030c188
--- /dev/null
+++ b/arrow/compute/scalar_nested_test.go
@@ -0,0 +1,2489 @@
+// 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.
+
+//go:build go1.18
+
+package compute_test
+
+import (
+ "context"
+ "fmt"
+ "reflect"
+ "strings"
+ "testing"
+
+ "github.com/apache/arrow-go/v18/arrow"
+ "github.com/apache/arrow-go/v18/arrow/array"
+ "github.com/apache/arrow-go/v18/arrow/compute"
+ "github.com/apache/arrow-go/v18/arrow/decimal"
+ "github.com/apache/arrow-go/v18/arrow/memory"
+ "github.com/apache/arrow-go/v18/arrow/scalar"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+type denseUnionExtensionArray struct {
+ array.ExtensionArrayBase
+}
+
+func (a *denseUnionExtensionArray) ValueStr(i int) string {
+ if a.IsNull(i) {
+ return array.NullValueStr
+ }
+ return "dense_union"
+}
+
+type denseUnionExtensionType struct {
+ arrow.ExtensionBase
+}
+
+func (denseUnionExtensionType) ArrayType() reflect.Type {
+ return reflect.TypeOf(denseUnionExtensionArray{})
+}
+
+func (denseUnionExtensionType) ExtensionName() string {
+ return "compute-test.dense-union"
+}
+
+func (t *denseUnionExtensionType) ExtensionEquals(other arrow.ExtensionType)
bool {
+ rhs, ok := other.(*denseUnionExtensionType)
+ return ok && arrow.TypeEqual(t.StorageType(), rhs.StorageType())
+}
+
+func (denseUnionExtensionType) Serialize() string { return "" }
+
+func (t *denseUnionExtensionType) Deserialize(storage arrow.DataType, _
string) (arrow.ExtensionType, error) {
+ return &denseUnionExtensionType{ExtensionBase:
arrow.ExtensionBase{Storage: storage}}, nil
+}
+
+type runEndExtensionArray struct {
+ array.ExtensionArrayBase
+}
+
+func (a *runEndExtensionArray) ValueStr(i int) string {
+ return a.Storage().ValueStr(i)
+}
+
+type runEndExtensionType struct {
+ arrow.ExtensionBase
+}
+
+func (runEndExtensionType) ArrayType() reflect.Type {
+ return reflect.TypeOf(runEndExtensionArray{})
+}
+
+func (runEndExtensionType) ExtensionName() string {
+ return "compute-test.run-end"
+}
+
+func (t *runEndExtensionType) ExtensionEquals(other arrow.ExtensionType) bool {
+ rhs, ok := other.(*runEndExtensionType)
+ return ok && arrow.TypeEqual(t.StorageType(), rhs.StorageType())
+}
+
+func (runEndExtensionType) Serialize() string { return "" }
+
+func (t *runEndExtensionType) Deserialize(storage arrow.DataType, _ string)
(arrow.ExtensionType, error) {
+ return &runEndExtensionType{ExtensionBase: arrow.ExtensionBase{Storage:
storage}}, nil
+}
+
+func listElementInput(t *testing.T, mem memory.Allocator, typ arrow.DataType,
values string) arrow.Array {
+ arr, _, err := array.FromJSON(mem, typ, strings.NewReader(values))
+ require.NoError(t, err)
+ return arr
+}
+
+func TestListElement(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ types := []arrow.DataType{
+ arrow.ListOf(arrow.PrimitiveTypes.Int32),
+ arrow.LargeListOf(arrow.PrimitiveTypes.Int32),
+ arrow.ListViewOf(arrow.PrimitiveTypes.Int32),
+ arrow.LargeListViewOf(arrow.PrimitiveTypes.Int32),
+ arrow.FixedSizeListOf(2, arrow.PrimitiveTypes.Int32),
+ }
+ for _, typ := range types {
+ t.Run(typ.String(), func(t *testing.T) {
+ input := listElementInput(t, mem, typ, `[[1, 2], [3,
4], null, [5, 6]]`)
+ defer input.Release()
+ expected := listElementInput(t, mem,
arrow.PrimitiveTypes.Int32, `[2, 4, null, 6]`)
+ defer expected.Release()
+
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ArrayDatum{Value: input.Data()},
+ &compute.ScalarDatum{Value:
scalar.NewInt64Scalar(1)},
+ )
+ require.NoError(t, err)
+ defer result.Release()
+
+ actual := result.(*compute.ArrayDatum).MakeArray()
+ defer actual.Release()
+ assert.True(t, array.Equal(expected, actual),
"expected: %s\ngot: %s", expected, actual)
+ })
+ }
+}
+
+func TestListElementAllIntegerIndexTypes(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ listTypes := []arrow.DataType{
+ arrow.ListOf(arrow.PrimitiveTypes.Int32),
+ arrow.LargeListOf(arrow.PrimitiveTypes.Int32),
+ arrow.ListViewOf(arrow.PrimitiveTypes.Int32),
+ arrow.LargeListViewOf(arrow.PrimitiveTypes.Int32),
+ arrow.FixedSizeListOf(2, arrow.PrimitiveTypes.Int32),
+ }
+ indexes := []struct {
+ name string
+ typ arrow.DataType
+ scalar scalar.Scalar
+ }{
+ {name: "int8", typ: arrow.PrimitiveTypes.Int8, scalar:
scalar.NewInt8Scalar(1)},
+ {name: "int16", typ: arrow.PrimitiveTypes.Int16, scalar:
scalar.NewInt16Scalar(1)},
+ {name: "int32", typ: arrow.PrimitiveTypes.Int32, scalar:
scalar.NewInt32Scalar(1)},
+ {name: "int64", typ: arrow.PrimitiveTypes.Int64, scalar:
scalar.NewInt64Scalar(1)},
+ {name: "uint8", typ: arrow.PrimitiveTypes.Uint8, scalar:
scalar.NewUint8Scalar(1)},
+ {name: "uint16", typ: arrow.PrimitiveTypes.Uint16, scalar:
scalar.NewUint16Scalar(1)},
+ {name: "uint32", typ: arrow.PrimitiveTypes.Uint32, scalar:
scalar.NewUint32Scalar(1)},
+ {name: "uint64", typ: arrow.PrimitiveTypes.Uint64, scalar:
scalar.NewUint64Scalar(1)},
+ }
+
+ for _, listType := range listTypes {
+ t.Run(listType.String(), func(t *testing.T) {
+ input := listElementInput(t, mem, listType, `[[10, 20],
null]`)
+ defer input.Release()
+ expected := listElementInput(t, mem,
arrow.PrimitiveTypes.Int32, `[20, null]`)
+ defer expected.Release()
+
+ for _, tc := range indexes {
+ tc := tc
+ t.Run(tc.name+" scalar", func(t *testing.T) {
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ArrayDatum{Value:
input.Data()},
+ &compute.ScalarDatum{Value:
tc.scalar},
+ )
+ require.NoError(t, err)
+ defer result.Release()
+
+ actual :=
result.(*compute.ArrayDatum).MakeArray()
+ defer actual.Release()
+ require.NoError(t,
array.ValidateFull(actual))
+ assert.True(t, array.Equal(expected,
actual), "expected: %s\ngot: %s", expected, actual)
+ })
+
+ t.Run(tc.name+" array", func(t *testing.T) {
+ singleInput := listElementInput(t, mem,
listType, `[[10, 20]]`)
+ defer singleInput.Release()
+ index := listElementInput(t, mem,
tc.typ, `[1]`)
+ defer index.Release()
+ expectedSingle := listElementInput(t,
mem, arrow.PrimitiveTypes.Int32, `[20]`)
+ defer expectedSingle.Release()
+
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ArrayDatum{Value:
singleInput.Data()},
+ &compute.ArrayDatum{Value:
index.Data()},
+ )
+ require.NoError(t, err)
+ defer result.Release()
+
+ actual :=
result.(*compute.ArrayDatum).MakeArray()
+ defer actual.Release()
+ require.NoError(t,
array.ValidateFull(actual))
+ assert.True(t,
array.Equal(expectedSingle, actual), "expected: %s\ngot: %s", expectedSingle,
actual)
+ })
+ }
+ })
+ }
+}
+
+func TestListElementNumericChildren(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ childTypes := []arrow.DataType{
+ arrow.PrimitiveTypes.Int8,
+ arrow.PrimitiveTypes.Int16,
+ arrow.PrimitiveTypes.Int32,
+ arrow.PrimitiveTypes.Int64,
+ arrow.PrimitiveTypes.Uint8,
+ arrow.PrimitiveTypes.Uint16,
+ arrow.PrimitiveTypes.Uint32,
+ arrow.PrimitiveTypes.Uint64,
+ arrow.FixedWidthTypes.Float16,
+ arrow.PrimitiveTypes.Float32,
+ arrow.PrimitiveTypes.Float64,
+ }
+ for _, childType := range childTypes {
+ t.Run(childType.String(), func(t *testing.T) {
+ listTypes := []arrow.DataType{
+ arrow.ListOf(childType),
+ arrow.LargeListOf(childType),
+ arrow.ListViewOf(childType),
+ arrow.LargeListViewOf(childType),
+ arrow.FixedSizeListOf(2, childType),
+ }
+ for _, listType := range listTypes {
+ t.Run(listType.String(), func(t *testing.T) {
+ input := listElementInput(t, mem,
listType, `[[1, 2], [3, 4], null, [5, 6]]`)
+ defer input.Release()
+ expected := listElementInput(t, mem,
childType, `[2, 4, null, 6]`)
+ defer expected.Release()
+
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ArrayDatum{Value:
input.Data()},
+ &compute.ScalarDatum{Value:
scalar.NewInt64Scalar(1)},
+ )
+ require.NoError(t, err)
+ defer result.Release()
+
+ actual :=
result.(*compute.ArrayDatum).MakeArray()
+ defer actual.Release()
+ require.NoError(t,
array.ValidateFull(actual))
+ assert.True(t, array.Equal(expected,
actual), "expected: %s\ngot: %s", expected, actual)
+ })
+ }
+ })
+ }
+}
+
+func TestListElementPreservesChildNulls(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ input := listElementInput(t, mem,
arrow.ListOf(arrow.PrimitiveTypes.Int32), `[[1, null], [2, 3]]`)
+ defer input.Release()
+ expected := listElementInput(t, mem, arrow.PrimitiveTypes.Int32,
`[null, 3]`)
+ defer expected.Release()
+
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ArrayDatum{Value: input.Data()},
+ &compute.ScalarDatum{Value: scalar.NewInt8Scalar(1)},
+ )
+ require.NoError(t, err)
+ defer result.Release()
+
+ actual := result.(*compute.ArrayDatum).MakeArray()
+ defer actual.Release()
+ assert.True(t, array.Equal(expected, actual), "expected: %s\ngot: %s",
expected, actual)
+}
+
+func TestListElementSlicedInputs(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ tests := []arrow.DataType{
+ arrow.ListOf(arrow.PrimitiveTypes.Int32),
+ arrow.FixedSizeListOf(2, arrow.PrimitiveTypes.Int32),
+ }
+ for _, typ := range tests {
+ t.Run(typ.String(), func(t *testing.T) {
+ input := listElementInput(t, mem, typ, `[[0, 1], [2,
3], [4, 5], [6, 7]]`)
+ defer input.Release()
+ sliced := array.NewSlice(input, 1, 3)
+ defer sliced.Release()
+ expected := listElementInput(t, mem,
arrow.PrimitiveTypes.Int32, `[3, 5]`)
+ defer expected.Release()
+
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ArrayDatum{Value: sliced.Data()},
+ &compute.ScalarDatum{Value:
scalar.NewInt64Scalar(1)},
+ )
+ require.NoError(t, err)
+ defer result.Release()
+
+ actual := result.(*compute.ArrayDatum).MakeArray()
+ defer actual.Release()
+ assert.True(t, array.Equal(expected, actual),
"expected: %s\ngot: %s", expected, actual)
+
+ })
+ }
+}
+
+func TestListElementListViewUsesSizes(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ tests := []struct {
+ name string
+ input arrow.Array
+ }{
+ {name: "list view", input:
makeListViewWithOutOfOrderOffsets(mem)},
+ {name: "large list view", input:
makeLargeListViewWithOutOfOrderOffsets(mem)},
+ }
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ defer tc.input.Release()
+ expected := listElementInput(t, mem,
arrow.PrimitiveTypes.Int32, `[14, 10, 12]`)
+ defer expected.Release()
+
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ArrayDatum{Value: tc.input.Data()},
+ &compute.ScalarDatum{Value:
scalar.NewInt64Scalar(0)},
+ )
+ require.NoError(t, err)
+ defer result.Release()
+
+ actual := result.(*compute.ArrayDatum).MakeArray()
+ defer actual.Release()
+ assert.True(t, array.Equal(expected, actual),
"expected: %s\ngot: %s", expected, actual)
+
+ sliced := array.NewSlice(tc.input, 1,
int64(tc.input.Len()))
+ defer sliced.Release()
+ slicedExpected := listElementInput(t, mem,
arrow.PrimitiveTypes.Int32, `[10, 12]`)
+ defer slicedExpected.Release()
+ slicedResult, err := compute.ListElement(
+ context.Background(),
+ &compute.ArrayDatum{Value: sliced.Data()},
+ &compute.ScalarDatum{Value:
scalar.NewInt64Scalar(0)},
+ )
+ require.NoError(t, err)
+ defer slicedResult.Release()
+ slicedActual :=
slicedResult.(*compute.ArrayDatum).MakeArray()
+ defer slicedActual.Release()
+ assert.True(t, array.Equal(slicedExpected,
slicedActual), "expected: %s\ngot: %s", slicedExpected, slicedActual)
+ })
+ }
+}
+
+func TestListElementRejectsInvalidListViewOffsets(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ tests := []struct {
+ name string
+ build func(memory.Allocator) arrow.Array
+ }{
+ {"list view negative offset", func(mem memory.Allocator)
arrow.Array {
+ builder := array.NewListViewBuilder(mem,
arrow.PrimitiveTypes.Int32)
+ builder.ValueBuilder().(*array.Int32Builder).Append(7)
+ builder.AppendDimensions(-1, 1)
+ result := builder.NewArray()
+ builder.Release()
+ return result
+ }},
+ {"list view child overflow", func(mem memory.Allocator)
arrow.Array {
+ builder := array.NewListViewBuilder(mem,
arrow.PrimitiveTypes.Int32)
+ builder.ValueBuilder().(*array.Int32Builder).Append(7)
+ builder.AppendDimensions(1, 1)
+ result := builder.NewArray()
+ builder.Release()
+ return result
+ }},
+ {"large list view negative offset", func(mem memory.Allocator)
arrow.Array {
+ builder := array.NewLargeListViewBuilder(mem,
arrow.PrimitiveTypes.Int32)
+ builder.ValueBuilder().(*array.Int32Builder).Append(7)
+ builder.AppendDimensions(-1, 1)
+ result := builder.NewArray()
+ builder.Release()
+ return result
+ }},
+ {"large list view child overflow", func(mem memory.Allocator)
arrow.Array {
+ builder := array.NewLargeListViewBuilder(mem,
arrow.PrimitiveTypes.Int32)
+ builder.ValueBuilder().(*array.Int32Builder).Append(7)
+ builder.AppendDimensions(1, 1)
+ result := builder.NewArray()
+ builder.Release()
+ return result
+ }},
+ {"large list view offset overflow", func(mem memory.Allocator)
arrow.Array {
+ builder := array.NewLargeListViewBuilder(mem,
arrow.PrimitiveTypes.Int32)
+ builder.AppendDimensions(int(^uint(0)>>1), 1)
+ result := builder.NewArray()
+ builder.Release()
+ return result
+ }},
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ input := tc.build(mem)
+ defer input.Release()
+
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ArrayDatum{Value: input.Data()},
+ &compute.ScalarDatum{Value:
scalar.NewInt64Scalar(0)},
+ )
+ if err == nil && result != nil {
+ result.Release()
+ }
+ require.ErrorIs(t, err, arrow.ErrInvalid)
+ })
+ }
+}
+
+func TestListElementRejectsFixedSizeListOffsetOverflow(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ valuesBuilder := array.NewInt32Builder(mem)
+ valuesBuilder.AppendValues([]int32{10, 20, 30, 40}, nil)
+ values := valuesBuilder.NewArray()
+ valuesBuilder.Release()
+ defer values.Release()
+
+ const offset = int64(6148914691236517205)
+ typ := arrow.FixedSizeListOf(3, arrow.PrimitiveTypes.Int32)
+ data := array.NewData(typ, 1, []*memory.Buffer{nil},
[]arrow.ArrayData{values.Data()}, 0, int(offset))
+ input := array.NewFixedSizeListData(data)
+ data.Release()
+ defer input.Release()
+
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ArrayDatum{Value: input.Data()},
+ &compute.ScalarDatum{Value: scalar.NewInt64Scalar(0)},
+ )
+ if err == nil && result != nil {
+ result.Release()
+ }
+ require.ErrorIs(t, err, arrow.ErrInvalid)
+}
+
+func makeListViewWithOutOfOrderOffsets(mem memory.Allocator) arrow.Array {
+ builder := array.NewListViewBuilder(mem, arrow.PrimitiveTypes.Int32)
+ values := builder.ValueBuilder().(*array.Int32Builder)
+ values.AppendValues([]int32{10, 11, 12, 13, 14, 15}, nil)
+ builder.AppendDimensions(4, 2)
+ builder.AppendDimensions(0, 1)
+ builder.AppendDimensions(2, 2)
+ result := builder.NewArray()
+ builder.Release()
+ return result
+}
+
+func makeLargeListViewWithOutOfOrderOffsets(mem memory.Allocator) arrow.Array {
+ builder := array.NewLargeListViewBuilder(mem,
arrow.PrimitiveTypes.Int32)
+ values := builder.ValueBuilder().(*array.Int32Builder)
+ values.AppendValues([]int32{10, 11, 12, 13, 14, 15}, nil)
+ builder.AppendDimensions(4, 2)
+ builder.AppendDimensions(0, 1)
+ builder.AppendDimensions(2, 2)
+ result := builder.NewArray()
+ builder.Release()
+ return result
+}
+
+func TestListElementNestedListViewChild(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ tests := []struct {
+ name string
+ build func(memory.Allocator) arrow.Array
+ elemTyp arrow.DataType
+ }{
+ {"list view", func(mem memory.Allocator) arrow.Array {
+ outer := array.NewListBuilder(mem,
arrow.ListViewOf(arrow.PrimitiveTypes.Int32))
+ inner := outer.ValueBuilder().(*array.ListViewBuilder)
+ values := inner.ValueBuilder().(*array.Int32Builder)
+ outer.Append(true)
+ values.AppendValues([]int32{10, 11, 20, 21}, nil)
+ inner.AppendDimensions(0, 2)
+ inner.AppendDimensions(2, 2)
+ result := outer.NewArray()
+ outer.Release()
+ return result
+ }, arrow.ListViewOf(arrow.PrimitiveTypes.Int32)},
+ {"large list view", func(mem memory.Allocator) arrow.Array {
+ outer := array.NewListBuilder(mem,
arrow.LargeListViewOf(arrow.PrimitiveTypes.Int32))
+ inner :=
outer.ValueBuilder().(*array.LargeListViewBuilder)
+ values := inner.ValueBuilder().(*array.Int32Builder)
+ outer.Append(true)
+ values.AppendValues([]int32{10, 11, 20, 21}, nil)
+ inner.AppendDimensions(0, 2)
+ inner.AppendDimensions(2, 2)
+ result := outer.NewArray()
+ outer.Release()
+ return result
+ }, arrow.LargeListViewOf(arrow.PrimitiveTypes.Int32)},
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ input := tc.build(mem)
+ defer input.Release()
+ expected := listElementInput(t, mem, tc.elemTyp, `[[20,
21]]`)
+ defer expected.Release()
+
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ArrayDatum{Value: input.Data()},
+ &compute.ScalarDatum{Value:
scalar.NewInt64Scalar(1)},
+ )
+ require.NoError(t, err)
+ defer result.Release()
+
+ actual := result.(*compute.ArrayDatum).MakeArray()
+ defer actual.Release()
+ require.NoError(t, array.ValidateFull(actual))
+ assert.True(t, array.Equal(expected, actual),
"expected: %s\ngot: %s", expected, actual)
+ })
+ }
+}
+
+func TestListElementEmptyNestedListViews(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ for _, elemType := range []arrow.DataType{
+ arrow.ListViewOf(arrow.PrimitiveTypes.Int32),
+ arrow.LargeListViewOf(arrow.PrimitiveTypes.Int32),
+ } {
+ t.Run(elemType.String(), func(t *testing.T) {
+ builder := array.NewListBuilder(mem, elemType)
+ input := builder.NewArray()
+ builder.Release()
+ defer input.Release()
+
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ArrayDatum{Value: input.Data()},
+ &compute.ScalarDatum{Value:
scalar.NewInt64Scalar(0)},
+ )
+ require.NoError(t, err)
+ defer result.Release()
+
+ actual := result.(*compute.ArrayDatum).MakeArray()
+ defer actual.Release()
+ require.Equal(t, 0, actual.Len())
+ require.True(t, arrow.TypeEqual(elemType,
actual.DataType()))
+ require.NoError(t, array.ValidateFull(actual))
+ })
+ }
+}
+
+func TestListElementEmptyRunEndEncodedChild(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ elemType := arrow.RunEndEncodedOf(arrow.PrimitiveTypes.Int32,
arrow.PrimitiveTypes.Int32)
+ builder := array.NewListBuilder(mem, elemType)
+ input := builder.NewArray()
+ builder.Release()
+ defer input.Release()
+
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ArrayDatum{Value: input.Data()},
+ &compute.ScalarDatum{Value: scalar.NewInt64Scalar(0)},
+ )
+ require.NoError(t, err)
+ defer result.Release()
+
+ actual := result.(*compute.ArrayDatum).MakeArray()
+ defer actual.Release()
+ require.Equal(t, 0, actual.Len())
+ require.True(t, arrow.TypeEqual(elemType, actual.DataType()))
+ require.NoError(t, array.ValidateFull(actual))
+}
+
+func TestListElementNullParentEmptyListViews(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ for _, elemType := range []arrow.DataType{
+ arrow.ListViewOf(arrow.PrimitiveTypes.Int32),
+ arrow.LargeListViewOf(arrow.PrimitiveTypes.Int32),
+ } {
+ t.Run(elemType.String(), func(t *testing.T) {
+ builder := array.NewListBuilder(mem, elemType)
+ builder.AppendNull()
+ input := builder.NewArray()
+ builder.Release()
+ defer input.Release()
+
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ArrayDatum{Value: input.Data()},
+ &compute.ScalarDatum{Value:
scalar.NewInt64Scalar(0)},
+ )
+ require.NoError(t, err)
+ defer result.Release()
+
+ actual := result.(*compute.ArrayDatum).MakeArray()
+ defer actual.Release()
+ require.Equal(t, 1, actual.Len())
+ require.Equal(t, 1, actual.NullN())
+ require.True(t, actual.IsNull(0))
+ require.True(t, arrow.TypeEqual(elemType,
actual.DataType()))
+ require.NoError(t, array.ValidateFull(actual))
+ })
+ }
+}
+
+func TestListElementScalarListWithViewElement(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ for _, values := range []arrow.Array{
+ makeListViewWithOutOfOrderOffsets(mem),
+ makeLargeListViewWithOutOfOrderOffsets(mem),
+ } {
+ t.Run(values.DataType().String(), func(t *testing.T) {
+ defer values.Release()
+ lists := scalar.NewListScalar(values)
+ defer lists.Release()
+
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ScalarDatum{Value: lists},
+ &compute.ScalarDatum{Value:
scalar.NewInt64Scalar(0)},
+ )
+ if err == nil && result != nil {
+ result.Release()
+ }
+ require.ErrorIs(t, err, arrow.ErrNotImplemented)
+ })
+ }
+}
+
+func makeListElementViewValues(mem memory.Allocator, binary bool) arrow.Array {
+ values := []string{strings.Repeat("a", 32), strings.Repeat("b", 32)}
+ if binary {
+ builder := array.NewBinaryViewBuilder(mem)
+ builder.SetBlockSize(1)
+ for _, value := range values {
+ builder.Append([]byte(value))
+ }
+ result := builder.NewArray()
+ builder.Release()
+ return result
+ }
+
+ builder := array.NewStringViewBuilder(mem)
+ builder.SetBlockSize(1)
+ for _, value := range values {
+ builder.Append(value)
+ }
+ result := builder.NewArray()
+ builder.Release()
+ return result
+}
+
+func makeListElementArrayWithChild(mem memory.Allocator, elemType
arrow.DataType, child arrow.Array) arrow.Array {
+ offsetsBuilder := array.NewInt32Builder(mem)
+ offsetsBuilder.AppendValues([]int32{0, 1, 2}, nil)
+ offsets := offsetsBuilder.NewArray()
+ offsetsBuilder.Release()
+
+ data := array.NewData(
+ arrow.ListOf(elemType),
+ 2,
+ []*memory.Buffer{nil, offsets.Data().Buffers()[1]},
+ []arrow.ArrayData{child.Data()},
+ 0,
+ 0,
+ )
+ result := array.NewListData(data)
+ data.Release()
+ offsets.Release()
+ child.Release()
+ return result
+}
+
+func TestListElementRejectsNestedViewChildren(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ viewType := arrow.BinaryTypes.StringView
+ tests := []struct {
+ name string
+ build func() arrow.Array
+ }{
+ {
+ name: "string_view",
+ build: func() arrow.Array {
+ return makeListElementArrayWithChild(mem,
viewType, makeListElementViewValues(mem, false))
+ },
+ },
+ {
+ name: "binary_view",
+ build: func() arrow.Array {
+ return makeListElementArrayWithChild(mem,
arrow.BinaryTypes.BinaryView, makeListElementViewValues(mem, true))
+ },
+ },
+ {
+ name: "struct_string_view",
+ build: func() arrow.Array {
+ typ := arrow.StructOf(arrow.Field{Name:
"value", Type: viewType, Nullable: true})
+ builder := array.NewStructBuilder(mem, typ)
+ values :=
builder.FieldBuilder(0).(*array.StringViewBuilder)
+ values.SetBlockSize(1)
+ for _, value := range
[]string{strings.Repeat("a", 32), strings.Repeat("b", 32)} {
+ builder.Append(true)
+ values.Append(value)
+ }
+ child := builder.NewArray()
+ builder.Release()
+ return makeListElementArrayWithChild(mem, typ,
child)
+ },
+ },
+ {
+ name: "list_string_view",
+ build: func() arrow.Array {
+ typ := arrow.ListOf(viewType)
+ builder := array.NewListBuilder(mem, viewType)
+ values :=
builder.ValueBuilder().(*array.StringViewBuilder)
+ values.SetBlockSize(1)
+ for _, value := range
[]string{strings.Repeat("a", 32), strings.Repeat("b", 32)} {
+ builder.Append(true)
+ values.Append(value)
+ }
+ child := builder.NewArray()
+ builder.Release()
+ return makeListElementArrayWithChild(mem, typ,
child)
+ },
+ },
+ {
+ name: "fixed_size_list_string_view",
+ build: func() arrow.Array {
+ typ := arrow.FixedSizeListOf(2, viewType)
+ builder := array.NewFixedSizeListBuilder(mem,
2, viewType)
+ values :=
builder.ValueBuilder().(*array.StringViewBuilder)
+ values.SetBlockSize(1)
+ for _, value := range
[]string{strings.Repeat("a", 32), strings.Repeat("b", 32)} {
+ builder.Append(true)
+ values.Append(value)
+ values.Append(value)
+ }
+ child := builder.NewArray()
+ builder.Release()
+ return makeListElementArrayWithChild(mem, typ,
child)
+ },
+ },
+ {
+ name: "dictionary_string_view",
+ build: func() arrow.Array {
+ typ := &arrow.DictionaryType{IndexType:
arrow.PrimitiveTypes.Int8, ValueType: viewType}
+ values := makeListElementViewValues(mem, false)
+ indicesBuilder := array.NewInt8Builder(mem)
+ indicesBuilder.AppendValues([]int8{0, 1}, nil)
+ indices := indicesBuilder.NewArray()
+ indicesBuilder.Release()
+ child := array.NewDictionaryArray(typ, indices,
values)
+ indices.Release()
+ values.Release()
+ return makeListElementArrayWithChild(mem, typ,
child)
+ },
+ },
+ {
+ name: "run_end_encoded_string_view",
+ build: func() arrow.Array {
+ typ :=
arrow.RunEndEncodedOf(arrow.PrimitiveTypes.Int32, viewType)
+ builder := array.NewRunEndEncodedBuilder(mem,
typ.RunEnds(), typ.Encoded())
+ values :=
builder.ValueBuilder().(*array.StringViewBuilder)
+ values.SetBlockSize(1)
+ for _, value := range
[]string{strings.Repeat("a", 32), strings.Repeat("b", 32)} {
+ builder.Append(1)
+ values.Append(value)
+ }
+ child := builder.NewArray()
+ builder.Release()
+ return makeListElementArrayWithChild(mem, typ,
child)
+ },
+ },
+ {
+ name: "extension_string_view",
+ build: func() arrow.Array {
+ typ := &denseUnionExtensionType{ExtensionBase:
arrow.ExtensionBase{Storage: viewType}}
+ storage := makeListElementViewValues(mem, false)
+ child :=
array.NewExtensionArrayWithStorage(typ, storage)
+ storage.Release()
+ return makeListElementArrayWithChild(mem, typ,
child)
+ },
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ input := tc.build()
+ defer input.Release()
+
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ArrayDatum{Value: input.Data()},
+ &compute.ScalarDatum{Value:
scalar.NewInt64Scalar(0)},
+ )
+ if err == nil && result != nil {
+ result.Release()
+ }
+ require.ErrorIs(t, err, arrow.ErrNotImplemented)
+ })
+ }
+}
+
+func TestListElementRejectsDictionaryBackedExtensionChildren(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ dictType := &arrow.DictionaryType{IndexType: arrow.PrimitiveTypes.Int8,
ValueType: arrow.BinaryTypes.String}
+ extType := &denseUnionExtensionType{ExtensionBase:
arrow.ExtensionBase{Storage: dictType}}
+ valuesBuilder := array.NewStringBuilder(mem)
+ valuesBuilder.AppendValues([]string{"a", "b"}, nil)
+ values := valuesBuilder.NewArray()
+ valuesBuilder.Release()
+ defer values.Release()
+ indicesBuilder := array.NewInt8Builder(mem)
+ indicesBuilder.AppendValues([]int8{0, 1}, nil)
+ indices := indicesBuilder.NewArray()
+ indicesBuilder.Release()
+ defer indices.Release()
+
+ data := array.NewDataWithDictionary(extType, 2,
indices.Data().Buffers(), 0, 0, values.Data().(*array.Data))
+ child := array.NewExtensionData(data)
+ data.Release()
+ defer child.Release()
+ require.NoError(t, array.ValidateFull(child.Storage()))
+ nested, err := array.NewStructArray([]arrow.Array{child},
[]string{"value"})
+ require.NoError(t, err)
+ defer nested.Release()
+
+ for _, child := range []arrow.Array{child, nested} {
+ t.Run(child.DataType().String(), func(t *testing.T) {
+ child.Retain()
+ input := makeListElementArrayWithChild(mem,
child.DataType(), child)
+ defer input.Release()
+ empty := array.NewSlice(input, 0, 0)
+ defer empty.Release()
+ chunked := arrow.NewChunked(input.DataType(),
[]arrow.Array{input})
+ defer chunked.Release()
+ listScalar := scalar.NewListScalar(child)
+ defer listScalar.Release()
+ nullScalar :=
scalar.MakeNullScalar(input.DataType()).(scalar.ListScalar)
+ defer nullScalar.Release()
+
+ for _, input := range []compute.Datum{
+ &compute.ArrayDatum{Value: input.Data()},
+ &compute.ArrayDatum{Value: empty.Data()},
+ &compute.ChunkedDatum{Value: chunked},
+ &compute.ScalarDatum{Value: listScalar},
+ &compute.ScalarDatum{Value: nullScalar},
+ } {
+ result, err := compute.ListElement(
+ context.Background(), input,
+ &compute.ScalarDatum{Value:
scalar.NewInt64Scalar(0)},
+ )
+ if err == nil && result != nil {
+ result.Release()
+ }
+ require.ErrorIs(t, err, arrow.ErrNotImplemented)
+ }
+ })
+ }
+}
+
+func TestListElementDecimalArrayChildren(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ tests := []struct {
+ name string
+ typ arrow.DataType
+ add func(array.Builder, int32)
+ }{
+ {
+ name: "decimal32",
+ typ: &arrow.Decimal32Type{Precision: 6, Scale: 2},
+ add: func(builder array.Builder, value int32) {
+
builder.(*array.Decimal32Builder).Append(decimal.Decimal32(value))
+ },
+ },
+ {
+ name: "decimal64",
+ typ: &arrow.Decimal64Type{Precision: 12, Scale: 2},
+ add: func(builder array.Builder, value int32) {
+
builder.(*array.Decimal64Builder).Append(decimal.Decimal64(value))
+ },
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ listBuilder := array.NewListBuilder(mem, tc.typ)
+ values := listBuilder.ValueBuilder()
+ for _, row := range [][]int32{{1, 2}, {3, 4}} {
+ listBuilder.Append(true)
+ for _, value := range row {
+ tc.add(values, value)
+ }
+ }
+ input := listBuilder.NewArray()
+ listBuilder.Release()
+ defer input.Release()
+
+ expectedBuilder := array.NewBuilder(mem, tc.typ)
+ tc.add(expectedBuilder, 2)
+ tc.add(expectedBuilder, 4)
+ expected := expectedBuilder.NewArray()
+ expectedBuilder.Release()
+ defer expected.Release()
+
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ArrayDatum{Value: input.Data()},
+ &compute.ScalarDatum{Value:
scalar.NewInt64Scalar(1)},
+ )
+ require.NoError(t, err)
+ defer result.Release()
+
+ actual := result.(*compute.ArrayDatum).MakeArray()
+ defer actual.Release()
+ require.NoError(t, array.ValidateFull(actual))
+ assert.True(t, array.Equal(expected, actual),
"expected: %s\ngot: %s", expected, actual)
+ })
+ }
+}
+
+func TestListElementScalarListWithUnsupportedDecimalValues(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ tests := []struct {
+ name string
+ typ arrow.DataType
+ }{
+ {name: "decimal32", typ: &arrow.Decimal32Type{Precision: 6,
Scale: 2}},
+ {name: "decimal64", typ: &arrow.Decimal64Type{Precision: 12,
Scale: 2}},
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ builder := array.NewBuilder(mem, tc.typ)
+ switch b := builder.(type) {
+ case *array.Decimal32Builder:
+ b.Append(decimal.Decimal32(123))
+ case *array.Decimal64Builder:
+ b.Append(decimal.Decimal64(123))
+ default:
+ t.Fatalf("unexpected builder type %T", builder)
+ }
+ values := builder.NewArray()
+ builder.Release()
+ defer values.Release()
+
+ list := scalar.NewListScalar(values)
+ defer list.Release()
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ScalarDatum{Value: list},
+ &compute.ScalarDatum{Value:
scalar.NewInt64Scalar(0)},
+ )
+ if err == nil && result != nil {
+ result.Release()
+ }
+ require.ErrorIs(t, err, arrow.ErrNotImplemented)
+ })
+ }
+}
+
+func TestListElementSingleIndexArray(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ input := listElementInput(t, mem,
arrow.ListOf(arrow.PrimitiveTypes.Int32), `[[1, 2]]`)
+ defer input.Release()
+ index := listElementInput(t, mem, arrow.PrimitiveTypes.Int64, `[1]`)
+ defer index.Release()
+ expected := listElementInput(t, mem, arrow.PrimitiveTypes.Int32, `[2]`)
+ defer expected.Release()
+
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ArrayDatum{Value: input.Data()},
+ &compute.ArrayDatum{Value: index.Data()},
+ )
+ require.NoError(t, err)
+ defer result.Release()
+
+ actual := result.(*compute.ArrayDatum).MakeArray()
+ defer actual.Release()
+ assert.True(t, array.Equal(expected, actual), "expected: %s\ngot: %s",
expected, actual)
+}
+
+func TestListElementSingleIndexArrayRejectsMismatchedLengths(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ input := listElementInput(t, mem,
arrow.ListOf(arrow.PrimitiveTypes.Int32), `[[1, 2], [3, 4]]`)
+ defer input.Release()
+ index := listElementInput(t, mem, arrow.PrimitiveTypes.Int64, `[1]`)
+ defer index.Release()
+ result, err := compute.CallFunction(
+ context.Background(),
+ "list_element",
+ nil,
+ &compute.ArrayDatum{Value: input.Data()},
+ &compute.ArrayDatum{Value: index.Data()},
+ )
+ if err == nil && result != nil {
+ result.Release()
+ }
+ require.ErrorIs(t, err, arrow.ErrInvalid)
+}
+
+func TestListElementDispatchBest(t *testing.T) {
+ listType := arrow.ListOf(arrow.PrimitiveTypes.Int32)
+ CheckDispatchBest(t, "list_element",
+ []arrow.DataType{listType, arrow.PrimitiveTypes.Int64},
+ []arrow.DataType{listType, arrow.PrimitiveTypes.Int64})
+}
+
+func TestListElementFunctionDoc(t *testing.T) {
+ fn, ok := compute.GetFunctionRegistry().GetFunction("list_element")
+ require.True(t, ok)
+ require.NoError(t, fn.Validate())
+}
+
+func TestListElementRejectsMultipleIndicesIndependentOfExecutionSpans(t
*testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ input := listElementInput(t, mem,
arrow.ListOf(arrow.PrimitiveTypes.Int32), `[[10, 11], [20, 21]]`)
+ defer input.Release()
+ index := listElementInput(t, mem, arrow.PrimitiveTypes.Int64, `[0, 1]`)
+ defer index.Release()
+
+ chunk0 := array.NewSlice(input, 0, 1)
+ defer chunk0.Release()
+ chunk1 := array.NewSlice(input, 1, 2)
+ defer chunk1.Release()
+ chunkedLists := arrow.NewChunked(input.DataType(),
[]arrow.Array{chunk0, chunk1})
+ defer chunkedLists.Release()
+
+ execCtx := compute.DefaultExecCtx()
+ execCtx.ChunkSize = 1
+
+ tests := []struct {
+ name string
+ ctx context.Context
+ lists compute.Datum
+ }{
+ {name: "regular execution span", ctx: context.Background(),
lists: &compute.ArrayDatum{Value: input.Data()}},
+ {name: "chunk size one", ctx:
compute.SetExecCtx(context.Background(), execCtx), lists:
&compute.ArrayDatum{Value: input.Data()}},
+ {name: "chunked lists", ctx: context.Background(), lists:
&compute.ChunkedDatum{Value: chunkedLists}},
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ result, err := compute.ListElement(
+ tc.ctx,
+ tc.lists,
+ &compute.ArrayDatum{Value: index.Data()},
+ )
+ if err == nil && result != nil {
+ result.Release()
+ }
+ require.ErrorIs(t, err, arrow.ErrNotImplemented)
+ })
+ }
+}
+
+func TestListElementSingleIndexChunkedArray(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ input := listElementInput(t, mem,
arrow.ListOf(arrow.PrimitiveTypes.Int32), `[[1, 2]]`)
+ defer input.Release()
+ index := listElementInput(t, mem, arrow.PrimitiveTypes.Int64, `[1]`)
+ defer index.Release()
+ chunkedIndex := arrow.NewChunked(index.DataType(), []arrow.Array{index})
+ defer chunkedIndex.Release()
+ expected := listElementInput(t, mem, arrow.PrimitiveTypes.Int32, `[2]`)
+ defer expected.Release()
+
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ArrayDatum{Value: input.Data()},
+ &compute.ChunkedDatum{Value: chunkedIndex},
+ )
+ require.NoError(t, err)
+ defer result.Release()
+
+ chunkedResult, ok := result.(*compute.ChunkedDatum)
+ require.True(t, ok)
+ require.Len(t, chunkedResult.Value.Chunks(), 1)
+ assert.True(t, array.Equal(expected, chunkedResult.Value.Chunk(0)),
+ "expected: %s\ngot: %s", expected, chunkedResult.Value.Chunk(0))
+}
+
+func TestListElementScalarList(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ values := listElementInput(t, mem, arrow.PrimitiveTypes.Int32, `[10,
20]`)
+ defer values.Release()
+ list := scalar.NewListScalar(values)
+ defer list.Release()
+
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ScalarDatum{Value: list},
+ &compute.ScalarDatum{Value: scalar.NewInt64Scalar(1)},
+ )
+ require.NoError(t, err)
+ defer result.Release()
+
+ actual := result.(*compute.ScalarDatum).Value
+ assert.True(t, scalar.Equals(scalar.NewInt32Scalar(20), actual),
"expected: 20\ngot: %s", actual)
+}
+
+func TestListElementRejectsNonListScalar(t *testing.T) {
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ScalarDatum{Value: scalar.NewInt32Scalar(7)},
+ &compute.ScalarDatum{Value: scalar.NewInt64Scalar(0)},
+ )
+ if err == nil && result != nil {
+ result.Release()
+ }
+ require.ErrorIs(t, err, arrow.ErrType)
+}
+
+func TestListElementRejectsNilScalarIndex(t *testing.T) {
+ result, err := compute.ListElement(
+ context.Background(),
+ compute.EmptyDatum{},
+ &compute.ScalarDatum{},
+ )
+ if err == nil && result != nil {
+ result.Release()
+ }
+ require.ErrorIs(t, err, arrow.ErrType)
+}
+
+func TestListElementScalarListWithArrayIndex(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ values := listElementInput(t, mem, arrow.PrimitiveTypes.Int32, `[10,
20]`)
+ defer values.Release()
+ list := scalar.NewListScalar(values)
+ defer list.Release()
+ index := listElementInput(t, mem, arrow.PrimitiveTypes.Int64, `[1]`)
+ defer index.Release()
+ expected := listElementInput(t, mem, arrow.PrimitiveTypes.Int32, `[20]`)
+ defer expected.Release()
+
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ScalarDatum{Value: list},
+ &compute.ArrayDatum{Value: index.Data()},
+ )
+ require.NoError(t, err)
+ defer result.Release()
+
+ arrayResult, ok := result.(*compute.ArrayDatum)
+ require.True(t, ok)
+ actual := arrayResult.MakeArray()
+ defer actual.Release()
+ assert.True(t, array.Equal(expected, actual), "expected: %s\ngot: %s",
expected, actual)
+}
+
+func TestListElementErrors(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ tests := []struct {
+ name string
+ input string
+ index scalar.Scalar
+ }{
+ {name: "out of bounds", input: `[[1], [2, 3]]`, index:
scalar.NewInt64Scalar(1)},
+ {name: "empty list", input: `[[], [1]]`, index:
scalar.NewInt64Scalar(0)},
+ {name: "negative index", input: `[[1]]`, index:
scalar.NewInt64Scalar(-1)},
+ {name: "large unsigned index", input: `[[1]]`, index:
scalar.NewUint64Scalar(^uint64(0))},
+ {name: "null index", input: `[[1]]`, index:
scalar.MakeNullScalar(arrow.PrimitiveTypes.Int64)},
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ input := listElementInput(t, mem,
arrow.ListOf(arrow.PrimitiveTypes.Int32), tc.input)
+ defer input.Release()
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ArrayDatum{Value: input.Data()},
+ &compute.ScalarDatum{Value: tc.index},
+ )
+ if err == nil && result != nil {
+ result.Release()
+ }
+ assert.ErrorIs(t, err, arrow.ErrInvalid)
+ })
+ }
+}
+
+func TestListElementComplexChildren(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ tests := []struct {
+ name string
+ typ arrow.DataType
+ input string
+ expected string
+ }{
+ {name: "string", typ: arrow.ListOf(arrow.BinaryTypes.String),
input: `[["a", "b"], ["c", "d"]]`, expected: `["b", "d"]`},
+ {name: "nested list", typ:
arrow.ListOf(arrow.ListOf(arrow.PrimitiveTypes.Int32)), input: `[[[1, 2], [3]],
[[4], [5, 6]]]`, expected: `[[3], [5, 6]]`},
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ input := listElementInput(t, mem, tc.typ, tc.input)
+ defer input.Release()
+ expected := listElementInput(t, mem,
tc.typ.(arrow.ListLikeType).Elem(), tc.expected)
+ defer expected.Release()
+
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ArrayDatum{Value: input.Data()},
+ &compute.ScalarDatum{Value:
scalar.NewUint8Scalar(1)},
+ )
+ require.NoError(t, err)
+ defer result.Release()
+
+ actual := result.(*compute.ArrayDatum).MakeArray()
+ defer actual.Release()
+ assert.True(t, array.Equal(expected, actual),
"expected: %s\ngot: %s", expected, actual)
+ })
+ }
+}
+
+func TestListElementMonthDayNanoIntervalChild(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ typ := arrow.ListOf(arrow.FixedWidthTypes.MonthDayNanoInterval)
+ input := listElementInput(t, mem, typ,
+ `[[{"months": 1, "days": 2, "nanoseconds": 3}, {"months": 4,
"days": 5, "nanoseconds": 6}], [{"months": 7, "days": 8, "nanoseconds": 9},
{"months": 10, "days": 11, "nanoseconds": 12}]]`)
+ defer input.Release()
+ expected := listElementInput(t, mem,
arrow.FixedWidthTypes.MonthDayNanoInterval,
+ `[{"months": 4, "days": 5, "nanoseconds": 6}, {"months": 10,
"days": 11, "nanoseconds": 12}]`)
+ defer expected.Release()
+
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ArrayDatum{Value: input.Data()},
+ &compute.ScalarDatum{Value: scalar.NewInt64Scalar(1)},
+ )
+ require.NoError(t, err)
+ defer result.Release()
+
+ actual := result.(*compute.ArrayDatum).MakeArray()
+ defer actual.Release()
+ assert.True(t, array.Equal(expected, actual), "expected: %s\ngot: %s",
expected, actual)
+}
+
+func TestListElementDenseUnionChild(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ unionType := arrow.DenseUnionOf(
+ []arrow.Field{
+ {Name: "number", Type: arrow.PrimitiveTypes.Int32,
Nullable: true},
+ {Name: "text", Type: arrow.BinaryTypes.String,
Nullable: true},
+ },
+ []arrow.UnionTypeCode{0, 1},
+ )
+
+ builder := array.NewListBuilder(mem, unionType)
+ values := builder.ValueBuilder().(*array.DenseUnionBuilder)
+ builder.Append(true)
+ values.Append(0)
+ values.Child(0).(*array.Int32Builder).Append(10)
+ values.Append(1)
+ values.Child(1).(*array.StringBuilder).Append("a")
+ builder.Append(true)
+ values.Append(1)
+ values.Child(1).(*array.StringBuilder).Append("b")
+ values.Append(0)
+ values.Child(0).(*array.Int32Builder).Append(20)
+ input := builder.NewArray()
+ builder.Release()
+ defer input.Release()
+
+ expectedBuilder := array.NewDenseUnionBuilder(mem, unionType)
+ expectedBuilder.Append(1)
+ expectedBuilder.Child(1).(*array.StringBuilder).Append("a")
+ expectedBuilder.Append(0)
+ expectedBuilder.Child(0).(*array.Int32Builder).Append(20)
+ expected := expectedBuilder.NewArray()
+ expectedBuilder.Release()
+ defer expected.Release()
+
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ArrayDatum{Value: input.Data()},
+ &compute.ScalarDatum{Value: scalar.NewInt64Scalar(1)},
+ )
+ require.NoError(t, err)
+ defer result.Release()
+
+ actual := result.(*compute.ArrayDatum).MakeArray()
+ defer actual.Release()
+ assert.True(t, array.Equal(expected, actual), "expected: %s\ngot: %s",
expected, actual)
+}
+
+func TestListElementDenseUnionExtensionChild(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ unionType := arrow.DenseUnionOf(
+ []arrow.Field{
+ {Name: "number", Type: arrow.PrimitiveTypes.Int32,
Nullable: true},
+ {Name: "text", Type: arrow.BinaryTypes.String,
Nullable: true},
+ },
+ []arrow.UnionTypeCode{0, 1},
+ )
+ extType := &denseUnionExtensionType{ExtensionBase:
arrow.ExtensionBase{Storage: unionType}}
+
+ builder := array.NewListBuilder(mem, extType)
+ values :=
builder.ValueBuilder().(*array.ExtensionBuilder).StorageBuilder().(*array.DenseUnionBuilder)
+ builder.Append(true)
+ values.Append(0)
+ values.Child(0).(*array.Int32Builder).Append(10)
+ values.Append(1)
+ values.Child(1).(*array.StringBuilder).Append("a")
+ builder.Append(true)
+ values.Append(1)
+ values.Child(1).(*array.StringBuilder).Append("b")
+ values.Append(0)
+ values.Child(0).(*array.Int32Builder).Append(20)
+ input := builder.NewArray()
+ builder.Release()
+ defer input.Release()
+
+ expectedBuilder := array.NewDenseUnionBuilder(mem, unionType)
+ expectedBuilder.Append(1)
+ expectedBuilder.Child(1).(*array.StringBuilder).Append("a")
+ expectedBuilder.Append(0)
+ expectedBuilder.Child(0).(*array.Int32Builder).Append(20)
+ expectedStorage := expectedBuilder.NewArray()
+ expectedBuilder.Release()
+ expected := array.NewExtensionArrayWithStorage(extType, expectedStorage)
+ expectedStorage.Release()
+ defer expected.Release()
+
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ArrayDatum{Value: input.Data()},
+ &compute.ScalarDatum{Value: scalar.NewInt64Scalar(1)},
+ )
+ require.NoError(t, err)
+ defer result.Release()
+
+ actual := result.(*compute.ArrayDatum).MakeArray()
+ defer actual.Release()
+ require.True(t, arrow.TypeEqual(extType, actual.DataType()))
+ storage := actual.(array.ExtensionArray).Storage()
+ require.NoError(t, array.ValidateFull(storage))
+ assert.True(t, array.Equal(expected, actual), "expected: %s\ngot: %s",
expected, actual)
+}
+
+func TestListElementExtensionRunEndEncodedNullParent(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ storageType := arrow.RunEndEncodedOf(arrow.PrimitiveTypes.Int32,
arrow.PrimitiveTypes.Int32)
+ extType := &runEndExtensionType{ExtensionBase:
arrow.ExtensionBase{Storage: storageType}}
+
+ builder := array.NewListBuilder(mem, extType)
+ builder.AppendNull()
+ input := builder.NewArray()
+ builder.Release()
+ defer input.Release()
+
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ArrayDatum{Value: input.Data()},
+ &compute.ScalarDatum{Value: scalar.NewInt64Scalar(0)},
+ )
+ require.NoError(t, err)
+ defer result.Release()
+
+ actual := result.(*compute.ArrayDatum).MakeArray()
+ defer actual.Release()
+ require.True(t, arrow.TypeEqual(extType, actual.DataType()))
+ require.NoError(t, array.ValidateFull(actual))
+ storage := actual.(array.ExtensionArray).Storage()
+ require.True(t, storage.(*array.RunEndEncoded).Values().IsNull(0))
+ require.NoError(t, array.ValidateFull(storage))
+}
+
+func TestListElementDenseUnionWithUnusedGenericChild(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ unionType := arrow.DenseUnionOf(
+ []arrow.Field{
+ {Name: "number", Type: arrow.PrimitiveTypes.Int32,
Nullable: true},
+ {Name: "values", Type:
arrow.ListOf(arrow.PrimitiveTypes.Int32), Nullable: true},
+ },
+ []arrow.UnionTypeCode{0, 1},
+ )
+
+ builder := array.NewListBuilder(mem, unionType)
+ values := builder.ValueBuilder().(*array.DenseUnionBuilder)
+ builder.Append(true)
+ values.Append(0)
+ values.Child(0).(*array.Int32Builder).Append(10)
+ builder.Append(true)
+ values.Append(0)
+ values.Child(0).(*array.Int32Builder).Append(20)
+ input := builder.NewArray()
+ builder.Release()
+ defer input.Release()
+
+ expectedBuilder := array.NewDenseUnionBuilder(mem, unionType)
+ expectedBuilder.Append(0)
+ expectedBuilder.Child(0).(*array.Int32Builder).Append(10)
+ expectedBuilder.Append(0)
+ expectedBuilder.Child(0).(*array.Int32Builder).Append(20)
+ expected := expectedBuilder.NewArray()
+ expectedBuilder.Release()
+ defer expected.Release()
+
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ArrayDatum{Value: input.Data()},
+ &compute.ScalarDatum{Value: scalar.NewInt64Scalar(0)},
+ )
+ require.NoError(t, err)
+ defer result.Release()
+
+ actual := result.(*compute.ArrayDatum).MakeArray()
+ defer actual.Release()
+ assert.True(t, array.Equal(expected, actual), "expected: %s\ngot: %s",
expected, actual)
+}
+
+func TestListElementDenseUnionScalarWithUnusedUnsupportedChild(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ unionType := arrow.DenseUnionOf(
+ []arrow.Field{
+ {Name: "number", Type: arrow.PrimitiveTypes.Int32,
Nullable: true},
+ {Name: "values", Type:
arrow.ListViewOf(arrow.PrimitiveTypes.Int32), Nullable: true},
+ },
+ []arrow.UnionTypeCode{0, 1},
+ )
+
+ builder := array.NewListBuilder(mem, unionType)
+ values := builder.ValueBuilder().(*array.DenseUnionBuilder)
+ builder.Append(true)
+ values.Append(0)
+ values.Child(0).(*array.Int32Builder).Append(42)
+ input := builder.NewArray()
+ builder.Release()
+ defer input.Release()
+
+ listValue, err := scalar.GetScalar(input, 0)
+ require.NoError(t, err)
+ defer listValue.(scalar.Releasable).Release()
+
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ScalarDatum{Value: listValue},
+ &compute.ScalarDatum{Value: scalar.NewInt64Scalar(0)},
+ )
+ require.NoError(t, err)
+ defer result.Release()
+
+ actual := result.(*compute.ScalarDatum).Value
+ require.Equal(t, arrow.DENSE_UNION, actual.DataType().ID())
+ assert.Equal(t, int32(42),
actual.(scalar.Union).ChildValue().(*scalar.Int32).Value)
+}
+
+func TestListElementDenseUnionScalarWithActiveUnsupportedChild(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ unionType := arrow.DenseUnionOf(
+ []arrow.Field{{Name: "values", Type:
arrow.ListViewOf(arrow.PrimitiveTypes.Int32), Nullable: true}},
+ []arrow.UnionTypeCode{0},
+ )
+ builder := array.NewListBuilder(mem, unionType)
+ values := builder.ValueBuilder().(*array.DenseUnionBuilder)
+ listBuilder := values.Child(0).(*array.ListViewBuilder)
+ builder.Append(true)
+ values.Append(0)
+ listBuilder.ValueBuilder().(*array.Int32Builder).Append(9)
+ listBuilder.AppendDimensions(0, 1)
+ input := builder.NewArray()
+ builder.Release()
+ defer input.Release()
+
+ listValue := scalar.NewListScalar(input.(*array.List).ListValues())
+ defer listValue.Release()
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ScalarDatum{Value: listValue},
+ &compute.ScalarDatum{Value: scalar.NewInt64Scalar(0)},
+ )
+ if err == nil && result != nil {
+ result.Release()
+ }
+ require.ErrorIs(t, err, arrow.ErrNotImplemented)
+}
+
+func TestListElementDenseUnionScalarWithActiveUnsupportedDecimalChild(t
*testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ for _, tc := range []struct {
+ name string
+ typ arrow.DataType
+ add func(array.Builder)
+ }{
+ {
+ name: "decimal32",
+ typ: &arrow.Decimal32Type{Precision: 6, Scale: 2},
+ add: func(builder array.Builder) {
+
builder.(*array.Decimal32Builder).Append(decimal.Decimal32(123))
+ },
+ },
+ {
+ name: "decimal64",
+ typ: &arrow.Decimal64Type{Precision: 12, Scale: 2},
+ add: func(builder array.Builder) {
+
builder.(*array.Decimal64Builder).Append(decimal.Decimal64(123))
+ },
+ },
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ unionType := arrow.DenseUnionOf(
+ []arrow.Field{{Name: "value", Type: tc.typ,
Nullable: true}},
+ []arrow.UnionTypeCode{0},
+ )
+ builder := array.NewListBuilder(mem, unionType)
+ values :=
builder.ValueBuilder().(*array.DenseUnionBuilder)
+ builder.Append(true)
+ values.Append(0)
+ tc.add(values.Child(0))
+ input := builder.NewArray()
+ builder.Release()
+ defer input.Release()
+
+ listValue :=
scalar.NewListScalar(input.(*array.List).ListValues())
+ defer listValue.Release()
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ScalarDatum{Value: listValue},
+ &compute.ScalarDatum{Value:
scalar.NewInt64Scalar(0)},
+ )
+ if err == nil && result != nil {
+ result.Release()
+ }
+ require.ErrorIs(t, err, arrow.ErrNotImplemented)
+ })
+ }
+}
+
+func TestListElementDenseUnionScalarWithActiveNullUnsupportedChild(t
*testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ for _, tc := range []struct {
+ name string
+ typ arrow.DataType
+ }{
+ {
+ name: "binary_view",
+ typ: arrow.BinaryTypes.BinaryView,
+ },
+ {
+ name: "string_view",
+ typ: arrow.BinaryTypes.StringView,
+ },
+ {
+ name: "decimal32",
+ typ: &arrow.Decimal32Type{Precision: 6, Scale: 2},
+ },
+ {
+ name: "decimal64",
+ typ: &arrow.Decimal64Type{Precision: 12, Scale: 2},
+ },
+ {
+ name: "list_view",
+ typ: arrow.ListViewOf(arrow.PrimitiveTypes.Int32),
+ },
+ {
+ name: "large_list_view",
+ typ: arrow.LargeListViewOf(arrow.PrimitiveTypes.Int32),
+ },
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ unionType := arrow.DenseUnionOf(
+ []arrow.Field{{Name: "value", Type: tc.typ,
Nullable: true}},
+ []arrow.UnionTypeCode{0},
+ )
+ builder := array.NewListBuilder(mem, unionType)
+ values :=
builder.ValueBuilder().(*array.DenseUnionBuilder)
+ builder.Append(true)
+ values.Append(0)
+ values.Child(0).AppendNull()
+ input := builder.NewArray()
+ builder.Release()
+ defer input.Release()
+
+ listValue :=
scalar.NewListScalar(input.(*array.List).ListValues())
+ defer listValue.Release()
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ScalarDatum{Value: listValue},
+ &compute.ScalarDatum{Value:
scalar.NewInt64Scalar(0)},
+ )
+ if err == nil && result != nil {
+ result.Release()
+ }
+ require.ErrorIs(t, err, arrow.ErrNotImplemented)
+ })
+ }
+}
+
+func TestListElementDenseUnionExtensionScalarWithUnusedUnsupportedChild(t
*testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ unionType := arrow.DenseUnionOf(
+ []arrow.Field{
+ {Name: "number", Type: arrow.PrimitiveTypes.Int32,
Nullable: true},
+ {Name: "values", Type:
arrow.ListViewOf(arrow.PrimitiveTypes.Int32), Nullable: true},
+ },
+ []arrow.UnionTypeCode{0, 1},
+ )
+ extType := &denseUnionExtensionType{ExtensionBase:
arrow.ExtensionBase{Storage: unionType}}
+
+ builder := array.NewListBuilder(mem, extType)
+ values :=
builder.ValueBuilder().(*array.ExtensionBuilder).StorageBuilder().(*array.DenseUnionBuilder)
+ builder.Append(true)
+ values.Append(0)
+ values.Child(0).(*array.Int32Builder).Append(42)
+ values.Append(0)
+ values.Child(0).(*array.Int32Builder).AppendNull()
+ input := builder.NewArray()
+ builder.Release()
+ defer input.Release()
+
+ listValue, err := scalar.GetScalar(input, 0)
+ require.NoError(t, err)
+ defer listValue.(scalar.Releasable).Release()
+
+ for i := int64(0); i < 2; i++ {
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ScalarDatum{Value: listValue},
+ &compute.ScalarDatum{Value: scalar.NewInt64Scalar(i)},
+ )
+ require.NoError(t, err)
+ defer result.Release()
+
+ actual := result.(*compute.ScalarDatum).Value
+ require.True(t, arrow.TypeEqual(extType, actual.DataType()))
+ assert.Equal(t, i == 0, actual.IsValid())
+ assert.NoError(t, actual.ValidateFull())
+ if i == 0 {
+ assert.Equal(t, int32(42),
actual.(*scalar.Extension).Value.(scalar.Union).ChildValue().(*scalar.Int32).Value)
+ }
+ }
+}
+
+func TestListElementDenseUnionMonthDayNanoIntervalChild(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ unionType := arrow.DenseUnionOf(
+ []arrow.Field{
+ {Name: "interval", Type:
arrow.FixedWidthTypes.MonthDayNanoInterval, Nullable: true},
+ {Name: "number", Type: arrow.PrimitiveTypes.Int32,
Nullable: true},
+ },
+ []arrow.UnionTypeCode{0, 1},
+ )
+
+ builder := array.NewListBuilder(mem, unionType)
+ values := builder.ValueBuilder().(*array.DenseUnionBuilder)
+ builder.Append(true)
+ values.Append(0)
+
values.Child(0).(*array.MonthDayNanoIntervalBuilder).Append(arrow.MonthDayNanoInterval{Months:
1, Days: 2, Nanoseconds: 3})
+ builder.Append(true)
+ values.Append(0)
+
values.Child(0).(*array.MonthDayNanoIntervalBuilder).Append(arrow.MonthDayNanoInterval{Months:
4, Days: 5, Nanoseconds: 6})
+ input := builder.NewArray()
+ builder.Release()
+ defer input.Release()
+
+ expectedBuilder := array.NewDenseUnionBuilder(mem, unionType)
+ expectedBuilder.Append(0)
+
expectedBuilder.Child(0).(*array.MonthDayNanoIntervalBuilder).Append(arrow.MonthDayNanoInterval{Months:
1, Days: 2, Nanoseconds: 3})
+ expectedBuilder.Append(0)
+
expectedBuilder.Child(0).(*array.MonthDayNanoIntervalBuilder).Append(arrow.MonthDayNanoInterval{Months:
4, Days: 5, Nanoseconds: 6})
+ expected := expectedBuilder.NewArray()
+ expectedBuilder.Release()
+ defer expected.Release()
+
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ArrayDatum{Value: input.Data()},
+ &compute.ScalarDatum{Value: scalar.NewInt64Scalar(0)},
+ )
+ require.NoError(t, err)
+ defer result.Release()
+
+ actual := result.(*compute.ArrayDatum).MakeArray()
+ defer actual.Release()
+ assert.True(t, array.Equal(expected, actual), "expected: %s\ngot: %s",
expected, actual)
+}
+
+func TestListElementSparseUnionChild(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ unionType := arrow.SparseUnionOf(
+ []arrow.Field{
+ {Name: "number", Type: arrow.PrimitiveTypes.Int32,
Nullable: true},
+ {Name: "text", Type: arrow.BinaryTypes.String,
Nullable: true},
+ },
+ []arrow.UnionTypeCode{0, 1},
+ )
+
+ builder := array.NewListBuilder(mem, unionType)
+ values := builder.ValueBuilder().(*array.SparseUnionBuilder)
+ appendValue := func(code arrow.UnionTypeCode, number int32, text
string) {
+ values.Append(code)
+ if code == 0 {
+ values.Child(0).(*array.Int32Builder).Append(number)
+ values.Child(1).(*array.StringBuilder).AppendNull()
+ } else {
+ values.Child(0).(*array.Int32Builder).AppendNull()
+ values.Child(1).(*array.StringBuilder).Append(text)
+ }
+ }
+ builder.Append(true)
+ appendValue(0, 10, "")
+ appendValue(1, 0, "a")
+ builder.Append(true)
+ appendValue(1, 0, "b")
+ appendValue(0, 20, "")
+ input := builder.NewArray()
+ builder.Release()
+ defer input.Release()
+
+ expectedBuilder := array.NewSparseUnionBuilder(mem, unionType)
+ expectedBuilder.Append(1)
+ expectedBuilder.Child(0).(*array.Int32Builder).AppendNull()
+ expectedBuilder.Child(1).(*array.StringBuilder).Append("a")
+ expectedBuilder.Append(0)
+ expectedBuilder.Child(0).(*array.Int32Builder).Append(20)
+ expectedBuilder.Child(1).(*array.StringBuilder).AppendNull()
+ expected := expectedBuilder.NewArray()
+ expectedBuilder.Release()
+ defer expected.Release()
+
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ArrayDatum{Value: input.Data()},
+ &compute.ScalarDatum{Value: scalar.NewInt64Scalar(1)},
+ )
+ require.NoError(t, err)
+ defer result.Release()
+
+ actual := result.(*compute.ArrayDatum).MakeArray()
+ defer actual.Release()
+ assert.True(t, array.Equal(expected, actual), "expected: %s\ngot: %s",
expected, actual)
+}
+
+func TestListElementSparseUnionSlicedValuesChild(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ typeIDsBuilder := array.NewInt8Builder(mem)
+ typeIDsBuilder.AppendValues([]int8{0, 1, 0}, nil)
+ typeIDs := typeIDsBuilder.NewArray()
+ typeIDsBuilder.Release()
+ defer typeIDs.Release()
+
+ numbersBuilder := array.NewInt32Builder(mem)
+ numbersBuilder.Append(10)
+ numbersBuilder.AppendNull()
+ numbersBuilder.Append(20)
+ numbers := numbersBuilder.NewArray()
+ numbersBuilder.Release()
+ defer numbers.Release()
+
+ textBuilder := array.NewStringBuilder(mem)
+ textBuilder.AppendNull()
+ textBuilder.Append("a")
+ textBuilder.AppendNull()
+ texts := textBuilder.NewArray()
+ textBuilder.Release()
+ defer texts.Release()
+
+ union, err := array.NewSparseUnionFromArraysWithFieldCodes(
+ typeIDs,
+ []arrow.Array{numbers, texts},
+ []string{"number", "text"},
+ []arrow.UnionTypeCode{0, 1},
+ )
+ require.NoError(t, err)
+ defer union.Release()
+
+ slicedUnion := array.NewSlice(union, 1, 3)
+ defer slicedUnion.Release()
+
+ offsetsBuilder := array.NewInt32Builder(mem)
+ offsetsBuilder.AppendValues([]int32{0, 2}, nil)
+ offsets := offsetsBuilder.NewArray()
+ offsetsBuilder.Release()
+ defer offsets.Release()
+
+ data := array.NewData(
+ arrow.ListOf(union.DataType()),
+ 1,
+ []*memory.Buffer{nil, offsets.Data().Buffers()[1]},
+ []arrow.ArrayData{slicedUnion.Data()},
+ 0,
+ 0,
+ )
+ input := array.NewListData(data)
+ data.Release()
+ defer input.Release()
+
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ArrayDatum{Value: input.Data()},
+ &compute.ScalarDatum{Value: scalar.NewInt64Scalar(0)},
+ )
+ require.NoError(t, err)
+ defer result.Release()
+
+ actual := result.(*compute.ArrayDatum).MakeArray().(*array.SparseUnion)
+ defer actual.Release()
+ require.NoError(t, array.ValidateFull(actual))
+ require.Equal(t, []arrow.UnionTypeCode{1}, actual.RawTypeCodes())
+ require.True(t, actual.Field(0).IsNull(0))
+ require.Equal(t, "a", actual.Field(1).(*array.String).Value(0))
+}
+
+func TestListElementSparseUnionPreservesUnusualChildren(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ dictionaryType := &arrow.DictionaryType{
+ IndexType: arrow.PrimitiveTypes.Int8,
+ ValueType: arrow.BinaryTypes.String,
+ }
+ encodedType := arrow.RunEndEncodedOf(arrow.PrimitiveTypes.Int32,
arrow.BinaryTypes.String)
+ unionType := arrow.SparseUnionOf(
+ []arrow.Field{
+ {Name: "number", Type: arrow.PrimitiveTypes.Int32,
Nullable: true},
+ {Name: "dictionary", Type: dictionaryType, Nullable:
true},
+ {Name: "encoded", Type: encodedType, Nullable: true},
+ },
+ []arrow.UnionTypeCode{5, 42, 100},
+ )
+
+ builder := array.NewListBuilder(mem, unionType)
+ values := builder.ValueBuilder().(*array.SparseUnionBuilder)
+ numbers := values.Child(0).(*array.Int32Builder)
+ dictionaries := values.Child(1).(*array.BinaryDictionaryBuilder)
+ encoded := values.Child(2).(*array.RunEndEncodedBuilder)
+ encodedValues := encoded.ValueBuilder().(*array.StringBuilder)
+ appendValue := func(code arrow.UnionTypeCode, number int32, dictionary,
encodedValue string) {
+ values.Append(code)
+ if code == 5 {
+ numbers.Append(number)
+ } else {
+ numbers.AppendNull()
+ }
+ if code == 42 {
+ require.NoError(t,
dictionaries.AppendString(dictionary))
+ } else {
+ dictionaries.AppendNull()
+ }
+ encoded.Append(1)
+ if encodedValue == "" {
+ encodedValues.AppendNull()
+ } else {
+ encodedValues.Append(encodedValue)
+ }
+ }
+
+ builder.Append(true)
+ appendValue(5, 10, "", "")
+ appendValue(42, 0, "a", "")
+ builder.Append(true)
+ appendValue(100, 0, "", "inactive")
+ appendValue(5, 20, "", "")
+ builder.Append(false)
+ input := builder.NewArray()
+ builder.Release()
+ defer input.Release()
+
+ expectedBuilder := array.NewSparseUnionBuilder(mem, unionType)
+ expectedNumbers := expectedBuilder.Child(0).(*array.Int32Builder)
+ expectedDictionaries :=
expectedBuilder.Child(1).(*array.BinaryDictionaryBuilder)
+ expectedEncoded :=
expectedBuilder.Child(2).(*array.RunEndEncodedBuilder)
+ expectedEncodedValues :=
expectedEncoded.ValueBuilder().(*array.StringBuilder)
+ expectedBuilder.Append(42)
+ expectedNumbers.AppendNull()
+ require.NoError(t, expectedDictionaries.AppendString("a"))
+ expectedEncoded.Append(1)
+ expectedEncodedValues.AppendNull()
+ expectedBuilder.Append(5)
+ expectedNumbers.Append(20)
+ expectedDictionaries.AppendNull()
+ expectedEncoded.Append(1)
+ expectedEncodedValues.AppendNull()
+ expectedBuilder.Append(5)
+ expectedNumbers.AppendNull()
+ expectedDictionaries.AppendNull()
+ expectedEncoded.Append(1)
+ expectedEncodedValues.AppendNull()
+ expected := expectedBuilder.NewArray()
+ expectedBuilder.Release()
+ defer expected.Release()
+
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ArrayDatum{Value: input.Data()},
+ &compute.ScalarDatum{Value: scalar.NewInt64Scalar(1)},
+ )
+ require.NoError(t, err)
+ defer result.Release()
+
+ actual := result.(*compute.ArrayDatum).MakeArray()
+ defer actual.Release()
+ require.NoError(t, array.ValidateFull(actual))
+ assert.True(t, array.Equal(expected, actual), "expected: %s\ngot: %s",
expected, actual)
+
+ sliced := array.NewSlice(input, 1, 3)
+ defer sliced.Release()
+ slicedResult, err := compute.ListElement(
+ context.Background(),
+ &compute.ArrayDatum{Value: sliced.Data()},
+ &compute.ScalarDatum{Value: scalar.NewInt64Scalar(1)},
+ )
+ require.NoError(t, err)
+ defer slicedResult.Release()
+ slicedActual := slicedResult.(*compute.ArrayDatum).MakeArray()
+ defer slicedActual.Release()
+ require.NoError(t, array.ValidateFull(slicedActual))
+ slicedUnion := slicedActual.(*array.SparseUnion)
+ assert.Equal(t, []arrow.UnionTypeCode{5, 5}, slicedUnion.RawTypeCodes())
+ assert.Equal(t, int32(20), slicedUnion.Field(0).(*array.Int32).Value(0))
+ assert.True(t, slicedUnion.Field(0).IsNull(1))
+}
+
+func TestListElementNestedDictionaryWithNullParent(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ dictionaryType := &arrow.DictionaryType{
+ IndexType: arrow.PrimitiveTypes.Int8,
+ ValueType: arrow.ListOf(arrow.PrimitiveTypes.Int32),
+ }
+ dictionaryValuesBuilder := array.NewListBuilder(mem,
arrow.PrimitiveTypes.Int32)
+ dictionaryValuesBuilder.Append(true)
+ dictionaryValuesBuilder.ValueBuilder().(*array.Int32Builder).Append(7)
+ dictionaryValues := dictionaryValuesBuilder.NewArray()
+ dictionaryValuesBuilder.Release()
+ defer dictionaryValues.Release()
+
+ dictionaryIndicesBuilder := array.NewInt8Builder(mem)
+ dictionaryIndicesBuilder.Append(0)
+ dictionaryIndices := dictionaryIndicesBuilder.NewArray()
+ dictionaryIndicesBuilder.Release()
+ dictionary := array.NewDictionaryArray(dictionaryType,
dictionaryIndices, dictionaryValues)
+ dictionaryIndices.Release()
+ defer dictionary.Release()
+
+ offsetsBuilder := array.NewInt32Builder(mem)
+ offsetsBuilder.Append(0)
+ offsetsBuilder.Append(1)
+ offsetsBuilder.Append(1)
+ offsets := offsetsBuilder.NewArray()
+ offsetsBuilder.Release()
+ defer offsets.Release()
+
+ validity := memory.NewResizableBuffer(mem)
+ validity.Resize(1)
+ validity.Bytes()[0] = 0x01
+ data := array.NewData(
+ arrow.ListOf(dictionaryType),
+ 2,
+ []*memory.Buffer{validity, offsets.Data().Buffers()[1]},
+ []arrow.ArrayData{dictionary.Data()},
+ 1,
+ 0,
+ )
+ validity.Release()
+ input := array.NewListData(data)
+ data.Release()
+ defer input.Release()
+
+ expectedIndicesBuilder := array.NewInt8Builder(mem)
+ expectedIndicesBuilder.Append(0)
+ expectedIndicesBuilder.AppendNull()
+ expectedIndices := expectedIndicesBuilder.NewArray()
+ expectedIndicesBuilder.Release()
+ defer expectedIndices.Release()
+ expected := array.NewDictionaryArray(dictionaryType, expectedIndices,
dictionaryValues)
+ defer expected.Release()
+
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ArrayDatum{Value: input.Data()},
+ &compute.ScalarDatum{Value: scalar.NewInt64Scalar(0)},
+ )
+ require.NoError(t, err)
+ defer result.Release()
+
+ actual := result.(*compute.ArrayDatum).MakeArray()
+ defer actual.Release()
+ require.NoError(t, array.ValidateFull(actual))
+ assert.True(t, array.Equal(expected, actual), "expected: %s\ngot: %s",
expected, actual)
+ assert.True(t, array.Equal(dictionaryValues,
actual.(*array.Dictionary).Dictionary()))
+}
+
+func TestListElementRunEndEncodedNestedDictionaryWithNullParent(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ dictType := &arrow.DictionaryType{
+ IndexType: arrow.PrimitiveTypes.Int8,
+ ValueType: arrow.ListOf(arrow.PrimitiveTypes.Int32),
+ }
+ valuesBuilder := array.NewListBuilder(mem, arrow.PrimitiveTypes.Int32)
+ valuesBuilder.Append(true)
+ valuesBuilder.ValueBuilder().(*array.Int32Builder).Append(7)
+ values := valuesBuilder.NewArray()
+ valuesBuilder.Release()
+ defer values.Release()
+ indicesBuilder := array.NewInt8Builder(mem)
+ indicesBuilder.Append(0)
+ indices := indicesBuilder.NewArray()
+ indicesBuilder.Release()
+ defer indices.Release()
+ dictionary := array.NewDictionaryArray(dictType, indices, values)
+ defer dictionary.Release()
+
+ for _, runEndType := range []arrow.DataType{
+ arrow.PrimitiveTypes.Int16, arrow.PrimitiveTypes.Int32,
arrow.PrimitiveTypes.Int64,
+ } {
+ t.Run(runEndType.String(), func(t *testing.T) {
+ endsBuilder := array.NewBuilder(mem, runEndType)
+ require.NoError(t,
endsBuilder.AppendValueFromString("1"))
+ ends := endsBuilder.NewArray()
+ endsBuilder.Release()
+ defer ends.Release()
+ child := array.NewRunEndEncodedArray(ends, dictionary,
1, 0)
+ defer child.Release()
+
+ offsetsBuilder := array.NewInt32Builder(mem)
+ offsetsBuilder.AppendValues([]int32{0, 1, 1}, nil)
+ offsets := offsetsBuilder.NewArray()
+ offsetsBuilder.Release()
+ defer offsets.Release()
+ validity := memory.NewResizableBuffer(mem)
+ validity.Resize(1)
+ validity.Bytes()[0] = 0x01
+ data := array.NewData(arrow.ListOf(child.DataType()), 2,
+ []*memory.Buffer{validity,
offsets.Data().Buffers()[1]},
+ []arrow.ArrayData{child.Data()}, 1, 0)
+ validity.Release()
+ input := array.NewListData(data)
+ data.Release()
+ defer input.Release()
+ require.NoError(t, array.ValidateFull(input))
+
+ for _, start := range []int64{0, 1} {
+ sliced := array.NewSlice(input, start, 2)
+ defer sliced.Release()
+ result, err := compute.ListElement(
+
compute.WithAllocator(context.Background(), mem),
+ &compute.ArrayDatum{Value:
sliced.Data()},
+ &compute.ScalarDatum{Value:
scalar.NewInt64Scalar(0)},
+ )
+ require.NoError(t, err)
+ defer result.Release()
+ actual :=
result.(*compute.ArrayDatum).MakeArray()
+ defer actual.Release()
+ require.NoError(t, array.ValidateFull(actual))
+ require.Equal(t, 2-int(start), actual.Len())
+ encoded := actual.(*array.RunEndEncoded)
+ actualValues :=
encoded.Values().(*array.Dictionary)
+ assert.True(t, array.Equal(values,
actualValues.Dictionary()))
+ assert.True(t,
actualValues.IsNull(encoded.GetPhysicalIndex(actual.Len()-1)))
+ if start == 0 {
+ assert.False(t,
actualValues.IsNull(encoded.GetPhysicalIndex(0)))
+ assert.Equal(t, 0,
actualValues.GetValueIndex(encoded.GetPhysicalIndex(0)))
+ }
+ }
+ })
+ }
+}
+
+func TestListElementDenseUnionRecursiveErrorReleasesTemporaryIndices(t
*testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ sparseType := arrow.SparseUnionOf(
+ []arrow.Field{
+ {Name: "number", Type: arrow.PrimitiveTypes.Int32,
Nullable: true},
+ {Name: "text", Type: arrow.BinaryTypes.String,
Nullable: true},
+ },
+ []arrow.UnionTypeCode{0, 1},
+ )
+ structType := arrow.StructOf(arrow.Field{Name: "union", Type:
sparseType, Nullable: true})
+ denseType := arrow.DenseUnionOf(
+ []arrow.Field{{Name: "struct", Type: structType, Nullable:
true}},
+ []arrow.UnionTypeCode{0},
+ )
+
+ builder := array.NewListBuilder(mem, denseType)
+ values := builder.ValueBuilder().(*array.DenseUnionBuilder)
+ structBuilder := values.Child(0).(*array.StructBuilder)
+ unionBuilder :=
structBuilder.FieldBuilder(0).(*array.SparseUnionBuilder)
+ for i := 0; i < 2; i++ {
+ builder.Append(true)
+ values.Append(0)
+ structBuilder.Append(true)
+ unionBuilder.Append(0)
+ unionBuilder.Child(0).(*array.Int32Builder).Append(int32(i))
+ unionBuilder.Child(1).(*array.StringBuilder).AppendNull()
+ }
+ input := builder.NewArray()
+ builder.Release()
+ defer input.Release()
+
+ for i := 0; i < 20; i++ {
+ result, err := compute.ListElement(
+ context.Background(),
+ &compute.ArrayDatum{Value: input.Data()},
+ &compute.ScalarDatum{Value: scalar.NewInt64Scalar(0)},
+ )
+ if err == nil && result != nil {
+ result.Release()
+ }
+ require.Error(t, err)
+ }
+}
+
+func TestListElementValidatesScalarIndexForEmptyInputs(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ empty := listElementInput(t, mem,
arrow.ListOf(arrow.PrimitiveTypes.Int32), `[]`)
+ defer empty.Release()
+ chunked := arrow.NewChunked(empty.DataType(), []arrow.Array{empty})
+ defer chunked.Release()
+
+ tests := []struct {
+ name string
+ index scalar.Scalar
+ wantErr bool
+ }{
+ {name: "null scalar", index:
scalar.MakeNullScalar(arrow.PrimitiveTypes.Int64), wantErr: true},
+ {name: "negative scalar", index: scalar.NewInt64Scalar(-1),
wantErr: true},
+ {name: "zero scalar", index: scalar.NewInt64Scalar(0)},
+ {name: "large unsigned scalar", index:
scalar.NewUint64Scalar(^uint64(0))},
+ }
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ for _, lists := range []compute.Datum{
+ &compute.ArrayDatum{Value: empty.Data()},
+ &compute.ChunkedDatum{Value: chunked},
+ } {
+ result, err := compute.ListElement(
+ context.Background(),
+ lists,
+ &compute.ScalarDatum{Value: tc.index},
+ )
+ if err == nil && result != nil {
+ result.Release()
+ }
+ if tc.wantErr {
+ assert.ErrorIs(t, err, arrow.ErrInvalid)
+ } else {
+ assert.NoError(t, err)
+ }
+ }
+ })
+ }
+}
+
+func TestListElementNullScalarDenseUnionChildren(t *testing.T) {
+ for _, unsupported := range []arrow.DataType{
+ &arrow.Decimal32Type{Precision: 6, Scale: 2},
+ &arrow.Decimal64Type{Precision: 12, Scale: 2},
+ arrow.ListViewOf(arrow.PrimitiveTypes.Int32),
+ arrow.LargeListViewOf(arrow.PrimitiveTypes.Int32),
+ } {
+ for _, unsupportedFirst := range []bool{true, false} {
+ fields := []arrow.Field{
+ {Name: "unsupported", Type: unsupported,
Nullable: true},
+ {Name: "number", Type:
arrow.PrimitiveTypes.Int32, Nullable: true},
+ }
+ if !unsupportedFirst {
+ fields[0], fields[1] = fields[1], fields[0]
+ }
+ union := arrow.DenseUnionOf(fields,
[]arrow.UnionTypeCode{3, 7})
+ for _, elem := range []arrow.DataType{
+ union,
+ arrow.StructOf(arrow.Field{Name: "union", Type:
union, Nullable: true}),
+ &denseUnionExtensionType{ExtensionBase:
arrow.ExtensionBase{Storage: union}},
+ arrow.DenseUnionOf([]arrow.Field{{Name:
"nested", Type: union, Nullable: true}}, []arrow.UnionTypeCode{5}),
+ } {
+ for _, listType := range
[]arrow.DataType{arrow.ListOf(elem), arrow.LargeListOf(elem),
arrow.FixedSizeListOf(2, elem)} {
+
t.Run(fmt.Sprintf("%s/unsupported-first=%t", listType, unsupportedFirst),
func(t *testing.T) {
+ mem :=
memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+ ctx :=
compute.WithAllocator(context.Background(), mem)
+ list :=
scalar.MakeNullScalar(listType)
+ defer
list.(scalar.Releasable).Release()
+ result, err :=
compute.ListElement(ctx,
+
&compute.ScalarDatum{Value: list},
+
&compute.ScalarDatum{Value: scalar.NewInt64Scalar(0)})
+ if err == nil && result != nil {
+ defer result.Release()
+ }
+ if unsupportedFirst {
+ require.ErrorIs(t, err,
arrow.ErrNotImplemented)
+ } else {
+ require.NoError(t, err)
+ actual :=
result.(*compute.ScalarDatum).Value
+ require.False(t,
actual.IsValid())
+ require.NoError(t,
actual.ValidateFull())
+ }
+ })
+ }
+ }
+ }
+ }
+}
+
+func TestListElementEmptyUnionChild(t *testing.T) {
+ for _, elem := range []arrow.DataType{arrow.DenseUnionOf(nil, nil),
arrow.SparseUnionOf(nil, nil)} {
+ t.Run(elem.ID().String(), func(t *testing.T) {
+ mem :=
memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+ builder := array.NewListBuilder(mem, elem)
+ builder.AppendNull()
+ input := builder.NewArray()
+ builder.Release()
+ defer input.Release()
+ chunked := arrow.NewChunked(input.DataType(),
[]arrow.Array{input})
+ defer chunked.Release()
+ for _, lists := range []compute.Datum{
+ &compute.ArrayDatum{Value: input.Data()},
+ &compute.ChunkedDatum{Value: chunked},
+ } {
+ result, err :=
compute.ListElement(compute.WithAllocator(context.Background(), mem),
+ lists, &compute.ScalarDatum{Value:
scalar.NewInt64Scalar(0)})
+ if err == nil && result != nil {
+ result.Release()
+ }
+ require.ErrorIs(t, err, arrow.ErrNotImplemented)
+ }
+ })
+ }
+}
+
+func TestListElementNullScalarValidation(t *testing.T) {
+ for _, elem := range []arrow.DataType{
+ arrow.DenseUnionOf(nil, nil),
+ arrow.SparseUnionOf(nil, nil),
+ } {
+ t.Run(elem.ID().String(), func(t *testing.T) {
+ list := scalar.MakeNullScalar(arrow.ListOf(elem))
+ defer list.(scalar.Releasable).Release()
+ result, err := compute.ListElement(context.Background(),
+ &compute.ScalarDatum{Value: list},
+ &compute.ScalarDatum{Value:
scalar.NewInt64Scalar(0)})
+ if err == nil && result != nil {
+ result.Release()
+ }
+ require.ErrorIs(t, err, arrow.ErrNotImplemented)
+ })
+ }
+
+ list := scalar.MakeNullScalar(arrow.ListOf(arrow.PrimitiveTypes.Int32))
+ defer list.(scalar.Releasable).Release()
+ for _, index := range []scalar.Scalar{scalar.NewInt64Scalar(-1),
scalar.MakeNullScalar(arrow.PrimitiveTypes.Int64)} {
+ result, err := compute.ListElement(context.Background(),
+ &compute.ScalarDatum{Value: list},
&compute.ScalarDatum{Value: index})
+ if err == nil && result != nil {
+ result.Release()
+ }
+ require.ErrorIs(t, err, arrow.ErrInvalid)
+ }
+
+ ctx, cancel := context.WithCancel(context.Background())
+ cancel()
+ result, err := compute.ListElement(ctx,
+ &compute.ScalarDatum{Value: list}, &compute.ScalarDatum{Value:
scalar.NewInt64Scalar(0)})
+ if err == nil && result != nil {
+ result.Release()
+ }
+ require.ErrorIs(t, err, context.Canceled)
+
+ mapValue :=
scalar.MakeNullScalar(arrow.MapOf(arrow.PrimitiveTypes.Int32,
arrow.PrimitiveTypes.Int32))
+ defer mapValue.(scalar.Releasable).Release()
+ result, err = compute.ListElement(context.Background(),
+ &compute.ScalarDatum{Value: mapValue},
&compute.ScalarDatum{Value: scalar.NewInt64Scalar(0)})
+ if err == nil && result != nil {
+ result.Release()
+ }
+ require.Error(t, err)
+}
+
+func TestListElementNullStructScalarWithDenseUnionChild(t *testing.T) {
+ for _, unsupportedFirst := range []bool{true, false} {
+ t.Run(fmt.Sprintf("unsupported-first=%t", unsupportedFirst),
func(t *testing.T) {
+ mem :=
memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+ fields := []arrow.Field{
+ {Name: "unsupported", Type:
&arrow.Decimal32Type{Precision: 6, Scale: 2}, Nullable: true},
+ {Name: "number", Type:
arrow.PrimitiveTypes.Int32, Nullable: true},
+ }
+ childID := 1
+ if !unsupportedFirst {
+ fields[0], fields[1] = fields[1], fields[0]
+ childID = 0
+ }
+ codes := []arrow.UnionTypeCode{3, 7}
+ unionType := arrow.DenseUnionOf(fields, codes)
+ builder := array.NewDenseUnionBuilder(mem, unionType)
+ builder.Append(codes[childID])
+ builder.Child(childID).(*array.Int32Builder).Append(42)
+ values := builder.NewArray()
+ builder.Release()
+ defer values.Release()
+
+ structType := arrow.StructOf(arrow.Field{Name: "union",
Type: unionType, Nullable: true})
+ validity := memory.NewBufferBytes([]byte{0})
+ data := array.NewData(structType, 1,
[]*memory.Buffer{validity}, []arrow.ArrayData{values.Data()}, 1, 0)
+ validity.Release()
+ child := array.NewStructData(data)
+ data.Release()
+ defer child.Release()
+ list := scalar.NewListScalar(child)
+ defer list.Release()
+
+ result, err :=
compute.ListElement(compute.WithAllocator(context.Background(), mem),
+ &compute.ScalarDatum{Value: list},
+ &compute.ScalarDatum{Value:
scalar.NewInt64Scalar(0)})
+ if err == nil && result != nil {
+ defer result.Release()
+ }
+ if unsupportedFirst {
+ require.ErrorIs(t, err, arrow.ErrNotImplemented)
+ } else {
+ require.NoError(t, err)
+ actual := result.(*compute.ScalarDatum).Value
+ require.False(t, actual.IsValid())
+ require.NoError(t, actual.ValidateFull())
+ }
+ })
+ }
+}
+
+func BenchmarkListElement(b *testing.B) {
+ for _, size := range []int{1_000, 100_000, 1_000_000} {
+ b.Run(fmt.Sprintf("%d", size), func(b *testing.B) {
+ mem := memory.NewGoAllocator()
+ input := makeBenchmarkList(mem, size)
+ defer input.Release()
+ lists := &compute.ArrayDatum{Value: input.Data()}
+ index := &compute.ScalarDatum{Value:
scalar.NewInt64Scalar(0)}
+
+ b.ReportAllocs()
+ b.SetBytes(int64(size * 4))
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ result, err :=
compute.ListElement(context.Background(), lists, index)
+ if err != nil {
+ b.Fatal(err)
+ }
+ result.Release()
+ }
+ })
+ }
+}
+
+func BenchmarkListElementNested(b *testing.B) {
+ for _, size := range []int{1_000, 100_000} {
+ b.Run(fmt.Sprintf("%d", size), func(b *testing.B) {
+ mem := memory.NewGoAllocator()
+ input := makeBenchmarkNestedList(mem, size)
+ defer input.Release()
+ lists := &compute.ArrayDatum{Value: input.Data()}
+ index := &compute.ScalarDatum{Value:
scalar.NewInt64Scalar(0)}
+
+ b.ReportAllocs()
+ b.SetBytes(int64(size * 4))
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ result, err :=
compute.ListElement(context.Background(), lists, index)
+ if err != nil {
+ b.Fatal(err)
+ }
+ result.Release()
+ }
+ })
+ }
+}
+
+func makeBenchmarkList(mem memory.Allocator, length int) arrow.Array {
+ builder := array.NewListBuilder(mem, arrow.PrimitiveTypes.Int32)
+ values := builder.ValueBuilder().(*array.Int32Builder)
+ builder.Reserve(length)
+ values.Reserve(length)
+ for i := 0; i < length; i++ {
+ builder.Append(true)
+ values.Append(int32(i))
+ }
+ result := builder.NewArray()
+ builder.Release()
+ return result
+}
+
+func makeBenchmarkNestedList(mem memory.Allocator, length int) arrow.Array {
+ outer := array.NewListBuilder(mem,
arrow.ListOf(arrow.PrimitiveTypes.Int32))
+ inner := outer.ValueBuilder().(*array.ListBuilder)
+ values := inner.ValueBuilder().(*array.Int32Builder)
+ outer.Reserve(length)
+ inner.Reserve(length)
+ values.Reserve(length)
+ for i := 0; i < length; i++ {
+ outer.Append(true)
+ inner.Append(true)
+ values.Append(int32(i))
+ }
+ result := outer.NewArray()
+ outer.Release()
+ return result
+}
diff --git a/arrow/scalar/scalar.go b/arrow/scalar/scalar.go
index 1d8dd13a..34166df6 100644
--- a/arrow/scalar/scalar.go
+++ b/arrow/scalar/scalar.go
@@ -615,7 +615,9 @@ func GetScalar(arr arrow.Array, idx int) (Scalar, error) {
if err != nil {
return nil, err
}
- return NewExtensionScalar(storage, arr.DataType()), nil
+ result := NewExtensionScalar(storage, arr.DataType())
+ result.Valid = storage.IsValid()
+ return result, nil
case *array.FixedSizeBinary:
width := arr.DataType().(*arrow.FixedSizeBinaryType).ByteWidth
buf := scalarValueBuffer(arr.Data().Buffers()[1],
(arr.Data().Offset()+idx)*width, width)