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 c6964a7a7a [flink] Support Blob tables in dedicated split generation 
(#8914)
c6964a7a7a is described below

commit c6964a7a7acc952370b86b8f037aafe6ff1096bf
Author: wangwj <[email protected]>
AuthorDate: Thu Jul 30 12:03:37 2026 +0800

    [flink] Support Blob tables in dedicated split generation (#8914)
---
 .../paimon/flink/source/FlinkSourceBuilder.java    |   7 +-
 .../flink/source/operator/MonitorSource.java       |  40 ++++++-
 .../paimon/flink/source/operator/ReadOperator.java |  32 ++++-
 .../org/apache/paimon/flink/BlobTableITCase.java   |  36 ++++++
 .../source/operator/ReadOperatorBlobTest.java      | 129 +++++++++++++++++++++
 5 files changed, 240 insertions(+), 4 deletions(-)

diff --git 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/source/FlinkSourceBuilder.java
 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/source/FlinkSourceBuilder.java
index a49ba7ae41..dca0f3b119 100644
--- 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/source/FlinkSourceBuilder.java
+++ 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/source/FlinkSourceBuilder.java
@@ -475,12 +475,13 @@ public class FlinkSourceBuilder {
             throw new IllegalArgumentException(
                     "Cannot limit streaming source, please use batch execution 
mode.");
         }
+        ReadBuilder readBuilder = createReadBuilder(projectedRowType());
         dataStream =
                 MonitorSource.buildSource(
                         env,
                         sourceName,
                         produceTypeInfo(),
-                        createReadBuilder(projectedRowType()),
+                        readBuilder,
                         
conf.get(CoreOptions.CONTINUOUS_DISCOVERY_INTERVAL).toMillis(),
                         watermarkStrategy == null,
                         
conf.get(FlinkConnectorOptions.READ_SHUFFLE_BUCKET_WITH_PARTITION),
@@ -488,7 +489,9 @@ public class FlinkSourceBuilder {
                         outerProject(),
                         isBounded,
                         limit,
-                        table);
+                        table,
+                        readBuilder.readType(),
+                        conf.get(CoreOptions.BLOB_AS_DESCRIPTOR));
         if (parallelism != null) {
             dataStream.getTransformation().setParallelism(parallelism);
         }
diff --git 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/source/operator/MonitorSource.java
 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/source/operator/MonitorSource.java
index b6de64472b..3fbec3697b 100644
--- 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/source/operator/MonitorSource.java
+++ 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/source/operator/MonitorSource.java
@@ -34,6 +34,7 @@ import org.apache.paimon.table.source.ReadBuilder;
 import org.apache.paimon.table.source.Split;
 import org.apache.paimon.table.source.StreamTableScan;
 import org.apache.paimon.table.source.TableScan;
+import org.apache.paimon.types.RowType;
 
 import org.apache.flink.api.common.eventtime.Watermark;
 import org.apache.flink.api.common.eventtime.WatermarkStrategy;
@@ -274,6 +275,38 @@ public class MonitorSource extends 
AbstractNonCoordinatedSource<Split> {
             boolean isBounded,
             @Nullable Long limit,
             @Nullable Table table) {
+        return buildSource(
+                env,
+                name,
+                typeInfo,
+                readBuilder,
+                monitorInterval,
+                emitSnapshotWatermark,
+                shuffleBucketWithPartition,
+                unordered,
+                nestedProjectedRowData,
+                isBounded,
+                limit,
+                table,
+                readBuilder.readType(),
+                false);
+    }
+
+    public static DataStream<RowData> buildSource(
+            StreamExecutionEnvironment env,
+            String name,
+            TypeInformation<RowData> typeInfo,
+            ReadBuilder readBuilder,
+            long monitorInterval,
+            boolean emitSnapshotWatermark,
+            boolean shuffleBucketWithPartition,
+            boolean unordered,
+            NestedProjectedRowData nestedProjectedRowData,
+            boolean isBounded,
+            @Nullable Long limit,
+            @Nullable Table table,
+            RowType readType,
+            boolean blobAsDescriptor) {
         MonitorSource monitorSource =
                 new MonitorSource(readBuilder, monitorInterval, 
emitSnapshotWatermark, isBounded);
         Source<Split, SimpleSourceSplit, NoOpEnumState> source = monitorSource;
@@ -296,7 +329,12 @@ public class MonitorSource extends 
AbstractNonCoordinatedSource<Split> {
         return sourceDataStream.transform(
                 name + "-Reader",
                 typeInfo,
-                new ReadOperator(readBuilder::newRead, nestedProjectedRowData, 
limit));
+                new ReadOperator(
+                        readBuilder::newRead,
+                        nestedProjectedRowData,
+                        limit,
+                        readType,
+                        blobAsDescriptor));
     }
 
     private static DataStream<Split> shuffleUnordered(
diff --git 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/source/operator/ReadOperator.java
 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/source/operator/ReadOperator.java
index 30b2ab0d62..95bf5606e7 100644
--- 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/source/operator/ReadOperator.java
+++ 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/source/operator/ReadOperator.java
@@ -21,12 +21,15 @@ package org.apache.paimon.flink.source.operator;
 import org.apache.paimon.data.InternalRow;
 import org.apache.paimon.disk.IOManager;
 import org.apache.paimon.flink.FlinkRowData;
+import org.apache.paimon.flink.FlinkRowDataWithBlob;
 import org.apache.paimon.flink.NestedProjectedRowData;
 import org.apache.paimon.flink.source.RecordLimiter;
 import org.apache.paimon.flink.source.metrics.FileStoreSourceReaderMetrics;
 import org.apache.paimon.table.source.DataSplit;
 import org.apache.paimon.table.source.Split;
 import org.apache.paimon.table.source.TableRead;
+import org.apache.paimon.types.BlobType;
+import org.apache.paimon.types.RowType;
 import org.apache.paimon.utils.CloseableIterator;
 import org.apache.paimon.utils.SerializableSupplier;
 
@@ -42,6 +45,9 @@ import org.slf4j.LoggerFactory;
 
 import javax.annotation.Nullable;
 
+import java.util.HashSet;
+import java.util.Set;
+
 /**
  * The operator that reads the {@link Split splits} received from the 
preceding {@link
  * MonitorSource}. Contrary to the {@link MonitorSource} which has a 
parallelism of 1, this operator
@@ -56,6 +62,8 @@ public class ReadOperator extends 
AbstractStreamOperator<RowData>
 
     private final SerializableSupplier<TableRead> readSupplier;
     @Nullable private final NestedProjectedRowData nestedProjectedRowData;
+    @Nullable private final RowType readType;
+    private final boolean blobAsDescriptor;
 
     private transient TableRead read;
     private transient StreamRecord<RowData> reuseRecord;
@@ -76,9 +84,20 @@ public class ReadOperator extends 
AbstractStreamOperator<RowData>
             SerializableSupplier<TableRead> readSupplier,
             @Nullable NestedProjectedRowData nestedProjectedRowData,
             @Nullable Long limit) {
+        this(readSupplier, nestedProjectedRowData, limit, null, false);
+    }
+
+    public ReadOperator(
+            SerializableSupplier<TableRead> readSupplier,
+            @Nullable NestedProjectedRowData nestedProjectedRowData,
+            @Nullable Long limit,
+            @Nullable RowType readType,
+            boolean blobAsDescriptor) {
         this.readSupplier = readSupplier;
         this.nestedProjectedRowData = nestedProjectedRowData;
         this.limit = limit;
+        this.readType = readType;
+        this.blobAsDescriptor = blobAsDescriptor;
     }
 
     @Override
@@ -101,7 +120,18 @@ public class ReadOperator extends 
AbstractStreamOperator<RowData>
                                 .getSpillingDirectoriesPaths());
         this.read = readSupplier.get().withIOManager(ioManager);
         this.recordLimiter = RecordLimiter.create(limit);
-        this.reuseRow = new FlinkRowData(null);
+        Set<Integer> blobFields = new HashSet<>();
+        if (readType != null) {
+            for (int i = 0; i < readType.getFieldCount(); i++) {
+                if (BlobType.isBlobFileField(readType.getTypeAt(i))) {
+                    blobFields.add(i);
+                }
+            }
+        }
+        this.reuseRow =
+                blobFields.isEmpty()
+                        ? new FlinkRowData(null)
+                        : new FlinkRowDataWithBlob(null, blobFields, 
blobAsDescriptor);
         this.reuseRecord = new StreamRecord<>(null);
         this.idlingStarted();
     }
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 99b785f641..d9e894646e 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
@@ -83,6 +83,42 @@ public class BlobTableITCase extends CatalogITCaseBase {
         assertThat(batchSql("SELECT file_path FROM 
`blob_table$files`").size()).isEqualTo(2);
     }
 
+    @Test
+    public void testDedicatedSplitGenerationWithBlobProjection() {
+        tEnv.executeSql(
+                "CREATE TABLE dedicated_blob_table (id INT, data STRING, 
picture BYTES)"
+                        + " WITH ('row-tracking.enabled'='true',"
+                        + " 'data-evolution.enabled'='true',"
+                        + " 'blob-field'='picture')");
+        batchSql(
+                "INSERT INTO dedicated_blob_table VALUES"
+                        + " (1, 'paimon', X'48656C6C6F'),"
+                        + " (2, 'flink', X'5945')");
+
+        assertThat(
+                        batchSql(
+                                "SELECT picture, id FROM dedicated_blob_table"
+                                        + " /*+ 
OPTIONS('scan.dedicated-split-generation'='true') */"
+                                        + " ORDER BY id"))
+                .containsExactly(
+                        Row.of(new byte[] {72, 101, 108, 108, 111}, 1),
+                        Row.of(new byte[] {89, 69}, 2));
+
+        batchSql("ALTER TABLE dedicated_blob_table SET 
('blob-as-descriptor'='true')");
+        List<Row> descriptorRows =
+                batchSql(
+                        "SELECT picture, id FROM dedicated_blob_table"
+                                + " /*+ 
OPTIONS('scan.dedicated-split-generation'='true') */"
+                                + " ORDER BY id");
+        assertThat(descriptorRows).hasSize(2);
+        assertThat(BlobDescriptor.deserialize((byte[]) 
descriptorRows.get(0).getField(0)).length())
+                .isEqualTo(5);
+        assertThat(descriptorRows.get(0).getField(1)).isEqualTo(1);
+        assertThat(BlobDescriptor.deserialize((byte[]) 
descriptorRows.get(1).getField(0)).length())
+                .isEqualTo(2);
+        assertThat(descriptorRows.get(1).getField(1)).isEqualTo(2);
+    }
+
     @Test
     public void testMultipleBlobs() {
         batchSql("SELECT * FROM multiple_blob_table");
diff --git 
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/source/operator/ReadOperatorBlobTest.java
 
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/source/operator/ReadOperatorBlobTest.java
new file mode 100644
index 0000000000..19ddab1a38
--- /dev/null
+++ 
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/source/operator/ReadOperatorBlobTest.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.paimon.flink.source.operator;
+
+import org.apache.paimon.data.BinaryRow;
+import org.apache.paimon.data.Blob;
+import org.apache.paimon.data.BlobDescriptor;
+import org.apache.paimon.data.GenericRow;
+import org.apache.paimon.data.InternalRow;
+import org.apache.paimon.disk.IOManager;
+import org.apache.paimon.metrics.MetricRegistry;
+import org.apache.paimon.reader.RecordReader;
+import org.apache.paimon.table.source.DataSplit;
+import org.apache.paimon.table.source.Split;
+import org.apache.paimon.table.source.TableRead;
+import org.apache.paimon.types.DataTypes;
+import org.apache.paimon.types.RowType;
+import org.apache.paimon.utils.IteratorRecordReader;
+import org.apache.paimon.utils.UriReader;
+
+import org.apache.flink.streaming.runtime.streamrecord.StreamRecord;
+import org.apache.flink.streaming.util.OneInputStreamOperatorTestHarness;
+import org.apache.flink.table.data.RowData;
+import org.apache.flink.table.runtime.typeutils.InternalSerializers;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.util.Collections;
+
+import static org.apache.paimon.flink.LogicalTypeConversion.toLogicalType;
+import static org.assertj.core.api.Assertions.assertThat;
+
+/** Tests {@link ReadOperator} reading BLOB values on the dedicated split 
path. */
+public class ReadOperatorBlobTest {
+
+    private static final RowType READ_TYPE = RowType.of(DataTypes.BLOB());
+
+    @Test
+    public void testReadBlobAsDescriptor() throws Exception {
+        BlobDescriptor descriptor = new BlobDescriptor("file:///blob", 7, 11);
+        byte[] result = readBlob(Blob.fromDescriptor(UriReader.fromHttp(), 
descriptor), true);
+
+        assertThat(result).isEqualTo(descriptor.serialize());
+    }
+
+    @Test
+    public void testReadBlobAsData() throws Exception {
+        byte[] data = new byte[] {1, 2, 3};
+
+        assertThat(readBlob(Blob.fromData(data), false)).isEqualTo(data);
+    }
+
+    private byte[] readBlob(Blob blob, boolean blobAsDescriptor) throws 
Exception {
+        ReadOperator operator =
+                new ReadOperator(
+                        () -> new TestingTableRead(GenericRow.of(blob)),
+                        null,
+                        null,
+                        READ_TYPE,
+                        blobAsDescriptor);
+        OneInputStreamOperatorTestHarness<Split, RowData> harness =
+                new OneInputStreamOperatorTestHarness<>(operator);
+        harness.setup(InternalSerializers.create(toLogicalType(READ_TYPE)));
+        harness.open();
+        try {
+            DataSplit split =
+                    DataSplit.builder()
+                            .withPartition(BinaryRow.EMPTY_ROW)
+                            .withBucket(0)
+                            .withBucketPath("bucket-0")
+                            .withDataFiles(Collections.emptyList())
+                            .build();
+            harness.processElement(new StreamRecord<>(split));
+
+            assertThat(harness.getOutput()).hasSize(1);
+            @SuppressWarnings("unchecked")
+            StreamRecord<RowData> result =
+                    (StreamRecord<RowData>) 
harness.getOutput().iterator().next();
+            return result.getValue().getBinary(0);
+        } finally {
+            harness.close();
+        }
+    }
+
+    private static class TestingTableRead implements TableRead {
+
+        private final InternalRow row;
+
+        private TestingTableRead(InternalRow row) {
+            this.row = row;
+        }
+
+        @Override
+        public TableRead withMetricRegistry(MetricRegistry registry) {
+            return this;
+        }
+
+        @Override
+        public TableRead executeFilter() {
+            return this;
+        }
+
+        @Override
+        public TableRead withIOManager(IOManager ioManager) {
+            return this;
+        }
+
+        @Override
+        public RecordReader<InternalRow> createReader(Split split) throws 
IOException {
+            return new 
IteratorRecordReader<>(Collections.singleton(row).iterator());
+        }
+    }
+}

Reply via email to