zeroshade commented on code in PR #1641:
URL: https://github.com/apache/iceberg-go/pull/1641#discussion_r3732110680


##########
table/update_spec.go:
##########
@@ -369,11 +370,14 @@ func (us *UpdateSpec) partitionField(key transformKey, 
name string) (iceberg.Par
                }
                for _, field := range historicalFields {
                        if field.SourceID() == sourceId && 
field.Transform.String() == transformName {
-                               if len(name) > 0 && field.Name == name {
+                               // Reuse the historical field's ID when no 
explicit name is
+                               // requested (match on source + transform 
alone) or when the
+                               // requested name matches.
+                               if len(name) == 0 || field.Name == name {

Review Comment:
   A remove and re-add of the same source/transform with a different name in 
this same `UpdateSpec` skips this branch and falls through to `newFieldId()`. 
Java treats that operation as undo-delete plus rename, preserving the current 
field ID. Since partition field IDs are permanent once committed, allocating 
the fresh ID here cannot be undone after commit. **Suggested fix:** detect a 
field deleted by this update separately, restore its current ID, and apply the 
requested rename; only a different-name add in a later update after a committed 
removal should receive a fresh ID.



##########
table/update_spec_test.go:
##########
@@ -568,3 +568,126 @@ func TestUpdateSpecCommit(t *testing.T) {
                assert.Nil(t, err)
        })
 }
+
+func TestUpdateSpecReuseHistoricalFieldID(t *testing.T) {
+       t.Run("re-add deleted field without a name reuses historical field ID", 
func(t *testing.T) {
+               txn := testPartitionedTable.NewTransaction()
+               specUpdate := table.NewUpdateSpec(txn, false)
+
+               _, _, err := specUpdate.
+                       RemoveField("id_identity").
+                       AddField("id", iceberg.IdentityTransform{}, "").
+                       BuildUpdates()
+               require.NoError(t, err)
+
+               newSpec, err := specUpdate.Apply()
+               require.NoError(t, err)
+
+               // The re-added field on source 1 must recycle the original 
field ID
+               // (PartitionDataIDStart == 1000) and the historical name, 
rather than
+               // allocating a brand-new field ID.
+               reAdded := newSpec.FieldsBySourceID(1)
+               require.Len(t, reAdded, 1)
+               assert.Equal(t, iceberg.PartitionDataIDStart, 
reAdded[0].FieldID)
+               assert.Equal(t, "id_identity", reAdded[0].Name)
+               assert.Equal(t, iceberg.IdentityTransform{}, 
reAdded[0].Transform)
+
+               // The untouched field must remain unchanged.
+               untouched := newSpec.FieldsBySourceID(5)
+               require.Len(t, untouched, 1)
+               assert.Equal(t, iceberg.PartitionDataIDStart+1, 
untouched[0].FieldID)
+               assert.Equal(t, "street_void", untouched[0].Name)
+       })
+
+       t.Run("re-add deleted field with matching name reuses historical field 
ID", func(t *testing.T) {
+               txn := testPartitionedTable.NewTransaction()
+               specUpdate := table.NewUpdateSpec(txn, false)
+
+               _, _, err := specUpdate.
+                       RemoveField("id_identity").
+                       AddField("id", iceberg.IdentityTransform{}, 
"id_identity").
+                       BuildUpdates()
+               require.NoError(t, err)
+
+               newSpec, err := specUpdate.Apply()
+               require.NoError(t, err)
+
+               reAdded := newSpec.FieldsBySourceID(1)
+               require.Len(t, reAdded, 1)
+               assert.Equal(t, iceberg.PartitionDataIDStart, 
reAdded[0].FieldID)
+               assert.Equal(t, "id_identity", reAdded[0].Name)
+       })
+
+       t.Run("re-add deleted field with a different name allocates a new field 
ID", func(t *testing.T) {
+               txn := testPartitionedTable.NewTransaction()
+               specUpdate := table.NewUpdateSpec(txn, false)
+
+               _, _, err := specUpdate.
+                       RemoveField("id_identity").
+                       AddField("id", iceberg.IdentityTransform{}, 
"id_renamed").
+                       BuildUpdates()
+               require.NoError(t, err)
+
+               newSpec, err := specUpdate.Apply()
+               require.NoError(t, err)
+
+               // A different explicit name must NOT reuse the historical 
field ID; a
+               // fresh field ID is allocated after the last assigned one 
(1001 -> 1002).
+               reAdded := newSpec.FieldsBySourceID(1)
+               require.Len(t, reAdded, 1)
+               assert.Equal(t, iceberg.PartitionDataIDStart+2, 
reAdded[0].FieldID)

Review Comment:
   This expectation codifies the incorrect same-update behavior. The 
remove/re-add with `id_renamed` should preserve `PartitionDataIDStart` and 
rename the restored field. **Suggested fix:** change this assertion 
accordingly, then add a distinct case that commits the removal to the catalog 
and performs the different-name add in a later update, where a fresh ID is 
expected.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to