diff --git a/src/components/sidebar/TreeItem.vue b/src/components/sidebar/TreeItem.vue index e2bd2736bc..6606d83483 100644 --- a/src/components/sidebar/TreeItem.vue +++ b/src/components/sidebar/TreeItem.vue @@ -73,6 +73,7 @@ import { FIELD_LINEAGE_SUPPORTED_TYPES, TREE_SCHEMA_TYPES, PG_LIKE_STRUCTURE_TYPES, + CREATE_DATABASE_SUPPORTED_TYPES, isSchemaAware, usesFetchFirst, } from "@/lib/databaseCapabilities"; @@ -378,6 +379,13 @@ const showTruncateTableConfirm = ref(false); const showDuplicateDialog = ref(false); const duplicateTableName = ref(""); +const showCreateDatabaseDialog = ref(false); +const createDatabaseName = ref(""); +const showDropDatabaseConfirm = ref(false); +const showCreateSchemaDialog = ref(false); +const createSchemaName = ref(""); +const showDropSchemaConfirm = ref(false); + const isTableNotView = computed(() => props.node.type === "table"); const supportsTruncate = computed(() => { @@ -395,6 +403,26 @@ const canCreateTable = computed(() => { ); }); +const canCreateDatabase = computed(() => { + const config = props.node.connectionId ? connectionStore.getConfig(props.node.connectionId) : undefined; + return props.node.type === "connection" && !!config && CREATE_DATABASE_SUPPORTED_TYPES.has(config.db_type); +}); + +const canDropDatabase = computed(() => { + const config = props.node.connectionId ? connectionStore.getConfig(props.node.connectionId) : undefined; + return props.node.type === "database" && !!config && CREATE_DATABASE_SUPPORTED_TYPES.has(config.db_type); +}); + +const canCreateSchema = computed(() => { + const config = props.node.connectionId ? connectionStore.getConfig(props.node.connectionId) : undefined; + return props.node.type === "database" && !!config && TREE_SCHEMA_TYPES.has(config.db_type); +}); + +const canDropSchema = computed(() => { + const config = props.node.connectionId ? connectionStore.getConfig(props.node.connectionId) : undefined; + return props.node.type === "schema" && !!config && TREE_SCHEMA_TYPES.has(config.db_type); +}); + function buildDropTableSql(): string { return `DROP TABLE ${qualifiedTableName(props.node.label, props.node.schema)};`; } @@ -463,6 +491,93 @@ async function confirmTruncateTable() { } } +function buildDropDatabaseSql(): string { + return `DROP DATABASE ${quoteIdent(props.node.label)};`; +} + +function buildDropSchemaSql(): string { + const dbType = currentDatabaseType(); + const name = quoteIdent(props.node.label); + if (dbType === "postgres" || dbType === "gaussdb") return `DROP SCHEMA ${name} CASCADE;`; + return `DROP SCHEMA ${name};`; +} + +function openCreateDatabaseDialog() { + createDatabaseName.value = ""; + showCreateDatabaseDialog.value = true; +} + +async function confirmCreateDatabase() { + const node = props.node; + const name = createDatabaseName.value.trim(); + if (!name || !node.connectionId) return; + showCreateDatabaseDialog.value = false; + try { + await connectionStore.ensureConnected(node.connectionId); + const sql = `CREATE DATABASE ${quoteIdent(name)};`; + await api.executeQuery(node.connectionId, "", sql); + toast(t("contextMenu.createDatabaseSuccess", { name }), 3000); + await connectionStore.loadDatabases(node.connectionId); + } catch (e: any) { + toast(t("contextMenu.tableOperationFailed", { message: e?.message || String(e) }), 5000); + } +} + +function dropDatabase() { + showDropDatabaseConfirm.value = true; +} + +async function confirmDropDatabase() { + const node = props.node; + if (!node.connectionId) return; + try { + await connectionStore.ensureConnected(node.connectionId); + await api.executeQuery(node.connectionId, "", buildDropDatabaseSql()); + toast(t("contextMenu.dropDatabaseSuccess", { name: node.label }), 3000); + await connectionStore.loadDatabases(node.connectionId); + } catch (e: any) { + toast(t("contextMenu.tableOperationFailed", { message: e?.message || String(e) }), 5000); + } +} + +function openCreateSchemaDialog() { + createSchemaName.value = ""; + showCreateSchemaDialog.value = true; +} + +async function confirmCreateSchema() { + const node = props.node; + const name = createSchemaName.value.trim(); + if (!name || !node.connectionId || !node.database) return; + showCreateSchemaDialog.value = false; + try { + await connectionStore.ensureConnected(node.connectionId); + const sql = `CREATE SCHEMA ${quoteIdent(name)};`; + await api.executeQuery(node.connectionId, node.database, sql); + toast(t("contextMenu.createSchemaSuccess", { name }), 3000); + await connectionStore.loadSchemas(node.connectionId, node.database); + } catch (e: any) { + toast(t("contextMenu.tableOperationFailed", { message: e?.message || String(e) }), 5000); + } +} + +function dropSchema() { + showDropSchemaConfirm.value = true; +} + +async function confirmDropSchema() { + const node = props.node; + if (!node.connectionId || !node.database) return; + try { + await connectionStore.ensureConnected(node.connectionId); + await api.executeQuery(node.connectionId, node.database, buildDropSchemaSql()); + toast(t("contextMenu.dropSchemaSuccess", { name: node.label }), 3000); + await connectionStore.loadSchemas(node.connectionId, node.database); + } catch (e: any) { + toast(t("contextMenu.tableOperationFailed", { message: e?.message || String(e) }), 5000); + } +} + function duplicateStructure() { duplicateTableName.value = `${props.node.label}_copy`; showDuplicateDialog.value = true; @@ -1163,6 +1278,9 @@ const isDragging = computed(() => dragState.active && dragState.draggedId === pr {{ t("sqlFile.title") }} + + {{ t("contextMenu.createDatabase") }} + @@ -1228,6 +1346,9 @@ const isDragging = computed(() => dragState.active && dragState.draggedId === pr {{ t("contextMenu.createTable") }} + + {{ t("contextMenu.createSchema") }} + {{ t("sqlFile.title") }} @@ -1252,6 +1373,15 @@ const isDragging = computed(() => dragState.active && dragState.draggedId === pr {{ t("contextMenu.exportDatabase") }} + diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index 49cde113ba..a3d5e2cb18 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -451,6 +451,22 @@ export default { tableOperationFailed: "Operation failed: {message}", duplicateNameTitle: "Duplicate Structure", duplicateNamePlaceholder: "New table name", + createDatabase: "Create Database", + dropDatabase: "Drop Database", + confirmDropDatabaseTitle: "Drop Database", + confirmDropDatabaseMessage: + 'Are you sure you want to drop database "{name}"? This will permanently delete the database and all its data.', + createDatabaseSuccess: 'Database "{name}" created', + dropDatabaseSuccess: 'Database "{name}" dropped', + createDatabaseNamePlaceholder: "Database name", + createSchema: "Create Schema", + dropSchema: "Drop Schema", + confirmDropSchemaTitle: "Drop Schema", + confirmDropSchemaMessage: + 'Are you sure you want to drop schema "{name}"? This will permanently delete the schema and all its objects.', + createSchemaSuccess: 'Schema "{name}" created', + dropSchemaSuccess: 'Schema "{name}" dropped', + createSchemaNamePlaceholder: "Schema name", }, tree: { columns: "Columns", diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts index d872778797..f66a07a477 100644 --- a/src/i18n/locales/zh-CN.ts +++ b/src/i18n/locales/zh-CN.ts @@ -442,6 +442,20 @@ export default { tableOperationFailed: "操作失败:{message}", duplicateNameTitle: "复制表结构", duplicateNamePlaceholder: "新表名", + createDatabase: "新建数据库", + dropDatabase: "删除数据库", + confirmDropDatabaseTitle: "删除数据库", + confirmDropDatabaseMessage: "确定要删除数据库「{name}」吗?这将永久删除该数据库及其所有数据。", + createDatabaseSuccess: "数据库「{name}」已创建", + dropDatabaseSuccess: "数据库「{name}」已删除", + createDatabaseNamePlaceholder: "数据库名称", + createSchema: "新建 Schema", + dropSchema: "删除 Schema", + confirmDropSchemaTitle: "删除 Schema", + confirmDropSchemaMessage: "确定要删除 Schema「{name}」吗?这将永久删除该 Schema 及其所有对象。", + createSchemaSuccess: "Schema「{name}」已创建", + dropSchemaSuccess: "Schema「{name}」已删除", + createSchemaNamePlaceholder: "Schema 名称", }, tree: { columns: "字段", diff --git a/src/lib/databaseCapabilities.ts b/src/lib/databaseCapabilities.ts index 8aa555a483..62080b4a30 100644 --- a/src/lib/databaseCapabilities.ts +++ b/src/lib/databaseCapabilities.ts @@ -52,6 +52,19 @@ export const TABLE_IMPORT_SUPPORTED_TYPES = new Set([ export const TABLE_STRUCTURE_SUPPORTED_TYPES = new Set(["mysql", "postgres", "sqlite", "sqlserver"]); +export const CREATE_DATABASE_SUPPORTED_TYPES = new Set([ + "mysql", + "postgres", + "sqlserver", + "clickhouse", + "oracle", + "dameng", + "gaussdb", + "doris", + "starrocks", + "redshift", +]); + export const FIELD_LINEAGE_SUPPORTED_TYPES = new Set([ "mysql", "postgres",