From ee757fbe651e946101bdb57531324e0fafb89171 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=88=E7=A7=8B?= <49514152+yiqiui@users.noreply.github.com> Date: Thu, 17 Sep 2026 12:54:56 +0800 Subject: [PATCH] feat(gbase8s): support dropping a database from the tree --- .../com/dbx/agent/gbase8s/Gbase8sAgent.java | 57 ++++++++++++++++--- .../dbx/agent/gbase8s/Gbase8sAgentTest.java | 11 ++++ .../sidebar/SidebarTreeRuntimeHost.vue | 2 +- 3 files changed, 62 insertions(+), 8 deletions(-) diff --git a/agents/drivers/gbase8s/src/main/java/com/dbx/agent/gbase8s/Gbase8sAgent.java b/agents/drivers/gbase8s/src/main/java/com/dbx/agent/gbase8s/Gbase8sAgent.java index b9a2907218..d9ca578684 100644 --- a/agents/drivers/gbase8s/src/main/java/com/dbx/agent/gbase8s/Gbase8sAgent.java +++ b/agents/drivers/gbase8s/src/main/java/com/dbx/agent/gbase8s/Gbase8sAgent.java @@ -52,6 +52,12 @@ public final class Gbase8sAgent extends ConfiguredJdbcAgent { private static final Pattern CREATE_DATABASE_LOCALE_DIRECTIVE = Pattern.compile( "^\\s*--\\s*DBX_DB_LOCALE\\s*=\\s*(\\S+)\\s*\\r?\\n(.*)$", Pattern.DOTALL); + // A bare `DROP DATABASE ` (unquoted identifier, optional trailing semicolon). The target + // database's own locale is resolvable (it exists in sysdbslocale), so the drop is routed here + // without a directive. + private static final Pattern DROP_DATABASE_STATEMENT = Pattern.compile( + "^\\s*DROP\\s+DATABASE\\s+([A-Za-z0-9_]+)\\s*;?\\s*$", Pattern.CASE_INSENSITIVE); + public static final JdbcAgentProfile GBASE8S_PROFILE = new JdbcAgentProfile( "com.gbasedbt.jdbc.Driver", "jdbc:gbasedbt-sqli://{host}:{port}/{database}:GBASEDBTSERVER=gbase8s", @@ -181,9 +187,13 @@ public final class Gbase8sAgent extends ConfiguredJdbcAgent { return localized; } - private String resolveCollate(ConnectParams params, String database) { - String key = params.getHost() + "|" + params.getPort() + "|" + getGbaseServer(params) + private static String collateCacheKey(ConnectParams params, String database) { + return params.getHost() + "|" + params.getPort() + "|" + getGbaseServer(params) + "|" + database.toLowerCase(Locale.ROOT); + } + + private String resolveCollate(ConnectParams params, String database) { + String key = collateCacheKey(params, database); String cached = collateByDatabase.get(key); if (cached != null) { return cached; @@ -270,7 +280,17 @@ public final class Gbase8sAgent extends ConfiguredJdbcAgent { public QueryResult executeQuery(String sql, String schema, ExecuteQueryOptions options) { CreateDatabaseLocaleDirective directive = parseCreateDatabaseLocaleDirective(sql); if (directive != null) { - runCreateDatabaseWithLocale(directive.statement(), directive.locale()); + runDdlOnSysmasterWithLocale(directive.statement(), directive.locale()); + clearMetadataCache(); + return new QueryResult(Collections.emptyList(), Collections.emptyList(), 0, 0); + } + String dropTarget = parseDropDatabaseName(sql); + if (dropTarget != null && databaseListParams != null) { + // Informix cannot drop a database from a session whose DB_LOCALE differs, nor the + // current database; run it from sysmaster pinned to the target database's own locale. + String collate = resolveCollate(databaseListParams, dropTarget); + runDdlOnSysmasterWithLocale("DROP DATABASE " + dropTarget, collate); + invalidateCollateCache(dropTarget); clearMetadataCache(); return new QueryResult(Collections.emptyList(), Collections.emptyList(), 0, 0); } @@ -281,6 +301,27 @@ public final class Gbase8sAgent extends ConfiguredJdbcAgent { return result; } + /** + * Parse {@code sql} as a bare {@code DROP DATABASE } statement, returning the (unquoted) + * target database name or {@code null} for anything else. Package visible so the routing + * decision is testable without a live connection. + */ + static String parseDropDatabaseName(String sql) { + if (sql == null) { + return null; + } + Matcher drop = DROP_DATABASE_STATEMENT.matcher(sql); + return drop.matches() ? drop.group(1) : null; + } + + private void invalidateCollateCache(String database) { + ConnectParams base = databaseListParams; + if (base == null) { + return; + } + collateByDatabase.remove(collateCacheKey(base, database)); + } + /** * A {@code CREATE DATABASE} statement plus the DB_LOCALE carried by its leading directive * comment. Package visible so the routing decision is directly testable without a live @@ -307,12 +348,14 @@ public final class Gbase8sAgent extends ConfiguredJdbcAgent { } /** - * Run a {@code CREATE DATABASE} statement on a sysmaster session whose {@code DB_LOCALE} is - * pinned to {@code locale}, so the freshly created database inherits that codeset (Informix has - * no charset clause in {@code CREATE DATABASE}). Falls back to the configured locale when + * Run a DDL statement (e.g. {@code CREATE DATABASE} / {@code DROP DATABASE}) on a sysmaster + * session whose {@code DB_LOCALE} is pinned to {@code locale}. Informix has no charset clause + * in {@code CREATE DATABASE} (the new database inherits the creating session's DB_LOCALE) and + * cannot drop a database from a session whose locale differs, or drop the current database — + * so both run from sysmaster with the relevant locale. Falls back to the configured locale when * {@code locale} is blank or unsafe. */ - private void runCreateDatabaseWithLocale(String statement, String locale) { + private void runDdlOnSysmasterWithLocale(String statement, String locale) { ConnectParams base = databaseListParams; if (base == null) { throw new IllegalStateException("Not connected"); diff --git a/agents/drivers/gbase8s/src/test/java/com/dbx/agent/gbase8s/Gbase8sAgentTest.java b/agents/drivers/gbase8s/src/test/java/com/dbx/agent/gbase8s/Gbase8sAgentTest.java index 4a751bacdd..56660cf979 100644 --- a/agents/drivers/gbase8s/src/test/java/com/dbx/agent/gbase8s/Gbase8sAgentTest.java +++ b/agents/drivers/gbase8s/src/test/java/com/dbx/agent/gbase8s/Gbase8sAgentTest.java @@ -258,6 +258,17 @@ class Gbase8sAgentTest { Assertions.assertNull(Gbase8sAgent.parseCreateDatabaseLocaleDirective(null)); } + @Test + void parseDropDatabaseNameRecognizesBareDrop() { + Assertions.assertEquals("app_db", Gbase8sAgent.parseDropDatabaseName("DROP DATABASE app_db;")); + Assertions.assertEquals("app_db", Gbase8sAgent.parseDropDatabaseName(" drop database app_db ")); + Assertions.assertEquals("app_db", Gbase8sAgent.parseDropDatabaseName("Drop Database app_db")); + Assertions.assertNull(Gbase8sAgent.parseDropDatabaseName("SELECT * FROM t;")); + Assertions.assertNull(Gbase8sAgent.parseDropDatabaseName("CREATE DATABASE app_db;")); + Assertions.assertNull(Gbase8sAgent.parseDropDatabaseName("DROP DATABASE \"app db\";")); + Assertions.assertNull(Gbase8sAgent.parseDropDatabaseName(null)); + } + @Test void omitsOwnerSchemasWhenTheDatabaseCannotUseThemInDml() { List sql = new ArrayList<>(); diff --git a/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue b/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue index f2137045de..aec60b3fc6 100644 --- a/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue +++ b/apps/desktop/src/components/sidebar/SidebarTreeRuntimeHost.vue @@ -3522,7 +3522,7 @@ const canSetCreateDatabaseLocale = computed(() => { const canDropDatabase = computed(() => { const config = activeNode.value.connectionId ? connectionStore.getConfig(activeNode.value.connectionId) : undefined; - return activeNode.value.type === "database" && !isSqlServerLinkedNode(activeNode.value) && supportsDatabaseCreation(config?.db_type); + return activeNode.value.type === "database" && !isSqlServerLinkedNode(activeNode.value) && (supportsDatabaseCreation(config?.db_type) || supportsCreateDatabaseLocale(config?.db_type, config?.driver_profile)); }); const databasePropertyGroups = computed(() => {