-
Notifications
You must be signed in to change notification settings - Fork 231
fix(table): allocate partition IDs from history #1988
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
abc35da
ebf0a16
e01dbb2
c9bae41
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,7 @@ import ( | |
| "strings" | ||
| "testing" | ||
|
|
||
| "github.com/apache/iceberg-go" | ||
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
@@ -85,6 +86,55 @@ func TestParseMetadataBytesAssignsMissingPartitionFieldIDs(t *testing.T) { | |
| } | ||
| } | ||
|
|
||
| func TestParseMetadataBytesPreservesStaleLastPartitionIDForCommit(t *testing.T) { | ||
| data := strings.Replace(ExampleTableMetadataV2, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit — End-to-end allocation half of the issue's suggested regression coverage is not asserted Issue #1987 asks for coverage that asserts both that the parsed counter becomes 1000 and that the next distinct partition field receives 1001. TestParseMetadataBytesNormalizesStaleLastPartitionID asserts only the former. I verified the latter holds today, so this is purely about locking in the user-visible symptom (the cross-spec ID collision) rather than only its parse-level cause.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This only exercises the fix if the Replace actually lands. If the fixture spacing ever changes and the replace silently no-ops, last-partition-id stays 1000 and both assertions pass without touching the fix. I'd add a require.Contains right after, asserting the "last-partition-id": 999 substring is present in data, so a missed replace fails loudly.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Good call—added explicit fixture guards so silent replacement failures go red. |
||
| `"last-partition-id": 1000`, `"last-partition-id": 999`, 1) | ||
| data = strings.Replace(data, | ||
| `"default-spec-id": 0,`, `"default-spec-id": 1,`, 1) | ||
| data = strings.Replace(data, | ||
| `"partition-specs": [{"spec-id": 0, "fields": [{"name": "x", "transform": "identity", "source-id": 1, "field-id": 1000}]}],`, | ||
| `"partition-specs": [{"spec-id": 0, "fields": [{"name": "x", "transform": "identity", "source-id": 1, "field-id": 1000}]}, {"spec-id": 1, "fields": []}],`, 1) | ||
| require.Contains(t, data, `"last-partition-id": 999`) | ||
| require.Contains(t, data, `"spec-id": 1`) | ||
|
|
||
| parsed, err := ParseMetadataBytes([]byte(data)) | ||
| require.NoError(t, err) | ||
| require.NotNil(t, parsed.LastPartitionSpecID()) | ||
| assert.Equal(t, 999, *parsed.LastPartitionSpecID()) | ||
|
|
||
| update := NewUpdateSpec(New(nil, parsed, "", nil, nil).NewTransaction(), false). | ||
| AddField("x", iceberg.BucketTransform{NumBuckets: 16}, "x_bucket") | ||
| _, requirements, err := update.BuildUpdates() | ||
| require.NoError(t, err) | ||
| assert.Equal(t, []int{999}, lastAssignedPartitionAssertions(requirements)) | ||
| updated, err := update.Apply() | ||
| require.NoError(t, err) | ||
| require.Equal(t, 1, updated.NumFields()) | ||
| assert.Equal(t, 1001, updated.Field(0).FieldID) | ||
| } | ||
|
|
||
| func TestAssignMissingPartitionFieldIDsPreservesConsistentMetadata(t *testing.T) { | ||
| for _, tt := range []struct { | ||
| name string | ||
| input string | ||
| }{ | ||
| { | ||
| name: "counter below assignment floor with no fields", | ||
| input: `{"last-updated-ms":0,"last-partition-id":0,"partition-specs":[{"spec-id":0,"fields":[]}]}`, | ||
| }, | ||
| { | ||
| name: "counter above greatest field ID", | ||
| input: `{"last-updated-ms":0,"last-partition-id":1001,"partition-specs":[{"spec-id":0,"fields":[{"field-id":1000}]}]}`, | ||
| }, | ||
| } { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| normalized, err := assignMissingPartitionFieldIDs([]byte(tt.input)) | ||
| require.NoError(t, err) | ||
| assert.Equal(t, tt.input, string(normalized)) | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func TestParseMetadataBytesRejectsCaseFoldedFormatVersionCollision(t *testing.T) { | ||
| data := strings.Replace( | ||
| ExampleTableMetadataV2, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor — New early-return condition adds three branches, only one is tested
The condition at metadata.go:2047 introduces distinct branches: counter below max field ID (tested), counter above max field ID (must stay untouched), counter below the 999 floor with no field IDs, and stale counter combined with a missing field-id. Only the first has a test. I verified the untested ones behave as follows -- add cases for them so the condition is pinned: counter-above-max stays untouched, and stale-counter-plus-missing-field-id assigns correctly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for identifying the missing branches. Added coverage in ebf0a16 for sub-999 counters with no assigned fields, counters above the greatest assigned field ID, and stale counters combined with a missing field ID. The first two cases also verify the unchanged-byte fast path.