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() {