yuqi1129 commented on code in PR #11731:
URL: https://github.com/apache/gravitino/pull/11731#discussion_r3449721022


##########
catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/operation/DorisTableOperations.java:
##########
@@ -387,15 +481,67 @@ protected List<Index> getIndexes(Connection connection, 
String databaseName, Str
       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}}));
+        // Index_type column may not exist in older Doris versions (e.g. 
1.2.x).
+        // Fall back to null so mapDorisIndexType can infer from indexName.
+        String indexType;
+        try {
+          indexType = resultSet.getString("Index_type");
+        } catch (SQLException e) {
+          LOG.warn(
+              "Index_type column not available in SHOW INDEX output, "
+                  + "inferring index type from index name '{}'. "
+                  + "Upgrade to Doris 3.0+ for accurate index type mapping.",
+              indexName);
+          indexType = null;
+        }
+        Index.IndexType gravitinoIndexType = mapDorisIndexType(indexType, 
indexName);
+        indexes.add(Indexes.of(gravitinoIndexType, indexName, new String[][] 
{{columnName}}));
       }
       return indexes;
     } catch (SQLException e) {
       throw exceptionMapper.toGravitinoException(e);
     }
   }
 
+  @VisibleForTesting
+  static Index.IndexType mapDorisIndexType(String indexType, String indexName) 
{
+    if (indexType == null) {
+      // Index_type column unavailable (Doris 1.2.x) or returned null.
+      // Infer from index name: "PRIMARY" is the primary key, everything else 
defaults to
+      // INVERTED as a safe fallback (Doris 1.2.x indexes are all BTREE-based 
key indexes,
+      // but without Index_type we cannot distinguish UNIQUE_KEY from 
PRIMARY_KEY).
+      if ("PRIMARY".equals(indexName)) {
+        return Index.IndexType.PRIMARY_KEY;
+      }
+      LOG.warn(
+          "Index_type is null for index '{}', defaulting to INVERTED. "
+              + "Load table metadata from Doris 3.0+ for accurate index type 
mapping.",
+          indexName);
+      return Index.IndexType.INVERTED;
+    }
+    switch (indexType.toUpperCase()) {
+      case "BTREE":
+        if ("PRIMARY".equals(indexName)) {
+          return Index.IndexType.PRIMARY_KEY;
+        }
+        return Index.IndexType.UNIQUE_KEY;
+      case "INVERTED":
+        return Index.IndexType.INVERTED;
+      case "BITMAP":

Review Comment:
   Read maps `BITMAP`->`IndexType.BITMAP`, but the write path forces 
`BITMAP`->`USING INVERTED` (around line 302). A BITMAP index round-tripped 
through Gravitino silently becomes INVERTED. Pick one direction consistently, 
ideally version-aware.



##########
catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/operation/DorisTableOperations.java:
##########
@@ -233,23 +244,106 @@ private static void appendIndexesSql(Index[] indexes, 
StringBuilder sqlBuilder)
       return;
     }
 
-    // validate indexes
-    Arrays.stream(indexes)
-        .forEach(
-            index -> {
-              if (index.fieldNames().length > 1) {
-                throw new IllegalArgumentException("Index does not support 
multi fields in Doris");
-              }
-            });
+    // Log index types for debugging
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("appendIndexesSql: processing {} indexes", indexes.length);
+      for (Index index : indexes) {
+        LOG.debug("  index: name={}, type={}", index.name(), index.type());
+      }
+    }
 
-    String indexSql =
+    // Filter out PRIMARY_KEY and UNIQUE_KEY indexes — they are table model 
keys in Doris,
+    // defined via UNIQUE KEY(col) / DUPLICATE KEY(col) syntax, not via INDEX 
clause.
+    List<Index> nonKeyIndexes =
         Arrays.stream(indexes)
