From 0329d9088e6831548785308aac6f48346df8560f Mon Sep 17 00:00:00 2001 From: zipg Date: Wed, 8 Jul 2026 19:25:20 +0800 Subject: [PATCH] feat(dameng): support schema creation --- .../com/dbx/agent/dameng/DamengAgent.java | 58 +++++++++++-------- .../agent/dameng/DamengAgentMetadataTest.java | 17 ++++++ .../src/components/sidebar/TreeItem.vue | 47 +++++++++++---- .../database/databaseFeatureSupport.spec.ts | 3 +- .../lib/database/databaseNamespaceCreation.ts | 4 +- crates/dbx-core/src/db_admin_sql.rs | 9 +++ 6 files changed, 101 insertions(+), 37 deletions(-) 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 c31f91480b..103b20692d 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 @@ -106,38 +106,48 @@ public final class DamengAgent extends BaseDatabaseAgent { @Override public List listDatabases() { - return unchecked(() -> { - List result = new ArrayList<>(); - String placeholders = String.join(",", SYSTEM_USERS.stream().map(user -> "?").toList()); - String sql = "SELECT USERNAME FROM ALL_USERS WHERE USERNAME NOT IN (" + placeholders + ") ORDER BY USERNAME"; - try (PreparedStatement stmt = requireConnected().prepareStatement(sql)) { - int index = 1; - for (String user : SYSTEM_USERS) { - stmt.setString(index++, user); - } - try (ResultSet rs = stmt.executeQuery()) { - while (rs.next()) { - result.add(new DatabaseInfo(rs.getString(1))); - } - } - } - return result; - }); + return unchecked(() -> listVisibleUsers().stream().map(DatabaseInfo::new).toList()); } @Override public List listSchemas() { - return unchecked(() -> { - List result = new ArrayList<>(); - String sql = "SELECT DISTINCT OWNER FROM ALL_OBJECTS ORDER BY OWNER"; - try (java.sql.Statement stmt = requireConnected().createStatement(); - ResultSet rs = stmt.executeQuery(sql)) { + return unchecked(this::listVisibleSchemas); + } + + private List listVisibleUsers() throws Exception { + List result = new ArrayList<>(); + String placeholders = String.join(",", SYSTEM_USERS.stream().map(user -> "?").toList()); + String sql = "SELECT USERNAME FROM ALL_USERS WHERE USERNAME NOT IN (" + placeholders + ") ORDER BY USERNAME"; + try (PreparedStatement stmt = requireConnected().prepareStatement(sql)) { + int index = 1; + for (String user : SYSTEM_USERS) { + stmt.setString(index++, user); + } + try (ResultSet rs = stmt.executeQuery()) { while (rs.next()) { result.add(rs.getString(1)); } } - return result; - }); + } + return result; + } + + private List listVisibleSchemas() throws Exception { + List result = new ArrayList<>(); + String placeholders = String.join(",", SYSTEM_USERS.stream().map(user -> "?").toList()); + String sql = "SELECT NAME FROM SYS.SYSOBJECTS WHERE TYPE$ = 'SCH' AND NAME NOT IN (" + placeholders + ") ORDER BY NAME"; + try (PreparedStatement stmt = requireConnected().prepareStatement(sql)) { + int index = 1; + for (String user : SYSTEM_USERS) { + stmt.setString(index++, user); + } + try (ResultSet rs = stmt.executeQuery()) { + while (rs.next()) { + result.add(rs.getString(1)); + } + } + } + return result; } @Override 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 416ae879d0..2358890e66 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 @@ -80,6 +80,20 @@ class DamengAgentMetadataTest { Assertions.assertEquals("用户示例表", objects.get(0).getComment()); } + @Test + void listSchemasIncludesSchemaObjectsWithoutChildren() { + DamengAgent agent = new DamengAgent(); + List sqls = new ArrayList<>(); + TestSupport.setPrivateConnection(agent, metadataConnection("id comment", null, false, List.of(), sqls, "")); + + List schemas = agent.listSchemas(); + + Assertions.assertEquals(List.of("APP", "EMPTY_SCHEMA"), schemas); + Assertions.assertTrue(sqls.stream().anyMatch(sql -> sql.contains("SYS.SYSOBJECTS") && sql.contains("TYPE$ = 'SCH'")), String.join("\n", sqls)); + Assertions.assertTrue(sqls.stream().noneMatch(sql -> sql.contains("ALL_USERS")), String.join("\n", sqls)); + Assertions.assertTrue(sqls.stream().noneMatch(sql -> sql.contains("ALL_OBJECTS")), String.join("\n", sqls)); + } + @Test void mapsMaterializedViewsFromMetadata() { DamengAgent agent = new DamengAgent(); @@ -312,6 +326,9 @@ class DamengAgentMetadataTest { if (sql.contains("DBMS_METADATA.GET_DDL")) { return dbmsMetadataStatement(dbmsMetadataDdl); } + if (sql.contains("SYS.SYSOBJECTS") && sql.contains("TYPE$ = 'SCH'")) { + return metadataStatement(List.of(List.of("APP"), List.of("EMPTY_SCHEMA"))); + } if (sql.contains("ALL_CONS_COLUMNS")) { return metadataStatement(List.of(List.of("ID"))); } diff --git a/apps/desktop/src/components/sidebar/TreeItem.vue b/apps/desktop/src/components/sidebar/TreeItem.vue index d7759768d1..7da49baa85 100644 --- a/apps/desktop/src/components/sidebar/TreeItem.vue +++ b/apps/desktop/src/components/sidebar/TreeItem.vue @@ -2419,6 +2419,11 @@ const isDuckDbConnection = computed(() => { return props.node.type === "connection" && connectionNamespaceCreationTarget(config) === "attach"; }); +const isConnectionSchemaCreation = computed(() => { + const config = props.node.connectionId ? connectionStore.getConfig(props.node.connectionId) : undefined; + return props.node.type === "connection" && connectionNamespaceCreationTarget(config) === "schema"; +}); + const canSetCreateDatabaseCharset = computed(() => { const config = props.node.connectionId ? connectionStore.getConfig(props.node.connectionId) : undefined; return connectionNamespaceCreationTarget(config) === "database" && supportsCreateDatabaseCharset(config?.db_type, config?.driver_profile); @@ -2831,6 +2836,20 @@ function openCreateDatabaseDialog() { } } +function openConnectionNamespaceCreation() { + if (isConnectionSchemaCreation.value) { + openCreateSchemaDialog(); + return; + } + void openCreateDatabase(); +} + +function connectionNamespaceCreationLabel() { + if (isDuckDbConnection.value) return t("contextMenu.createDuckDbFile"); + if (isConnectionSchemaCreation.value) return t("contextMenu.createSchema"); + return t("contextMenu.createDatabase"); +} + function updateCreateDatabaseCharset(value: string) { const previousCharset = createDatabaseCharset.value; createDatabaseCharset.value = value; @@ -2857,7 +2876,11 @@ async function loadCreateDatabaseCharsetMetadata(target: "create" | "edit" = "cr createDatabaseCollationsByCharset.value = metadata.collationsByCharset; const selectedCharset = target === "create" ? createDatabaseCharset.value : editDatabaseCharset.value; if (!createDatabaseCharsetOptions.value.includes(selectedCharset) && createDatabaseCharsetOptions.value.length) { - target === "create" ? updateCreateDatabaseCharset(createDatabaseCharsetOptions.value[0]) : updateEditDatabaseCharset(createDatabaseCharsetOptions.value[0]); + if (target === "create") { + updateCreateDatabaseCharset(createDatabaseCharsetOptions.value[0]); + } else { + updateEditDatabaseCharset(createDatabaseCharsetOptions.value[0]); + } } else { if (target === "create") { createDatabaseCollation.value = nextCreateDatabaseCollation(createDatabaseCharset.value, createDatabaseCharset.value, createDatabaseCollation.value, createDatabaseCollationsByCharset.value); @@ -3149,21 +3172,25 @@ function openCreateSchemaDialog() { async function confirmCreateSchema() { const node = props.node; const name = createSchemaName.value.trim(); - if (!name || !node.connectionId || !node.database) return; + const config = node.connectionId ? connectionStore.getConfig(node.connectionId) : undefined; + const isConnectionLevelSchemaCreation = node.type === "connection" && connectionNamespaceCreationTarget(config) === "schema"; + const targetDatabase = isConnectionLevelSchemaCreation ? "" : node.database; + if (!name || !node.connectionId || (!targetDatabase && !isConnectionLevelSchemaCreation)) return; showCreateSchemaDialog.value = false; try { await connectionStore.ensureConnected(node.connectionId); const sql = await buildCreateSchemaSql({ - databaseType: currentDatabaseType(), + databaseType: effectiveDatabaseTypeForConnection(config), name, }); - await api.executeQuery(node.connectionId, node.database, sql); + await api.executeQuery(node.connectionId, targetDatabase || "", sql); toast(t("contextMenu.createSchemaSuccess", { name }), 3000); - const config = connectionStore.getConfig(node.connectionId); - if (config?.db_type === "sqlserver") { - await connectionStore.loadSqlServerDatabaseObjects(node.connectionId, node.database, { force: true }); + if (isConnectionLevelSchemaCreation) { + await connectionStore.loadDatabases(node.connectionId, { force: true }); + } else if (config?.db_type === "sqlserver") { + await connectionStore.loadSqlServerDatabaseObjects(node.connectionId, targetDatabase || "", { force: true }); } else { - await connectionStore.loadSchemas(node.connectionId, node.database, { force: true }); + await connectionStore.loadSchemas(node.connectionId, targetDatabase || "", { force: true }); } } catch (e: any) { toast(t("contextMenu.tableOperationFailed", { message: e?.message || String(e) }), 5000); @@ -4483,8 +4510,8 @@ function treeItemMenuItems(): ContextMenuItem[] { } if (canCreateDatabase.value) { items.push({ - label: isDuckDbConnection.value ? t("contextMenu.createDuckDbFile") : t("contextMenu.createDatabase"), - action: openCreateDatabase, + label: connectionNamespaceCreationLabel(), + action: openConnectionNamespaceCreation, icon: Plus, }); } diff --git a/apps/desktop/src/lib/__tests__/database/databaseFeatureSupport.spec.ts b/apps/desktop/src/lib/__tests__/database/databaseFeatureSupport.spec.ts index 3ef3ba60b1..91b4728586 100644 --- a/apps/desktop/src/lib/__tests__/database/databaseFeatureSupport.spec.ts +++ b/apps/desktop/src/lib/__tests__/database/databaseFeatureSupport.spec.ts @@ -101,7 +101,8 @@ describe("database namespace creation", () => { expect(connectionNamespaceCreationTarget({ db_type: "tdengine" })).toBe("database"); }); - it("keeps special creation flows explicit", () => { + it("keeps non-database creation flows explicit", () => { + expect(connectionNamespaceCreationTarget({ db_type: "dameng" })).toBe("schema"); expect(connectionNamespaceCreationTarget({ db_type: "duckdb" })).toBe("attach"); expect(connectionNamespaceCreationTarget({ db_type: "mongodb" })).toBe("special"); expect(connectionNamespaceCreationTarget({ db_type: "mongodb", driver_profile: "mongodb-legacy" })).toBeNull(); diff --git a/apps/desktop/src/lib/database/databaseNamespaceCreation.ts b/apps/desktop/src/lib/database/databaseNamespaceCreation.ts index 63cdde552b..fe0f897589 100644 --- a/apps/desktop/src/lib/database/databaseNamespaceCreation.ts +++ b/apps/desktop/src/lib/database/databaseNamespaceCreation.ts @@ -2,7 +2,7 @@ import type { ConnectionConfig, DatabaseType, TreeNodeType } from "@/types/datab export type DatabaseNamespaceCreationTarget = "database" | "schema" | "attach" | "special"; -type ConnectionCreationTarget = Extract; +type ConnectionCreationTarget = Extract; type DatabaseNodeCreationTarget = Extract; export interface DatabaseNamespaceCreationMatrixEntry { @@ -36,7 +36,7 @@ export const DATABASE_NAMESPACE_CREATION_MATRIX = { manticoresearch: { deferred: "index/table creation is not database creation" }, databend: { connection: "database" }, redshift: { connection: "database", database: "schema" }, - dameng: { deferred: "schema/user creation requires a dedicated user workflow" }, + dameng: { connection: "schema" }, gaussdb: { connection: "database", database: "schema" }, kingbase: { connection: "database", database: "schema" }, highgo: { connection: "database", database: "schema" }, diff --git a/crates/dbx-core/src/db_admin_sql.rs b/crates/dbx-core/src/db_admin_sql.rs index 3e587fbe78..2d187b8126 100644 --- a/crates/dbx-core/src/db_admin_sql.rs +++ b/crates/dbx-core/src/db_admin_sql.rs @@ -251,6 +251,7 @@ pub fn supports_create_schema_target(database_type: Option) -> boo | DatabaseType::Highgo | DatabaseType::Vastbase | DatabaseType::Yashandb + | DatabaseType::Dameng | DatabaseType::Databricks | DatabaseType::SapHana | DatabaseType::Teradata @@ -1231,6 +1232,14 @@ mod tests { .unwrap(), "CREATE SCHEMA [analytics];" ); + assert_eq!( + build_create_schema_sql(SchemaNameSqlOptions { + database_type: Some(DatabaseType::Dameng), + name: "analytics".to_string(), + }) + .unwrap(), + "CREATE SCHEMA \"analytics\";" + ); assert_eq!( build_create_schema_sql(SchemaNameSqlOptions { database_type: Some(DatabaseType::Db2),