diff --git a/agents/drivers/dameng/src/main/java/com/dbx/agent/dameng/DamengAgent.java b/agents/drivers/dameng/src/main/java/com/dbx/agent/dameng/DamengAgent.java index ca45476e24..fbb4eec7c3 100644 --- a/agents/drivers/dameng/src/main/java/com/dbx/agent/dameng/DamengAgent.java +++ b/agents/drivers/dameng/src/main/java/com/dbx/agent/dameng/DamengAgent.java @@ -383,7 +383,8 @@ public final class DamengAgent extends BaseDatabaseAgent { stmt.setString(3, schema); try (ResultSet rs = stmt.executeQuery()) { if (rs.next()) { - return appendTableAndColumnComments(coalesce(rs.getString(1)), schema, table); + String ddl = appendTableAndColumnComments(coalesce(rs.getString(1)), schema, table); + return appendIndependentIndexDdl(ddl, schema, table); } } } @@ -831,6 +832,64 @@ public final class DamengAgent extends BaseDatabaseAgent { return result.toString(); } + private String appendIndependentIndexDdl(String ddl, String schema, String table) throws Exception { + StringBuilder result = new StringBuilder(ddl == null ? "" : ddl.trim()); + for (String indexName : independentIndexNames(schema, table)) { + if (containsCreateIndex(result.toString(), schema, indexName)) { + continue; + } + String indexDdl = indexDdl(schema, indexName); + if (notBlank(indexDdl)) { + appendDdlStatement(result, ensureStatementTerminator(indexDdl)); + } + } + return result.toString(); + } + + private List independentIndexNames(String schema, String table) throws Exception { + List result = new ArrayList<>(); + // Primary-key and unique-constraint backing indexes are already represented in table DDL. + String sql = """ + SELECT i.INDEX_NAME + FROM ALL_INDEXES i + WHERE i.TABLE_OWNER = ? AND i.TABLE_NAME = ? + AND NOT EXISTS ( + SELECT 1 + FROM ALL_CONSTRAINTS c + WHERE c.OWNER = i.TABLE_OWNER + AND c.TABLE_NAME = i.TABLE_NAME + AND c.INDEX_NAME = i.INDEX_NAME + AND c.CONSTRAINT_TYPE IN ('P', 'U') + ) + ORDER BY i.INDEX_NAME + """.stripIndent().trim(); + try (PreparedStatement stmt = requireConnected().prepareStatement(sql)) { + stmt.setString(1, schema); + stmt.setString(2, table); + try (ResultSet rs = stmt.executeQuery()) { + while (rs.next()) { + String indexName = rs.getString(1); + if (notBlank(indexName)) { + result.add(indexName); + } + } + } + } + return result; + } + + private String indexDdl(String schema, String indexName) throws Exception { + String sql = "SELECT DBMS_METADATA.GET_DDL(?, ?, ?) FROM DUAL"; + try (PreparedStatement stmt = requireConnected().prepareStatement(sql)) { + stmt.setString(1, "INDEX"); + stmt.setString(2, indexName); + stmt.setString(3, schema); + try (ResultSet rs = stmt.executeQuery()) { + return rs.next() ? rs.getString(1) : null; + } + } + } + private String tableComment(String schema, String table) throws Exception { String sql = """ SELECT COMMENTS @@ -847,6 +906,10 @@ public final class DamengAgent extends BaseDatabaseAgent { } private static void appendCommentStatement(StringBuilder ddl, String statement) { + appendDdlStatement(ddl, statement); + } + + private static void appendDdlStatement(StringBuilder ddl, String statement) { if (!ddl.isEmpty()) { if (ddl.charAt(ddl.length() - 1) != '\n') { ddl.append("\n"); @@ -856,6 +919,14 @@ public final class DamengAgent extends BaseDatabaseAgent { ddl.append(statement); } + private static String ensureStatementTerminator(String statement) { + String trimmed = coalesce(statement).trim(); + if (trimmed.isEmpty() || trimmed.endsWith(";")) { + return trimmed; + } + return trimmed + ";"; + } + private static boolean containsCommentOnTable(String ddl, String schema, String table) { return normalizedDdl(ddl).contains("COMMENT ON TABLE " + normalizedQualifiedName(schema, table)); } @@ -864,6 +935,12 @@ public final class DamengAgent extends BaseDatabaseAgent { return normalizedDdl(ddl).contains("COMMENT ON COLUMN " + normalizedQualifiedName(schema, table) + "." + normalizedIdentifier(column)); } + private static boolean containsCreateIndex(String ddl, String schema, String indexName) { + String normalized = normalizedDdl(ddl); + return normalized.contains(" INDEX " + normalizedQualifiedName(schema, indexName) + " ") + || normalized.contains(" INDEX " + normalizedIdentifier(indexName) + " "); + } + private static String qualifiedName(String schema, String name) { if (!notBlank(schema)) { return JdbcIdentifiers.INSTANCE.doubleQuote(name); diff --git a/agents/drivers/dameng/src/test/java/com/dbx/agent/dameng/DamengAgentMetadataTest.java b/agents/drivers/dameng/src/test/java/com/dbx/agent/dameng/DamengAgentMetadataTest.java index cd0f561b2f..1b46909586 100644 --- a/agents/drivers/dameng/src/test/java/com/dbx/agent/dameng/DamengAgentMetadataTest.java +++ b/agents/drivers/dameng/src/test/java/com/dbx/agent/dameng/DamengAgentMetadataTest.java @@ -19,6 +19,7 @@ import java.sql.ResultSet; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; class DamengAgentMetadataTest { @Test @@ -188,6 +189,27 @@ class DamengAgentMetadataTest { Assertions.assertTrue(ddl.contains("COMMENT ON COLUMN \"APP\".\"USERS\".\"ID\" IS 'id comment';"), ddl); } + @Test + void appendsIndependentIndexesToTableDdl() { + DamengAgent agent = new DamengAgent(); + List sqls = new ArrayList<>(); + TestSupport.setPrivateConnection(agent, metadataConnectionWithIndexes(sqls)); + + String ddl = agent.getTableDdl("APP", "USERS"); + + Assertions.assertTrue( + ddl.contains("CREATE INDEX \"APP\".\"IDX_USERS_NAME\" ON \"APP\".\"USERS\" (\"NAME\");"), + ddl + ); + Assertions.assertTrue( + ddl.contains("CREATE UNIQUE INDEX \"APP\".\"UX_USERS_EMAIL\" ON \"APP\".\"USERS\" (\"EMAIL\");"), + ddl + ); + Assertions.assertFalse(ddl.contains("PK_USERS"), ddl); + String indexSql = sqls.stream().filter(sql -> sql.contains("ALL_INDEXES")).findFirst().orElseThrow(); + Assertions.assertTrue(indexSql.contains("CONSTRAINT_TYPE IN ('P', 'U')"), indexSql); + } + private static Connection metadataConnection() { return metadataConnection("id comment", null); } @@ -197,12 +219,40 @@ class DamengAgentMetadataTest { } private static Connection metadataConnection(String allColumnComment, String fallbackColumnComment, boolean includeMaterializedView) { + return metadataConnection(allColumnComment, fallbackColumnComment, includeMaterializedView, List.of(), Map.of(), null); + } + + private static Connection metadataConnectionWithIndexes(List sqls) { + return metadataConnection( + "id comment", + null, + false, + List.of("IDX_USERS_NAME", "UX_USERS_EMAIL"), + Map.of( + "IDX_USERS_NAME", "CREATE INDEX \"APP\".\"IDX_USERS_NAME\" ON \"APP\".\"USERS\" (\"NAME\")", + "UX_USERS_EMAIL", "CREATE UNIQUE INDEX \"APP\".\"UX_USERS_EMAIL\" ON \"APP\".\"USERS\" (\"EMAIL\")" + ), + sqls + ); + } + + private static Connection metadataConnection( + String allColumnComment, + String fallbackColumnComment, + boolean includeMaterializedView, + List independentIndexNames, + Map indexDdlByName, + List sqls + ) { return proxy(Connection.class, (method, args) -> { String name = method.getName(); if ("prepareStatement".equals(name)) { String sql = (String) args[0]; + if (sqls != null) { + sqls.add(sql); + } if (sql.contains("DBMS_METADATA.GET_DDL")) { - return metadataStatement(List.of(List.of("CREATE TABLE \"APP\".\"USERS\" (\n \"ID\" NUMBER\n);"))); + return dbmsMetadataStatement(indexDdlByName); } if (sql.contains("ALL_CONS_COLUMNS")) { return metadataStatement(List.of(List.of("ID"))); @@ -237,6 +287,9 @@ class DamengAgentMetadataTest { if (sql.contains("ALL_OBJECTS")) { return metadataStatement(List.of()); } + if (sql.contains("ALL_INDEXES")) { + return metadataStatement(independentIndexNames.stream().map(indexName -> List.of((Object) indexName)).toList()); + } if (sql.contains("ALL_TAB_COMMENTS")) { List> rows = new ArrayList<>(); rows.add(List.of("USERS", "TABLE", "用户示例表")); @@ -278,6 +331,33 @@ class DamengAgentMetadataTest { }); } + private static PreparedStatement dbmsMetadataStatement(Map indexDdlByName) { + List params = new ArrayList<>(); + return proxy(PreparedStatement.class, (method, args) -> { + String name = method.getName(); + if ("executeQuery".equals(name)) { + String objectType = params.isEmpty() ? "" : params.get(0); + if ("INDEX".equals(objectType)) { + String indexName = params.size() > 1 ? params.get(1) : ""; + return metadataResultSet(List.of(List.of(indexDdlByName.getOrDefault(indexName, "")))); + } + return metadataResultSet(List.of(List.of("CREATE TABLE \"APP\".\"USERS\" (\n \"ID\" NUMBER\n);"))); + } + if ("setString".equals(name)) { + int index = ((Integer) args[0]) - 1; + while (params.size() <= index) { + params.add(""); + } + params.set(index, String.valueOf(args[1])); + return null; + } + if ("close".equals(name)) { + return null; + } + return defaultValue(method.getReturnType()); + }); + } + private static void setConnectedUsername(DamengAgent agent, String username) { try { Field field = DamengAgent.class.getDeclaredField("connectedUsername");