-            .map(index -> String.format("INDEX %s (%s)", index.name(), 
index.fieldNames()[0][0]))
+            .filter(
+                index ->
+                    index.type() != Index.IndexType.PRIMARY_KEY
+                        && index.type() != Index.IndexType.UNIQUE_KEY)
+            .collect(Collectors.toList());
+
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("appendIndexesSql: {} non-key indexes after filtering", 
nonKeyIndexes.size());
+    }
+
+    if (nonKeyIndexes.isEmpty()) {
+      return;
+    }
+
+    nonKeyIndexes.forEach(
+        index -> {
+          if (index.fieldNames().length > 1) {
+            throw new IllegalArgumentException("Index does not support multi 
fields in Doris");
+          }
+        });
+
+    String indexSql =
+        nonKeyIndexes.stream()
+            .map(
+                index -> {
+                  String usingClause = mapIndexTypeToUsingClause(index.type());
+                  return String.format(
+                      "INDEX `%s` (`%s`) %s", index.name(), 
index.fieldNames()[0][0], usingClause);
+                })
             .collect(Collectors.joining(",\n"));
 
     sqlBuilder.append(",").append(NEW_LINE).append(indexSql);
   }
 
+  private static String mapIndexTypeToUsingClause(Index.IndexType indexType) {
+    switch (indexType) {
+      case PRIMARY_KEY:
+      case UNIQUE_KEY:
+        throw new IllegalStateException(
+            "PRIMARY_KEY and UNIQUE_KEY should be filtered out before index 
SQL generation, got: "
+                + indexType);
+      case INVERTED:
+        return "USING INVERTED";
+      case BITMAP:

Review Comment:
   This unconditionally emits `USING INVERTED` for every non-key index 
(including BITMAP). There is no Doris server-version detection anywhere in the 
catalog, so the "cross-version compatibility" here is really one hardcoded 
choice applied to all versions. Two concerns:
   
   1. Inverted index is not GA until Doris 2.0. On 1.2.x (still in the test 
matrix as `VERSION_1_2`) `USING INVERTED` likely fails, whereas the previous 
`INDEX name (col)` form defaulted to a bitmap index and worked — this looks 
like a regression for the oldest supported version.
   2. BITMAP is silently rewritten to INVERTED even on versions that still 
support BITMAP (2.x/3.x), changing user intent. Note the read path 
(`mapDorisIndexType`) maps `BITMAP`->`BITMAP`, so write/read are asymmetric.
   
   Consider detecting the server version once and branching, or explicitly 
narrowing the supported range. Please also verify against Doris docs: when 
inverted GA'd, and whether 3.0+ truly converts BITMAP->INVERTED internally / 
4.0.6 removed BITMAP.



##########
catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/operation/DorisTableOperations.java:
##########
@@ -233,23 +244,106 @@ private static void appendIndexesSql(Index[] indexes, 
StringBuilder sqlBuilder)
       return;
     }
 
-    // validate indexes
-    Arrays.stream(indexes)
-        .forEach(
-            index -> {
-              if (index.fieldNames().length > 1) {
-                throw new IllegalArgumentException("Index does not support 
multi fields in Doris");
-              }
-            });
+    // Log index types for debugging
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("appendIndexesSql: processing {} indexes", indexes.length);
+      for (Index index : indexes) {
+        LOG.debug("  index: name={}, type={}", index.name(), index.type());
+      }
+    }
 
-    String indexSql =
+    // Filter out PRIMARY_KEY and UNIQUE_KEY indexes — they are table model 
keys in Doris,
+    // defined via UNIQUE KEY(col) / DUPLICATE KEY(col) syntax, not via INDEX 
clause.
+    List<Index> nonKeyIndexes =
         Arrays.stream(indexes)
-            .map(index -> String.format("INDEX %s (%s)", index.name(), 
index.fieldNames()[0][0]))
+            .filter(
+                index ->
+                    index.type() != Index.IndexType.PRIMARY_KEY
+                        && index.type() != Index.IndexType.UNIQUE_KEY)
+            .collect(Collectors.toList());
+
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("appendIndexesSql: {} non-key indexes after filtering", 
nonKeyIndexes.size());
+    }
+
+    if (nonKeyIndexes.isEmpty()) {
+      return;
+    }
+
+    nonKeyIndexes.forEach(
+        index -> {
+          if (index.fieldNames().length > 1) {
+            throw new IllegalArgumentException("Index does not support multi 
fields in Doris");
+          }
+        });
+
+    String indexSql =
+        nonKeyIndexes.stream()
+            .map(
+                index -> {
+                  String usingClause = mapIndexTypeToUsingClause(index.type());
+                  return String.format(
+                      "INDEX `%s` (`%s`) %s", index.name(), 
index.fieldNames()[0][0], usingClause);
+                })
             .collect(Collectors.joining(",\n"));
 
     sqlBuilder.append(",").append(NEW_LINE).append(indexSql);
   }
 
