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


##########
table/row_delta.go:
##########
@@ -198,8 +198,10 @@ func (rd *RowDelta) Commit(ctx context.Context) error {
                                ct, f.FilePath())
                }
 
-               if err := validateDeletionVectorFormatVersion(f, 
meta.formatVersion, "row delta"); err != nil {
-                       return err
+               if IsDeletionVector(f) {

Review Comment:
   This branch is where the parity claim breaks. It only validates when 
`IsDeletionVector(f)` is true, so a plain Parquet `EntryContentPosDeletes` file 
on a v3 table skips every check and commits fine.
   
   ReplaceFiles rejects exactly this a few lines up in 
`validateDeleteFilesToAdd` (`position delete file %s must be a deletion vector 
for v%d table`), and Java gates both RowDelta and RewriteFiles through the same 
`validateDeleteFileForVersion` — so today RowDelta is the one path that lets a 
non-DV pos-delete onto v3. It's not cosmetic: if an engine later writes a DV 
for the same data file, the merge logic expects at most one DV per data file 
and won't find the stray pos-delete to fold in, so deleted rows can come back.
   
   I'd add the symmetric guard here, something like `if !IsDeletionVector(f) && 
ct == iceberg.EntryContentPosDeletes && meta.formatVersion >= 3` returning the 
same "must be a deletion vector" error. wdyt?



##########
table/transaction.go:
##########
@@ -1191,9 +1191,27 @@ type deleteFilesToAddSet struct {
        dvsByRef map[string]rewriteDeleteFileAddition
 }
 
-func validateDeletionVectorFormatVersion(df iceberg.DataFile, formatVersion 
int, operation string) error {
-       if IsDeletionVector(df) && formatVersion < 3 {
-               return fmt.Errorf("deletion vector %s requires table format 
version >= 3 for %s", df.FilePath(), operation)
+func validateDeletionVectorToAdd(df iceberg.DataFile, formatVersion int, 
operation string) error {

Review Comment:
   The old helper self-guarded with `IsDeletionVector(df) &&`; this one drops 
that and opens straight into the format-version check. Both call sites happen 
to wrap it in `if IsDeletionVector`, so it's correct today, but the contract is 
now invisible — a future caller passing an equality delete (nil 
`ContentOffset`) would get a misleading "missing content_offset".
   
   I'd either make it the first line here (`if !IsDeletionVector(df) { return 
nil }`) or drop a one-line doc comment stating the precondition. wdyt?



##########
table/transaction.go:
##########
@@ -1289,24 +1303,10 @@ func (t *Transaction) 
validateDeleteFilesToAdd(deleteFiles []rewriteDeleteFileAd
                }
 
                if IsDeletionVector(df) {
-                       ref := df.ReferencedDataFile()
-                       if ref == nil || *ref == "" {
-                               return nil, fmt.Errorf("deletion vector to add 
is missing referenced_data_file for %s", operation)
-                       }
-                       offset := df.ContentOffset()
-                       if offset == nil {
-                               return nil, fmt.Errorf("deletion vector %s is 
missing content_offset for %s", path, operation)
-                       }
-                       if *offset < 0 {
-                               return nil, fmt.Errorf("deletion vector %s has 
invalid content_offset %d for %s", path, *offset, operation)
-                       }
-                       length := df.ContentSizeInBytes()
-                       if length == nil {
-                               return nil, fmt.Errorf("deletion vector %s is 
missing content_size_in_bytes for %s", path, operation)
-                       }
-                       if *length <= 0 {
-                               return nil, fmt.Errorf("deletion vector %s has 
invalid content_size_in_bytes %d for %s", path, *length, operation)
+                       if err := validateDeletionVectorToAdd(df, 
meta.formatVersion, operation); err != nil {
+                               return nil, err
                        }
+                       ref, offset, length := df.ReferencedDataFile(), 
df.ContentOffset(), df.ContentSizeInBytes()

Review Comment:
   We re-fetch ref/offset/length here and deref them just below without a nil 
check — safe only because `validateDeletionVectorToAdd` just guaranteed them 
non-nil, but that coupling is invisible at this line. Either return the 
validated values from the helper, or a short `// safe: 
validateDeletionVectorToAdd guarantees non-nil` would make it obvious.



##########
table/row_delta_test.go:
##########
@@ -1190,22 +1204,15 @@ func TestRowDeltaRejectsDeletionVectorBelowV3(t 
*testing.T) {
 }
 
 func TestRowDeltaAcceptsDeletionVectorOnV3(t *testing.T) {
-       tbl := newRowDeltaCommitTestTableVersion(t, 3)
-       dataPath := tbl.Location() + "/data/insert.parquet"
-       dvPath := tbl.Location() + "/data/dv-001.puffin"
-
-       tx := tbl.NewTransaction()
-       require.NoError(t, tx.NewRowDelta(nil).AddDeletes(buildDVFile(t, 
dvPath, dataPath)).Commit(t.Context()))
-       tbl, err := tx.Commit(t.Context())
-       require.NoError(t, err)
+       tbl, _, dataPath, dv := newTableWithLiveDV(t)

Review Comment:
   This test no longer exercises the RowDelta commit it's named for. 
`newTableWithLiveDV` does the commit and hands back an already-committed table, 
so the body only reads `CurrentSnapshot()` — the "RowDelta accepts a valid DV 
without erroring" assertion has effectively moved into the helper.
   
   I'd keep an explicit `require.NoError` on a fresh RowDelta commit with a 
valid DV here, or rename the test to say it's checking snapshot structure. 
Otherwise if the helper changes, we lose the accept-path coverage without 
noticing.



##########
table/transaction.go:
##########
@@ -1191,9 +1191,27 @@ type deleteFilesToAddSet struct {
        dvsByRef map[string]rewriteDeleteFileAddition
 }
 
-func validateDeletionVectorFormatVersion(df iceberg.DataFile, formatVersion 
int, operation string) error {
-       if IsDeletionVector(df) && formatVersion < 3 {
-               return fmt.Errorf("deletion vector %s requires table format 
version >= 3 for %s", df.FilePath(), operation)
+func validateDeletionVectorToAdd(df iceberg.DataFile, formatVersion int, 
operation string) error {
+       path := df.FilePath()
+       if formatVersion < 3 {
+               return fmt.Errorf("deletion vector %s requires table format 
version >= 3 for %s", path, operation)
+       }
+       if ref := df.ReferencedDataFile(); ref == nil || *ref == "" {
+               return fmt.Errorf("deletion vector %s is missing 
referenced_data_file for %s", path, operation)

Review Comment:
   This quietly changes the referenced_data_file message from "deletion vector 
to add is missing referenced_data_file for %s" to include the path and drop "to 
add", and it hits the ReplaceFiles path too. Our tests are substring-only so 
they still pass. Fine by me either way — just flagging it's an intentional 
contract change so anyone matching on the old string knows.



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