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 83cd9965a2 [core] Harden managed BLOB writes in primary-key tables 
(#8654)
83cd9965a2 is described below

commit 83cd9965a2388ab86f6129d47d60c49bebf7f6af
Author: Jingsong Lee <[email protected]>
AuthorDate: Wed Jul 15 18:22:18 2026 +0800

    [core] Harden managed BLOB writes in primary-key tables (#8654)
    
    Follow up on managed BLOB support for primary-key tables by covering the
    postpone-bucket write path and tightening resource and reference
    handling.
    
    The postpone-bucket writer previously bypassed managed BLOB
    externalization, so raw `BLOB` and `ARRAY<BLOB>` values could reach
    descriptor-backed data-file writers. Pack finalization could also leave
    the underlying output stream open when flush failed, while reference
    collection repeatedly parsed identical descriptor URIs and created
    redundant deduplication containers.
---
 .../paimon/blob/ManagedBlobReferenceCollector.java |  16 +++-
 .../paimon/blob/ManagedBlobReferenceFile.java      |  12 ++-
 .../paimon/blob/PrimaryKeyBlobExternalizer.java    |   6 +-
 .../paimon/io/KeyValueFileWriterFactory.java       |   4 +
 .../paimon/postpone/PostponeBucketWriter.java      |  34 +++++--
 .../blob/PrimaryKeyBlobExternalizerTest.java       |  47 ++++++++++
 .../operation/PrimaryKeyManagedBlobStoreTest.java  | 102 ++++++++++++++++++++-
 .../paimon/postpone/PostponeBucketWriterTest.java  |  80 ++++++++++++++++
 8 files changed, 280 insertions(+), 21 deletions(-)

diff --git 
a/paimon-core/src/main/java/org/apache/paimon/blob/ManagedBlobReferenceCollector.java
 
b/paimon-core/src/main/java/org/apache/paimon/blob/ManagedBlobReferenceCollector.java
index 38b898aea2..21fc29dead 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/blob/ManagedBlobReferenceCollector.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/blob/ManagedBlobReferenceCollector.java
@@ -43,7 +43,7 @@ public class ManagedBlobReferenceCollector {
     private final Path sidecar;
     private final int[] blobFieldIndexes;
     private final boolean[] blobArrayFields;
-    private final Set<ManagedBlobReferenceFile.Reference> references;
+    private final Set<String> descriptorUris;
 
     private boolean closed;
 
@@ -68,7 +68,7 @@ public class ManagedBlobReferenceCollector {
         for (int i = 0; i < arrayFields.size(); i++) {
             blobArrayFields[i] = arrayFields.get(i);
         }
-        this.references = new HashSet<>();
+        this.descriptorUris = new HashSet<>();
     }
 
     public void write(KeyValue keyValue) {
@@ -97,8 +97,7 @@ public class ManagedBlobReferenceCollector {
 
     private void collect(Blob blob) {
         if (blob instanceof BlobRef) {
-            
ManagedBlobReferenceFile.fromDescriptorUri(blob.toDescriptor().uri())
-                    .ifPresent(references::add);
+            descriptorUris.add(blob.toDescriptor().uri());
         }
     }
 
@@ -107,7 +106,14 @@ public class ManagedBlobReferenceCollector {
             return;
         }
         try {
-            ManagedBlobReferenceFile.write(fileIO, sidecar, new 
ArrayList<>(references));
+            List<ManagedBlobReferenceFile.Reference> references =
+                    new ArrayList<>(descriptorUris.size());
+            for (String descriptorUri : descriptorUris) {
+                ManagedBlobReferenceFile.fromDescriptorUri(descriptorUri)
+                        .ifPresent(references::add);
+            }
+            descriptorUris.clear();
+            ManagedBlobReferenceFile.write(fileIO, sidecar, references);
             closed = true;
         } catch (IOException e) {
             abort();
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/blob/ManagedBlobReferenceFile.java
 
b/paimon-core/src/main/java/org/apache/paimon/blob/ManagedBlobReferenceFile.java
index d04457ecc2..f7b10512c6 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/blob/ManagedBlobReferenceFile.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/blob/ManagedBlobReferenceFile.java
@@ -26,7 +26,6 @@ import java.io.DataOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Comparator;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
@@ -61,10 +60,19 @@ public class ManagedBlobReferenceFile {
 
     public static void write(FileIO fileIO, Path path, List<Reference> 
references)
             throws IOException {
-        List<Reference> normalized = new ArrayList<>(new 
HashSet<>(references));
+        List<Reference> normalized = new ArrayList<>(references);
         normalized.sort(
                 Comparator.comparing(Reference::storageRootId)
                         .thenComparing(Reference::relativePath));
+        int uniqueCount = 0;
+        for (Reference reference : normalized) {
+            if (uniqueCount == 0 || 
!reference.equals(normalized.get(uniqueCount - 1))) {
+                normalized.set(uniqueCount++, reference);
+            }
+        }
+        if (uniqueCount < normalized.size()) {
+            normalized.subList(uniqueCount, normalized.size()).clear();
+        }
 
         try (DataOutputStream out = new 
DataOutputStream(fileIO.newOutputStream(path, false))) {
             out.writeInt(MAGIC);
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/blob/PrimaryKeyBlobExternalizer.java
 
b/paimon-core/src/main/java/org/apache/paimon/blob/PrimaryKeyBlobExternalizer.java
index b09c59b349..9216f7597c 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/blob/PrimaryKeyBlobExternalizer.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/blob/PrimaryKeyBlobExternalizer.java
@@ -279,10 +279,10 @@ public class PrimaryKeyBlobExternalizer {
             if (writer == null) {
                 return;
             }
-            try {
+            PositionOutputStream currentOut = out;
+            try (PositionOutputStream ignored = currentOut) {
                 writer.close();
-                out.flush();
-                out.close();
+                currentOut.flush();
             } finally {
                 writer = null;
                 out = null;
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/io/KeyValueFileWriterFactory.java 
b/paimon-core/src/main/java/org/apache/paimon/io/KeyValueFileWriterFactory.java
index 0e17ec5e4b..547d827a73 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/io/KeyValueFileWriterFactory.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/io/KeyValueFileWriterFactory.java
@@ -107,6 +107,10 @@ public class KeyValueFileWriterFactory {
         return valueType;
     }
 
+    public boolean hasBlobExternalizer() {
+        return blobExternalizer != null;
+    }
+
     public InternalRow externalizeBlob(RowKind valueKind, InternalRow value) 
throws IOException {
         return blobExternalizer == null ? value : 
blobExternalizer.externalize(valueKind, value);
     }
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/postpone/PostponeBucketWriter.java
 
b/paimon-core/src/main/java/org/apache/paimon/postpone/PostponeBucketWriter.java
index 47256749b9..47068513a8 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/postpone/PostponeBucketWriter.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/postpone/PostponeBucketWriter.java
@@ -22,6 +22,7 @@ import org.apache.paimon.KeyValue;
 import org.apache.paimon.KeyValueSerializer;
 import org.apache.paimon.annotation.VisibleForTesting;
 import org.apache.paimon.compression.CompressOptions;
+import org.apache.paimon.data.InternalRow;
 import org.apache.paimon.disk.IOManager;
 import org.apache.paimon.fs.FileIO;
 import org.apache.paimon.io.CompactIncrement;
@@ -54,8 +55,6 @@ import java.util.List;
 /** {@link RecordWriter} for {@code bucket = -2} tables. */
 public class PostponeBucketWriter implements RecordWriter<KeyValue>, 
MemoryOwner {
 
-    private final FileIO fileIO;
-    private final DataFilePathFactory pathFactory;
     private final MergeFunction<KeyValue> mergeFunction;
     private final KeyValueFileWriterFactory writerFactory;
     private final List<DataFileMeta> files;
@@ -84,8 +83,6 @@ public class PostponeBucketWriter implements 
RecordWriter<KeyValue>, MemoryOwner
         this.mergeFunction = mergeFunction;
         this.writerFactory = writerFactory;
         this.fileRead = fileRead;
-        this.fileIO = fileIO;
-        this.pathFactory = pathFactory;
         this.spillCompression = spillCompression;
         this.maxDiskSize = maxDiskSize;
         this.files = new ArrayList<>();
@@ -104,11 +101,25 @@ public class PostponeBucketWriter implements 
RecordWriter<KeyValue>, MemoryOwner
 
     @Override
     public void write(KeyValue record) throws Exception {
-        validateRetract(record);
-        boolean success = sinkWriter.write(record);
+        KeyValue externalizedRecord = record;
+        if (writerFactory.hasBlobExternalizer()) {
+            InternalRow value = 
writerFactory.externalizeBlob(record.valueKind(), record.value());
+            if (value != record.value()) {
+                externalizedRecord =
+                        new KeyValue()
+                                .replace(
+                                        record.key(),
+                                        record.sequenceNumber(),
+                                        record.valueKind(),
+                                        value)
+                                .setLevel(record.level());
+            }
+        }
+        validateRetract(externalizedRecord);
+        boolean success = sinkWriter.write(externalizedRecord);
         if (!success) {
             flush();
-            success = sinkWriter.write(record);
+            success = sinkWriter.write(externalizedRecord);
             if (!success) {
                 // Should not get here, because writeBuffer will throw too big 
exception out.
                 // But we throw again in case of something unexpected happens. 
(like someone changed
@@ -205,7 +216,7 @@ public class PostponeBucketWriter implements 
RecordWriter<KeyValue>, MemoryOwner
             } finally {
                 // remove small files
                 for (DataFileMeta file : files) {
-                    fileIO.deleteQuietly(pathFactory.toPath(file));
+                    writerFactory.deleteFile(file);
                 }
             }
         }
@@ -214,6 +225,7 @@ public class PostponeBucketWriter implements 
RecordWriter<KeyValue>, MemoryOwner
     @Override
     public CommitIncrement prepareCommit(boolean waitCompaction) throws 
Exception {
         flush();
+        writerFactory.prepareCommit();
         List<DataFileMeta> result = new ArrayList<>(files);
         files.clear();
         return new CommitIncrement(
@@ -237,6 +249,10 @@ public class PostponeBucketWriter implements 
RecordWriter<KeyValue>, MemoryOwner
 
     @Override
     public void close() throws Exception {
-        sinkWriter.close();
+        try {
+            writerFactory.abortManagedBlobWrites();
+        } finally {
+            sinkWriter.close();
+        }
     }
 }
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/blob/PrimaryKeyBlobExternalizerTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/blob/PrimaryKeyBlobExternalizerTest.java
index bb01bb4c06..5fe4686c9f 100644
--- 
a/paimon-core/src/test/java/org/apache/paimon/blob/PrimaryKeyBlobExternalizerTest.java
+++ 
b/paimon-core/src/test/java/org/apache/paimon/blob/PrimaryKeyBlobExternalizerTest.java
@@ -26,6 +26,8 @@ import org.apache.paimon.data.GenericRow;
 import org.apache.paimon.data.InternalArray;
 import org.apache.paimon.data.InternalRow;
 import org.apache.paimon.fs.Path;
+import org.apache.paimon.fs.PositionOutputStream;
+import org.apache.paimon.fs.PositionOutputStreamWrapper;
 import org.apache.paimon.fs.local.LocalFileIO;
 import org.apache.paimon.io.DataFilePathFactory;
 import org.apache.paimon.types.DataTypes;
@@ -35,8 +37,10 @@ import org.apache.paimon.types.RowType;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
 
+import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.util.Collections;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -46,6 +50,49 @@ class PrimaryKeyBlobExternalizerTest {
 
     @TempDir java.nio.file.Path tempDir;
 
+    @Test
+    void testClosesPackStreamWhenFlushFails() throws Exception {
+        AtomicBoolean closed = new AtomicBoolean();
+        LocalFileIO fileIO =
+                new LocalFileIO() {
+                    @Override
+                    public PositionOutputStream newOutputStream(Path path, 
boolean overwrite)
+                            throws IOException {
+                        return new PositionOutputStreamWrapper(
+                                super.newOutputStream(path, overwrite)) {
+                            @Override
+                            public void flush() throws IOException {
+                                throw new IOException("flush failure");
+                            }
+
+                            @Override
+                            public void close() throws IOException {
+                                closed.set(true);
+                                super.close();
+                            }
+                        };
+                    }
+                };
+        Path bucketPath = new Path(tempDir.resolve("bucket-0").toUri());
+        fileIO.mkdirs(bucketPath);
+        DataFilePathFactory pathFactory =
+                new DataFilePathFactory(
+                        bucketPath, "avro", "data-", "changelog-", false, 
null, null);
+        PrimaryKeyBlobExternalizer externalizer =
+                new PrimaryKeyBlobExternalizer(
+                        fileIO,
+                        RowType.of(DataTypes.BLOB()),
+                        Collections.singleton("f0"),
+                        pathFactory,
+                        1024L);
+        externalizer.externalize(RowKind.INSERT, 
GenericRow.of(Blob.fromData(new byte[] {1})));
+
+        assertThatThrownBy(externalizer::prepareCommit)
+                .isInstanceOf(IOException.class)
+                .hasMessage("flush failure");
+        assertThat(closed).isTrue();
+    }
+
     @Test
     void testRejectsNonBlobManagedField() throws Exception {
         LocalFileIO fileIO = LocalFileIO.create();
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/operation/PrimaryKeyManagedBlobStoreTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/operation/PrimaryKeyManagedBlobStoreTest.java
index 333020d29c..7781189c0d 100644
--- 
a/paimon-core/src/test/java/org/apache/paimon/operation/PrimaryKeyManagedBlobStoreTest.java
+++ 
b/paimon-core/src/test/java/org/apache/paimon/operation/PrimaryKeyManagedBlobStoreTest.java
@@ -27,6 +27,7 @@ import org.apache.paimon.data.Blob;
 import org.apache.paimon.data.GenericArray;
 import org.apache.paimon.data.GenericRow;
 import org.apache.paimon.data.InternalArray;
+import org.apache.paimon.disk.IOManager;
 import org.apache.paimon.fs.FileIO;
 import org.apache.paimon.fs.Path;
 import org.apache.paimon.fs.local.LocalFileIO;
@@ -35,10 +36,12 @@ import org.apache.paimon.io.DataFilePathFactory;
 import org.apache.paimon.manifest.ManifestCommittable;
 import org.apache.paimon.manifest.ManifestEntry;
 import org.apache.paimon.mergetree.compact.DeduplicateMergeFunction;
+import org.apache.paimon.postpone.PostponeBucketWriter;
 import org.apache.paimon.schema.KeyValueFieldsExtractor;
 import org.apache.paimon.schema.Schema;
 import org.apache.paimon.schema.SchemaManager;
 import org.apache.paimon.schema.TableSchema;
+import org.apache.paimon.table.BucketMode;
 import org.apache.paimon.table.SpecialFields;
 import org.apache.paimon.table.sink.CommitMessage;
 import org.apache.paimon.types.DataField;
@@ -85,6 +88,96 @@ class PrimaryKeyManagedBlobStoreTest {
         assertThat(read.value().getBlob(1).toData()).isEqualTo(expected);
     }
 
+    @Test
+    void testPostponeBucketExternalizesBlobArray() throws Exception {
+        FileIO fileIO = LocalFileIO.create();
+        TestFileStore store =
+                createStore(
+                        fileIO,
+                        "payloads",
+                        DataTypes.ARRAY(DataTypes.BLOB()),
+                        BucketMode.POSTPONE_BUCKET);
+        byte[] expected = 
"postpone-bucket-blob-array".getBytes(StandardCharsets.UTF_8);
+        KeyValue keyValue =
+                new KeyValue()
+                        .replace(
+                                GenericRow.of(1),
+                                RowKind.INSERT,
+                                GenericRow.of(
+                                        1,
+                                        new GenericArray(new Object[] 
{Blob.fromData(expected)})));
+
+        store.commitData(
+                Collections.singletonList(keyValue),
+                ignored -> BinaryRow.EMPTY_ROW,
+                ignored -> BucketMode.POSTPONE_BUCKET);
+
+        ManifestEntry entry = store.newScan().plan().files().get(0);
+        assertThat(entry.bucket()).isEqualTo(BucketMode.POSTPONE_BUCKET);
+        assertThat(references(fileIO, store, entry)).hasSize(1);
+        KeyValue read =
+                
store.readKvsFromSnapshot(store.snapshotManager().latestSnapshotId()).get(0);
+        
assertThat(read.value().getArray(1).getBlob(0).toData()).isEqualTo(expected);
+    }
+
+    @Test
+    void testPostponeBucketBufferedRewriteKeepsManagedBlobReferences() throws 
Exception {
+        FileIO fileIO = LocalFileIO.create();
+        TestFileStore store =
+                createStore(fileIO, "payload", DataTypes.BLOB(), 
BucketMode.POSTPONE_BUCKET);
+        AbstractFileStoreWrite<KeyValue> write = store.newWrite();
+        try (IOManager ioManager = 
IOManager.create(tempDir.resolve("io").toString())) {
+            try {
+                write.withIOManager(ioManager);
+                write.write(
+                        BinaryRow.EMPTY_ROW,
+                        BucketMode.POSTPONE_BUCKET,
+                        keyValue(
+                                1,
+                                RowKind.INSERT,
+                                
"before-buffered-rewrite".getBytes(StandardCharsets.UTF_8)));
+                PostponeBucketWriter writer =
+                        (PostponeBucketWriter)
+                                write.getWriterWrapper(
+                                                BinaryRow.EMPTY_ROW, 
BucketMode.POSTPONE_BUCKET)
+                                        .writer;
+                writer.toBufferedWriter();
+
+                assertThat(writer.useBufferedSinkWriter()).isTrue();
+                Path bucketPath =
+                        store.pathFactory()
+                                .bucketPath(BinaryRow.EMPTY_ROW, 
BucketMode.POSTPONE_BUCKET);
+                assertThat(fileIO.listStatus(bucketPath))
+                        .extracting(status -> status.getPath().getName())
+                        .allMatch(
+                                name ->
+                                        name.endsWith(
+                                                
ManagedBlobReferenceFile.MANAGED_BLOB_SUFFIX));
+
+                write.write(
+                        BinaryRow.EMPTY_ROW,
+                        BucketMode.POSTPONE_BUCKET,
+                        keyValue(
+                                2,
+                                RowKind.INSERT,
+                                
"after-buffered-rewrite".getBytes(StandardCharsets.UTF_8)));
+                List<CommitMessage> messages = write.prepareCommit(false, 1L);
+                try (FileStoreCommit commit = store.newCommit()) {
+                    commit.commit(new ManifestCommittable(1L, null, messages), 
false);
+                }
+            } finally {
+                write.close();
+            }
+        }
+
+        ManifestEntry entry = store.newScan().plan().files().get(0);
+        assertThat(references(fileIO, store, entry)).hasSize(2);
+        
assertThat(store.readKvsFromSnapshot(store.snapshotManager().latestSnapshotId()))
+                .extracting(
+                        kv -> new String(kv.value().getBlob(1).toData(), 
StandardCharsets.UTF_8))
+                .containsExactlyInAnyOrder("before-buffered-rewrite", 
"after-buffered-rewrite");
+    }
+
     @Test
     void testExternalizeAndReadBlobArray() throws Exception {
         FileIO fileIO = LocalFileIO.create();
@@ -213,6 +306,11 @@ class PrimaryKeyManagedBlobStoreTest {
 
     private TestFileStore createStore(FileIO fileIO, String payloadName, 
DataType payloadType)
             throws Exception {
+        return createStore(fileIO, payloadName, payloadType, 1);
+    }
+
+    private TestFileStore createStore(
+            FileIO fileIO, String payloadName, DataType payloadType, int 
bucket) throws Exception {
         Path tablePath = new Path(tempDir.toUri());
         List<DataField> valueFields =
                 Arrays.asList(
@@ -227,7 +325,7 @@ class PrimaryKeyManagedBlobStoreTest {
                                         SpecialFields.KEY_FIELD_PREFIX + "id",
                                         DataTypes.INT())));
         Map<String, String> options = new HashMap<>();
-        options.put(CoreOptions.BUCKET.key(), "1");
+        options.put(CoreOptions.BUCKET.key(), String.valueOf(bucket));
         options.put(CoreOptions.BLOB_FIELD.key(), payloadName);
         options.put(CoreOptions.BLOB_TARGET_FILE_SIZE.key(), "1 b");
         TableSchema schema =
@@ -256,7 +354,7 @@ class PrimaryKeyManagedBlobStoreTest {
         return new TestFileStore.Builder(
                         "avro",
                         tempDir.toString(),
-                        1,
+                        bucket,
                         RowType.of(),
                         keyType,
                         valueType,
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/postpone/PostponeBucketWriterTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/postpone/PostponeBucketWriterTest.java
new file mode 100644
index 0000000000..5b8994f5d6
--- /dev/null
+++ 
b/paimon-core/src/test/java/org/apache/paimon/postpone/PostponeBucketWriterTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.postpone;
+
+import org.apache.paimon.KeyValue;
+import org.apache.paimon.compression.CompressOptions;
+import org.apache.paimon.data.GenericRow;
+import org.apache.paimon.fs.FileIO;
+import org.apache.paimon.io.DataFileMeta;
+import org.apache.paimon.io.DataFilePathFactory;
+import org.apache.paimon.io.KeyValueFileWriterFactory;
+import org.apache.paimon.io.RollingFileWriter;
+import org.apache.paimon.mergetree.compact.MergeFunction;
+import org.apache.paimon.options.MemorySize;
+import org.apache.paimon.types.RowKind;
+
+import org.junit.jupiter.api.Test;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/** Tests for {@link PostponeBucketWriter}. */
+class PostponeBucketWriterTest {
+
+    @Test
+    @SuppressWarnings("unchecked")
+    void testSkipBlobExternalizationWithoutExternalizer() throws Exception {
+        KeyValueFileWriterFactory writerFactory = 
mock(KeyValueFileWriterFactory.class);
+        RollingFileWriter<KeyValue, DataFileMeta> rollingWriter = 
mock(RollingFileWriter.class);
+        when(writerFactory.hasBlobExternalizer()).thenReturn(false);
+        when(writerFactory.createRollingMergeTreeFileWriter(anyInt(), any()))
+                .thenReturn(rollingWriter);
+        PostponeBucketWriter writer =
+                new PostponeBucketWriter(
+                        mock(FileIO.class),
+                        mock(DataFilePathFactory.class),
+                        CompressOptions.defaultOptions(),
+                        new MemorySize(1024),
+                        null,
+                        mock(MergeFunction.class),
+                        writerFactory,
+                        null,
+                        false,
+                        false,
+                        null);
+        KeyValue record =
+                new KeyValue()
+                        .replace(
+                                GenericRow.of(1),
+                                RowKind.INSERT,
+                                GenericRow.of(1, "ordinary-value"));
+
+        writer.write(record);
+
+        verify(writerFactory).hasBlobExternalizer();
+        verify(writerFactory, never()).externalizeBlob(any(), any());
+        verify(rollingWriter).write(same(record));
+    }
+}

Reply via email to