+  private static String mapIndexTypeToUsingClause(Index.IndexType indexType) {
+    switch (indexType) {
+      case PRIMARY_KEY:
+      case UNIQUE_KEY:
+        throw new IllegalStateException(
+            "PRIMARY_KEY and UNIQUE_KEY should be filtered out before index 
SQL generation, got: "
+                + indexType);
+      case INVERTED:
+        return "USING INVERTED";
+      case BITMAP:
+        // Doris 4.0.6 removed BITMAP index support; 3.0+ internally converts 
BITMAP to INVERTED.
+        // Always generate USING INVERTED for cross-version compatibility.
+        return "USING INVERTED";
+      case VECTOR:
+        return "USING ANN";
+      default:
+        // Doris does not support BTREE as an explicit USING clause for 
non-key indexes.
+        // Known types that reach here (e.g. BLOOMFILTER) are table-level 
properties, not indexes.
+        throw new UnsupportedOperationException(
+            "Doris does not support index type " + indexType + " via ADD INDEX 
syntax");
+    }
+  }
+
+  private static void appendTableModelKeySql(Index[] indexes, StringBuilder 
sqlBuilder) {

Review Comment:
   Validation/generation mismatch. The base `validateIncrementCol(columns, 
indexes)` accepts an auto-increment column keyed by **PRIMARY_KEY or 
UNIQUE_KEY**, but this method only emits `UNIQUE KEY(...)` for UNIQUE_KEY and 
silently skips PRIMARY_KEY. So a table with an auto-inc column + PRIMARY_KEY 
passes validation yet generates DDL with no key (DUPLICATE model): the key 
intent is lost and Doris may reject the auto-inc column. Either map 
PRIMARY_KEY->`UNIQUE KEY` on supported versions, or reject it clearly — don't 
accept in validation and drop in generation. (Also a behavior change vs. the 
old code, which emitted PRIMARY_KEY as an INDEX; uniqueness is now silently 
dropped on create.)



##########
catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/operation/DorisTableOperations.java:
##########
@@ -233,23 +244,106 @@ private static void appendIndexesSql(Index[] indexes, 
StringBuilder sqlBuilder)
       return;
     }
 
-    // validate indexes
-    Arrays.stream(indexes)
-        .forEach(
-            index -> {
-              if (index.fieldNames().length > 1) {
-                throw new IllegalArgumentException("Index does not support 
multi fields in Doris");
-              }
-            });
+    // Log index types for debugging
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("appendIndexesSql: processing {} indexes", indexes.length);
+      for (Index index : indexes) {
+        LOG.debug("  index: name={}, type={}", index.name(), index.type());
+      }
+    }
 
-    String indexSql =
+    // Filter out PRIMARY_KEY and UNIQUE_KEY indexes — they are table model 
keys in Doris,
+    // defined via UNIQUE KEY(col) / DUPLICATE KEY(col) syntax, not via INDEX 
clause.
+    List<Index> nonKeyIndexes =
         Arrays.stream(indexes)
-            .map(index -> String.format("INDEX %s (%s)", index.name(), 
index.fieldNames()[0][0]))
+            .filter(
+                index ->
+                    index.type() != Index.IndexType.PRIMARY_KEY
+                        && index.type() != Index.IndexType.UNIQUE_KEY)
+            .collect(Collectors.toList());
+
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("appendIndexesSql: {} non-key indexes after filtering", 
nonKeyIndexes.size());
+    }
+
+    if (nonKeyIndexes.isEmpty()) {
+      return;
+    }
+
+    nonKeyIndexes.forEach(
+        index -> {
+          if (index.fieldNames().length > 1) {
+            throw new IllegalArgumentException("Index does not support multi 
fields in Doris");
+          }
+        });
+
+    String indexSql =
+        nonKeyIndexes.stream()
+            .map(
+                index -> {
+                  String usingClause = mapIndexTypeToUsingClause(index.type());
+                  return String.format(
+                      "INDEX `%s` (`%s`) %s", index.name(), 
index.fieldNames()[0][0], usingClause);
+                })
             .collect(Collectors.joining(",\n"));
 
     sqlBuilder.append(",").append(NEW_LINE).append(indexSql);
   }
 
