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 3bef1a871 fix(manifest): reject unsupported format versions (#1612)
3bef1a871 is described below
commit 3bef1a8717e2338b4d516bd2fb5b2567522046b9
Author: Minh Vu <[email protected]>
AuthorDate: Tue Aug 4 12:19:01 2026 +0200
fix(manifest): reject unsupported format versions (#1612)
## What changed
Validate manifest and manifest-list format versions before selecting a
reader schema. Versions 1, 2, and 3 remain supported, and a missing
version still defaults to version 1.
## Why
Manifest lists previously routed every version other than 1 through the
v2/v3 reader. Manifest files also accepted arbitrary integers. Unknown
versions could therefore be interpreted with a schema whose semantics
may not match the file.
## Testing
- Added manifest and manifest-list cases for versions -1, 0, and 4
- Existing version 1, 2, 3, and missing-version coverage remains intact
- `go test ./...`
---------
Signed-off-by: Minh Vu <[email protected]>
---
manifest.go | 18 ++++++++++++++++--
manifest_test.go | 45 +++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 61 insertions(+), 2 deletions(-)
diff --git a/manifest.go b/manifest.go
index 532660b7b..d41c008b2 100644
--- a/manifest.go
+++ b/manifest.go
@@ -546,10 +546,10 @@ type hasFieldToIDMap interface {
setFieldIDToDecimalScaleMap(map[int]int)
}
-// ManifestFile is the interface which covers both V1 and V2 manifest files.
+// ManifestFile is the interface for version 1, 2, and 3 manifest files.
type ManifestFile interface {
// Version returns the version number of this manifest file.
- // It should be 1 or 2.
+ // It must be 1, 2, or 3.
Version() int
// FilePath is the location URI of this manifest file.
FilePath() string
@@ -744,6 +744,9 @@ func NewManifestReader(file ManifestFile, in io.Reader)
(*ManifestReader, error)
return nil, fmt.Errorf("manifest file's
'format-version' metadata is invalid: %w", err)
}
}
+ if err := validateManifestFormatVersion(formatVersion); err != nil {
+ return nil, err
+ }
// The manifest's own metadata is authoritative for its version. A v2/v3
// manifest list may reference older manifests so a table can be
upgraded
// in place without rewriting history; only a manifest newer than the
list
@@ -1015,6 +1018,9 @@ func ReadManifestList(in io.Reader) ([]ManifestFile,
error) {
version = v
}
+ if err := validateManifestFormatVersion(version); err != nil {
+ return nil, err
+ }
if version == 1 {
return manifestFileV1Reader, nil
@@ -1033,6 +1039,14 @@ func ReadManifestList(in io.Reader) ([]ManifestFile,
error) {
return decodeManifests[*manifestFile](rd, version)
}
+func validateManifestFormatVersion(version int) error {
+ if version < 1 || version > 3 {
+ return fmt.Errorf("unsupported manifest format version: %d",
version)
+ }
+
+ return nil
+}
+
type writerImpl interface {
prepareEntry(*manifestEntry, int64) (ManifestEntry, error)
}
diff --git a/manifest_test.go b/manifest_test.go
index ffb163699..002b5fd37 100644
--- a/manifest_test.go
+++ b/manifest_test.go
@@ -1026,6 +1026,23 @@ func (m *ManifestTestSuite)
TestReadManifestListMissingFormatVersion() {
m.Empty(files) // the file has no entries, just headers
}
+func (m *ManifestTestSuite)
TestReadManifestListRejectsUnsupportedFormatVersion() {
+ for _, version := range []int{-1, 0, 4} {
+ m.Run(strconv.Itoa(version), func() {
+ fileSchema, err := internal.NewManifestFileSchema(2)
+ m.Require().NoError(err)
+ var buf bytes.Buffer
+ writer, err := ocf.NewWriter(&buf, fileSchema,
+
ocf.WithMetadata(map[string][]byte{"format-version":
[]byte(strconv.Itoa(version))}))
+ m.Require().NoError(err)
+ m.Require().NoError(writer.Close())
+
+ _, err = ReadManifestList(&buf)
+ m.ErrorContains(err, "unsupported manifest format
version")
+ })
+ }
+}
+
// writeManifestNoFormatVersion writes a valid v1 manifest entry Avro file that
// omits the "format-version" metadata key, simulating files produced by the
Java
// Iceberg library (format-version is optional for v1 per the Iceberg spec).
@@ -1079,6 +1096,34 @@ func (m *ManifestTestSuite)
TestNewManifestReaderMissingFormatVersion() {
m.NoError(reader.Close())
}
+func (m *ManifestTestSuite)
TestNewManifestReaderRejectsUnsupportedFormatVersion() {
+ for _, version := range []int{-1, 0, 4} {
+ m.Run(strconv.Itoa(version), func() {
+ spec := NewPartitionSpec()
+ partitionSchema, err :=
partitionTypeToAvroSchema(spec.PartitionType(testSchema))
+ m.Require().NoError(err)
+ entrySchema, err :=
internal.NewManifestEntrySchema(partitionSchema, 1)
+ m.Require().NoError(err)
+ schemaJSON, err := json.Marshal(testSchema)
+ m.Require().NoError(err)
+ var manifest bytes.Buffer
+ writer, err := ocf.NewWriter(&manifest, entrySchema,
ocf.WithMetadata(map[string][]byte{
+ "format-version":
[]byte(strconv.Itoa(version)),
+ "schema": schemaJSON,
+ "schema-id":
[]byte(strconv.Itoa(testSchema.ID)),
+ "partition-spec": []byte("[]"),
+ "partition-spec-id": []byte("0"),
+ "content": []byte("data"),
+ }))
+ m.Require().NoError(err)
+ m.Require().NoError(writer.Close())
+
+ _, err = NewManifestReader(&manifestFile{version:
version}, &manifest)
+ m.ErrorContains(err, "unsupported manifest format
version")
+ })
+ }
+}
+
func (m *ManifestTestSuite) TestV3DataManifestFirstRowIDInheritance() {
// Build a v3 data manifest with two entries that have null
first_row_id.
partitionSpec := NewPartitionSpecID(1,