fix(databend): skip schema wildcard escaping so databases with underscore list tables

This commit is contained in:
t8y2 committed 2026-09-04 18:35:45 +08:00
1 parent 5ca30bb25b
commit b853d49ac1
4 files changed
+89 -6

No files matched your search

@@ -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<String> excludedSchemas,
List<String> 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();
@@ -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<ObjectInfo> 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<String> procedureNames = new LinkedHashSet<>();
try (ResultSet rs = meta.getProcedures(catalog, schemaPattern, "%")) {
while (rs.next()) {
@@ -707,11 +709,17 @@ public final class StandardJdbcMetadata {
*
* <p>当 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();
@@ -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<Object[]> capturedArgs = new AtomicReference<>();
Connection conn = schemaEscapeConnection("\\", rows(
row("TABLE_NAME", "A", "TABLE_TYPE", "TABLE", "REMARKS", null)
), capturedArgs);
List<TableInfo> 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<Object[]> capturedArgs) {
DatabaseMetaData meta = proxy(DatabaseMetaData.class, new MethodHandler() {
@Override