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 5a70451c84 [core] Support column placement policies for MAP
shared-shredding (#8743)
5a70451c84 is described below
commit 5a70451c84fa00651da755e8134a578ca530205d
Author: lxy <[email protected]>
AuthorDate: Mon Jul 20 21:35:28 2026 +0800
[core] Support column placement policies for MAP shared-shredding (#8743)
---
.../main/java/org/apache/paimon/CoreOptions.java | 49 +++++++
.../LruMapSharedShreddingColumnAllocator.java | 120 ++++++++++++++++++
.../MapSharedShreddingColumnAllocator.java | 51 ++++----
.../shredding/MapSharedShreddingRowConverter.java | 39 +++++-
.../shredding/MapSharedShreddingWritePlan.java | 9 +-
.../MapSharedShreddingWritePlanFactory.java | 8 +-
.../PlainMapSharedShreddingColumnAllocator.java | 51 ++++++++
...equentialMapSharedShreddingColumnAllocator.java | 41 ++++++
.../LruMapSharedShreddingColumnAllocatorTest.java | 83 ++++++++++++
.../MapSharedShreddingRowConverterTest.java | 141 +++++++++++++++++++--
.../shredding/MapSharedShreddingWritePlanTest.java | 72 ++++++++++-
...lainMapSharedShreddingColumnAllocatorTest.java} | 60 ++++++---
...ntialMapSharedShreddingColumnAllocatorTest.java | 58 +++++++++
.../org/apache/paimon/schema/SchemaValidation.java | 1 +
.../java/org/apache/paimon/CoreOptionsTest.java | 22 ++++
.../apache/paimon/append/AppendOnlyWriterTest.java | 3 +
.../apache/paimon/schema/SchemaValidationTest.java | 15 +++
.../paimon/table/MapSharedShreddingTableTest.java | 56 ++++++++
18 files changed, 818 insertions(+), 61 deletions(-)
diff --git a/paimon-api/src/main/java/org/apache/paimon/CoreOptions.java
b/paimon-api/src/main/java/org/apache/paimon/CoreOptions.java
index 0f26a2a79b..0d2a5926da 100644
--- a/paimon-api/src/main/java/org/apache/paimon/CoreOptions.java
+++ b/paimon-api/src/main/java/org/apache/paimon/CoreOptions.java
@@ -100,6 +100,9 @@ public class CoreOptions implements Serializable {
public static final String MAP_SHARED_SHREDDING_MAX_COLUMNS =
"map.shared-shredding.max-columns";
+ public static final String MAP_SHARED_SHREDDING_COLUMN_PLACEMENT_POLICY =
+ "map.shared-shredding.column-placement-policy";
+
public static final String FILE_INDEX = "file-index";
public static final String COLUMNS = "columns";
@@ -5299,6 +5302,18 @@ public class CoreOptions implements Serializable {
return maxColumns;
}
+ public MapSharedShreddingColumnPlacementPolicy
mapSharedShreddingColumnPlacementPolicy(
+ String fieldName) {
+ return options.get(
+ key(FIELDS_PREFIX
+ + "."
+ + fieldName
+ + "."
+ + MAP_SHARED_SHREDDING_COLUMN_PLACEMENT_POLICY)
+
.enumType(MapSharedShreddingColumnPlacementPolicy.class)
+
.defaultValue(MapSharedShreddingColumnPlacementPolicy.LRU));
+ }
+
/** MAP storage layout. */
public enum MapStorageLayout implements DescribedEnum {
DEFAULT(
@@ -5331,6 +5346,40 @@ public class CoreOptions implements Serializable {
}
}
+ /** Physical column placement policy for shared-shredding MAP fields. */
+ public enum MapSharedShreddingColumnPlacementPolicy implements
DescribedEnum {
+ PLAIN(
+ "plain",
+ "Keep each MAP row's input key order and place the first K
keys into physical "
+ + "columns."),
+ SEQUENTIAL(
+ "sequential",
+ "Order keys by their field dictionary IDs and place the first
K keys into physical "
+ + "columns."),
+ LRU(
+ "lru",
+ "Reuse physical columns for recently seen keys and evict the
least recently used "
+ + "column when necessary.");
+
+ private final String value;
+ private final String description;
+
+ MapSharedShreddingColumnPlacementPolicy(String value, String
description) {
+ this.value = value;
+ this.description = description;
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+
+ @Override
+ public InlineElement getDescription() {
+ return text(description);
+ }
+ }
+
/**
* Action to take when an UPDATE (e.g. via MERGE INTO) modifies columns
that are covered by a
* global index.
diff --git
a/paimon-common/src/main/java/org/apache/paimon/data/shredding/LruMapSharedShreddingColumnAllocator.java
b/paimon-common/src/main/java/org/apache/paimon/data/shredding/LruMapSharedShreddingColumnAllocator.java
new file mode 100644
index 0000000000..ebbf722c8e
--- /dev/null
+++
b/paimon-common/src/main/java/org/apache/paimon/data/shredding/LruMapSharedShreddingColumnAllocator.java
@@ -0,0 +1,120 @@
+/*
+ * 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.data.shredding;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Allocator that retains resident field-to-column assignments and evicts the
least recently used
+ * column when necessary.
+ */
+public class LruMapSharedShreddingColumnAllocator extends
MapSharedShreddingColumnAllocator {
+
+ private final int[] residentFieldByColumn;
+ private final long[] lastUsed;
+ private long lruClock;
+
+ public LruMapSharedShreddingColumnAllocator(int numColumns) {
+ super(numColumns);
+ this.residentFieldByColumn = emptyColumnMapping();
+ this.lastUsed = new long[numColumns];
+ }
+
+ @Override
+ public RowAllocation allocateRow(List<Integer> fieldIds) {
+ List<Integer> sortedFieldIds = new ArrayList<>(fieldIds);
+ Collections.sort(sortedFieldIds);
+
+ int[] colToField = emptyColumnMapping();
+ int[] nextResidentFieldByColumn = residentFieldByColumn.clone();
+ boolean[] usedColumns = new boolean[numColumns];
+ List<Integer> unassignedFields = new ArrayList<>();
+
+ for (Integer fieldId : sortedFieldIds) {
+ int column = findResidentColumn(fieldId);
+ if (column == -1) {
+ unassignedFields.add(fieldId);
+ } else {
+ usedColumns[column] = true;
+ colToField[column] = fieldId;
+ }
+ }
+
+ List<Integer> overflowFields = new ArrayList<>();
+ for (Integer fieldId : unassignedFields) {
+ int column = selectColumn(usedColumns, nextResidentFieldByColumn);
+ if (column == -1) {
+ overflowFields.add(fieldId);
+ continue;
+ }
+
+ usedColumns[column] = true;
+ colToField[column] = fieldId;
+ nextResidentFieldByColumn[column] = fieldId;
+ }
+
+ RowAllocation allocation = new RowAllocation(colToField,
overflowFields);
+ updateLastUsed(colToField);
+ System.arraycopy(nextResidentFieldByColumn, 0, residentFieldByColumn,
0, numColumns);
+ commitRow(allocation, sortedFieldIds);
+ return allocation;
+ }
+
+ private int findResidentColumn(int fieldId) {
+ for (int column = 0; column < numColumns; column++) {
+ if (residentFieldByColumn[column] == fieldId) {
+ return column;
+ }
+ }
+ return -1;
+ }
+
+ private int selectColumn(boolean[] usedColumns, int[]
plannedResidentFieldByColumn) {
+ int selectedColumn = -1;
+ long selectedLastUsed = Long.MAX_VALUE;
+ for (int column = 0; column < numColumns; column++) {
+ if (usedColumns[column]) {
+ continue;
+ }
+ if (plannedResidentFieldByColumn[column] == -1) {
+ return column;
+ }
+ if (lastUsed[column] < selectedLastUsed) {
+ selectedColumn = column;
+ selectedLastUsed = lastUsed[column];
+ }
+ }
+ return selectedColumn;
+ }
+
+ private void updateLastUsed(int[] colToField) {
+ boolean touched = false;
+ for (int column = 0; column < numColumns; column++) {
+ if (colToField[column] != -1) {
+ lastUsed[column] = lruClock;
+ touched = true;
+ }
+ }
+ if (touched) {
+ lruClock++;
+ }
+ }
+}
diff --git
a/paimon-common/src/main/java/org/apache/paimon/data/shredding/MapSharedShreddingColumnAllocator.java
b/paimon-common/src/main/java/org/apache/paimon/data/shredding/MapSharedShreddingColumnAllocator.java
index 9a3a9c8965..05a418fb06 100644
---
a/paimon-common/src/main/java/org/apache/paimon/data/shredding/MapSharedShreddingColumnAllocator.java
+++
b/paimon-common/src/main/java/org/apache/paimon/data/shredding/MapSharedShreddingColumnAllocator.java
@@ -29,44 +29,43 @@ import java.util.TreeSet;
/**
* Per-row physical column allocator for one shared-shredding MAP column.
*
- * <p>This is a simple temporary implementation which assigns fields to
physical columns by row
- * order. A later version will use a more sophisticated LRU-style allocator to
improve column reuse
- * across rows.
+ * <p>Implementations decide the physical column placement for each row. This
base class accumulates
+ * the file-level metadata shared by all placement policies.
*/
-public class MapSharedShreddingColumnAllocator {
+public abstract class MapSharedShreddingColumnAllocator {
- private final int numColumns;
+ protected final int numColumns;
private final Map<Integer, Set<Integer>> fieldToColumns = new TreeMap<>();
private final Set<Integer> overflowFieldSet = new TreeSet<>();
private int maxRowWidth = 0;
- public MapSharedShreddingColumnAllocator(int numColumns) {
+ protected MapSharedShreddingColumnAllocator(int numColumns) {
this.numColumns = numColumns;
}
- public RowAllocation allocateRow(List<Integer> fieldIds) {
+ /** Allocates physical columns for one row's field IDs. */
+ public abstract RowAllocation allocateRow(List<Integer> fieldIds);
+
+ /** Commits one row allocation and updates accumulated file-level
metadata. */
+ protected void commitRow(RowAllocation allocation, List<Integer> fieldIds)
{
maxRowWidth = Math.max(maxRowWidth, fieldIds.size());
- int[] colToField = new int[numColumns];
- for (int i = 0; i < numColumns; i++) {
- colToField[i] = -1;
+ for (int column = 0; column < numColumns; column++) {
+ int fieldId = allocation.colToField[column];
+ if (fieldId != -1) {
+ fieldToColumns.computeIfAbsent(fieldId, ignored -> new
TreeSet<>()).add(column);
+ }
}
- int assignLimit = Math.min(fieldIds.size(), numColumns);
- for (int i = 0; i < assignLimit; i++) {
- int fieldId = fieldIds.get(i);
- colToField[i] = fieldId;
- fieldToColumns.computeIfAbsent(fieldId, ignored -> new
TreeSet<>()).add(i);
- }
+ overflowFieldSet.addAll(allocation.overflowFields);
+ }
- List<Integer> overflowFields = new ArrayList<>();
- for (int i = assignLimit; i < fieldIds.size(); i++) {
- int fieldId = fieldIds.get(i);
- overflowFields.add(fieldId);
- overflowFieldSet.add(fieldId);
+ protected int[] emptyColumnMapping() {
+ int[] colToField = new int[numColumns];
+ for (int i = 0; i < numColumns; i++) {
+ colToField[i] = -1;
}
-
- return new RowAllocation(colToField, overflowFields);
+ return colToField;
}
public Map<Integer, List<Integer>> fieldToColumns() {
@@ -97,9 +96,9 @@ public class MapSharedShreddingColumnAllocator {
private final int[] colToField;
private final List<Integer> overflowFields;
- private RowAllocation(int[] colToField, List<Integer> overflowFields) {
- this.colToField = colToField;
- this.overflowFields = Collections.unmodifiableList(overflowFields);
+ RowAllocation(int[] colToField, List<Integer> overflowFields) {
+ this.colToField = colToField.clone();
+ this.overflowFields = Collections.unmodifiableList(new
ArrayList<>(overflowFields));
}
public int[] colToField() {
diff --git
a/paimon-common/src/main/java/org/apache/paimon/data/shredding/MapSharedShreddingRowConverter.java
b/paimon-common/src/main/java/org/apache/paimon/data/shredding/MapSharedShreddingRowConverter.java
index 2b0bc532c7..8a099b12d2 100644
---
a/paimon-common/src/main/java/org/apache/paimon/data/shredding/MapSharedShreddingRowConverter.java
+++
b/paimon-common/src/main/java/org/apache/paimon/data/shredding/MapSharedShreddingRowConverter.java
@@ -18,6 +18,7 @@
package org.apache.paimon.data.shredding;
+import org.apache.paimon.CoreOptions.MapSharedShreddingColumnPlacementPolicy;
import org.apache.paimon.data.BinaryString;
import org.apache.paimon.data.Blob;
import org.apache.paimon.data.Decimal;
@@ -42,6 +43,8 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import static org.apache.paimon.utils.Preconditions.checkArgument;
+
/** Converts logical rows containing shared-shredding MAP fields into physical
rows. */
public class MapSharedShreddingRowConverter {
@@ -52,7 +55,9 @@ public class MapSharedShreddingRowConverter {
private final List<String> shreddingFieldNames;
public MapSharedShreddingRowConverter(
- RowType logicalType, Map<String, Integer> fieldToNumColumns) {
+ RowType logicalType,
+ Map<String, Integer> fieldToNumColumns,
+ Map<String, MapSharedShreddingColumnPlacementPolicy>
fieldToColumnPlacementPolicy) {
this.logicalType = logicalType;
this.physicalType =
MapSharedShreddingUtils.logicalToPhysicalSchema(logicalType,
fieldToNumColumns);
@@ -68,7 +73,14 @@ public class MapSharedShreddingRowConverter {
}
MapType mapType = (MapType) field.type();
- ColumnContext context = new ColumnContext(field.name(),
numColumns, mapType);
+ MapSharedShreddingColumnPlacementPolicy placementPolicy =
+ fieldToColumnPlacementPolicy.get(field.name());
+ checkArgument(
+ placementPolicy != null,
+ "Missing column placement policy for shared-shredding
field '%s'.",
+ field.name());
+ ColumnContext context =
+ new ColumnContext(field.name(), numColumns, mapType,
placementPolicy);
contextByFieldName.put(field.name(), context);
contextByFieldPos[i] = context;
shreddingFieldNames.add(field.name());
@@ -289,14 +301,33 @@ public class MapSharedShreddingRowConverter {
private final MapSharedShreddingFieldDict dict;
private final MapSharedShreddingColumnAllocator allocator;
- private ColumnContext(String fieldName, int numColumns, MapType
mapType) {
+ private ColumnContext(
+ String fieldName,
+ int numColumns,
+ MapType mapType,
+ MapSharedShreddingColumnPlacementPolicy placementPolicy) {
this.fieldName = fieldName;
this.numColumns = numColumns;
this.keyGetter =
InternalArray.createElementGetter(mapType.getKeyType());
DataType valueType = mapType.getValueType();
this.valueGetter = InternalArray.createElementGetter(valueType);
this.dict = new MapSharedShreddingFieldDict();
- this.allocator = new MapSharedShreddingColumnAllocator(numColumns);
+ this.allocator = createAllocator(numColumns, placementPolicy);
+ }
+
+ private static MapSharedShreddingColumnAllocator createAllocator(
+ int numColumns, MapSharedShreddingColumnPlacementPolicy
placementPolicy) {
+ switch (placementPolicy) {
+ case PLAIN:
+ return new
PlainMapSharedShreddingColumnAllocator(numColumns);
+ case SEQUENTIAL:
+ return new
SequentialMapSharedShreddingColumnAllocator(numColumns);
+ case LRU:
+ return new
LruMapSharedShreddingColumnAllocator(numColumns);
+ default:
+ throw new IllegalArgumentException(
+ "Unknown shared-shredding column placement policy:
" + placementPolicy);
+ }
}
}
}
diff --git
a/paimon-common/src/main/java/org/apache/paimon/data/shredding/MapSharedShreddingWritePlan.java
b/paimon-common/src/main/java/org/apache/paimon/data/shredding/MapSharedShreddingWritePlan.java
index af0f614906..9555a2f45e 100644
---
a/paimon-common/src/main/java/org/apache/paimon/data/shredding/MapSharedShreddingWritePlan.java
+++
b/paimon-common/src/main/java/org/apache/paimon/data/shredding/MapSharedShreddingWritePlan.java
@@ -18,6 +18,7 @@
package org.apache.paimon.data.shredding;
+import org.apache.paimon.CoreOptions.MapSharedShreddingColumnPlacementPolicy;
import org.apache.paimon.data.InternalRow;
import org.apache.paimon.types.RowType;
@@ -35,9 +36,13 @@ public class MapSharedShreddingWritePlan implements
ShreddingWritePlan {
@Nullable private Map<String, Map<String, String>> fieldMetadata;
public MapSharedShreddingWritePlan(
- RowType logicalRowType, Map<String, Integer> fieldToNumColumns) {
+ RowType logicalRowType,
+ Map<String, Integer> fieldToNumColumns,
+ Map<String, MapSharedShreddingColumnPlacementPolicy>
fieldToColumnPlacementPolicy) {
this.logicalRowType = logicalRowType;
- this.converter = new MapSharedShreddingRowConverter(logicalRowType,
fieldToNumColumns);
+ this.converter =
+ new MapSharedShreddingRowConverter(
+ logicalRowType, fieldToNumColumns,
fieldToColumnPlacementPolicy);
}
@Override
diff --git
a/paimon-common/src/main/java/org/apache/paimon/data/shredding/MapSharedShreddingWritePlanFactory.java
b/paimon-common/src/main/java/org/apache/paimon/data/shredding/MapSharedShreddingWritePlanFactory.java
index 24c47207fa..7818b24baa 100644
---
a/paimon-common/src/main/java/org/apache/paimon/data/shredding/MapSharedShreddingWritePlanFactory.java
+++
b/paimon-common/src/main/java/org/apache/paimon/data/shredding/MapSharedShreddingWritePlanFactory.java
@@ -19,6 +19,7 @@
package org.apache.paimon.data.shredding;
import org.apache.paimon.CoreOptions;
+import org.apache.paimon.CoreOptions.MapSharedShreddingColumnPlacementPolicy;
import org.apache.paimon.data.InternalMap;
import org.apache.paimon.data.InternalRow;
import org.apache.paimon.format.shredding.ShreddingWritePlanFactory;
@@ -38,6 +39,7 @@ public class MapSharedShreddingWritePlanFactory implements
ShreddingWritePlanFac
private final RowType logicalRowType;
private final Map<String, Integer> fieldToMaxColumns;
+ private final Map<String, MapSharedShreddingColumnPlacementPolicy>
fieldToColumnPlacementPolicy;
private final Map<String, Integer> fieldToPosition;
public MapSharedShreddingWritePlanFactory(RowType logicalRowType, Options
options) {
@@ -47,8 +49,11 @@ public class MapSharedShreddingWritePlanFactory implements
ShreddingWritePlanFac
MapSharedShreddingUtils.detectShreddingColumns(logicalRowType,
coreOptions);
this.fieldToMaxColumns =
MapSharedShreddingUtils.buildColumnToNumColumns(shreddingFields, coreOptions);
+ this.fieldToColumnPlacementPolicy = new LinkedHashMap<>();
this.fieldToPosition = new LinkedHashMap<>();
for (String field : shreddingFields) {
+ fieldToColumnPlacementPolicy.put(
+ field,
coreOptions.mapSharedShreddingColumnPlacementPolicy(field));
fieldToPosition.put(field, logicalRowType.getFieldIndex(field));
}
}
@@ -97,6 +102,7 @@ public class MapSharedShreddingWritePlanFactory implements
ShreddingWritePlanFac
}
// TODO: Infer the column count from recent file metadata instead of
current-file samples.
- return new MapSharedShreddingWritePlan(logicalRowType,
fieldToNumColumns);
+ return new MapSharedShreddingWritePlan(
+ logicalRowType, fieldToNumColumns,
fieldToColumnPlacementPolicy);
}
}
diff --git
a/paimon-common/src/main/java/org/apache/paimon/data/shredding/PlainMapSharedShreddingColumnAllocator.java
b/paimon-common/src/main/java/org/apache/paimon/data/shredding/PlainMapSharedShreddingColumnAllocator.java
new file mode 100644
index 0000000000..14b778ffc9
--- /dev/null
+++
b/paimon-common/src/main/java/org/apache/paimon/data/shredding/PlainMapSharedShreddingColumnAllocator.java
@@ -0,0 +1,51 @@
+/*
+ * 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.data.shredding;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Allocator that maps fields to physical columns in the input MAP entry
order. */
+public class PlainMapSharedShreddingColumnAllocator extends
MapSharedShreddingColumnAllocator {
+
+ public PlainMapSharedShreddingColumnAllocator(int numColumns) {
+ super(numColumns);
+ }
+
+ @Override
+ public RowAllocation allocateRow(List<Integer> fieldIds) {
+ RowAllocation allocation = allocateLeadingColumns(fieldIds);
+ commitRow(allocation, fieldIds);
+ return allocation;
+ }
+
+ protected RowAllocation allocateLeadingColumns(List<Integer> fieldIds) {
+ int[] colToField = emptyColumnMapping();
+ List<Integer> overflowFields = new ArrayList<>();
+ for (int i = 0; i < fieldIds.size(); i++) {
+ int fieldId = fieldIds.get(i);
+ if (i < numColumns) {
+ colToField[i] = fieldId;
+ } else {
+ overflowFields.add(fieldId);
+ }
+ }
+ return new RowAllocation(colToField, overflowFields);
+ }
+}
diff --git
a/paimon-common/src/main/java/org/apache/paimon/data/shredding/SequentialMapSharedShreddingColumnAllocator.java
b/paimon-common/src/main/java/org/apache/paimon/data/shredding/SequentialMapSharedShreddingColumnAllocator.java
new file mode 100644
index 0000000000..a759402ffe
--- /dev/null
+++
b/paimon-common/src/main/java/org/apache/paimon/data/shredding/SequentialMapSharedShreddingColumnAllocator.java
@@ -0,0 +1,41 @@
+/*
+ * 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.data.shredding;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/** Allocator that orders fields by dictionary ID before filling physical
columns. */
+public class SequentialMapSharedShreddingColumnAllocator
+ extends PlainMapSharedShreddingColumnAllocator {
+
+ public SequentialMapSharedShreddingColumnAllocator(int numColumns) {
+ super(numColumns);
+ }
+
+ @Override
+ public RowAllocation allocateRow(List<Integer> fieldIds) {
+ List<Integer> sortedFieldIds = new ArrayList<>(fieldIds);
+ Collections.sort(sortedFieldIds);
+ RowAllocation allocation = allocateLeadingColumns(sortedFieldIds);
+ commitRow(allocation, sortedFieldIds);
+ return allocation;
+ }
+}
diff --git
a/paimon-common/src/test/java/org/apache/paimon/data/shredding/LruMapSharedShreddingColumnAllocatorTest.java
b/paimon-common/src/test/java/org/apache/paimon/data/shredding/LruMapSharedShreddingColumnAllocatorTest.java
new file mode 100644
index 0000000000..39e4def32d
--- /dev/null
+++
b/paimon-common/src/test/java/org/apache/paimon/data/shredding/LruMapSharedShreddingColumnAllocatorTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.data.shredding;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/** Tests for {@link LruMapSharedShreddingColumnAllocator}. */
+class LruMapSharedShreddingColumnAllocatorTest {
+
+ @Test
+ void testAllocatesWithHitRetainEvictAndOverflow() {
+ LruMapSharedShreddingColumnAllocator allocator =
+ new LruMapSharedShreddingColumnAllocator(3);
+
+ MapSharedShreddingColumnAllocator.RowAllocation row0 =
+ allocator.allocateRow(Arrays.asList(0, 1, 2));
+ assertThat(row0.colToField()).containsExactly(0, 1, 2);
+ assertThat(row0.overflowFields()).isEmpty();
+
+ MapSharedShreddingColumnAllocator.RowAllocation row1 =
+ allocator.allocateRow(Arrays.asList(0, 1));
+ assertThat(row1.colToField()).containsExactly(0, 1, -1);
+ assertThat(row1.overflowFields()).isEmpty();
+
+ MapSharedShreddingColumnAllocator.RowAllocation row2 =
+ allocator.allocateRow(Arrays.asList(3, 4, 5));
+ assertThat(row2.colToField()).containsExactly(4, 5, 3);
+ assertThat(row2.overflowFields()).isEmpty();
+
+ MapSharedShreddingColumnAllocator.RowAllocation row3 =
+ allocator.allocateRow(Arrays.asList(0, 3, 4, 5));
+ assertThat(row3.colToField()).containsExactly(4, 5, 3);
+ assertThat(row3.overflowFields()).containsExactly(0);
+
+ assertThat(allocator.maxRowWidth()).isEqualTo(4);
+ assertThat(allocator.fieldToColumns().get(0)).containsExactly(0);
+ assertThat(allocator.fieldToColumns().get(1)).containsExactly(1);
+ assertThat(allocator.fieldToColumns().get(2)).containsExactly(2);
+ assertThat(allocator.fieldToColumns().get(3)).containsExactly(2);
+ assertThat(allocator.fieldToColumns().get(4)).containsExactly(0);
+ assertThat(allocator.fieldToColumns().get(5)).containsExactly(1);
+ assertThat(allocator.overflowFieldSet()).containsExactly(0);
+ }
+
+ @Test
+ void testHandlesEmptyRows() {
+ LruMapSharedShreddingColumnAllocator allocator =
+ new LruMapSharedShreddingColumnAllocator(2);
+
+ MapSharedShreddingColumnAllocator.RowAllocation emptyRow =
+ allocator.allocateRow(Collections.emptyList());
+ assertThat(emptyRow.colToField()).containsExactly(-1, -1);
+ assertThat(emptyRow.overflowFields()).isEmpty();
+ assertThat(allocator.maxRowWidth()).isZero();
+
+ MapSharedShreddingColumnAllocator.RowAllocation row =
+ allocator.allocateRow(Collections.singletonList(7));
+ assertThat(row.colToField()).containsExactly(7, -1);
+ assertThat(row.overflowFields()).isEmpty();
+ assertThat(allocator.maxRowWidth()).isEqualTo(1);
+ }
+}
diff --git
a/paimon-common/src/test/java/org/apache/paimon/data/shredding/MapSharedShreddingRowConverterTest.java
b/paimon-common/src/test/java/org/apache/paimon/data/shredding/MapSharedShreddingRowConverterTest.java
index 3fed33f722..7960b119a5 100644
---
a/paimon-common/src/test/java/org/apache/paimon/data/shredding/MapSharedShreddingRowConverterTest.java
+++
b/paimon-common/src/test/java/org/apache/paimon/data/shredding/MapSharedShreddingRowConverterTest.java
@@ -18,6 +18,7 @@
package org.apache.paimon.data.shredding;
+import org.apache.paimon.CoreOptions.MapSharedShreddingColumnPlacementPolicy;
import org.apache.paimon.data.BinaryString;
import org.apache.paimon.data.GenericArray;
import org.apache.paimon.data.GenericMap;
@@ -53,7 +54,7 @@ class MapSharedShreddingRowConverterTest {
DataTypes.FIELD(
1, "tags", DataTypes.MAP(DataTypes.STRING(),
DataTypes.BIGINT())));
MapSharedShreddingRowConverter converter =
- new MapSharedShreddingRowConverter(logicalType,
columns("tags", 3));
+ createPlainConverter(logicalType, columns("tags", 3));
assertThat(converter.shreddingFieldNames()).containsExactly("tags");
assertThatThrownBy(() ->
converter.shreddingFieldNames().add("metrics"))
.isInstanceOf(UnsupportedOperationException.class);
@@ -109,7 +110,7 @@ class MapSharedShreddingRowConverterTest {
"metrics",
DataTypes.MAP(DataTypes.STRING(),
DataTypes.BIGINT())));
MapSharedShreddingRowConverter converter =
- new MapSharedShreddingRowConverter(logicalType,
columns("metrics", 2));
+ createPlainConverter(logicalType, columns("metrics", 2));
InternalRow row = converter.convert(GenericRow.of(stringKeyMap("a",
null, "b", 20L)));
InternalRow metrics = row.getRow(0, 4);
@@ -142,7 +143,7 @@ class MapSharedShreddingRowConverterTest {
"metrics",
DataTypes.MAP(DataTypes.STRING(),
DataTypes.BIGINT())));
MapSharedShreddingRowConverter converter =
- new MapSharedShreddingRowConverter(logicalType,
columns("metrics", 2));
+ createPlainConverter(logicalType, columns("metrics", 2));
InternalRow row =
converter.convert(GenericRow.of(stringKeyMap("a", 10L, "b",
20L, "c", 30L)));
@@ -176,7 +177,7 @@ class MapSharedShreddingRowConverterTest {
"metrics",
DataTypes.MAP(DataTypes.STRING(),
DataTypes.BIGINT())));
MapSharedShreddingRowConverter converter =
- new MapSharedShreddingRowConverter(logicalType,
columns("metrics", 2));
+ createPlainConverter(logicalType, columns("metrics", 2));
InternalRow nullRow = converter.convert(GenericRow.of((InternalMap)
null));
assertThat(nullRow.isNullAt(0)).isTrue();
@@ -212,7 +213,7 @@ class MapSharedShreddingRowConverterTest {
DataTypes.ROW(
DataTypes.FIELD(0, "tags",
DataTypes.MAP(DataTypes.STRING(), valueType)));
MapSharedShreddingRowConverter converter =
- new MapSharedShreddingRowConverter(logicalType,
columns("tags", 2));
+ createPlainConverter(logicalType, columns("tags", 2));
InternalRow row =
converter.convert(
@@ -253,7 +254,7 @@ class MapSharedShreddingRowConverterTest {
DataTypes.MAP(
DataTypes.STRING(),
DataTypes.ARRAY(DataTypes.INT()))));
MapSharedShreddingRowConverter converter =
- new MapSharedShreddingRowConverter(logicalType,
columns("tags", 2));
+ createPlainConverter(logicalType, columns("tags", 2));
InternalRow first =
converter.convert(
@@ -335,7 +336,7 @@ class MapSharedShreddingRowConverterTest {
DataTypes.FIELD(
1, "nested", DataTypes.MAP(DataTypes.STRING(),
innerMapType)));
MapSharedShreddingRowConverter converter =
- new MapSharedShreddingRowConverter(logicalType,
columns("nested", 2));
+ createPlainConverter(logicalType, columns("nested", 2));
InternalRow first =
converter.convert(
@@ -421,7 +422,7 @@ class MapSharedShreddingRowConverterTest {
DataTypes.FIELD(0, "id", DataTypes.INT()),
DataTypes.FIELD(1, "data",
DataTypes.MAP(DataTypes.STRING(), valueType)));
MapSharedShreddingRowConverter converter =
- new MapSharedShreddingRowConverter(logicalType,
columns("data", 2));
+ createPlainConverter(logicalType, columns("data", 2));
InternalRow first =
converter.convert(
@@ -527,7 +528,7 @@ class MapSharedShreddingRowConverterTest {
fieldToNumColumns.put("tags", 2);
fieldToNumColumns.put("attrs", 3);
MapSharedShreddingRowConverter converter =
- new MapSharedShreddingRowConverter(logicalType,
fieldToNumColumns);
+ createPlainConverter(logicalType, fieldToNumColumns);
InternalRow first =
converter.convert(
@@ -620,6 +621,106 @@ class MapSharedShreddingRowConverterTest {
4));
}
+ @Test
+ void testSequentialPlacementUsesDictionaryOrder() {
+ RowType logicalType =
+ DataTypes.ROW(
+ DataTypes.FIELD(0, "id", DataTypes.INT()),
+ DataTypes.FIELD(
+ 1, "tags", DataTypes.MAP(DataTypes.STRING(),
DataTypes.BIGINT())));
+ MapSharedShreddingRowConverter converter =
+ createConverter(
+ logicalType, "tags", 3,
MapSharedShreddingColumnPlacementPolicy.SEQUENTIAL);
+
+ InternalRow first = converter.convert(GenericRow.of(100,
stringKeyMap("a", 1L, "b", 2L)));
+ InternalRow firstTags = first.getRow(1, 5);
+ assertThat(firstTags.getArray(0).toIntArray()).containsExactly(0, 1,
-1);
+ assertThat(firstTags.getLong(1)).isEqualTo(1L);
+ assertThat(firstTags.getLong(2)).isEqualTo(2L);
+ assertThat(firstTags.isNullAt(3)).isTrue();
+ assertThat(firstTags.isNullAt(4)).isTrue();
+
+ InternalRow second =
+ converter.convert(GenericRow.of(200, stringKeyMap("b", 3L,
"c", 4L, "a", 5L)));
+ InternalRow secondTags = second.getRow(1, 5);
+ assertThat(secondTags.getArray(0).toIntArray()).containsExactly(0, 1,
2);
+ assertThat(secondTags.getLong(1)).isEqualTo(5L);
+ assertThat(secondTags.getLong(2)).isEqualTo(3L);
+ assertThat(secondTags.getLong(3)).isEqualTo(4L);
+ assertThat(secondTags.isNullAt(4)).isTrue();
+
+ assertThat(converter.buildFieldMeta("tags"))
+ .isEqualTo(
+ new MapSharedShreddingFieldMeta(
+ nameToId("a", 0, "b", 1, "c", 2),
+ fieldToColumns(
+ 0, Collections.singletonList(0),
+ 1, Collections.singletonList(1),
+ 2, Collections.singletonList(2)),
+ new TreeSet<Integer>(),
+ 3,
+ 3));
+ }
+
+ @Test
+ void testLruPlacementPreservesResidentColumns() {
+ RowType logicalType =
+ DataTypes.ROW(
+ DataTypes.FIELD(0, "id", DataTypes.INT()),
+ DataTypes.FIELD(
+ 1, "tags", DataTypes.MAP(DataTypes.STRING(),
DataTypes.BIGINT())));
+ MapSharedShreddingRowConverter converter =
+ createConverter(
+ logicalType, "tags", 3,
MapSharedShreddingColumnPlacementPolicy.LRU);
+
+ InternalRow first =
+ converter.convert(GenericRow.of(1, stringKeyMap("a", 10L, "b",
20L, "c", 30L)));
+ assertThat(first.getRow(1,
5).getArray(0).toIntArray()).containsExactly(0, 1, 2);
+
+ InternalRow second = converter.convert(GenericRow.of(2,
stringKeyMap("a", 40L, "b", 50L)));
+ assertThat(second.getRow(1,
5).getArray(0).toIntArray()).containsExactly(0, 1, -1);
+
+ InternalRow third =
+ converter.convert(GenericRow.of(3, stringKeyMap("d", 60L, "e",
70L, "f", 80L)));
+ InternalRow thirdTags = third.getRow(1, 5);
+ assertThat(thirdTags.getArray(0).toIntArray()).containsExactly(4, 5,
3);
+ assertThat(thirdTags.getLong(1)).isEqualTo(70L);
+ assertThat(thirdTags.getLong(2)).isEqualTo(80L);
+ assertThat(thirdTags.getLong(3)).isEqualTo(60L);
+ assertThat(thirdTags.isNullAt(4)).isTrue();
+
+ InternalRow fourth =
+ converter.convert(
+ GenericRow.of(4, stringKeyMap("a", 90L, "d", 100L,
"e", 110L, "f", 120L)));
+ InternalRow fourthTags = fourth.getRow(1, 5);
+ assertThat(fourthTags.getArray(0).toIntArray()).containsExactly(4, 5,
3);
+ assertThat(fourthTags.getLong(1)).isEqualTo(110L);
+ assertThat(fourthTags.getLong(2)).isEqualTo(120L);
+ assertThat(fourthTags.getLong(3)).isEqualTo(100L);
+ assertThat(fourthTags.getMap(4)).isEqualTo(intKeyMap(0, 90L));
+
+ assertThat(converter.buildFieldMeta("tags"))
+ .isEqualTo(
+ new MapSharedShreddingFieldMeta(
+ nameToId(
+ "a", 0,
+ "b", 1,
+ "c", 2,
+ "d", 3,
+ "e", 4,
+ "f", 5),
+ fieldToColumns(
+ 0, Collections.singletonList(0),
+ 1, Collections.singletonList(1),
+ 2, Collections.singletonList(2),
+ 3, Collections.singletonList(2),
+ 4, Collections.singletonList(0),
+ 5, Collections.singletonList(1)),
+ new TreeSet<>(Collections.singletonList(0)),
+ 3,
+ 4));
+ }
+
@Test
void testBuildFieldMetaInvalidFieldName() {
RowType logicalType =
@@ -628,7 +729,7 @@ class MapSharedShreddingRowConverterTest {
DataTypes.FIELD(
1, "tags", DataTypes.MAP(DataTypes.STRING(),
DataTypes.BIGINT())));
MapSharedShreddingRowConverter converter =
- new MapSharedShreddingRowConverter(logicalType,
columns("tags", 3));
+ createPlainConverter(logicalType, columns("tags", 3));
assertThat(converter.buildFieldMeta("tags"))
.isEqualTo(
@@ -652,6 +753,26 @@ class MapSharedShreddingRowConverterTest {
return columns;
}
+ private static MapSharedShreddingRowConverter createPlainConverter(
+ RowType logicalType, Map<String, Integer> fieldToNumColumns) {
+ Map<String, MapSharedShreddingColumnPlacementPolicy> policies = new
HashMap<>();
+ for (String fieldName : fieldToNumColumns.keySet()) {
+ policies.put(fieldName,
MapSharedShreddingColumnPlacementPolicy.PLAIN);
+ }
+ return new MapSharedShreddingRowConverter(logicalType,
fieldToNumColumns, policies);
+ }
+
+ private static MapSharedShreddingRowConverter createConverter(
+ RowType logicalType,
+ String fieldName,
+ int numColumns,
+ MapSharedShreddingColumnPlacementPolicy policy) {
+ return new MapSharedShreddingRowConverter(
+ logicalType,
+ Collections.singletonMap(fieldName, numColumns),
+ Collections.singletonMap(fieldName, policy));
+ }
+
private static GenericMap stringKeyMap(Object... keyValues) {
Map<Object, Object> values = new LinkedHashMap<>();
for (int i = 0; i < keyValues.length; i += 2) {
diff --git
a/paimon-common/src/test/java/org/apache/paimon/data/shredding/MapSharedShreddingWritePlanTest.java
b/paimon-common/src/test/java/org/apache/paimon/data/shredding/MapSharedShreddingWritePlanTest.java
index ccc323bf54..1652e81717 100644
---
a/paimon-common/src/test/java/org/apache/paimon/data/shredding/MapSharedShreddingWritePlanTest.java
+++
b/paimon-common/src/test/java/org/apache/paimon/data/shredding/MapSharedShreddingWritePlanTest.java
@@ -18,6 +18,7 @@
package org.apache.paimon.data.shredding;
+import org.apache.paimon.CoreOptions.MapSharedShreddingColumnPlacementPolicy;
import org.apache.paimon.data.BinaryString;
import org.apache.paimon.data.GenericMap;
import org.apache.paimon.data.GenericRow;
@@ -45,7 +46,11 @@ class MapSharedShreddingWritePlanTest {
DataTypes.FIELD(
1, "tags", DataTypes.MAP(DataTypes.STRING(),
DataTypes.BIGINT())));
MapSharedShreddingWritePlan writePlan =
- new MapSharedShreddingWritePlan(logicalType,
Collections.singletonMap("tags", 4));
+ new MapSharedShreddingWritePlan(
+ logicalType,
+ Collections.singletonMap("tags", 4),
+ Collections.singletonMap(
+ "tags",
MapSharedShreddingColumnPlacementPolicy.PLAIN));
InternalRow physicalRow =
writePlan.toPhysicalRow(
@@ -117,12 +122,69 @@ class MapSharedShreddingWritePlanTest {
logicalType, Collections.singletonMap("tags",
2)));
}
+ @Test
+ void testFactoryUsesConfiguredColumnPlacementPolicy() {
+ RowType logicalType =
+ DataTypes.ROW(
+ DataTypes.FIELD(
+ 0, "tags", DataTypes.MAP(DataTypes.STRING(),
DataTypes.INT())));
+ MapSharedShreddingWritePlanFactory factory =
createFactory(logicalType, 3, "sequential");
+ InternalRow first = GenericRow.of(stringKeyMap("a", 1, "b", 2, "c",
6));
+ InternalRow second = GenericRow.of(stringKeyMap("b", 3, "d", 4, "a",
5));
+ ShreddingWritePlan writePlan =
factory.createWritePlan(Collections.singletonList(first));
+
+ writePlan.toPhysicalRow(first).getRow(0, 5);
+ InternalRow physicalMap = writePlan.toPhysicalRow(second).getRow(0, 5);
+
+ assertThat(physicalMap.getArray(0).toIntArray()).containsExactly(0, 1,
3);
+ assertThat(physicalMap.getInt(1)).isEqualTo(5);
+ assertThat(physicalMap.getInt(2)).isEqualTo(3);
+ assertThat(physicalMap.getInt(3)).isEqualTo(4);
+ }
+
+ @Test
+ void testFactoryUsesLruColumnPlacementByDefault() {
+ RowType logicalType =
+ DataTypes.ROW(
+ DataTypes.FIELD(
+ 0, "tags", DataTypes.MAP(DataTypes.STRING(),
DataTypes.INT())));
+ MapSharedShreddingWritePlanFactory factory =
createFactory(logicalType, 3);
+ InternalRow first = GenericRow.of(stringKeyMap("a", 10, "b", 20, "c",
30));
+ ShreddingWritePlan writePlan =
factory.createWritePlan(Collections.singletonList(first));
+
+ writePlan.toPhysicalRow(first).getRow(0, 5);
+ writePlan.toPhysicalRow(GenericRow.of(stringKeyMap("a", 40, "b",
50))).getRow(0, 5);
+ writePlan
+ .toPhysicalRow(GenericRow.of(stringKeyMap("d", 60, "e", 70,
"f", 80)))
+ .getRow(0, 5);
+ InternalRow physicalMap =
+ writePlan
+ .toPhysicalRow(
+ GenericRow.of(stringKeyMap("a", 90, "d", 100,
"e", 110, "f", 120)))
+ .getRow(0, 5);
+
+ assertThat(physicalMap.getArray(0).toIntArray()).containsExactly(4, 5,
3);
+ assertThat(physicalMap.getInt(1)).isEqualTo(110);
+ assertThat(physicalMap.getInt(2)).isEqualTo(120);
+ assertThat(physicalMap.getInt(3)).isEqualTo(100);
+ assertThat(physicalMap.getMap(4)).isEqualTo(intKeyMap(0, 90));
+ }
+
private static MapSharedShreddingWritePlanFactory createFactory(
RowType logicalType, int maxColumns) {
+ return createFactory(logicalType, maxColumns, null);
+ }
+
+ private static MapSharedShreddingWritePlanFactory createFactory(
+ RowType logicalType, int maxColumns, String placementPolicy) {
Options options = new Options();
options.setString("fields.tags.map.storage-layout",
"shared-shredding");
options.setString(
"fields.tags.map.shared-shredding.max-columns",
String.valueOf(maxColumns));
+ if (placementPolicy != null) {
+ options.setString(
+
"fields.tags.map.shared-shredding.column-placement-policy", placementPolicy);
+ }
return new MapSharedShreddingWritePlanFactory(logicalType, options);
}
@@ -133,4 +195,12 @@ class MapSharedShreddingWritePlanTest {
}
return new GenericMap(values);
}
+
+ private static GenericMap intKeyMap(Object... keyValues) {
+ Map<Object, Object> values = new LinkedHashMap<>();
+ for (int i = 0; i < keyValues.length; i += 2) {
+ values.put(keyValues[i], keyValues[i + 1]);
+ }
+ return new GenericMap(values);
+ }
}
diff --git
a/paimon-common/src/test/java/org/apache/paimon/data/shredding/MapSharedShreddingColumnAllocatorTest.java
b/paimon-common/src/test/java/org/apache/paimon/data/shredding/PlainMapSharedShreddingColumnAllocatorTest.java
similarity index 65%
rename from
paimon-common/src/test/java/org/apache/paimon/data/shredding/MapSharedShreddingColumnAllocatorTest.java
rename to
paimon-common/src/test/java/org/apache/paimon/data/shredding/PlainMapSharedShreddingColumnAllocatorTest.java
index 933e1faf61..c1dc967b8f 100644
---
a/paimon-common/src/test/java/org/apache/paimon/data/shredding/MapSharedShreddingColumnAllocatorTest.java
+++
b/paimon-common/src/test/java/org/apache/paimon/data/shredding/PlainMapSharedShreddingColumnAllocatorTest.java
@@ -28,12 +28,13 @@ import java.util.TreeSet;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
-/** Tests for {@link MapSharedShreddingColumnAllocator}. */
-class MapSharedShreddingColumnAllocatorTest {
+/** Tests for {@link PlainMapSharedShreddingColumnAllocator}. */
+class PlainMapSharedShreddingColumnAllocatorTest {
@Test
void testBasicAllocation() {
- MapSharedShreddingColumnAllocator allocator = new
MapSharedShreddingColumnAllocator(3);
+ PlainMapSharedShreddingColumnAllocator allocator =
+ new PlainMapSharedShreddingColumnAllocator(3);
MapSharedShreddingColumnAllocator.RowAllocation allocation =
allocator.allocateRow(Arrays.asList(10, 20));
@@ -44,7 +45,8 @@ class MapSharedShreddingColumnAllocatorTest {
@Test
void testExactlyKFields() {
- MapSharedShreddingColumnAllocator allocator = new
MapSharedShreddingColumnAllocator(3);
+ PlainMapSharedShreddingColumnAllocator allocator =
+ new PlainMapSharedShreddingColumnAllocator(3);
MapSharedShreddingColumnAllocator.RowAllocation allocation =
allocator.allocateRow(Arrays.asList(0, 1, 2));
@@ -55,7 +57,8 @@ class MapSharedShreddingColumnAllocatorTest {
@Test
void testOverflowWhenExceedK() {
- MapSharedShreddingColumnAllocator allocator = new
MapSharedShreddingColumnAllocator(2);
+ PlainMapSharedShreddingColumnAllocator allocator =
+ new PlainMapSharedShreddingColumnAllocator(2);
MapSharedShreddingColumnAllocator.RowAllocation allocation =
allocator.allocateRow(Arrays.asList(10, 20, 30, 40));
@@ -68,7 +71,8 @@ class MapSharedShreddingColumnAllocatorTest {
@Test
void testEmptyRow() {
- MapSharedShreddingColumnAllocator allocator = new
MapSharedShreddingColumnAllocator(3);
+ PlainMapSharedShreddingColumnAllocator allocator =
+ new PlainMapSharedShreddingColumnAllocator(3);
MapSharedShreddingColumnAllocator.RowAllocation allocation =
allocator.allocateRow(Arrays.asList());
@@ -79,7 +83,8 @@ class MapSharedShreddingColumnAllocatorTest {
@Test
void testMaxRowWidthTracked() {
- MapSharedShreddingColumnAllocator allocator = new
MapSharedShreddingColumnAllocator(3);
+ PlainMapSharedShreddingColumnAllocator allocator =
+ new PlainMapSharedShreddingColumnAllocator(3);
allocator.allocateRow(Arrays.asList(1, 2));
allocator.allocateRow(Arrays.asList(1, 2, 3, 4, 5));
@@ -90,7 +95,8 @@ class MapSharedShreddingColumnAllocatorTest {
@Test
void testFieldToColumnsAccumulated() {
- MapSharedShreddingColumnAllocator allocator = new
MapSharedShreddingColumnAllocator(3);
+ PlainMapSharedShreddingColumnAllocator allocator =
+ new PlainMapSharedShreddingColumnAllocator(3);
allocator.allocateRow(Arrays.asList(10, 20, 30));
allocator.allocateRow(Arrays.asList(20, 40));
@@ -108,7 +114,8 @@ class MapSharedShreddingColumnAllocatorTest {
@Test
void testOverflowFieldSetAccumulated() {
- MapSharedShreddingColumnAllocator allocator = new
MapSharedShreddingColumnAllocator(2);
+ PlainMapSharedShreddingColumnAllocator allocator =
+ new PlainMapSharedShreddingColumnAllocator(2);
allocator.allocateRow(Arrays.asList(1, 2, 3));
allocator.allocateRow(Arrays.asList(4, 5, 6, 7));
@@ -118,21 +125,40 @@ class MapSharedShreddingColumnAllocatorTest {
.isInstanceOf(UnsupportedOperationException.class);
}
- @Test
- void testGetNumColumns() {
- MapSharedShreddingColumnAllocator allocator = new
MapSharedShreddingColumnAllocator(5);
-
- assertThat(allocator.numColumns()).isEqualTo(5);
- }
-
@Test
void testSingleColumnAllocator() {
- MapSharedShreddingColumnAllocator allocator = new
MapSharedShreddingColumnAllocator(1);
+ PlainMapSharedShreddingColumnAllocator allocator =
+ new PlainMapSharedShreddingColumnAllocator(1);
MapSharedShreddingColumnAllocator.RowAllocation allocation =
allocator.allocateRow(Arrays.asList(10, 20, 30));
assertThat(allocation.colToField()).containsExactly(10);
assertThat(allocation.overflowFields()).containsExactly(20, 30);
+ assertThat(allocator.numColumns()).isEqualTo(1);
+ }
+
+ @Test
+ void testUsesInputOrder() {
+ PlainMapSharedShreddingColumnAllocator allocator =
+ new PlainMapSharedShreddingColumnAllocator(3);
+
+ MapSharedShreddingColumnAllocator.RowAllocation row0 =
+ allocator.allocateRow(Arrays.asList(2, 0, 1));
+ assertThat(row0.colToField()).containsExactly(2, 0, 1);
+ assertThat(row0.overflowFields()).isEmpty();
+
+ MapSharedShreddingColumnAllocator.RowAllocation row1 =
+ allocator.allocateRow(Arrays.asList(4, 3, 5, 6));
+ assertThat(row1.colToField()).containsExactly(4, 3, 5);
+ assertThat(row1.overflowFields()).containsExactly(6);
+
+ assertThat(allocator.fieldToColumns().get(0)).containsExactly(1);
+ assertThat(allocator.fieldToColumns().get(1)).containsExactly(2);
+ assertThat(allocator.fieldToColumns().get(2)).containsExactly(0);
+ assertThat(allocator.fieldToColumns().get(3)).containsExactly(1);
+ assertThat(allocator.fieldToColumns().get(4)).containsExactly(0);
+ assertThat(allocator.fieldToColumns().get(5)).containsExactly(2);
+ assertThat(allocator.overflowFieldSet()).containsExactly(6);
}
}
diff --git
a/paimon-common/src/test/java/org/apache/paimon/data/shredding/SequentialMapSharedShreddingColumnAllocatorTest.java
b/paimon-common/src/test/java/org/apache/paimon/data/shredding/SequentialMapSharedShreddingColumnAllocatorTest.java
new file mode 100644
index 0000000000..879712b9a4
--- /dev/null
+++
b/paimon-common/src/test/java/org/apache/paimon/data/shredding/SequentialMapSharedShreddingColumnAllocatorTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.data.shredding;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/** Tests for {@link SequentialMapSharedShreddingColumnAllocator}. */
+class SequentialMapSharedShreddingColumnAllocatorTest {
+
+ @Test
+ void testSortsAndUsesLeadingColumns() {
+ SequentialMapSharedShreddingColumnAllocator allocator =
+ new SequentialMapSharedShreddingColumnAllocator(3);
+
+ MapSharedShreddingColumnAllocator.RowAllocation row0 =
+ allocator.allocateRow(Arrays.asList(1, 2));
+ assertThat(row0.colToField()).containsExactly(1, 2, -1);
+ assertThat(row0.overflowFields()).isEmpty();
+
+ MapSharedShreddingColumnAllocator.RowAllocation row1 =
+ allocator.allocateRow(Arrays.asList(2, 3));
+ assertThat(row1.colToField()).containsExactly(2, 3, -1);
+ assertThat(row1.overflowFields()).isEmpty();
+
+ MapSharedShreddingColumnAllocator.RowAllocation row2 =
+ allocator.allocateRow(Arrays.asList(7, 4, 6, 5));
+ assertThat(row2.colToField()).containsExactly(4, 5, 6);
+ assertThat(row2.overflowFields()).containsExactly(7);
+
+ assertThat(allocator.fieldToColumns().get(1)).containsExactly(0);
+ assertThat(allocator.fieldToColumns().get(2)).containsExactly(0, 1);
+ assertThat(allocator.fieldToColumns().get(3)).containsExactly(1);
+ assertThat(allocator.fieldToColumns().get(4)).containsExactly(0);
+ assertThat(allocator.fieldToColumns().get(5)).containsExactly(1);
+ assertThat(allocator.fieldToColumns().get(6)).containsExactly(2);
+ assertThat(allocator.overflowFieldSet()).containsExactly(7);
+ }
+}
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 d145704fef..452ba953f8 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
@@ -677,6 +677,7 @@ public class SchemaValidation {
fieldName));
}
options.mapSharedShreddingMaxColumns(fieldName);
+ options.mapSharedShreddingColumnPlacementPolicy(fieldName);
}
if (hasSharedShredding) {
diff --git a/paimon-core/src/test/java/org/apache/paimon/CoreOptionsTest.java
b/paimon-core/src/test/java/org/apache/paimon/CoreOptionsTest.java
index 031dd29679..a996942292 100644
--- a/paimon-core/src/test/java/org/apache/paimon/CoreOptionsTest.java
+++ b/paimon-core/src/test/java/org/apache/paimon/CoreOptionsTest.java
@@ -127,19 +127,32 @@ public class CoreOptionsTest {
assertThat(options.mapStorageLayout("metrics"))
.isEqualTo(CoreOptions.MapStorageLayout.DEFAULT);
assertThat(options.mapSharedShreddingMaxColumns("metrics")).isEqualTo(256);
+ assertThat(options.mapSharedShreddingColumnPlacementPolicy("metrics"))
+
.isEqualTo(CoreOptions.MapSharedShreddingColumnPlacementPolicy.LRU);
conf.setString("fields.metrics.map.storage-layout",
"shared-shredding");
conf.setString("fields.metrics.map.shared-shredding.max-columns",
"32");
+
conf.setString("fields.metrics.map.shared-shredding.column-placement-policy",
"sequential");
options = new CoreOptions(conf);
assertThat(options.mapStorageLayout("metrics"))
.isEqualTo(CoreOptions.MapStorageLayout.SHARED_SHREDDING);
assertThat(options.mapSharedShreddingMaxColumns("metrics")).isEqualTo(32);
+ assertThat(options.mapSharedShreddingColumnPlacementPolicy("metrics"))
+
.isEqualTo(CoreOptions.MapSharedShreddingColumnPlacementPolicy.SEQUENTIAL);
+
+
conf.setString("fields.metrics.map.shared-shredding.column-placement-policy",
"lru");
+ options = new CoreOptions(conf);
+ assertThat(options.mapSharedShreddingColumnPlacementPolicy("metrics"))
+
.isEqualTo(CoreOptions.MapSharedShreddingColumnPlacementPolicy.LRU);
conf = new Options();
conf.setString("fields.metrics.map.storage-layout",
"Shared-Shredding");
+
conf.setString("fields.metrics.map.shared-shredding.column-placement-policy",
"PLAIN");
options = new CoreOptions(conf);
assertThat(options.mapStorageLayout("metrics"))
.isEqualTo(CoreOptions.MapStorageLayout.SHARED_SHREDDING);
+ assertThat(options.mapSharedShreddingColumnPlacementPolicy("metrics"))
+
.isEqualTo(CoreOptions.MapSharedShreddingColumnPlacementPolicy.PLAIN);
conf = new Options();
conf.setString("fields.metrics.map.storage-layout", "invalid");
@@ -147,6 +160,15 @@ public class CoreOptionsTest {
assertThatThrownBy(() ->
invalidLayoutOptions.mapStorageLayout("metrics"))
.hasMessageContaining("invalid");
+ conf = new Options();
+
conf.setString("fields.metrics.map.shared-shredding.column-placement-policy",
"invalid");
+ final CoreOptions invalidPlacementPolicyOptions = new
CoreOptions(conf);
+ assertThatThrownBy(
+ () ->
+ invalidPlacementPolicyOptions
+
.mapSharedShreddingColumnPlacementPolicy("metrics"))
+ .hasMessageContaining("invalid");
+
conf = new Options();
conf.setString("fields.metrics.map.shared-shredding.max-columns", "0");
final CoreOptions zeroMaxColumnsOptions = new CoreOptions(conf);
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 47ebf0fbaa..bd5d1d2cf5 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
@@ -869,6 +869,9 @@ public class AppendOnlyWriterTest {
options.setString(
"fields." + fieldName +
".map.shared-shredding.max-columns",
String.valueOf(fieldToMaxColumns[i + 1]));
+ options.setString(
+ "fields." + fieldName +
".map.shared-shredding.column-placement-policy",
+ "plain");
}
options.setString("metadata.stats-mode", "none");
return options;
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 ec07c17c4f..de116cb600 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
@@ -504,6 +504,21 @@ class SchemaValidationTest {
options,
"")))
.hasMessageContaining("options
map.shared-shredding.max-columns must > 0");
+
+ options.put("fields.metrics.map.shared-shredding.max-columns", "1");
+
options.put("fields.metrics.map.shared-shredding.column-placement-policy",
"invalid");
+ assertThatThrownBy(
+ () ->
+ validateTableSchema(
+ new TableSchema(
+ 1,
+ fields,
+ 10,
+ emptyList(),
+ emptyList(),
+ options,
+ "")))
+ .hasMessageContaining("invalid");
}
@Test
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 de5b74572b..35e86d9a1d 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
@@ -103,6 +103,62 @@ public class MapSharedShreddingTableTest extends
TableTestBase {
.containsEntry(4, javaMapOf("a", null, "b", 42L, "c", null));
}
+ @ParameterizedTest
+ @CsvSource({
+ "orc,plain",
+ "orc,sequential",
+ "orc,lru",
+ "parquet,plain",
+ "parquet,sequential",
+ "parquet,lru"
+ })
+ public void testColumnPlacementPolicies(String format, String
placementPolicy)
+ throws Exception {
+ Table table = createTable(format, 3, "metrics");
+ catalog.alterTable(
+ identifier(format),
+ Collections.singletonList(
+ SchemaChange.setOption(
+
"fields.metrics.map.shared-shredding.column-placement-policy",
+ placementPolicy)),
+ false);
+ table = catalog.getTable(identifier(format));
+
+ write(
+ table,
+ GenericRow.of(1, mapOf("a", 10L, "b", 20L, "c", 30L)),
+ GenericRow.of(2, mapOf("a", 40L, "b", 50L)),
+ GenericRow.of(3, mapOf("d", 60L)),
+ GenericRow.of(4, mapOf("a", 70L, "b", 80L, "c", 90L, "d",
100L)));
+
+ FileStoreTable fileStoreTable = (FileStoreTable) table;
+ List<DataFileWithSplit> files = currentDataFiles(fileStoreTable);
+ assertThat(files).hasSize(1);
+ MapSharedShreddingFieldMeta fieldMeta =
+ readSharedShreddingFieldMeta(fileStoreTable, files.get(0),
"metrics");
+ assertThat(fieldMeta.nameToId()).containsOnlyKeys("a", "b", "c", "d");
+ assertThat(fieldMeta.numColumns()).isEqualTo(3);
+ assertThat(fieldMeta.maxRowWidth()).isEqualTo(4);
+ assertThat(fieldMeta.overflowFieldSet()).hasSize(1);
+
+ if ("lru".equals(placementPolicy)) {
+
assertThat(fieldMeta.overflowFieldSet()).containsExactly(fieldMeta.nameToId().get("c"));
+ } else {
+
assertThat(fieldMeta.overflowFieldSet()).containsExactly(fieldMeta.nameToId().get("d"));
+ }
+
+ 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, 4)
+ .containsEntry(1, javaMapOf("a", 10L, "b", 20L, "c", 30L))
+ .containsEntry(2, javaMapOf("a", 40L, "b", 50L))
+ .containsEntry(3, javaMapOf("d", 60L))
+ .containsEntry(4, javaMapOf("a", 70L, "b", 80L, "c", 90L, "d",
100L));
+ }
+
@ParameterizedTest
@ValueSource(strings = {"orc", "parquet"})
public void testAppendOnlyTableReadWriteWithTwoMapFields(String format)
throws Exception {