mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-21 05:13:38 +00:00
feat(dameng): support schema creation
This commit is contained in:
@@ -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" },
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user