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 7f4134c2a7 [global-index] Reuse sorted build for bitmap indexes (#8283)
7f4134c2a7 is described below

commit 7f4134c2a77e9ba4e61a483676f9b8b1bd3606c5
Author: Jingsong Lee <[email protected]>
AuthorDate: Fri Jun 19 11:19:39 2026 +0800

    [global-index] Reuse sorted build for bitmap indexes (#8283)
    
    This PR generalizes the BTree global-index build path into a
    sorted-index build path and reuses it for Bitmap global indexes. It also
    exposes Bitmap global-index creation through Flink and Spark procedures
    and updates the related documentation.
---
 docs/docs/flink/procedures.md                      |   8 +-
 docs/docs/multimodal-table/global-index.mdx        |   6 +-
 docs/docs/multimodal-table/global-index/bitmap.mdx |   8 ++
 docs/docs/multimodal-table/global-index/btree.mdx  |   7 +-
 docs/docs/multimodal-table/index.mdx               |   2 +-
 docs/docs/spark/procedures.md                      |   3 +-
 .../globalindex/sorted/SortedIndexOptions.java     |  41 ++++++++
 .../globalindex/sorted/SortedIndexOptionsTest.java |  45 +++++++++
 .../SortedGlobalIndexBuilder.java}                 |  36 ++++---
 .../test/java/org/apache/paimon/JavaPyE2ETest.java |  11 ++-
 .../DataEvolutionRowIdReassignerTest.java          |   7 +-
 .../SortedGlobalIndexBuilderSplitTest.java}        |  15 +--
 .../SortedGlobalIndexBuilderTest.java}             |  22 +++--
 .../metastore/VisibilityWaitCallbackTest.java      |  13 +--
 .../paimon/table/BtreeGlobalIndexTableTest.java    |   6 +-
 .../SortedIndexTopoBuilder.java}                   | 104 +++++++++++----------
 .../procedure/CreateGlobalIndexProcedure.java      |  26 ++++--
 ...dexITCase.java => SortedGlobalIndexITCase.java} |  37 +++++++-
 .../SortedIndexTopoBuilderTest.java}               |  32 +++----
 .../procedure/CreateGlobalIndexProcedureTest.java  |   5 +-
 .../globalindex/DefaultGlobalIndexTopoBuilder.java |   5 -
 .../globalindex/GlobalIndexTopologyBuilder.java    |   6 +-
 .../GlobalIndexTopologyBuilderUtils.java           |  40 ++------
 .../SortedIndexTopoBuilder.java}                   |  42 +++++----
 ...on.spark.globalindex.GlobalIndexTopologyBuilder |  16 ----
 .../procedure/CreateGlobalIndexProcedureTest.java  |   5 +-
 .../procedure/CreateGlobalIndexProcedureTest.scala |  38 ++++++++
 27 files changed, 378 insertions(+), 208 deletions(-)

diff --git a/docs/docs/flink/procedures.md b/docs/docs/flink/procedures.md
index 0dbf8f0c1b..a59306df18 100644
--- a/docs/docs/flink/procedures.md
+++ b/docs/docs/flink/procedures.md
@@ -1004,7 +1004,7 @@ All available procedures are listed below.
          To create a global index on a table for accelerating queries. 
Arguments:
             <li>table(required): the target table identifier.</li>
             <li>index_column(required): the column name to build index on.</li>
-            <li>index_type(required): the type of global index, supported 
types include 'btree', 'ivf-flat', 'ivf-pq', 'ivf-hnsw-flat', 'ivf-hnsw-sq', 
'tantivy-fulltext'.</li>
+            <li>index_type(required): the type of global index, supported 
types include 'btree', 'bitmap', 'ivf-flat', 'ivf-pq', 'ivf-hnsw-flat', 
'ivf-hnsw-sq', 'tantivy-fulltext'.</li>
             <li>partitions(optional): partition filter for selective index 
creation.</li>
             <li>options(optional): additional dynamic options for index 
creation.</li>
       </td>
@@ -1014,6 +1014,12 @@ All available procedures are listed below.
             `table` => 'default.T',<br/>
             `index_column` => 'name',<br/>
             `index_type` => 'btree')<br/><br/>
