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


##########
table/equality_delete_reader.go:
##########
@@ -547,12 +677,20 @@ func processEqualityDeletesColumnar(ctx context.Context, 
eqDeleteSets []*equalit
                for _, eqDel := range eqDeleteSets {
                        encoders := make([]colEncoder, len(eqDel.colNames))
                        for i, name := range eqDel.colNames {
-                               indices := r.Schema().FieldIndices(name)
-                               if len(indices) == 0 {
-                                       return nil, fmt.Errorf("equality delete 
column %q not found in data record", name)
+                               fieldID := 0
+                               if i < len(eqDel.fieldIDs) {
+                                       fieldID = eqDel.fieldIDs[i]
                                }
 
-                               encoders[i] = 
makeColEncoder(r.Column(indices[0]))
+                               ref, err := resolveArrowField(r.Schema(), 
fieldID, name, dataFilePath)

Review Comment:
   I'd resolve these once before returning the closure rather than per batch. 
`resolveArrowField` runs on every record batch here, but the schema is constant 
across all batches of a file, so we're repeating the full recursive walk with 
identical inputs each time, and the recursive struct descent makes that more 
expensive than the old flat `FieldIndices` scan it replaced.
   
   `readEqualityDeleteFile` already does this the right way: it resolves the 
`fieldRefs` once up front and reuses them in the batch loop. I'd mirror that 
here, computing the refs above the `return func(...)` and closing over them. 
wdyt?



##########
table/equality_delete_reader.go:
##########
@@ -547,12 +677,20 @@ func processEqualityDeletesColumnar(ctx context.Context, 
eqDeleteSets []*equalit
                for _, eqDel := range eqDeleteSets {
                        encoders := make([]colEncoder, len(eqDel.colNames))
                        for i, name := range eqDel.colNames {
-                               indices := r.Schema().FieldIndices(name)
-                               if len(indices) == 0 {
-                                       return nil, fmt.Errorf("equality delete 
column %q not found in data record", name)
+                               fieldID := 0

Review Comment:
   This defensive guard quietly makes the ID-first resolution optional. When `i 
>= len(eqDel.fieldIDs)`, `fieldID` stays `0`, so `resolveArrowField` skips the 
`fieldID > 0` branch entirely and falls back to name-only lookup, which is the 
behavior this PR is trying to get away from.
   
   Today `fieldIDs` and `colNames` are always co-built at equal length so this 
never trips, but if that invariant ever slips we'd silently lose ID matching 
with no error to point at. I'd drop the guard and index `eqDel.fieldIDs[i]` 
directly, or assert the two are equal-length at construction, so a mismatch 
fails loudly instead of degrading. wdyt?



##########
table/equality_delete_reader.go:
##########
@@ -350,7 +476,7 @@ func encodeArrowValue(buf *bytes.Buffer, arr arrow.Array, 
idx int) {
 // rows whose equality key columns match any entry in the delete sets.
 // Each set is applied independently (they may have different field IDs).
 func processEqualityDeletes(ctx context.Context, eqDeleteSets 
[]*equalityDeleteSet) (recProcessFn, error) {
-       return processEqualityDeletesColumnar(ctx, eqDeleteSets)
+       return processEqualityDeletesColumnarForFile(ctx, eqDeleteSets, "")

Review Comment:
   Now that `arrow_scanner.go` calls `processEqualityDeletesColumnarForFile` 
directly, this wrapper (and `processEqualityDeletesColumnar` just below) has no 
production callers left: `processEqualityDeletes` is dead, and 
`processEqualityDeletesColumnar` only survives because the internal test calls 
it.
   
   I'd collapse both into `processEqualityDeletesColumnarForFile` and point the 
test at it with a synthetic path. That keeps one entry point and stops the 
bench in `equality_delete_reader_bench_test.go` from quietly exercising the 
empty-path variant instead of the real one.



##########
table/equality_delete_reader.go:
##########
@@ -46,6 +50,124 @@ type equalityDeleteSet struct {
        colNames []string
 }
 
+type arrowFieldRef struct {
+       path []int
+}
+
+func resolveArrowField(schema *arrow.Schema, fieldID int, fieldName, filePath 
string) (arrowFieldRef, error) {
+       type candidate struct {
+               ref           arrowFieldRef
+               pathName      string
+               hasIDMetadata bool
+       }
+
+       var (
+               idMatches   []candidate
+               nameMatches []candidate
+               pathMatches []candidate
+       )
+       targetName := fieldName
+       if dot := strings.LastIndexByte(targetName, '.'); dot >= 0 {
+               targetName = targetName[dot+1:]
+       }
+
+       var visit func([]arrow.Field, []int, string)
+       visit = func(fields []arrow.Field, parentPath []int, parentName string) 
{
+               for i, field := range fields {
+                       path := append(append([]int(nil), parentPath...), i)
+                       pathName := field.Name
+                       if parentName != "" {
+                               pathName = parentName + "." + field.Name
+                       }
+                       fieldIDValue := getFieldID(field)
+                       fieldHasIDMetadata := fieldIDValue != nil
+
+                       if fieldIDValue != nil && *fieldIDValue == fieldID {
+                               idMatches = append(idMatches, candidate{ref: 
arrowFieldRef{path: path}, pathName: pathName, hasIDMetadata: 
fieldHasIDMetadata})
+                       }
+                       if field.Name == targetName {
+                               match := candidate{ref: arrowFieldRef{path: 
path}, pathName: pathName, hasIDMetadata: fieldHasIDMetadata}
+                               nameMatches = append(nameMatches, match)
+                               if pathName == fieldName {
+                                       pathMatches = append(pathMatches, match)
+                               }
+                       }
+
+                       if nested, ok := field.Type.(*arrow.StructType); ok {
+                               visit(nested.Fields(), path, pathName)
+                       }
+               }
+       }
+       visit(schema.Fields(), nil, "")
+
+       location := filePath
+       if location == "" {
+               location = "data record"
+       }
+
+       if fieldID > 0 {
+               switch len(idMatches) {
+               case 1:
+                       return idMatches[0].ref, nil
+               case 0:

Review Comment:
   The empty `case 0:` reads like a dropped line. It took me a second to see 
that zero ID matches is meant to fall through to the name-based resolution 
below rather than being an unfinished branch, and revive's empty-block check 
will likely flag it too.
   
   I'd add a `// zero ID matches, fall through to name resolution` line inside 
the case, or drop the switch for explicit `if len(idMatches) == 1 {...}` / `if 
len(idMatches) > 1 {...}`. Either's fine, just something that signals the 
fall-through is intentional.



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