From b853d49ac19fa02e3e1bf662fab3a2115999c09f Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Fri, 4 Sep 2026 18:35:45 +0800 Subject: [PATCH] fix(databend): skip schema wildcard escaping so databases with underscore list tables --- .../java/com/dbx/agent/JdbcAgentProfile.java | 40 +++++++++++++++++++ .../com/dbx/agent/StandardJdbcMetadata.java | 20 +++++++--- .../dbx/agent/StandardJdbcMetadataTest.java | 32 +++++++++++++++ .../com/dbx/agent/databend/DatabendAgent.java | 3 ++ 4 files changed, 89 insertions(+), 6 deletions(-) diff --git a/agents/common/src/main/java/com/dbx/agent/JdbcAgentProfile.java b/agents/common/src/main/java/com/dbx/agent/JdbcAgentProfile.java index 6a7c8cc7da..75c402ade4 100644 --- a/agents/common/src/main/java/com/dbx/agent/JdbcAgentProfile.java +++ b/agents/common/src/main/java/com/dbx/agent/JdbcAgentProfile.java @@ -18,6 +18,7 @@ public class JdbcAgentProfile { private final boolean nativeTableDdlSupported; private final boolean objectSourceSupported; private final boolean triggersSupported; + private final boolean escapeSchemaWildcards; public JdbcAgentProfile(String driverClass, String urlTemplate) { this(driverClass, urlTemplate, 0); @@ -75,6 +76,40 @@ public class JdbcAgentProfile { boolean nativeTableDdlSupported, boolean objectSourceSupported, boolean triggersSupported + ) { + this( + driverClass, + urlTemplate, + defaultPort, + skipExecutionContext, + excludedSchemas, + tableTypes, + identifierQuote, + schemaSwitchPrefix, + catalogFallbackEnabled, + nativeTableDdlSupported, + objectSourceSupported, + triggersSupported, + // 默认对 schema pattern 里的 _ / % 通配符转义(getSearchStringEscape()), + // HANA 的 _SYS_RT 等含下划线 schema 依赖它避免误匹配。 + true + ); + } + + public JdbcAgentProfile( + String driverClass, + String urlTemplate, + int defaultPort, + boolean skipExecutionContext, + Set excludedSchemas, + List tableTypes, + String identifierQuote, + String schemaSwitchPrefix, + boolean catalogFallbackEnabled, + boolean nativeTableDdlSupported, + boolean objectSourceSupported, + boolean triggersSupported, + boolean escapeSchemaWildcards ) { this.driverClass = driverClass; this.urlTemplate = urlTemplate; @@ -88,6 +123,7 @@ public class JdbcAgentProfile { this.nativeTableDdlSupported = nativeTableDdlSupported; this.objectSourceSupported = objectSourceSupported; this.triggersSupported = triggersSupported; + this.escapeSchemaWildcards = escapeSchemaWildcards; } public String getDriverClass() { @@ -138,6 +174,10 @@ public class JdbcAgentProfile { return triggersSupported; } + public boolean getEscapeSchemaWildcards() { + return escapeSchemaWildcards; + } + public String buildUrl(ConnectParams params) { if (!params.getConnection_string().trim().isEmpty()) { return params.getConnection_string(); diff --git a/agents/common/src/main/java/com/dbx/agent/StandardJdbcMetadata.java b/agents/common/src/main/java/com/dbx/agent/StandardJdbcMetadata.java index 8edf4598c9..8c1c9ab612 100644 --- a/agents/common/src/main/java/com/dbx/agent/StandardJdbcMetadata.java +++ b/agents/common/src/main/java/com/dbx/agent/StandardJdbcMetadata.java @@ -123,12 +123,12 @@ public final class StandardJdbcMetadata { listObjects(listTables(conn, profile, configuredDatabase, schema, tableConstraints), schema) ); DatabaseMetaData meta = conn.getMetaData(); - appendRoutines(result, meta, null, schema, schema); + appendRoutines(result, meta, profile.getEscapeSchemaWildcards(), null, schema, schema); if (!containsRoutine(result) && profile.getCatalogFallbackEnabled() && configuredDatabase != null && !configuredDatabase.trim().isEmpty()) { - appendRoutines(result, meta, configuredDatabase, schema, schema); + appendRoutines(result, meta, profile.getEscapeSchemaWildcards(), configuredDatabase, schema, schema); } result.sort(Comparator.comparing(ObjectInfo::getName)); return normalized.filterObjects(result); @@ -350,7 +350,7 @@ public final class StandardJdbcMetadata { // JDBC has no portable metadata limit/offset. Use table types for safe pushdown and filter/page locally. // schema 是 DatabaseMetaData.getTables() 的 schemaPattern,_ 和 % 具有通配符语义; // HANA 存在 _SYS_RT 等含下划线的 schema,需按 getSearchStringEscape() 转义,避免混入其他 schema 的对象。 - try (ResultSet rs = meta.getTables(catalog, escapeSchemaPattern(meta, schema), "%", tableTypes)) { + try (ResultSet rs = meta.getTables(catalog, escapeSchemaPattern(meta, schema, profile.getEscapeSchemaWildcards()), "%", tableTypes)) { while (rs.next()) { result.add(new TableInfo( rs.getString("TABLE_NAME"), @@ -635,12 +635,14 @@ public final class StandardJdbcMetadata { private static void appendRoutines( List result, DatabaseMetaData meta, + boolean escapeSchemaWildcards, String catalog, String schema, String schemaLabel ) { - // schema 作为 getProcedures/getFunctions 的 schemaPattern,同样需要转义 _ 和 % 通配符。 - String schemaPattern = escapeSchemaPattern(meta, schema); + // schema 作为 getProcedures/getFunctions 的 schemaPattern,同样需要转义 _ 和 % 通配符 + // (databend 等驱动除外,见 escapeSchemaPattern)。 + String schemaPattern = escapeSchemaPattern(meta, schema, escapeSchemaWildcards); Set procedureNames = new LinkedHashSet<>(); try (ResultSet rs = meta.getProcedures(catalog, schemaPattern, "%")) { while (rs.next()) { @@ -707,11 +709,17 @@ public final class StandardJdbcMetadata { * *

当 schema 为空白(将映射为 {@code null},表示不做 schema 过滤)或驱动未提供转义字符时,原样返回。 */ - private static String escapeSchemaPattern(DatabaseMetaData meta, String schema) { + private static String escapeSchemaPattern(DatabaseMetaData meta, String schema, boolean escapeWildcards) { String normalized = blankToNull(schema); if (normalized == null) { return null; } + // 某些驱动(如 databend)的 getSearchStringEscape() 返回 '\',但其 getTables/getProcedures 把 _ 当字面量 + // 且忽略转义,转义反而匹配不到,导致含 _ 的库名(如 my_db)返回 0 行(#8114)。 + // 这类驱动通过 profile.escapeSchemaWildcards=false 关闭转义。 + if (!escapeWildcards) { + return normalized; + } String escape; try { escape = meta.getSearchStringEscape(); diff --git a/agents/common/src/test/java/com/dbx/agent/StandardJdbcMetadataTest.java b/agents/common/src/test/java/com/dbx/agent/StandardJdbcMetadataTest.java index 15e19506ab..722980bc19 100644 --- a/agents/common/src/test/java/com/dbx/agent/StandardJdbcMetadataTest.java +++ b/agents/common/src/test/java/com/dbx/agent/StandardJdbcMetadataTest.java @@ -279,6 +279,38 @@ class StandardJdbcMetadataTest { assertEquals("SALES", capturedArgs.get()[1]); } + @Test + void listTablesSkipsEscapeWhenProfileDisablesWildcards() { + // databend 等驱动的 getSearchStringEscape() 返回 "\\",但 getTables 把 _ 当字面量且忽略转义, + // 转义含 _ 的库名(如 my_db)会返回 0 行(#8114)。profile.escapeSchemaWildcards=false 时应原样传入。 + JdbcAgentProfile noEscapeProfile = new JdbcAgentProfile( + "example.Driver", + "jdbc:example://{host}:{port}/{database}", + 0, + false, + Collections.emptySet(), + Arrays.asList("TABLE", "VIEW", "BASE TABLE"), + "\"", + "USE", + true, + false, + false, + false, + false + ); + AtomicReference capturedArgs = new AtomicReference<>(); + Connection conn = schemaEscapeConnection("\\", rows( + row("TABLE_NAME", "A", "TABLE_TYPE", "TABLE", "REMARKS", null) + ), capturedArgs); + + List tables = StandardJdbcMetadata.INSTANCE.listTables(conn, noEscapeProfile, "", "my_db"); + + assertEquals(1, tables.size()); + assertEquals("A", tables.get(0).getName()); + // 关闭转义:schemaPattern 应为原始 "my_db",而非 "my\\_db" + assertEquals("my_db", capturedArgs.get()[1]); + } + private static Connection schemaEscapeConnection(String searchEscape, ResultSet tables, AtomicReference capturedArgs) { DatabaseMetaData meta = proxy(DatabaseMetaData.class, new MethodHandler() { @Override diff --git a/agents/drivers/databend/src/main/java/com/dbx/agent/databend/DatabendAgent.java b/agents/drivers/databend/src/main/java/com/dbx/agent/databend/DatabendAgent.java index afd4ee6c46..d0ae69ea9f 100644 --- a/agents/drivers/databend/src/main/java/com/dbx/agent/databend/DatabendAgent.java +++ b/agents/drivers/databend/src/main/java/com/dbx/agent/databend/DatabendAgent.java @@ -31,6 +31,9 @@ public final class DatabendAgent extends ConfiguredJdbcAgent { true, false, false, + false, + // databend 的 getSearchStringEscape() 返回 '\',但 getTables/getProcedures 把 _ 当字面量且忽略转义, + // 对含 _ 的库名(如 my_db)转义后返回 0 行(#8114),故关闭 schema 通配符转义。 false );