feat(dameng): support schema creation

This commit is contained in:
zipg
2026-07-08 19:25:20 +08:00
committed by GitHub
parent fb919efe0a
commit 0329d9088e
6 changed files with 101 additions and 37 deletions
@@ -106,38 +106,48 @@ public final class DamengAgent extends BaseDatabaseAgent {
@Override
public List<DatabaseInfo> listDatabases() {
return unchecked(() -> {
List<DatabaseInfo> 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<String> listSchemas() {
return unchecked(() -> {
List<String> 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<String> listVisibleUsers() throws Exception {
List<String> 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<String> listVisibleSchemas() throws Exception {
List<String> 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
@@ -80,6 +80,20 @@ class DamengAgentMetadataTest {
Assertions.assertEquals("用户示例表", objects.get(0).getComment());
}
@Test
void listSchemasIncludesSchemaObjectsWithoutChildren() {
DamengAgent agent = new DamengAgent();
List<String> sqls = new ArrayList<>();
TestSupport.setPrivateConnection(agent, metadataConnection("id comment", null, false, List.of(), sqls, ""));
List<String> 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")));
}
@@ -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,
});
}
@@ -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();
@@ -2,7 +2,7 @@ import type { ConnectionConfig, DatabaseType, TreeNodeType } from "@/types/datab
export type DatabaseNamespaceCreationTarget = "database" | "schema" | "attach" | "special";
type ConnectionCreationTarget = Extract<DatabaseNamespaceCreationTarget, "database" | "attach" | "special">;
type ConnectionCreationTarget = Extract<DatabaseNamespaceCreationTarget, "database" | "schema" | "attach" | "special">;
type DatabaseNodeCreationTarget = Extract<DatabaseNamespaceCreationTarget, "schema">;
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" },
+9
View File
@@ -251,6 +251,7 @@ pub fn supports_create_schema_target(database_type: Option<DatabaseType>) -> 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),