+         -- Create bitmap index<br/>
+         CALL sys.create_global_index(<br/>
+            `table` => 'default.T',<br/>
+            `index_column` => 'tag',<br/>
+            `index_type` => 'bitmap',<br/>
+            `options` => 'sorted-index.records-per-range=1000000')<br/><br/>
          -- Create index for specific partitions<br/>
          CALL sys.create_global_index(<br/>
             `table` => 'default.T',<br/>
diff --git a/docs/docs/multimodal-table/global-index.mdx 
b/docs/docs/multimodal-table/global-index.mdx
index d7b0041b71..90a8ac33bc 100644
--- a/docs/docs/multimodal-table/global-index.mdx
+++ b/docs/docs/multimodal-table/global-index.mdx
@@ -148,9 +148,11 @@ These table options affect global index build and read 
behavior:
 |---|---|---|
 | `global-index.enabled` | `true` | Whether scans can use global indexes. |
 | `global-index.external-path` | Not set | Root directory for global index 
files. If not set, files are stored under the table index directory. |
-| `global-index.row-count-per-shard` | `100000` | Target row count per shard 
for non-BTree global index builds. |
+| `sorted-index.records-per-range` | `10000000` | Expected number of records 
per sorted global index file for BTree and Bitmap builds. |
+| `sorted-index.build.max-parallelism` | `4096` | Maximum Flink or Spark 
parallelism for building sorted global indexes. |
+| `global-index.row-count-per-shard` | `100000` | Target row count per shard 
for non-sorted global index builds such as vector and full-text indexes. |
 | `global-index.build.max-shard` | `32` | Preferred maximum shard count for 
global index builds. |
-| `global-index.build.max-parallelism` | `4096` | Maximum Flink or Spark 
parallelism for building global indexes. |
+| `global-index.build.max-parallelism` | `4096` | Maximum Flink or Spark 
parallelism for building non-sorted global indexes. |
 | `global-index.thread-num` | `32` | Maximum number of concurrent threads for 
global index I/O. |
 | `visibility-callback.enabled` | `false` | Whether batch or bounded-stream 
commits wait until existing global indexes cover newly added files. |
 | `visibility-callback.timeout` | `30 min` | Maximum wait time for visibility 
callback. |
diff --git a/docs/docs/multimodal-table/global-index/bitmap.mdx 
b/docs/docs/multimodal-table/global-index/bitmap.mdx
index b9715687d5..453d8dbf00 100644
--- a/docs/docs/multimodal-table/global-index/bitmap.mdx
+++ b/docs/docs/multimodal-table/global-index/bitmap.mdx
@@ -76,10 +76,18 @@ CALL sys.create_global_index(
 );
 ```
 
+Bitmap indexes share the sorted index build path with BTree indexes. Use
+`sorted-index.records-per-range` to control the expected records per generated 
index
+file, and `sorted-index.build.max-parallelism` to cap Flink or Spark build
+parallelism. The legacy `btree-index.records-per-range` and
+`btree-index.build.max-parallelism` keys are still recognized as fallback keys.
+
 ## Bitmap Options
 
 | Option | Default | Description |
 |---|---|---|
+| `sorted-index.records-per-range` | `10000000` | Expected number of records 
per sorted global index file for BTree and Bitmap builds. |
+| `sorted-index.build.max-parallelism` | `4096` | Maximum Flink or Spark 
parallelism for building sorted global indexes. |
 | `bitmap-index.dictionary-block-size` | `16 kb` | Target size of dictionary 
blocks in bitmap global index files. Smaller blocks reduce dictionary read 
amplification for high-cardinality columns; larger blocks reduce dictionary 
block index size. |
 | `bitmap-index.compression` | `none` | Compression algorithm for bitmap 
dictionary blocks and the dictionary block index. Supported values are the same 
block codecs as BTree index, such as `none`, `lz4`, `lzo`, and `zstd`. |
 | `bitmap-index.compression-level` | `1` | Compression level used by codecs 
that support levels, such as `zstd`. |
diff --git a/docs/docs/multimodal-table/global-index/btree.mdx 
b/docs/docs/multimodal-table/global-index/btree.mdx
index 236447c987..ee50db0aed 100644
--- a/docs/docs/multimodal-table/global-index/btree.mdx
+++ b/docs/docs/multimodal-table/global-index/btree.mdx
@@ -72,13 +72,16 @@ CALL sys.create_global_index(
 
 | Option | Default | Description |
 |---|---|---|
-| `btree-index.records-per-range` | `10000000` | Expected number of records 
per BTree index file. |
-| `btree-index.build.max-parallelism` | `4096` | Maximum Flink or Spark 
parallelism for building BTree indexes. |
+| `sorted-index.records-per-range` | `10000000` | Expected number of records 
per sorted global index file for BTree and Bitmap builds. |
+| `sorted-index.build.max-parallelism` | `4096` | Maximum Flink or Spark 
parallelism for building sorted global indexes. |
 | `btree-index.block-size` | `64 kb` | Block size used by BTree index files. |
 | `btree-index.cache-size` | `128 mb` | Cache size used by BTree index 
readers. |
 | `btree-index.fallback-scan-max-size` | `256 mb` | Maximum total size of 
candidate BTree global index files to allow fallback index scans. Set to `0 b` 
to disable fallback scans. |
 | `btree-index.compression` | `none` | Compression algorithm used by BTree 
index blocks. |
 
+The legacy `btree-index.records-per-range` and
+`btree-index.build.max-parallelism` keys are still recognized as fallback keys.
+
 ## Query with BTree Index
 
 Once a BTree index is built, it is automatically used during scan when a 
filter predicate matches the indexed column.
diff --git a/docs/docs/multimodal-table/index.mdx 
b/docs/docs/multimodal-table/index.mdx
index f0a4b29750..01a0e65f25 100644
--- a/docs/docs/multimodal-table/index.mdx
+++ b/docs/docs/multimodal-table/index.mdx
@@ -37,7 +37,7 @@ Key capabilities:
 - **[Data Evolution](./data-evolution)**: Update partial columns without 
rewriting entire files, enabling efficient schema evolution.
 - **[Blob Storage](./blob)**: Store large binary objects (images, videos, 
audio) in dedicated `.blob` files with efficient column projection.
 - **[Vector Storage](./vector)**: Store and manage vector embeddings in 
dedicated Vortex-format files optimized for vector workloads.
-- **[Global Index](./global-index)**: Build BTree, vector, and full-text 
(Tantivy) indexes for efficient lookups and similarity search.
+- **[Global Index](./global-index)**: Build BTree, Bitmap, vector, and 
full-text (Tantivy) indexes for efficient lookups and similarity search.
 
 All multimodal features require the following table properties:
 
diff --git a/docs/docs/spark/procedures.md b/docs/docs/spark/procedures.md
index d693346e00..0fd5f733b6 100644
--- a/docs/docs/spark/procedures.md
+++ b/docs/docs/spark/procedures.md
@@ -517,12 +517,13 @@ This section introduce all available spark procedures 
about paimon.
          To create global index files for a given column. The table must have 
<code>row-tracking.enabled=true</code>. Arguments:
             <li>table: the target table identifier. Cannot be empty.</li>
             <li>index_column: the name of the column to index. Cannot be 
empty.</li>
-            <li>index_type: type of the index to build, e.g. 'btree'. Cannot 
be empty.</li>
+            <li>index_type: type of the index to build, e.g. 'btree' or 
'bitmap'. Cannot be empty.</li>
             <li>partitions: partition filter to limit the partitions on which 
to build the index. The comma (",") represents "AND", the semicolon (";") 
represents "OR". Left empty for all partitions.</li>
             <li>options: additional dynamic options of the table. It 
prioritizes higher than original `tableProp` and lower than `procedureArg`.</li>
       </td>
       <td>
          CALL sys.create_global_index(table => 'default.T', index_column => 
'name', index_type => 'btree')<br/><br/>
+         CALL sys.create_global_index(table => 'default.T', index_column => 
'tag', index_type => 'bitmap', options => 
'sorted-index.records-per-range=1000000')<br/><br/>
          CALL sys.create_global_index(table => 'default.T', index_column => 
'name', index_type => 'btree', partitions => 'pt=p1;pt=p2')<br/><br/>
          CALL sys.create_global_index(table => 'default.T', index_column => 
'content', index_type => 'tantivy-fulltext', options => 
'tantivy.tokenizer=ngram,tantivy.ngram.min-gram=2,tantivy.ngram.max-gram=2')<br/><br/>
          CALL sys.create_global_index(table => 'default.T', index_column => 
'content', index_type => 'tantivy-fulltext', options => 
'tantivy.tokenizer=jieba')<br/><br/>
diff --git 
a/paimon-common/src/main/java/org/apache/paimon/globalindex/sorted/SortedIndexOptions.java
 
b/paimon-common/src/main/java/org/apache/paimon/globalindex/sorted/SortedIndexOptions.java
new file mode 100644
index 0000000000..4c437d890a
--- /dev/null
+++ 
b/paimon-common/src/main/java/org/apache/paimon/globalindex/sorted/SortedIndexOptions.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.globalindex.sorted;
+
+import org.apache.paimon.options.ConfigOption;
+import org.apache.paimon.options.ConfigOptions;
+
+/** Options for sorted global index build. */
+public class SortedIndexOptions {
+
+    public static final ConfigOption<Long> SORTED_INDEX_RECORDS_PER_RANGE =
+            ConfigOptions.key("sorted-index.records-per-range")
+                    .longType()
+                    .defaultValue(10_000_000L)
+                    .withFallbackKeys("btree-index.records-per-range")
+                    .withDescription("The expected number of records per 
sorted index file.");
+
+    public static final ConfigOption<Integer> 
SORTED_INDEX_BUILD_MAX_PARALLELISM =
+            ConfigOptions.key("sorted-index.build.max-parallelism")
+                    .intType()
+                    .defaultValue(4096)
+                    .withFallbackKeys("btree-index.build.max-parallelism")
+                    .withDescription(
+                            "The max parallelism of Flink/Spark for building 
sorted indexes.");
+}
diff --git 
a/paimon-common/src/test/java/org/apache/paimon/globalindex/sorted/SortedIndexOptionsTest.java
 
b/paimon-common/src/test/java/org/apache/paimon/globalindex/sorted/SortedIndexOptionsTest.java
new file mode 100644
index 0000000000..8ad1c55793
--- /dev/null
+++ 
b/paimon-common/src/test/java/org/apache/paimon/globalindex/sorted/SortedIndexOptionsTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.globalindex.sorted;
+
+import org.apache.paimon.options.Options;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/** Tests for {@link SortedIndexOptions}. */
+class SortedIndexOptionsTest {
+
+    @Test
+    void testDefaultRecordsPerRange() {
+        
assertThat(SortedIndexOptions.SORTED_INDEX_RECORDS_PER_RANGE.defaultValue())
+                .isEqualTo(10_000_000L);
+    }
+
+    @Test
+    void testBTreeBuildOptionFallbacks() {
+        Options options = new Options();
+        options.setString("btree-index.records-per-range", "100");
+        options.setString("btree-index.build.max-parallelism", "8");
+
+        
assertThat(options.get(SortedIndexOptions.SORTED_INDEX_RECORDS_PER_RANGE)).isEqualTo(100L);
+        
assertThat(options.get(SortedIndexOptions.SORTED_INDEX_BUILD_MAX_PARALLELISM)).isEqualTo(8);
+    }
+}
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/globalindex/btree/BTreeGlobalIndexBuilder.java
 
b/paimon-core/src/main/java/org/apache/paimon/globalindex/sorted/SortedGlobalIndexBuilder.java
similarity index 94%
rename from 
paimon-core/src/main/java/org/apache/paimon/globalindex/btree/BTreeGlobalIndexBuilder.java
rename to 
paimon-core/src/main/java/org/apache/paimon/globalindex/sorted/SortedGlobalIndexBuilder.java
index 0fcbbcbb43..e8ba93ffc8 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/globalindex/btree/BTreeGlobalIndexBuilder.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/globalindex/sorted/SortedGlobalIndexBuilder.java
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-package org.apache.paimon.globalindex.btree;
+package org.apache.paimon.globalindex.sorted;
 
 import org.apache.paimon.CoreOptions;
 import org.apache.paimon.Snapshot;
@@ -80,13 +80,12 @@ import static 
org.apache.paimon.globalindex.GlobalIndexBuilderUtils.toIndexFileM
 import static org.apache.paimon.types.VectorType.isVectorStoreFile;
 import static org.apache.paimon.utils.Preconditions.checkArgument;
 
-/** Builder to build btree global index. */
-public class BTreeGlobalIndexBuilder implements Serializable {
+/** Builder to build sorted global index. */
+public class SortedGlobalIndexBuilder implements Serializable {
 
     private static final long serialVersionUID = 1L;
     private static final double FLOATING = 1.2;
-    private static final String INDEX_TYPE = "btree";
-
+    private final String indexType;
     private final FileStoreTable table;
     private final RowType rowType;
     private final Options options;
@@ -100,15 +99,20 @@ public class BTreeGlobalIndexBuilder implements 
Serializable {
 
     @Nullable private PartitionPredicate partitionPredicate;
 
-    public BTreeGlobalIndexBuilder(Table table) {
+    public SortedGlobalIndexBuilder(Table table, String indexType) {
+        this(table, indexType, ((FileStoreTable) 
table).coreOptions().toConfiguration());
+    }
+
+    public SortedGlobalIndexBuilder(Table table, String indexType, Options 
options) {
+        this.indexType = indexType;
         this.table = (FileStoreTable) table;
         this.rowType = this.table.rowType();
-        this.options = this.table.coreOptions().toConfiguration();
+        this.options = options;
         this.recordsPerRange =
-                (long) 
(options.get(BTreeIndexOptions.BTREE_INDEX_RECORDS_PER_RANGE) * FLOATING);
+                (long) 
(options.get(SortedIndexOptions.SORTED_INDEX_RECORDS_PER_RANGE) * FLOATING);
     }
 
-    public BTreeGlobalIndexBuilder withIndexField(String indexField) {
+    public SortedGlobalIndexBuilder withIndexField(String indexField) {
         checkArgument(
                 rowType.containsField(indexField),
                 "Column '%s' does not exist in table '%s'.",
@@ -123,12 +127,12 @@ public class BTreeGlobalIndexBuilder implements 
Serializable {
         return this;
     }
 
-    public BTreeGlobalIndexBuilder withPartitionPredicate(PartitionPredicate 
partitionPredicate) {
+    public SortedGlobalIndexBuilder withPartitionPredicate(PartitionPredicate 
partitionPredicate) {
         this.partitionPredicate = partitionPredicate;
         return this;
     }
 
-    public BTreeGlobalIndexBuilder withSnapshot(Snapshot snapshot) {
+    public SortedGlobalIndexBuilder withSnapshot(Snapshot snapshot) {
         this.snapshot = snapshot;
         return this;
     }
@@ -192,7 +196,7 @@ public class BTreeGlobalIndexBuilder implements 
Serializable {
     private List<Range> indexedRowRanges(Snapshot snapshot) {
         List<Range> ranges = new ArrayList<>();
         for (IndexManifestEntry entry :
-                table.store().newIndexFileHandler().scan(snapshot, "btree")) {
+                table.store().newIndexFileHandler().scan(snapshot, indexType)) 
{
             if (partitionPredicate != null && 
!partitionPredicate.test(entry.partition())) {
                 continue;
             }
@@ -286,10 +290,12 @@ public class BTreeGlobalIndexBuilder implements 
Serializable {
 
     public GlobalIndexSingleColumnWriter createWriter() throws IOException {
         GlobalIndexSingleColumnWriter currentWriter;
-        GlobalIndexWriter indexWriter = createIndexWriter(table, INDEX_TYPE, 
indexField, options);
+        GlobalIndexWriter indexWriter = createIndexWriter(table, indexType, 
indexField, options);
         if (!(indexWriter instanceof GlobalIndexSingleColumnWriter)) {
             throw new RuntimeException(
-                    "Unexpected implementation, the index writer of BTree 
should be an instance of GlobalIndexSingleColumnWriter, but found: "
+                    "Unexpected implementation, the index writer of "
+                            + indexType
+                            + " should be an instance of 
GlobalIndexSingleColumnWriter, but found: "
                             + indexWriter.getClass().getName());
         }
         currentWriter = (GlobalIndexSingleColumnWriter) indexWriter;
@@ -306,7 +312,7 @@ public class BTreeGlobalIndexBuilder implements 
Serializable {
                         table.coreOptions(),
                         rowRange,
                         indexField.id(),
-                        INDEX_TYPE,
+                        indexType,
                         resultEntries);
         DataIncrement dataIncrement = 
DataIncrement.indexIncrement(indexFileMetas);
         return new CommitMessageImpl(
diff --git a/paimon-core/src/test/java/org/apache/paimon/JavaPyE2ETest.java 
b/paimon-core/src/test/java/org/apache/paimon/JavaPyE2ETest.java
index 543ca8eab6..1d2a4f3b85 100644
--- a/paimon-core/src/test/java/org/apache/paimon/JavaPyE2ETest.java
+++ b/paimon-core/src/test/java/org/apache/paimon/JavaPyE2ETest.java
@@ -34,7 +34,7 @@ import org.apache.paimon.disk.IOManager;
 import org.apache.paimon.fs.FileIOFinder;
 import org.apache.paimon.fs.Path;
 import org.apache.paimon.fs.local.LocalFileIO;
-import org.apache.paimon.globalindex.btree.BTreeGlobalIndexBuilder;
+import org.apache.paimon.globalindex.sorted.SortedGlobalIndexBuilder;
 import org.apache.paimon.io.DataFileMeta;
 import org.apache.paimon.manifest.IndexManifestEntry;
 import org.apache.paimon.options.MemorySize;
@@ -572,7 +572,8 @@ public class JavaPyE2ETest {
         }
 
         // build index
-        BTreeGlobalIndexBuilder builder = new 
BTreeGlobalIndexBuilder(table).withIndexField("k");
+        SortedGlobalIndexBuilder builder =
+                new SortedGlobalIndexBuilder(table, 
"btree").withIndexField("k");
         try (BatchTableCommit commit = writeBuilder.newCommit()) {
             commit.commit(
                     builder.build(
@@ -648,7 +649,8 @@ public class JavaPyE2ETest {
         }
 
         // build index
-        BTreeGlobalIndexBuilder builder = new 
BTreeGlobalIndexBuilder(table).withIndexField("k");
+        SortedGlobalIndexBuilder builder =
+                new SortedGlobalIndexBuilder(table, 
"btree").withIndexField("k");
         try (BatchTableCommit commit = writeBuilder.newCommit()) {
             commit.commit(
                     builder.build(
@@ -726,7 +728,8 @@ public class JavaPyE2ETest {
         }
 
         // build index
-        BTreeGlobalIndexBuilder builder = new 
BTreeGlobalIndexBuilder(table).withIndexField("k");
+        SortedGlobalIndexBuilder builder =
+                new SortedGlobalIndexBuilder(table, 
"btree").withIndexField("k");
         try (BatchTableCommit commit = writeBuilder.newCommit()) {
             commit.commit(
                     builder.build(
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/append/dataevolution/DataEvolutionRowIdReassignerTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/append/dataevolution/DataEvolutionRowIdReassignerTest.java
index ffaade20cb..b17c912aaf 100644
--- 
a/paimon-core/src/test/java/org/apache/paimon/append/dataevolution/DataEvolutionRowIdReassignerTest.java
+++ 
b/paimon-core/src/test/java/org/apache/paimon/append/dataevolution/DataEvolutionRowIdReassignerTest.java
@@ -26,8 +26,8 @@ import org.apache.paimon.data.BinaryString;
 import org.apache.paimon.data.GenericRow;
 import org.apache.paimon.data.InternalRow;
 import org.apache.paimon.data.serializer.InternalRowSerializer;
-import org.apache.paimon.globalindex.btree.BTreeGlobalIndexBuilder;
 import org.apache.paimon.globalindex.btree.BTreeIndexOptions;
+import org.apache.paimon.globalindex.sorted.SortedGlobalIndexBuilder;
 import org.apache.paimon.index.GlobalIndexMeta;
 import org.apache.paimon.index.IndexFileMeta;
 import org.apache.paimon.io.DataFileMeta;
@@ -910,7 +910,8 @@ public class DataEvolutionRowIdReassignerTest extends 
TableTestBase {
     }
 
     private void createBTreeIndex(FileStoreTable table) throws Exception {
-        BTreeGlobalIndexBuilder builder = new 
BTreeGlobalIndexBuilder(table).withIndexField("id");
+        SortedGlobalIndexBuilder builder =
+                new SortedGlobalIndexBuilder(table, 
"btree").withIndexField("id");
         List<DataSplit> dataSplits =
                 builder.scan()
                         .map(Pair::getRight)
@@ -919,7 +920,7 @@ public class DataEvolutionRowIdReassignerTest extends 
TableTestBase {
                                         new IllegalStateException(
                                                 "Expected scan result when 
building index."));
         List<CommitMessage> commitMessages = new ArrayList<>();
-        for (DataSplit dataSplit : 
BTreeGlobalIndexBuilder.splitByContiguousRowRange(dataSplits)) {
+        for (DataSplit dataSplit : 
SortedGlobalIndexBuilder.splitByContiguousRowRange(dataSplits)) {
             commitMessages.addAll(builder.build(dataSplit, ioManager));
         }
         try (BatchTableCommit commit = 
table.newBatchWriteBuilder().newCommit()) {
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/globalindex/btree/BTreeGlobalIndexBuilderSplitTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/globalindex/sorted/SortedGlobalIndexBuilderSplitTest.java
similarity index 90%
rename from 
paimon-core/src/test/java/org/apache/paimon/globalindex/btree/BTreeGlobalIndexBuilderSplitTest.java
rename to 
paimon-core/src/test/java/org/apache/paimon/globalindex/sorted/SortedGlobalIndexBuilderSplitTest.java
index 60431faa12..2d64ce9db5 100644
--- 
a/paimon-core/src/test/java/org/apache/paimon/globalindex/btree/BTreeGlobalIndexBuilderSplitTest.java
+++ 
b/paimon-core/src/test/java/org/apache/paimon/globalindex/sorted/SortedGlobalIndexBuilderSplitTest.java
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-package org.apache.paimon.globalindex.btree;
+package org.apache.paimon.globalindex.sorted;
 
 import org.apache.paimon.data.BinaryRow;
 import org.apache.paimon.globalindex.IndexedSplit;
@@ -38,8 +38,8 @@ import java.util.UUID;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
-/** Tests for split regrouping in {@link BTreeGlobalIndexBuilder}. */
-public class BTreeGlobalIndexBuilderSplitTest {
+/** Tests for split regrouping in {@link SortedGlobalIndexBuilder}. */
+public class SortedGlobalIndexBuilderSplitTest {
 
     @Test
     public void testSplitByContiguousRowRangeFromDataFiles() {
@@ -58,14 +58,15 @@ public class BTreeGlobalIndexBuilderSplitTest {
                         .build();
 
         List<DataSplit> rebuilt =
-                
BTreeGlobalIndexBuilder.splitByContiguousRowRange(Collections.singletonList(split));
+                SortedGlobalIndexBuilder.splitByContiguousRowRange(
+                        Collections.singletonList(split));
 
         assertThat(rebuilt).hasSize(2);
         assertThat(rebuilt.get(0).dataFiles()).containsExactly(file1, file3);
         assertThat(rebuilt.get(1).dataFiles()).containsExactly(file2);
-        assertThat(BTreeGlobalIndexBuilder.calcRowRange(rebuilt.get(0)))
+        assertThat(SortedGlobalIndexBuilder.calcRowRange(rebuilt.get(0)))
                 .isEqualTo(new Range(0, 199));
-        assertThat(BTreeGlobalIndexBuilder.calcRowRange(rebuilt.get(1)))
+        assertThat(SortedGlobalIndexBuilder.calcRowRange(rebuilt.get(1)))
                 .isEqualTo(new Range(300, 399));
     }
 
@@ -86,7 +87,7 @@ public class BTreeGlobalIndexBuilderSplitTest {
                         .build();
 
         Map<BinaryRow, Map<Range, List<Split>>> result =
-                BTreeGlobalIndexBuilder.groupSplitsByRange(
+                SortedGlobalIndexBuilder.groupSplitsByRange(
                         RowRangeIndex.create(
                                 Arrays.asList(new Range(4750, 4900), new 
Range(5938, 7599))),
                         Collections.singletonList(split));
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/globalindex/btree/BTreeGlobalIndexBuilderTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/globalindex/sorted/SortedGlobalIndexBuilderTest.java
similarity index 95%
rename from 
paimon-core/src/test/java/org/apache/paimon/globalindex/btree/BTreeGlobalIndexBuilderTest.java
rename to 
paimon-core/src/test/java/org/apache/paimon/globalindex/sorted/SortedGlobalIndexBuilderTest.java
index 5e8c5cb82e..7e0d18c2c9 100644
--- 
a/paimon-core/src/test/java/org/apache/paimon/globalindex/btree/BTreeGlobalIndexBuilderTest.java
+++ 
b/paimon-core/src/test/java/org/apache/paimon/globalindex/sorted/SortedGlobalIndexBuilderTest.java
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-package org.apache.paimon.globalindex.btree;
+package org.apache.paimon.globalindex.sorted;
 
 import org.apache.paimon.CoreOptions;
 import org.apache.paimon.Snapshot;
@@ -25,6 +25,7 @@ import org.apache.paimon.data.BinaryString;
 import org.apache.paimon.data.BlobData;
 import org.apache.paimon.data.GenericRow;
 import org.apache.paimon.globalindex.KeySerializer;
+import org.apache.paimon.globalindex.btree.BTreeIndexOptions;
 import org.apache.paimon.index.GlobalIndexMeta;
 import org.apache.paimon.index.IndexFileHandler;
 import org.apache.paimon.index.IndexFileMeta;
@@ -57,8 +58,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 
-/** Test class for {@link BTreeGlobalIndexBuilder}. */
-public class BTreeGlobalIndexBuilderTest extends TableTestBase {
+/** Test class for {@link SortedGlobalIndexBuilder}. */
+public class SortedGlobalIndexBuilderTest extends TableTestBase {
 
     private static final long PART_ROW_NUM = 1000L;
     private static final KeySerializer KEY_SERIALIZER = 
KeySerializer.create(DataTypes.INT());
@@ -109,7 +110,7 @@ public class BTreeGlobalIndexBuilderTest extends 
TableTestBase {
     private void createIndex(PartitionPredicate partitionPredicate) throws 
Exception {
         FileStoreTable table = getTableDefault();
 
-        BTreeGlobalIndexBuilder builder = new BTreeGlobalIndexBuilder(table);
+        SortedGlobalIndexBuilder builder = new SortedGlobalIndexBuilder(table, 
"btree");
         builder.withIndexField("f0");
         builder.withPartitionPredicate(partitionPredicate);
         List<DataSplit> dataSplits =
@@ -168,7 +169,7 @@ public class BTreeGlobalIndexBuilderTest extends 
TableTestBase {
         createTableDefault();
 
         FileStoreTable table = getTableDefault();
-        BTreeGlobalIndexBuilder builder = new BTreeGlobalIndexBuilder(table);
+        SortedGlobalIndexBuilder builder = new SortedGlobalIndexBuilder(table, 
"btree");
         builder.withIndexField("f0");
 
         Assertions.assertFalse(
@@ -182,7 +183,7 @@ public class BTreeGlobalIndexBuilderTest extends 
TableTestBase {
         createIndex(null);
 
         FileStoreTable table = getTableDefault();
-        BTreeGlobalIndexBuilder builder = new BTreeGlobalIndexBuilder(table);
+        SortedGlobalIndexBuilder builder = new SortedGlobalIndexBuilder(table, 
"btree");
         builder.withIndexField("f0");
 
         Assertions.assertFalse(
@@ -212,7 +213,7 @@ public class BTreeGlobalIndexBuilderTest extends 
TableTestBase {
         }
 
         table = getTableDefault();
-        BTreeGlobalIndexBuilder builder = new BTreeGlobalIndexBuilder(table);
+        SortedGlobalIndexBuilder builder = new SortedGlobalIndexBuilder(table, 
"btree");
         builder.withIndexField("f0");
 
         Optional<Pair<org.apache.paimon.utils.RowRangeIndex, List<DataSplit>>> 
incrementalScan =
@@ -270,7 +271,7 @@ public class BTreeGlobalIndexBuilderTest extends 
TableTestBase {
         predicate =
                 PartitionPredicate.createPartitionPredicate(
                         partType, Collections.singletonMap("dt", 
BinaryString.fromString("p0")));
-        BTreeGlobalIndexBuilder builder = new BTreeGlobalIndexBuilder(table);
+        SortedGlobalIndexBuilder builder = new SortedGlobalIndexBuilder(table, 
"btree");
         builder.withIndexField("f0");
         
builder.withPartitionPredicate(PartitionPredicate.fromPredicate(partType, 
predicate));
 
@@ -330,7 +331,8 @@ public class BTreeGlobalIndexBuilderTest extends 
TableTestBase {
                 containsBlobFile(table.store().newScan().plan().files()),
                 "Test table should contain blob manifest entries.");
 
-        BTreeGlobalIndexBuilder builder = new 
BTreeGlobalIndexBuilder(table).withIndexField("f0");
+        SortedGlobalIndexBuilder builder =
+                new SortedGlobalIndexBuilder(table, 
"btree").withIndexField("f0");
         assertNoBlobFiles(
                 builder.scan()
                         .map(Pair::getRight)
@@ -385,7 +387,7 @@ public class BTreeGlobalIndexBuilderTest extends 
TableTestBase {
                 Assertions.assertNotEquals(
                         "blob",
                         file.fileFormat(),
-                        "BTree global index scan should not include blob 
files.");
+                        "Sorted global index scan should not include blob 
files.");
             }
         }
     }
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/metastore/VisibilityWaitCallbackTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/metastore/VisibilityWaitCallbackTest.java
index 030b2af41c..9440988e88 100644
--- 
a/paimon-core/src/test/java/org/apache/paimon/metastore/VisibilityWaitCallbackTest.java
+++ 
b/paimon-core/src/test/java/org/apache/paimon/metastore/VisibilityWaitCallbackTest.java
@@ -22,7 +22,7 @@ import org.apache.paimon.CoreOptions;
 import org.apache.paimon.catalog.Identifier;
 import org.apache.paimon.data.BinaryString;
 import org.apache.paimon.data.GenericRow;
-import org.apache.paimon.globalindex.btree.BTreeGlobalIndexBuilder;
+import org.apache.paimon.globalindex.sorted.SortedGlobalIndexBuilder;
 import org.apache.paimon.partition.PartitionPredicate;
 import org.apache.paimon.predicate.Predicate;
 import org.apache.paimon.schema.Schema;
@@ -97,8 +97,8 @@ public class VisibilityWaitCallbackTest extends TableTestBase 
{
             buildIndex(getTableDefault(), true);
             writeFuture.get(10, TimeUnit.SECONDS);
 
-            BTreeGlobalIndexBuilder builder =
-                    new 
BTreeGlobalIndexBuilder(getTableDefault()).withIndexField("f1");
+            SortedGlobalIndexBuilder builder =
+                    new SortedGlobalIndexBuilder(getTableDefault(), 
"btree").withIndexField("f1");
             assertThat(builder.incrementalScan()).isNotPresent();
         } finally {
             executor.shutdownNow();
@@ -159,7 +159,8 @@ public class VisibilityWaitCallbackTest extends 
TableTestBase {
     }
 
     private void buildIndex(FileStoreTable table, boolean incremental) throws 
Exception {
-        BTreeGlobalIndexBuilder builder = new 
BTreeGlobalIndexBuilder(table).withIndexField("f1");
+        SortedGlobalIndexBuilder builder =
+                new SortedGlobalIndexBuilder(table, 
"btree").withIndexField("f1");
         Optional<Pair<RowRangeIndex, List<DataSplit>>> scan =
                 incremental ? builder.incrementalScan() : builder.scan();
         assertThat(scan).isPresent();
@@ -175,8 +176,8 @@ public class VisibilityWaitCallbackTest extends 
TableTestBase {
     }
 
     private void buildPartitionIndex(FileStoreTable table, String partition) 
throws Exception {
-        BTreeGlobalIndexBuilder builder =
-                new BTreeGlobalIndexBuilder(table)
+        SortedGlobalIndexBuilder builder =
+                new SortedGlobalIndexBuilder(table, "btree")
                         .withIndexField("f1")
                         .withPartitionPredicate(partitionPredicate(table, 
partition));
         Optional<Pair<RowRangeIndex, List<DataSplit>>> scan = builder.scan();
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/table/BtreeGlobalIndexTableTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/table/BtreeGlobalIndexTableTest.java
index 80c82fd626..3fcd8f2ae4 100644
--- 
a/paimon-core/src/test/java/org/apache/paimon/table/BtreeGlobalIndexTableTest.java
+++ 
b/paimon-core/src/test/java/org/apache/paimon/table/BtreeGlobalIndexTableTest.java
@@ -24,7 +24,7 @@ import org.apache.paimon.globalindex.DataEvolutionBatchScan;
 import org.apache.paimon.globalindex.GlobalIndexResult;
 import org.apache.paimon.globalindex.GlobalIndexScanner;
 import org.apache.paimon.globalindex.IndexedSplit;
-import org.apache.paimon.globalindex.btree.BTreeGlobalIndexBuilder;
+import org.apache.paimon.globalindex.sorted.SortedGlobalIndexBuilder;
 import org.apache.paimon.partition.PartitionPredicate;
 import org.apache.paimon.predicate.Predicate;
 import org.apache.paimon.predicate.PredicateBuilder;
@@ -206,8 +206,8 @@ public class BtreeGlobalIndexTableTest extends 
DataEvolutionTestBase {
 
     private void createIndex(String fieldName, List<Range> rowRanges) throws 
Exception {
         FileStoreTable table = (FileStoreTable) catalog.getTable(identifier());
-        BTreeGlobalIndexBuilder builder =
-                new BTreeGlobalIndexBuilder(table).withIndexField(fieldName);
+        SortedGlobalIndexBuilder builder =
+                new SortedGlobalIndexBuilder(table, 
"btree").withIndexField(fieldName);
         List<DataSplit> dataSplits =
                 builder.scan()
                         .map(org.apache.paimon.utils.Pair::getRight)
diff --git 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/btree/BTreeIndexTopoBuilder.java
 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/globalindex/SortedIndexTopoBuilder.java
similarity index 84%
rename from 
paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/btree/BTreeIndexTopoBuilder.java
rename to 
paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/globalindex/SortedIndexTopoBuilder.java
index 98697fae73..4a17bb68f1 100644
--- 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/btree/BTreeIndexTopoBuilder.java
+++ 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/globalindex/SortedIndexTopoBuilder.java
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-package org.apache.paimon.flink.btree;
+package org.apache.paimon.flink.globalindex;
 
 import org.apache.paimon.CoreOptions;
 import org.apache.paimon.data.BinaryRow;
@@ -38,8 +38,8 @@ import org.apache.paimon.flink.utils.BoundedOneInputOperator;
 import org.apache.paimon.flink.utils.JavaTypeInfo;
 import org.apache.paimon.flink.utils.StreamExecutionEnvironmentUtils;
 import org.apache.paimon.globalindex.GlobalIndexSingleColumnWriter;
-import org.apache.paimon.globalindex.btree.BTreeGlobalIndexBuilder;
-import org.apache.paimon.globalindex.btree.BTreeIndexOptions;
+import org.apache.paimon.globalindex.sorted.SortedGlobalIndexBuilder;
+import org.apache.paimon.globalindex.sorted.SortedIndexOptions;
 import org.apache.paimon.options.Options;
 import org.apache.paimon.partition.PartitionPredicate;
 import org.apache.paimon.reader.RecordReader;
@@ -69,26 +69,34 @@ import 
org.apache.flink.table.runtime.typeutils.InternalTypeInfo;
 import java.io.IOException;
 import java.io.Serializable;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.UUID;
 import java.util.function.Supplier;
 
-import static 
org.apache.paimon.globalindex.btree.BTreeGlobalIndexBuilder.groupSplitsByRange;
-import static 
org.apache.paimon.globalindex.btree.BTreeGlobalIndexBuilder.splitByContiguousRowRange;
+import static 
org.apache.paimon.globalindex.sorted.SortedGlobalIndexBuilder.groupSplitsByRange;
+import static 
org.apache.paimon.globalindex.sorted.SortedGlobalIndexBuilder.splitByContiguousRowRange;
 
-/** The {@link BTreeIndexTopoBuilder} for BTree index in Flink. */
-public class BTreeIndexTopoBuilder {
+/** The topology builder for sorted indexes in Flink. */
+public class SortedIndexTopoBuilder {
 
-    private static final String BUILD_TASK_ID_FIELD = "_BTREE_BUILD_TASK_ID";
+    private static final String BUILD_TASK_ID_FIELD = 
"_SORTED_INDEX_BUILD_TASK_ID";
     private static final int BUILD_TASK_ID_FIELD_ID = -1;
+    private static final HashSet<String> SUPPORTED_INDEX_TYPES =
+            new HashSet<>(Arrays.asList("btree", "bitmap"));
+
+    public static boolean supports(String indexType) {
+        return SUPPORTED_INDEX_TYPES.contains(indexType);
+    }
 
     public static boolean buildIndex(
             StreamExecutionEnvironment env,
-            Supplier<BTreeGlobalIndexBuilder> indexBuilderSupplier,
+            Supplier<SortedGlobalIndexBuilder> indexBuilderSupplier,
             FileStoreTable table,
             List<String> indexColumns,
             PartitionPredicate partitionPredicate,
@@ -96,7 +104,7 @@ public class BTreeIndexTopoBuilder {
             throws Exception {
         List<DataStream<Committable>> allStreams = new ArrayList<>();
         for (String indexColumn : indexColumns) {
-            BTreeGlobalIndexBuilder indexBuilder =
+            SortedGlobalIndexBuilder indexBuilder =
                     indexBuilderSupplier.get().withIndexField(indexColumn);
             if (partitionPredicate != null) {
                 indexBuilder = 
indexBuilder.withPartitionPredicate(partitionPredicate);
@@ -133,9 +141,10 @@ public class BTreeIndexTopoBuilder {
             DataType indexFieldType = sortReadType.getTypeAt(indexFieldPos);
 
             // 3. Calculate maximum parallelism bound
-            long recordsPerRange = 
userOptions.get(BTreeIndexOptions.BTREE_INDEX_RECORDS_PER_RANGE);
+            long recordsPerRange =
+                    
userOptions.get(SortedIndexOptions.SORTED_INDEX_RECORDS_PER_RANGE);
             int maxParallelism =
-                    
userOptions.get(BTreeIndexOptions.BTREE_INDEX_BUILD_MAX_PARALLELISM);
+                    
userOptions.get(SortedIndexOptions.SORTED_INDEX_BUILD_MAX_PARALLELISM);
 
             // 4. Build one topology for all contiguous row ranges
             CoreOptions coreOptions = table.coreOptions();
@@ -145,8 +154,8 @@ public class BTreeIndexTopoBuilder {
             sortColumns.add(indexColumn);
             int partitionFieldSize = table.partitionKeys().size();
             BinaryRowSerializer binaryRowSerializer = new 
BinaryRowSerializer(partitionFieldSize);
-            List<BTreeBuildTask> buildTasks = new ArrayList<>();
-            List<BTreeSplitTask> splitTasks = new ArrayList<>();
+            List<SortedBuildTask> buildTasks = new ArrayList<>();
+            List<SortedSplitTask> splitTasks = new ArrayList<>();
             for (Map.Entry<BinaryRow, Map<Range, List<Split>>> partitionEntry :
                     partitionRangeSplits.entrySet()) {
                 BinaryRow partition = partitionEntry.getKey();
@@ -159,9 +168,9 @@ public class BTreeIndexTopoBuilder {
                     }
 
                     int taskId = buildTasks.size();
-                    buildTasks.add(new BTreeBuildTask(taskId, range, 
partitionBytes));
+                    buildTasks.add(new SortedBuildTask(taskId, range, 
partitionBytes));
                     for (Split split : rangeSplits) {
-                        splitTasks.add(new BTreeSplitTask(taskId, split));
+                        splitTasks.add(new SortedSplitTask(taskId, split));
                     }
                 }
             }
@@ -205,26 +214,27 @@ public class BTreeIndexTopoBuilder {
             StreamExecutionEnvironment env,
             FileStoreTable table,
             String indexColumn,
+            String indexType,
             PartitionPredicate partitionPredicate,
             Options userOptions)
             throws Exception {
         if (buildIndex(
                 env,
-                () -> new BTreeGlobalIndexBuilder(table),
+                () -> new SortedGlobalIndexBuilder(table, indexType, 
userOptions),
                 table,
                 Collections.singletonList(indexColumn),
                 partitionPredicate,
                 userOptions)) {
-            env.execute("Create btree global index for table: " + 
table.name());
+            env.execute("Create " + indexType + " global index for table: " + 
table.name());
         }
     }
 
     protected static DataStream<Committable> executeForBuildTasks(
             StreamExecutionEnvironment env,
-            List<BTreeBuildTask> buildTasks,
-            List<BTreeSplitTask> splitTasks,
+            List<SortedBuildTask> buildTasks,
+            List<SortedSplitTask> splitTasks,
             ReadBuilder readBuilder,
-            BTreeGlobalIndexBuilder indexBuilder,
+            SortedGlobalIndexBuilder indexBuilder,
             int partitionFieldSize,
             int taskIdPos,
             int indexFieldPos,
@@ -237,9 +247,9 @@ public class BTreeIndexTopoBuilder {
             int maxParallelism) {
         int parallelism = calculateParallelism(buildTasks, recordsPerRange, 
maxParallelism);
 
-        DataStream<BTreeSplitTask> sourceStream =
+        DataStream<SortedSplitTask> sourceStream =
                 StreamExecutionEnvironmentUtils.fromData(
-                                env, splitTasks, new 
JavaTypeInfo<>(BTreeSplitTask.class))
+                                env, splitTasks, new 
JavaTypeInfo<>(SortedSplitTask.class))
                         .name("Global Index Source")
                         .setParallelism(1);
 
@@ -267,7 +277,7 @@ public class BTreeIndexTopoBuilder {
 
         return sortedStream
                 .transform(
-                        "write-btree-index",
+                        "write-sorted-index",
                         new CommittableTypeInfo(),
                         new WriteIndexOperator(
                                 buildTasks,
@@ -281,9 +291,9 @@ public class BTreeIndexTopoBuilder {
     }
 
     static int calculateParallelism(
-            List<BTreeBuildTask> buildTasks, long recordsPerRange, int 
maxParallelism) {
+            List<SortedBuildTask> buildTasks, long recordsPerRange, int 
maxParallelism) {
         long totalRecords = 0;
-        for (BTreeBuildTask task : buildTasks) {
+        for (SortedBuildTask task : buildTasks) {
             long count = task.rowRange.count();
             if (Long.MAX_VALUE - totalRecords < count) {
                 totalRecords = Long.MAX_VALUE;
@@ -317,7 +327,7 @@ public class BTreeIndexTopoBuilder {
                 new CommitterOperatorFactory<>(
                         false,
                         true,
-                        "BTreeIndexCommitter-" + UUID.randomUUID(),
+                        "SortedIndexCommitter-" + UUID.randomUUID(),
                         context ->
                                 new StoreCommitter(
                                         table, 
table.newCommit(context.commitUser()), context),
@@ -332,7 +342,7 @@ public class BTreeIndexTopoBuilder {
     private static class ReadDataOperator
             extends 
org.apache.flink.table.runtime.operators.TableStreamOperator<RowData>
             implements 
org.apache.flink.streaming.api.operators.OneInputStreamOperator<
-                    BTreeSplitTask, RowData> {
+                    SortedSplitTask, RowData> {
 
         private static final long serialVersionUID = 1L;
 
@@ -351,8 +361,8 @@ public class BTreeIndexTopoBuilder {
         }
 
         @Override
-        public void processElement(StreamRecord<BTreeSplitTask> element) 
throws Exception {
-            BTreeSplitTask buildTask = element.getValue();
+        public void processElement(StreamRecord<SortedSplitTask> element) 
throws Exception {
+            SortedSplitTask buildTask = element.getValue();
             GenericRow taskId = GenericRow.of(buildTask.taskId);
             try (RecordReader<InternalRow> reader = 
tableRead.createReader(buildTask.split)) {
                 reader.forEachRemaining(
@@ -366,27 +376,27 @@ public class BTreeIndexTopoBuilder {
 
     private static class WriteIndexOperator extends 
BoundedOneInputOperator<RowData, Committable> {
 
-        private final List<BTreeBuildTask> buildTasks;
+        private final List<SortedBuildTask> buildTasks;
         private final int partitionFieldSize;
-        private final BTreeGlobalIndexBuilder builder;
+        private final SortedGlobalIndexBuilder builder;
         private final int taskIdPos;
         private final int indexFieldPos;
         private final int rowIdPos;
         private final DataType indexFieldType;
 
         private transient long counter;
-        private transient BTreeBuildTask currentTask;
+        private transient SortedBuildTask currentTask;
         private transient BinaryRow currentPartition;
         private transient GlobalIndexSingleColumnWriter currentWriter;
         private transient List<CommitMessage> commitMessages;
-        private transient Map<Integer, BTreeBuildTask> buildTasksById;
+        private transient Map<Integer, SortedBuildTask> buildTasksById;
         private transient InternalRow.FieldGetter indexFieldGetter;
         private transient BinaryRowSerializer binaryRowSerializer;
 
         public WriteIndexOperator(
-                List<BTreeBuildTask> buildTasks,
+                List<SortedBuildTask> buildTasks,
                 int partitionFieldSize,
-                BTreeGlobalIndexBuilder builder,
+                SortedGlobalIndexBuilder builder,
                 int taskIdPos,
                 int indexFieldPos,
                 int rowIdPos,
@@ -405,7 +415,7 @@ public class BTreeIndexTopoBuilder {
             super.open();
             commitMessages = new ArrayList<>();
             buildTasksById = new HashMap<>();
-            for (BTreeBuildTask task : buildTasks) {
+            for (SortedBuildTask task : buildTasks) {
                 buildTasksById.put(task.taskId, task);
             }
             indexFieldGetter = InternalRow.createFieldGetter(indexFieldType, 
indexFieldPos);
@@ -416,9 +426,9 @@ public class BTreeIndexTopoBuilder {
         public void processElement(StreamRecord<RowData> element) throws 
IOException {
             InternalRow row = new FlinkRowWrapper(element.getValue());
             int taskId = row.getInt(taskIdPos);
-            BTreeBuildTask task = buildTasksById.get(taskId);
+            SortedBuildTask task = buildTasksById.get(taskId);
             if (task == null) {
-                throw new IllegalArgumentException("Unknown BTree build task 
id: " + taskId);
+                throw new IllegalArgumentException("Unknown sorted index build 
task id: " + taskId);
             }
 
             if (currentTask == null || currentTask.taskId != taskId) {
@@ -463,8 +473,8 @@ public class BTreeIndexTopoBuilder {
         }
     }
 
-    /** Metadata for one BTree index build range. */
-    public static class BTreeBuildTask implements Serializable {
+    /** Metadata for one sorted index build range. */
+    public static class SortedBuildTask implements Serializable {
 
         private static final long serialVersionUID = 1L;
 
@@ -472,26 +482,26 @@ public class BTreeIndexTopoBuilder {
         private Range rowRange;
         private byte[] partition;
 
-        public BTreeBuildTask() {}
+        public SortedBuildTask() {}
 
-        BTreeBuildTask(int taskId, Range rowRange, byte[] partition) {
+        public SortedBuildTask(int taskId, Range rowRange, byte[] partition) {
             this.taskId = taskId;
             this.rowRange = rowRange;
             this.partition = partition;
         }
     }
 
-    /** Split assigned to one BTree index build task. */
-    public static class BTreeSplitTask implements Serializable {
+    /** Split assigned to one sorted index build task. */
+    public static class SortedSplitTask implements Serializable {
 
         private static final long serialVersionUID = 1L;
 
         private int taskId;
         private Split split;
 
-        public BTreeSplitTask() {}
+        public SortedSplitTask() {}
 
-        BTreeSplitTask(int taskId, Split split) {
+        public SortedSplitTask(int taskId, Split split) {
             this.taskId = taskId;
             this.split = split;
         }
diff --git 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/procedure/CreateGlobalIndexProcedure.java
 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/procedure/CreateGlobalIndexProcedure.java
index 1979547777..71cb07e398 100644
--- 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/procedure/CreateGlobalIndexProcedure.java
+++ 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/procedure/CreateGlobalIndexProcedure.java
@@ -18,8 +18,8 @@
 
 package org.apache.paimon.flink.procedure;
 
-import org.apache.paimon.flink.btree.BTreeIndexTopoBuilder;
 import org.apache.paimon.flink.globalindex.GenericIndexTopoBuilder;
+import org.apache.paimon.flink.globalindex.SortedIndexTopoBuilder;
 import org.apache.paimon.globalindex.GlobalIndexer;
 import org.apache.paimon.options.Options;
 import org.apache.paimon.partition.PartitionPredicate;
@@ -114,7 +114,6 @@ public class CreateGlobalIndexProcedure extends 
ProcedureBase {
         // Parse options
         Options userOptions = createUserOptions(table, options);
 
-        // Build global index based on index type
         indexType = indexType.toLowerCase().trim();
         if (indexColumns.size() > 1) {
             // Fail fast before submitting the job: index types that do not 
support multi-column
@@ -134,15 +133,18 @@ public class CreateGlobalIndexProcedure extends 
ProcedureBase {
             }
         }
         try {
-            if ("btree".equals(indexType)) {
-                BTreeIndexTopoBuilder.buildIndexAndExecute(
+            if (SortedIndexTopoBuilder.supports(indexType)) {
+                SortedIndexTopoBuilder.buildIndexAndExecute(
                         procedureContext.getExecutionEnvironment(),
                         table,
                         indexColumns.get(0),
+                        indexType,
                         partitionPredicate,
                         userOptions);
                 return new String[] {
-                    "BTree global index created successfully for table: " + 
table.name()
+                    displayIndexType(indexType)
+                            + " global index created successfully for table: "
+                            + table.name()
                 };
             } else {
                 GenericIndexTopoBuilder.buildIndexAndExecute(
@@ -162,7 +164,9 @@ public class CreateGlobalIndexProcedure extends 
ProcedureBase {
                     e);
         }
         return new String[] {
-            indexType + " global index created successfully for table: " + 
table.name()
+            displayIndexType(indexType)
+                    + " global index created successfully for table: "
+                    + table.name()
         };
     }
 
@@ -179,4 +183,14 @@ public class CreateGlobalIndexProcedure extends 
ProcedureBase {
                         table.coreOptions().partitionDefaultName());
         return 
PartitionPredicate.fromPredicate(table.schema().logicalPartitionType(), 
predicate);
     }
+
+    private static String displayIndexType(String indexType) {
+        if ("btree".equals(indexType)) {
+            return "BTree";
+        }
+        if ("bitmap".equals(indexType)) {
+            return "Bitmap";
+        }
+        return indexType;
+    }
 }
diff --git 
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/BTreeGlobalIndexITCase.java
 
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/SortedGlobalIndexITCase.java
similarity index 87%
rename from 
paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/BTreeGlobalIndexITCase.java
rename to 
paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/SortedGlobalIndexITCase.java
index 9433034d39..8b730819bb 100644
--- 
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/BTreeGlobalIndexITCase.java
+++ 
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/SortedGlobalIndexITCase.java
@@ -36,8 +36,8 @@ import java.util.stream.IntStream;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
-/** Test case for btree global index. */
-public class BTreeGlobalIndexITCase extends CatalogITCaseBase {
+/** Test case for sorted global indexes. */
+public class SortedGlobalIndexITCase extends CatalogITCaseBase {
 
     @Test
     public void testBTreeIndex() throws Catalog.TableNotExistException {
@@ -71,6 +71,39 @@ public class BTreeGlobalIndexITCase extends 
CatalogITCaseBase {
         assertThat(sql("SELECT * FROM T WHERE id = 
100")).containsOnly(Row.of(100, "name_100"));
     }
 
+    @Test
+    public void testBitmapIndex() throws Catalog.TableNotExistException {
+        sql(
+                "CREATE TABLE T_BITMAP (id INT, name STRING) WITH ("
+                        + "'global-index.enabled' = 'true', "
+                        + "'row-tracking.enabled' = 'true', "
+                        + "'data-evolution.enabled' = 'true'"
+                        + ")");
+        String values =
+                IntStream.range(0, 1_000)
+                        .mapToObj(i -> String.format("(%s, %s)", i, "'name_" + 
i + "'"))
+                        .collect(Collectors.joining(","));
+        sql("INSERT INTO T_BITMAP VALUES " + values);
+        sql(
+                "CALL sys.create_global_index(`table` => 'default.T_BITMAP', "
+                        + "index_column => 'id', index_type => 'bitmap', "
+                        + "options => 'sorted-index.records-per-range=200')");
+
+        FileStoreTable table = paimonTable("T_BITMAP");
+        List<IndexFileMeta> bitmapEntries =
+                table.store().newIndexFileHandler().scanEntries().stream()
+                        .map(IndexManifestEntry::indexFile)
+                        .filter(f -> "bitmap".equals(f.indexType()))
+                        .collect(Collectors.toList());
+
+        long totalRowCount = 
bitmapEntries.stream().mapToLong(IndexFileMeta::rowCount).sum();
+        assertThat(bitmapEntries).hasSizeGreaterThan(1);
+        assertThat(totalRowCount).isEqualTo(1000L);
+
+        assertThat(sql("SELECT * FROM T_BITMAP WHERE id = 100"))
+                .containsOnly(Row.of(100, "name_100"));
+    }
+
     @Test
     public void testBTreeIndexWithMultiPartition() throws 
Catalog.TableNotExistException {
         sql(
diff --git 
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/btree/BTreeIndexTopoBuilderTest.java
 
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/globalindex/SortedIndexTopoBuilderTest.java
similarity index 67%
rename from 
paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/btree/BTreeIndexTopoBuilderTest.java
rename to 
paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/globalindex/SortedIndexTopoBuilderTest.java
index 25260ee5c4..89a15bef31 100644
--- 
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/btree/BTreeIndexTopoBuilderTest.java
+++ 
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/globalindex/SortedIndexTopoBuilderTest.java
@@ -16,10 +16,10 @@
  * limitations under the License.
  */
 
-package org.apache.paimon.flink.btree;
+package org.apache.paimon.flink.globalindex;
 
-import org.apache.paimon.flink.btree.BTreeIndexTopoBuilder.BTreeBuildTask;
-import org.apache.paimon.globalindex.btree.BTreeGlobalIndexBuilder;
+import 
org.apache.paimon.flink.globalindex.SortedIndexTopoBuilder.SortedBuildTask;
+import org.apache.paimon.globalindex.sorted.SortedGlobalIndexBuilder;
 import org.apache.paimon.options.Options;
 import org.apache.paimon.table.FileStoreTable;
 import org.apache.paimon.utils.Range;
@@ -37,18 +37,18 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.when;
 
-/** Tests for {@link BTreeIndexTopoBuilder}. */
-public class BTreeIndexTopoBuilderTest {
+/** Tests for {@link SortedIndexTopoBuilder}. */
+public class SortedIndexTopoBuilderTest {
 
     @Test
     public void testBuildIndexReturnsFalseWhenNoBuildTask() throws Exception {
-        BTreeGlobalIndexBuilder indexBuilder = 
mock(BTreeGlobalIndexBuilder.class);
+        SortedGlobalIndexBuilder indexBuilder = 
mock(SortedGlobalIndexBuilder.class);
         when(indexBuilder.withIndexField("id")).thenReturn(indexBuilder);
         when(indexBuilder.scan()).thenReturn(Optional.empty());
         StreamExecutionEnvironment env = 
mock(StreamExecutionEnvironment.class);
 
         assertThat(
-                        BTreeIndexTopoBuilder.buildIndex(
+                        SortedIndexTopoBuilder.buildIndex(
                                 env,
                                 () -> indexBuilder,
                                 mock(FileStoreTable.class),
@@ -61,29 +61,29 @@ public class BTreeIndexTopoBuilderTest {
 
     @Test
     public void testCalculateParallelismByTotalRowsInsteadOfRangeCount() {
-        List<BTreeBuildTask> tasks = new ArrayList<>();
+        List<SortedBuildTask> tasks = new ArrayList<>();
         for (int i = 0; i < 100; i++) {
-            tasks.add(new BTreeBuildTask(i, new Range(i * 10L, i * 10L + 9), 
new byte[0]));
+            tasks.add(new SortedBuildTask(i, new Range(i * 10L, i * 10L + 9), 
new byte[0]));
         }
 
-        assertThat(BTreeIndexTopoBuilder.calculateParallelism(tasks, 1000L, 
4096)).isEqualTo(1);
+        assertThat(SortedIndexTopoBuilder.calculateParallelism(tasks, 1000L, 
4096)).isEqualTo(1);
     }
 
     @Test
     public void testCalculateParallelismHonorsMaxParallelism() {
-        List<BTreeBuildTask> tasks = new ArrayList<>();
+        List<SortedBuildTask> tasks = new ArrayList<>();
         for (int i = 0; i < 100; i++) {
-            tasks.add(new BTreeBuildTask(i, new Range(i * 1000L, i * 1000L + 
999), new byte[0]));
+            tasks.add(new SortedBuildTask(i, new Range(i * 1000L, i * 1000L + 
999), new byte[0]));
         }
 
-        assertThat(BTreeIndexTopoBuilder.calculateParallelism(tasks, 1000L, 
16)).isEqualTo(16);
+        assertThat(SortedIndexTopoBuilder.calculateParallelism(tasks, 1000L, 
16)).isEqualTo(16);
     }
 
     @Test
     public void testCalculateParallelismKeepsSingleRangeBehavior() {
-        List<BTreeBuildTask> tasks = new ArrayList<>();
-        tasks.add(new BTreeBuildTask(0, new Range(0, 1499), new byte[0]));
+        List<SortedBuildTask> tasks = new ArrayList<>();
+        tasks.add(new SortedBuildTask(0, new Range(0, 1499), new byte[0]));
 
-        assertThat(BTreeIndexTopoBuilder.calculateParallelism(tasks, 1000L, 
16)).isEqualTo(1);
+        assertThat(SortedIndexTopoBuilder.calculateParallelism(tasks, 1000L, 
16)).isEqualTo(1);
     }
 }
diff --git 
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/procedure/CreateGlobalIndexProcedureTest.java
 
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/procedure/CreateGlobalIndexProcedureTest.java
index 5b879628b2..025623bcec 100644
--- 
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/procedure/CreateGlobalIndexProcedureTest.java
+++ 
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/procedure/CreateGlobalIndexProcedureTest.java
@@ -19,6 +19,7 @@
 package org.apache.paimon.flink.procedure;
 
 import org.apache.paimon.globalindex.btree.BTreeIndexOptions;
+import org.apache.paimon.globalindex.sorted.SortedIndexOptions;
 import org.apache.paimon.options.Options;
 
 import org.junit.jupiter.api.Test;
@@ -41,11 +42,11 @@ public class CreateGlobalIndexProcedureTest {
         Options userOptions =
                 CreateGlobalIndexProcedure.createUserOptions(
                         tableOptions,
-                        BTreeIndexOptions.BTREE_INDEX_RECORDS_PER_RANGE.key()
+                        SortedIndexOptions.SORTED_INDEX_RECORDS_PER_RANGE.key()
                                 + "=200;procedure-only=procedure-value");
 
         
assertThat(userOptions.get(BTreeIndexOptions.BTREE_INDEX_COMPRESSION)).isEqualTo("zstd");
-        
assertThat(userOptions.get(BTreeIndexOptions.BTREE_INDEX_RECORDS_PER_RANGE))
+        
assertThat(userOptions.get(SortedIndexOptions.SORTED_INDEX_RECORDS_PER_RANGE))
                 .isEqualTo(200L);
         
assertThat(userOptions.get("unrelated-table-option")).isEqualTo("table-value");
         
assertThat(userOptions.get("procedure-only")).isEqualTo("procedure-value");
diff --git 
a/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/globalindex/DefaultGlobalIndexTopoBuilder.java
 
b/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/globalindex/DefaultGlobalIndexTopoBuilder.java
index 4671006929..b2112149a1 100644
--- 
a/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/globalindex/DefaultGlobalIndexTopoBuilder.java
+++ 
b/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/globalindex/DefaultGlobalIndexTopoBuilder.java
@@ -63,11 +63,6 @@ import static 
org.apache.paimon.utils.Preconditions.checkArgument;
 /** Default topology builder. */
 public class DefaultGlobalIndexTopoBuilder implements 
GlobalIndexTopologyBuilder {
 
-    @Override
-    public String identifier() {
-        return "default";
-    }
-
     @Override
     public List<CommitMessage> buildIndex(
             SparkSession spark,
diff --git 
a/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/globalindex/GlobalIndexTopologyBuilder.java
 
b/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/globalindex/GlobalIndexTopologyBuilder.java
index d7a47cfdc9..74fcec67f6 100644
--- 
a/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/globalindex/GlobalIndexTopologyBuilder.java
+++ 
b/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/globalindex/GlobalIndexTopologyBuilder.java
@@ -31,11 +31,9 @@ import 
org.apache.spark.sql.execution.datasources.v2.DataSourceV2Relation;
 import java.io.IOException;
 import java.util.List;
 
-/** User defined topology builder. */
+/** Topology builder for Spark global index creation. */
 public interface GlobalIndexTopologyBuilder {
 
-    String identifier();
-
     List<CommitMessage> buildIndex(
             SparkSession spark,
             DataSourceV2Relation relation,
@@ -62,7 +60,7 @@ public interface GlobalIndexTopologyBuilder {
             throw new UnsupportedOperationException(
                     String.format(
                             "Topology builder '%s' does not support 
multi-column index, got extra columns: %s",
-                            identifier(), extraFields));
+                            getClass().getSimpleName(), extraFields));
         }
         return buildIndex(
                 spark,
diff --git 
a/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/globalindex/GlobalIndexTopologyBuilderUtils.java
 
b/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/globalindex/GlobalIndexTopologyBuilderUtils.java
index 022e6607eb..405438c593 100644
--- 
a/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/globalindex/GlobalIndexTopologyBuilderUtils.java
+++ 
b/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/globalindex/GlobalIndexTopologyBuilderUtils.java
@@ -18,43 +18,15 @@
 
 package org.apache.paimon.spark.globalindex;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.apache.paimon.spark.globalindex.sorted.SortedIndexTopoBuilder;
 
-import java.util.HashMap;
-import java.util.Map;
-import java.util.ServiceLoader;
-
-/**
- * Utility class for loading {@link GlobalIndexTopologyBuilder} 
implementations via Java's {@link
- * ServiceLoader} mechanism.
- *
- * <p>Factories are loaded once during class initialization and cached for 
subsequent lookups.
- */
+/** Utility class for creating {@link GlobalIndexTopologyBuilder} 
implementations. */
 public class GlobalIndexTopologyBuilderUtils {
 
-    private static final Logger LOG =
-            LoggerFactory.getLogger(GlobalIndexTopologyBuilderUtils.class);
-
-    private static final Map<String, GlobalIndexTopologyBuilder> FACTORIES = 
new HashMap<>();
-
-    static {
-        ServiceLoader<GlobalIndexTopologyBuilder> serviceLoader =
-                ServiceLoader.load(GlobalIndexTopologyBuilder.class);
-
-        for (GlobalIndexTopologyBuilder builder : serviceLoader) {
-            String identifier = builder.identifier();
-            if (FACTORIES.put(identifier, builder) != null) {
-                LOG.warn(
-                        "Found multiple GlobalIndexBuilderFactory 
implementations for type '{}'. "
-                                + "Using the last one loaded.",
-                        identifier);
-            }
-        }
-    }
-
     public static GlobalIndexTopologyBuilder createTopoBuilder(String 
indexType) {
-        GlobalIndexTopologyBuilder builder = FACTORIES.get(indexType);
-        return builder == null ? new DefaultGlobalIndexTopoBuilder() : builder;
+        if (SortedIndexTopoBuilder.supports(indexType)) {
+            return new SortedIndexTopoBuilder();
+        }
+        return new DefaultGlobalIndexTopoBuilder();
     }
 }
diff --git 
a/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/globalindex/btree/BTreeIndexTopoBuilder.java
 
b/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/globalindex/sorted/SortedIndexTopoBuilder.java
similarity index 83%
rename from 
paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/globalindex/btree/BTreeIndexTopoBuilder.java
rename to 
paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/globalindex/sorted/SortedIndexTopoBuilder.java
index 5f7bfa453e..2174418009 100644
--- 
a/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/globalindex/btree/BTreeIndexTopoBuilder.java
+++ 
b/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/globalindex/sorted/SortedIndexTopoBuilder.java
@@ -16,13 +16,13 @@
  * limitations under the License.
  */
 
-package org.apache.paimon.spark.globalindex.btree;
+package org.apache.paimon.spark.globalindex.sorted;
 
 import org.apache.paimon.data.BinaryRow;
 import org.apache.paimon.data.InternalRow;
 import org.apache.paimon.data.serializer.BinaryRowSerializer;
-import org.apache.paimon.globalindex.btree.BTreeGlobalIndexBuilder;
-import org.apache.paimon.globalindex.btree.BTreeIndexOptions;
+import org.apache.paimon.globalindex.sorted.SortedGlobalIndexBuilder;
+import org.apache.paimon.globalindex.sorted.SortedIndexOptions;
 import org.apache.paimon.options.Options;
 import org.apache.paimon.partition.PartitionPredicate;
 import org.apache.paimon.spark.SparkRow;
@@ -52,21 +52,25 @@ import org.apache.spark.sql.functions;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 
-import static 
org.apache.paimon.globalindex.btree.BTreeGlobalIndexBuilder.groupSplitsByRange;
-import static 
org.apache.paimon.globalindex.btree.BTreeGlobalIndexBuilder.splitByContiguousRowRange;
+import static 
org.apache.paimon.globalindex.sorted.SortedGlobalIndexBuilder.groupSplitsByRange;
+import static 
org.apache.paimon.globalindex.sorted.SortedGlobalIndexBuilder.splitByContiguousRowRange;
 
-/** The {@link GlobalIndexTopologyBuilder} for BTree index. */
-public class BTreeIndexTopoBuilder implements GlobalIndexTopologyBuilder {
+/** The {@link GlobalIndexTopologyBuilder} for sorted indexes. */
+public class SortedIndexTopoBuilder implements GlobalIndexTopologyBuilder {
 
-    @Override
-    public String identifier() {
-        return "btree";
+    private static final HashSet<String> SUPPORTED_INDEX_TYPES =
+            new HashSet<>(Arrays.asList("btree", "bitmap"));
+
+    public static boolean supports(String indexType) {
+        return SUPPORTED_INDEX_TYPES.contains(indexType);
     }
 
     @Override
@@ -80,9 +84,9 @@ public class BTreeIndexTopoBuilder implements 
GlobalIndexTopologyBuilder {
             DataField indexField,
             Options options)
             throws IOException {
-        // 1. read the whole dataset of target partitions
-        BTreeGlobalIndexBuilder indexBuilder =
-                new 
BTreeGlobalIndexBuilder(table).withIndexField(indexField.name());
+        SortedGlobalIndexBuilder indexBuilder =
+                new SortedGlobalIndexBuilder(table, indexType, options)
+                        .withIndexField(indexField.name());
         if (partitionPredicate != null) {
             indexBuilder = 
indexBuilder.withPartitionPredicate(partitionPredicate);
         }
@@ -107,8 +111,8 @@ public class BTreeIndexTopoBuilder implements 
GlobalIndexTopologyBuilder {
         List<String> selectedColumns = new 
ArrayList<>(readType.getFieldNames());
 
         // Calculate maximum parallelism bound
-        long recordsPerRange = 
options.get(BTreeIndexOptions.BTREE_INDEX_RECORDS_PER_RANGE);
-        int maxParallelism = 
options.get(BTreeIndexOptions.BTREE_INDEX_BUILD_MAX_PARALLELISM);
+        long recordsPerRange = 
options.get(SortedIndexOptions.SORTED_INDEX_RECORDS_PER_RANGE);
+        int maxParallelism = 
options.get(SortedIndexOptions.SORTED_INDEX_BUILD_MAX_PARALLELISM);
 
         List<CommitMessage> allMessages = new ArrayList<>();
         List<String> sortColumns = new ArrayList<>();
@@ -155,7 +159,7 @@ public class BTreeIndexTopoBuilder implements 
GlobalIndexTopologyBuilder {
                                 .mapPartitions(
                                         
(FlatMapFunction<Iterator<InternalRow>, byte[]>)
                                                 iter ->
-                                                        buildBTreeIndex(
+                                                        buildSortedIndex(
                                                                 iter,
                                                                 
serializedBuilder,
                                                                 range,
@@ -168,7 +172,7 @@ public class BTreeIndexTopoBuilder implements 
GlobalIndexTopologyBuilder {
         return allMessages;
     }
 
-    private static Iterator<byte[]> buildBTreeIndex(
+    private static Iterator<byte[]> buildSortedIndex(
             Iterator<InternalRow> input,
             byte[] serializedBuilder,
             Range range,
@@ -177,9 +181,9 @@ public class BTreeIndexTopoBuilder implements 
GlobalIndexTopologyBuilder {
             throws IOException, ClassNotFoundException {
         final BinaryRowSerializer binaryRowSerializer = new 
BinaryRowSerializer(partitionKeyNum);
         BinaryRow partition = 
binaryRowSerializer.deserializeFromBytes(partitionBytes);
-        BTreeGlobalIndexBuilder builder =
+        SortedGlobalIndexBuilder builder =
                 InstantiationUtil.deserializeObject(
-                        serializedBuilder, 
BTreeGlobalIndexBuilder.class.getClassLoader());
+                        serializedBuilder, 
SortedGlobalIndexBuilder.class.getClassLoader());
         return CommitMessageSerializer.serializeAll(
                         builder.buildForSinglePartition(range, partition, 
input))
                 .iterator();
diff --git 
a/paimon-spark/paimon-spark-common/src/main/resources/META-INF/services/org.apache.paimon.spark.globalindex.GlobalIndexTopologyBuilder
 
b/paimon-spark/paimon-spark-common/src/main/resources/META-INF/services/org.apache.paimon.spark.globalindex.GlobalIndexTopologyBuilder
deleted file mode 100644
index 07a41cb47a..0000000000
--- 
a/paimon-spark/paimon-spark-common/src/main/resources/META-INF/services/org.apache.paimon.spark.globalindex.GlobalIndexTopologyBuilder
+++ /dev/null
@@ -1,16 +0,0 @@
-# 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.
-
-org.apache.paimon.spark.globalindex.btree.BTreeIndexTopoBuilder
diff --git 
a/paimon-spark/paimon-spark-common/src/test/java/org/apache/paimon/spark/procedure/CreateGlobalIndexProcedureTest.java
 
b/paimon-spark/paimon-spark-common/src/test/java/org/apache/paimon/spark/procedure/CreateGlobalIndexProcedureTest.java
index 0a13f06197..991f5d8627 100644
--- 
a/paimon-spark/paimon-spark-common/src/test/java/org/apache/paimon/spark/procedure/CreateGlobalIndexProcedureTest.java
+++ 
b/paimon-spark/paimon-spark-common/src/test/java/org/apache/paimon/spark/procedure/CreateGlobalIndexProcedureTest.java
@@ -23,6 +23,7 @@ import org.apache.paimon.data.BinaryRowWriter;
 import org.apache.paimon.fs.Path;
 import org.apache.paimon.globalindex.IndexedSplit;
 import org.apache.paimon.globalindex.btree.BTreeIndexOptions;
+import org.apache.paimon.globalindex.sorted.SortedIndexOptions;
 import org.apache.paimon.io.DataFileMeta;
 import org.apache.paimon.io.PojoDataFileMeta;
 import org.apache.paimon.manifest.FileKind;
@@ -63,11 +64,11 @@ public class CreateGlobalIndexProcedureTest {
         Options userOptions =
                 CreateGlobalIndexProcedure.createUserOptions(
                         tableOptions,
-                        BTreeIndexOptions.BTREE_INDEX_RECORDS_PER_RANGE.key()
+                        SortedIndexOptions.SORTED_INDEX_RECORDS_PER_RANGE.key()
                                 + "=200, procedure-only=procedure-value");
 
         
assertThat(userOptions.get(BTreeIndexOptions.BTREE_INDEX_COMPRESSION)).isEqualTo("zstd");
-        
assertThat(userOptions.get(BTreeIndexOptions.BTREE_INDEX_RECORDS_PER_RANGE))
+        
assertThat(userOptions.get(SortedIndexOptions.SORTED_INDEX_RECORDS_PER_RANGE))
                 .isEqualTo(200L);
         
assertThat(userOptions.get("unrelated-table-option")).isEqualTo("table-value");
         
assertThat(userOptions.get("procedure-only")).isEqualTo("procedure-value");
diff --git 
a/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/procedure/CreateGlobalIndexProcedureTest.scala
 
b/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/procedure/CreateGlobalIndexProcedureTest.scala
index 9a28029f1c..9182ff70d5 100644
--- 
a/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/procedure/CreateGlobalIndexProcedureTest.scala
+++ 
b/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/procedure/CreateGlobalIndexProcedureTest.scala
@@ -110,6 +110,44 @@ class CreateGlobalIndexProcedureTest extends 
PaimonSparkTestBase with StreamTest
     }
   }
 
+  test("create bitmap global index") {
+    withTable("T") {
+      spark.sql("""
+                  |CREATE TABLE T (id INT, name STRING)
+                  |TBLPROPERTIES (
+                  |  'bucket' = '-1',
+                  |  'global-index.row-count-per-shard' = '10000',
+                  |  'row-tracking.enabled' = 'true',
+                  |  'data-evolution.enabled' = 'true')
+                  |""".stripMargin)
+
+      val values =
+        (0 until 10000).map(i => s"($i, 'name_$i')").mkString(",")
+      spark.sql(s"INSERT INTO T VALUES $values")
+
+      val output =
+        spark
+          .sql(
+            "CALL sys.create_global_index(table => 'test.T', index_column => 
'name', index_type => 'bitmap'," +
+              " options => 'sorted-index.records-per-range=1000')")
+          .collect()
+          .head
+
+      assert(output.getBoolean(0))
+      val table = loadTable("T")
+      val bitmapEntries = table
+        .store()
+        .newIndexFileHandler()
+        .scanEntries()
+        .asScala
+        .filter(_.indexFile().indexType() == "bitmap")
+        .map(_.indexFile())
+      assert(bitmapEntries.nonEmpty)
+      assert(bitmapEntries.map(_.rowCount()).sum == 10000L)
+      bitmapEntries.foreach(e => assert(e.globalIndexMeta() != null))
+    }
+  }
+
   test("create btree global index with multiple partitions") {
     withTable("T") {
       spark.sql("""

Reply via email to