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 16e98f43 fix: serialize empty BinaryRow with fixed part (#604)
16e98f43 is described below

commit 16e98f437c5f9459e403342eddf7d8d03ee69696
Author: WAR10CK <[email protected]>
AuthorDate: Thu Jul 23 23:15:44 2026 +0800

    fix: serialize empty BinaryRow with fixed part (#604)
---
 crates/paimon/src/spec/binary_row.rs | 19 +++++++++++++++++++
 crates/paimon/src/table/source.rs    | 35 +++++++++++++++++++++++++++++++++++
 2 files changed, 54 insertions(+)

diff --git a/crates/paimon/src/spec/binary_row.rs 
b/crates/paimon/src/spec/binary_row.rs
index ce08ad8b..236cf91e 100644
--- a/crates/paimon/src/spec/binary_row.rs
+++ b/crates/paimon/src/spec/binary_row.rs
@@ -119,6 +119,14 @@ impl BinaryRow {
 
     /// Serialize this BinaryRow to bytes (arity prefix + data), the inverse 
of `from_serialized_bytes`.
     pub fn to_serialized_bytes(&self) -> Vec<u8> {
+        // Java's BinaryRow.EMPTY_ROW points to its 8-byte fixed part, so the
+        // schemaless wire representation is 4 bytes of arity plus that body.
+        // BinaryRow::new(0) is an in-memory stub without backing data; 
normalize
+        // it here instead of emitting a truncated row that the strict decoder
+        // correctly rejects.
+        if self.arity == 0 && self.data.is_empty() {
+            return EMPTY_SERIALIZED_ROW.clone();
+        }
         let mut buf = Vec::with_capacity(4 + self.data.len());
         buf.extend_from_slice(&self.arity.to_be_bytes());
         buf.extend_from_slice(&self.data);
@@ -1556,6 +1564,17 @@ mod tests {
         assert_eq!(row.data(), &[] as &[u8]);
     }
 
+    #[test]
+    fn test_empty_binary_row_serializes_to_java_wire_format() {
+        let serialized = BinaryRow::new(0).to_serialized_bytes();
+        assert_eq!(serialized, *EMPTY_SERIALIZED_ROW);
+        assert_eq!(serialized.len(), 12);
+        assert_eq!(
+            BinaryRow::from_serialized_bytes(&serialized).unwrap(),
+            BinaryRow::from_bytes(0, vec![0; 8])
+        );
+    }
+
     #[test]
     fn test_binary_row_constants() {
         assert_eq!(BinaryRow::cal_bit_set_width_in_bytes(0), 8);
diff --git a/crates/paimon/src/table/source.rs 
b/crates/paimon/src/table/source.rs
index a8d6b785..41836580 100644
--- a/crates/paimon/src/table/source.rs
+++ b/crates/paimon/src/table/source.rs
@@ -1325,6 +1325,41 @@ mod tests {
         assert_eq!(restored.bucket_path(), split.bucket_path());
     }
 
+    #[test]
+    fn data_split_native_round_trip_with_empty_partition() {
+        // Generated by Apache Paimon Java 1.4.2 using BinaryRow.EMPTY_ROW and
+        // DataSplit#serialize. The partition field is length 12: arity=0 plus
+        // the 8-byte fixed part initialized by Java's BinaryRow static block.
+        let java_golden = [
+            0xde, 0xc3, 0xd2, 0x30, 0x2c, 0x19, 0xec, 0x66, 0x00, 0x00, 0x00, 
0x08, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 
0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x12,
+            0x66, 0x69, 0x6c, 0x65, 0x3a, 0x2f, 0x74, 0x6d, 0x70, 0x2f, 0x62, 
0x75, 0x63, 0x6b,
+            0x65, 0x74, 0x2d, 0x30, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 
0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+        ];
+        let split = DataSplit::builder()
+            .with_snapshot(1)
+            .with_partition(BinaryRow::new(0))
+            .with_bucket(0)
+            .with_bucket_path("file:/tmp/bucket-0".to_string())
+            .with_total_buckets(1)
+            .with_data_files(vec![])
+            .build()
+            .unwrap();
+
+        let bytes = split.serialize().expect("serialize");
+        assert_eq!(bytes, java_golden);
+        let restored = DataSplit::deserialize(&bytes).expect("deserialize");
+        assert_eq!(restored.snapshot_id(), split.snapshot_id());
+        assert_eq!(restored.partition().arity(), 0);
+        assert_eq!(
+            restored.partition().to_serialized_bytes(),
+            *crate::spec::EMPTY_SERIALIZED_ROW
+        );
+        assert_eq!(restored.serialize().expect("reserialize"), bytes);
+    }
+
     /// Raw convertible split without deletion files: physical sum is exact.
     #[test]
     fn test_merged_row_count_raw_convertible_sums_physical_rows() {

Reply via email to