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


##########
table/metadata.go:
##########
@@ -1517,12 +1517,17 @@ func (b *MetadataBuilder) buildCommonMetadata() 
(*commonMetadata, error) {
                b.lastUpdatedMS = time.Now().UnixMilli()
        }
 
+       // Build may run more than once on the same builder, so the log is 
extended on a
+       // copy. Appending to b.metadataLog would add the previous file again 
on every call.
+       metadataLog := b.metadataLog
        if b.previousFileEntry != nil && b.HasChanges() {
                maxMetadataLogEntries := max(1,
                        b.props.GetInt(
                                MetadataPreviousVersionsMaxKey, 
MetadataPreviousVersionsMaxDefault))
-               b.AppendMetadataLog(*b.previousFileEntry)
-               b.TrimMetadataLogs(maxMetadataLogEntries)
+               metadataLog = append(slices.Clone(b.metadataLog), 
*b.previousFileEntry)
+               if len(metadataLog) > maxMetadataLogEntries {
+                       metadataLog = 
metadataLog[len(metadataLog)-maxMetadataLogEntries:]

Review Comment:
   This inline trim is now a copy of `TrimMetadataLogs`, just without the 
receiver mutation. I'd pull the shared logic into a pure helper (something like 
`trimMetadataLog(log, maxEntries)` that returns the last `maxEntries` without 
touching its input) and call it from both here and `TrimMetadataLogs`. 
Otherwise a future change to trim semantics has to land in two places and one 
will drift.



##########
table/metadata.go:
##########
@@ -1517,12 +1517,17 @@ func (b *MetadataBuilder) buildCommonMetadata() 
(*commonMetadata, error) {
                b.lastUpdatedMS = time.Now().UnixMilli()
        }
 
+       // Build may run more than once on the same builder, so the log is 
extended on a
+       // copy. Appending to b.metadataLog would add the previous file again 
on every call.
+       metadataLog := b.metadataLog

Review Comment:
   I'd clone here too, `metadataLog := slices.Clone(b.metadataLog)`, so both 
exits return a fresh slice. The changed branch below clones but this no-changes 
path hands back `b.metadataLog` by alias; it's safe today given how `append` 
behaves, but the asymmetry is the kind of thing that bites whoever edits this 
next, and it's one alloc only on the no-change path.



##########
table/metadata_builder_internal_test.go:
##########
@@ -1489,6 +1489,48 @@ func TestExpireMetadataLog(t *testing.T) {
        require.Len(t, meta.(*metadataV2).MetadataLog, 2)
 }
 
+func TestBuildDoesNotGrowMetadataLog(t *testing.T) {
+       builder := builderWithoutChanges(2)
+       require.NoError(t, builder.SetProperties(map[string]string{"test.prop": 
"value"}))
+
+       for range 3 {
+               meta, err := builder.Build()
+               require.NoError(t, err)
+               require.Len(t, meta.(*metadataV2).MetadataLog, 1)

Review Comment:
   Since this is an internal test, I'd assert on the builder's own slice 
directly inside the loop, `require.Len(t, builder.metadataLog, 1, "builder log 
must not grow across Build calls")`. Right now non-mutation is inferred from 
the returned length staying at 1; asserting on `builder.metadataLog` pins the 
exact invariant the fix is about.



##########
table/metadata.go:
##########
@@ -1517,12 +1517,17 @@ func (b *MetadataBuilder) buildCommonMetadata() 
(*commonMetadata, error) {
                b.lastUpdatedMS = time.Now().UnixMilli()

Review Comment:
   Not blocking, but while we're making `Build()` side-effect-free on the log: 
it still writes `b.lastUpdatedMS` on the first call, and `updateSnapshotLog()` 
further down still writes back `b.snapshotLog`, so `Build()` is idempotent on 
the metadata log now but not on those two. Harmless in the current CoW path 
since both get overwritten from the snapshot timestamp before commit, but a 
builder cloned after a `Build()` inherits a frozen `lastUpdatedMS`. Do we 
extend the same copy-on-build treatment here, or document that `Build()` caches 
these on purpose? wdyt?



##########
table/metadata_builder_internal_test.go:
##########
@@ -1489,6 +1489,48 @@ func TestExpireMetadataLog(t *testing.T) {
        require.Len(t, meta.(*metadataV2).MetadataLog, 2)
 }
 
+func TestBuildDoesNotGrowMetadataLog(t *testing.T) {

Review Comment:
   Could we add a case for the other new branch, `previousFileEntry` set but 
`HasChanges() == false`? That's the path where the fix returns `b.metadataLog` 
untouched, and right now `builderWithoutChanges` immediately gets a 
`SetProperties` so every test enters the active-changes branch. A quick 
`Build()` with no updates asserting `PreviousFiles()` stays empty would cover 
it.



##########
table/copy_on_write_metadata_log_test.go:
##########
@@ -0,0 +1,64 @@
+// 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_test
+
+import (
+       "context"
+       "path/filepath"
+       "slices"
+       "testing"
+
+       "github.com/apache/iceberg-go"
+       iceio "github.com/apache/iceberg-go/io"
+       "github.com/apache/iceberg-go/table"
+       "github.com/stretchr/testify/require"
+)
+
+// A copy-on-write delete builds the transaction's metadata once per rewritten

Review Comment:
   The comment says `Build` runs "once per rewritten file," but the duplicates 
on `main` actually come from classification and `StagedTable()` building the 
same builder too, not just the per-file rewrites, so the real count is higher 
than the comment implies. I'd reword it so the regression stays legible if 
those call sites ever move. While here, give the final `require.Len` a message 
like the setup assertion has.



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