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 1ef01ddc2c [core] Add coverage for dedicated-format bundle fallback 
(#7598)
1ef01ddc2c is described below

commit 1ef01ddc2c7cfe2d80f6af961ea54c57d5f59df3
Author: QuakeWang <[email protected]>
AuthorDate: Tue Jun 23 15:24:49 2026 +0800

    [core] Add coverage for dedicated-format bundle fallback (#7598)
---
 .../DedicatedFormatRollingFileWriterTest.java      | 169 +++++++++++++++++++--
 ...DedicatedFormatRollingFileWriterVectorTest.java | 156 +++++++++++++++++++
 2 files changed, 314 insertions(+), 11 deletions(-)

diff --git 
a/paimon-core/src/test/java/org/apache/paimon/append/DedicatedFormatRollingFileWriterTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/append/DedicatedFormatRollingFileWriterTest.java
index c0bd2814ce..ffd6baedb6 100644
--- 
a/paimon-core/src/test/java/org/apache/paimon/append/DedicatedFormatRollingFileWriterTest.java
+++ 
b/paimon-core/src/test/java/org/apache/paimon/append/DedicatedFormatRollingFileWriterTest.java
@@ -43,9 +43,11 @@ import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
 
 import java.io.IOException;
+import java.nio.file.Files;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Random;
+import java.util.stream.Stream;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
@@ -146,8 +148,80 @@ public class DedicatedFormatRollingFileWriterTest {
     }
 
     @Test
-    public void testBundleWriting() throws IOException {
-        // Create a bundle of records
+    public void testBundleWritingPreservesMainFileIndexSideEffects() throws 
IOException {
+        Options options = new Options();
+        options.set("file-index.bloom-filter.columns", "f0");
+        options.set("file-index.in-manifest-threshold", "1 MB");
+        CoreOptions coreOptions = new CoreOptions(options);
+        writer =
+                new DedicatedFormatRollingFileWriter(
+                        LocalFileIO.create(),
+                        SCHEMA_ID,
+                        FileFormat.fromIdentifier("parquet", new Options()),
+                        null,
+                        TARGET_FILE_SIZE,
+                        TARGET_FILE_SIZE,
+                        TARGET_FILE_SIZE,
+                        SCHEMA,
+                        pathFactory,
+                        () -> seqNumCounter,
+                        COMPRESSION,
+                        new StatsCollectorFactories(coreOptions),
+                        new FileIndexOptions(coreOptions),
+                        FileSource.APPEND,
+                        false,
+                        BlobFileContext.create(SCHEMA, coreOptions));
+
+        List<InternalRow> rows =
+                Arrays.asList(
+                        GenericRow.of(
+                                1, BinaryString.fromString("test1"), new 
BlobData(testBlobData)),
+                        GenericRow.of(
+                                2, BinaryString.fromString("test2"), new 
BlobData(testBlobData)),
+                        GenericRow.of(
+                                3, BinaryString.fromString("test3"), new 
BlobData(testBlobData)));
+
+        writer.writeBundle(new SingleUseBundleRecords(rows));
+        writer.close();
+
+        DataFileMeta mainFile =
+                writer.result().stream()
+                        .filter(file -> "parquet".equals(file.fileFormat()))
+                        .findFirst()
+                        .get();
+
+        assertThat(mainFile.rowCount()).isEqualTo(rows.size());
+        assertThat(mainFile.embeddedIndex()).isNotNull();
+        assertThat(mainFile.embeddedIndex()).isNotEmpty();
+        assertThat(mainFile.extraFiles()).isEmpty();
+    }
+
+    @Test
+    public void testBundleWritingWithExternalStorageFallback() throws 
IOException {
+        Options options = new Options();
+        options.set(CoreOptions.BLOB_DESCRIPTOR_FIELD, "f2");
+        options.set(CoreOptions.BLOB_EXTERNAL_STORAGE_FIELD, "f2");
+        java.nio.file.Path externalStoragePath = 
tempDir.resolve("external-storage-blob-path");
+        options.set(CoreOptions.BLOB_EXTERNAL_STORAGE_PATH, 
externalStoragePath.toString());
+        writer =
+                new DedicatedFormatRollingFileWriter(
+                        LocalFileIO.create(),
+                        SCHEMA_ID,
+                        FileFormat.fromIdentifier("parquet", new Options()),
+                        null,
+                        TARGET_FILE_SIZE,
+                        TARGET_FILE_SIZE,
+                        TARGET_FILE_SIZE,
+                        SCHEMA,
+                        pathFactory,
+                        () -> seqNumCounter,
+                        COMPRESSION,
+                        new StatsCollectorFactories(new CoreOptions(options)),
+                        new FileIndexOptions(),
+                        FileSource.APPEND,
+                        false,
+                        BlobFileContext.create(SCHEMA, new 
CoreOptions(options)));
+
         List<InternalRow> rows =
                 Arrays.asList(
                         GenericRow.of(
@@ -157,10 +231,17 @@ public class DedicatedFormatRollingFileWriterTest {
                         GenericRow.of(
                                 3, BinaryString.fromString("test3"), new 
BlobData(testBlobData)));
 
-        // Write bundle
-        writer.writeBundle(new TestBundleRecords(rows));
+        writer.writeBundle(new SingleUseBundleRecords(rows));
+        writer.close();
+        List<DataFileMeta> metasResult = writer.result();
 
         assertThat(writer.recordCount()).isEqualTo(3);
+        assertThat(metasResult).hasSize(1);
+        assertThat(metasResult.get(0).rowCount()).isEqualTo(3);
+        assertThat(Files.exists(externalStoragePath)).isTrue();
+        try (Stream<java.nio.file.Path> stream = 
Files.list(externalStoragePath)) {
+            assertThat(stream.anyMatch(p -> 
p.getFileName().toString().endsWith(".blob"))).isTrue();
+        }
     }
 
     @Test
@@ -253,6 +334,64 @@ public class DedicatedFormatRollingFileWriterTest {
         assertThat(blobSizeTestWriter.recordCount()).isEqualTo(400);
     }
 
+    @Test
+    public void testBundleWritingRespectsBlobTargetFileSize() throws 
IOException {
+        long blobTargetFileSize = 2 * 1024 * 1024L;
+        DedicatedFormatRollingFileWriter bundleWriter =
+                new DedicatedFormatRollingFileWriter(
+                        LocalFileIO.create(),
+                        SCHEMA_ID,
+                        FileFormat.fromIdentifier("parquet", new Options()),
+                        null,
+                        128 * 1024 * 1024,
+                        blobTargetFileSize,
+                        128 * 1024 * 1024,
+                        SCHEMA,
+                        new DataFilePathFactory(
+                                new Path(tempDir + "/bundle-blob-size-test"),
+                                "parquet",
+                                "data-",
+                                "changelog",
+                                false,
+                                null,
+                                null),
+                        () -> new LongCounter(),
+                        COMPRESSION,
+                        new StatsCollectorFactories(new CoreOptions(new 
Options())),
+                        new FileIndexOptions(),
+                        FileSource.APPEND,
+                        false,
+                        BlobFileContext.create(SCHEMA, new CoreOptions(new 
Options())));
+
+        byte[] blobData = new byte[1024 * 1024];
+        new Random(321).nextBytes(blobData);
+        List<InternalRow> rows = new java.util.ArrayList<>();
+        for (int i = 0; i < 10; i++) {
+            rows.add(
+                    GenericRow.of(
+                            i,
+                            BinaryString.fromString("bundle-blob-test-" + i),
+                            new BlobData(blobData)));
+        }
+
+        bundleWriter.writeBundle(new SingleUseBundleRecords(rows));
+        bundleWriter.close();
+
+        List<DataFileMeta> blobFiles =
+                bundleWriter.result().stream()
+                        .filter(file -> "blob".equals(file.fileFormat()))
+                        .collect(java.util.stream.Collectors.toList());
+        assertThat(blobFiles)
+                .as("Bundle writes should still roll blob files inside the 
bundle.")
+                .hasSizeGreaterThan(1);
+        for (DataFileMeta blobFile : blobFiles.subList(0, blobFiles.size() - 
1)) {
+            assertThat(blobFile.fileSize())
+                    .isGreaterThanOrEqualTo(blobTargetFileSize)
+                    .isLessThanOrEqualTo(blobTargetFileSize + blobData.length);
+        }
+        assertThat(bundleWriter.recordCount()).isEqualTo(rows.size());
+    }
+
     @Test
     public void testSchemaValidation() throws IOException {
         // Test that the writer correctly handles the schema with blob field
@@ -494,18 +633,21 @@ public class DedicatedFormatRollingFileWriterTest {
 
     @Test
     void testSequenceNumberIncrementInBlobWritePathBatch() throws IOException {
-        // Write multiple rows as a batch and verify sequence-number 
continuity in blob files
         int numRows = 10;
+        List<InternalRow> rows = new java.util.ArrayList<>();
         for (int i = 0; i < numRows; i++) {
-            InternalRow row =
+            rows.add(
                     GenericRow.of(
-                            i, BinaryString.fromString("test" + i), new 
BlobData(testBlobData));
-            writer.write(row);
+                            i, BinaryString.fromString("test" + i), new 
BlobData(testBlobData)));
         }
+        writer.writeBundle(new SingleUseBundleRecords(rows));
 
         writer.close();
         List<DataFileMeta> metasResult = writer.result();
 
+        assertThat(metasResult).hasSize(2);
+        
assertThat(metasResult.get(0).rowCount()).isEqualTo(metasResult.get(1).rowCount());
+
         // Extract blob files (skip the first normal file)
         List<DataFileMeta> blobFiles =
                 metasResult.stream()
@@ -616,16 +758,21 @@ public class DedicatedFormatRollingFileWriterTest {
         assertThat(writer.recordCount()).isEqualTo(3);
     }
 
-    /** Simple implementation of BundleRecords for testing. */
-    private static class TestBundleRecords implements BundleRecords {
+    /** Bundle implementation that can only be iterated once. */
+    private static class SingleUseBundleRecords implements BundleRecords {
         private final List<InternalRow> rows;
+        private boolean iterated;
 
-        public TestBundleRecords(List<InternalRow> rows) {
+        private SingleUseBundleRecords(List<InternalRow> rows) {
             this.rows = rows;
         }
 
         @Override
         public java.util.Iterator<InternalRow> iterator() {
+            if (iterated) {
+                throw new IllegalStateException("Bundle should only be 
consumed once.");
+            }
+            iterated = true;
             return rows.iterator();
         }
 
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/append/DedicatedFormatRollingFileWriterVectorTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/append/DedicatedFormatRollingFileWriterVectorTest.java
index 826d2c0c79..156c79672e 100644
--- 
a/paimon-core/src/test/java/org/apache/paimon/append/DedicatedFormatRollingFileWriterVectorTest.java
+++ 
b/paimon-core/src/test/java/org/apache/paimon/append/DedicatedFormatRollingFileWriterVectorTest.java
@@ -25,18 +25,24 @@ import org.apache.paimon.data.BlobData;
 import org.apache.paimon.data.GenericRow;
 import org.apache.paimon.data.InternalRow;
 import org.apache.paimon.fileindex.FileIndexOptions;
+import org.apache.paimon.fileindex.FileIndexPredicate;
+import org.apache.paimon.fileindex.bitmap.BitmapIndexResult;
 import org.apache.paimon.format.FileFormat;
 import org.apache.paimon.format.blob.BlobFileFormat;
+import org.apache.paimon.fs.ByteArraySeekableStream;
 import org.apache.paimon.fs.Path;
 import org.apache.paimon.fs.local.LocalFileIO;
+import org.apache.paimon.io.BundleRecords;
 import org.apache.paimon.io.DataFileMeta;
 import org.apache.paimon.io.DataFilePathFactory;
 import org.apache.paimon.manifest.FileSource;
 import org.apache.paimon.operation.BlobFileContext;
 import org.apache.paimon.options.Options;
+import org.apache.paimon.predicate.PredicateBuilder;
 import org.apache.paimon.types.DataTypes;
 import org.apache.paimon.types.RowType;
 import org.apache.paimon.utils.LongCounter;
+import org.apache.paimon.utils.RoaringBitmap32;
 import org.apache.paimon.utils.StatsCollectorFactories;
 
 import org.junit.jupiter.api.BeforeEach;
@@ -47,6 +53,7 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Random;
 
@@ -172,6 +179,122 @@ public class DedicatedFormatRollingFileWriterVectorTest {
         assertThat(writer.recordCount()).isEqualTo(rowNum);
     }
 
+    @Test
+    public void testBundleWriting() throws Exception {
+        List<InternalRow> rows = makeRows(8, 10);
+        writer.writeBundle(new SingleUseBundleRecords(rows));
+        writer.close();
+        List<DataFileMeta> metasResult = writer.result();
+
+        assertThat(writer.recordCount()).isEqualTo(rows.size());
+        assertThat(metasResult).hasSize(3);
+        assertThat(metasResult.get(0).rowCount()).isEqualTo(rows.size());
+        assertThat(metasResult.get(1).rowCount()).isEqualTo(rows.size());
+        assertThat(metasResult.get(2).rowCount()).isEqualTo(rows.size());
+        assertBundleSequenceSideEffects(metasResult, rows.size());
+    }
+
+    @Test
+    public void testBundleWritingRespectsVectorTargetFileSize() throws 
Exception {
+        long vectorTargetFileSize = 16 * 1024L;
+        writer =
+                new DedicatedFormatRollingFileWriter(
+                        LocalFileIO.create(),
+                        SCHEMA_ID,
+                        FileFormat.fromIdentifier("parquet", new Options()),
+                        FileFormat.fromIdentifier("json", new Options()),
+                        128 * 1024 * 1024,
+                        128 * 1024 * 1024,
+                        vectorTargetFileSize,
+                        SCHEMA,
+                        new DataFilePathFactory(
+                                new Path(tempDir + "/bundle-vector-size-test"),
+                                "parquet",
+                                "data-",
+                                "changelog",
+                                false,
+                                null,
+                                null),
+                        () -> new LongCounter(),
+                        COMPRESSION,
+                        new StatsCollectorFactories(new CoreOptions(new 
Options())),
+                        new FileIndexOptions(),
+                        FileSource.APPEND,
+                        false,
+                        BlobFileContext.create(SCHEMA, new CoreOptions(new 
Options())));
+
+        List<InternalRow> rows = makeRows(2000, 1);
+        writer.writeBundle(new SingleUseBundleRecords(rows));
+        writer.close();
+
+        List<DataFileMeta> vectorStoreFiles =
+                writer.result().stream()
+                        .filter(file -> isVectorStoreFile(file.fileName()))
+                        .collect(java.util.stream.Collectors.toList());
+        assertThat(vectorStoreFiles)
+                .as("Bundle writes should still roll vector files inside the 
bundle.")
+                .hasSizeGreaterThan(1);
+        for (DataFileMeta file : vectorStoreFiles.subList(0, 
vectorStoreFiles.size() - 1)) {
+            
assertThat(file.fileSize()).isGreaterThanOrEqualTo(vectorTargetFileSize);
+        }
+        assertThat(writer.recordCount()).isEqualTo(rows.size());
+    }
+
+    @Test
+    public void testBundleWritingPreservesMainFileIndexSideEffects() throws 
Exception {
+        Options options = new Options();
+        options.set("file-index.bitmap.columns", "f0");
+        options.set("file-index.in-manifest-threshold", "1 MB");
+        CoreOptions coreOptions = new CoreOptions(options);
+        writer =
+                new DedicatedFormatRollingFileWriter(
+                        LocalFileIO.create(),
+                        SCHEMA_ID,
+                        FileFormat.fromIdentifier("parquet", new Options()),
+                        FileFormat.fromIdentifier("json", new Options()),
+                        TARGET_FILE_SIZE,
+                        TARGET_FILE_SIZE,
+                        VECTOR_TARGET_FILE_SIZE,
+                        SCHEMA,
+                        pathFactory,
+                        () -> seqNumCounter,
+                        COMPRESSION,
+                        new StatsCollectorFactories(coreOptions),
+                        new FileIndexOptions(coreOptions),
+                        FileSource.APPEND,
+                        false,
+                        BlobFileContext.create(SCHEMA, coreOptions));
+
+        List<InternalRow> rows = makeRows(4, 10);
+        writer.writeBundle(new SingleUseBundleRecords(rows));
+        writer.close();
+
+        DataFileMeta mainFile =
+                writer.result().stream()
+                        .filter(file -> "parquet".equals(file.fileFormat()))
+                        .findFirst()
+                        .get();
+
+        assertThat(mainFile.rowCount()).isEqualTo(rows.size());
+        assertThat(mainFile.embeddedIndex()).isNotEmpty();
+        assertThat(mainFile.extraFiles()).isEmpty();
+
+        RowType normalFileSchema =
+                RowType.builder()
+                        .field("f0", DataTypes.INT())
+                        .field("f1", DataTypes.STRING())
+                        .field("f4", DataTypes.INT())
+                        .build();
+        PredicateBuilder predicateBuilder = new 
PredicateBuilder(normalFileSchema);
+        try (FileIndexPredicate index =
+                new FileIndexPredicate(
+                        new ByteArraySeekableStream(mainFile.embeddedIndex()), 
normalFileSchema)) {
+            assertThat(((BitmapIndexResult) 
index.evaluate(predicateBuilder.equal(0, 2))).get())
+                    .isEqualTo(RoaringBitmap32.bitmapOf(2));
+            assertThat(index.evaluate(predicateBuilder.equal(0, 
99)).remain()).isFalse();
+        }
+    }
+
     @Test
     void testVectorStoreFileNameFormatWithSharedUuid() throws Exception {
         // 100k vector-store data would create 1 normal, 1 blob, and 3 
vector-store files
@@ -310,4 +433,37 @@ public class DedicatedFormatRollingFileWriterVectorTest {
         }
         return rows;
     }
+
+    private void assertBundleSequenceSideEffects(List<DataFileMeta> metas, 
long rowCount) {
+        long expectedCounter = rowCount * metas.size();
+        assertThat(seqNumCounter.getValue()).isEqualTo(expectedCounter);
+        for (DataFileMeta meta : metas) {
+            assertThat(meta.minSequenceNumber()).isEqualTo(expectedCounter - 
rowCount);
+            assertThat(meta.maxSequenceNumber()).isEqualTo(expectedCounter - 
1);
+        }
+    }
+
+    private static class SingleUseBundleRecords implements BundleRecords {
+
+        private final List<InternalRow> rows;
+        private boolean iterated;
+
+        private SingleUseBundleRecords(List<InternalRow> rows) {
+            this.rows = rows;
+        }
+
+        @Override
+        public Iterator<InternalRow> iterator() {
+            if (iterated) {
+                throw new IllegalStateException("Bundle should only be 
consumed once.");
+            }
+            iterated = true;
+            return rows.iterator();
+        }
+
+        @Override
+        public long rowCount() {
+            return rows.size();
+        }
+    }
 }

Reply via email to