This is an automated email from the ASF dual-hosted git repository.
zeroshade pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-go.git
The following commit(s) were added to refs/heads/main by this push:
new 76e295ceb refactor(metadata)!: name the row-lineage column predicate
explicitly (#2338)
76e295ceb is described below
commit 76e295ceb369efd0e50c8249d13eb70f150b3b0c
Author: Minh Vu <[email protected]>
AuthorDate: Wed Oct 28 18:11:31 2026 +0100
refactor(metadata)!: name the row-lineage column predicate explicitly
(#2338)
Signed-off-by: Minh Vu <[email protected]>
---
metadata_columns.go | 7 +++--
metadata_columns_test.go | 47 +++++++++++++++++++++++++++++++++
table/internal/parquet_files.go | 2 +-
table/metadata_builder_internal_test.go | 2 +-
table/metadata_schema_compatibility.go | 2 +-
website/src/releases.md | 1 +
6 files changed, 56 insertions(+), 5 deletions(-)
diff --git a/metadata_columns.go b/metadata_columns.go
index 3e2fe4577..eaceb857a 100644
--- a/metadata_columns.go
+++ b/metadata_columns.go
@@ -82,8 +82,11 @@ func LastUpdatedSequenceNumber() NestedField {
}
}
-// IsMetadataColumn returns true if the field ID is a reserved metadata column
(e.g. row lineage).
-func IsMetadataColumn(fieldID int) bool {
+// IsRowLineageColumn reports whether fieldID is one of the two row-lineage
+// metadata columns: _row_id or _last_updated_sequence_number.
+// Other reserved metadata field IDs return false. Use [IsReservedFieldID]
+// to check the full range reserved for metadata columns.
+func IsRowLineageColumn(fieldID int) bool {
return fieldID == RowIDFieldID || fieldID ==
LastUpdatedSequenceNumberFieldID
}
diff --git a/metadata_columns_test.go b/metadata_columns_test.go
new file mode 100644
index 000000000..14b225353
--- /dev/null
+++ b/metadata_columns_test.go
@@ -0,0 +1,47 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package iceberg
+
+import (
+ "math"
+ "testing"
+)
+
+func TestIsRowLineageColumn(t *testing.T) {
+ tests := []struct {
+ name string
+ fieldID int
+ want bool
+ }{
+ {name: "row ID", fieldID: RowIDFieldID, want: true},
+ {name: "last updated sequence", fieldID:
LastUpdatedSequenceNumberFieldID, want: true},
+ {name: "position delete file path", fieldID:
DeleteFilePathFieldID},
+ {name: "position delete position", fieldID:
DeleteFilePosFieldID},
+ {name: "file metadata column", fieldID: math.MaxInt32 - 1},
+ {name: "other reserved ID", fieldID: MaxStructFieldID + 1},
+ {name: "user column", fieldID: 1},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := IsRowLineageColumn(tt.fieldID); got !=
tt.want {
+ t.Errorf("IsRowLineageColumn(%d) = %t, want
%t", tt.fieldID, got, tt.want)
+ }
+ })
+ }
+}
diff --git a/table/internal/parquet_files.go b/table/internal/parquet_files.go
index 3abe24fc1..c1cc61c37 100644
--- a/table/internal/parquet_files.go
+++ b/table/internal/parquet_files.go
@@ -1672,7 +1672,7 @@ func resolveParquetStatsColumns(meta
*metadata.FileMetaData, statsCols map[int]S
// _last_updated_sequence_number) may legitimately
appear in a file
// without a metrics-plan entry. Writers materialize
them without
// adding them to the table schema, so skip them
entirely.
- if iceberg.IsMetadataColumn(fieldID) {
+ if iceberg.IsRowLineageColumn(fieldID) {
descriptor.skipStats = true
} else {
descriptor.resolveErr = fmt.Errorf("field id %d
(column %q) not found in the metrics plan", fieldID, descriptor.path)
diff --git a/table/metadata_builder_internal_test.go
b/table/metadata_builder_internal_test.go
index 375ed99ef..a740c6f44 100644
--- a/table/metadata_builder_internal_test.go
+++ b/table/metadata_builder_internal_test.go
@@ -3036,7 +3036,7 @@ func TestUnknownTypeValidation(t *testing.T) {
// reservedNonLineageFieldID is a spec-reserved metadata column ID (_file) that
// is not one of the row-lineage columns, exercising the full reserved range
-// rather than just iceberg.IsMetadataColumn.
+// rather than just iceberg.IsRowLineageColumn.
const reservedNonLineageFieldID = 2147483646
func TestValidateNoReservedFieldIDs(t *testing.T) {
diff --git a/table/metadata_schema_compatibility.go
b/table/metadata_schema_compatibility.go
index 62599575e..4c8dc26e7 100644
--- a/table/metadata_schema_compatibility.go
+++ b/table/metadata_schema_compatibility.go
@@ -140,7 +140,7 @@ func checkSchemaCompatibility(sc *iceberg.Schema,
formatVersion int) error {
// throwaway metadata from those schemas via AddSchema. User
schemas are
// validated against the full reserved range separately, before
// reassignIDs, in NewMetadataWithUUID.
- if iceberg.IsMetadataColumn(field.ID) {
+ if iceberg.IsRowLineageColumn(field.ID) {
return fmt.Errorf("%w: field '%s' uses reserved
metadata column ID %d",
iceberg.ErrInvalidSchema, colName, field.ID)
}
diff --git a/website/src/releases.md b/website/src/releases.md
index 80cf90d01..5e74577c3 100644
--- a/website/src/releases.md
+++ b/website/src/releases.md
@@ -78,6 +78,7 @@ Per-release notes (highlights, breaking changes,
contributors) are published on
- **Partition and sort source accessors renamed.** Replace
`PartitionField.SourceID()` and `SortField.SourceID()` with `FirstSourceID()`.
Their behavior and serialized fields are unchanged.
+- **Row-lineage column predicate renamed.** Use `iceberg.IsRowLineageColumn`
instead of `iceberg.IsMetadataColumn` to identify `_row_id` and
`_last_updated_sequence_number`. Use `iceberg.IsReservedFieldID` to check the
full reserved metadata field ID range.
- **File writes and directory walks now take an operation context.** Update
`WriteFileIO` and `ListableIO` implementations and calls to `Create`,
`WriteFile`, and `WalkDir` to pass `context.Context`; cloud writes and listings
use it for cancellation.
## Verifying and producing releases