This is an automated email from the ASF dual-hosted git repository.

jerryshao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/main by this push:
     new d4d95ebfe5 [#14066] fix(starrocks): Map index types when loading 
StarRocks tables (#14067)
d4d95ebfe5 is described below

commit d4d95ebfe5c92e18e2ae2e8ae88d95aeb0c0251d
Author: Yuhui <[email protected]>
AuthorDate: Fri Oct 23 15:23:25 2026 +0800

    [#14066] fix(starrocks): Map index types when loading StarRocks tables 
(#14067)
    
    ### What changes were proposed in this pull request?
    
    Map StarRocks `SHOW INDEX` `Index_type` to Gravitino index types when
    loading a table: BITMAP to `BITMAP`, GIN to `INVERTED`, VECTOR to
    `VECTOR`. Indexes without a Gravitino equivalent (e.g. NGRAMBF) are
    skipped. Update the StarRocks catalog docs accordingly.
    
    ### Why are the changes needed?
    
    Every index returned by StarRocks `SHOW INDEX` (BITMAP, NGRAMBF, GIN,
    ...) was reported as a `PRIMARY_KEY` index, so readers saw primary keys
    that the table does not have, while the table's real keys are not
    returned by `SHOW INDEX` at all.
    
    Fix: #14066
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes. Loaded StarRocks tables report BITMAP, GIN and VECTOR indexes with
    their real types, and no longer report NGRAMBF indexes as primary keys.
    
    ### How was this patch tested?
    
    Added a unit test in `TestStarRocksTableOperationsSqlGeneration`, and
    verified BITMAP, GIN and NGRAMBF against StarRocks 3.3 manually.
    
    ---------
    
    Co-authored-by: Jerry Shao <[email protected]>
---
 .../org/apache/gravitino/rel/indexes/Index.java    |  9 +++--
 .../operations/StarRocksTableOperations.java       | 45 ++++++++++++++++++++-
 .../operation/TestStarRocksTableOperations.java    | 25 ++++++++++++
 .../TestStarRocksTableOperationsSqlGeneration.java | 46 ++++++++++++++++++++++
 .../gravitino/api/rel/indexes/index.py             |  9 +++++
 .../tests/unittests/dto/rel/test_index_serdes.py   | 18 +++++++++
 docs/jdbc-starrocks-catalog.md                     | 10 ++++-
 7 files changed, 156 insertions(+), 6 deletions(-)

diff --git a/api/src/main/java/org/apache/gravitino/rel/indexes/Index.java 
b/api/src/main/java/org/apache/gravitino/rel/indexes/Index.java
index dbfdbe0195..e7cf8b429c 100644
--- a/api/src/main/java/org/apache/gravitino/rel/indexes/Index.java
+++ b/api/src/main/java/org/apache/gravitino/rel/indexes/Index.java
@@ -92,7 +92,8 @@ public interface Index {
     /**
      * Bitmap index is a type of database index that uses bit arrays (bitmaps) 
to represent the
      * presence or absence of values in a column, enabling efficient querying 
and data retrieval.
-     * Currently, this type is only applicable to Lance.
+     * Currently, this type is applicable to Lance, is accepted when creating 
Doris tables, and is
+     * reported when loading StarRocks tables.
      */
     BITMAP,
     /**
@@ -103,12 +104,14 @@ public interface Index {
     /**
      * INVERTED index is a data structure used to optimize full-text searches 
by mapping terms to
      * their locations within a dataset, allowing for quick retrieval of 
documents containing
-     * specific words or phrases. Currently, this type is only applicable to 
Lance.
+     * specific words or phrases. Currently, this type is applicable to Lance 
and Doris, and is
+     * reported when loading StarRocks tables.
      */
     INVERTED,
     /**
      * VECTOR index is used to optimize similarity searches in 
high-dimensional vector spaces.
-     * Currently, this type is only applicable to Lance.
+     * Currently, this type is applicable to Lance and Doris, and is reported 
when loading StarRocks
+     * tables.
      */
     VECTOR,
     /** IVF_FLAT (Inverted File with Flat quantization) is an indexing method 
used for efficient */
diff --git 
a/catalogs/catalog-jdbc-starrocks/src/main/java/org/apache/gravitino/catalog/starrocks/operations/StarRocksTableOperations.java
 
b/catalogs/catalog-jdbc-starrocks/src/main/java/org/apache/gravitino/catalog/starrocks/operations/StarRocksTableOperations.java
index 71551d036f..d3a8c2f290 100644
--- 
a/catalogs/catalog-jdbc-starrocks/src/main/java/org/apache/gravitino/catalog/starrocks/operations/StarRocksTableOperations.java
+++ 
b/catalogs/catalog-jdbc-starrocks/src/main/java/org/apache/gravitino/catalog/starrocks/operations/StarRocksTableOperations.java
@@ -32,10 +32,12 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
+import javax.annotation.Nullable;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.BooleanUtils;
 import org.apache.commons.lang3.StringUtils;
@@ -260,12 +262,24 @@ public class StarRocksTableOperations extends 
JdbcTableOperations {
     try (PreparedStatement preparedStatement = 
connection.prepareStatement(sql);
         ResultSet resultSet = preparedStatement.executeQuery()) {
 
+      // SHOW INDEX lists only secondary indexes, one row per single-column 
index; table keys are
+      // not included.
       List<Index> indexes = new ArrayList<>();
       while (resultSet.next()) {
         String indexName = resultSet.getString("Key_name");
         String columnName = resultSet.getString("Column_name");
-        indexes.add(
-            Indexes.of(Index.IndexType.PRIMARY_KEY, indexName, new String[][] 
{{columnName}}));
+        String starRocksIndexType = resultSet.getString("Index_type");
+        Index.IndexType indexType = mapStarRocksIndexType(starRocksIndexType);
+        if (indexType == null) {
+          LOG.warn(
+              "Skip StarRocks index '{}' on table {}.{} with unsupported type 
'{}'",
+              indexName,
+              databaseName,
+              tableName,
+              starRocksIndexType);
+          continue;
+        }
+        indexes.add(Indexes.of(indexType, indexName, new String[][] 
{{columnName}}));
       }
       return indexes;
     } catch (SQLException e) {
@@ -630,4 +644,31 @@ public class StarRocksTableOperations extends 
JdbcTableOperations {
             .build();
     return appendColumnDefinition(newColumn, sqlBuilder).toString();
   }
+
+  /**
+   * Maps the {@code Index_type} value of StarRocks {@code SHOW INDEX} to a 
Gravitino index type.
+   * The value may carry index properties, e.g. {@code GIN("parser" = 
"english")}; only the type
+   * name is used and the properties are not kept, since StarRocks indexes 
cannot be created or
+   * altered through Gravitino.
+   *
+   * @param indexType the {@code Index_type} value returned by StarRocks
+   * @return the Gravitino index type, or null if the type has no Gravitino 
equivalent
+   */
+  @Nullable
+  private static Index.IndexType mapStarRocksIndexType(@Nullable String 
indexType) {
+    if (indexType == null) {
+      return null;
+    }
+    String typeName = StringUtils.substringBefore(indexType, "(").trim();
+    switch (typeName.toUpperCase(Locale.ROOT)) {
+      case "BITMAP":
+        return Index.IndexType.BITMAP;
+      case "GIN":
+        return Index.IndexType.INVERTED;
+      case "VECTOR":
+        return Index.IndexType.VECTOR;
+      default:
+        return null;
+    }
+  }
 }
diff --git 
a/catalogs/catalog-jdbc-starrocks/src/test/java/org/apache/gravitino/catalog/starrocks/operation/TestStarRocksTableOperations.java
 
b/catalogs/catalog-jdbc-starrocks/src/test/java/org/apache/gravitino/catalog/starrocks/operation/TestStarRocksTableOperations.java
index fb4a4c0ae4..06a5b74200 100644
--- 
a/catalogs/catalog-jdbc-starrocks/src/test/java/org/apache/gravitino/catalog/starrocks/operation/TestStarRocksTableOperations.java
+++ 
b/catalogs/catalog-jdbc-starrocks/src/test/java/org/apache/gravitino/catalog/starrocks/operation/TestStarRocksTableOperations.java
@@ -846,4 +846,29 @@ public class TestStarRocksTableOperations extends 
TestStarRocks {
       TABLE_OPERATIONS.drop(databaseName, tableName);
     }
   }
+
+  @ParameterizedTest
+  @ValueSource(strings = {"PRIMARY", "UNIQUE", "DUPLICATE"})
+  void testLoadTableReportsOnlySecondaryIndexes(String keyType) throws 
SQLException {
+    String tableName = GravitinoITUtils.genRandomName("starrocks_index");
+    try (Connection connection = DATA_SOURCE.getConnection();
+        Statement statement = connection.createStatement()) {
+      statement.executeUpdate(
+          String.format(
+              "CREATE TABLE `%s`.`%s` (`id` INT NOT NULL, `region` VARCHAR(32) 
NOT NULL, "
+                  + "INDEX `idx_region` (`region`) USING BITMAP) "
+                  + "%s KEY(`id`, `region`) DISTRIBUTED BY HASH(`id`) BUCKETS 
1 "
+                  + "PROPERTIES (\"replication_num\" = \"1\")",
+              databaseName, tableName, keyType));
+    }
+    try {
+      JdbcTable table = TABLE_OPERATIONS.load(databaseName, tableName);
+      Assertions.assertEquals(1, table.index().length);
+      Assertions.assertEquals(Index.IndexType.BITMAP, table.index()[0].type());
+      Assertions.assertEquals("idx_region", table.index()[0].name());
+      Assertions.assertArrayEquals(new String[][] {{"region"}}, 
table.index()[0].fieldNames());
+    } finally {
+      TABLE_OPERATIONS.drop(databaseName, tableName);
+    }
+  }
 }
diff --git 
a/catalogs/catalog-jdbc-starrocks/src/test/java/org/apache/gravitino/catalog/starrocks/operation/TestStarRocksTableOperationsSqlGeneration.java
 
b/catalogs/catalog-jdbc-starrocks/src/test/java/org/apache/gravitino/catalog/starrocks/operation/TestStarRocksTableOperationsSqlGeneration.java
index 36ab0c3b84..28dd162b7a 100644
--- 
a/catalogs/catalog-jdbc-starrocks/src/test/java/org/apache/gravitino/catalog/starrocks/operation/TestStarRocksTableOperationsSqlGeneration.java
+++ 
b/catalogs/catalog-jdbc-starrocks/src/test/java/org/apache/gravitino/catalog/starrocks/operation/TestStarRocksTableOperationsSqlGeneration.java
@@ -19,6 +19,9 @@
 package org.apache.gravitino.catalog.starrocks.operation;
 
 import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
@@ -46,6 +49,7 @@ import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.EmptySource;
 import org.junit.jupiter.params.provider.EnumSource;
 import org.junit.jupiter.params.provider.NullSource;
+import org.mockito.Mockito;
 
 public class TestStarRocksTableOperationsSqlGeneration {
 
@@ -100,6 +104,11 @@ public class TestStarRocksTableOperationsSqlGeneration {
       return generateAlterTableSql("database", tableName, changes);
     }
 
+    List<Index> indexes(Connection connection, String databaseName, String 
tableName)
+        throws SQLException {
+      return getIndexes(connection, databaseName, tableName);
+    }
+
     @Override
     protected JdbcTable getOrCreateTable(
         String databaseName, String tableName, JdbcTable lazyLoadCreateTable) {
@@ -410,6 +419,43 @@ public class TestStarRocksTableOperationsSqlGeneration {
         "StarRocks does not support adding an auto-increment column: col2", 
exception.getMessage());
   }
 
+  @Test
+  void testGetIndexesMapsTypesAndSkipsUnsupported() throws SQLException {
+    TestableStarRocksTableOperations ops = new 
TestableStarRocksTableOperations();
+    Connection connection = Mockito.mock(Connection.class);
+    PreparedStatement statement = Mockito.mock(PreparedStatement.class);
+    ResultSet resultSet = Mockito.mock(ResultSet.class);
+    Mockito.when(connection.prepareStatement("SHOW INDEX FROM `table` FROM 
`database`"))
+        .thenReturn(statement);
+    Mockito.when(statement.executeQuery()).thenReturn(resultSet);
+    Mockito.when(resultSet.next()).thenReturn(true, true, true, true, true, 
true, false);
+    Mockito.when(resultSet.getString("Key_name"))
+        .thenReturn("idx_region", "idx_ngram", "idx_text", "idx_vector", 
"idx_unknown", "idx_null");
+    Mockito.when(resultSet.getString("Column_name"))
+        .thenReturn("region", "name", "text", "embedding", "c5", "c6");
+    Mockito.when(resultSet.getString("Index_type"))
+        .thenReturn(
+            "BITMAP",
+            "NGRAMBF(\"gram_num\" = \"4\")",
+            "GIN(\"parser\" = \"english\")",
+            "VECTOR(\"index_type\" = \"hnsw\", \"dim\" = \"3\")",
+            "UNKNOWN",
+            null);
+
+    List<Index> indexes = ops.indexes(connection, "database", "table");
+
+    Assertions.assertEquals(3, indexes.size());
+    Assertions.assertEquals(Index.IndexType.BITMAP, indexes.get(0).type());
+    Assertions.assertEquals("idx_region", indexes.get(0).name());
+    Assertions.assertArrayEquals(new String[][] {{"region"}}, 
indexes.get(0).fieldNames());
+    Assertions.assertEquals(Index.IndexType.INVERTED, indexes.get(1).type());
+    Assertions.assertEquals("idx_text", indexes.get(1).name());
+    Assertions.assertArrayEquals(new String[][] {{"text"}}, 
indexes.get(1).fieldNames());
+    Assertions.assertEquals(Index.IndexType.VECTOR, indexes.get(2).type());
+    Assertions.assertEquals("idx_vector", indexes.get(2).name());
+    Assertions.assertArrayEquals(new String[][] {{"embedding"}}, 
indexes.get(2).fieldNames());
+  }
+
   @Test
   void testRenameColumnQuotesIdentifiers() {
     TestableStarRocksTableOperations ops = new 
TestableStarRocksTableOperations("id", "order-id");
diff --git a/clients/client-python/gravitino/api/rel/indexes/index.py 
b/clients/client-python/gravitino/api/rel/indexes/index.py
index 0fe335acc2..2803423490 100644
--- a/clients/client-python/gravitino/api/rel/indexes/index.py
+++ b/clients/client-python/gravitino/api/rel/indexes/index.py
@@ -52,6 +52,15 @@ class Index(ABC):
         columns that are not designated as the primary key. The uniqueness 
constraint imposed by
         UNIQUE KEY helps in avoiding redundancy and ensuring data accuracy in 
the database."""
 
+        BITMAP = "BITMAP"
+        """Bitmap index, which uses bit arrays to represent the presence of 
values in a column."""
+
+        INVERTED = "INVERTED"
+        """Inverted index, which maps terms to their locations to speed up 
full-text searches."""
+
+        VECTOR = "VECTOR"
+        """Vector index, which speeds up similarity searches in 
high-dimensional vector spaces."""
+
         DATA_SKIPPING_ANNOY = "DATA_SKIPPING_ANNOY"
         """Legacy ClickHouse Annoy index metadata. This type is 
metadata-only."""
 
diff --git a/clients/client-python/tests/unittests/dto/rel/test_index_serdes.py 
b/clients/client-python/tests/unittests/dto/rel/test_index_serdes.py
index 99247713ac..263270ecd4 100644
--- a/clients/client-python/tests/unittests/dto/rel/test_index_serdes.py
+++ b/clients/client-python/tests/unittests/dto/rel/test_index_serdes.py
@@ -177,6 +177,24 @@ class TestIndexSerdes(unittest.TestCase):
         self.assertEqual(Index.IndexType.DATA_SKIPPING_USEARCH, 
round_tripped[1].type())
         self.assertEqual(usearch_properties, round_tripped[1].properties())
 
+    def test_index_serdes_bitmap_inverted_vector(self):
+        for index_type in (
+            Index.IndexType.BITMAP,
+            Index.IndexType.INVERTED,
+            Index.IndexType.VECTOR,
+        ):
+            index = IndexSerdes.deserialize(
+                {
+                    "indexType": index_type.value,
+                    "name": "idx",
+                    "fieldNames": [["col"]],
+                }
+            )
+            self.assertEqual(index_type, index.type())
+            self.assertEqual(
+                index_type.value, IndexSerdes.serialize(index)["indexType"]
+            )
+
     def test_vector_similarity_index_properties_round_trip(self):
         json_string = """
         {
diff --git a/docs/jdbc-starrocks-catalog.md b/docs/jdbc-starrocks-catalog.md
index 318ea46114..3bac876332 100644
--- a/docs/jdbc-starrocks-catalog.md
+++ b/docs/jdbc-starrocks-catalog.md
@@ -153,7 +153,15 @@ Unsupported for now.
 
 ### Table Indexes
 
-Unsupported. Creating a table with non-empty index definitions is rejected 
before the table is created.
+Creating a table with non-empty index definitions is rejected before the table 
is created.
+
+When loading a table, Gravitino reports StarRocks secondary indexes with the 
following type mapping. Indexes of other types (e.g. `NGRAMBF`) are not 
reported, and table keys are not reported as indexes.
+
+| StarRocks Index Type | Gravitino Index Type |
+|----------------------|----------------------|
+| `BITMAP`             | `BITMAP`             |
+| `GIN`                | `INVERTED`           |
+| `VECTOR`             | `VECTOR`             |
 
 ### Table Partitioning
 

Reply via email to