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 b18c14d647 [common][flink] Fix BlobDescriptor lost during shuffle 
serialization (#8494)
b18c14d647 is described below

commit b18c14d6471c71d18404eb8491a12650969ef0c4
Author: liming.1018 <[email protected]>
AuthorDate: Tue Jul 7 20:48:29 2026 +0800

    [common][flink] Fix BlobDescriptor lost during shuffle serialization (#8494)
---
 .../apache/paimon/data/AbstractBinaryWriter.java   |  7 +-
 .../paimon/data/serializer/BlobSerializer.java     | 10 ++-
 .../org/apache/paimon/flink/BlobTableITCase.java   | 83 ++++++++++++++++++++++
 3 files changed, 97 insertions(+), 3 deletions(-)

diff --git 
a/paimon-common/src/main/java/org/apache/paimon/data/AbstractBinaryWriter.java 
b/paimon-common/src/main/java/org/apache/paimon/data/AbstractBinaryWriter.java
index bb632d5b31..bbbab73dd6 100644
--- 
a/paimon-common/src/main/java/org/apache/paimon/data/AbstractBinaryWriter.java
+++ 
b/paimon-common/src/main/java/org/apache/paimon/data/AbstractBinaryWriter.java
@@ -203,7 +203,12 @@ abstract class AbstractBinaryWriter implements 
BinaryWriter {
 
     @Override
     public void writeBlob(int pos, Blob blob) {
-        byte[] bytes = blob.toData();
+        byte[] bytes;
+        if (blob instanceof BlobData) {
+            bytes = blob.toData();
+        } else {
+            bytes = Blob.serializeBlob(blob);
+        }
         writeBinary(pos, bytes, 0, bytes.length);
     }
 
diff --git 
a/paimon-common/src/main/java/org/apache/paimon/data/serializer/BlobSerializer.java
 
b/paimon-common/src/main/java/org/apache/paimon/data/serializer/BlobSerializer.java
index 28f9233a37..55f6c8667b 100644
--- 
a/paimon-common/src/main/java/org/apache/paimon/data/serializer/BlobSerializer.java
+++ 
b/paimon-common/src/main/java/org/apache/paimon/data/serializer/BlobSerializer.java
@@ -39,12 +39,18 @@ public class BlobSerializer extends 
SerializerSingleton<Blob> {
 
     @Override
     public void serialize(Blob blob, DataOutputView target) throws IOException 
{
-        BinarySerializer.INSTANCE.serialize(blob.toData(), target);
+        byte[] bytes;
+        if (blob instanceof BlobData) {
+            bytes = blob.toData();
+        } else {
+            bytes = Blob.serializeBlob(blob);
+        }
+        BinarySerializer.INSTANCE.serialize(bytes, target);
     }
 
     @Override
     public Blob deserialize(DataInputView source) throws IOException {
         byte[] bytes = BinarySerializer.INSTANCE.deserialize(source);
-        return new BlobData(bytes);
+        return Blob.fromBytes(bytes, null, null);
     }
 }
diff --git 
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/BlobTableITCase.java
 
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/BlobTableITCase.java
index d09ccedb16..280535d6e3 100644
--- 
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/BlobTableITCase.java
+++ 
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/BlobTableITCase.java
@@ -724,6 +724,89 @@ public class BlobTableITCase extends CatalogITCaseBase {
         assertThat(AbstractFlinkTableFactory.schemaEquals(convertedRowType, 
flinkRowType)).isTrue();
     }
 
+    @Test
+    public void testBlobDescriptorFieldStreamingWriteWithShuffle() throws 
Exception {
+        // Verifies that blob-descriptor-field works when shuffle 
serialization occurs
+        // in streaming mode. When source and sink have different parallelism, 
Flink
+        // inserts a REBALANCE that serializes InternalRow to BinaryRow via
+        // InternalRowTypeSerializer. writeBlob() must preserve the 
BlobDescriptor so
+        // that BlobDescriptorWriter can serialize it at the sink.
+
+        byte[] blobData = "multimodal-content".getBytes();
+        FileIO fileIO = new LocalFileIO();
+        String uri = "file://" + warehouse + "/multimodal_blob_stream";
+        try (OutputStream outputStream =
+                fileIO.newOutputStream(new org.apache.paimon.fs.Path(uri), 
true)) {
+            outputStream.write(blobData);
+        }
+
+        sEnv.executeSql(
+                "CREATE TABLE multimodal_assets_metadata ("
+                        + "id STRING, "
+                        + "source_uri STRING, "
+                        + "media_content BYTES, "
+                        + "dt STRING"
+                        + ") PARTITIONED BY (dt) WITH ("
+                        + "'row-tracking.enabled'='true',"
+                        + "'data-evolution.enabled'='true',"
+                        + "'blob-field'='media_content',"
+                        + "'blob-descriptor-field'='media_content'"
+                        + ")");
+
+        // Streaming INSERT with sys.path_to_descriptor
+        // The /*+ OPTIONS('sink.parallelism' = '2') */ hint forces a shuffle
+        // from the source (parallelism 1 for VALUES) to the sink (parallelism 
2).
+        // After fix, writeBlob() preserves BlobDescriptor through the shuffle.
+        sEnv.getConfig().set("table.dml-sync", "true");
+        sEnv.executeSql(
+                        "INSERT INTO multimodal_assets_metadata "
+                                + "/*+ OPTIONS('sink.parallelism' = '2') */ "
+                                + "VALUES ('test-id', 'test-uri', "
+                                + "sys.path_to_descriptor('"
+                                + uri
+                                + "'), '2025-07-07')")
+                .await();
+    }
+
+    @Test
+    public void testBlobDescriptorFieldBatchWriteWithShuffle() throws 
Exception {
+        // Same as streaming test but in batch mode.
+        // Verifies that blob-descriptor-field works when shuffle serialization
+        // occurs in batch mode.
+
+        byte[] blobData = "batch-multimodal-content".getBytes();
+        FileIO fileIO = new LocalFileIO();
+        String uri = "file://" + warehouse + "/multimodal_blob_batch";
+        try (OutputStream outputStream =
+                fileIO.newOutputStream(new org.apache.paimon.fs.Path(uri), 
true)) {
+            outputStream.write(blobData);
+        }
+
+        tEnv.executeSql(
+                "CREATE TABLE batch_multimodal_metadata ("
+                        + "id STRING, "
+                        + "source_uri STRING, "
+                        + "media_content BYTES, "
+                        + "dt STRING"
+                        + ") PARTITIONED BY (dt) WITH ("
+                        + "'row-tracking.enabled'='true',"
+                        + "'data-evolution.enabled'='true',"
+                        + "'blob-field'='media_content',"
+                        + "'blob-descriptor-field'='media_content'"
+                        + ")");
+
+        // Batch INSERT with sys.path_to_descriptor
+        // The /*+ OPTIONS('sink.parallelism' = '2') */ hint forces a shuffle,
+        // same as streaming mode.
+        batchSql(
+                "INSERT INTO batch_multimodal_metadata "
+                        + "/*+ OPTIONS('sink.parallelism' = '2') */ "
+                        + "VALUES ('test-id', 'test-uri', "
+                        + "sys.path_to_descriptor('"
+                        + uri
+                        + "'), '2025-07-07')");
+    }
+
     private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
 
     public static String bytesToHex(byte[] bytes) {

Reply via email to