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

yuqi1129 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 f7f37af71e [#12916] fix(doris): fail closed when loading NGRAM_BF 
indexes (#12919)
f7f37af71e is described below

commit f7f37af71eefeb2e646c5735c232b0b74b472af6
Author: StormSpirit <[email protected]>
AuthorDate: Sun Sep 20 21:00:03 2026 +0800

    [#12916] fix(doris): fail closed when loading NGRAM_BF indexes (#12919)
    
    ### What changes were proposed in this pull request?
    
    This pull request makes the Doris metadata read path fail closed for
    native NGRAM_BF indexes instead of mapping them to unrelated existing
    index types.
    
    - Add an explicit NGRAM_BF branch in `mapDorisIndexType()` that throws
    `UnsupportedOperationException` with the index name and normalized
    native type.
    - Read `Index_type` once in `getIndexes()` and give NGRAM_BF detection
    precedence over the legacy `PRIMARY` name mapping, while preserving that
    mapping for every other native type.
    - Add focused unit coverage and real Doris 3.0.6.2 and 4.0.6 integration
    coverage for fail-closed behavior, safe error context, unchanged native
    index type and properties, and the existing INVERTED mapping.
    - Document the temporary unsupported boundary.
    
    ### Why are the changes needed?
    
    Doris exposes NGRAM_BF as a distinct native index type with `gram_size`
    and `bf_size` properties. Gravitino currently has no matching index
    representation, so an ordinary index name falls through to INVERTED
    while the legacy `PRIMARY` name mapping returns PRIMARY_KEY. Both paths
    drop the native semantics and properties. Rejecting the recognized but
    unrepresentable metadata prevents callers from receiving a fabricated
    index definition while keeping unrelated compatibility behavior
    unchanged.
    
    Fix: #12916
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes. Loading a Doris table that contains a native NGRAM_BF index now
    throws `UnsupportedOperationException` instead of returning an unrelated
    existing index type. The error identifies the schema, table, index, and
    native type without including native index properties.
    
    There is no public API, enum, property-key, CREATE TABLE, or ALTER
    behavior change. Existing mappings for BTREE, INVERTED, BITMAP,
    BLOOMFILTER, and ANN remain unchanged, as does the generic fallback for
    unrelated unknown Doris index types.
    
    ### How was this patch tested?
    
    - `./gradlew :catalogs:catalog-jdbc-doris:spotlessCheck` — passed.
    - `./gradlew rat` — passed.
    - `./gradlew :catalogs:catalog-jdbc-doris:test -PskipITs` — 35 tests
    passed, 0 skipped, 0 failures, 0 errors.
    - `./gradlew :catalogs:catalog-jdbc-doris:test --tests
    
'org.apache.gravitino.catalog.doris.integration.test.CatalogDoris3xIT.testNativeNgramBfIndexFailsClosed'
    --tests
    
'org.apache.gravitino.catalog.doris.integration.test.CatalogDoris3xIT.testCreateTableWithInvertedIndex'
    -PskipDockerTests=false -PdorisMultiVersionTest` — Doris 3.0.6.2: 2
    tests passed, 0 skipped, 0 failures, 0 errors.
    - `./gradlew :catalogs:catalog-jdbc-doris:test --tests
    
'org.apache.gravitino.catalog.doris.integration.test.CatalogDoris4xIT.testNativeNgramBfIndexFailsClosed'
    --tests
    
'org.apache.gravitino.catalog.doris.integration.test.CatalogDoris4xIT.testCreateTableWithInvertedIndex'
    -PskipDockerTests=false -PdorisMultiVersionTest` — Doris 4.0.6: 2 tests
    passed, 0 skipped, 0 failures, 0 errors.
    - `./gradlew :catalogs:catalog-jdbc-doris:build -x test` — passed.
    
    Signed-off-by: jiangxt2 <[email protected]>
---
 .../doris/operation/DorisTableOperations.java      | 10 +++-
 .../doris/integration/test/CatalogDoris3xIT.java   | 70 ++++++++++++++++++++++
 .../doris/integration/test/CatalogDoris4xIT.java   | 70 ++++++++++++++++++++++
 .../TestDorisTableOperationsSqlGeneration.java     | 14 +++--
 docs/jdbc-doris-catalog.md                         |  1 +
 5 files changed, 158 insertions(+), 7 deletions(-)

diff --git 
a/catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/operation/DorisTableOperations.java
 
b/catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/operation/DorisTableOperations.java
index d093c30613..8c25e45f56 100644
--- 
a/catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/operation/DorisTableOperations.java
+++ 
b/catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/operation/DorisTableOperations.java
@@ -575,14 +575,14 @@ public class DorisTableOperations extends 
JdbcTableOperations {
         String indexName = resultSet.getString("Key_name");
         String columnName = resultSet.getString("Column_name");
         String dorisIndexType = hasIndexType ? 
resultSet.getString("Index_type") : null;
-        // Doris always names the primary key index "PRIMARY"; detect it first.
+        // Preserve the legacy PRIMARY mapping unless authoritative metadata 
identifies NGRAM_BF,
+        // which must fail closed.
         Index.IndexType gravitinoIndexType;
-        if ("PRIMARY".equals(indexName)) {
+        if ("PRIMARY".equals(indexName) && 
!"NGRAM_BF".equalsIgnoreCase(dorisIndexType)) {
           gravitinoIndexType = Index.IndexType.PRIMARY_KEY;
         } else if (hasIndexType) {
           gravitinoIndexType = mapDorisIndexType(dorisIndexType, indexName);
         } else {
-          // Doris 1.2.x: no Index_type column, infer from index name
           gravitinoIndexType = mapDorisIndexType(null, indexName);
         }
         Map<String, String> indexProperties = Collections.emptyMap();
@@ -633,6 +633,10 @@ public class DorisTableOperations extends 
JdbcTableOperations {
         return Index.IndexType.DATA_SKIPPING_BLOOM_FILTER;
       case "ANN":
         return Index.IndexType.VECTOR;
+      case "NGRAM_BF":
+        throw new UnsupportedOperationException(
+            String.format(
+                "Doris index '%s' uses unsupported native index type 
'NGRAM_BF'", indexName));
       default:
         LOG.warn(
             "Unknown Doris index type '{}' for index '{}', falling back to 
INVERTED",
diff --git 
a/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/integration/test/CatalogDoris3xIT.java
 
b/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/integration/test/CatalogDoris3xIT.java
index 8c5a6ba0ba..fc5e250f90 100644
--- 
a/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/integration/test/CatalogDoris3xIT.java
+++ 
b/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/integration/test/CatalogDoris3xIT.java
@@ -33,11 +33,13 @@ import com.google.common.collect.Maps;
 import java.io.IOException;
 import java.sql.Connection;
 import java.sql.DriverManager;
+import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
@@ -517,6 +519,60 @@ public class CatalogDoris3xIT extends BaseIT {
     assertEquals(colName2, t.index()[0].fieldNames()[0][0]);
   }
 
+  @Test
+  void testNativeNgramBfIndexFailsClosed() throws Exception {
+    TableCatalog tc = catalog.asTableCatalog();
+    String tableName = GravitinoITUtils.genRandomName("t_ngram_bf");
+    NameIdentifier tid = NameIdentifier.of(schemaName, tableName);
+    String indexName = "PRIMARY";
+
+    tc.createTable(
+        tid,
+        basicColumns(),
+        tableComment,
+        Collections.emptyMap(),
+        Transforms.EMPTY_TRANSFORM,
+        hashDist(),
+        null,
+        null);
+
+    DorisContainer dorisContainer = 
containerSuite.getDorisContainer(DorisImageName.VERSION_3_0);
+    String jdbcUrl =
+        String.format(
+            "jdbc:mysql://%s:%d/%s",
+            dorisContainer.getContainerIpAddress(), 
dorisContainer.getFeMysqlPort(), schemaName);
+    try (Connection connection =
+            DriverManager.getConnection(
+                jdbcUrl, DorisContainer.USER_NAME, DorisContainer.PASSWORD);
+        Statement statement = connection.createStatement()) {
+      statement.execute(
+          String.format(
+              "CREATE INDEX `%s` ON `%s` (`%s`) USING NGRAM_BF "
+                  + "PROPERTIES(\"gram_size\"=\"3\", \"bf_size\"=\"256\")",
+              indexName, tableName, colName2));
+
+      Awaitility.await()
+          .atMost(MAX_WAIT_IN_SECONDS, TimeUnit.SECONDS)
+          .pollInterval(WAIT_INTERVAL_IN_SECONDS, TimeUnit.SECONDS)
+          .untilAsserted(
+              () ->
+                  assertEquals(
+                      "NGRAM_BF", getNativeIndexMetadata(statement, tableName, 
indexName).get(0)));
+
+      List<String> metadataBefore = getNativeIndexMetadata(statement, 
tableName, indexName);
+      UnsupportedOperationException exception =
+          assertThrows(UnsupportedOperationException.class, () -> 
tc.loadTable(tid));
+
+      assertTrue(exception.getMessage().contains(schemaName));
+      assertTrue(exception.getMessage().contains(tableName));
+      assertTrue(exception.getMessage().contains(indexName));
+      assertTrue(exception.getMessage().contains("NGRAM_BF"));
+      assertFalse(exception.getMessage().contains("gram_size"));
+      assertFalse(exception.getMessage().contains("bf_size"));
+      assertEquals(metadataBefore, getNativeIndexMetadata(statement, 
tableName, indexName));
+    }
+  }
+
   @Test
   void testExternalTypeRoundTrip() {
     // Verify ExternalType columns survive the create → Doris 3.0 → load 
round-trip.
@@ -773,6 +829,20 @@ public class CatalogDoris3xIT extends BaseIT {
                     "light_schema_change=true should appear after ALTER TABLE 
SET"));
   }
 
+  private List<String> getNativeIndexMetadata(
+      Statement statement, String tableName, String indexName) throws 
SQLException {
+    try (ResultSet resultSet =
+        statement.executeQuery(String.format("SHOW INDEX FROM `%s`", 
tableName))) {
+      while (resultSet.next()) {
+        if (indexName.equals(resultSet.getString("Key_name"))) {
+          return Arrays.asList(
+              resultSet.getString("Index_type"), 
resultSet.getString("Properties"));
+        }
+      }
+    }
+    throw new AssertionError("Index not found: " + indexName);
+  }
+
   private void executeSql(String sql) throws SQLException {
     try (Connection connection =
             DriverManager.getConnection(
diff --git 
a/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/integration/test/CatalogDoris4xIT.java
 
b/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/integration/test/CatalogDoris4xIT.java
index 46e80b6de5..c54154e511 100644
--- 
a/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/integration/test/CatalogDoris4xIT.java
+++ 
b/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/integration/test/CatalogDoris4xIT.java
@@ -33,11 +33,13 @@ import com.google.common.collect.Maps;
 import java.io.IOException;
 import java.sql.Connection;
 import java.sql.DriverManager;
+import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
@@ -552,6 +554,60 @@ public class CatalogDoris4xIT extends BaseIT {
     assertEquals(colName2, t.index()[0].fieldNames()[0][0]);
   }
 
+  @Test
+  void testNativeNgramBfIndexFailsClosed() throws Exception {
+    TableCatalog tc = catalog.asTableCatalog();
+    String tableName = GravitinoITUtils.genRandomName("t_ngram_bf");
+    NameIdentifier tid = NameIdentifier.of(schemaName, tableName);
+    String indexName = "PRIMARY";
+
+    tc.createTable(
+        tid,
+        basicColumns(),
+        tableComment,
+        Collections.emptyMap(),
+        Transforms.EMPTY_TRANSFORM,
+        hashDist(),
+        null,
+        null);
+
+    DorisContainer dorisContainer = 
containerSuite.getDorisContainer(DorisImageName.VERSION_4_0);
+    String jdbcUrl =
+        String.format(
+            "jdbc:mysql://%s:%d/%s",
+            dorisContainer.getContainerIpAddress(), 
dorisContainer.getFeMysqlPort(), schemaName);
+    try (Connection connection =
+            DriverManager.getConnection(
+                jdbcUrl, DorisContainer.USER_NAME, DorisContainer.PASSWORD);
+        Statement statement = connection.createStatement()) {
+      statement.execute(
+          String.format(
+              "CREATE INDEX `%s` ON `%s` (`%s`) USING NGRAM_BF "
+                  + "PROPERTIES(\"gram_size\"=\"3\", \"bf_size\"=\"256\")",
+              indexName, tableName, colName2));
+
+      Awaitility.await()
+          .atMost(MAX_WAIT_IN_SECONDS, TimeUnit.SECONDS)
+          .pollInterval(WAIT_INTERVAL_IN_SECONDS, TimeUnit.SECONDS)
+          .untilAsserted(
+              () ->
+                  assertEquals(
+                      "NGRAM_BF", getNativeIndexMetadata(statement, tableName, 
indexName).get(0)));
+
+      List<String> metadataBefore = getNativeIndexMetadata(statement, 
tableName, indexName);
+      UnsupportedOperationException exception =
+          assertThrows(UnsupportedOperationException.class, () -> 
tc.loadTable(tid));
+
+      assertTrue(exception.getMessage().contains(schemaName));
+      assertTrue(exception.getMessage().contains(tableName));
+      assertTrue(exception.getMessage().contains(indexName));
+      assertTrue(exception.getMessage().contains("NGRAM_BF"));
+      assertFalse(exception.getMessage().contains("gram_size"));
+      assertFalse(exception.getMessage().contains("bf_size"));
+      assertEquals(metadataBefore, getNativeIndexMetadata(statement, 
tableName, indexName));
+    }
+  }
+
   @Test
   void testExternalTypeRoundTrip() {
     // Verify ExternalType columns survive the create → Doris 4.0 → load 
round-trip.
@@ -809,6 +865,20 @@ public class CatalogDoris4xIT extends BaseIT {
                     "light_schema_change=true should appear after ALTER TABLE 
SET"));
   }
 
+  private List<String> getNativeIndexMetadata(
+      Statement statement, String tableName, String indexName) throws 
SQLException {
+    try (ResultSet resultSet =
+        statement.executeQuery(String.format("SHOW INDEX FROM `%s`", 
tableName))) {
+      while (resultSet.next()) {
+        if (indexName.equals(resultSet.getString("Key_name"))) {
+          return Arrays.asList(
+              resultSet.getString("Index_type"), 
resultSet.getString("Properties"));
+        }
+      }
+    }
+    throw new AssertionError("Index not found: " + indexName);
+  }
+
   private void executeSql(String sql) throws SQLException {
     try (Connection connection =
             DriverManager.getConnection(
diff --git 
a/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/operation/TestDorisTableOperationsSqlGeneration.java
 
b/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/operation/TestDorisTableOperationsSqlGeneration.java
index 9b9b7de262..164e9bb6b1 100644
--- 
a/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/operation/TestDorisTableOperationsSqlGeneration.java
+++ 
b/catalogs/catalog-jdbc-doris/src/test/java/org/apache/gravitino/catalog/doris/operation/TestDorisTableOperationsSqlGeneration.java
@@ -460,6 +460,13 @@ public class TestDorisTableOperationsSqlGeneration {
         DorisTableOperations.mapDorisIndexType("BLOOMFILTER", "idx_name"));
     Assertions.assertEquals(
         Index.IndexType.VECTOR, DorisTableOperations.mapDorisIndexType("ANN", 
"idx_name"));
+    UnsupportedOperationException exception =
+        Assertions.assertThrows(
+            UnsupportedOperationException.class,
+            () -> DorisTableOperations.mapDorisIndexType("NGRAM_BF", 
"idx_ngram"));
+    Assertions.assertEquals(
+        "Doris index 'idx_ngram' uses unsupported native index type 
'NGRAM_BF'",
+        exception.getMessage());
     // Unknown type should fall back to INVERTED
     Assertions.assertEquals(
         Index.IndexType.INVERTED,
@@ -529,10 +536,9 @@ public class TestDorisTableOperationsSqlGeneration {
     Mockito.when(metaData.getColumnName(4)).thenReturn("Properties");
     Mockito.when(metaData.getColumnName(5)).thenReturn("Comment");
     Mockito.when(resultSet.next()).thenReturn(true, true, true, false);
-    Mockito.when(resultSet.getString("Key_name"))
-        .thenReturn("idx_first", "idx_ngram", "idx_second");
-    Mockito.when(resultSet.getString("Column_name")).thenReturn("text_a", 
"text_ngram", "text_b");
-    Mockito.when(resultSet.getString("Index_type")).thenReturn("INVERTED", 
"NGRAM_BF", "INVERTED");
+    Mockito.when(resultSet.getString("Key_name")).thenReturn("idx_first", 
"idx_ann", "idx_second");
+    Mockito.when(resultSet.getString("Column_name")).thenReturn("text_a", 
"text_ann", "text_b");
+    Mockito.when(resultSet.getString("Index_type")).thenReturn("INVERTED", 
"ANN", "INVERTED");
     Mockito.when(resultSet.getString("Properties"))
         .thenReturn("(\"parser\" = \"english\")", "(\"support_phrase\" = 
\"true\")");
 
diff --git a/docs/jdbc-doris-catalog.md b/docs/jdbc-doris-catalog.md
index 0a92de67fd..3a315ee69c 100644
--- a/docs/jdbc-doris-catalog.md
+++ b/docs/jdbc-doris-catalog.md
@@ -243,6 +243,7 @@ The Doris catalog supports the following index types. Each 
index applies to a si
 - `SHOW INDEX` does not escape embedded double quotes in property keys or 
values. Gravitino therefore does not guarantee their round-trip and rejects 
metadata that falls outside the supported flat quoted-pair format.
 - Index comments are not currently represented by the Gravitino `Index` API 
and are not preserved on round-trip.
 - `BITMAP` is a write-only legacy type for backward compatibility with Doris 
1.2.x. The write path generates a bare `INDEX` (no USING clause), but the read 
path maps it back to `INVERTED` because Doris 4.0.6 removed BITMAP from the 
grammar. Creating a BITMAP index and reading it back will show `INVERTED`.
+- Native Doris `NGRAM_BF` indexes are detected during table loading but are 
not currently representable by a Gravitino index type. Loading a table that 
contains one fails with `UnsupportedOperationException` instead of mapping it 
to an unrelated Gravitino index type. Creating or altering NGRAM_BF indexes 
through Gravitino and preserving their `gram_size` or `bf_size` properties are 
not supported.
 :::
 
 **Primary Key example:**

Reply via email to