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/iceberg-go.git


The following commit(s) were added to refs/heads/main by this push:
     new b1de1bd57 fix(table): match equality deletes on dictionary columns 
(#2427)
b1de1bd57 is described below

commit b1de1bd578c895faa16bdd28bfbfc0fdfa776ca9
Author: Harrison Crosse <[email protected]>
AuthorDate: Wed Nov 4 12:31:22 2026 -0500

    fix(table): match equality deletes on dictionary columns (#2427)
    
    Encode dictionary columns by their values so equality keys match plain 
columns
    on both the data side and the delete file side.
    
    Signed-off-by: Harrison Crosse <[email protected]>
---
 table/equality_delete_reader.go               |   6 ++
 table/equality_delete_reader_internal_test.go | 106 ++++++++++++++++++++++++++
 2 files changed, 112 insertions(+)

diff --git a/table/equality_delete_reader.go b/table/equality_delete_reader.go
index 55b7e9ac9..f1d051df2 100644
--- a/table/equality_delete_reader.go
+++ b/table/equality_delete_reader.go
@@ -1327,6 +1327,12 @@ func makeColEncoder(arr arrow.Array) colEncoder {
                        buf.WriteByte(1)
                        buf.Write(a.Value(row))
                })
+       case *array.Dictionary:
+               valueEncoder := makeColEncoder(a.Dictionary())
+
+               return withNullGuard(a, func(buf *bytes.Buffer, row int) {
+                       valueEncoder(buf, a.GetValueIndex(row))
+               })
        default:
                return withNullGuard(arr, func(buf *bytes.Buffer, row int) {
                        encodeArrowValue(buf, arr, row)
diff --git a/table/equality_delete_reader_internal_test.go 
b/table/equality_delete_reader_internal_test.go
index d5a12ae98..784ed0726 100644
--- a/table/equality_delete_reader_internal_test.go
+++ b/table/equality_delete_reader_internal_test.go
@@ -160,6 +160,112 @@ func TestMakeArrowFieldEncoderHonorsNullStructParents(t 
*testing.T) {
        assert.Equal(t, want.Bytes(), got.Bytes())
 }
 
+// buildEqualityDictionaryTestRecord returns id [1..5], a dictionary value
+// column [apple, bob, null index, apple, null dictionary value] and k [7, 7, 
7, 8, 7].
+func buildEqualityDictionaryTestRecord(t *testing.T, mem memory.Allocator, 
indexType, valueType arrow.DataType) arrow.RecordBatch {
+       t.Helper()
+
+       valueBuilder := array.NewBuilder(mem, valueType)
+       defer valueBuilder.Release()
+       appendEqualityBytesTestValue(t, valueBuilder, "apple", true)
+       appendEqualityBytesTestValue(t, valueBuilder, "bob", true)
+       appendEqualityBytesTestValue(t, valueBuilder, "", false)
+       appendEqualityBytesTestValue(t, valueBuilder, "unused", true)
+       dictionary := valueBuilder.NewArray()
+       defer dictionary.Release()
+
+       indices, _, err := array.FromJSON(mem, indexType, 
strings.NewReader("[0, 1, null, 0, 2]"))
+       require.NoError(t, err)
+       defer indices.Release()
+
+       dictType := &arrow.DictionaryType{IndexType: indexType, ValueType: 
valueType}
+       values := array.NewDictionaryArray(dictType, indices, dictionary)
+       defer values.Release()
+
+       ids, _, err := array.FromJSON(mem, arrow.PrimitiveTypes.Int64, 
strings.NewReader("[1, 2, 3, 4, 5]"))
+       require.NoError(t, err)
+       defer ids.Release()
+       ks, _, err := array.FromJSON(mem, arrow.PrimitiveTypes.Int32, 
strings.NewReader("[7, 7, 7, 8, 7]"))
+       require.NoError(t, err)
+       defer ks.Release()
+
+       schema := arrow.NewSchema([]arrow.Field{
+               {Name: "id", Type: arrow.PrimitiveTypes.Int64},
+               {Name: "value", Type: dictType, Nullable: true},
+               {Name: "k", Type: arrow.PrimitiveTypes.Int32},
+       }, nil)
+
+       return array.NewRecordBatch(schema, []arrow.Array{ids, values, ks}, 5)
+}
+
+func TestProcessEqualityDeletesDictionary(t *testing.T) {
+       shapes := []struct {
+               name      string
+               indexType arrow.DataType
+               valueType arrow.DataType
+               iceType   iceberg.Type
+       }{
+               {"int8/binary", arrow.PrimitiveTypes.Int8, 
arrow.BinaryTypes.Binary, iceberg.PrimitiveTypes.Binary},
+               {"uint16/large_binary", arrow.PrimitiveTypes.Uint16, 
arrow.BinaryTypes.LargeBinary, iceberg.PrimitiveTypes.Binary},
+               {"int32/binary", arrow.PrimitiveTypes.Int32, 
arrow.BinaryTypes.Binary, iceberg.PrimitiveTypes.Binary},
+               {"int64/string", arrow.PrimitiveTypes.Int64, 
arrow.BinaryTypes.String, iceberg.PrimitiveTypes.String},
+               {"int32/large_string", arrow.PrimitiveTypes.Int32, 
arrow.BinaryTypes.LargeString, iceberg.PrimitiveTypes.String},
+       }
+       cases := []struct {
+               name     string
+               fieldIDs []int
+               colNames []string
+               keys     set[string]
+               wantIDs  []int64
+       }{
+               {
+                       name: "value", fieldIDs: []int{2}, colNames: 
[]string{"value"},
+                       keys:    set[string]{"\x01\x00\x00\x00\x05apple": {}},
+                       wantIDs: []int64{2, 3, 5},
+               },
+               {
+                       name: "null", fieldIDs: []int{2}, colNames: 
[]string{"value"},
+                       keys:    set[string]{"\x00": {}},
+                       wantIDs: []int64{1, 2, 4},
+               },
+               {
+                       name: "multi_column", fieldIDs: []int{2, 3}, colNames: 
[]string{"value", "k"},
+                       keys: set[string]{
+                               
"\x01\x00\x00\x00\x05apple\x01\x00\x00\x00\x07": {},
+                               "\x00\x01\x00\x00\x00\x07":                     
 {},
+                       },
+                       wantIDs: []int64{2, 4},
+               },
+       }
+       for _, shape := range shapes {
+               fileSchema := iceberg.NewSchema(0,
+                       iceberg.NestedField{ID: 1, Name: "id", Type: 
iceberg.PrimitiveTypes.Int64, Required: true},
+                       iceberg.NestedField{ID: 2, Name: "value", Type: 
shape.iceType},
+                       iceberg.NestedField{ID: 3, Name: "k", Type: 
iceberg.PrimitiveTypes.Int32, Required: true},
+               )
+               for _, tc := range cases {
+                       t.Run(shape.name+"/"+tc.name, func(t *testing.T) {
+                               mem := 
memory.NewCheckedAllocator(memory.DefaultAllocator)
+                               defer mem.AssertSize(t, 0)
+                               ctx := compute.WithAllocator(t.Context(), mem)
+                               record := buildEqualityDictionaryTestRecord(t, 
mem, shape.indexType, shape.valueType)
+                               defer record.Release()
+
+                               deleteSet := &equalityDeleteSet{keys: tc.keys, 
fieldIDs: tc.fieldIDs, colNames: tc.colNames}
+                               process, err := 
processEqualityDeletesColumnarForFile(ctx,
+                                       []*equalityDeleteSet{deleteSet}, 
fileSchema, "data.parquet")
+                               require.NoError(t, err)
+
+                               record.Retain()
+                               got, err := process(record)
+                               require.NoError(t, err)
+                               defer got.Release()
+                               assert.Equal(t, tc.wantIDs, 
got.Column(0).(*array.Int64).Int64Values())
+                       })
+               }
+       }
+}
+
 type encoderArrayShape int
 
 const (

Reply via email to