+  private static String mapIndexTypeToUsingClause(Index.IndexType indexType) {
+    switch (indexType) {
+      case PRIMARY_KEY:
+      case UNIQUE_KEY:
+        throw new IllegalStateException(
+            "PRIMARY_KEY and UNIQUE_KEY should be filtered out before index 
SQL generation, got: "
+                + indexType);
+      case INVERTED:
+        return "USING INVERTED";
+      case BITMAP:
+        // Doris 4.0.6 removed BITMAP index support; 3.0+ internally converts 
BITMAP to INVERTED.
+        // Always generate USING INVERTED for cross-version compatibility.
+        return "USING INVERTED";
+      case VECTOR:
+        return "USING ANN";
+      default:
+        // Doris does not support BTREE as an explicit USING clause for 
non-key indexes.
+        // Known types that reach here (e.g. BLOOMFILTER) are table-level 
properties, not indexes.
+        throw new UnsupportedOperationException(
+            "Doris does not support index type " + indexType + " via ADD INDEX 
syntax");
+    }
+  }
+
+  private static void appendTableModelKeySql(Index[] indexes, StringBuilder 
sqlBuilder) {
+    // Only emit UNIQUE KEY declaration when an explicit UNIQUE_KEY index is 
present.
+    // PRIMARY_KEY indexes are intentionally skipped: Doris 1.2.x does not 
support UNIQUE KEY
+    // syntax (only DUPLICATE KEY / AGGREGATE KEY), and the default DUPLICATE 
KEY model already
+    // satisfies PRIMARY_KEY's uniqueness semantics. Users targeting Doris 
2.0+ who need the
+    // UNIQUE KEY model should pass Index.IndexType.UNIQUE_KEY instead.
+    long keyIndexCount =
+        Arrays.stream(indexes)
+            .filter(
+                index ->
+                    index.type() == Index.IndexType.PRIMARY_KEY
+                        || index.type() == Index.IndexType.UNIQUE_KEY)
+            .count();
+    Preconditions.checkArgument(
+        keyIndexCount <= 1,
+        "Doris table model key can have at most one PRIMARY_KEY or UNIQUE_KEY 
index, got: %s",
+        keyIndexCount);
+
+    Arrays.stream(indexes)
+        .filter(index -> index.type() == Index.IndexType.UNIQUE_KEY)
+        .findFirst()
+        .ifPresent(
+            keyIndex -> {
+              String cols =
+                  Arrays.stream(keyIndex.fieldNames())
+                      .map(field -> BACK_QUOTE + field[0] + BACK_QUOTE)
+                      .collect(Collectors.joining(", "));
+              sqlBuilder.append(NEW_LINE).append("UNIQUE 
KEY(").append(cols).append(")");

Review Comment:
   Doris requires key columns to be an ordered prefix of the schema. This emits 
`UNIQUE KEY(col)` without validating/reordering column position, so a key 
column that isn't first produces invalid DDL (Doris: "Key columns should be a 
ordered prefix of the schema"). At minimum document the constraint, or validate 
it.



##########
catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/operation/DorisTableOperations.java:
##########
@@ -387,15 +481,67 @@ protected List<Index> getIndexes(Connection connection, 
String databaseName, Str
       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}}));
+        // Index_type column may not exist in older Doris versions (e.g. 
1.2.x).
+        // Fall back to null so mapDorisIndexType can infer from indexName.
+        String indexType;
+        try {
+          indexType = resultSet.getString("Index_type");
+        } catch (SQLException e) {
+          LOG.warn(
+              "Index_type column not available in SHOW INDEX output, "
+                  + "inferring index type from index name '{}'. "
+                  + "Upgrade to Doris 3.0+ for accurate index type mapping.",
+              indexName);
+          indexType = null;
+        }
+        Index.IndexType gravitinoIndexType = mapDorisIndexType(indexType, 
indexName);
+        indexes.add(Indexes.of(gravitinoIndexType, indexName, new String[][] 
{{columnName}}));
       }
       return indexes;
     } catch (SQLException e) {
       throw exceptionMapper.toGravitinoException(e);
     }
   }
 
