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 80ce889e perf(compute): filter dictionary indices directly (#1352)
80ce889e is described below
commit 80ce889e01085d6709efae7db51783066a4da4eb
Author: Minh Vu <[email protected]>
AuthorDate: Tue Oct 13 14:22:00 2026 +0200
perf(compute): filter dictionary indices directly (#1352)
**What**
- Filter dictionary indices with the original mask.
- Reuse the original dictionary values.
**Why**
- Skip the temporary take indices and extra dictionary Take dispatch.
**Implementation**
- Preserve filter null options, sliced offsets, and shared dictionary
data.
- Add selection-pattern benchmarks and null/offset coverage.
---
arrow/compute/dictionary_filter_bench_test.go | 149 ++++++++++++
arrow/compute/dictionary_filter_test.go | 325 ++++++++++++++++++++++++++
arrow/compute/selection.go | 46 ++--
3 files changed, 499 insertions(+), 21 deletions(-)
diff --git a/arrow/compute/dictionary_filter_bench_test.go
b/arrow/compute/dictionary_filter_bench_test.go
new file mode 100644
index 00000000..40adab31
--- /dev/null
+++ b/arrow/compute/dictionary_filter_bench_test.go
@@ -0,0 +1,149 @@
+// 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.24
+
+package compute_test
+
+import (
+ "context"
+ "fmt"
+ "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/memory"
+)
+
+var dictionaryFilterBenchmarkOutputLength int
+
+func BenchmarkFilterDictionaryIndices(b *testing.B) {
+ patterns := []struct {
+ name string
+ selected func(int) bool
+ indexNull func(int) bool
+ filterNull func(int) bool
+ nullSelect compute.NullSelectionBehavior
+ }{
+ {name: "random10", selected: func(i int) bool { return
dictionaryFilterSelect(i, 10) }},
+ {name: "random50", selected: func(i int) bool { return
dictionaryFilterSelect(i, 50) }},
+ {name: "random90", selected: func(i int) bool { return
dictionaryFilterSelect(i, 90) }},
+ {name: "alternating", selected: func(i int) bool { return i%2
== 0 }},
+ {name: "clustered50", selected: func(i int) bool { return
(i/4096)%2 == 0 }},
+ {
+ name: "nullable-indices-random50",
+ selected: func(i int) bool { return
dictionaryFilterSelect(i, 50) },
+ indexNull: func(i int) bool { return i%11 == 0 },
+ nullSelect: compute.SelectionDropNulls,
+ },
+ {
+ name: "nullable-random50",
+ selected: func(i int) bool { return
dictionaryFilterSelect(i, 50) },
+ filterNull: func(i int) bool { return i%11 == 0 },
+ nullSelect: compute.SelectionDropNulls,
+ },
+ {
+ name: "nullable-random50-emit",
+ selected: func(i int) bool { return
dictionaryFilterSelect(i, 50) },
+ filterNull: func(i int) bool { return i%11 == 0 },
+ nullSelect: compute.SelectionEmitNulls,
+ },
+ }
+
+ for _, size := range []int{64, 1 << 16, 1 << 20} {
+ for _, pattern := range patterns {
+ b.Run(fmt.Sprintf("size=%d/%s", size, pattern.name),
func(b *testing.B) {
+ values, filter :=
makeDictionaryFilterBenchmarkInput(
+ b, size, pattern.selected,
pattern.indexNull, pattern.filterNull,
+ )
+ defer values.Release()
+ defer filter.Release()
+ ctx := context.Background()
+
+ b.ReportAllocs()
+ b.SetBytes(int64(size * 4))
+ for b.Loop() {
+ result, err := compute.FilterArray(ctx,
values, filter, compute.FilterOptions{NullSelection: pattern.nullSelect})
+ if err != nil {
+ b.Fatal(err)
+ }
+ dictionaryFilterBenchmarkOutputLength =
result.Len()
+ result.Release()
+ }
+ })
+ }
+ }
+}
+
+func makeDictionaryFilterBenchmarkInput(
+ b *testing.B,
+ size int,
+ selected func(int) bool,
+ indexNull func(int) bool,
+ filterNull func(int) bool,
+) (arrow.Array, arrow.Array) {
+ b.Helper()
+ mem := memory.DefaultAllocator
+
+ indicesBuilder := array.NewInt32Builder(mem)
+ indicesBuilder.Reserve(size)
+ for i := 0; i < size; i++ {
+ if indexNull != nil && indexNull(i) {
+ indicesBuilder.AppendNull()
+ continue
+ }
+ indicesBuilder.Append(int32(i % 256))
+ }
+ indices := indicesBuilder.NewArray()
+ indicesBuilder.Release()
+
+ dictionaryBuilder := array.NewInt64Builder(mem)
+ dictionaryBuilder.Reserve(256)
+ for i := 0; i < 256; i++ {
+ dictionaryBuilder.Append(int64(i))
+ }
+ dictionary := dictionaryBuilder.NewArray()
+ dictionaryBuilder.Release()
+
+ dictType := &arrow.DictionaryType{IndexType:
arrow.PrimitiveTypes.Int32, ValueType: arrow.PrimitiveTypes.Int64}
+ values := array.NewDictionaryArray(dictType, indices, dictionary)
+ indices.Release()
+ dictionary.Release()
+
+ filterBuilder := array.NewBooleanBuilder(mem)
+ filterBuilder.Reserve(size)
+ for i := 0; i < size; i++ {
+ if filterNull != nil && filterNull(i) {
+ filterBuilder.AppendNull()
+ continue
+ }
+ filterBuilder.Append(selected(i))
+ }
+ filter := filterBuilder.NewArray()
+ filterBuilder.Release()
+
+ return values, filter
+}
+
+func dictionaryFilterSelect(i, percent int) bool {
+ x := uint64(i) + 0x9e3779b97f4a7c15
+ x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9
+ x = (x ^ (x >> 27)) * 0x94d049bb133111eb
+ x ^= x >> 31
+ return x%100 < uint64(percent)
+}
diff --git a/arrow/compute/dictionary_filter_test.go
b/arrow/compute/dictionary_filter_test.go
new file mode 100644
index 00000000..47293d8d
--- /dev/null
+++ b/arrow/compute/dictionary_filter_test.go
@@ -0,0 +1,325 @@
+// 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"
+ "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/memory"
+ "github.com/stretchr/testify/require"
+)
+
+func TestDictionaryFilterDirectIndices(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+ ctx := compute.WithAllocator(context.Background(), mem)
+
+ dictType := &arrow.DictionaryType{
+ IndexType: arrow.PrimitiveTypes.Int8,
+ ValueType: arrow.BinaryTypes.String,
+ }
+ input, err := array.DictArrayFromJSON(mem, dictType, `[2, 0, 1, 0, 2,
1]`, `["a", null, "c"]`)
+ require.NoError(t, err)
+ defer input.Release()
+
+ cases := []struct {
+ name string
+ filter string
+ nullSelect compute.NullSelectionBehavior
+ want string
+ }{
+ {
+ name: "drop null filter values",
+ filter: `[true, true, false, null, true, false]`,
+ nullSelect: compute.SelectionDropNulls,
+ want: `[2, 0, 2]`,
+ },
+ {
+ name: "emit null filter values",
+ filter: `[true, true, false, null, true, null]`,
+ nullSelect: compute.SelectionEmitNulls,
+ want: `[2, 0, null, 2, null]`,
+ },
+ {
+ name: "all selected",
+ filter: `[true, true, true, true, true, true]`,
+ nullSelect: compute.SelectionDropNulls,
+ want: `[2, 0, 1, 0, 2, 1]`,
+ },
+ {
+ name: "none selected",
+ filter: `[false, false, false, false, false,
false]`,
+ nullSelect: compute.SelectionDropNulls,
+ want: `[]`,
+ },
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.name, func(t *testing.T) {
+ filter, _, err := array.FromJSON(mem,
arrow.FixedWidthTypes.Boolean, strings.NewReader(tc.filter))
+ require.NoError(t, err)
+ defer filter.Release()
+
+ got, err := compute.FilterArray(ctx, input, filter,
compute.FilterOptions{NullSelection: tc.nullSelect})
+ require.NoError(t, err)
+ defer got.Release()
+
+ gotDict, ok := got.(*array.Dictionary)
+ require.Truef(t, ok, "expected *array.Dictionary, got
%T", got)
+ require.True(t, arrow.TypeEqual(dictType,
gotDict.DataType()))
+
+ wantIndices, _, err := array.FromJSON(mem,
dictType.IndexType, strings.NewReader(tc.want))
+ require.NoError(t, err)
+ defer wantIndices.Release()
+ require.True(t, array.Equal(wantIndices,
gotDict.Indices()))
+
+ require.True(t,
array.Equal(input.(*array.Dictionary).Dictionary(), gotDict.Dictionary()))
+ for i, buf := range
input.(*array.Dictionary).Dictionary().Data().Buffers() {
+ require.Same(t, buf,
gotDict.Dictionary().Data().Buffers()[i])
+ }
+ require.Equal(t,
input.(*array.Dictionary).Dictionary().Len(), gotDict.Dictionary().Len())
+ })
+ }
+
+ allSelected := mustBoolArray(t, mem, `[true, true, true, true, true,
true]`)
+ defer allSelected.Release()
+ filtered, err := compute.FilterArray(ctx, input, allSelected,
*compute.DefaultFilterOptions())
+ require.NoError(t, err)
+ defer filtered.Release()
+ filteredDict := filtered.(*array.Dictionary)
+ require.False(t, filteredDict.Indices().IsNull(2))
+ require.True(t, filteredDict.Dictionary().IsNull(1))
+}
+
+func TestDictionaryFilterSlicedInputs(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+ ctx := compute.WithAllocator(context.Background(), mem)
+
+ dictType := &arrow.DictionaryType{
+ IndexType: arrow.PrimitiveTypes.Int8,
+ ValueType: arrow.BinaryTypes.String,
+ }
+ baseInput, err := array.DictArrayFromJSON(mem, dictType, `[0, 1, 2, 0,
0, 1, 2, 0]`, `["a", null, "c"]`)
+ require.NoError(t, err)
+ defer baseInput.Release()
+ input := array.NewSlice(baseInput, 1, 7).(*array.Dictionary)
+ defer input.Release()
+
+ baseFilter := mustBoolArray(t, mem, `[false, false, true, true, false,
true, false, true, true, false]`)
+ defer baseFilter.Release()
+ filter := array.NewSlice(baseFilter, 2, 8)
+ defer filter.Release()
+
+ got, err := compute.FilterArray(ctx, input, filter,
*compute.DefaultFilterOptions())
+ require.NoError(t, err)
+ defer got.Release()
+
+ gotDict := got.(*array.Dictionary)
+ wantIndices, _, err := array.FromJSON(mem, dictType.IndexType,
strings.NewReader(`[1, 2, 0, 2]`))
+ require.NoError(t, err)
+ defer wantIndices.Release()
+ require.True(t, array.Equal(wantIndices, gotDict.Indices()))
+ require.True(t, array.Equal(input.Dictionary(), gotDict.Dictionary()))
+ for i, buf := range input.Dictionary().Data().Buffers() {
+ require.Same(t, buf, gotDict.Dictionary().Data().Buffers()[i])
+ }
+}
+
+func TestDictionaryFilterRetainsNestedDictionary(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+ ctx := compute.WithAllocator(context.Background(), mem)
+ dictType := &arrow.DictionaryType{
+ IndexType: arrow.PrimitiveTypes.Int16,
+ ValueType: arrow.ListOf(arrow.PrimitiveTypes.Int32),
+ Ordered: true,
+ }
+
+ // Release the inputs before reading the result to check ownership of
both
+ // the sliced dictionary buffers and its nested values.
+ var got arrow.Array
+ func() {
+ base, _, err := array.FromJSON(mem, dictType.ValueType,
strings.NewReader(`[[-1], [10, 20], null, [], [30], [99]]`))
+ require.NoError(t, err)
+ defer base.Release()
+ dictionary := array.NewSlice(base, 1, 5)
+ defer dictionary.Release()
+ indices, _, err := array.FromJSON(mem, dictType.IndexType,
strings.NewReader(`[0, 1, 2, 3, 0]`))
+ require.NoError(t, err)
+ defer indices.Release()
+ input := array.NewDictionaryArray(dictType, indices, dictionary)
+ defer input.Release()
+ filter := mustBoolArray(t, mem, `[false, true, true, null,
true]`)
+ defer filter.Release()
+
+ got, err = compute.FilterArray(ctx, input, filter,
compute.FilterOptions{NullSelection: compute.SelectionEmitNulls})
+ require.NoError(t, err)
+ }()
+ defer got.Release()
+
+ want, err := array.DictArrayFromJSON(mem, dictType, `[1, 2, null, 0]`,
`[[10, 20], null, [], [30]]`)
+ require.NoError(t, err)
+ defer want.Release()
+ require.True(t, array.Equal(want, got))
+ require.Equal(t, 1, got.NullN())
+ require.Equal(t, 1,
got.(*array.Dictionary).Dictionary().Data().Offset())
+}
+
+func mustBoolArray(t *testing.T, mem memory.Allocator, json string)
arrow.Array {
+ t.Helper()
+ arr, _, err := array.FromJSON(mem, arrow.FixedWidthTypes.Boolean,
strings.NewReader(json))
+ require.NoError(t, err)
+ return arr
+}
+
+func TestDictionaryFilterIndexTypesAndChunking(t *testing.T) {
+ for _, indexType := range []arrow.DataType{arrow.PrimitiveTypes.Int8,
arrow.PrimitiveTypes.Uint8, arrow.PrimitiveTypes.Int16,
arrow.PrimitiveTypes.Uint16, arrow.PrimitiveTypes.Int32,
arrow.PrimitiveTypes.Uint32, arrow.PrimitiveTypes.Int64,
arrow.PrimitiveTypes.Uint64} {
+ t.Run(indexType.String(), func(t *testing.T) {
+ mem :=
memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+ dt := &arrow.DictionaryType{IndexType: indexType,
ValueType: arrow.BinaryTypes.String, Ordered: true}
+ base, err := array.DictArrayFromJSON(mem, dt, `[0, 2,
null, 1, 0, 2, 1, 0]`, `["a", null, "c"]`)
+ require.NoError(t, err)
+ defer base.Release()
+ input := array.NewSlice(base, 1, 7)
+ defer input.Release()
+ baseFilter := mustBoolArray(t, mem, `[false, true,
true, null, false, true, true, false]`)
+ defer baseFilter.Release()
+ filter := array.NewSlice(baseFilter, 1, 7)
+ defer filter.Release()
+ execCtx := compute.DefaultExecCtx()
+ execCtx.ChunkSize = 2
+ ctx :=
compute.SetExecCtx(compute.WithAllocator(context.Background(), mem), execCtx)
+ for _, tc := range []struct {
+ mode compute.NullSelectionBehavior
+ want string
+ }{
+ {compute.SelectionDropNulls, `[2, null, 2, 1]`},
+ {compute.SelectionEmitNulls, `[2, null, null,
2, 1]`},
+ } {
+ valuesDatum, filterDatum :=
compute.NewDatum(input), compute.NewDatum(filter)
+ defer valuesDatum.Release()
+ defer filterDatum.Release()
+ result, err := compute.Filter(ctx, valuesDatum,
filterDatum, compute.FilterOptions{NullSelection: tc.mode})
+ require.NoError(t, err)
+ defer result.Release()
+ require.IsType(t, &compute.ChunkedDatum{},
result)
+ got, err :=
array.Concatenate(result.(*compute.ChunkedDatum).Chunks(), mem)
+ require.NoError(t, err)
+ defer got.Release()
+ dict := got.(*array.Dictionary)
+ want, _, err := array.FromJSON(mem, indexType,
strings.NewReader(tc.want))
+ require.NoError(t, err)
+ defer want.Release()
+ require.True(t, array.Equal(want,
dict.Indices()))
+ require.True(t, arrow.TypeEqual(dt,
dict.DataType()))
+ require.True(t,
array.Equal(base.(*array.Dictionary).Dictionary(), dict.Dictionary()))
+ }
+ })
+ }
+}
+
+func TestDictionaryFilterNullsAcrossExecutionChunks(t *testing.T) {
+ cases := []struct {
+ name string
+ indicesJSON string
+ maskValid []bool
+ wantDrop string
+ wantEmit string
+ }{
+ {
+ name: "nullable indices",
+ indicesJSON: `[0, null, 1]`,
+ wantDrop: `[0, null, 1]`,
+ wantEmit: `[0, null, 1]`,
+ },
+ {
+ name: "nullable filter",
+ indicesJSON: `[0, 1, 2]`,
+ maskValid: []bool{true, false, true},
+ wantDrop: `[0, 2]`,
+ wantEmit: `[0, null, 2]`,
+ },
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.name, func(t *testing.T) {
+ mem :=
memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ dt := &arrow.DictionaryType{IndexType:
arrow.PrimitiveTypes.Int8, ValueType: arrow.BinaryTypes.String}
+ input, err := array.DictArrayFromJSON(mem, dt,
tc.indicesJSON, `["a", "b", "c"]`)
+ require.NoError(t, err)
+ defer input.Release()
+
+ // Keep the value bit set at the null position.
DropNulls must
+ // consult the validity bitmap rather than that value
bit.
+ builder := array.NewBooleanBuilder(mem)
+ builder.AppendValues([]bool{true, true, true},
tc.maskValid)
+ mask := builder.NewBooleanArray()
+ builder.Release()
+ defer mask.Release()
+ if tc.maskValid != nil {
+ require.True(t, mask.IsNull(1))
+ require.True(t, mask.Value(1))
+ }
+
+ execCtx := compute.DefaultExecCtx()
+ execCtx.ChunkSize = 1
+ ctx :=
compute.SetExecCtx(compute.WithAllocator(context.Background(), mem), execCtx)
+
+ for _, mode := range []struct {
+ name string
+ opt compute.NullSelectionBehavior
+ want string
+ }{
+ {"drop nulls", compute.SelectionDropNulls,
tc.wantDrop},
+ {"emit nulls", compute.SelectionEmitNulls,
tc.wantEmit},
+ } {
+ t.Run(mode.name, func(t *testing.T) {
+ valuesDatum := compute.NewDatum(input)
+ defer valuesDatum.Release()
+ maskDatum := compute.NewDatum(mask)
+ defer maskDatum.Release()
+
+ result, err := compute.Filter(ctx,
valuesDatum, maskDatum, compute.FilterOptions{NullSelection: mode.opt})
+ require.NoError(t, err)
+ defer result.Release()
+ require.IsType(t,
&compute.ChunkedDatum{}, result)
+
+ got, err :=
array.Concatenate(result.(*compute.ChunkedDatum).Chunks(), mem)
+ require.NoError(t, err)
+ defer got.Release()
+
+ want, _, err := array.FromJSON(mem,
dt.IndexType, strings.NewReader(mode.want))
+ require.NoError(t, err)
+ defer want.Release()
+ require.True(t, array.Equal(want,
got.(*array.Dictionary).Indices()))
+ })
+ }
+ })
+ }
+}
diff --git a/arrow/compute/selection.go b/arrow/compute/selection.go
index 6b748daa..fa17cab1 100644
--- a/arrow/compute/selection.go
+++ b/arrow/compute/selection.go
@@ -507,8 +507,7 @@ func extensionTakeImpl(ctx *exec.KernelCtx, batch
*exec.ExecSpan, out *exec.Exec
return nil
}
-func structFilter(ctx *exec.KernelCtx, batch *exec.ExecSpan, out
*exec.ExecResult) error {
- // transform filter to selection indices and use take
+func filterWithTake(ctx *exec.KernelCtx, batch *exec.ExecSpan, out
*exec.ExecResult) error {
indices, err := kernels.GetTakeIndices(exec.GetAllocator(ctx.Ctx),
&batch.Values[1].Array,
ctx.State.(kernels.FilterState).NullSelection)
if err != nil {
@@ -540,32 +539,37 @@ func structFilter(ctx *exec.KernelCtx, batch
*exec.ExecSpan, out *exec.ExecResul
// The shared dictionary is reused as-is and never compacted, so the result
may retain values
// that are no longer referenced by any index.
func dictionaryFilter(ctx *exec.KernelCtx, batch *exec.ExecSpan, out
*exec.ExecResult) error {
- // convert the filter (boolean array) to indices to take from the
dictionary array.
- indices, err := kernels.GetTakeIndices(exec.GetAllocator(ctx.Ctx),
- &batch.Values[1].Array,
ctx.State.(kernels.FilterState).NullSelection)
- if err != nil {
- return err
+ filterState := ctx.State.(kernels.FilterState)
+ // UpdateNullCount caches its result on the ArraySpan. Count on copies
+ // so a null-free execution slice cannot hide nulls in later slices.
+ valuesSpan := batch.Values[0].Array
+ filterSpan := batch.Values[1].Array
+ if valuesSpan.UpdateNullCount() > 0 ||
+ (filterState.NullSelection == kernels.DropNulls &&
+ filterSpan.UpdateNullCount() > 0) {
+ // PrimitiveFilter cannot use its SIMD path when the values
have nulls,
+ // or when DropNulls has to combine filter validity with its
values.
+ // Keep the take-index path for these cases to avoid
regressions.
+ return filterWithTake(ctx, batch, out)
}
- defer indices.Release()
- filter := NewDatum(indices)
- defer filter.Release()
-
- valData := batch.Values[0].Array.MakeData()
- defer valData.Release()
+ dictArr := batch.Values[0].Array.MakeArray().(*array.Dictionary)
+ defer dictArr.Release()
- vals := NewDatum(valData)
- defer vals.Release()
+ selection := batch.Values[1].Array.MakeArray()
+ defer selection.Release()
- // run 'take' on the dictionary array, which will call dictionaryTake.
- // we know the bounds are good because the indices were just created by
GetTakeIndices
- result, err := Take(ctx.Ctx, kernels.TakeOptions{BoundsCheck: false},
vals, filter)
+ filteredIndices, err := FilterArray(ctx.Ctx, dictArr.Indices(),
selection, filterState)
if err != nil {
return err
}
- defer result.Release()
+ defer filteredIndices.Release()
- out.TakeOwnership(result.(*ArrayDatum).Value)
+ // Retain the result buffers directly without constructing a temporary
dictionary array.
+ out.TakeOwnership(filteredIndices.Data())
+ out.Type = dictArr.DataType()
+ out.ResizeChildren(1)
+ out.Dictionary().TakeOwnership(dictArr.Data().Dictionary())
return nil
}
@@ -646,7 +650,7 @@ func RegisterVectorSelection(reg FunctionRegistry) {
{In: exec.NewIDInput(arrow.DENSE_UNION), Exec:
denseUnionImpl(kernels.FilterExec(kernels.DenseUnionImpl))},
{In: exec.NewIDInput(arrow.DICTIONARY), Exec: dictionaryFilter},
{In: exec.NewIDInput(arrow.EXTENSION), Exec:
extensionFilterImpl},
- {In: exec.NewIDInput(arrow.STRUCT), Exec: structFilter},
+ {In: exec.NewIDInput(arrow.STRUCT), Exec: filterWithTake},
}...)
takeKernels = append(takeKernels, []kernels.SelectionKernelData{