This is an automated email from the ASF dual-hosted git repository.
JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git
The following commit(s) were added to refs/heads/master by this push:
new 19a933b7d9 [python] Fix BLOB CRC32 compatibility with Java (#9426)
19a933b7d9 is described below
commit 19a933b7d96658f19a87301b0990919702464fd3
Author: liangjie <[email protected]>
AuthorDate: Fri Aug 28 15:41:33 2026 +0800
[python] Fix BLOB CRC32 compatibility with Java (#9426)
---
paimon-python/pypaimon/tests/blob_test.py | 90 +++++++++++++++++++++-
paimon-python/pypaimon/write/blob_format_writer.py | 6 +-
2 files changed, 88 insertions(+), 8 deletions(-)
diff --git a/paimon-python/pypaimon/tests/blob_test.py
b/paimon-python/pypaimon/tests/blob_test.py
index 4c6a7b1932..bbc095cdf7 100644
--- a/paimon-python/pypaimon/tests/blob_test.py
+++ b/paimon-python/pypaimon/tests/blob_test.py
@@ -2228,12 +2228,29 @@ class BlobEndToEndTest(unittest.TestCase):
return output.getvalue(), payload
def test_blob_crc_fallback_matches_zlib(self):
+ record, _ = self._write_blob_record_with_crc_backend(zlib)
+ self._assert_record_crc(record)
+
+ def test_blob_array_crc_includes_record_length(self):
+ from pypaimon.write.blob_format_writer import BlobFormatWriter
+
+ output = io.BytesIO()
+ writer = BlobFormatWriter(output)
+ writer.add_blob_array(
+ 'blob_field', [BlobData(b'first'), None, BlobData(b'second')])
+ self._assert_record_crc(output.getvalue())
+
+ def test_blob_map_crc_includes_record_length(self):
from pypaimon.write.blob_format_writer import BlobFormatWriter
- record, payload = self._write_blob_record_with_crc_backend(zlib)
- expected_crc = zlib.crc32(
- struct.pack('<I', BlobFormatWriter.MAGIC_NUMBER))
- expected_crc = zlib.crc32(payload, expected_crc) & 0xffffffff
+ output = io.BytesIO()
+ writer = BlobFormatWriter(output)
+ writer.add_blob_map(
+ 'blob_field', {'key': BlobData(b'value')}, AtomicType('STRING'))
+ self._assert_record_crc(output.getvalue())
+
+ def _assert_record_crc(self, record):
+ expected_crc = zlib.crc32(record[:-4]) & 0xffffffff
actual_crc = struct.unpack('<I', record[-4:])[0]
self.assertEqual(expected_crc, actual_crc)
@@ -2765,6 +2782,71 @@ class BlobEndToEndTest(unittest.TestCase):
+ struct.pack('<I', index_length)
)
+ def test_blob_golden_bytes(self):
+ from pypaimon.write.blob_format_writer import BlobFormatWriter
+
+ source_file_path = os.path.join(self.temp_dir, "source.bin")
+ with open(source_file_path, 'wb') as source_file:
+ source_file.write(b"descriptor")
+
+ blob_file_path = os.path.join(self.temp_dir, "blob_golden.blob")
+ fields = [DataField(0, "blob", AtomicType("BLOB"))]
+ writer = BlobFormatWriter(open(blob_file_path, 'wb'))
+ writer.add_element(GenericRow([BlobData(b"inline")], fields,
RowKind.INSERT))
+ writer.add_element(
+ GenericRow([Blob.from_local(source_file_path)], fields,
RowKind.INSERT)
+ )
+ writer.add_element(GenericRow([None], fields, RowKind.INSERT))
+ writer.add_element(GenericRow([Blob.PLACE_HOLDER], fields,
RowKind.INSERT))
+ writer.close()
+
+ with open(blob_file_path, 'rb') as blob_file:
+ self.assertEqual(
+ blob_file.read().hex(),
+ "cf114e58696e6c696e6516000000000000002960c8e9"
+ "cf114e5864657363726970746f721a000000000000003f69146b"
+ "2c0835010400000001",
+ )
+
+ def test_array_blob_golden_bytes(self):
+ from pypaimon.write.blob_format_writer import BlobFormatWriter
+
+ source_file_path = os.path.join(self.temp_dir, "source.bin")
+ with open(source_file_path, 'wb') as source_file:
+ source_file.write(b"descriptor")
+
+ blob_file_path = os.path.join(self.temp_dir, "array_golden.blob")
+ fields = [DataField(
+ 0,
+ "blob_array",
+ ArrayType(True, AtomicType("BLOB")),
+ )]
+ writer = BlobFormatWriter(open(blob_file_path, 'wb'))
+ writer.add_element(GenericRow([[]], fields, RowKind.INSERT))
+ writer.add_element(GenericRow(
+ [[
+ BlobData(b"inline"),
+ None,
+ BlobData(b""),
+ Blob.from_local(source_file_path),
+ ]],
+ fields,
+ RowKind.INSERT,
+ ))
+ writer.add_element(GenericRow([None], fields, RowKind.INSERT))
+ writer.add_element(
+ GenericRow([Blob.ARRAY_PLACE_HOLDER], fields, RowKind.INSERT)
+ )
+ writer.close()
+
+ with open(blob_file_path, 'rb') as blob_file:
+ self.assertEqual(
+ blob_file.read().hex(),
+ "cf114e58424342410100000000000000001d000000000000009bd49157"
+ "cf114e58424342410104000000696e6c696e6564657363726970746f72"
+ "0c0d0214040000003100000000000000d08307713a2863010400000001",
+ )
+
def test_map_blob_golden_bytes(self):
from pypaimon.write.blob_format_writer import BlobFormatWriter
diff --git a/paimon-python/pypaimon/write/blob_format_writer.py
b/paimon-python/pypaimon/write/blob_format_writer.py
index 8a7969b271..0dfa44d180 100644
--- a/paimon-python/pypaimon/write/blob_format_writer.py
+++ b/paimon-python/pypaimon/write/blob_format_writer.py
@@ -129,8 +129,7 @@ class BlobFormatWriter:
# Write length (8 bytes, little endian)
length_bytes = struct.pack('<Q', bin_length)
- self.output_stream.write(length_bytes)
- self.position += 8
+ crc32 = self._write_with_crc(length_bytes, crc32)
# Write CRC32 (4 bytes, little endian)
crc_bytes = struct.pack('<I', crc32 & 0xffffffff)
@@ -195,8 +194,7 @@ class BlobFormatWriter:
bin_length = self.position - previous_pos + self.METADATA_SIZE
self.lengths.append(bin_length)
- self.output_stream.write(struct.pack('<Q', bin_length))
- self.position += 8
+ crc32 = self._write_with_crc(struct.pack('<Q', bin_length), crc32)
self.output_stream.write(struct.pack('<I', crc32 & 0xffffffff))
self.position += 4