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{

Reply via email to