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

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


The following commit(s) were added to refs/heads/main by this push:
     new d81476a0 feat(spec): support the Avro deflate codec (#982)
d81476a0 is described below

commit d81476a0a8006f4659c1e164ba148c46a4c07791
Author: jackylee <[email protected]>
AuthorDate: Fri Oct 2 10:08:01 2026 +0800

    feat(spec): support the Avro deflate codec (#982)
---
 crates/paimon/src/spec/avro/ocf.rs     | 49 ++++++++++++++++++++++++++++++++++
 crates/paimon/src/spec/objects_file.rs | 17 +++++++++++-
 2 files changed, 65 insertions(+), 1 deletion(-)

diff --git a/crates/paimon/src/spec/avro/ocf.rs 
b/crates/paimon/src/spec/avro/ocf.rs
index 58b89ca7..1004c23d 100644
--- a/crates/paimon/src/spec/avro/ocf.rs
+++ b/crates/paimon/src/spec/avro/ocf.rs
@@ -36,6 +36,7 @@ pub struct OcfHeader {
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 pub enum OcfCodec {
     Null,
+    Deflate,
     Snappy,
     Zstandard,
 }
@@ -113,6 +114,20 @@ impl<'a> OcfBlockIter<'a> {
     fn decompress(&mut self, data: &'a [u8]) -> crate::Result<Cow<'a, [u8]>> {
         match self.codec {
             OcfCodec::Null => Ok(Cow::Borrowed(data)),
+            OcfCodec::Deflate => {
+                // The Avro `deflate` codec is raw DEFLATE (RFC 1951), with no
+                // zlib or gzip wrapper.
+                use std::io::Read;
+                let mut decoder = flate2::read::DeflateDecoder::new(data);
+                let mut decompressed = Vec::new();
+                decoder
+                    .read_to_end(&mut decompressed)
+                    .map_err(|e| Error::UnexpectedError {
+                        message: format!("avro ocf: deflate decompression 
failed: {e}"),
+                        source: None,
+                    })?;
+                Ok(Cow::Owned(decompressed))
+            }
             OcfCodec::Snappy => {
                 if data.len() < 4 {
                     return Err(Error::UnexpectedError {
@@ -176,6 +191,7 @@ pub fn parse_ocf_streaming(bytes: &[u8]) -> 
crate::Result<(OcfHeader, OcfBlockIt
 
     let codec = match meta.get("avro.codec").map(|s| s.as_str()) {
         None | Some("null") => OcfCodec::Null,
+        Some("deflate") => OcfCodec::Deflate,
         Some("snappy") => OcfCodec::Snappy,
         Some("zstandard") => OcfCodec::Zstandard,
         Some(other) => {
@@ -309,4 +325,37 @@ mod tests {
         assert_eq!(blocks.len(), 1);
         assert_eq!(blocks[0].object_count, 1);
     }
+
+    #[test]
+    fn test_parse_ocf_deflate() {
+        use apache_avro::{from_avro_datum, types::Value, Codec, 
DeflateSettings, Schema, Writer};
+
+        let schema = Schema::parse_str(
+            r#"{"type": "record", "name": "test", "fields": [{"name": "x", 
"type": "long"}]}"#,
+        )
+        .unwrap();
+        let mut writer = Writer::with_codec(
+            &schema,
+            Vec::new(),
+            Codec::Deflate(DeflateSettings::default()),
+        );
+        let mut record = apache_avro::types::Record::new(&schema).unwrap();
+        record.put("x", 424242i64);
+        writer.append(record).unwrap();
+        let bytes = writer.into_inner().unwrap();
+
+        let (header, blocks) = parse_ocf(&bytes).unwrap();
+        assert_eq!(header.codec, OcfCodec::Deflate);
+        assert_eq!(blocks.len(), 1);
+        assert_eq!(blocks[0].object_count, 1);
+
+        // The decompressed block must decode back to the original record, 
which
+        // proves the deflate output is correct rather than merely 
non-erroring.
+        let mut cursor = blocks[0].data.as_ref();
+        let value = from_avro_datum(&schema, &mut cursor, None).unwrap();
+        assert_eq!(
+            value,
+            Value::Record(vec![("x".to_string(), Value::Long(424242))])
+        );
+    }
 }
diff --git a/crates/paimon/src/spec/objects_file.rs 
b/crates/paimon/src/spec/objects_file.rs
index c30ecc8f..a4e60c4f 100644
--- a/crates/paimon/src/spec/objects_file.rs
+++ b/crates/paimon/src/spec/objects_file.rs
@@ -15,7 +15,9 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use apache_avro::{from_value, to_value, Codec, Reader, Schema, Writer, 
ZstandardSettings};
+use apache_avro::{
+    from_value, to_value, Codec, DeflateSettings, Reader, Schema, Writer, 
ZstandardSettings,
+};
 use serde::de::DeserializeOwned;
 use serde::Serialize;
 
@@ -70,6 +72,7 @@ pub(crate) fn avro_codec(compression: &str) -> 
crate::Result<Codec> {
     match compression.to_ascii_lowercase().as_str() {
         "zstd" | "zstandard" => 
Ok(Codec::Zstandard(ZstandardSettings::default())),
         "null" | "none" | "uncompressed" => Ok(Codec::Null),
+        "deflate" => Ok(Codec::Deflate(DeflateSettings::default())),
         "snappy" => Ok(Codec::Snappy),
         other => Err(crate::Error::Unsupported {
             message: format!("Unsupported Avro compression: {other}"),
@@ -333,6 +336,18 @@ mod tests {
         assert_eq!(original, decoded);
     }
 
+    #[test]
+    fn test_roundtrip_manifest_entry_deflate() {
+        // Writing with the deflate codec and reading back through the fast
+        // decoder must round-trip, exercising both the write-side codec 
mapping
+        // and the read-side deflate decompression.
+        let original = vec![manifest_entry()];
+        let bytes =
+            to_avro_bytes_with_compression(MANIFEST_ENTRY_SCHEMA, &original, 
"deflate").unwrap();
+        let decoded = from_avro_bytes_fast::<ManifestEntry>(&bytes).unwrap();
+        assert_eq!(original, decoded);
+    }
+
     #[test]
     fn test_roundtrip_index_manifest_field_order() {
         let entry: IndexManifestEntry = 
serde_json::from_value(serde_json::json!({

Reply via email to