This is an automated email from the ASF dual-hosted git repository.

liurenjie1024 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-rust.git


The following commit(s) were added to refs/heads/main by this push:
     new 230135201 fix(spec): Include delete file content to V3 manifest (#1979)
230135201 is described below

commit 2301352019bc9f5cdfd488bf829f92c6c3d0da25
Author: Shawn Chang <[email protected]>
AuthorDate: Sun Jan 4 17:57:47 2026 -0800

    fix(spec): Include delete file content to V3 manifest (#1979)
    
    ## Which issue does this PR close?
    
    - Closes #1973
    
    ## What changes are included in this PR?
    
    - Write `content` to V3 manifest so the field is preserved correctly for
    delete entry
    
    
    ## Are these changes tested?
    Yes
---
 crates/iceberg/src/spec/manifest/writer.rs | 98 +++++++++++++++++++++++++++++-
 1 file changed, 95 insertions(+), 3 deletions(-)

diff --git a/crates/iceberg/src/spec/manifest/writer.rs 
b/crates/iceberg/src/spec/manifest/writer.rs
index 2fb6a4206..066965160 100644
--- a/crates/iceberg/src/spec/manifest/writer.rs
+++ b/crates/iceberg/src/spec/manifest/writer.rs
@@ -437,9 +437,12 @@ impl ManifestWriter {
             "format-version".to_string(),
             (self.metadata.format_version as u8).to_string(),
         )?;
-        if self.metadata.format_version == FormatVersion::V2 {
-            avro_writer
-                .add_user_metadata("content".to_string(), 
self.metadata.content.to_string())?;
+        match self.metadata.format_version {
+            FormatVersion::V1 => {}
+            FormatVersion::V2 | FormatVersion::V3 => {
+                avro_writer
+                    .add_user_metadata("content".to_string(), 
self.metadata.content.to_string())?;
+            }
         }
 
         let partition_summary = 
self.construct_partition_summaries(&partition_type)?;
@@ -708,4 +711,93 @@ mod tests {
         entries[0].file_sequence_number = None;
         assert_eq!(actual_manifest, Manifest::new(metadata, entries));
     }
+
+    #[tokio::test]
+    async fn test_v3_delete_manifest_delete_file_roundtrip() {
+        let schema = Arc::new(
+            Schema::builder()
+                .with_fields(vec![
+                    Arc::new(NestedField::optional(
+                        1,
+                        "id",
+                        Type::Primitive(PrimitiveType::Long),
+                    )),
+                    Arc::new(NestedField::optional(
+                        2,
+                        "data",
+                        Type::Primitive(PrimitiveType::String),
+                    )),
+                ])
+                .build()
+                .unwrap(),
+        );
+
+        let partition_spec = PartitionSpec::builder(schema.clone())
+            .with_spec_id(0)
+            .build()
+            .unwrap();
+
+        // Create a position delete file entry
+        let delete_entry = ManifestEntry {
+            status: ManifestStatus::Added,
+            snapshot_id: None,
+            sequence_number: None,
+            file_sequence_number: None,
+            data_file: DataFile {
+                content: DataContentType::PositionDeletes,
+                file_path: 
"s3://bucket/table/data/delete-00000.parquet".to_string(),
+                file_format: DataFileFormat::Parquet,
+                partition: Struct::empty(),
+                record_count: 10,
+                file_size_in_bytes: 1024,
+                column_sizes: HashMap::new(),
+                value_counts: HashMap::new(),
+                null_value_counts: HashMap::new(),
+                nan_value_counts: HashMap::new(),
+                lower_bounds: HashMap::new(),
+                upper_bounds: HashMap::new(),
+                key_metadata: None,
+                split_offsets: None,
+                equality_ids: None,
+                sort_order_id: None,
+                partition_spec_id: 0,
+                first_row_id: None,
+                referenced_data_file: None,
+                content_offset: None,
+                content_size_in_bytes: None,
+            },
+        };
+
+        // Write a V3 delete manifest
+        let tmp_dir = TempDir::new().unwrap();
+        let path = tmp_dir.path().join("v3_delete_manifest.avro");
+        let io = FileIOBuilder::new_fs_io().build().unwrap();
+        let output_file = io.new_output(path.to_str().unwrap()).unwrap();
+
+        let mut writer = ManifestWriterBuilder::new(
+            output_file,
+            Some(1),
+            None,
+            schema.clone(),
+            partition_spec.clone(),
+        )
+        .build_v3_deletes();
+
+        writer.add_entry(delete_entry).unwrap();
+        let manifest_file = writer.write_manifest_file().await.unwrap();
+
+        // The returned ManifestFile correctly reports Deletes content
+        assert_eq!(manifest_file.content, ManifestContentType::Deletes);
+
+        // Read back the manifest file
+        let actual_manifest =
+            Manifest::parse_avro(fs::read(&path).expect("read_file must 
succeed").as_slice())
+                .unwrap();
+
+        // Verify the content type is correctly preserved as Deletes
+        assert_eq!(
+            actual_manifest.metadata().content,
+            ManifestContentType::Deletes,
+        );
+    }
 }

Reply via email to