You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by xu...@apache.org on 2025/04/21 08:07:20 UTC

(iceberg-rust) branch main updated: Skip producing empty parquet files (#1230)

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

xuanwo 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 e1e3ba48 Skip producing empty parquet files (#1230)
e1e3ba48 is described below

commit e1e3ba48cc5cf2bf06e128650e48cb5e82379f27
Author: Renjie Liu <li...@gmail.com>
AuthorDate: Mon Apr 21 16:07:14 2025 +0800

    Skip producing empty parquet files (#1230)
    
    ## Which issue does this PR close?
    
    
    - Closes #1224.
    
    ## What changes are included in this PR?
    
    Skip generating empty parquet files.
    
    ## Are these changes tested?
    
    UT.
---
 crates/iceberg/src/io/file_io.rs                   |  9 ++-
 .../src/writer/file_writer/parquet_writer.rs       | 77 ++++++++++++++++++----
 2 files changed, 72 insertions(+), 14 deletions(-)

diff --git a/crates/iceberg/src/io/file_io.rs b/crates/iceberg/src/io/file_io.rs
index 97deae50..cadef7d5 100644
--- a/crates/iceberg/src/io/file_io.rs
+++ b/crates/iceberg/src/io/file_io.rs
@@ -331,10 +331,17 @@ impl OutputFile {
     }
 
     /// Checks if file exists.
-    pub async fn exists(&self) -> crate::Result<bool> {
+    pub async fn exists(&self) -> Result<bool> {
         Ok(self.op.exists(&self.path[self.relative_path_pos..]).await?)
     }
 
+    /// Deletes file.
+    ///
+    /// If the file does not exist, it will not return error.
+    pub async fn delete(&self) -> Result<()> {
+        Ok(self.op.delete(&self.path[self.relative_path_pos..]).await?)
+    }
+
     /// Converts into [`InputFile`].
     pub fn to_input_file(self) -> InputFile {
         InputFile {
diff --git a/crates/iceberg/src/writer/file_writer/parquet_writer.rs b/crates/iceberg/src/writer/file_writer/parquet_writer.rs
index 32ee2bdf..56b0ca53 100644
--- a/crates/iceberg/src/writer/file_writer/parquet_writer.rs
+++ b/crates/iceberg/src/writer/file_writer/parquet_writer.rs
@@ -554,7 +554,7 @@ impl FileWriter for ParquetWriter {
         Ok(())
     }
 
-    async fn close(mut self) -> crate::Result<Vec<crate::spec::DataFileBuilder>> {
+    async fn close(mut self) -> Result<Vec<DataFileBuilder>> {
         let writer = match self.inner_writer.take() {
             Some(writer) => writer,
             None => return Ok(vec![]),
@@ -566,22 +566,33 @@ impl FileWriter for ParquetWriter {
 
         let written_size = self.written_size.load(std::sync::atomic::Ordering::Relaxed);
 
-        let parquet_metadata =
-            Arc::new(self.thrift_to_parquet_metadata(metadata).map_err(|err| {
+        if self.current_row_num == 0 {
+            self.out_file.delete().await.map_err(|err| {
                 Error::new(
                     ErrorKind::Unexpected,
-                    "Failed to convert metadata from thrift to parquet.",
+                    "Failed to delete empty parquet file.",
                 )
                 .with_source(err)
-            })?);
-
-        Ok(vec![Self::parquet_to_data_file_builder(
-            self.schema,
-            parquet_metadata,
-            written_size as usize,
-            self.out_file.location().to_string(),
-            self.nan_value_count_visitor.nan_value_counts,
-        )?])
+            })?;
+            Ok(vec![])
+        } else {
+            let parquet_metadata =
+                Arc::new(self.thrift_to_parquet_metadata(metadata).map_err(|err| {
+                    Error::new(
+                        ErrorKind::Unexpected,
+                        "Failed to convert metadata from thrift to parquet.",
+                    )
+                    .with_source(err)
+                })?);
+
+            Ok(vec![Self::parquet_to_data_file_builder(
+                self.schema,
+                parquet_metadata,
+                written_size as usize,
+                self.out_file.location().to_string(),
+                self.nan_value_count_visitor.nan_value_counts,
+            )?])
+        }
     }
 }
 
@@ -2218,4 +2229,44 @@ mod tests {
 
         Ok(())
     }
+
+    #[tokio::test]
+    async fn test_write_empty_parquet_file() {
+        let temp_dir = TempDir::new().unwrap();
+        let file_io = FileIOBuilder::new_fs_io().build().unwrap();
+        let location_gen =
+            MockLocationGenerator::new(temp_dir.path().to_str().unwrap().to_string());
+        let file_name_gen =
+            DefaultFileNameGenerator::new("test".to_string(), None, DataFileFormat::Parquet);
+
+        // write data
+        let pw = ParquetWriterBuilder::new(
+            WriterProperties::builder().build(),
+            Arc::new(
+                Schema::builder()
+                    .with_schema_id(1)
+                    .with_fields(vec![NestedField::required(
+                        0,
+                        "col",
+                        Type::Primitive(PrimitiveType::Long),
+                    )
+                    .with_id(0)
+                    .into()])
+                    .build()
+                    .expect("Failed to create schema"),
+            ),
+            file_io.clone(),
+            location_gen,
+            file_name_gen,
+        )
+        .build()
+        .await
+        .unwrap();
+
+        let res = pw.close().await.unwrap();
+        assert_eq!(res.len(), 0);
+
+        // Check that file should have been deleted.
+        assert_eq!(std::fs::read_dir(temp_dir.path()).unwrap().count(), 0);
+    }
 }