diff --git a/crates/iceberg/src/spec/manifest/metadata.rs b/crates/iceberg/src/spec/manifest/metadata.rs index 25e4ae7e06..0dd24e138c 100644 --- a/crates/iceberg/src/spec/manifest/metadata.rs +++ b/crates/iceberg/src/spec/manifest/metadata.rs @@ -80,13 +80,29 @@ impl ManifestMetadata { "partition-spec is required in manifest metadata but not found", ) })?; - serde_json::from_slice::>(bs).map_err(|err| { - Error::new( - ErrorKind::DataInvalid, - "Fail to parse partition spec in manifest metadata", - ) - .with_source(err) - })? + // Accept either shape that Iceberg writers produce here: + // - bare JSON array `[{...}]` — historical iceberg-rust shortcut + // - spec-compliant object `{"spec-id":N,"fields":[{...}]}` — + // iceberg-java / iceberg-cpp / pyiceberg + // Previously only the bare array was accepted, which made + // fast_append fail against tables where any non-rust writer + // had committed before (the parent-manifest load for the + // duplicate-file check tripped on the object shape). + serde_json::from_slice::>(bs) + .or_else(|_| { + #[derive(serde::Deserialize)] + struct PartitionSpecJson { + fields: Vec, + } + serde_json::from_slice::(bs).map(|s| s.fields) + }) + .map_err(|err| { + Error::new( + ErrorKind::DataInvalid, + "Fail to parse partition spec in manifest metadata", + ) + .with_source(err) + })? }; let spec_id = meta .get("partition-spec-id") diff --git a/crates/iceberg/src/spec/manifest/writer.rs b/crates/iceberg/src/spec/manifest/writer.rs index 1b3b605fd8..0eb5d42588 100644 --- a/crates/iceberg/src/spec/manifest/writer.rs +++ b/crates/iceberg/src/spec/manifest/writer.rs @@ -428,7 +428,12 @@ impl ManifestWriter { )?; avro_writer.add_user_metadata( "partition-spec".to_string(), - to_vec(&self.metadata.partition_spec.fields()).map_err(|err| { + // Serialize the full PartitionSpec object ({"spec-id":N,"fields":[...]}) + // so iceberg-java / iceberg-cpp / pyiceberg can read manifests + // written by iceberg-rust. The previous output (bare `fields` + // array) is a rust-only shortcut that other Iceberg + // implementations reject. + to_vec(&self.metadata.partition_spec).map_err(|err| { Error::new(ErrorKind::DataInvalid, "Fail to serialize partition spec") .with_source(err) })?,