laskoviymishka commented on code in PR #1592:
URL: https://github.com/apache/iceberg-go/pull/1592#discussion_r3680745795


##########
table/metadata.go:
##########
@@ -2277,6 +2281,18 @@ func (c *commonMetadata) validate() error {
        return nil
 }
 
+func (c *commonMetadata) checkSnapshots() error {

Review Comment:
   one thing worth calling out: this makes us stricter than the other clients 
on the read path. PyIceberg and iceberg-rust both silently accept duplicate 
snapshot IDs (last-one-wins), and Java only rejects at write time in the 
builder — its parser doesn't check. So after this, iceberg-go will refuse to 
open a metadata file that py/rust will happily read.
   
   I actually think rejecting is the better behavior — it stops the 
order-dependent `SnapshotByID()` ambiguity from ever entering the system, which 
is the whole point of the fix. But it's a deliberate divergence, and it changes 
the observable behavior of `ParseMetadata` for callers who previously tolerated 
these files, so I'd note it in the PR description / changelog so it's a 
conscious choice rather than a surprise.
   
   If we'd rather stay lenient-on-read like the others, the alternative is to 
log a warning here and keep the hard error in the builder's 
`addSnapshotInternal`. I lean toward keeping the hard reject — just want it to 
be intentional. wdyt?



##########
table/metadata_internal_test.go:
##########
@@ -564,6 +564,44 @@ func TestRejectInvalidSchemaEntries(t *testing.T) {
        })
 }
 
+func TestRejectDuplicateSnapshotIDs(t *testing.T) {
+       var metadata map[string]any
+       decoder := json.NewDecoder(strings.NewReader(ExampleTableMetadataV2))
+       decoder.UseNumber()
+       require.NoError(t, decoder.Decode(&metadata))
+       snapshots := metadata["snapshots"].([]any)
+       current := snapshots[len(snapshots)-1].(map[string]any)
+
+       tests := []struct {
+               name      string
+               duplicate map[string]any
+       }{
+               {name: "identical snapshot", duplicate: maps.Clone(current)},
+               {
+                       name: "different manifest list",

Review Comment:
   a one-line comment here would help — at a glance this case looks identical 
to the first, and the point (that ID equality alone drives rejection, 
regardless of `manifest-list` or other fields) is worth stating so nobody 
deletes it later as a dup.
   
   while we're here, the IIFE is a bit opaque — the sibling 
`TestRejectInvalidSchemaEntries` builds per-case state inside the `t.Run` body. 
Building the duplicate there instead would read more naturally, but that's 
taste.



##########
table/metadata_internal_test.go:
##########
@@ -564,6 +564,44 @@ func TestRejectInvalidSchemaEntries(t *testing.T) {
        })
 }
 
+func TestRejectDuplicateSnapshotIDs(t *testing.T) {
+       var metadata map[string]any
+       decoder := json.NewDecoder(strings.NewReader(ExampleTableMetadataV2))
+       decoder.UseNumber()
+       require.NoError(t, decoder.Decode(&metadata))
+       snapshots := metadata["snapshots"].([]any)
+       current := snapshots[len(snapshots)-1].(map[string]any)
+
+       tests := []struct {
+               name      string
+               duplicate map[string]any
+       }{
+               {name: "identical snapshot", duplicate: maps.Clone(current)},
+               {
+                       name: "different manifest list",
+                       duplicate: func() map[string]any {
+                               duplicate := maps.Clone(current)
+                               duplicate["manifest-list"] = 
"s3://bucket/metadata/different.avro"
+
+                               return duplicate
+                       }(),
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       invalid := maps.Clone(metadata)
+                       invalid["snapshots"] = append(slices.Clone(snapshots), 
tt.duplicate)
+                       data, err := json.Marshal(invalid)
+                       require.NoError(t, err)
+
+                       _, err = ParseMetadataBytes(data)
+                       require.ErrorIs(t, err, ErrInvalidMetadata)
+                       assert.ErrorContains(t, err, "duplicate snapshot ID 
3055729675574597004")

Review Comment:
   this hard-codes the current snapshot's ID from `ExampleTableMetadataV2`. If 
that fixture ever changes, the assertion goes stale silently — a false negative 
instead of a compile error, and unlike the schema-ID sibling test where `0` is 
structurally fixed, these IDs are arbitrary large ints.
   
   since you're already decoding with `UseNumber()` (good call — these IDs are 
> 2^53 and plain `Unmarshal` would corrupt them as float64), you can pull the 
expected value straight from the fixture:
   
   ```go
   assert.ErrorContains(t, err, fmt.Sprintf("duplicate snapshot ID %v", 
current["snapshot-id"]))
   ```
   
   `current["snapshot-id"]` is a `json.Number`, so `%v` prints the raw integer 
with no precision loss.



##########
table/metadata_internal_test.go:
##########
@@ -564,6 +564,44 @@ func TestRejectInvalidSchemaEntries(t *testing.T) {
        })
 }
 
+func TestRejectDuplicateSnapshotIDs(t *testing.T) {
+       var metadata map[string]any
+       decoder := json.NewDecoder(strings.NewReader(ExampleTableMetadataV2))

Review Comment:
   the check lives in `commonMetadata.validate()` so it covers all three 
versions, but the test only exercises V2. V3's `UnmarshalJSON` is structurally 
different — it runs `checkNextRowID` and `checkLastSequenceNumber` before 
`validate()` — so a regression in that wrapper that skipped `validate()` 
wouldn't be caught here.
   
   could we add a `t.Run` with `ExampleTableMetadataV3` injecting a duplicate? 
V1 would be nice too, but V3 is the one under active development.



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