+  @VisibleForTesting
+  static Index.IndexType mapDorisIndexType(String indexType, String indexName) 
{
+    if (indexType == null) {
+      // Index_type column unavailable (Doris 1.2.x) or returned null.
+      // Infer from index name: "PRIMARY" is the primary key, everything else 
defaults to
+      // INVERTED as a safe fallback (Doris 1.2.x indexes are all BTREE-based 
key indexes,
+      // but without Index_type we cannot distinguish UNIQUE_KEY from 
PRIMARY_KEY).
+      if ("PRIMARY".equals(indexName)) {
+        return Index.IndexType.PRIMARY_KEY;
+      }
+      LOG.warn(
+          "Index_type is null for index '{}', defaulting to INVERTED. "
+              + "Load table metadata from Doris 3.0+ for accurate index type 
mapping.",
+          indexName);
+      return Index.IndexType.INVERTED;

Review Comment:
   Inconsistent mapping for the same physical index: when `Index_type` is 
present and `BTREE` (non-PRIMARY) you return `UNIQUE_KEY` (line 527), but when 
the column is absent (1.2.x, where everything is BTREE) the same index defaults 
to `INVERTED` here. For consistency the null branch should default to 
`UNIQUE_KEY`, matching the BTREE case the comment describes.



##########
catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/operation/DorisTableOperations.java:
##########
@@ -87,7 +87,21 @@ protected String generateCreateTableSql(
       Distribution distribution,
       Index[] indexes) {
 
-    validateIncrementCol(columns);
+    // Log index information for debugging
+    if (LOG.isDebugEnabled()) {
+      LOG.debug(
+          "generateCreateTableSql: tableName={}, indexes.length={}", 
tableName, indexes.length);
+      for (int i = 0; i < indexes.length; i++) {
+        LOG.debug(
+            "  indexes[{}]: name={}, type={}, type.name()={}",
+            i,
+            indexes[i].name(),
+            indexes[i].type(),
+            indexes[i].type().name());
+      }
+    }
+
+    validateIncrementCol(columns, indexes);

Review Comment:
   Delegating to the base validator is fine, but it also removes the only 
version guard: Doris auto-increment requires 2.1.0+. On older servers the 
column now reaches Doris and fails with a less clear server-side error (the old 
code gave a clear message). The title says 3.0+, but this affects 1.2.x/2.0.x 
too — consider a clear lower-bound check or documenting the minimum.



##########
integration-test-common/src/test/java/org/apache/gravitino/integration/test/container/DorisImageName.java:
##########
@@ -0,0 +1,48 @@
+/*
+ * 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.gravitino.integration.test.container;
+
+/**
+ * Doris Docker image versions for multi-version integration testing.
+ *
+ * <p>{@link #VERSION_1_2} uses the Gravitino CI all-in-one image (FE + BE in 
a single image) for
+ * backward-compatible testing against Doris 1.2.x.
+ *
+ * <p>{@link #VERSION_3_0} uses the official Apache Doris split images ({@code 
apache/doris:fe-3.x}
+ * and {@code apache/doris:be-3.x}). {@link DorisContainer} detects the {@code 
:fe-} prefix and
+ * automatically derives the corresponding BE image, passing both via compose 
environment variables.
+ *
+ * @see DorisContainer
+ * @see ContainerSuite#startDorisContainer(DorisImageName)
+ */
+public enum DorisImageName {
+  VERSION_1_2("apache/gravitino-ci:doris-0.1.5"),
+  VERSION_3_0("apache/doris:fe-3.0.6.2");

Review Comment:
   The PR description says integration tests ran on Doris 4.0.6 and 3.0.6.2, 
but the image matrix only has `VERSION_1_2` and `VERSION_3_0` (3.0.6.2) — no 
4.0.6. The BITMAP->INVERTED logic is justified by "4.0.6 removed BITMAP", yet 
4.0.6 isn't in the automated matrix, so that version-specific behavior isn't 
actually covered. Either add a 4.0.6 image or adjust the description.



##########
catalogs/catalog-jdbc-doris/src/main/java/org/apache/gravitino/catalog/doris/operation/DorisTableOperations.java:
##########
@@ -87,7 +87,21 @@ protected String generateCreateTableSql(
       Distribution distribution,
       Index[] indexes) {
 
-    validateIncrementCol(columns);
+    // Log index information for debugging

Review Comment:
   Minor: this debug block logs both `index.type()` and `index.type().name()` 
(redundant), and there is a second similar loop in `appendIndexesSql`. Consider 
trimming to one concise line.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to