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 bbe084067c [core][spark] Support switching MAP storage layouts (#8941)
bbe084067c is described below

commit bbe084067c5e135c226644c4b53f9e530dd77aeb
Author: lszskye <[email protected]>
AuthorDate: Thu Jul 30 08:01:06 2026 -0700

    [core][spark] Support switching MAP storage layouts (#8941)
---
 .../data/shredding/MapSharedShreddingReadPlan.java | 118 ++++++++--
 .../MapSharedShreddingReadPlanFactory.java         |   5 +-
 .../data/shredding/MapSharedShreddingUtils.java    |  21 +-
 .../shredding/MapSharedShreddingReadPlanTest.java  |  56 ++++-
 .../shredding/MapSharedShreddingUtilsTest.java     | 105 +++++++++
 .../org/apache/paimon/schema/SchemaManager.java    |  46 ----
 .../apache/paimon/schema/SchemaManagerTest.java    |  67 +++---
 .../paimon/table/MapSharedShreddingTableTest.java  | 241 ++++++++++++++++++---
 ...MapSelectedKeysSharedShreddingE2ETestBase.scala |  93 ++++++++
 9 files changed, 621 insertions(+), 131 deletions(-)

diff --git 
a/paimon-common/src/main/java/org/apache/paimon/data/shredding/MapSharedShreddingReadPlan.java
 
b/paimon-common/src/main/java/org/apache/paimon/data/shredding/MapSharedShreddingReadPlan.java
index f09c58d685..6cc8dae4e3 100644
--- 
a/paimon-common/src/main/java/org/apache/paimon/data/shredding/MapSharedShreddingReadPlan.java
+++ 
b/paimon-common/src/main/java/org/apache/paimon/data/shredding/MapSharedShreddingReadPlan.java
@@ -45,14 +45,17 @@ import java.util.Map;
 
 import static org.apache.paimon.utils.Preconditions.checkArgument;
 
-/** Read plan that rebuilds logical MAP values from shared-shredding physical 
ROW values. */
+/**
+ * Read plan that rebuilds logical MAP values from shared-shredding physical 
ROW values and projects
+ * selected keys from legacy default-layout MAP values.
+ */
 public class MapSharedShreddingReadPlan implements ShreddingReadPlan {
 
     private static final int FIELD_MAPPING_POSITION = 0;
 
     private final RowType logicalType;
     private final RowType physicalType;
-    private final Map<Integer, SharedShreddingContext> contextByFieldIndex;
+    private final Map<Integer, MapReadContext> contextByFieldIndex;
 
     public MapSharedShreddingReadPlan(
             RowType logicalType, Map<String, MapSharedShreddingFieldMeta> 
fieldMetas) {
@@ -81,15 +84,21 @@ public class MapSharedShreddingReadPlan implements 
ShreddingReadPlan {
         return new MapSharedShreddingBatchAssembler();
     }
 
-    private static Map<Integer, SharedShreddingContext> createContexts(
+    private static Map<Integer, MapReadContext> createContexts(
             RowType logicalType,
             RowType physicalType,
             Map<String, MapSharedShreddingFieldMeta> fieldMetas) {
-        Map<Integer, SharedShreddingContext> contexts = new LinkedHashMap<>();
+        Map<Integer, MapReadContext> contexts = new LinkedHashMap<>();
         for (int i = 0; i < logicalType.getFieldCount(); i++) {
             DataField field = logicalType.getFields().get(i);
             MapSharedShreddingFieldMeta fieldMeta = 
fieldMetas.get(field.name());
             if (fieldMeta == null) {
+                if 
(MapSelectedKeysMetadataUtils.isMapSelectedKeysField(field)) {
+                    contexts.put(
+                            i,
+                            new NormalMapSelectedKeysContext(
+                                    (RowType) field.type(), 
field.description()));
+                }
                 continue;
             }
 
@@ -117,14 +126,13 @@ public class MapSharedShreddingReadPlan implements 
ShreddingReadPlan {
         public VectorizedColumnBatch assemble(VectorizedColumnBatch 
physicalBatch) {
             ColumnVector[] logicalVectors = new 
ColumnVector[logicalType.getFieldCount()];
             for (int i = 0; i < logicalVectors.length; i++) {
-                SharedShreddingContext context = contextByFieldIndex.get(i);
+                MapReadContext context = contextByFieldIndex.get(i);
                 if (context == null) {
                     logicalVectors[i] = physicalBatch.columns[i];
                 } else {
                     logicalVectors[i] =
                             context.materialize(
-                                    (RowColumnVector) physicalBatch.columns[i],
-                                    physicalBatch.getNumRows());
+                                    physicalBatch.columns[i], 
physicalBatch.getNumRows());
                 }
             }
             return physicalBatch.copy(logicalVectors);
@@ -208,6 +216,37 @@ public class MapSharedShreddingReadPlan implements 
ShreddingReadPlan {
         return ColumnVectorUtils.createReadableColumnVector(selectedKeysType, 
rowVector);
     }
 
+    private static ColumnVector materializeSelectedKeysRowVector(
+            MapColumnVector physicalVector, NormalMapSelectedKeysContext 
context, int rowCount) {
+        RowType selectedKeysType = context.selectedKeysType;
+        WritableColumnVector[] selectedKeyVectors =
+                new WritableColumnVector[selectedKeysType.getFieldCount()];
+        for (int i = 0; i < selectedKeyVectors.length; i++) {
+            selectedKeyVectors[i] =
+                    ColumnVectorUtils.createWritableColumnVector(
+                            rowCount, selectedKeysType.getTypeAt(i));
+        }
+        HeapRowVector rowVector = new HeapRowVector(rowCount, 
selectedKeyVectors);
+
+        for (int row = 0; row < rowCount; row++) {
+            if (physicalVector.isNullAt(row)) {
+                rowVector.appendNull();
+                continue;
+            }
+
+            InternalMap map = physicalVector.getMap(row);
+            InternalArray keys = map.keyArray();
+            InternalArray values = map.valueArray();
+            for (int ordinal = 0; ordinal < selectedKeyVectors.length; 
ordinal++) {
+                appendSelectedKeyValue(
+                        keys, values, map.size(), context, ordinal, 
selectedKeyVectors[ordinal]);
+            }
+            rowVector.appendRow();
+        }
+
+        return ColumnVectorUtils.createReadableColumnVector(selectedKeysType, 
rowVector);
+    }
+
     private static ColumnVector[] physicalChildren(RowColumnVector 
physicalVector) {
         ColumnVector[] physicalChildren = physicalVector.getChildren();
         if (physicalChildren != null) {
@@ -329,6 +368,23 @@ public class MapSharedShreddingReadPlan implements 
ShreddingReadPlan {
         valueVector.appendNull();
     }
 
+    private static void appendSelectedKeyValue(
+            InternalArray keys,
+            InternalArray values,
+            int size,
+            NormalMapSelectedKeysContext context,
+            int ordinal,
+            WritableColumnVector valueVector) {
+        BinaryString selectedKey = context.selectedKeys[ordinal];
+        for (int i = 0; i < size; i++) {
+            if (!keys.isNullAt(i) && selectedKey.equals(keys.getString(i))) {
+                appendValue(context.selectedValueConverters[ordinal], values, 
i, valueVector);
+                return;
+            }
+        }
+        valueVector.appendNull();
+    }
+
     private static void appendValue(
             RowToColumnConverter.ElementConverter converter,
             ColumnVector source,
@@ -401,9 +457,9 @@ public class MapSharedShreddingReadPlan implements 
ShreddingReadPlan {
         return overflowVector.isNullAt(row) ? null : ((MapColumnVector) 
overflowVector).getMap(row);
     }
 
-    private interface SharedShreddingContext {
+    private interface MapReadContext {
 
-        ColumnVector materialize(RowColumnVector physicalVector, int rowCount);
+        ColumnVector materialize(ColumnVector physicalVector, int rowCount);
     }
 
     private static class SharedPhysicalContext {
@@ -475,7 +531,7 @@ public class MapSharedShreddingReadPlan implements 
ShreddingReadPlan {
         }
     }
 
-    private static class FullMapContext implements SharedShreddingContext {
+    private static class FullMapContext implements MapReadContext {
 
         private final SharedPhysicalContext physical;
         private final MapType mapType;
@@ -491,12 +547,12 @@ public class MapSharedShreddingReadPlan implements 
ShreddingReadPlan {
         }
 
         @Override
-        public ColumnVector materialize(RowColumnVector physicalVector, int 
rowCount) {
-            return materializeLogicalMapVector(physicalVector, this, rowCount);
+        public ColumnVector materialize(ColumnVector physicalVector, int 
rowCount) {
+            return materializeLogicalMapVector((RowColumnVector) 
physicalVector, this, rowCount);
         }
     }
 
-    private static class SelectedKeysContext implements SharedShreddingContext 
{
+    private static class SelectedKeysContext implements MapReadContext {
 
         private final SharedPhysicalContext physical;
         private final RowType selectedKeysType;
@@ -537,8 +593,9 @@ public class MapSharedShreddingReadPlan implements 
ShreddingReadPlan {
         }
 
         @Override
-        public ColumnVector materialize(RowColumnVector physicalVector, int 
rowCount) {
-            return materializeSelectedKeysRowVector(physicalVector, this, 
rowCount);
+        public ColumnVector materialize(ColumnVector physicalVector, int 
rowCount) {
+            return materializeSelectedKeysRowVector(
+                    (RowColumnVector) physicalVector, this, rowCount);
         }
 
         private static int[] candidateColumns(MapSharedShreddingFieldMeta 
fieldMeta, int fieldId) {
@@ -553,4 +610,35 @@ public class MapSharedShreddingReadPlan implements 
ShreddingReadPlan {
             return result;
         }
     }
+
+    private static class NormalMapSelectedKeysContext implements 
MapReadContext {
+
+        private final RowType selectedKeysType;
+        private final BinaryString[] selectedKeys;
+        private final RowToColumnConverter.ElementConverter[] 
selectedValueConverters;
+
+        private NormalMapSelectedKeysContext(
+                RowType selectedKeysType, String selectedKeysMetadata) {
+            this.selectedKeysType = selectedKeysType;
+            List<String> keys = 
MapSelectedKeysMetadataUtils.selectedKeys(selectedKeysMetadata);
+            checkArgument(
+                    keys.size() == selectedKeysType.getFieldCount(),
+                    "Selected-key metadata size %s does not match selected ROW 
field count %s.",
+                    keys.size(),
+                    selectedKeysType.getFieldCount());
+            this.selectedKeys = new BinaryString[keys.size()];
+            this.selectedValueConverters = new 
RowToColumnConverter.ElementConverter[keys.size()];
+            for (int i = 0; i < keys.size(); i++) {
+                this.selectedKeys[i] = BinaryString.fromString(keys.get(i));
+                this.selectedValueConverters[i] =
+                        
RowToColumnConverter.createElementConverter(selectedKeysType.getTypeAt(i));
+            }
+        }
+
+        @Override
+        public ColumnVector materialize(ColumnVector physicalVector, int 
rowCount) {
+            return materializeSelectedKeysRowVector(
+                    (MapColumnVector) physicalVector, this, rowCount);
+        }
+    }
 }
diff --git 
a/paimon-common/src/main/java/org/apache/paimon/data/shredding/MapSharedShreddingReadPlanFactory.java
 
b/paimon-common/src/main/java/org/apache/paimon/data/shredding/MapSharedShreddingReadPlanFactory.java
index 36ee35b8f7..aeedf4057e 100644
--- 
a/paimon-common/src/main/java/org/apache/paimon/data/shredding/MapSharedShreddingReadPlanFactory.java
+++ 
b/paimon-common/src/main/java/org/apache/paimon/data/shredding/MapSharedShreddingReadPlanFactory.java
@@ -27,7 +27,7 @@ import javax.annotation.Nullable;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
-/** Creates per-file shared-shredding MAP read plans. */
+/** Creates per-file MAP read plans for shared-shredding and selected-key 
fallback reads. */
 public class MapSharedShreddingReadPlanFactory implements 
ShreddingReadPlanFactory {
 
     private final RowType logicalRowType;
@@ -46,7 +46,8 @@ public class MapSharedShreddingReadPlanFactory implements 
ShreddingReadPlanFacto
             Map<String, Map<String, String>> fieldMetadata, @Nullable Object 
fileSchema) {
         for (DataField field : logicalRowType.getFields()) {
             Map<String, String> metadata = fieldMetadata.get(field.name());
-            if (MapSharedShreddingUtils.hasShreddingMetadata(metadata)) {
+            if (MapSharedShreddingUtils.hasShreddingMetadata(metadata)
+                    || 
MapSelectedKeysMetadataUtils.isMapSelectedKeysField(field)) {
                 return true;
             }
         }
diff --git 
a/paimon-common/src/main/java/org/apache/paimon/data/shredding/MapSharedShreddingUtils.java
 
b/paimon-common/src/main/java/org/apache/paimon/data/shredding/MapSharedShreddingUtils.java
index 6fc2d13b5a..caf4bbc887 100644
--- 
a/paimon-common/src/main/java/org/apache/paimon/data/shredding/MapSharedShreddingUtils.java
+++ 
b/paimon-common/src/main/java/org/apache/paimon/data/shredding/MapSharedShreddingUtils.java
@@ -28,6 +28,7 @@ import org.apache.paimon.types.ArrayType;
 import org.apache.paimon.types.DataField;
 import org.apache.paimon.types.DataType;
 import org.apache.paimon.types.DataTypeRoot;
+import org.apache.paimon.types.DataTypes;
 import org.apache.paimon.types.IntType;
 import org.apache.paimon.types.MapType;
 import org.apache.paimon.types.RowType;
@@ -108,24 +109,30 @@ public class MapSharedShreddingUtils {
     public static RowType buildPhysicalReadType(
             RowType logicalReadType,
             Map<String, MapSharedShreddingFieldMeta> 
sharedShreddingFieldMetas) {
-        if (sharedShreddingFieldMetas.isEmpty()) {
-            return logicalReadType;
-        }
-
         List<DataField> physicalReadFields = new ArrayList<>();
         boolean converted = false;
         for (DataField logicalReadField : logicalReadType.getFields()) {
             MapSharedShreddingFieldMeta fieldMeta =
                     sharedShreddingFieldMetas.get(logicalReadField.name());
             if (fieldMeta == null) {
-                physicalReadFields.add(logicalReadField);
+                if 
(MapSelectedKeysMetadataUtils.isMapSelectedKeysField(logicalReadField)) {
+                    // Read a legacy/default-layout file while selected-key 
pushdown is active.
+                    DataType valueType = selectedKeysValueType((RowType) 
logicalReadField.type());
+                    DataType physicalType =
+                            DataTypes.MAP(DataTypes.STRING().notNull(), 
valueType)
+                                    
.copy(logicalReadField.type().isNullable());
+                    
physicalReadFields.add(logicalReadField.newType(physicalType));
+                    converted = true;
+                } else {
+                    physicalReadFields.add(logicalReadField);
+                }
                 continue;
             }
 
             DataType valueType;
             DataType physicalType;
             if 
(MapSelectedKeysMetadataUtils.isMapSelectedKeysField(logicalReadField)) {
-                // recall partial key with shared shredding pushdown
+                // Read only selected keys from a shared-shredding ROW.
                 valueType = selectedKeysValueType((RowType) 
logicalReadField.type());
                 physicalType =
                         buildSpecificPhysicalStructType(
@@ -134,7 +141,7 @@ public class MapSharedShreddingUtils {
                                         
selectedKeysIncludeOverflow(logicalReadField, fieldMeta))
                                 .copy(logicalReadField.type().isNullable());
             } else {
-                // recall whole field without shared shredding pushdown
+                // Rebuild the whole MAP from a shared-shredding ROW.
                 valueType = ((MapType) logicalReadField.type()).getValueType();
                 physicalType =
                         buildPhysicalStructType(valueType, 
fieldMeta.numColumns())
diff --git 
a/paimon-common/src/test/java/org/apache/paimon/data/shredding/MapSharedShreddingReadPlanTest.java
 
b/paimon-common/src/test/java/org/apache/paimon/data/shredding/MapSharedShreddingReadPlanTest.java
index 6b781be417..b376a11d95 100644
--- 
a/paimon-common/src/test/java/org/apache/paimon/data/shredding/MapSharedShreddingReadPlanTest.java
+++ 
b/paimon-common/src/test/java/org/apache/paimon/data/shredding/MapSharedShreddingReadPlanTest.java
@@ -28,11 +28,13 @@ import org.apache.paimon.data.columnar.MapColumnVector;
 import org.apache.paimon.data.columnar.RowColumnVector;
 import org.apache.paimon.data.columnar.VectorizedColumnBatch;
 import org.apache.paimon.data.columnar.heap.HeapArrayVector;
+import org.apache.paimon.data.columnar.heap.HeapBytesVector;
 import org.apache.paimon.data.columnar.heap.HeapIntVector;
 import org.apache.paimon.data.columnar.heap.HeapLongVector;
 import org.apache.paimon.data.columnar.heap.HeapMapVector;
 import org.apache.paimon.data.columnar.heap.HeapRowVector;
 import org.apache.paimon.types.DataTypes;
+import org.apache.paimon.types.MapType;
 import org.apache.paimon.types.RowType;
 
 import org.junit.jupiter.api.Test;
@@ -185,6 +187,48 @@ class MapSharedShreddingReadPlanTest {
         assertThat(selectedKeys.isNullAt(2)).isTrue();
     }
 
+    @Test
+    void testReadSelectedKeysFromNormalMap() {
+        MapSharedShreddingReadPlan readPlan =
+                new MapSharedShreddingReadPlan(selectedKeysLogicalType(), 
Collections.emptyMap());
+        
assertThat(readPlan.physicalRowType().getTypeAt(0)).isInstanceOf(MapType.class);
+
+        HeapBytesVector keys = new HeapBytesVector(3);
+        appendString(keys, "key2");
+        appendString(keys, "key1");
+        appendString(keys, "key1");
+        HeapLongVector values = new HeapLongVector(3);
+        values.appendLong(20L);
+        values.appendLong(10L);
+        values.appendNull();
+        HeapMapVector normalMaps = new HeapMapVector(3, keys, values);
+        normalMaps.putOffsetLength(0, 0, 2);
+        normalMaps.putOffsetLength(1, 2, 1);
+        normalMaps.setNullAt(2);
+        normalMaps.putOffsetLength(2, 3, 0);
+
+        RowColumnVector selectedKeysVector = 
assembleSelectedKeysVector(readPlan, normalMaps, 3);
+        InternalRow first = selectedKeysVector.getRow(0);
+        assertThat(first.getLong(0)).isEqualTo(10L);
+        assertThat(first.getLong(1)).isEqualTo(20L);
+        assertThat(first.isNullAt(2)).isTrue();
+
+        InternalRow second = selectedKeysVector.getRow(1);
+        assertThat(second.isNullAt(0)).isTrue();
+        assertThat(second.isNullAt(1)).isTrue();
+        assertThat(second.isNullAt(2)).isTrue();
+        assertThat(selectedKeysVector.isNullAt(2)).isTrue();
+    }
+
+    @Test
+    void testFactoryCreatesSelectedKeysPlanForNormalMap() {
+        MapSharedShreddingReadPlanFactory factory =
+                new 
MapSharedShreddingReadPlanFactory(selectedKeysLogicalType());
+
+        assertThat(factory.shouldCreateReadPlan(Collections.emptyMap(), 
null)).isTrue();
+        assertThat(factory.createReadPlan(Collections.emptyMap(), 
null).isIdentity()).isFalse();
+    }
+
     private static InternalMap readMap(
             MapSharedShreddingFieldMeta fieldMeta, HeapRowVector physicalMap) {
         return assembleMapVector(fieldMeta, physicalMap).getMap(0);
@@ -205,11 +249,14 @@ class MapSharedShreddingReadPlanTest {
 
     private static RowColumnVector assembleSelectedKeysVector(
             MapSharedShreddingReadPlan readPlan, HeapRowVector physicalMap) {
-        
assertThat(readPlan.physicalRowType().getTypeAt(0)).isInstanceOf(RowType.class);
+        return assembleSelectedKeysVector(readPlan, physicalMap, 1);
+    }
 
+    private static RowColumnVector assembleSelectedKeysVector(
+            MapSharedShreddingReadPlan readPlan, ColumnVector physicalMap, int 
rowCount) {
         VectorizedColumnBatch physicalBatch =
                 new VectorizedColumnBatch(new ColumnVector[] {physicalMap});
-        physicalBatch.setNumRows(1);
+        physicalBatch.setNumRows(rowCount);
         VectorizedColumnBatch logicalBatch = 
readPlan.batchAssembler().assemble(physicalBatch);
         return (RowColumnVector) logicalBatch.columns[0];
     }
@@ -282,6 +329,11 @@ class MapSharedShreddingReadPlanTest {
         return vector;
     }
 
+    private static void appendString(HeapBytesVector vector, String value) {
+        byte[] bytes = BinaryString.fromString(value).toBytes();
+        vector.appendByteArray(bytes, 0, bytes.length);
+    }
+
     private static HeapMapVector overflowMap(int key, Long value) {
         HeapIntVector keys = new HeapIntVector(1);
         keys.appendInt(key);
diff --git 
a/paimon-common/src/test/java/org/apache/paimon/data/shredding/MapSharedShreddingUtilsTest.java
 
b/paimon-common/src/test/java/org/apache/paimon/data/shredding/MapSharedShreddingUtilsTest.java
index dd628bc5a3..12acb8b099 100644
--- 
a/paimon-common/src/test/java/org/apache/paimon/data/shredding/MapSharedShreddingUtilsTest.java
+++ 
b/paimon-common/src/test/java/org/apache/paimon/data/shredding/MapSharedShreddingUtilsTest.java
@@ -27,6 +27,7 @@ import org.apache.paimon.types.RowType;
 import org.junit.jupiter.api.Test;
 
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -187,6 +188,110 @@ class MapSharedShreddingUtilsTest {
                 .isEqualTo(logical);
     }
 
+    @Test
+    void testBuildPhysicalReadTypeWithoutSharedShreddingMetadata() {
+        RowType fullMapReadType =
+                DataTypes.ROW(
+                        DataTypes.FIELD(0, "id", DataTypes.INT()),
+                        DataTypes.FIELD(
+                                1,
+                                "metrics",
+                                DataTypes.MAP(
+                                        DataTypes.STRING().notNull(),
+                                        DataTypes.BIGINT().notNull())));
+        assertThat(
+                        MapSharedShreddingUtils.buildPhysicalReadType(
+                                fullMapReadType, Collections.emptyMap()))
+                .isSameAs(fullMapReadType);
+
+        DataField selectedKeysField =
+                MapSelectedKeysMetadataUtils.withSelectedKeys(
+                        fullMapReadType.getField("metrics"),
+                        DataTypes.ROW(
+                                DataTypes.FIELD(0, "0", 
DataTypes.BIGINT().notNull()),
+                                DataTypes.FIELD(1, "1", 
DataTypes.BIGINT().notNull()),
+                                DataTypes.FIELD(2, "2", 
DataTypes.BIGINT().notNull())),
+                        Arrays.asList("key1", "key2", "missing"));
+        RowType selectedKeysReadType =
+                new RowType(
+                        false, Arrays.asList(fullMapReadType.getField("id"), 
selectedKeysField));
+
+        RowType physicalReadType =
+                MapSharedShreddingUtils.buildPhysicalReadType(
+                        selectedKeysReadType, Collections.emptyMap());
+        assertThat(physicalReadType.isNullable()).isFalse();
+        
assertThat(physicalReadType.getField("id")).isSameAs(fullMapReadType.getField("id"));
+        DataField physicalMapField = physicalReadType.getField("metrics");
+        assertThat(physicalMapField.id()).isEqualTo(selectedKeysField.id());
+        
assertThat(physicalMapField.description()).isEqualTo(selectedKeysField.description());
+        assertThat(physicalMapField.type())
+                .isEqualTo(
+                        DataTypes.MAP(DataTypes.STRING().notNull(), 
DataTypes.BIGINT().notNull()));
+    }
+
+    @Test
+    void testBuildPhysicalReadTypeWithSharedShreddingMetadata() {
+        Map<String, Integer> nameToId = new TreeMap<>();
+        nameToId.put("key1", 0);
+        nameToId.put("key2", 1);
+        Map<Integer, List<Integer>> fieldToColumns = new TreeMap<>();
+        fieldToColumns.put(0, Collections.singletonList(2));
+        MapSharedShreddingFieldMeta fieldMeta =
+                new MapSharedShreddingFieldMeta(
+                        nameToId,
+                        fieldToColumns,
+                        new TreeSet<Integer>(Collections.singletonList(1)),
+                        4,
+                        2);
+        Map<String, MapSharedShreddingFieldMeta> fieldMetas = new HashMap<>();
+        fieldMetas.put("metrics", fieldMeta);
+
+        DataField fullMapField =
+                DataTypes.FIELD(
+                        1,
+                        "metrics",
+                        DataTypes.MAP(DataTypes.STRING().notNull(), 
DataTypes.BIGINT()).notNull());
+        RowType fullMapReadType =
+                new RowType(
+                        false,
+                        Arrays.asList(DataTypes.FIELD(0, "id", 
DataTypes.INT()), fullMapField));
+        RowType fullPhysicalReadType =
+                MapSharedShreddingUtils.buildPhysicalReadType(fullMapReadType, 
fieldMetas);
+        DataField fullPhysicalMapField = 
fullPhysicalReadType.getField("metrics");
+        assertThat(fullPhysicalMapField.id()).isEqualTo(fullMapField.id());
+        assertThat(fullPhysicalMapField.type().isNullable()).isFalse();
+        assertThat(((RowType) fullPhysicalMapField.type()).getFieldNames())
+                .containsExactly(
+                        "__field_mapping",
+                        "__col_0",
+                        "__col_1",
+                        "__col_2",
+                        "__col_3",
+                        "__overflow");
+
+        DataField selectedKeysField =
+                MapSelectedKeysMetadataUtils.withSelectedKeys(
+                        fullMapField,
+                        DataTypes.ROW(
+                                        DataTypes.FIELD(0, "0", 
DataTypes.BIGINT()),
+                                        DataTypes.FIELD(1, "1", 
DataTypes.BIGINT()),
+                                        DataTypes.FIELD(2, "2", 
DataTypes.BIGINT()))
+                                .notNull(),
+                        Arrays.asList("key1", "key2", "missing"));
+        RowType selectedKeysReadType =
+                new RowType(
+                        false, Arrays.asList(fullMapReadType.getField("id"), 
selectedKeysField));
+        RowType selectedPhysicalReadType =
+                
MapSharedShreddingUtils.buildPhysicalReadType(selectedKeysReadType, fieldMetas);
+        DataField selectedPhysicalMapField = 
selectedPhysicalReadType.getField("metrics");
+        
assertThat(selectedPhysicalMapField.id()).isEqualTo(selectedKeysField.id());
+        assertThat(selectedPhysicalMapField.description())
+                .isEqualTo(selectedKeysField.description());
+        assertThat(selectedPhysicalMapField.type().isNullable()).isFalse();
+        assertThat(((RowType) selectedPhysicalMapField.type()).getFieldNames())
+                .containsExactly("__field_mapping", "__col_2", "__overflow");
+    }
+
     @Test
     void testBuildSpecificPhysicalStructType() {
         RowType physicalType =
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/schema/SchemaManager.java 
b/paimon-core/src/main/java/org/apache/paimon/schema/SchemaManager.java
index 5eadd040c7..4c238781b9 100644
--- a/paimon-core/src/main/java/org/apache/paimon/schema/SchemaManager.java
+++ b/paimon-core/src/main/java/org/apache/paimon/schema/SchemaManager.java
@@ -637,56 +637,10 @@ public class SchemaManager implements Serializable {
                         newSchema.primaryKeys(),
                         newSchema.options(),
                         newSchema.comment());
-        checkMapStorageLayoutUnchanged(oldTableSchema, newTableSchema);
         SchemaValidation.validateTableSchema(newTableSchema);
         return newTableSchema;
     }
 
-    private static void checkMapStorageLayoutUnchanged(
-            TableSchema oldTableSchema, TableSchema newTableSchema) {
-        Map<Integer, CoreOptions.MapStorageLayout> oldLayouts =
-                mapStorageLayoutByFieldId(oldTableSchema);
-        Map<Integer, CoreOptions.MapStorageLayout> newLayouts =
-                mapStorageLayoutByFieldId(newTableSchema);
-        Map<Integer, String> oldNames = fieldNameById(oldTableSchema);
-        Map<Integer, String> newNames = fieldNameById(newTableSchema);
-
-        for (Map.Entry<Integer, CoreOptions.MapStorageLayout> oldLayout : 
oldLayouts.entrySet()) {
-            Integer fieldId = oldLayout.getKey();
-            CoreOptions.MapStorageLayout newLayout = newLayouts.get(fieldId);
-            if (newLayout == null || oldLayout.getValue() == newLayout) {
-                continue;
-            }
-
-            throw new UnsupportedOperationException(
-                    String.format(
-                            "Cannot change map storage layout for field id %s 
('%s' -> '%s') from '%s' to '%s'.",
-                            fieldId,
-                            oldNames.get(fieldId),
-                            newNames.get(fieldId),
-                            oldLayout.getValue(),
-                            newLayout));
-        }
-    }
-
-    private static Map<Integer, CoreOptions.MapStorageLayout> 
mapStorageLayoutByFieldId(
-            TableSchema schema) {
-        CoreOptions options = new CoreOptions(schema.options());
-        Map<Integer, CoreOptions.MapStorageLayout> layouts = new HashMap<>();
-        for (DataField field : schema.fields()) {
-            layouts.put(field.id(), options.mapStorageLayout(field.name()));
-        }
-        return layouts;
-    }
-
-    private static Map<Integer, String> fieldNameById(TableSchema schema) {
-        Map<Integer, String> names = new HashMap<>();
-        for (DataField field : schema.fields()) {
-            names.put(field.id(), field.name());
-        }
-        return names;
-    }
-
     // gets the rootType at the defined depth
     // ex: ARRAY<MAP<STRING, ARRAY<INT>>>
     // if we want to update ARRAY<INT> -> ARRAY<BIGINT>
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/schema/SchemaManagerTest.java 
b/paimon-core/src/test/java/org/apache/paimon/schema/SchemaManagerTest.java
index 2b0f4f18bf..aed3a9a309 100644
--- a/paimon-core/src/test/java/org/apache/paimon/schema/SchemaManagerTest.java
+++ b/paimon-core/src/test/java/org/apache/paimon/schema/SchemaManagerTest.java
@@ -172,41 +172,52 @@ public class SchemaManagerTest {
     }
 
     @Test
-    public void testCannotChangeMapStorageLayoutForExistingField() throws 
Exception {
+    public void testChangeMapStorageLayoutForExistingField() throws Exception {
         retryArtificialException(() -> 
manager.createTable(mapStorageLayoutSchema("default")));
 
-        assertThatThrownBy(
-                        () ->
-                                retryArtificialException(
-                                        () ->
-                                                manager.commitChanges(
-                                                        SchemaChange.setOption(
-                                                                
"fields.metrics.map.storage-layout",
-                                                                
"shared-shredding"))))
-                .isInstanceOf(UnsupportedOperationException.class)
-                .hasMessageContaining(
-                        "Cannot change map storage layout for field id 1 
('metrics' -> 'metrics') from 'default' to 'shared-shredding'.");
+        retryArtificialException(
+                () ->
+                        manager.commitChanges(
+                                SchemaChange.setOption(
+                                        "fields.metrics.map.storage-layout", 
"shared-shredding")));
+        Optional<TableSchema> sharedShredding = retryArtificialException(() -> 
manager.latest());
+        assertThat(sharedShredding).isPresent();
+        assertThat(sharedShredding.get().options())
+                .containsEntry("fields.metrics.map.storage-layout", 
"shared-shredding")
+                
.containsEntry("fields.metrics.map.shared-shredding.max-columns", "2");
+
+        retryArtificialException(
+                () ->
+                        manager.commitChanges(
+                                SchemaChange.setOption(
+                                        "fields.metrics.map.storage-layout", 
"default")));
+        Optional<TableSchema> defaultLayout = retryArtificialException(() -> 
manager.latest());
+        assertThat(defaultLayout).isPresent();
+        assertThat(defaultLayout.get().options())
+                .containsEntry("fields.metrics.map.storage-layout", "default")
+                
.containsEntry("fields.metrics.map.shared-shredding.max-columns", "2");
     }
 
     @Test
-    public void testCannotChangeMapStorageLayoutByRenameColumn() throws 
Exception {
+    public void testChangeMapStorageLayoutByRenameColumn() throws Exception {
         retryArtificialException(() -> 
manager.createTable(mapStorageLayoutSchema(null)));
 
-        assertThatThrownBy(
-                        () ->
-                                retryArtificialException(
-                                        () ->
-                                                manager.commitChanges(
-                                                        Arrays.asList(
-                                                                
SchemaChange.renameColumn(
-                                                                        
"metrics",
-                                                                        
"renamed_metrics"),
-                                                                
SchemaChange.setOption(
-                                                                        
"fields.renamed_metrics.map.storage-layout",
-                                                                        
"shared-shredding")))))
-                .isInstanceOf(UnsupportedOperationException.class)
-                .hasMessageContaining(
-                        "Cannot change map storage layout for field id 1 
('metrics' -> 'renamed_metrics') from 'default' to 'shared-shredding'.");
+        retryArtificialException(
+                () ->
+                        manager.commitChanges(
+                                Arrays.asList(
+                                        SchemaChange.renameColumn("metrics", 
"renamed_metrics"),
+                                        SchemaChange.setOption(
+                                                
"fields.renamed_metrics.map.storage-layout",
+                                                "shared-shredding"))));
+
+        Optional<TableSchema> latest = retryArtificialException(() -> 
manager.latest());
+        assertThat(latest).isPresent();
+        assertThat(latest.get().fields().get(1).id()).isEqualTo(1);
+        
assertThat(latest.get().fields().get(1).name()).isEqualTo("renamed_metrics");
+        assertThat(latest.get().options())
+                .doesNotContainKey("fields.metrics.map.storage-layout")
+                .containsEntry("fields.renamed_metrics.map.storage-layout", 
"shared-shredding");
     }
 
     @ParameterizedTest
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 c300bb4a70..2a7adce1c4 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
@@ -76,7 +76,6 @@ import java.util.stream.Stream;
 
 import static 
org.apache.paimon.deletionvectors.DeletionVectorsIndexFile.DELETION_VECTORS_INDEX;
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 /** Table-level tests for MAP shared-shredding. */
 public class MapSharedShreddingTableTest extends TableTestBase {
@@ -248,6 +247,45 @@ public class MapSharedShreddingTableTest extends 
TableTestBase {
         assertThat(actual).containsExactly(Arrays.asList(10L, null, null));
     }
 
+    @ParameterizedTest
+    @ValueSource(strings = {"orc", "parquet"})
+    public void 
testReadSelectedKeysAfterDefaultToSharedAndValueTypeEvolution(String format)
+            throws Exception {
+        catalog.createTable(
+                identifier(format),
+                Schema.newBuilder()
+                        .column("id", DataTypes.INT())
+                        .column(
+                                "metrics",
+                                DataTypes.MAP(DataTypes.STRING().notNull(), 
DataTypes.INT()))
+                        .option("bucket", "-1")
+                        .option("file.format", format)
+                        .option(CoreOptions.WRITE_ONLY.key(), "true")
+                        .build(),
+                true);
+        Table table = catalog.getTable(identifier(format));
+        Map<BinaryString, Integer> oldValues = new LinkedHashMap<>();
+        oldValues.put(BinaryString.fromString("key1"), 10);
+        write(table, GenericRow.of(1, new GenericMap(oldValues)));
+
+        catalog.alterTable(
+                identifier(format),
+                Arrays.asList(
+                        SchemaChange.updateColumnType(
+                                new String[] {"metrics", "value"}, 
DataTypes.BIGINT(), false),
+                        SchemaChange.setOption(
+                                "fields.metrics.map.storage-layout", 
"shared-shredding"),
+                        SchemaChange.setOption(
+                                
"fields.metrics.map.shared-shredding.max-columns", "1")),
+                false);
+        table = catalog.getTable(identifier(format));
+        write(table, GenericRow.of(2, mapOf("key2", 20L)));
+
+        assertThat(readSelectedKeysById(table))
+                .containsEntry(1, Arrays.asList(10L, null, null))
+                .containsEntry(2, Arrays.asList(null, 20L, null));
+    }
+
     @ParameterizedTest
     @ValueSource(strings = {"orc", "parquet"})
     public void testReadSelectedKeysAfterRenameColumn(String format) throws 
Exception {
@@ -285,6 +323,36 @@ public class MapSharedShreddingTableTest extends 
TableTestBase {
                 .containsEntry(2, Arrays.asList(null, 30L, null));
     }
 
+    @ParameterizedTest
+    @ValueSource(strings = {"orc", "parquet"})
+    public void testReadSelectedKeysAfterDefaultToSharedAndRenameColumn(String 
format)
+            throws Exception {
+        Table table =
+                createTableWithBucket(
+                        format,
+                        1,
+                        "1",
+                        Collections.singletonList("metrics"),
+                        Collections.emptyList());
+        write(table, GenericRow.of(1, mapOf("key1", 10L, "key2", 20L)));
+
+        catalog.alterTable(
+                identifier(format),
+                Arrays.asList(
+                        SchemaChange.renameColumn("metrics", 
"renamed_metrics"),
+                        SchemaChange.setOption(
+                                "fields.renamed_metrics.map.storage-layout", 
"shared-shredding"),
+                        SchemaChange.setOption(
+                                
"fields.renamed_metrics.map.shared-shredding.max-columns", "1")),
+                false);
+        table = catalog.getTable(identifier(format));
+        write(table, GenericRow.of(2, mapOf("key2", 30L)));
+
+        assertThat(readSelectedKeysById(table, "renamed_metrics"))
+                .containsEntry(1, Arrays.asList(10L, 20L, null))
+                .containsEntry(2, Arrays.asList(null, 30L, null));
+    }
+
     @ParameterizedTest
     @ValueSource(strings = {"orc", "parquet"})
     public void testAppendOnlyTableReadWriteWithTwoMapFields(String format) 
throws Exception {
@@ -857,29 +925,89 @@ public class MapSharedShreddingTableTest extends 
TableTestBase {
 
     @ParameterizedTest
     @ValueSource(strings = {"orc", "parquet"})
-    public void 
testCannotSwitchMapLayoutAndUseMaxColumnsWithoutMetadata(String format)
-            throws Exception {
-        createTableWithBucket(
-                format, 4, "1", Arrays.asList("metrics", "labels"), 
Arrays.asList("labels"));
+    public void testSwitchMapLayoutAndInferColumns(String format) throws 
Exception {
+        Table table =
+                createTableWithBucket(
+                        format,
+                        4,
+                        "1",
+                        Arrays.asList("metrics", "labels"),
+                        Arrays.asList("labels"));
 
-        assertThatThrownBy(
-                        () ->
-                                catalog.alterTable(
-                                        identifier(format),
-                                        Arrays.asList(
-                                                SchemaChange.setOption(
-                                                        
"fields.metrics.map.storage-layout",
-                                                        "shared-shredding"),
-                                                SchemaChange.setOption(
-                                                        
"fields.metrics.map.shared-shredding.max-columns",
-                                                        "3"),
-                                                SchemaChange.setOption(
-                                                        
"fields.labels.map.storage-layout",
-                                                        "default")),
-                                        false))
-                .isInstanceOf(UnsupportedOperationException.class)
-                .hasMessageContaining(
-                        "Cannot change map storage layout for field id 1 
('metrics' -> 'metrics') from 'default' to 'shared-shredding'.");
+        write(table, GenericRow.of(1, mapOf("a", 11L, "b", 12L), mapOf("x", 
21L)));
+
+        catalog.alterTable(
+                identifier(format),
+                Arrays.asList(
+                        SchemaChange.setOption(
+                                "fields.metrics.map.storage-layout", 
"shared-shredding"),
+                        SchemaChange.setOption(
+                                
"fields.metrics.map.shared-shredding.max-columns", "3"),
+                        
SchemaChange.setOption("fields.labels.map.storage-layout", "default")),
+                false);
+        table = catalog.getTable(identifier(format));
+
+        write(table, GenericRow.of(2, mapOf("c", 31L), mapOf("y", 41L, "z", 
42L)));
+
+        FileStoreTable fileStoreTable = (FileStoreTable) table;
+        List<DataFileWithSplit> files = currentDataFiles(fileStoreTable);
+        files.sort(Comparator.comparingLong(file -> 
file.dataFile.minSequenceNumber()));
+        assertThat(files).hasSize(2);
+
+        MapSharedShreddingFieldMeta metricsMeta =
+                readSharedShreddingFieldMeta(fileStoreTable, files.get(1), 
"metrics");
+        assertThat(metricsMeta.numColumns()).isEqualTo(3);
+        assertThat(metricsMeta.maxRowWidth()).isEqualTo(1);
+
+        Map<Integer, List<Map<String, Long>>> actual = new LinkedHashMap<>();
+        for (InternalRow row : read(table)) {
+            actual.put(
+                    row.getInt(0),
+                    Arrays.asList(
+                            row.isNullAt(1) ? null : toJavaMap(row.getMap(1)),
+                            row.isNullAt(2) ? null : 
toJavaMap(row.getMap(2))));
+        }
+
+        assertThat(actual)
+                .containsEntry(1, Arrays.asList(javaMapOf("a", 11L, "b", 12L), 
javaMapOf("x", 21L)))
+                .containsEntry(
+                        2, Arrays.asList(javaMapOf("c", 31L), javaMapOf("y", 
41L, "z", 42L)));
+    }
+
+    @ParameterizedTest
+    @ValueSource(strings = {"orc", "parquet"})
+    public void testSwitchSharedShreddingToDefaultMap(String format) throws 
Exception {
+        Table table =
+                createTableWithBucket(
+                        format,
+                        1,
+                        "1",
+                        Collections.singletonList("metrics"),
+                        Collections.singletonList("metrics"));
+        write(table, GenericRow.of(1, mapOf("key1", 10L, "key2", 20L)));
+
+        catalog.alterTable(
+                identifier(format),
+                Collections.singletonList(
+                        
SchemaChange.setOption("fields.metrics.map.storage-layout", "default")),
+                false);
+        table = catalog.getTable(identifier(format));
+        write(table, GenericRow.of(2, mapOf("key2", 30L, "cold", 40L)));
+
+        assertThat(readMapsById(table.newReadBuilder()))
+                .containsOnlyKeys(1, 2)
+                .containsEntry(1, javaMapOf("key1", 10L, "key2", 20L))
+                .containsEntry(2, javaMapOf("key2", 30L, "cold", 40L));
+        assertThat(countSharedShreddingFiles((FileStoreTable) 
table)).isEqualTo(1);
+
+        table = enableForcedCompaction(table);
+        compact(table, BinaryRow.EMPTY_ROW, 0);
+        assertThat(countSharedShreddingFiles((FileStoreTable) table)).isZero();
+        assertThat(currentDataFiles((FileStoreTable) table)).hasSize(1);
+        assertThat(readMapsById(table.newReadBuilder()))
+                .containsOnlyKeys(1, 2)
+                .containsEntry(1, javaMapOf("key1", 10L, "key2", 20L))
+                .containsEntry(2, javaMapOf("key2", 30L, "cold", 40L));
     }
 
     @ParameterizedTest
@@ -1099,6 +1227,13 @@ public class MapSharedShreddingTableTest extends 
TableTestBase {
         return createTable(format, 2, sharedShreddingFields);
     }
 
+    private Table enableForcedCompaction(Table table) {
+        Map<String, String> options = new LinkedHashMap<>();
+        options.put(CoreOptions.WRITE_ONLY.key(), "false");
+        options.put(CoreOptions.COMPACTION_FORCE_REWRITE_ALL_FILES.key(), 
"true");
+        return table.copy(options);
+    }
+
     private Table createTable(String format, int maxColumns, String... 
sharedShreddingFields)
             throws Exception {
         return createTableWithBucket(format, maxColumns, "-1", 
sharedShreddingFields);
@@ -1537,21 +1672,65 @@ public class MapSharedShreddingTableTest extends 
TableTestBase {
         return files;
     }
 
+    private int countSharedShreddingFiles(FileStoreTable table) throws 
Exception {
+        int count = 0;
+        for (DataFileWithSplit file : currentDataFiles(table)) {
+            Map<String, Map<String, String>> fieldMetadata = 
readFieldMetadata(table, file);
+            if 
(MapSharedShreddingUtils.hasShreddingMetadata(fieldMetadata.get("metrics"))) {
+                count++;
+            }
+        }
+        return count;
+    }
+
     private MapSharedShreddingFieldMeta readSharedShreddingFieldMeta(
             FileStoreTable table, DataFileWithSplit file, String fieldName) 
throws Exception {
+        return MapSharedShreddingUtils.deserializeMetadata(
+                readFieldMetadata(table, file).get(fieldName));
+    }
+
+    private Map<String, Map<String, String>> readFieldMetadata(
+            FileStoreTable table, DataFileWithSplit file) throws Exception {
         DataFilePathFactory pathFactory =
                 
table.store().pathFactory().createDataFilePathFactory(file.partition, 
file.bucket);
         FileFormat fileFormat =
                 FileFormatDiscover.of(new CoreOptions(table.options()))
                         .discover(file.dataFile.fileFormat());
-        Map<String, Map<String, String>> fieldMetadata =
-                ((SupportsFieldMetadata) fileFormat)
-                        .readFieldMetadata(
-                                new FormatReaderContext(
-                                        table.fileIO(),
-                                        pathFactory.toPath(file.dataFile),
-                                        file.dataFile.fileSize()));
-        return 
MapSharedShreddingUtils.deserializeMetadata(fieldMetadata.get(fieldName));
+        return ((SupportsFieldMetadata) fileFormat)
+                .readFieldMetadata(
+                        new FormatReaderContext(
+                                table.fileIO(),
+                                pathFactory.toPath(file.dataFile),
+                                file.dataFile.fileSize()));
+    }
+
+    private Map<Integer, List<Long>> readSelectedKeysById(Table table) throws 
Exception {
+        return readSelectedKeysById(table, "metrics");
+    }
+
+    private Map<Integer, List<Long>> readSelectedKeysById(Table table, String 
fieldName)
+            throws Exception {
+        ReadBuilder readBuilder =
+                
table.newReadBuilder().withReadType(selectedKeysReadType(fieldName));
+        Map<Integer, List<Long>> actual = new LinkedHashMap<>();
+        try (RecordReader<InternalRow> reader =
+                
readBuilder.newRead().createReader(readBuilder.newScan().plan())) {
+            reader.forEachRemaining(
+                    row -> {
+                        if (row.isNullAt(1)) {
+                            actual.put(row.getInt(0), null);
+                            return;
+                        }
+                        InternalRow selectedKeys = row.getRow(1, 3);
+                        actual.put(
+                                row.getInt(0),
+                                Arrays.asList(
+                                        selectedKeys.isNullAt(0) ? null : 
selectedKeys.getLong(0),
+                                        selectedKeys.isNullAt(1) ? null : 
selectedKeys.getLong(1),
+                                        selectedKeys.isNullAt(2) ? null : 
selectedKeys.getLong(2)));
+                    });
+        }
+        return actual;
     }
 
     private GenericMap mapOf(Object... entries) {
diff --git 
a/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/MapSelectedKeysSharedShreddingE2ETestBase.scala
 
b/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/MapSelectedKeysSharedShreddingE2ETestBase.scala
index f2351ac965..fa3f49b77c 100644
--- 
a/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/MapSelectedKeysSharedShreddingE2ETestBase.scala
+++ 
b/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/MapSelectedKeysSharedShreddingE2ETestBase.scala
@@ -64,6 +64,88 @@ abstract class MapSelectedKeysSharedShreddingE2ETestBase 
extends PaimonSparkTest
         }
       }
 
+      test(s"switch map storage layout in both directions for $format") {
+        withTable("T") {
+          sql(s"""
+                 |CREATE TABLE T (id INT, attrs MAP<STRING, BIGINT>)
+                 |TBLPROPERTIES (
+                 |  'bucket' = '-1',
+                 |  'file.format' = '$format'
+                 |)
+                 |""".stripMargin)
+          sql("""
+                |INSERT INTO T VALUES
+                |  (1, map('key1', CAST(10 AS BIGINT), 'key2', CAST(20 AS 
BIGINT)))
+                |""".stripMargin)
+
+          sql("""
+                |ALTER TABLE T SET TBLPROPERTIES (
+                |  'fields.attrs.map.storage-layout' = 'shared-shredding',
+                |  'fields.attrs.map.shared-shredding.max-columns' = '1'
+                |)
+                |""".stripMargin)
+          sql("""
+                |INSERT INTO T VALUES
+                |  (2, map('key2', CAST(30 AS BIGINT), 'cold', CAST(40 AS 
BIGINT)))
+                |""".stripMargin)
+
+          val firstSharedQuery =
+            sql("SELECT id, attrs['key1'], attrs['key2'] FROM T ORDER BY id")
+          assert(
+            pushedMapSelectedKeys(firstSharedQuery).contains("attrs"),
+            s"Expected selected-key pushdown after switching $format to 
shared-shredding")
+          checkAnswer(firstSharedQuery, Row(1, 10L, 20L) :: Row(2, null, 30L) 
:: Nil)
+
+          sql("""
+                |ALTER TABLE T SET TBLPROPERTIES (
+                |  'fields.attrs.map.storage-layout' = 'default'
+                |)
+                |""".stripMargin)
+          sql("""
+                |INSERT INTO T VALUES
+                |  (3, map('key1', CAST(50 AS BIGINT)))
+                |""".stripMargin)
+
+          val defaultQuery =
+            sql("SELECT id, attrs['key1'], attrs['key2'] FROM T ORDER BY id")
+          assert(
+            pushedMapSelectedKeys(defaultQuery).isEmpty,
+            s"Expected selected-key pushdown to be disabled after switching 
$format to default")
+          checkAnswer(
+            defaultQuery,
+            Row(1, 10L, 20L) :: Row(2, null, 30L) :: Row(3, 50L, null) :: Nil)
+
+          sql("""
+                |ALTER TABLE T SET TBLPROPERTIES (
+                |  'fields.attrs.map.storage-layout' = 'shared-shredding'
+                |)
+                |""".stripMargin)
+          sql("""
+                |INSERT INTO T VALUES
+                |  (4, map('key2', CAST(60 AS BIGINT)))
+                |""".stripMargin)
+
+          val secondSharedQuery =
+            sql("SELECT id, attrs['key1'], attrs['key2'] FROM T ORDER BY id")
+          assert(
+            pushedMapSelectedKeys(secondSharedQuery).contains("attrs"),
+            s"Expected selected-key pushdown after switching $format back to 
shared-shredding")
+          checkAnswer(
+            secondSharedQuery,
+            Row(1, 10L, 20L) ::
+              Row(2, null, 30L) ::
+              Row(3, 50L, null) ::
+              Row(4, null, 60L) :: Nil)
+          checkAnswer(
+            sql("SELECT id, attrs FROM T ORDER BY id"),
+            Row(1, Map("key1" -> 10L, "key2" -> 20L)) ::
+              Row(2, Map("key2" -> 30L, "cold" -> 40L)) ::
+              Row(3, Map("key1" -> 50L)) ::
+              Row(4, Map("key2" -> 60L)) :: Nil
+          )
+        }
+      }
+
       Seq(false, true).foreach {
         thinMode =>
           test(s"skip selected-key pushdown for merge_map from $format with 
thin mode $thinMode") {
@@ -212,4 +294,15 @@ abstract class MapSelectedKeysSharedShreddingE2ETestBase 
extends PaimonSparkTest
       checkAnswer(query, Row(1, 10L, 30L) :: Row(2, 50L, null) :: Nil)
     }
   }
+
+  private def pushedMapSelectedKeys(
+      query: org.apache.spark.sql.DataFrame): Map[String, Seq[String]] = {
+    val sparkPlan = query.queryExecution.sparkPlan
+    sparkPlan
+      .collectFirst {
+        case scan: BatchScanExec if scan.scan.isInstanceOf[PaimonScan] =>
+          scan.scan.asInstanceOf[PaimonScan].pushedMapSelectedKeys
+      }
+      .getOrElse(fail(s"Expected a Paimon scan in physical plan:\n$sparkPlan"))
+  }
 }

Reply via email to