laskoviymishka commented on code in PR #1902:
URL: https://github.com/apache/iceberg-go/pull/1902#discussion_r3874734366
##########
table/equality_delete_reader.go:
##########
@@ -183,10 +183,19 @@ func makeArrowFieldEncoder(record arrow.RecordBatch, ref
arrowFieldRef, fieldID
}
type equalityDeleteFileSet struct {
- id int
+ id int
+ groupKey string
*equalityDeleteSet
}
+func newEqualityDeleteFileSet(id int, deleteSet *equalityDeleteSet)
*equalityDeleteFileSet {
+ return &equalityDeleteFileSet{
+ id: id,
+ groupKey: fmt.Sprint(deleteSet.fieldIDs),
Review Comment:
Caching the key here so it's computed once per unique delete file is the
right call.
One thing worth flagging while this is the single place the key gets built:
`fmt.Sprint(fieldIDs)` is order-sensitive, so `[1 2]` and `[2 1]` land in
separate groups, whereas Java keys on a `Set<Integer>` (via
`TypeUtil.selectInIdOrder`) and treats any permutation of the same IDs as one
group. Two delete files covering the same equality columns but storing
`equality_ids` in different orders would merge under Java and split here. It's
pre-existing, not something this PR introduces, but since the key is now
canonicalized to one spot, sorting `fieldIDs` ascending before the `Sprint`
(and before `colNames`) would close the gap cheaply and match Java. At minimum
a comment that the format has to stay collision-free for `[]int` would help.
wdyt?
##########
table/equality_delete_reader.go:
##########
@@ -298,16 +304,45 @@ func buildEqualityDeleteSetsPerTask(
continue
}
- // Group delete files by their field IDs key.
- groups := make(map[string][]*equalityDeleteFileSet)
+ var (
+ groupKey string
+ groupFiles []*equalityDeleteFileSet
+ groups map[string][]*equalityDeleteFileSet
+ )
+
for _, d := range t.EqualityDeleteFiles {
dk, ok := perFile[d.FilePath()]
if !ok {
continue
}
- groupKey := fmt.Sprint(dk.fieldIDs)
- groups[groupKey] = append(groups[groupKey], dk)
+ if groups == nil {
+ if len(groupFiles) == 0 {
+ groupKey = dk.groupKey
+ } else if dk.groupKey != groupKey {
+ groups =
make(map[string][]*equalityDeleteFileSet, 2)
+ groups[groupKey] = groupFiles
+ }
+ }
+
+ if groups == nil {
+ groupFiles = append(groupFiles, dk)
+ } else {
+ groups[dk.groupKey] =
append(groups[dk.groupKey], dk)
Review Comment:
This path, a file with the original key arriving after the map's been
promoted, has no test behind it. Neither existing test builds a task where
`groupFiles` holds more than one file at the moment of promotion, or where a
later file lands back on the already-migrated key.
I'd add two cases to
`TestBuildEqualityDeleteSetsPerTaskKeepsFieldGroupsSeparate`: `[A=[1], B=[1],
C=[2]]` (two group-1 files accumulate, then `groups["[1]"] = groupFiles`
assigns a 2-element slice), and `[A=[1], B=[2], C=[1]]` (promotion fires on B,
then C appends back through this line into the migrated `[1]` group). Assert
the `[1]` group ends up with both A and C in the second case.
That second layout is the one I care about most. It's the only path where
the migrated key takes a further append, and it's exactly what a future edit to
the promotion block could silently break.
##########
table/equality_delete_reader.go:
##########
@@ -298,16 +304,45 @@ func buildEqualityDeleteSetsPerTask(
continue
}
- // Group delete files by their field IDs key.
- groups := make(map[string][]*equalityDeleteFileSet)
+ var (
+ groupKey string
+ groupFiles []*equalityDeleteFileSet
+ groups map[string][]*equalityDeleteFileSet
+ )
+
for _, d := range t.EqualityDeleteFiles {
dk, ok := perFile[d.FilePath()]
if !ok {
continue
}
- groupKey := fmt.Sprint(dk.fieldIDs)
- groups[groupKey] = append(groups[groupKey], dk)
+ if groups == nil {
+ if len(groupFiles) == 0 {
+ groupKey = dk.groupKey
+ } else if dk.groupKey != groupKey {
+ groups =
make(map[string][]*equalityDeleteFileSet, 2)
+ groups[groupKey] = groupFiles
+ }
+ }
+
+ if groups == nil {
+ groupFiles = append(groupFiles, dk)
+ } else {
+ groups[dk.groupKey] =
append(groups[dk.groupKey], dk)
+ }
+ }
+
+ if groups == nil {
+ if len(groupFiles) == 0 {
+ continue
Review Comment:
This new early-`continue` (a task whose delete files are all absent from
`perFile`) isn't hit by either existing test, since both always match. A
one-line case with a task whose delete paths aren't keys in `perFile`,
asserting `perTask` has no entry for that index, would lock it in. Fine as a
follow-up.
##########
table/equality_delete_reader.go:
##########
@@ -298,16 +304,45 @@ func buildEqualityDeleteSetsPerTask(
continue
}
- // Group delete files by their field IDs key.
- groups := make(map[string][]*equalityDeleteFileSet)
+ var (
+ groupKey string
+ groupFiles []*equalityDeleteFileSet
+ groups map[string][]*equalityDeleteFileSet
+ )
+
for _, d := range t.EqualityDeleteFiles {
dk, ok := perFile[d.FilePath()]
if !ok {
continue
}
- groupKey := fmt.Sprint(dk.fieldIDs)
- groups[groupKey] = append(groups[groupKey], dk)
+ if groups == nil {
Review Comment:
These two consecutive `if groups == nil` blocks read like independent
guards, but they aren't. The first can flip `groups` from nil to a live map,
and the second observes that flip to decide where the current file goes. The
transition branch deliberately doesn't append `dk` itself; it relies on
fall-through to the second check to do it, which is the hardest part to see,
and a stray `continue` or a hoisted block would silently break the invariant
that each file is added exactly once.
An `if/else if/else` makes the three per-iteration paths explicit, and lets
us null out `groupFiles` right after it's handed to the map so nothing can
later append into the shared backing array:
```go
if groups != nil {
groups[dk.groupKey] = append(groups[dk.groupKey], dk)
} else if len(groupFiles) == 0 {
groupKey = dk.groupKey
groupFiles = append(groupFiles, dk)
} else if dk.groupKey != groupKey {
groups = make(map[string][]*equalityDeleteFileSet, 2)
groups[groupKey] = groupFiles
groupFiles = nil
groups[dk.groupKey] = append(groups[dk.groupKey], dk)
} else {
groupFiles = append(groupFiles, dk)
}
```
wdyt?
--
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]