This is an automated email from the ASF dual-hosted git repository.

laskoviymishka 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 b5bb5b547 perf(table): avoid redundant filter cache schema traversal 
(#2028)
b5bb5b547 is described below

commit b5bb5b547d48a09c564be649807b6cde4bc711a2
Author: Minh Vu <[email protected]>
AuthorDate: Wed Sep 23 19:48:23 2026 +0200

    perf(table): avoid redundant filter cache schema traversal (#2028)
    
    * perf(table): avoid redundant filter cache schema traversal
    
    Signed-off-by: Minh Vu <[email protected]>
    
    * test(table): benchmark physical filter schema keys
    
    Signed-off-by: Minh Vu <[email protected]>
    
    * perf(table): compact physical schema key types
    
    Signed-off-by: Minh Vu <[email protected]>
    
    * fix(table): preserve unexpected schema key panics
    
    Recover only explicitly marked invalid inputs, retain typed-nil rejection,
    and cover compact type-tag uniqueness and panic propagation.
    
    Signed-off-by: Minh Vu <[email protected]>
    
    ---------
    
    Signed-off-by: Minh Vu <[email protected]>
---
 table/arrow_filter_plan.go          | 125 +++++++++++++++++++++------------
 table/arrow_filter_plan_key_test.go |  91 ++++++++++++++++++++++++
 table/arrow_filter_plan_test.go     | 135 ++++++++++++++++++++++++++++++++++++
 table/arrow_scanner_bench_test.go   |  91 ++++++++++++++++++++++++
 4 files changed, 399 insertions(+), 43 deletions(-)

diff --git a/table/arrow_filter_plan.go b/table/arrow_filter_plan.go
index e839ce820..0c4227c82 100644
--- a/table/arrow_filter_plan.go
+++ b/table/arrow_filter_plan.go
@@ -20,6 +20,7 @@ package table
 import (
        "context"
        "fmt"
+       "reflect"
        "strconv"
        "strings"
        "sync"
@@ -27,6 +28,7 @@ import (
        "github.com/apache/arrow-go/v18/arrow/compute/exprs"
        "github.com/apache/arrow-go/v18/parquet/metadata"
        "github.com/apache/iceberg-go"
+       iceinternal "github.com/apache/iceberg-go/internal"
        tblutils "github.com/apache/iceberg-go/table/internal"
        "github.com/apache/iceberg-go/table/substrait"
        "github.com/substrait-io/substrait-go/v8/expr"
@@ -160,51 +162,33 @@ func (as *arrowScan) cachedFileFilterPlans(fileSchema 
*iceberg.Schema, includePr
        return plans, nil
 }
 
-func physicalSchemaKey(fileSchema *iceberg.Schema) (string, error) {
-       // The key includes field names, IDs, requiredness, and type details. 
Schema
-       // IDs, docs, and defaults do not affect filter translation or 
compilation.
-       visitor := &physicalSchemaKeyVisitor{}
-       _, err := iceberg.Visit(fileSchema, visitor)
-       if err != nil {
-               return "", fmt.Errorf("%w: cannot encode physical schema key: 
%v", iceberg.ErrInvalidSchema, err)
-       }
-
-       return visitor.builder.String(), nil
-}
+// physicalSchemaKeyPanic marks invalid input, not unexpected implementation 
panics.
+type physicalSchemaKeyPanic string
 
-type physicalSchemaKeyVisitor struct {
-       builder strings.Builder
-       depth   int
-}
-
-func (v *physicalSchemaKeyVisitor) Schema(_ *iceberg.Schema, _ struct{}) 
struct{} { return struct{}{} }
-func (v *physicalSchemaKeyVisitor) Struct(_ iceberg.StructType, _ []struct{}) 
struct{} {
-       return struct{}{}
-}
+func physicalSchemaKey(fileSchema *iceberg.Schema) (key string, err error) {
+       defer func() {
+               if r := recover(); r != nil {
+                       invalid, ok := r.(physicalSchemaKeyPanic)
+                       if !ok {
+                               panic(r)
+                       }
+                       err = fmt.Errorf("%w: cannot encode physical schema 
key: %s", iceberg.ErrInvalidSchema, invalid)
+               }
+       }()
 
-func (v *physicalSchemaKeyVisitor) Field(_ iceberg.NestedField, _ struct{}) 
struct{} {
-       return struct{}{}
-}
-func (v *physicalSchemaKeyVisitor) List(_ iceberg.ListType, _ struct{}) 
struct{}  { return struct{}{} }
-func (v *physicalSchemaKeyVisitor) Map(_ iceberg.MapType, _, _ struct{}) 
struct{} { return struct{}{} }
-func (v *physicalSchemaKeyVisitor) Primitive(_ iceberg.PrimitiveType) struct{} 
   { return struct{}{} }
-func (v *physicalSchemaKeyVisitor) Variant(_ iceberg.VariantType) struct{}     
   { return struct{}{} }
-
-func (v *physicalSchemaKeyVisitor) BeforeField(field iceberg.NestedField) {
-       if v.depth == 0 {
-               writePhysicalFieldKey(&v.builder, field)
+       if fileSchema == nil {
+               panic(physicalSchemaKeyPanic("nil schema"))
        }
-       v.depth++
-}
 
-func (v *physicalSchemaKeyVisitor) AfterField(_ iceberg.NestedField) { 
v.depth-- }
+       // The key includes field names, IDs, requiredness, and type details. 
Schema
+       // IDs, docs, and defaults do not affect filter translation or 
compilation.
+       var builder strings.Builder
+       for _, field := range fileSchema.FieldsRef(iceinternal.SchemaRef{}) {
+               writePhysicalFieldKey(&builder, field)
+       }
 
-func (v *physicalSchemaKeyVisitor) BeforeListElement(_ iceberg.NestedField) { 
v.depth++ }
-func (v *physicalSchemaKeyVisitor) AfterListElement(_ iceberg.NestedField)  { 
v.depth-- }
-func (v *physicalSchemaKeyVisitor) BeforeMapKey(_ iceberg.NestedField)      { 
v.depth++ }
-func (v *physicalSchemaKeyVisitor) AfterMapKey(_ iceberg.NestedField)       { 
v.depth-- }
-func (v *physicalSchemaKeyVisitor) BeforeMapValue(_ iceberg.NestedField)    { 
v.depth++ }
-func (v *physicalSchemaKeyVisitor) AfterMapValue(_ iceberg.NestedField)     { 
v.depth-- }
+       return builder.String(), nil
+}
 
 func writePhysicalFieldKey(builder *strings.Builder, field 
iceberg.NestedField) {
        builder.WriteByte('f')
@@ -219,8 +203,13 @@ func writePhysicalFieldKey(builder *strings.Builder, field 
iceberg.NestedField)
 }
 
 func writePhysicalTypeKey(builder *strings.Builder, typ iceberg.Type) {
+       // Type tags are internal to the cache key. Parameterized types append 
their
+       // parameters so equal physical layouts still produce equal keys.
        switch t := typ.(type) {
        case *iceberg.StructType:
+               if t == nil {
+                       panic(physicalSchemaKeyPanic("nil struct type"))
+               }
                builder.WriteByte('s')
                builder.WriteByte('{')
                for _, field := range t.FieldList {
@@ -228,6 +217,9 @@ func writePhysicalTypeKey(builder *strings.Builder, typ 
iceberg.Type) {
                }
                builder.WriteByte('}')
        case *iceberg.ListType:
+               if t == nil {
+                       panic(physicalSchemaKeyPanic("nil list type"))
+               }
                builder.WriteByte('l')
                writePhysicalInt(builder, t.ElementID)
                if t.ElementRequired {
@@ -237,6 +229,9 @@ func writePhysicalTypeKey(builder *strings.Builder, typ 
iceberg.Type) {
                }
                writePhysicalTypeKey(builder, t.Element)
        case *iceberg.MapType:
+               if t == nil {
+                       panic(physicalSchemaKeyPanic("nil map type"))
+               }
                builder.WriteByte('m')
                writePhysicalInt(builder, t.KeyID)
                writePhysicalTypeKey(builder, t.KeyType)
@@ -247,11 +242,55 @@ func writePhysicalTypeKey(builder *strings.Builder, typ 
iceberg.Type) {
                        builder.WriteByte('0')
                }
                writePhysicalTypeKey(builder, t.ValueType)
-       case nil:
-               builder.WriteString("n")
-       default:
+       case iceberg.BooleanType:
+               builder.WriteByte('b')
+       case iceberg.Int32Type:
+               builder.WriteByte('i')
+       case iceberg.Int64Type:
+               builder.WriteByte('j')
+       case iceberg.Float32Type:
+               builder.WriteByte('k')
+       case iceberg.Float64Type:
+               builder.WriteByte('d')
+       case iceberg.DateType:
+               builder.WriteByte('D')
+       case iceberg.TimeType:
+               builder.WriteByte('T')
+       case iceberg.TimestampType:
+               builder.WriteByte('t')
+       case iceberg.TimestampTzType:
+               builder.WriteByte('z')
+       case iceberg.StringType:
+               builder.WriteByte('S')
+       case iceberg.UUIDType:
+               builder.WriteByte('u')
+       case iceberg.BinaryType:
+               builder.WriteByte('B')
+       case iceberg.TimestampNsType:
+               builder.WriteByte('n')
+       case iceberg.TimestampTzNsType:
+               builder.WriteByte('N')
+       case iceberg.UnknownType:
+               builder.WriteByte('U')
+       case iceberg.VariantType:
+               builder.WriteByte('v')
+       case iceberg.FixedType:
+               builder.WriteByte('F')
+               writePhysicalInt(builder, t.Len())
+       case iceberg.DecimalType:
+               builder.WriteByte('q')
+               writePhysicalInt(builder, t.Precision())
+               writePhysicalInt(builder, t.Scale())
+       case iceberg.PrimitiveType:
+               if value := reflect.ValueOf(t); value.Kind() == reflect.Pointer 
&& value.IsNil() {
+                       panic(physicalSchemaKeyPanic("nil primitive type"))
+               }
+               // Keep custom and parameterized primitive types (for example 
geometry)
+               // compatible with the previous structural key encoding.
                builder.WriteByte('p')
                writePhysicalString(builder, typ.String())
+       default:
+               panic(physicalSchemaKeyPanic(fmt.Sprintf("unsupported physical 
type: %T", typ)))
        }
 }
 
diff --git a/table/arrow_filter_plan_key_test.go 
b/table/arrow_filter_plan_key_test.go
new file mode 100644
index 000000000..838cc51cb
--- /dev/null
+++ b/table/arrow_filter_plan_key_test.go
@@ -0,0 +1,91 @@
+// 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.
+
+package table
+
+import (
+       "testing"
+
+       "github.com/apache/iceberg-go"
+       "github.com/stretchr/testify/require"
+)
+
+func TestPhysicalSchemaKeyDistinctTypeTags(t *testing.T) {
+       types := []iceberg.Type{
+               iceberg.BooleanType{},
+               iceberg.Int32Type{},
+               iceberg.Int64Type{},
+               iceberg.Float32Type{},
+               iceberg.Float64Type{},
+               iceberg.DateType{},
+               iceberg.TimeType{},
+               iceberg.TimestampType{},
+               iceberg.TimestampTzType{},
+               iceberg.TimestampNsType{},
+               iceberg.TimestampTzNsType{},
+               iceberg.StringType{},
+               iceberg.UUIDType{},
+               iceberg.BinaryType{},
+               iceberg.UnknownType{},
+               iceberg.VariantType{},
+               iceberg.FixedTypeOf(8),
+               iceberg.FixedTypeOf(16),
+               iceberg.DecimalTypeOf(10, 2),
+               iceberg.DecimalTypeOf(11, 2),
+               iceberg.DecimalTypeOf(10, 3),
+       }
+       keys := make(map[string]string, len(types))
+       for _, typ := range types {
+               t.Run(typ.String(), func(t *testing.T) {
+                       schema := iceberg.NewSchema(1, iceberg.NestedField{ID: 
1, Name: "value", Type: typ})
+                       key, err := physicalSchemaKey(schema)
+                       require.NoError(t, err)
+                       require.NotContains(t, keys, key, "type %s collides 
with %s", typ, keys[key])
+                       keys[key] = typ.String()
+               })
+       }
+}
+
+func TestPhysicalSchemaKeyRejectsNilPrimitives(t *testing.T) {
+       for _, typ := range []iceberg.Type{
+               (*iceberg.Int32Type)(nil),
+               (*iceberg.FixedType)(nil),
+               (*iceberg.DecimalType)(nil),
+       } {
+               key, err := physicalSchemaKey(iceberg.NewSchema(1,
+                       iceberg.NestedField{ID: 1, Name: "invalid", Type: typ}))
+               require.ErrorIs(t, err, iceberg.ErrInvalidSchema)
+               require.Empty(t, key)
+       }
+}
+
+type panickingPhysicalPrimitive struct {
+       iceberg.StringType
+}
+
+func (panickingPhysicalPrimitive) String() string {
+       panic("unexpected primitive panic")
+}
+
+func TestPhysicalSchemaKeyPreservesUnexpectedPanics(t *testing.T) {
+       schema := iceberg.NewSchema(1, iceberg.NestedField{
+               ID: 1, Name: "value", Type: panickingPhysicalPrimitive{},
+       })
+       require.PanicsWithValue(t, "unexpected primitive panic", func() {
+               _, _ = physicalSchemaKey(schema)
+       })
+}
diff --git a/table/arrow_filter_plan_test.go b/table/arrow_filter_plan_test.go
index 76f3bf87f..c96e726de 100644
--- a/table/arrow_filter_plan_test.go
+++ b/table/arrow_filter_plan_test.go
@@ -201,3 +201,138 @@ func 
TestCompiledFileFilterPlansDisablePruningForMissingInitialDefault(t *testin
        assert.Empty(t, plans.pruning.bloomPreds)
        assert.Empty(t, plans.pruning.dictionaryPreds)
 }
+
+func TestPhysicalSchemaKeyIgnoresMetadata(t *testing.T) {
+       fields := []iceberg.NestedField{{
+               ID: 1, Name: "root", Type: &iceberg.StructType{FieldList: 
[]iceberg.NestedField{
+                       {ID: 2, Name: "value", Type: 
iceberg.PrimitiveTypes.Int64, Required: true},
+               }},
+       }}
+       original := iceberg.NewSchema(1, fields...)
+       key, err := physicalSchemaKey(original)
+       require.NoError(t, err)
+
+       fields = original.Fields()
+       fields[0].Doc = "root documentation"
+       child := &fields[0].Type.(*iceberg.StructType).FieldList[0]
+       child.Doc = "value documentation"
+       child.InitialDefault = int64(7)
+       child.WriteDefault = int64(9)
+       withMetadata := iceberg.NewSchemaWithIdentifiers(2, []int{2}, fields...)
+       metadataKey, err := physicalSchemaKey(withMetadata)
+       require.NoError(t, err)
+       assert.Equal(t, key, metadataKey)
+
+       emptyKey, err := physicalSchemaKey(iceberg.NewSchema(1))
+       require.NoError(t, err)
+       assert.Empty(t, emptyKey)
+}
+
+func TestPhysicalSchemaKeyDistinguishesNestedLayouts(t *testing.T) {
+       schema := iceberg.NewSchema(1, iceberg.NestedField{
+               ID: 1, Name: "root", Type: &iceberg.StructType{FieldList: 
[]iceberg.NestedField{
+                       {ID: 2, Name: "items", Type: &iceberg.ListType{
+                               ElementID: 3, Element: 
iceberg.PrimitiveTypes.Int64,
+                       }},
+                       {ID: 4, Name: "lookup", Type: &iceberg.MapType{
+                               KeyID: 5, KeyType: 
iceberg.PrimitiveTypes.String,
+                               ValueID: 6, ValueType: 
iceberg.DecimalTypeOf(10, 2),
+                       }},
+                       {ID: 7, Name: "fixed", Type: iceberg.FixedTypeOf(8)},
+                       {ID: 8, Name: "variant", Type: iceberg.VariantType{}},
+               }},
+       })
+       original, err := physicalSchemaKey(schema)
+       require.NoError(t, err)
+
+       for _, tc := range []struct {
+               name   string
+               change func(*iceberg.StructType)
+       }{
+               {"field_id", func(s *iceberg.StructType) { s.FieldList[0].ID = 
20 }},
+               {"field_name", func(s *iceberg.StructType) { 
s.FieldList[0].Name = "items:;{}" }},
+               {"field_required", func(s *iceberg.StructType) { 
s.FieldList[0].Required = true }},
+               {"field_order", func(s *iceberg.StructType) { s.FieldList[0], 
s.FieldList[1] = s.FieldList[1], s.FieldList[0] }},
+               {"list_id", func(s *iceberg.StructType) { 
s.FieldList[0].Type.(*iceberg.ListType).ElementID = 30 }},
+               {"list_required", func(s *iceberg.StructType) { 
s.FieldList[0].Type.(*iceberg.ListType).ElementRequired = true }},
+               {"list_type", func(s *iceberg.StructType) {
+                       s.FieldList[0].Type.(*iceberg.ListType).Element = 
iceberg.PrimitiveTypes.Int32
+               }},
+               {"map_key_id", func(s *iceberg.StructType) { 
s.FieldList[1].Type.(*iceberg.MapType).KeyID = 50 }},
+               {"map_key_type", func(s *iceberg.StructType) {
+                       s.FieldList[1].Type.(*iceberg.MapType).KeyType = 
iceberg.PrimitiveTypes.Int32
+               }},
+               {"map_value_id", func(s *iceberg.StructType) { 
s.FieldList[1].Type.(*iceberg.MapType).ValueID = 60 }},
+               {"map_value_required", func(s *iceberg.StructType) { 
s.FieldList[1].Type.(*iceberg.MapType).ValueRequired = true }},
+               {"decimal_precision", func(s *iceberg.StructType) {
+                       s.FieldList[1].Type.(*iceberg.MapType).ValueType = 
iceberg.DecimalTypeOf(11, 2)
+               }},
+               {"decimal_scale", func(s *iceberg.StructType) {
+                       s.FieldList[1].Type.(*iceberg.MapType).ValueType = 
iceberg.DecimalTypeOf(10, 3)
+               }},
+               {"fixed_size", func(s *iceberg.StructType) { 
s.FieldList[2].Type = iceberg.FixedTypeOf(16) }},
+               {"variant_type", func(s *iceberg.StructType) { 
s.FieldList[3].Type = iceberg.PrimitiveTypes.Binary }},
+               {"empty_struct", func(s *iceberg.StructType) { s.FieldList = 
nil }},
+       } {
+               t.Run(tc.name, func(t *testing.T) {
+                       fields := schema.Fields()
+                       tc.change(fields[0].Type.(*iceberg.StructType))
+                       key, err := physicalSchemaKey(iceberg.NewSchema(1, 
fields...))
+                       require.NoError(t, err)
+                       assert.NotEqual(t, original, key)
+               })
+       }
+}
+
+func TestPhysicalSchemaKeyInvalidSchema(t *testing.T) {
+       for _, tc := range []struct {
+               name string
+               typ  iceberg.Type
+       }{
+               {"nil_type", nil},
+               {"nil_struct", (*iceberg.StructType)(nil)},
+               {"nil_list", (*iceberg.ListType)(nil)},
+               {"nil_map", (*iceberg.MapType)(nil)},
+               {"nil_struct_field", &iceberg.StructType{FieldList: 
[]iceberg.NestedField{{ID: 2, Name: "child"}}}},
+               {"nil_list_element", &iceberg.ListType{ElementID: 2}},
+               {"nil_map_key", &iceberg.MapType{KeyID: 2, ValueID: 3, 
ValueType: iceberg.PrimitiveTypes.String}},
+               {"nil_map_value", &iceberg.MapType{KeyID: 2, KeyType: 
iceberg.PrimitiveTypes.String, ValueID: 3}},
+       } {
+               t.Run(tc.name, func(t *testing.T) {
+                       key, err := physicalSchemaKey(iceberg.NewSchema(1, 
iceberg.NestedField{ID: 1, Name: "invalid", Type: tc.typ}))
+                       require.ErrorIs(t, err, iceberg.ErrInvalidSchema)
+                       assert.Empty(t, key)
+               })
+       }
+
+       key, err := physicalSchemaKey(nil)
+       require.ErrorIs(t, err, iceberg.ErrInvalidSchema)
+       assert.Empty(t, key)
+
+       _, err = (&arrowScan{}).cachedFileFilterPlans(nil, true)
+       require.ErrorIs(t, err, iceberg.ErrInvalidArgument)
+}
+
+func TestPhysicalSchemaKeySupportsParameterizedPrimitiveTypes(t *testing.T) {
+       geometry, err := iceberg.GeometryTypeOf("srid:4326")
+       require.NoError(t, err)
+       geography, err := iceberg.GeographyTypeOf("srid:4326", "spherical")
+       require.NoError(t, err)
+
+       schema := iceberg.NewSchema(1,
+               iceberg.NestedField{ID: 1, Name: "geometry", Type: geometry},
+               iceberg.NestedField{ID: 2, Name: "geography", Type: geography},
+       )
+       original, err := physicalSchemaKey(schema)
+       require.NoError(t, err)
+
+       otherGeometry, err := iceberg.GeometryTypeOf("srid:3857")
+       require.NoError(t, err)
+       other := iceberg.NewSchema(1,
+               iceberg.NestedField{ID: 1, Name: "geometry", Type: 
otherGeometry},
+               iceberg.NestedField{ID: 2, Name: "geography", Type: geography},
+       )
+       changed, err := physicalSchemaKey(other)
+       require.NoError(t, err)
+       assert.NotEqual(t, original, changed)
+}
diff --git a/table/arrow_scanner_bench_test.go 
b/table/arrow_scanner_bench_test.go
index 7706938ca..f3a1e5aa3 100644
--- a/table/arrow_scanner_bench_test.go
+++ b/table/arrow_scanner_bench_test.go
@@ -405,6 +405,97 @@ func BenchmarkArrowScanFilterPlanSetup(b *testing.B) {
        })
 }
 
+func BenchmarkArrowScanFilterPlanCacheHit(b *testing.B) {
+       for _, tc := range benchmarkPhysicalSchemaCases() {
+               b.Run(tc.name, func(b *testing.B) {
+                       scan := &arrowScan{boundRowFilter: 
iceberg.AlwaysTrue{}, caseSensitive: true}
+                       if _, err := scan.cachedFileFilterPlans(tc.schema, 
true); err != nil {
+                               b.Fatal(err)
+                       }
+
+                       b.ReportAllocs()
+                       for b.Loop() {
+                               plans, err := 
scan.cachedFileFilterPlans(tc.schema, true)
+                               if err != nil {
+                                       b.Fatal(err)
+                               }
+                               benchmarkCompiledFilterPlansSink = plans
+                       }
+               })
+       }
+}
+
+var benchmarkPhysicalSchemaKeySink string
+
+func BenchmarkPhysicalSchemaKey(b *testing.B) {
+       for _, tc := range benchmarkPhysicalSchemaCases() {
+               b.Run(tc.name, func(b *testing.B) {
+                       b.ReportAllocs()
+                       b.ResetTimer()
+                       for b.Loop() {
+                               key, err := physicalSchemaKey(tc.schema)
+                               if err != nil {
+                                       b.Fatal(err)
+                               }
+                               benchmarkPhysicalSchemaKeySink = key
+                       }
+               })
+       }
+}
+
+func benchmarkPhysicalSchemaCases() []struct {
+       name   string
+       schema *iceberg.Schema
+} {
+       flatFields := make([]iceberg.NestedField, 10)
+       for i := range flatFields {
+               flatFields[i] = iceberg.NestedField{
+                       ID: i + 1, Name: fmt.Sprintf("field_%d", i), Type: 
iceberg.PrimitiveTypes.Int64,
+               }
+       }
+       wideFields := make([]iceberg.NestedField, 100)
+       for i := range wideFields {
+               wideFields[i] = iceberg.NestedField{
+                       ID: i + 2, Name: fmt.Sprintf("field_%d", i), Type: 
iceberg.PrimitiveTypes.Int64,
+               }
+       }
+       mixedFields := []iceberg.NestedField{
+               {ID: 1, Name: "flag", Type: iceberg.PrimitiveTypes.Bool},
+               {ID: 2, Name: "count", Type: iceberg.PrimitiveTypes.Int32},
+               {ID: 3, Name: "ratio", Type: iceberg.PrimitiveTypes.Float64},
+               {ID: 4, Name: "fixed", Type: iceberg.FixedTypeOf(16)},
+               {ID: 5, Name: "price", Type: iceberg.DecimalTypeOf(20, 4)},
+               {ID: 6, Name: "created", Type: 
iceberg.PrimitiveTypes.TimestampTzNs},
+               {ID: 7, Name: "payload", Type: iceberg.VariantType{}},
+               {ID: 8, Name: "tags", Type: &iceberg.ListType{
+                       ElementID: 9, Element: iceberg.PrimitiveTypes.String,
+               }},
+       }
+       nestedSchema := func(depth int) *iceberg.Schema {
+               var typ iceberg.Type = iceberg.PrimitiveTypes.Int64
+               for id := depth + 1; id > 1; id-- {
+                       typ = &iceberg.StructType{FieldList: 
[]iceberg.NestedField{
+                               {ID: id, Name: "child", Type: typ},
+                       }}
+               }
+
+               return iceberg.NewSchema(1, iceberg.NestedField{ID: 1, Name: 
"root", Type: typ})
+       }
+
+       return []struct {
+               name   string
+               schema *iceberg.Schema
+       }{
+               {"flat_10", iceberg.NewSchema(1, flatFields...)},
+               {"nested_depth_3", nestedSchema(3)},
+               {"nested_depth_8", nestedSchema(8)},
+               {"wide_struct_100", iceberg.NewSchema(1, iceberg.NestedField{
+                       ID: 1, Name: "root", Type: 
&iceberg.StructType{FieldList: wideFields},
+               })},
+               {"mixed_types", iceberg.NewSchema(1, mixedFields...)},
+       }
+}
+
 func BenchmarkArrowScanAddTaskProjectedFieldIDs(b *testing.B) {
        schema := benchmarkScanSchema(8)
        filter := benchmarkComplexBoundFilter(b, schema)

Reply via email to