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


##########
table/arrow_scanner_posdelete_regression_test.go:
##########
@@ -32,6 +32,39 @@ import (
        "github.com/stretchr/testify/require"
 )
 
+func TestPositionDeleteColumnIndices(t *testing.T) {
+       t.Parallel()
+
+       tests := []struct {
+               name    string
+               fields  []arrow.Field
+               wantErr string
+       }{
+               {name: "valid", fields: []arrow.Field{{Name: "file_path", Type: 
arrow.BinaryTypes.String}, {Name: "pos", Type: arrow.PrimitiveTypes.Int64}}},
+               {name: "valid with row", fields: []arrow.Field{{Name: 
"file_path", Type: arrow.BinaryTypes.String}, {Name: "pos", Type: 
arrow.PrimitiveTypes.Int64}, {Name: "row", Type: arrow.BinaryTypes.String}}},
+               {name: "missing file_path", fields: []arrow.Field{{Name: "pos", 
Type: arrow.PrimitiveTypes.Int64}}, wantErr: `exactly one "file_path" column, 
found 0`},
+               {name: "missing pos", fields: []arrow.Field{{Name: "file_path", 
Type: arrow.BinaryTypes.String}}, wantErr: `exactly one "pos" column, found 0`},
+               {name: "missing both", fields: nil, wantErr: `exactly one 
"file_path" column, found 0`},
+               {name: "duplicate pos", fields: []arrow.Field{{Name: 
"file_path", Type: arrow.BinaryTypes.String}, {Name: "pos", Type: 
arrow.PrimitiveTypes.Int64}, {Name: "pos", Type: arrow.PrimitiveTypes.Int64}}, 
wantErr: `exactly one "pos" column, found 2`},
+       }
+
+       for _, test := range tests {
+               t.Run(test.name, func(t *testing.T) {
+                       filePathIndex, posIndex, err := 
positionDeleteColumnIndices(arrow.NewSchema(test.fields, nil))
+                       if test.wantErr != "" {
+                               require.ErrorIs(t, err, 
iceberg.ErrInvalidSchema)
+                               require.ErrorContains(t, err, test.wantErr)
+
+                               return
+                       }
+
+                       require.NoError(t, err)
+                       assert.Equal(t, 0, filePathIndex)

Review Comment:
   Both valid cases assert `filePathIndex == 0` / `posIndex == 1` 
unconditionally, so this only really checks the fixed ordering. A case with 
`pos` before `file_path` (legal for Arrow) with the expected indices 
parameterized per test would prove the lookup resolves by name rather than by 
position. Minor.



##########
table/arrow_scanner_posdelete_regression_test.go:
##########
@@ -32,6 +32,39 @@ import (
        "github.com/stretchr/testify/require"
 )
 
+func TestPositionDeleteColumnIndices(t *testing.T) {
+       t.Parallel()
+
+       tests := []struct {
+               name    string
+               fields  []arrow.Field
+               wantErr string
+       }{
+               {name: "valid", fields: []arrow.Field{{Name: "file_path", Type: 
arrow.BinaryTypes.String}, {Name: "pos", Type: arrow.PrimitiveTypes.Int64}}},
+               {name: "valid with row", fields: []arrow.Field{{Name: 
"file_path", Type: arrow.BinaryTypes.String}, {Name: "pos", Type: 
arrow.PrimitiveTypes.Int64}, {Name: "row", Type: arrow.BinaryTypes.String}}},
+               {name: "missing file_path", fields: []arrow.Field{{Name: "pos", 
Type: arrow.PrimitiveTypes.Int64}}, wantErr: `exactly one "file_path" column, 
found 0`},
+               {name: "missing pos", fields: []arrow.Field{{Name: "file_path", 
Type: arrow.BinaryTypes.String}}, wantErr: `exactly one "pos" column, found 0`},
+               {name: "missing both", fields: nil, wantErr: `exactly one 
"file_path" column, found 0`},
+               {name: "duplicate pos", fields: []arrow.Field{{Name: 
"file_path", Type: arrow.BinaryTypes.String}, {Name: "pos", Type: 
arrow.PrimitiveTypes.Int64}, {Name: "pos", Type: arrow.PrimitiveTypes.Int64}}, 
wantErr: `exactly one "pos" column, found 2`},
+       }
+
+       for _, test := range tests {
+               t.Run(test.name, func(t *testing.T) {
+                       filePathIndex, posIndex, err := 
positionDeleteColumnIndices(arrow.NewSchema(test.fields, nil))

Review Comment:
   This is the part I'd firm up before merge. The test calls 
`positionDeleteColumnIndices` directly, so it proves the helper is correct but 
never exercises the panic path this PR is actually fixing — the lookup inside 
`readDeletes`.
   
   `TestReadDeletesRejectsNullPos` already has the exact infra we need 
(in-memory Parquet via `pqarrow.WriteTable` + `iceio.NewMemFS`). I'd add one 
end-to-end case there: write a delete file whose schema drops `file_path` (or 
`pos`), call `readDeletes`, and assert `errors.Is(err, ErrInvalidSchema)`. That 
locks the fix in at the real entry point — right now if the helper ever got 
inlined or bypassed, nothing here would catch it.



##########
table/arrow_scanner.go:
##########
@@ -449,15 +449,42 @@ func readDeletes(ctx context.Context, fs iceio.IO, 
dataFile iceberg.DataFile) (_
        }
        defer tbl.Release()
 
-       filePathCol := 
tbl.Column(tbl.Schema().FieldIndices("file_path")[0]).Data()
-       posCol := tbl.Column(tbl.Schema().FieldIndices("pos")[0]).Data()
+       filePathIndex, posIndex, err := 
positionDeleteColumnIndices(tbl.Schema())
+       if err != nil {
+               return nil, err
+       }
+       filePathCol := tbl.Column(filePathIndex).Data()
+       posCol := tbl.Column(posIndex).Data()
        if posCol.NullN() > 0 {
                return nil, fmt.Errorf("%w: null pos in position delete file", 
iceberg.ErrInvalidSchema)
        }
 
        return groupPosDeletesByFilePath(ctx, filePathCol, posCol)
 }
 
+func positionDeleteColumnIndices(schema *arrow.Schema) (int, int, error) {
+       requiredColumn := func(name string) (int, error) {
+               indices := schema.FieldIndices(name)

Review Comment:
   Not blocking, and this predates your change — the old `[0]` lookup used 
`FieldIndices` too.
   
   While we're adding validation here though: `file_path` and `pos` are 
reserved metadata columns with spec-assigned field IDs (2147483546 / 
2147483545), and this same file already resolves lineage columns by reserved ID 
via `fieldIndexByID` rather than by name. A compliant writer that keeps the 
field IDs but names the columns differently would hit `ErrInvalidSchema` here 
instead of being read. Switching these two lookups to `fieldIndexByID` would 
make it consistent with the rest of the scanner. I'm fine leaving it name-based 
for this PR — if we do, a one-line comment noting the deliberate name-over-ID 
choice would help the next reader. wdyt?



##########
table/arrow_scanner_posdelete_regression_test.go:
##########
@@ -32,6 +32,39 @@ import (
        "github.com/stretchr/testify/require"
 )
 
+func TestPositionDeleteColumnIndices(t *testing.T) {
+       t.Parallel()
+
+       tests := []struct {
+               name    string
+               fields  []arrow.Field
+               wantErr string
+       }{
+               {name: "valid", fields: []arrow.Field{{Name: "file_path", Type: 
arrow.BinaryTypes.String}, {Name: "pos", Type: arrow.PrimitiveTypes.Int64}}},
+               {name: "valid with row", fields: []arrow.Field{{Name: 
"file_path", Type: arrow.BinaryTypes.String}, {Name: "pos", Type: 
arrow.PrimitiveTypes.Int64}, {Name: "row", Type: arrow.BinaryTypes.String}}},
+               {name: "missing file_path", fields: []arrow.Field{{Name: "pos", 
Type: arrow.PrimitiveTypes.Int64}}, wantErr: `exactly one "file_path" column, 
found 0`},
+               {name: "missing pos", fields: []arrow.Field{{Name: "file_path", 
Type: arrow.BinaryTypes.String}}, wantErr: `exactly one "pos" column, found 0`},
+               {name: "missing both", fields: nil, wantErr: `exactly one 
"file_path" column, found 0`},
+               {name: "duplicate pos", fields: []arrow.Field{{Name: 
"file_path", Type: arrow.BinaryTypes.String}, {Name: "pos", Type: 
arrow.PrimitiveTypes.Int64}, {Name: "pos", Type: arrow.PrimitiveTypes.Int64}}, 
wantErr: `exactly one "pos" column, found 2`},

Review Comment:
   `requiredColumn` handles `file_path` and `pos` symmetrically, but only the 
`pos` duplicate is covered. I'd add the mirror case — `{file_path, file_path, 
pos}` expecting `exactly one "file_path" column, found 2` — so both directions 
are pinned.



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