[core] Fix stale file index cleanup (#9085)
diff --git a/paimon-core/src/main/java/org/apache/paimon/index/FileIndexProcessor.java b/paimon-core/src/main/java/org/apache/paimon/index/FileIndexProcessor.java
index 84e48c5..db781f9 100644
--- a/paimon-core/src/main/java/org/apache/paimon/index/FileIndexProcessor.java
+++ b/paimon-core/src/main/java/org/apache/paimon/index/FileIndexProcessor.java
@@ -118,11 +118,11 @@
                 maintainers.remove(name);
             } else {
                 Map<String, byte[]> indexTypeBytes = maintainers.get(name);
-                for (String indexType : entry.getValue().keySet()) {
-                    if (!indexTypeBytes.containsKey(indexType)) {
-                        indexTypeBytes.remove(indexType);
-                    }
-                }
+                Set<String> configuredIndexTypes =
+                        schemaInfo.projectedIndexTypes.getOrDefault(name, Collections.emptySet());
+                indexTypeBytes
+                        .keySet()
+                        .removeIf(indexType -> !configuredIndexTypes.contains(indexType));
             }
         }
 
@@ -176,11 +176,11 @@
                 outputStream.write(baos.toByteArray());
             }
             extras.add(newIndexPath.getName());
-            return dataFileMeta.copy(extras);
+            return dataFileMeta.copy(extras).copy((byte[]) null);
         } else if (baos.size() == 0) {
-            return dataFileMeta.copy(extras);
+            return dataFileMeta.copy(extras).copy((byte[]) null);
         } else {
-            return dataFileMeta.copy(baos.toByteArray());
+            return dataFileMeta.copy(extras).copy(baos.toByteArray());
         }
     }
 
@@ -214,6 +214,7 @@
 
                 List<String> projectedColNames = new ArrayList<>();
                 Set<String> projectedColFullNames = new HashSet<>();
+                Map<String, Set<String>> projectedIndexTypes = new HashMap<>();
                 for (Map.Entry<FileIndexOptions.Column, Map<String, Options>> entry :
                         fileIndexOptions.entrySet()) {
                     FileIndexOptions.Column column = entry.getKey();
@@ -234,6 +235,9 @@
                                             columnName, column.getNestedColumnName())
                                     : column.getColumnName();
                     projectedColFullNames.add(fullColumnName);
+                    projectedIndexTypes
+                            .computeIfAbsent(fullColumnName, ignored -> new HashSet<>())
+                            .addAll(entry.getValue().keySet());
                 }
 
                 schemaInfos.put(
@@ -244,7 +248,8 @@
                                 projectedColNames.stream()
                                         .mapToInt(fileSchema::getFieldIndex)
                                         .toArray(),
-                                projectedColFullNames));
+                                projectedColFullNames,
+                                projectedIndexTypes));
                 fileSchemaIds.add(schemaId);
             }
 
@@ -276,16 +281,19 @@
         private final Map<String, String> colNameMapping;
         private final int[] projectedIndexCols;
         private final Set<String> projectedColFullNames;
+        private final Map<String, Set<String>> projectedIndexTypes;
 
         private SchemaInfo(
                 RowType fileSchema,
                 Map<String, String> colNameMapping,
                 int[] projectedIndexCols,
-                Set<String> projectedColFullNames) {
+                Set<String> projectedColFullNames,
+                Map<String, Set<String>> projectedIndexTypes) {
             this.fileSchema = fileSchema;
             this.colNameMapping = colNameMapping;
             this.projectedIndexCols = projectedIndexCols;
             this.projectedColFullNames = projectedColFullNames;
+            this.projectedIndexTypes = projectedIndexTypes;
         }
     }
 }
diff --git a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/procedure/RewriteFileIndexProcedureITCase.java b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/procedure/RewriteFileIndexProcedureITCase.java
index fe62eb3..ad83304 100644
--- a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/procedure/RewriteFileIndexProcedureITCase.java
+++ b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/procedure/RewriteFileIndexProcedureITCase.java
@@ -18,11 +18,13 @@
 
 package org.apache.paimon.flink.procedure;
 
+import org.apache.paimon.catalog.Identifier;
 import org.apache.paimon.data.BinaryString;
 import org.apache.paimon.data.InternalRow;
 import org.apache.paimon.fileindex.FileIndexFormat;
 import org.apache.paimon.fileindex.FileIndexReader;
 import org.apache.paimon.flink.CatalogITCaseBase;
+import org.apache.paimon.fs.ByteArraySeekableStream;
 import org.apache.paimon.fs.Path;
 import org.apache.paimon.io.DataFilePathFactory;
 import org.apache.paimon.manifest.ManifestEntry;
@@ -37,6 +39,7 @@
 import org.junit.jupiter.params.provider.ValueSource;
 
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
@@ -158,6 +161,130 @@
 
     @ParameterizedTest
     @ValueSource(booleans = {true, false})
