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 f842658251 [core] Support MAP shared-shredding for primary-key tables
and rewrites (#8709)
f842658251 is described below
commit f8426582514a29bf3eb99547c80f8755f0edcd4d
Author: lxy <[email protected]>
AuthorDate: Sat Jul 18 09:29:43 2026 +0800
[core] Support MAP shared-shredding for primary-key tables and rewrites
(#8709)
---
.../paimon/operation/BaseAppendFileStoreWrite.java | 10 -
.../org/apache/paimon/schema/SchemaValidation.java | 37 +-
.../apache/paimon/append/AppendOnlyWriterTest.java | 71 ++-
.../BucketedAppendFileStoreWriteTest.java | 105 ++++
.../apache/paimon/schema/SchemaValidationTest.java | 49 +-
.../paimon/table/MapSharedShreddingTableTest.java | 610 +++++++++++++++++++++
6 files changed, 834 insertions(+), 48 deletions(-)
diff --git
a/paimon-core/src/main/java/org/apache/paimon/operation/BaseAppendFileStoreWrite.java
b/paimon-core/src/main/java/org/apache/paimon/operation/BaseAppendFileStoreWrite.java
index 901fbe2fe0..7dceb12e5c 100644
---
a/paimon-core/src/main/java/org/apache/paimon/operation/BaseAppendFileStoreWrite.java
+++
b/paimon-core/src/main/java/org/apache/paimon/operation/BaseAppendFileStoreWrite.java
@@ -26,7 +26,6 @@ import org.apache.paimon.compact.CompactManager;
import org.apache.paimon.data.BinaryRow;
import org.apache.paimon.data.BlobConsumer;
import org.apache.paimon.data.InternalRow;
-import org.apache.paimon.data.shredding.MapSharedShreddingUtils;
import org.apache.paimon.deletionvectors.BucketedDvMaintainer;
import org.apache.paimon.deletionvectors.DeletionVector;
import org.apache.paimon.fileindex.FileIndexOptions;
@@ -228,7 +227,6 @@ public abstract class BaseAppendFileStoreWrite extends
MemoryFileStoreWrite<Inte
if (toCompact.isEmpty()) {
return Collections.emptyList();
}
- checkNoSharedShreddingRewrite("Compaction rewrite");
Exception collectedExceptions = null;
RowDataRollingFileWriter rewriter =
createRollingFileWriter(
@@ -261,7 +259,6 @@ public abstract class BaseAppendFileStoreWrite extends
MemoryFileStoreWrite<Inte
public List<DataFileMeta> clusterRewrite(
BinaryRow partition, int bucket, List<DataFileMeta> toCluster)
throws Exception {
- checkNoSharedShreddingRewrite("Cluster rewrite");
RecordReaderIterator<InternalRow> reader =
createFilesIterator(partition, bucket, toCluster, null);
@@ -298,13 +295,6 @@ public abstract class BaseAppendFileStoreWrite extends
MemoryFileStoreWrite<Inte
return rewriter.result();
}
- private void checkNoSharedShreddingRewrite(String rewriteName) {
- if (!MapSharedShreddingUtils.detectShreddingColumns(writeType,
options).isEmpty()) {
- throw new UnsupportedOperationException(
- rewriteName + " is not supported for MAP
shared-shredding.");
- }
- }
-
private RowDataRollingFileWriter createRollingFileWriter(
BinaryRow partition, int bucket, Supplier<LongCounter>
seqNumCounterSupplier) {
return new RowDataRollingFileWriter(
diff --git
a/paimon-core/src/main/java/org/apache/paimon/schema/SchemaValidation.java
b/paimon-core/src/main/java/org/apache/paimon/schema/SchemaValidation.java
index 4b81ba6ae4..d145704fef 100644
--- a/paimon-core/src/main/java/org/apache/paimon/schema/SchemaValidation.java
+++ b/paimon-core/src/main/java/org/apache/paimon/schema/SchemaValidation.java
@@ -682,35 +682,40 @@ public class SchemaValidation {
if (hasSharedShredding) {
validateMapSharedShreddingFileFormats(options);
validateMapSharedShreddingCompressions(options);
- validateUnsupportedTypesWithMapSharedShredding(schema);
- if (!schema.primaryKeys().isEmpty()) {
+ validateUnsupportedTypesWithMapSharedShredding(schema, options);
+ if (options.bucket() == BucketMode.POSTPONE_BUCKET) {
throw new IllegalArgumentException(
- "MAP shared-shredding currently only supports
append-only tables.");
- }
- if (options.bucket() != -1 && !options.writeOnly()) {
- throw new IllegalArgumentException(
- "MAP shared-shredding currently requires bucket = -1
or write-only = true because rewrite/compaction is not supported.");
+ "MAP shared-shredding currently does not support
postpone bucket mode.");
}
}
}
- private static void
validateUnsupportedTypesWithMapSharedShredding(TableSchema schema) {
+ private static void validateUnsupportedTypesWithMapSharedShredding(
+ TableSchema schema, CoreOptions options) {
RowType rowType = new RowType(schema.fields());
if (containsType(rowType, type -> type instanceof VariantType)) {
throw new IllegalArgumentException(
"MAP shared-shredding currently cannot be used with
Variant fields.");
}
- if (containsType(rowType, type -> type.is(DataTypeRoot.BLOB))) {
- throw new IllegalArgumentException(
- "MAP shared-shredding currently cannot be used with BLOB
fields.");
- }
if (containsType(rowType, type -> type instanceof MultisetType)) {
throw new IllegalArgumentException(
"MAP shared-shredding currently cannot be used with
MULTISET fields.");
}
- if (containsType(rowType, type -> type instanceof VectorType)) {
- throw new IllegalArgumentException(
- "MAP shared-shredding currently cannot be used with VECTOR
fields.");
+
+ for (DataField field : schema.fields()) {
+ if (options.mapStorageLayout(field.name()) !=
MapStorageLayout.SHARED_SHREDDING) {
+ continue;
+ }
+
+ DataType valueType = ((MapType) field.type()).getValueType();
+ if (containsType(valueType, type -> type.is(DataTypeRoot.BLOB))) {
+ throw new IllegalArgumentException(
+ "MAP shared-shredding currently cannot contain BLOB
fields.");
+ }
+ if (containsType(valueType, type -> type instanceof VectorType)) {
+ throw new IllegalArgumentException(
+ "MAP shared-shredding currently cannot contain VECTOR
fields.");
+ }
}
}
@@ -748,8 +753,6 @@ public class SchemaValidation {
}
validateMapSharedShreddingFileFormat(
CoreOptions.CHANGELOG_FILE_FORMAT.key(),
options.changelogFileFormat());
- validateMapSharedShreddingFileFormat(
- CoreOptions.VECTOR_FILE_FORMAT.key(),
options.vectorFileFormatString());
}
private static void validateMapSharedShreddingCompressions(CoreOptions
options) {
diff --git
a/paimon-core/src/test/java/org/apache/paimon/append/AppendOnlyWriterTest.java
b/paimon-core/src/test/java/org/apache/paimon/append/AppendOnlyWriterTest.java
index 8354945364..47ebf0fbaa 100644
---
a/paimon-core/src/test/java/org/apache/paimon/append/AppendOnlyWriterTest.java
+++
b/paimon-core/src/test/java/org/apache/paimon/append/AppendOnlyWriterTest.java
@@ -76,6 +76,8 @@ import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
+import javax.annotation.Nullable;
+
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
@@ -617,6 +619,62 @@ public class AppendOnlyWriterTest {
3));
}
+ @ParameterizedTest(name = "{0}")
+ @ValueSource(strings = {CoreOptions.FILE_FORMAT_PARQUET,
CoreOptions.FILE_FORMAT_ORC})
+ public void testSharedShreddingMapWithBlob(String fileFormat) throws
Exception {
+ RowType writeType =
+ DataTypes.ROW(
+ DataTypes.FIELD(0, "id", DataTypes.INT()),
+ DataTypes.FIELD(
+ 1,
+ "tags",
+ DataTypes.MAP(DataTypes.STRING().notNull(),
DataTypes.BIGINT())),
+ DataTypes.FIELD(2, "payload", new BlobType()));
+ Options rawOptions = sharedShreddingOptions("tags", 3);
+ SharedShreddingAppendContext context =
+ createSharedShreddingAppendContext(fileFormat, rawOptions);
+ BlobFileContext blobContext =
+ BlobFileContext.create(writeType, new CoreOptions(rawOptions));
+ assertThat(blobContext).isNotNull();
+
+ AppendOnlyWriter writer =
+ createSharedShreddingAppendWriter(
+ writeType, context, SCHEMA_ID, -1L, new
FileIndexOptions(), blobContext);
+
+ writer.write(
+ sharedShreddingLogicalRow(1, map("a", 10L), new BlobData(new
byte[] {1, 2, 3})));
+ CommitIncrement increment = writer.prepareCommit(true);
+ writer.close();
+
+ assertThat(increment.newFilesIncrement().newFiles()).hasSize(2);
+ DataFileMeta mainFile =
+ increment.newFilesIncrement().newFiles().stream()
+ .filter(file -> fileFormat.equals(file.fileFormat()))
+ .findFirst()
+ .orElseThrow(AssertionError::new);
+ assertThat(readSharedShreddingFieldMeta(context, mainFile, "tags"))
+ .isEqualTo(
+ sharedShreddingMeta(
+ nameToId("a", 0),
+ fieldToColumns(0, columns(0)),
+ overflowFields(),
+ 1,
+ 1));
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @ValueSource(strings = {CoreOptions.FILE_FORMAT_PARQUET,
CoreOptions.FILE_FORMAT_ORC})
+ public void testSharedShreddingMapAllowsForceBufferSpill(String
fileFormat) throws Exception {
+ RowType writeType = sharedShreddingTagsWriteType();
+ Options rawOptions = sharedShreddingOptions("tags", 3);
+ SharedShreddingAppendContext context =
+ createSharedShreddingAppendContext(fileFormat, rawOptions);
+ AppendOnlyWriter writer = createSharedShreddingAppendWriter(writeType,
context);
+
+
Assertions.assertThatCode(writer::toBufferedWriter).doesNotThrowAnyException();
+ writer.close();
+ }
+
@Test
public void testNoBuffer() throws Exception {
AppendOnlyWriter writer = createEmptyWriter(Long.MAX_VALUE);
@@ -947,6 +1005,17 @@ public class AppendOnlyWriterTest {
long schemaId,
long maxSequenceNumber,
FileIndexOptions fileIndexOptions) {
+ return createSharedShreddingAppendWriter(
+ writeType, context, schemaId, maxSequenceNumber,
fileIndexOptions, null);
+ }
+
+ private AppendOnlyWriter createSharedShreddingAppendWriter(
+ RowType writeType,
+ SharedShreddingAppendContext context,
+ long schemaId,
+ long maxSequenceNumber,
+ FileIndexOptions fileIndexOptions,
+ @Nullable BlobFileContext blobContext) {
return new AppendOnlyWriter(
context.fileIO,
null,
@@ -975,7 +1044,7 @@ public class AppendOnlyWriterTest {
false,
context.options.dataEvolutionEnabled(),
null,
- null);
+ blobContext);
}
private DataFileMeta writeSharedShreddingFile(AppendOnlyWriter writer,
InternalRow... rows)
diff --git
a/paimon-core/src/test/java/org/apache/paimon/operation/BucketedAppendFileStoreWriteTest.java
b/paimon-core/src/test/java/org/apache/paimon/operation/BucketedAppendFileStoreWriteTest.java
index 556209b2d3..2d0e322910 100644
---
a/paimon-core/src/test/java/org/apache/paimon/operation/BucketedAppendFileStoreWriteTest.java
+++
b/paimon-core/src/test/java/org/apache/paimon/operation/BucketedAppendFileStoreWriteTest.java
@@ -24,28 +24,38 @@ import org.apache.paimon.catalog.FileSystemCatalog;
import org.apache.paimon.catalog.Identifier;
import org.apache.paimon.data.BinaryRow;
import org.apache.paimon.data.BinaryRowWriter;
+import org.apache.paimon.data.BinaryString;
+import org.apache.paimon.data.GenericMap;
import org.apache.paimon.data.GenericRow;
+import org.apache.paimon.data.InternalMap;
import org.apache.paimon.data.InternalRow;
import org.apache.paimon.disk.ExternalBuffer;
import org.apache.paimon.disk.IOManager;
+import org.apache.paimon.disk.RowBuffer;
import org.apache.paimon.fs.Path;
import org.apache.paimon.fs.local.LocalFileIO;
import org.apache.paimon.io.DataFileMeta;
import org.apache.paimon.manifest.SimpleFileEntry;
+import org.apache.paimon.reader.RecordReader;
import org.apache.paimon.schema.Schema;
import org.apache.paimon.table.FileStoreTable;
import org.apache.paimon.table.sink.CommitMessage;
import org.apache.paimon.table.sink.CommitMessageImpl;
import org.apache.paimon.table.sink.StreamTableCommit;
+import org.apache.paimon.table.source.ReadBuilder;
import org.apache.paimon.types.DataTypes;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
@@ -117,6 +127,52 @@ public class BucketedAppendFileStoreWriteTest {
Assertions.assertThat(records).isEqualTo(11);
}
+ @ParameterizedTest(name = "{0}")
+ @ValueSource(strings = {"parquet", "orc"})
+ public void testSharedShreddingForceBufferSpill(String fileFormat) throws
Exception {
+ FileStoreTable table =
createSharedShreddingForceBufferSpillTable(fileFormat);
+ BucketedAppendFileStoreWrite write =
+ (BucketedAppendFileStoreWrite) table.store().newWrite("ss");
+ write.withIOManager(IOManager.create(tempDir.toString()));
+
+ write.write(partition(0), 0, GenericRow.of(0, 0, map("a", 10L)));
+ Assertions.assertThat(writeBuffers(write)).containsExactly((RowBuffer)
null);
+
+ // Creating the second writer reaches the configured limit and
triggers force spill.
+ write.write(partition(1), 1, GenericRow.of(1, 1, map("b", 20L)));
+ Assertions.assertThat(writeBuffers(write))
+ .hasSize(2)
+ .allSatisfy(
+ buffer ->
Assertions.assertThat(buffer).isInstanceOf(ExternalBuffer.class));
+
+ write.write(partition(0), 0, GenericRow.of(0, 2, map("c", 30L)));
+ write.write(partition(1), 1, GenericRow.of(1, 3, map("d", 40L)));
+
+ List<CommitMessage> messages = write.prepareCommit(true,
Long.MAX_VALUE);
+ try (StreamTableCommit commit =
table.newStreamWriteBuilder().newCommit()) {
+ commit.commit(0, messages);
+ }
+
+ List<List<Object>> actual = new ArrayList<>();
+ ReadBuilder readBuilder = table.newReadBuilder();
+ try (RecordReader<InternalRow> reader =
+
readBuilder.newRead().createReader(readBuilder.newScan().plan())) {
+ reader.forEachRemaining(
+ row ->
+ actual.add(
+ Arrays.asList(
+ row.getInt(0),
+ row.getInt(1),
+ toJavaMap(row.getMap(2)))));
+ }
+ Assertions.assertThat(actual)
+ .containsExactlyInAnyOrder(
+ Arrays.asList(0, 0, Collections.singletonMap("a",
10L)),
+ Arrays.asList(1, 1, Collections.singletonMap("b",
20L)),
+ Arrays.asList(0, 2, Collections.singletonMap("c",
30L)),
+ Arrays.asList(1, 3, Collections.singletonMap("d",
40L)));
+ }
+
@Test
public void testWritesInBatchWithNoExtraFiles() throws Exception {
FileStoreTable table = createFileStoreTable();
@@ -176,6 +232,55 @@ public class BucketedAppendFileStoreWriteTest {
return (FileStoreTable) catalog.getTable(identifier);
}
+ private FileStoreTable createSharedShreddingForceBufferSpillTable(String
fileFormat)
+ throws Exception {
+ Catalog catalog = new FileSystemCatalog(LocalFileIO.create(), new
Path(tempDir.toString()));
+ Schema schema =
+ Schema.newBuilder()
+ .column("f0", DataTypes.INT())
+ .column("f1", DataTypes.INT())
+ .column(
+ "metrics",
+ DataTypes.MAP(DataTypes.STRING().notNull(),
DataTypes.BIGINT()))
+ .partitionKeys("f0")
+ .option(BUCKET.key(), "100")
+ .option("bucket-key", "f1")
+ .option(WRITE_ONLY.key(), "true")
+ .option("file.format", fileFormat)
+ .option(WRITE_MAX_WRITERS_TO_SPILL.key(), "1")
+ .option("fields.metrics.map.storage-layout",
"shared-shredding")
+
.option("fields.metrics.map.shared-shredding.max-columns", "2")
+ .build();
+ Identifier identifier = Identifier.create("default", "spill_" +
fileFormat);
+ catalog.createDatabase("default", true);
+ catalog.createTable(identifier, schema, false);
+ return (FileStoreTable) catalog.getTable(identifier);
+ }
+
+ private GenericMap map(String key, long value) {
+ return new
GenericMap(Collections.singletonMap(BinaryString.fromString(key), value));
+ }
+
+ private List<RowBuffer> writeBuffers(BucketedAppendFileStoreWrite write) {
+ List<RowBuffer> buffers = new ArrayList<>();
+ for (Map<Integer, AbstractFileStoreWrite.WriterContainer<InternalRow>>
bucketWriters :
+ write.writers().values()) {
+ for (AbstractFileStoreWrite.WriterContainer<InternalRow>
writerContainer :
+ bucketWriters.values()) {
+ buffers.add(((AppendOnlyWriter)
writerContainer.writer).getWriteBuffer());
+ }
+ }
+ return buffers;
+ }
+
+ private Map<String, Long> toJavaMap(InternalMap map) {
+ Map<String, Long> result = new LinkedHashMap<>();
+ for (int i = 0; i < map.size(); i++) {
+ result.put(map.keyArray().getString(i).toString(),
map.valueArray().getLong(i));
+ }
+ return result;
+ }
+
@Test
public void testIgnorePreviousFilesChecksPartitionBucketNumber() throws
Exception {
FileStoreTable table = createFileStoreTable().copy(bucketOptions(2,
false, false));
diff --git
a/paimon-core/src/test/java/org/apache/paimon/schema/SchemaValidationTest.java
b/paimon-core/src/test/java/org/apache/paimon/schema/SchemaValidationTest.java
index 12608d1720..ec07c17c4f 100644
---
a/paimon-core/src/test/java/org/apache/paimon/schema/SchemaValidationTest.java
+++
b/paimon-core/src/test/java/org/apache/paimon/schema/SchemaValidationTest.java
@@ -19,6 +19,7 @@
package org.apache.paimon.schema;
import org.apache.paimon.CoreOptions;
+import org.apache.paimon.table.BucketMode;
import org.apache.paimon.types.DataField;
import org.apache.paimon.types.DataType;
import org.apache.paimon.types.DataTypes;
@@ -526,15 +527,11 @@ class SchemaValidationTest {
assertMapSharedShreddingValidationFailed(
mapValueFields(DataTypes.BLOB()),
mapSharedShreddingOptions(),
- "MAP shared-shredding currently cannot be used with BLOB
fields.");
+ "MAP shared-shredding currently cannot contain BLOB fields.");
assertMapSharedShreddingValidationFailed(
nestedMapValueFields(DataTypes.BLOB()),
mapSharedShreddingOptions(),
- "MAP shared-shredding currently cannot be used with BLOB
fields.");
- assertMapSharedShreddingValidationFailed(
- topLevelPayloadFields(DataTypes.BLOB()),
- mapSharedShreddingOptions(),
- "MAP shared-shredding currently cannot be used with BLOB
fields.");
+ "MAP shared-shredding currently cannot contain BLOB fields.");
DataType multisetType = DataTypes.MULTISET(DataTypes.INT());
assertMapSharedShreddingValidationFailed(
@@ -554,11 +551,7 @@ class SchemaValidationTest {
assertMapSharedShreddingValidationFailed(
mapValueFields(vectorType),
mapSharedShreddingOptions(),
- "MAP shared-shredding currently cannot be used with VECTOR
fields.");
- assertMapSharedShreddingValidationFailed(
- topLevelPayloadFields(vectorType),
- mapSharedShreddingOptions(),
- "MAP shared-shredding currently cannot be used with VECTOR
fields.");
+ "MAP shared-shredding currently cannot contain VECTOR
fields.");
}
@Test
@@ -595,12 +588,11 @@ class SchemaValidationTest {
vectorFormatOptions.put(DATA_EVOLUTION_ENABLED.key(), "true");
vectorFormatOptions.put(CoreOptions.ROW_TRACKING_ENABLED.key(),
"true");
vectorFormatOptions.put(CoreOptions.VECTOR_FILE_FORMAT.key(), "json");
- assertThatThrownBy(
+ assertThatCode(
() ->
validateTableSchema(
mapSharedShreddingSchema(vectorFormatOptions, emptyList())))
- .hasMessageContaining(
- "MAP shared-shredding only supports parquet/orc file
formats, but vector.file.format is json.");
+ .doesNotThrowAnyException();
}
@Test
@@ -651,23 +643,40 @@ class SchemaValidationTest {
public void testMapSharedShreddingTableModeValidation() {
Map<String, String> primaryKeyOptions = mapSharedShreddingOptions();
primaryKeyOptions.put(BUCKET.key(), "-1");
+ assertThatNoException()
+ .isThrownBy(
+ () ->
+ validateTableSchema(
+ mapSharedShreddingSchema(
+ primaryKeyOptions,
singletonList("id"))));
+
+ primaryKeyOptions.put(CoreOptions.WRITE_ONLY.key(), "true");
+ assertThatNoException()
+ .isThrownBy(
+ () ->
+ validateTableSchema(
+ mapSharedShreddingSchema(
+ primaryKeyOptions,
singletonList("id"))));
+
+ Map<String, String> postponeBucketOptions =
mapSharedShreddingOptions();
+ postponeBucketOptions.put(BUCKET.key(),
String.valueOf(BucketMode.POSTPONE_BUCKET));
+ postponeBucketOptions.put(CoreOptions.WRITE_ONLY.key(), "true");
assertThatThrownBy(
() ->
validateTableSchema(
mapSharedShreddingSchema(
- primaryKeyOptions,
singletonList("id"))))
+ postponeBucketOptions,
singletonList("id"))))
.hasMessageContaining(
- "MAP shared-shredding currently only supports
append-only tables.");
+ "MAP shared-shredding currently does not support
postpone bucket mode.");
Map<String, String> fixedBucketOptions = mapSharedShreddingOptions();
fixedBucketOptions.put(BUCKET.key(), "1");
fixedBucketOptions.put(CoreOptions.BUCKET_KEY.key(), "id");
- assertThatThrownBy(
+ assertThatNoException()
+ .isThrownBy(
() ->
validateTableSchema(
-
mapSharedShreddingSchema(fixedBucketOptions, emptyList())))
- .hasMessageContaining(
- "MAP shared-shredding currently requires bucket = -1
or write-only = true because rewrite/compaction is not supported.");
+
mapSharedShreddingSchema(fixedBucketOptions, emptyList())));
Map<String, String> writeOnlyOptions = mapSharedShreddingOptions();
writeOnlyOptions.put(BUCKET.key(), "1");
diff --git
a/paimon-core/src/test/java/org/apache/paimon/table/MapSharedShreddingTableTest.java
b/paimon-core/src/test/java/org/apache/paimon/table/MapSharedShreddingTableTest.java
index 66e166c7da..de5b74572b 100644
---
a/paimon-core/src/test/java/org/apache/paimon/table/MapSharedShreddingTableTest.java
+++
b/paimon-core/src/test/java/org/apache/paimon/table/MapSharedShreddingTableTest.java
@@ -21,6 +21,8 @@ package org.apache.paimon.table;
import org.apache.paimon.CoreOptions;
import org.apache.paimon.data.BinaryRow;
import org.apache.paimon.data.BinaryString;
+import org.apache.paimon.data.BinaryVector;
+import org.apache.paimon.data.BlobData;
import org.apache.paimon.data.Decimal;
import org.apache.paimon.data.GenericArray;
import org.apache.paimon.data.GenericMap;
@@ -35,8 +37,11 @@ import org.apache.paimon.format.FileFormat;
import org.apache.paimon.format.FileFormatDiscover;
import org.apache.paimon.format.FormatReaderContext;
import org.apache.paimon.format.SupportsFieldMetadata;
+import org.apache.paimon.index.IndexFileMeta;
import org.apache.paimon.io.DataFileMeta;
import org.apache.paimon.io.DataFilePathFactory;
+import org.apache.paimon.manifest.FileSource;
+import org.apache.paimon.reader.RecordReader;
import org.apache.paimon.schema.Schema;
import org.apache.paimon.schema.SchemaChange;
import org.apache.paimon.table.sink.BatchTableCommit;
@@ -46,14 +51,19 @@ import org.apache.paimon.table.sink.CommitMessage;
import org.apache.paimon.table.sink.CommitMessageImpl;
import org.apache.paimon.table.source.DataSplit;
import org.apache.paimon.table.source.ReadBuilder;
+import org.apache.paimon.table.source.ScanMode;
+import org.apache.paimon.table.source.Split;
import org.apache.paimon.types.DataTypes;
+import org.apache.paimon.types.RowKind;
import org.apache.paimon.types.RowType;
import org.apache.paimon.utils.Range;
import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
import java.math.BigDecimal;
+import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -61,7 +71,9 @@ import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.stream.Stream;
+import static
org.apache.paimon.deletionvectors.DeletionVectorsIndexFile.DELETION_VECTORS_INDEX;
import static org.assertj.core.api.Assertions.assertThat;
/** Table-level tests for MAP shared-shredding. */
@@ -134,6 +146,37 @@ public class MapSharedShreddingTableTest extends
TableTestBase {
.containsEntry(3, javaMapOf("x", 41L));
}
+ @ParameterizedTest
+ @ValueSource(strings = {"orc", "parquet"})
+ public void testAppendOnlyCompaction(String format) throws Exception {
+ Table table = createCompactingAppendOnlyTable(format);
+
+ write(table, GenericRow.of(1, mapOf("a", 11L, "b", 12L)));
+ write(table, GenericRow.of(2, mapOf("c", 21L)));
+ write(table, GenericRow.of(3, mapOf("d", 31L, "e", 32L, "f", 33L, "g",
34L)));
+ compact(table, BinaryRow.EMPTY_ROW, 0);
+
+ FileStoreTable fileStoreTable = (FileStoreTable) table;
+ List<DataFileWithSplit> files = currentDataFiles(fileStoreTable);
+ assertThat(files).hasSize(1);
+
assertThat(files.get(0).dataFile.fileSource()).hasValue(FileSource.COMPACT);
+ MapSharedShreddingFieldMeta compactedMeta =
+ readSharedShreddingFieldMeta(fileStoreTable, files.get(0),
"metrics");
+ assertThat(compactedMeta.numColumns()).isEqualTo(2);
+ assertThat(compactedMeta.maxRowWidth()).isEqualTo(4);
+ assertThat(compactedMeta.nameToId()).containsOnlyKeys("a", "b", "c",
"d", "e", "f", "g");
+
+ Map<Integer, Map<String, Long>> actual = new LinkedHashMap<>();
+ for (InternalRow row : read(table)) {
+ actual.put(row.getInt(0), toJavaMap(row.getMap(1)));
+ }
+ assertThat(actual)
+ .containsOnlyKeys(1, 2, 3)
+ .containsEntry(1, javaMapOf("a", 11L, "b", 12L))
+ .containsEntry(2, javaMapOf("c", 21L))
+ .containsEntry(3, javaMapOf("d", 31L, "e", 32L, "f", 33L, "g",
34L));
+ }
+
@ParameterizedTest
@ValueSource(strings = {"orc", "parquet"})
public void testAppendOnlyTableReadWriteWithComplexValue(String format)
throws Exception {
@@ -317,6 +360,311 @@ public class MapSharedShreddingTableTest extends
TableTestBase {
.containsEntry(3, javaMapOf("f", 31L));
}
+ @ParameterizedTest
+ @CsvSource({"orc,false", "orc,true", "parquet,false", "parquet,true"})
+ public void testPrimaryKeyWriteOnlyReadWrite(String format, boolean
thinMode) throws Exception {
+ Table table = createPrimaryKeyTable(format, thinMode, 2);
+
+ write(
+ table,
+ GenericRow.of(1, mapOf("a", 11L, "b", 12L, "c", 13L)),
+ GenericRow.of(2, null),
+ GenericRow.of(3, mapOf()));
+ write(
+ table,
+ GenericRow.of(1, mapOf("x", null, "y", 42L, "z", null)),
+ GenericRow.of(4, mapOf("d", 44L)));
+
+ Map<Integer, Map<String, Long>> actual = new LinkedHashMap<>();
+ for (InternalRow row : read(table)) {
+ actual.put(row.getInt(0), row.isNullAt(1) ? null :
toJavaMap(row.getMap(1)));
+ }
+
+ assertThat(actual)
+ .containsOnlyKeys(1, 2, 3, 4)
+ .containsEntry(1, javaMapOf("x", null, "y", 42L, "z", null))
+ .containsEntry(2, null)
+ .containsEntry(3, javaMapOf())
+ .containsEntry(4, javaMapOf("d", 44L));
+ }
+
+ @ParameterizedTest
+ @CsvSource({"orc,parquet", "parquet,orc"})
+ public void testPrimaryKeyInputChangelog(String dataFileFormat, String
changelogFileFormat)
+ throws Exception {
+ Table table = createPrimaryKeyInputChangelogTable(dataFileFormat,
changelogFileFormat);
+
+ List<CommitMessage> messages =
+ writeAndCommit(
+ table,
+ GenericRow.ofKind(RowKind.INSERT, 1, mapOf("a", 11L,
"b", 12L)),
+ GenericRow.ofKind(RowKind.INSERT, 2, mapOf("c", 21L)),
+ GenericRow.ofKind(RowKind.UPDATE_BEFORE, 1, mapOf("a",
11L, "b", 12L)),
+ GenericRow.ofKind(
+ RowKind.UPDATE_AFTER,
+ 1,
+ mapOf("x", 31L, "y", 32L, "overflow", 33L)),
+ GenericRow.ofKind(RowKind.DELETE, 2, mapOf("c", 21L)));
+
+ List<DataFileWithSplit> changelogFiles = new ArrayList<>();
+ for (CommitMessage message : messages) {
+ CommitMessageImpl commitMessage = (CommitMessageImpl) message;
+ for (DataFileMeta file :
commitMessage.newFilesIncrement().changelogFiles()) {
+ changelogFiles.add(
+ new DataFileWithSplit(
+ commitMessage.partition(),
commitMessage.bucket(), file));
+ }
+ }
+ assertThat(changelogFiles).hasSize(1);
+ DataFileWithSplit changelogFile = changelogFiles.get(0);
+
assertThat(changelogFile.dataFile.fileFormat()).isEqualTo(changelogFileFormat);
+ assertThat(changelogFile.dataFile.fileName())
+ .startsWith(((FileStoreTable)
table).coreOptions().changelogFilePrefix());
+
+ MapSharedShreddingFieldMeta changelogMeta =
+ readSharedShreddingFieldMeta((FileStoreTable) table,
changelogFile, "metrics");
+ assertThat(changelogMeta.numColumns()).isEqualTo(2);
+ assertThat(changelogMeta.maxRowWidth()).isEqualTo(3);
+ assertThat(changelogMeta.nameToId()).containsOnlyKeys("a", "b", "c",
"x", "y", "overflow");
+
+ FileStoreTable fileStoreTable = (FileStoreTable) table;
+ List<Split> changelogSplits =
+
fileStoreTable.newSnapshotReader().withMode(ScanMode.CHANGELOG).read().splits();
+ Map<String, Map<String, Long>> actualChangelog = new LinkedHashMap<>();
+ try (RecordReader<InternalRow> reader =
+ fileStoreTable.newRead().createReader(changelogSplits)) {
+ reader.forEachRemaining(
+ row ->
+ actualChangelog.put(
+ row.getRowKind().shortString() + ":" +
row.getInt(0),
+ toJavaMap(row.getMap(1))));
+ }
+ assertThat(actualChangelog)
+ .containsEntry("+I:1", javaMapOf("a", 11L, "b", 12L))
+ .containsEntry("+I:2", javaMapOf("c", 21L))
+ .containsEntry("-U:1", javaMapOf("a", 11L, "b", 12L))
+ .containsEntry("+U:1", javaMapOf("x", 31L, "y", 32L,
"overflow", 33L))
+ .containsEntry("-D:2", javaMapOf("c", 21L));
+
+ assertThat(readMapsById(table.newReadBuilder()))
+ .containsOnlyKeys(1)
+ .containsEntry(1, javaMapOf("x", 31L, "y", 32L, "overflow",
33L));
+ }
+
+ @ParameterizedTest
+ @CsvSource({"orc,false", "orc,true", "parquet,false", "parquet,true"})
+ public void testPrimaryKeyMergeMapAggregation(String format, boolean
thinMode)
+ throws Exception {
+ Table table = createPrimaryKeyAggregationTable(format, thinMode);
+
+ write(table, GenericRow.of(1, mapOf("a", 11L, "b", 12L)),
GenericRow.of(2, null));
+ write(
+ table,
+ GenericRow.of(1, mapOf("b", 22L, "c", 23L)),
+ GenericRow.of(2, mapOf("x", 31L)));
+
+ assertThat(readMapsById(table.newReadBuilder()))
+ .containsOnlyKeys(1, 2)
+ .containsEntry(1, javaMapOf("a", 11L, "b", 22L, "c", 23L))
+ .containsEntry(2, javaMapOf("x", 31L));
+
+ compact(table, BinaryRow.EMPTY_ROW, 0);
+
+ FileStoreTable fileStoreTable = (FileStoreTable) table;
+ List<DataFileWithSplit> files = currentDataFiles(fileStoreTable);
+ assertThat(files).hasSize(1);
+
assertThat(files.get(0).dataFile.fileSource()).hasValue(FileSource.COMPACT);
+ assertThat(readMapsById(table.newReadBuilder()))
+ .containsOnlyKeys(1, 2)
+ .containsEntry(1, javaMapOf("a", 11L, "b", 22L, "c", 23L))
+ .containsEntry(2, javaMapOf("x", 31L));
+ }
+
+ @ParameterizedTest
+ @CsvSource({"orc,false", "orc,true", "parquet,false", "parquet,true"})
+ public void testPrimaryKeyExternalSpillReadWrite(String format, boolean
thinMode)
+ throws Exception {
+ Table table = createPrimaryKeyTable(format, thinMode, 2);
+ Map<String, String> spillOptions = new LinkedHashMap<>();
+ spillOptions.put(CoreOptions.PAGE_SIZE.key(), "4 kb");
+ spillOptions.put(CoreOptions.WRITE_BUFFER_SIZE.key(), "12 kb");
+ spillOptions.put(CoreOptions.WRITE_BUFFER_SPILLABLE.key(), "true");
+ table = table.copy(spillOptions);
+
+ BatchWriteBuilder writeBuilder = table.newBatchWriteBuilder();
+ try (BatchTableWrite write = writeBuilder.newWrite();
+ BatchTableCommit commit = writeBuilder.newCommit()) {
+ write.withIOManager(ioManager);
+ for (int i = 0; i < 1000; i++) {
+ write.write(
+ GenericRow.of(
+ i % 100,
+ mapOf("a", (long) i, "b", (long) i + 1,
"overflow", (long) -i)));
+ }
+ assertThat(numberOfSpillFiles()).isGreaterThan(0);
+ commit.commit(write.prepareCommit());
+ }
+
+ Map<Integer, Map<String, Long>> actual = new LinkedHashMap<>();
+ for (InternalRow row : read(table)) {
+ actual.put(row.getInt(0), toJavaMap(row.getMap(1)));
+ }
+
+ assertThat(actual).hasSize(100);
+ for (int id = 0; id < 100; id++) {
+ long lastValue = 900L + id;
+ assertThat(actual)
+ .containsEntry(
+ id,
+ javaMapOf("a", lastValue, "b", lastValue + 1,
"overflow", -lastValue));
+ }
+ }
+
+ @ParameterizedTest
+ @CsvSource({"orc,false", "orc,true", "parquet,false", "parquet,true"})
+ public void testPrimaryKeyCompaction(String format, boolean thinMode)
throws Exception {
+ Table table = createPrimaryKeyTable(format, thinMode, 2, false);
+
+ write(
+ table,
+ GenericRow.of(1, mapOf("old", 11L)),
+ GenericRow.of(2, mapOf("a", 21L, "b", 22L, "overflow", 23L)));
+ write(
+ table,
+ GenericRow.of(1, mapOf("x", 31L, "y", 32L, "overflow", 33L)),
+ GenericRow.of(3, null));
+ compact(table, BinaryRow.EMPTY_ROW, 0);
+
+ FileStoreTable fileStoreTable = (FileStoreTable) table;
+ List<DataFileWithSplit> files = currentDataFiles(fileStoreTable);
+ assertThat(files).hasSize(1);
+
assertThat(files.get(0).dataFile.fileSource()).hasValue(FileSource.COMPACT);
+ MapSharedShreddingFieldMeta compactedMeta =
+ readSharedShreddingFieldMeta(fileStoreTable, files.get(0),
"metrics");
+ assertThat(compactedMeta.numColumns()).isEqualTo(2);
+ assertThat(compactedMeta.maxRowWidth()).isEqualTo(3);
+ assertThat(compactedMeta.nameToId()).containsOnlyKeys("a", "b",
"overflow", "x", "y");
+
+ Map<Integer, Map<String, Long>> actual = new LinkedHashMap<>();
+ for (InternalRow row : read(table)) {
+ actual.put(row.getInt(0), row.isNullAt(1) ? null :
toJavaMap(row.getMap(1)));
+ }
+ assertThat(actual)
+ .containsOnlyKeys(1, 2, 3)
+ .containsEntry(1, javaMapOf("x", 31L, "y", 32L, "overflow",
33L))
+ .containsEntry(2, javaMapOf("a", 21L, "b", 22L, "overflow",
23L))
+ .containsEntry(3, null);
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"orc", "parquet"})
+ public void testPrimaryKeyDeletionVectorCompaction(String format) throws
Exception {
+ Table table = createPrimaryKeyTable(format, false, 2, false, true);
+ String padding = String.join("", Collections.nCopies(2048, "X"));
+
+ write(
+ table,
+ ioManager,
+ GenericRow.of(1, mapOf("a", 10L, "b", 20L),
BinaryString.fromString(padding)),
+ GenericRow.of(2, mapOf("c", 30L),
BinaryString.fromString(padding)),
+ GenericRow.of(3, mapOf("d", 40L),
BinaryString.fromString(padding)),
+ GenericRow.of(4, null, BinaryString.fromString(padding)),
+ GenericRow.of(6, mapOf("j", 60L, "k", 70L),
BinaryString.fromString(padding)),
+ GenericRow.of(
+ 7, mapOf("l", 80L, "m", 90L, "n", 100L),
BinaryString.fromString(padding)),
+ GenericRow.of(8, mapOf("o", 110L),
BinaryString.fromString(padding)));
+ List<CommitMessage> upgradeMessages = compactAndCommit(table,
BinaryRow.EMPTY_ROW, 0, true);
+ assertThat(upgradeMessages)
+ .allSatisfy(
+ message ->
+ assertThat(
+ ((CommitMessageImpl) message)
+ .compactIncrement()
+ .newIndexFiles())
+ .isEmpty());
+
+ FileStoreTable fileStoreTable = (FileStoreTable) table;
+ List<DataFileWithSplit> upgradedFiles =
currentDataFiles(fileStoreTable);
+ assertThat(upgradedFiles).hasSize(1);
+ assertThat(upgradedFiles.get(0).dataFile.fileSize())
+
.isGreaterThan(fileStoreTable.coreOptions().compactionFileSize(true));
+
+ List<CommitMessage> compactMessages = new ArrayList<>();
+ compactMessages.addAll(
+ writeAndCommit(
+ table,
+ GenericRow.of(
+ 1, mapOf("a", 100L, "e", 500L),
BinaryString.fromString("u1")),
+ GenericRow.of(5, mapOf("h", 80L),
BinaryString.fromString("u5"))));
+ compactMessages.addAll(
+ writeAndCommit(
+ table,
+ GenericRow.of(
+ 2,
+ mapOf("c", 300L, "f", 600L, "g", 700L),
+ BinaryString.fromString("u2")),
+ GenericRow.of(
+ 5,
+ mapOf("h", 800L, "i", 900L),
+ BinaryString.fromString("u5-new"))));
+ compactMessages.addAll(compactAndCommit(table, BinaryRow.EMPTY_ROW, 0,
false));
+ List<IndexFileMeta> deletionVectorFiles = new ArrayList<>();
+ for (CommitMessage message : compactMessages) {
+ deletionVectorFiles.addAll(
+ ((CommitMessageImpl)
message).compactIncrement().newIndexFiles());
+ }
+ assertThat(deletionVectorFiles)
+ .isNotEmpty()
+ .allSatisfy(file ->
assertThat(file.indexType()).isEqualTo(DELETION_VECTORS_INDEX));
+ assertThat(deletionVectorFiles)
+ .anySatisfy(file -> assertThat(file.dvRanges()).isNotEmpty());
+
+ Map<Integer, Map<String, Long>> actual =
readMapsById(table.newReadBuilder());
+ assertThat(actual)
+ .containsOnlyKeys(1, 2, 3, 4, 5, 6, 7, 8)
+ .containsEntry(1, javaMapOf("a", 100L, "e", 500L))
+ .containsEntry(2, javaMapOf("c", 300L, "f", 600L, "g", 700L))
+ .containsEntry(3, javaMapOf("d", 40L))
+ .containsEntry(4, null)
+ .containsEntry(5, javaMapOf("h", 800L, "i", 900L))
+ .containsEntry(6, javaMapOf("j", 60L, "k", 70L))
+ .containsEntry(7, javaMapOf("l", 80L, "m", 90L, "n", 100L))
+ .containsEntry(8, javaMapOf("o", 110L));
+ }
+
+ @ParameterizedTest
+ @CsvSource({"orc,false", "orc,true", "parquet,false", "parquet,true"})
+ public void testPrimaryKeyInfersColumnCountPerFile(String format, boolean
thinMode)
+ throws Exception {
+ Table table = createPrimaryKeyTable(format, thinMode, 8);
+
+ write(table, GenericRow.of(1, mapOf("a", 11L, "b", 12L)));
+ write(table, GenericRow.of(2, mapOf("c", 22L)));
+
+ FileStoreTable fileStoreTable = (FileStoreTable) table;
+ List<DataFileWithSplit> files = currentDataFiles(fileStoreTable);
+ files.sort(Comparator.comparingLong(file ->
file.dataFile.minSequenceNumber()));
+ assertThat(files).hasSize(2);
+
+ MapSharedShreddingFieldMeta firstFileMeta =
+ readSharedShreddingFieldMeta(fileStoreTable, files.get(0),
"metrics");
+ assertThat(firstFileMeta.numColumns()).isEqualTo(2);
+ assertThat(firstFileMeta.maxRowWidth()).isEqualTo(2);
+
+ MapSharedShreddingFieldMeta secondFileMeta =
+ readSharedShreddingFieldMeta(fileStoreTable, files.get(1),
"metrics");
+ assertThat(secondFileMeta.numColumns()).isEqualTo(1);
+ assertThat(secondFileMeta.maxRowWidth()).isEqualTo(1);
+
+ Map<Integer, Map<String, Long>> actual = new LinkedHashMap<>();
+ for (InternalRow row : read(table)) {
+ actual.put(row.getInt(0), toJavaMap(row.getMap(1)));
+ }
+ assertThat(actual)
+ .containsEntry(1, javaMapOf("a", 11L, "b", 12L))
+ .containsEntry(2, javaMapOf("c", 22L));
+ }
+
@ParameterizedTest
@ValueSource(strings = {"orc", "parquet"})
public void testSwitchMapLayoutAndInferColumns(String format) throws
Exception {
@@ -462,6 +810,87 @@ public class MapSharedShreddingTableTest extends
TableTestBase {
102, Arrays.asList(javaMapOf("new-b", 222L),
javaMapOf("label-b", 102L)));
}
+ @ParameterizedTest
+ @ValueSource(strings = {"orc", "parquet"})
+ public void testSharedShreddingWithBlobAndVector(String format) throws
Exception {
+ Table table = createBlobAndVectorTable(format);
+
+ write(
+ table,
+ GenericRow.of(
+ 1,
+ mapOf("a", 11L, "b", 12L, "overflow", 13L),
+ new BlobData(new byte[] {1, 2, 3}),
+ BinaryVector.fromPrimitiveArray(new float[] {1.0f,
2.0f, 3.0f})),
+ GenericRow.of(2, null, null, null),
+ GenericRow.of(
+ 3,
+ mapOf(),
+ new BlobData(new byte[] {4, 5}),
+ BinaryVector.fromPrimitiveArray(new float[] {4.0f,
5.0f, 6.0f})));
+
+ Map<Integer, Map<String, Long>> initialMaps = new LinkedHashMap<>();
+ for (InternalRow row : read(table, new int[] {0, 1})) {
+ initialMaps.put(row.getInt(0), row.isNullAt(1) ? null :
toJavaMap(row.getMap(1)));
+ }
+ assertThat(initialMaps)
+ .containsEntry(1, javaMapOf("a", 11L, "b", 12L, "overflow",
13L))
+ .containsEntry(2, null)
+ .containsEntry(3, javaMapOf());
+
+ RowType rowType = table.rowType();
+ writeWithWriteType(
+ table,
+ rowType.project(Collections.singletonList("metrics")),
+ 0L,
+ GenericRow.of(mapOf("updated", 101L)),
+ GenericRow.of(mapOf("updated", 102L)),
+ GenericRow.of(mapOf("updated", 103L)));
+
+ Map<Integer, Map<String, Long>> maps = new LinkedHashMap<>();
+ Map<Integer, byte[]> blobs = new LinkedHashMap<>();
+ Map<Integer, float[]> vectors = new LinkedHashMap<>();
+ for (InternalRow row : read(table)) {
+ int id = row.getInt(0);
+ maps.put(id, toJavaMap(row.getMap(1)));
+ blobs.put(id, row.isNullAt(2) ? null : row.getBlob(2).toData());
+ vectors.put(id, row.isNullAt(3) ? null :
row.getVector(3).toFloatArray());
+ }
+
+ assertThat(maps)
+ .containsEntry(1, javaMapOf("updated", 101L))
+ .containsEntry(2, javaMapOf("updated", 102L))
+ .containsEntry(3, javaMapOf("updated", 103L));
+ assertThat(blobs.get(1)).containsExactly(1, 2, 3);
+ assertThat(blobs.get(2)).isNull();
+ assertThat(blobs.get(3)).containsExactly(4, 5);
+ assertThat(vectors.get(1)).containsExactly(1.0f, 2.0f, 3.0f);
+ assertThat(vectors.get(2)).isNull();
+ assertThat(vectors.get(3)).containsExactly(4.0f, 5.0f, 6.0f);
+
+ Map<Integer, Map<String, Long>> projectedMaps = new LinkedHashMap<>();
+ for (InternalRow row : read(table, new int[] {0, 1})) {
+ projectedMaps.put(row.getInt(0), toJavaMap(row.getMap(1)));
+ }
+ assertThat(projectedMaps).isEqualTo(maps);
+
+ for (InternalRow row : read(table, new int[] {0, 2, 3})) {
+ int id = row.getInt(0);
+ byte[] expectedBlob = blobs.get(id);
+ if (expectedBlob == null) {
+ assertThat(row.isNullAt(1)).isTrue();
+ } else {
+
assertThat(row.getBlob(1).toData()).containsExactly(expectedBlob);
+ }
+ float[] expectedVector = vectors.get(id);
+ if (expectedVector == null) {
+ assertThat(row.isNullAt(2)).isTrue();
+ } else {
+
assertThat(row.getVector(2).toFloatArray()).containsExactly(expectedVector);
+ }
+ }
+ }
+
private Table createTable(String format, String... sharedShreddingFields)
throws Exception {
return createTable(format, 2, sharedShreddingFields);
}
@@ -515,6 +944,179 @@ public class MapSharedShreddingTableTest extends
TableTestBase {
return catalog.getTable(identifier(format));
}
+ private Table createBlobAndVectorTable(String format) throws Exception {
+ catalog.createTable(
+ identifier(format),
+ Schema.newBuilder()
+ .column("id", DataTypes.INT())
+ .column(
+ "metrics",
+ DataTypes.MAP(DataTypes.STRING().notNull(),
DataTypes.BIGINT()))
+ .column("payload", DataTypes.BLOB())
+ .column("embedding", DataTypes.VECTOR(3,
DataTypes.FLOAT()))
+ .option("bucket", "-1")
+ .option("file.format", format)
+ .option(CoreOptions.FILE_COMPRESSION.key(), "none")
+ .option(CoreOptions.WRITE_ONLY.key(), "true")
+ .option(CoreOptions.ROW_TRACKING_ENABLED.key(), "true")
+ .option(CoreOptions.DATA_EVOLUTION_ENABLED.key(),
"true")
+ .option(CoreOptions.VECTOR_FILE_FORMAT.key(), "json")
+ .option("fields.metrics.map.storage-layout",
"shared-shredding")
+
.option("fields.metrics.map.shared-shredding.max-columns", "2")
+ .build(),
+ true);
+ return catalog.getTable(identifier(format));
+ }
+
+ private Table createCompactingAppendOnlyTable(String format) throws
Exception {
+ catalog.createTable(
+ identifier(format),
+ Schema.newBuilder()
+ .column("id", DataTypes.INT())
+ .column(
+ "metrics",
+ DataTypes.MAP(DataTypes.STRING().notNull(),
DataTypes.BIGINT()))
+ .option("bucket", "1")
+ .option("bucket-key", "id")
+ .option("file.format", format)
+ .option(CoreOptions.COMPACTION_MIN_FILE_NUM.key(),
"99")
+ .option("fields.metrics.map.storage-layout",
"shared-shredding")
+
.option("fields.metrics.map.shared-shredding.max-columns", "64")
+ .build(),
+ true);
+ return catalog.getTable(identifier(format));
+ }
+
+ private Table createPrimaryKeyTable(String format, boolean thinMode, int
maxColumns)
+ throws Exception {
+ return createPrimaryKeyTable(format, thinMode, maxColumns, true);
+ }
+
+ private Table createPrimaryKeyTable(
+ String format, boolean thinMode, int maxColumns, boolean
writeOnly) throws Exception {
+ return createPrimaryKeyTable(format, thinMode, maxColumns, writeOnly,
false);
+ }
+
+ private Table createPrimaryKeyTable(
+ String format,
+ boolean thinMode,
+ int maxColumns,
+ boolean writeOnly,
+ boolean deletionVectors)
+ throws Exception {
+ Schema.Builder builder =
+ Schema.newBuilder()
+ .column("id", DataTypes.INT())
+ .column(
+ "metrics",
+ DataTypes.MAP(DataTypes.STRING().notNull(),
DataTypes.BIGINT()));
+ if (deletionVectors) {
+ builder.column("padding", DataTypes.STRING())
+ .option(CoreOptions.TARGET_FILE_SIZE.key(), "1 kb")
+ .option(CoreOptions.FILE_COMPRESSION.key(), "none");
+ }
+ builder.primaryKey("id")
+ .option("bucket", "1")
+ .option("bucket-key", "id")
+ .option("file.format", format)
+ .option(CoreOptions.WRITE_ONLY.key(),
String.valueOf(writeOnly))
+ .option(CoreOptions.DELETION_VECTORS_ENABLED.key(),
String.valueOf(deletionVectors))
+ .option(CoreOptions.DATA_FILE_THIN_MODE.key(),
String.valueOf(thinMode))
+ .option("fields.metrics.map.storage-layout",
"shared-shredding")
+ .option(
+ "fields.metrics.map.shared-shredding.max-columns",
+ String.valueOf(maxColumns));
+ catalog.createTable(identifier(format), builder.build(), true);
+ return catalog.getTable(identifier(format));
+ }
+
+ private Table createPrimaryKeyInputChangelogTable(
+ String dataFileFormat, String changelogFileFormat) throws
Exception {
+ catalog.createTable(
+ identifier(dataFileFormat),
+ Schema.newBuilder()
+ .column("id", DataTypes.INT())
+ .column(
+ "metrics",
+ DataTypes.MAP(DataTypes.STRING().notNull(),
DataTypes.BIGINT()))
+ .primaryKey("id")
+ .option("bucket", "1")
+ .option("bucket-key", "id")
+ .option("file.format", dataFileFormat)
+ .option(CoreOptions.CHANGELOG_PRODUCER.key(), "input")
+ .option(CoreOptions.CHANGELOG_FILE_FORMAT.key(),
changelogFileFormat)
+ .option(CoreOptions.FILE_COMPRESSION.key(), "zstd")
+ .option(CoreOptions.CHANGELOG_FILE_COMPRESSION.key(),
"zstd")
+ .option("fields.metrics.map.storage-layout",
"shared-shredding")
+
.option("fields.metrics.map.shared-shredding.max-columns", "2")
+ .build(),
+ true);
+ return catalog.getTable(identifier(dataFileFormat));
+ }
+
+ private Table createPrimaryKeyAggregationTable(String format, boolean
thinMode)
+ throws Exception {
+ catalog.createTable(
+ identifier(format),
+ Schema.newBuilder()
+ .column("id", DataTypes.INT())
+ .column(
+ "metrics",
+ DataTypes.MAP(DataTypes.STRING().notNull(),
DataTypes.BIGINT()))
+ .primaryKey("id")
+ .option("bucket", "1")
+ .option("bucket-key", "id")
+ .option("file.format", format)
+ .option(CoreOptions.DATA_FILE_THIN_MODE.key(),
String.valueOf(thinMode))
+ .option(CoreOptions.MERGE_ENGINE.key(), "aggregation")
+ .option("fields.metrics.aggregate-function",
"merge_map")
+ .option("fields.metrics.map.storage-layout",
"shared-shredding")
+
.option("fields.metrics.map.shared-shredding.max-columns", "2")
+ .build(),
+ true);
+ return catalog.getTable(identifier(format));
+ }
+
+ private Map<Integer, Map<String, Long>> readMapsById(ReadBuilder
readBuilder) throws Exception {
+ Map<Integer, Map<String, Long>> result = new LinkedHashMap<>();
+ try (RecordReader<InternalRow> reader =
+
readBuilder.newRead().createReader(readBuilder.newScan().plan())) {
+ reader.forEachRemaining(
+ row ->
+ result.put(
+ row.getInt(0),
+ row.isNullAt(1) ? null :
toJavaMap(row.getMap(1))));
+ }
+ return result;
+ }
+
+ private List<CommitMessage> compactAndCommit(
+ Table table, BinaryRow partition, int bucket, boolean
fullCompaction) throws Exception {
+ BatchWriteBuilder writeBuilder = table.newBatchWriteBuilder();
+ try (BatchTableWrite write = writeBuilder.newWrite();
+ BatchTableCommit commit = writeBuilder.newCommit()) {
+ write.withIOManager(ioManager);
+ write.compact(partition, bucket, fullCompaction);
+ List<CommitMessage> messages = write.prepareCommit();
+ commit.commit(messages);
+ return messages;
+ }
+ }
+
+ private List<CommitMessage> writeAndCommit(Table table, InternalRow...
rows) throws Exception {
+ BatchWriteBuilder writeBuilder = table.newBatchWriteBuilder();
+ try (BatchTableWrite write = writeBuilder.newWrite();
+ BatchTableCommit commit = writeBuilder.newCommit()) {
+ write.withIOManager(ioManager);
+ for (InternalRow row : rows) {
+ write.write(row);
+ }
+ List<CommitMessage> messages = write.prepareCommit();
+ commit.commit(messages);
+ return messages;
+ }
+ }
+
private Table createComplexValueTable(String format) throws Exception {
catalog.createTable(
identifier(format),
@@ -822,6 +1424,14 @@ public class MapSharedShreddingTableTest extends
TableTestBase {
return result;
}
+ private long numberOfSpillFiles() throws Exception {
+ try (Stream<java.nio.file.Path> paths = Files.walk(tempPath)) {
+ return paths.filter(Files::isRegularFile)
+ .filter(path ->
path.getFileName().toString().endsWith(".channel"))
+ .count();
+ }
+ }
+
private Map<String, Long> javaMapOf(Object... entries) {
Map<String, Long> map = new LinkedHashMap<>();
for (int i = 0; i < entries.length; i += 2) {