tanmayrauth commented on code in PR #1593:
URL: https://github.com/apache/iceberg-go/pull/1593#discussion_r3670176012


##########
partitions.go:
##########
@@ -469,6 +488,26 @@ func (ps *PartitionSpec) UnmarshalJSON(b []byte) error {
        return nil
 }
 
+func validatePartitionFields(fields []PartitionField) error {
+       names := make(map[string]struct{}, len(fields))
+       definitions := make(map[string]struct{}, len(fields))
+       for _, field := range fields {
+               if _, ok := names[field.Name]; ok {
+                       return fmt.Errorf("%w: duplicate partition name: %s", 
ErrInvalidPartitionSpec, field.Name)
+               }
+               names[field.Name] = struct{}{}
+
+               definition := fmt.Sprintf("%v:%s", field.SourceIDs, 
field.Transform)

Review Comment:
   This rejects two fields that share (source-ids, transform), but `void` is 
the drop marker: dropping a partition field in a v1 spec rewrites it to void 
while keeping its source-id, so a column that had two
     partition fields both later dropped legally ends up with two 
`[source]:void` entries (distinct names/field-ids). This check collapses them 
and fails the whole spec on decode — so a table Java/PyIceberg
     wrote, and older iceberg-go read fine, now fails to open with 
ErrInvalidPartitionSpec. It's also self-inconsistent: NewPartitionSpecID builds 
such a spec with no complaint, MarshalJSON emits it, and this
     path then refuses to read it back.
     
     Exempt void from the redundancy check (keep the name-uniqueness check): 
after `names[field.Name] = struct{}{}`, add `if _, ok := 
field.Transform.(VoidTransform); ok { continue }` before building
     `definition`. ParseTransform returns value-typed transforms, so the 
non-pointer VoidTransform assertion is right. That still rejects two 
identity/bucket/etc. on the same source, which is the case you're
     actually targeting.



##########
partitions_test.go:
##########
@@ -647,10 +647,65 @@ func TestPartitionFieldUnmarshalJSON(t *testing.T) {
                }`
                var field iceberg.PartitionField
                err := json.Unmarshal([]byte(jsonData), &field)
-               require.NoError(t, err)
-               assert.Zero(t, field.SourceID())
-               assert.Equal(t, 1003, field.FieldID)
-               assert.Equal(t, "void", field.Name)
-               assert.Equal(t, iceberg.VoidTransform{}, field.Transform)
+               require.ErrorIs(t, err, iceberg.ErrInvalidPartitionSpec)
+               assert.ErrorContains(t, err, "requires source-id or source-ids")
        })
 }
+
+func TestPartitionSpecUnmarshalRejectsInvalidStructure(t *testing.T) {
+       tests := []struct {
+               name    string
+               data    string
+               message string
+       }{
+               {
+                       name:    "negative spec ID",
+                       data:    `{"spec-id":-1,"fields":[]}`,
+                       message: "spec ID must be non-negative",
+               },
+               {
+                       name:    "zero source ID",
+                       data:    
`{"spec-id":0,"fields":[{"source-id":0,"field-id":1000,"name":"part","transform":"identity"}]}`,
+                       message: "source ID must be positive",
+               },
+               {
+                       name:    "empty source IDs",
+                       data:    
`{"spec-id":0,"fields":[{"source-ids":[],"field-id":1000,"name":"part","transform":"identity"}]}`,
+                       message: "source ID must be positive",
+               },
+               {
+                       name:    "empty name",
+                       data:    
`{"spec-id":0,"fields":[{"source-id":1,"field-id":1000,"name":"","transform":"identity"}]}`,
+                       message: "partition name cannot be empty",
+               },
+               {
+                       name:    "duplicate names",
+                       data:    
`{"spec-id":0,"fields":[{"source-id":1,"field-id":1000,"name":"part","transform":"identity"},{"source-id":2,"field-id":1001,"name":"part","transform":"identity"}]}`,
+                       message: "duplicate partition name",
+               },
+               {
+                       name:    "duplicate field IDs",
+                       data:    
`{"spec-id":0,"fields":[{"source-id":1,"field-id":1000,"name":"first","transform":"identity"},{"source-id":2,"field-id":1000,"name":"second","transform":"identity"}]}`,
+                       message: "duplicate field ID provided",
+               },
+               {
+                       name:    "redundant fields",

Review Comment:
   This covers two identity fields on one source being rejected, but there's no 
positive case for two `void` fields on the same source-id — which is exactly 
the legal historical spec the redundancy check above
     wrongly rejects. Add a case that unmarshals `{spec-id, 
fields:[{source-id:1,field-id:1000,name:"a",transform:"void"},{source-id:1,field-id:1001,name:"b",transform:"void"}]}`
 and asserts NoError, so the void exemption is locked in.



-- 
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