From c42bfeb0bed81c7cd1e46ea38d08abdf47707374 Mon Sep 17 00:00:00 2001 From: Minh Vu Date: Wed, 24 Jun 2026 23:36:13 +0200 Subject: [PATCH 1/3] use transform equality for partition compatibility --- partitions.go | 2 +- partitions_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/partitions.go b/partitions.go index 0feb6c377..334805609 100644 --- a/partitions.go +++ b/partitions.go @@ -371,7 +371,7 @@ func (ps *PartitionSpec) CompatibleWith(other *PartitionSpec) bool { return slices.EqualFunc(ps.fields, other.fields, func(left, right PartitionField) bool { return slices.Equal(left.SourceIDs, right.SourceIDs) && left.Name == right.Name && - left.Transform == right.Transform + left.Transform.Equals(right.Transform) }) } diff --git a/partitions_test.go b/partitions_test.go index 05b0546bf..3f91843d0 100644 --- a/partitions_test.go +++ b/partitions_test.go @@ -19,6 +19,7 @@ package iceberg_test import ( "encoding/json" + "slices" "testing" "github.com/apache/iceberg-go" @@ -26,6 +27,17 @@ import ( "github.com/stretchr/testify/require" ) +type nonComparableTransform struct { + iceberg.IdentityTransform + values []int +} + +func (t nonComparableTransform) Equals(other iceberg.Transform) bool { + o, ok := other.(nonComparableTransform) + + return ok && slices.Equal(t.values, o.values) +} + func TestPartitionSpec(t *testing.T) { assert.Equal(t, 999, iceberg.UnpartitionedSpec.LastAssignedFieldID()) @@ -61,6 +73,32 @@ func TestPartitionSpec(t *testing.T) { assert.Equal(t, 1002, spec3.LastAssignedFieldID()) } +func TestPartitionSpecCompatibleWithUsesTransformEquals(t *testing.T) { + spec := iceberg.NewPartitionSpec(iceberg.PartitionField{ + SourceIDs: []int{1}, FieldID: 1001, Name: "id", + Transform: nonComparableTransform{values: []int{1, 2}}, + }) + sameTransformSpec := iceberg.NewPartitionSpec(iceberg.PartitionField{ + SourceIDs: []int{1}, FieldID: 1002, Name: "id", + Transform: nonComparableTransform{values: []int{1, 2}}, + }) + differentTransformSpec := iceberg.NewPartitionSpec(iceberg.PartitionField{ + SourceIDs: []int{1}, FieldID: 1003, Name: "id", + Transform: nonComparableTransform{values: []int{2, 3}}, + }) + + var compatible bool + require.NotPanics(t, func() { + compatible = spec.CompatibleWith(&sameTransformSpec) + }) + assert.True(t, compatible) + + require.NotPanics(t, func() { + compatible = spec.CompatibleWith(&differentTransformSpec) + }) + assert.False(t, compatible) +} + func TestUnpartitionedWithVoidField(t *testing.T) { spec := iceberg.NewPartitionSpec(iceberg.PartitionField{ SourceIDs: []int{3}, FieldID: 1001, Name: "void", Transform: iceberg.VoidTransform{}, From 35c4f52682522dc60904e3f370331a018ca1cfbd Mon Sep 17 00:00:00 2001 From: Minh Vu Date: Fri, 26 Jun 2026 23:45:41 +0200 Subject: [PATCH 2/3] test: cover built-in partition transform compatibility --- partitions_test.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/partitions_test.go b/partitions_test.go index 3f91843d0..0026313c8 100644 --- a/partitions_test.go +++ b/partitions_test.go @@ -97,6 +97,51 @@ func TestPartitionSpecCompatibleWithUsesTransformEquals(t *testing.T) { compatible = spec.CompatibleWith(&differentTransformSpec) }) assert.False(t, compatible) + + tests := []struct { + name string + left iceberg.Transform + right iceberg.Transform + compatible bool + }{ + { + name: "identical bucket transforms are compatible", + left: iceberg.BucketTransform{NumBuckets: 16}, + right: iceberg.BucketTransform{NumBuckets: 16}, + compatible: true, + }, + { + name: "different bucket transforms are incompatible", + left: iceberg.BucketTransform{NumBuckets: 16}, + right: iceberg.BucketTransform{NumBuckets: 32}, + compatible: false, + }, + { + name: "identical truncate transforms are compatible", + left: iceberg.TruncateTransform{Width: 4}, + right: iceberg.TruncateTransform{Width: 4}, + compatible: true, + }, + { + name: "different truncate transforms are incompatible", + left: iceberg.TruncateTransform{Width: 4}, + right: iceberg.TruncateTransform{Width: 8}, + compatible: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + left := iceberg.NewPartitionSpec(iceberg.PartitionField{ + SourceIDs: []int{1}, FieldID: 1001, Name: "id", Transform: tt.left, + }) + right := iceberg.NewPartitionSpec(iceberg.PartitionField{ + SourceIDs: []int{1}, FieldID: 1002, Name: "id", Transform: tt.right, + }) + + assert.Equal(t, tt.compatible, left.CompatibleWith(&right)) + }) + } } func TestUnpartitionedWithVoidField(t *testing.T) { From a04e78c8ea25b0c6f7f18ff33f61dfb725a3a61a Mon Sep 17 00:00:00 2001 From: Minh Vu Date: Mon, 29 Jun 2026 20:52:32 +0200 Subject: [PATCH 3/3] test: clarify partition transform compatibility regression --- partitions_test.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/partitions_test.go b/partitions_test.go index 0026313c8..e06764d3b 100644 --- a/partitions_test.go +++ b/partitions_test.go @@ -29,6 +29,7 @@ import ( type nonComparableTransform struct { iceberg.IdentityTransform + // values makes this transform non-comparable, which would have panicked with ==. values []int } @@ -87,16 +88,10 @@ func TestPartitionSpecCompatibleWithUsesTransformEquals(t *testing.T) { Transform: nonComparableTransform{values: []int{2, 3}}, }) - var compatible bool require.NotPanics(t, func() { - compatible = spec.CompatibleWith(&sameTransformSpec) + assert.True(t, spec.CompatibleWith(&sameTransformSpec)) + assert.False(t, spec.CompatibleWith(&differentTransformSpec)) }) - assert.True(t, compatible) - - require.NotPanics(t, func() { - compatible = spec.CompatibleWith(&differentTransformSpec) - }) - assert.False(t, compatible) tests := []struct { name string