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


##########
manifest.go:
##########
@@ -1358,16 +1359,18 @@ func NewManifestWriter(version int, out io.Writer, spec 
PartitionSpec, schema *S
 
 func (w *ManifestWriter) Close() error {
        if w.closed {
-               return nil
+               return w.closeErr
        }
+       w.closed = true
 
+       var emptyErr error
        if w.addedFiles+w.existingFiles+w.deletedFiles == 0 {
-               return errors.New("empty manifest file has been written")
+               emptyErr = errors.New("empty manifest file has been written")
        }
 
-       w.closed = true
+       w.closeErr = errors.Join(emptyErr, w.writer.Close())

Review Comment:
   This line does two things I'd tighten. `w.writer.Close()` runs 
unconditionally now, so an empty close where the writer was never built 
(`NewManifestWriter` can hand back a non-nil `w` alongside a non-nil error) 
panics where the old early return returned cleanly. And `errors.Join` wraps the 
empty-manifest error in a `*joinError`, so `errors.As` and sentinel/`==` checks 
against it stop matching (`errors.Is` still works).
   
   Both go away if we guard the writer and only join when there's a real second 
error:
   
   ```go
   var writerErr error
   if w.writer != nil {
        writerErr = w.writer.Close()
   }
   if emptyErr != nil {
        w.closeErr = errors.Join(emptyErr, writerErr)
   } else {
        w.closeErr = writerErr
   }
   ```
   
   Exposure is narrow today (callers check the constructor error, no exported 
sentinel yet), but it's a cheap fix that keeps the contract clean. wdyt?



##########
manifest.go:
##########
@@ -1358,16 +1359,18 @@ func NewManifestWriter(version int, out io.Writer, spec 
PartitionSpec, schema *S
 
 func (w *ManifestWriter) Close() error {
        if w.closed {
-               return nil
+               return w.closeErr
        }
+       w.closed = true
 
+       var emptyErr error
        if w.addedFiles+w.existingFiles+w.deletedFiles == 0 {
-               return errors.New("empty manifest file has been written")
+               emptyErr = errors.New("empty manifest file has been written")
        }
 
-       w.closed = true
+       w.closeErr = errors.Join(emptyErr, w.writer.Close())
 
-       return w.writer.Close()
+       return w.closeErr

Review Comment:
   Now that `Close()` is terminal and returns the cached error on every call, I 
want to check the public wrappers. `WriteManifest` and `WriteManifestV3` both 
call `Close()` explicitly and also `defer internal.CheckedClose(w, &err)`, so 
on an empty writer they'll join the returned error with a second `Close()` 
result — `errors.Join(emptyErr, emptyErr)` — and hand the caller a doubled 
`empty manifest file has been written\nempty manifest file has been written`.
   
   If those paths are reachable with zero entries (they look like they are — 
nothing guards the entry count before `WriteManifest`), I'd drop the redundant 
deferred `CheckedClose` there since `Close()` is already checked explicitly, or 
have `CheckedClose` skip an already-closed writer. Thoughts?



##########
manifest_test.go:
##########
@@ -2091,6 +2091,20 @@ func (m *ManifestTestSuite) TestManifestWriterMeta() {
        m.Equal("[]", string(md["partition-spec"]))
 }
 
+func (m *ManifestTestSuite) TestEmptyManifestWriterCloseIsTerminal() {
+       var out bytes.Buffer
+       writer, err := NewManifestWriter(2, &out, *UnpartitionedSpec, 
testSchema, snapshotID)
+       m.Require().NoError(err)
+
+       firstErr := writer.Close()

Review Comment:
   Good that this pins the terminal empty-close behavior. It only covers the 
empty branch though — I'd add the non-empty happy path (close a writer with 
entries, call `Close()` again, assert both return nil) so the new `closeErr` 
field's nil case is locked in, plus a case where the underlying writer errors 
on close so the two-error `errors.Join` path actually gets exercised.
   
   Minor: `m.Equal(firstErr, writer.Close())` passes on pointer identity (both 
calls hand back the same cached error), which is correct but implicit — a 
one-line comment there would help the next reader.



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