+    public void testFileIndexProcedureSwitchIndexType(boolean isNamedArgument) throws Exception {
+        sql(
+                "CREATE TABLE T ("
+                        + " k INT,"
+                        + " v STRING,"
+                        + " dt STRING"
+                        + ") PARTITIONED BY (dt) WITH ("
+                        + " 'write-only' = 'true',"
+                        + " 'file-index.bloom-filter.columns' = 'k',"
+                        + " 'file-index.in-manifest-threshold' = '1 MB',"
+                        + " 'bucket' = '-1'"
+                        + ")");
+        sql("INSERT INTO T VALUES (1, '100', '20221208')");
+
+        tEnv.getConfig().set(TableConfigOptions.TABLE_DML_SYNC, true);
+        if (isNamedArgument) {
+            sql("CALL sys.rewrite_file_index(`table` => 'default.T')");
+        } else {
+            sql("CALL sys.rewrite_file_index('default.T')");
+        }
+        assertFileIndexTypes("T", "bloom-filter", true);
+
+        sql("ALTER TABLE T SET ('file-index.in-manifest-threshold' = '1 B')");
+        sql("ALTER TABLE T RESET ('file-index.bloom-filter.columns')");
+        sql("ALTER TABLE T SET ('file-index.bitmap.columns' = 'k')");
+        if (isNamedArgument) {
+            sql("CALL sys.rewrite_file_index(`table` => 'default.T')");
+        } else {
+            sql("CALL sys.rewrite_file_index('default.T')");
+        }
+        assertFileIndexTypes("T", "bitmap", false);
+    }
+
+    @ParameterizedTest
+    @ValueSource(booleans = {true, false})
+    public void testFileIndexProcedureDropEmbeddedIndex(boolean isNamedArgument) throws Exception {
+        sql(
+                "CREATE TABLE T ("
+                        + " k INT,"
+                        + " dt STRING"
+                        + ") PARTITIONED BY (dt) WITH ("
+                        + " 'write-only' = 'true',"
+                        + " 'file-index.bloom-filter.columns' = 'k',"
+                        + " 'file-index.in-manifest-threshold' = '1 MB',"
+                        + " 'bucket' = '-1'"
+                        + ")");
+        sql("INSERT INTO T VALUES (1, '20221208')");
+
+        tEnv.getConfig().set(TableConfigOptions.TABLE_DML_SYNC, true);
+        if (isNamedArgument) {
+            sql("CALL sys.rewrite_file_index(`table` => 'default.T')");
+        } else {
+            sql("CALL sys.rewrite_file_index('default.T')");
+        }
+        assertFileIndexTypes("T", "bloom-filter", true);
+
+        sql("ALTER TABLE T RESET ('file-index.bloom-filter.columns')");
+        if (isNamedArgument) {
+            sql("CALL sys.rewrite_file_index(`table` => 'default.T')");
+        } else {
+            sql("CALL sys.rewrite_file_index('default.T')");
+        }
+        assertNoFileIndexes("T");
+    }
+
+    private void assertFileIndexTypes(
+            String tableName, String expectedIndexType, boolean expectedEmbeddedIndex)
+            throws Exception {
+        flinkCatalog()
+                .catalog()
+                .invalidateTable(Identifier.create(tEnv.getCurrentDatabase(), tableName));
+        FileStoreTable table = paimonTable(tableName);
+        for (ManifestEntry entry : table.store().newScan().plan().files()) {
+            byte[] embeddedIndex = entry.file().embeddedIndex();
+            FileIndexFormat.Reader reader;
+            if (expectedEmbeddedIndex) {
+                Assertions.assertThat(embeddedIndex).isNotNull();
+                Assertions.assertThat(entry.file().extraFiles())
+                        .noneMatch(s -> s.endsWith(DataFilePathFactory.INDEX_PATH_SUFFIX));
+                reader =
+                        FileIndexFormat.createReader(
+                                new ByteArraySeekableStream(embeddedIndex), table.rowType());
+            } else {
+                Assertions.assertThat(embeddedIndex).isNull();
+                String indexFile =
+                        entry.file().extraFiles().stream()
+                                .filter(s -> s.endsWith(DataFilePathFactory.INDEX_PATH_SUFFIX))
+                                .findFirst()
+                                .orElseThrow(
+                                        () ->
+                                                new AssertionError(
+                                                        "Missing file index for "
+                                                                + entry.file().fileName()));
+                Path indexFilePath =
+                        table.store()
+                                .pathFactory()
+                                .createDataFilePathFactory(entry.partition(), entry.bucket())
+                                .toAlignedPath(indexFile, entry.file());
+                reader =
+                        FileIndexFormat.createReader(
+                                table.fileIO().newInputStream(indexFilePath), table.rowType());
+            }
+            try (FileIndexFormat.Reader indexReader = reader) {
+                Map<String, Map<String, byte[]>> indexes = indexReader.readAll();
+                Assertions.assertThat(indexes).containsKey("k");
+                Assertions.assertThat(indexes.get("k").keySet()).containsExactly(expectedIndexType);
+            }
+        }
+    }
+
+    private void assertNoFileIndexes(String tableName) throws Exception {
+        flinkCatalog()
+                .catalog()
+                .invalidateTable(Identifier.create(tEnv.getCurrentDatabase(), tableName));
+        FileStoreTable table = paimonTable(tableName);
+        for (ManifestEntry entry : table.store().newScan().plan().files()) {
+            Assertions.assertThat(entry.file().embeddedIndex()).isNull();
+            Assertions.assertThat(entry.file().extraFiles())
+                    .noneMatch(s -> s.endsWith(DataFilePathFactory.INDEX_PATH_SUFFIX));
+        }
+    }
+
+    @ParameterizedTest
+    @ValueSource(booleans = {true, false})
     public void testFileIndexProcedureDropIndex(boolean isNamedArgument) throws Exception {
         sql(
                 "CREATE TABLE T ("