You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by at...@apache.org on 2026/07/09 17:02:50 UTC
(iceberg-go) branch main updated: fix: validate format-version in metadata constructors (#1387)
This is an automated email from the ASF dual-hosted git repository.
laskoviymishka 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 d93d43b9 fix: validate format-version in metadata constructors (#1387)
d93d43b9 is described below
commit d93d43b9dfa94d46b18001b5c6fe0bf851d69137
Author: Minh Vu <vu...@gmail.com>
AuthorDate: Thu Jul 9 19:02:44 2026 +0200
fix: validate format-version in metadata constructors (#1387)
## Why
Table and view metadata creation accepted invalid `format-version`
values silently. A bad property like `format-version: banana` currently
fell back to the default version and also mutated the caller's props
map, which can hide user mistakes and corrupt input ownership.
## What changed
- table metadata (`table/metadata.go`):
- In `NewMetadataWithUUID`, `format-version` is now parsed with strict
`Atoi` validation.
- Invalid values now return `iceberg.ErrInvalidFormatVersion` instead of
silently defaulting.
- `format-version` is removed from a **cloned** props map before passing
into metadata builder, so caller-provided maps are not mutated.
- view metadata (`view/metadata.go`):
- Mirrored the same strict validation in view metadata creation.
- Invalid values now return `iceberg.ErrInvalidFormatVersion`.
- Uses a cloned props map to avoid external mutation.
## Testing
- `go test ./table -run
'TestNewMetadataRejectInvalidFormatVersion|TestNewMetadataWithExplicitV1Format|TestNewMetadataV2Format'
-count=1`
- `go test ./view -run
'TestNewMetadataRejectInvalidFormatVersion|TestNewMetadata|TestUnmarshalViewMetadata'
-count=1`
---
table/metadata.go | 10 ++++++----
table/metadata_internal_test.go | 16 ++++++++++++++++
view/metadata.go | 14 ++++++++++----
view/metadata_test.go | 38 ++++++++++++++++++++++++++++++++++++++
4 files changed, 70 insertions(+), 8 deletions(-)
diff --git a/table/metadata.go b/table/metadata.go
index 9cf7481a..81b44b04 100644
--- a/table/metadata.go
+++ b/table/metadata.go
@@ -2205,15 +2205,17 @@ func NewMetadataWithUUID(sc *iceberg.Schema, partitions *iceberg.PartitionSpec,
if tableUuid == uuid.Nil {
tableUuid = uuid.New()
}
+ var inputProps iceberg.Properties
var err error
formatVersion := DefaultFormatVersion
if props != nil {
- verStr, ok := props[PropertyFormatVersion]
+ inputProps = maps.Clone(props)
+ verStr, ok := inputProps[PropertyFormatVersion]
if ok {
if formatVersion, err = strconv.Atoi(verStr); err != nil {
- formatVersion = DefaultFormatVersion
+ return nil, fmt.Errorf("%w: %s", iceberg.ErrInvalidFormatVersion, verStr)
}
- delete(props, PropertyFormatVersion)
+ delete(inputProps, PropertyFormatVersion)
}
}
@@ -2259,7 +2261,7 @@ func NewMetadataWithUUID(sc *iceberg.Schema, partitions *iceberg.PartitionSpec,
return nil, err
}
- if err = builder.SetProperties(props); err != nil {
+ if err = builder.SetProperties(inputProps); err != nil {
return nil, err
}
diff --git a/table/metadata_internal_test.go b/table/metadata_internal_test.go
index b890201c..1a36d612 100644
--- a/table/metadata_internal_test.go
+++ b/table/metadata_internal_test.go
@@ -780,6 +780,22 @@ func TestNewMetadataWithExplicitV1Format(t *testing.T) {
assert.Truef(t, expected.Equals(actual), "expected: %s\ngot: %s", expected, actual)
}
+func TestNewMetadataRejectInvalidFormatVersion(t *testing.T) {
+ props := iceberg.Properties{
+ "format-version": "banana",
+ "foo": "bar",
+ }
+ schema := iceberg.NewSchema(1, iceberg.NestedField{ID: 1, Name: "x", Type: iceberg.PrimitiveTypes.Int64})
+
+ metadata, err := NewMetadata(schema, iceberg.UnpartitionedSpec, UnsortedSortOrder, "s3://bucket/path", props)
+ require.Error(t, err)
+ require.ErrorIs(t, err, iceberg.ErrInvalidFormatVersion)
+ assert.Nil(t, metadata)
+ assert.Equal(t, "banana", props["format-version"])
+ assert.Equal(t, "bar", props["foo"])
+ assert.Len(t, props, 2)
+}
+
func TestNewMetadataV2Format(t *testing.T) {
schema := iceberg.NewSchemaWithIdentifiers(10,
[]int{22},
diff --git a/view/metadata.go b/view/metadata.go
index 06590c71..188a4186 100644
--- a/view/metadata.go
+++ b/view/metadata.go
@@ -518,18 +518,24 @@ func NewMetadataWithUUID(version *Version, sc *iceberg.Schema, location string,
viewUUID = uuid.New()
}
+ var inputProps iceberg.Properties
formatVersion := DefaultViewFormatVersion
if props != nil {
- verStr, ok := props["format-version"]
+ inputProps = maps.Clone(props)
+ verStr, ok := inputProps[table.PropertyFormatVersion]
if ok {
var err error
if formatVersion, err = strconv.Atoi(verStr); err != nil {
- formatVersion = DefaultViewFormatVersion
+ return nil, fmt.Errorf("%w: %s", iceberg.ErrInvalidFormatVersion, verStr)
}
- delete(props, "format-version")
+ delete(inputProps, table.PropertyFormatVersion)
}
}
+ if formatVersion > SupportedViewFormatVersion {
+ return nil, fmt.Errorf("%w: %d", iceberg.ErrInvalidFormatVersion, formatVersion)
+ }
+
// We assume that this constructor is used for building metadata for a new view.
// Thus, we enforce that the VersionID is the initial one defined in the spec.
if version.VersionID != InitialVersionID {
@@ -547,7 +553,7 @@ func NewMetadataWithUUID(version *Version, sc *iceberg.Schema, location string,
SetFormatVersion(formatVersion).
SetUUID(viewUUID).
SetLoc(location).
- SetProperties(props).
+ SetProperties(inputProps).
SetCurrentVersion(version, sc).
Build()
}
diff --git a/view/metadata_test.go b/view/metadata_test.go
index 7561ac74..c85f9114 100644
--- a/view/metadata_test.go
+++ b/view/metadata_test.go
@@ -25,6 +25,7 @@ import (
"testing"
"github.com/apache/iceberg-go"
+ "github.com/apache/iceberg-go/table"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -88,6 +89,43 @@ func TestNewMetadata(t *testing.T) {
assert.Equal(t, []VersionLogEntry{{TimestampMS: 1000, VersionID: 1}}, md.VersionLog())
}
+func TestNewMetadataRejectInvalidFormatVersion(t *testing.T) {
+ tests := []struct {
+ name string
+ formatVer string
+ }{
+ {
+ name: "non-numeric format-version",
+ formatVer: "banana",
+ },
+ {
+ name: "unsupported format-version",
+ formatVer: "2",
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ version := newTestVersion(1, LastAddedID,
+ WithVersionSummary(VersionSummary{"summary-key": "summary-val"}),
+ WithTimestampMS(1000))
+ schema := newTestSchema(0)
+ props := iceberg.Properties{
+ table.PropertyFormatVersion: tc.formatVer,
+ "foo": "bar",
+ }
+
+ md, err := NewMetadata(version, schema, "location", props)
+ require.Error(t, err)
+ require.ErrorIs(t, err, iceberg.ErrInvalidFormatVersion)
+ require.Nil(t, md)
+ assert.Equal(t, tc.formatVer, props[table.PropertyFormatVersion])
+ assert.Equal(t, "bar", props["foo"])
+ assert.Len(t, props, 2)
+ })
+ }
+}
+
func TestUnmarshalViewMetadata(t *testing.T) {
md, err := ParseMetadataString(exampleViewJSON)
require.NoError(t, err)