diff --git a/apps/desktop/src/components/sidebar/TreeItem.vue b/apps/desktop/src/components/sidebar/TreeItem.vue index db9d9bc729..305ed8a680 100644 --- a/apps/desktop/src/components/sidebar/TreeItem.vue +++ b/apps/desktop/src/components/sidebar/TreeItem.vue @@ -102,15 +102,19 @@ import { buildDropObjectSql, buildDropSchemaSql, buildDropTableSql, + buildDropTableChildObjectSql, buildDuplicateTableStructureSql, buildEmptyTableSql, buildTruncateTableSql, + type DropTableChildObjectSqlOptions, type DropObjectSqlOptions, + type TableChildObjectType, type TableAdminSqlOptions, } from "@/lib/dbAdminSql"; import { buildRenameObjectSql, supportsObjectRename, type RenameableObjectType } from "@/lib/objectRenameSql"; import { buildRoutineRenameObjectSourceStatements, supportsSourceBackedRoutineRename } from "@/lib/objectSourceEditor"; import { buildViewDdl } from "@/lib/viewDdl"; +import { getTableStructureCapabilities } from "@/lib/tableStructureCapabilities"; import { hexToRgba } from "@/lib/color"; import { focusSidebarRenameInput } from "@/lib/sidebarRenameFocus"; import { hasTreeNodeDatabaseContext } from "@/lib/treeNodeContext"; @@ -450,6 +454,12 @@ function isEditableShortcutTarget(target: EventTarget | null): boolean { function onKeydown(event: KeyboardEvent) { if (!isSelected.value || isEditableShortcutTarget(event.target)) return; + if (!event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey && isDeleteTreeNodeShortcut(event)) { + if (!requestDropSelectedNode()) return; + event.preventDefault(); + event.stopPropagation(); + return; + } const action = sidebarSelectionCopyAction(event); if (action !== "copy-name") return; event.preventDefault(); @@ -457,6 +467,10 @@ function onKeydown(event: KeyboardEvent) { copyName(); } +function isDeleteTreeNodeShortcut(event: KeyboardEvent): boolean { + return event.key === "Delete" || event.key === "Backspace"; +} + function onDoubleClick() { const action = treeNodeRowDoubleClickAction( props.node.type, @@ -704,6 +718,7 @@ async function duplicateConnection() { // --- Table Management Operations --- const showDropTableConfirm = ref(false); +const showDropTableChildObjectConfirm = ref(false); const showEmptyTableConfirm = ref(false); const showTruncateTableConfirm = ref(false); const showRenameObjectDialog = ref(false); @@ -714,6 +729,7 @@ const dropTablePreviewSql = ref(""); const emptyTablePreviewSql = ref(""); const truncateTablePreviewSql = ref(""); const dropObjectPreviewSql = ref(""); +const dropTableChildObjectPreviewSql = ref(""); const dropDatabasePreviewSql = ref(""); const dropSchemaPreviewSql = ref(""); const showDuplicateDialog = ref(false); @@ -735,21 +751,114 @@ const showProcedureExecutionConfirm = ref(false); function dropObjectSqlOptions(): DropObjectSqlOptions | null { const node = props.node; - if (node.type !== "procedure" && node.type !== "function") return null; + if (node.type !== "view" && node.type !== "procedure" && node.type !== "function") return null; return { databaseType: currentDatabaseType(), - objectType: node.type === "procedure" ? "PROCEDURE" : "FUNCTION", + objectType: node.type === "view" ? "VIEW" : node.type === "procedure" ? "PROCEDURE" : "FUNCTION", schema: node.schema, name: node.label, }; } +function tableChildDropObjectType(type: TreeNodeType): TableChildObjectType | null { + if (type === "column") return "COLUMN"; + if (type === "index") return "INDEX"; + if (type === "fkey") return "FOREIGN_KEY"; + if (type === "trigger") return "TRIGGER"; + return null; +} + +function tableChildDropObjectName(node: TreeNode): string { + if (node.type === "column") + return node.meta && "name" in node.meta ? node.meta.name : node.label.replace(/\s+\(.+\)$/, ""); + if (node.type === "index") + return node.meta && "name" in node.meta ? node.meta.name : node.label.replace(/\s+\(.+\)$/, ""); + if (node.type === "fkey") return node.meta && "name" in node.meta ? node.meta.name : node.label; + if (node.type === "trigger") + return node.meta && "name" in node.meta ? node.meta.name : node.label.replace(/\s+\(.+\)$/, ""); + return node.label; +} + +function dropTableChildObjectSqlOptions(): DropTableChildObjectSqlOptions | null { + const node = props.node; + const objectType = tableChildDropObjectType(node.type); + if (!objectType || !node.tableName) return null; + const name = tableChildDropObjectName(node).trim(); + if (!name) return null; + return { + databaseType: currentDatabaseType(), + objectType, + schema: node.schema, + tableName: node.tableName, + name, + }; +} + +const canDropTableChildObject = computed(() => { + const options = dropTableChildObjectSqlOptions(); + if (!options) return false; + const capabilities = getTableStructureCapabilities(currentDatabaseType()); + if (options.objectType === "COLUMN") return capabilities.dropColumn; + if (options.objectType === "INDEX") return capabilities.dropIndex; + return true; +}); + +function dropObjectMenuLabel(): string { + if (props.node.type === "view") return t("contextMenu.dropView"); + if (props.node.type === "procedure") return t("contextMenu.dropProcedure"); + if (props.node.type === "function") return t("contextMenu.dropFunction"); + return t("contextMenu.dropObject"); +} + +function dropObjectConfirmTitle(): string { + if (props.node.type === "view") return t("contextMenu.confirmDropViewTitle"); + if (props.node.type === "procedure") return t("contextMenu.confirmDropProcedureTitle"); + if (props.node.type === "function") return t("contextMenu.confirmDropFunctionTitle"); + return t("contextMenu.confirmDropObjectTitle"); +} + +function dropObjectConfirmMessage(): string { + if (props.node.type === "view") return t("contextMenu.confirmDropViewMessage", { name: props.node.label }); + if (props.node.type === "procedure") return t("contextMenu.confirmDropProcedureMessage", { name: props.node.label }); + if (props.node.type === "function") return t("contextMenu.confirmDropFunctionMessage", { name: props.node.label }); + return t("contextMenu.confirmDropObjectMessage", { name: props.node.label }); +} + +function dropTableChildObjectMenuLabel(): string { + if (props.node.type === "column") return t("contextMenu.dropColumn"); + if (props.node.type === "index") return t("contextMenu.dropIndex"); + if (props.node.type === "fkey") return t("contextMenu.dropForeignKey"); + if (props.node.type === "trigger") return t("contextMenu.dropTrigger"); + return t("contextMenu.dropObject"); +} + +function dropTableChildObjectConfirmTitle(): string { + if (props.node.type === "column") return t("contextMenu.confirmDropColumnTitle"); + if (props.node.type === "index") return t("contextMenu.confirmDropIndexTitle"); + if (props.node.type === "fkey") return t("contextMenu.confirmDropForeignKeyTitle"); + if (props.node.type === "trigger") return t("contextMenu.confirmDropTriggerTitle"); + return t("contextMenu.confirmDropObjectTitle"); +} + +function dropTableChildObjectConfirmMessage(): string { + return t("contextMenu.confirmDropTableChildObjectMessage", { + name: tableChildDropObjectName(props.node), + table: props.node.tableName || "", + }); +} + async function refreshDropObjectPreviewSql() { const options = dropObjectSqlOptions(); dropObjectPreviewSql.value = ""; dropObjectPreviewSql.value = options ? await buildDropObjectSql(options).catch(() => "") : ""; } +async function refreshDropTableChildObjectPreviewSql() { + const options = dropTableChildObjectSqlOptions(); + dropTableChildObjectPreviewSql.value = ""; + dropTableChildObjectPreviewSql.value = options ? await buildDropTableChildObjectSql(options).catch(() => "") : ""; +} + function viewObjectSource() { const node = props.node; if (!node.connectionId || !node.database) return; @@ -828,6 +937,28 @@ function requestDropObject() { showDropObjectConfirm.value = true; } +function requestDropTableChildObject() { + if (!canDropTableChildObject.value) return; + void refreshDropTableChildObjectPreviewSql(); + showDropTableChildObjectConfirm.value = true; +} + +function requestDropSelectedNode(): boolean { + if (props.node.type === "table") { + dropTable(); + return true; + } + if (props.node.type === "view" || props.node.type === "procedure" || props.node.type === "function") { + requestDropObject(); + return true; + } + if (canDropTableChildObject.value) { + requestDropTableChildObject(); + return true; + } + return false; +} + function nodeRenameObjectType(): RenameableObjectType | null { if (props.node.type === "table") return "TABLE"; if (props.node.type === "view") return "VIEW"; @@ -944,9 +1075,34 @@ async function confirmDropObject() { await connectionStore.ensureConnected(node.connectionId); const sql = dropObjectPreviewSql.value || (await buildDropObjectSql(options)); await api.executeQuery(node.connectionId, node.database, sql, node.schema); - const msgKey = node.type === "procedure" ? "contextMenu.dropProcedureSuccess" : "contextMenu.dropFunctionSuccess"; + const msgKey = + node.type === "view" + ? "contextMenu.dropViewSuccess" + : node.type === "procedure" + ? "contextMenu.dropProcedureSuccess" + : "contextMenu.dropFunctionSuccess"; toast(t(msgKey, { name: node.label }), 3000); - await refreshTableList(node); + if (node.type === "view") { + connectionStore.removeTreeNode(node.id); + } else { + await refreshTableList(node); + } + } catch (e: any) { + toast(t("contextMenu.tableOperationFailed", { message: e?.message || String(e) }), 5000); + } +} + +async function confirmDropTableChildObject() { + const node = props.node; + if (!node.connectionId || !node.database) return; + const options = dropTableChildObjectSqlOptions(); + if (!options) return; + try { + await connectionStore.ensureConnected(node.connectionId); + const sql = dropTableChildObjectPreviewSql.value || (await buildDropTableChildObjectSql(options)); + await api.executeQuery(node.connectionId, node.database, sql, node.schema); + toast(t("contextMenu.dropTableChildObjectSuccess", { name: options.name }), 3000); + connectionStore.removeTreeNode(node.id); } catch (e: any) { toast(t("contextMenu.tableOperationFailed", { message: e?.message || String(e) }), 5000); } @@ -2190,6 +2346,14 @@ function treeItemMenuItems(): ContextMenuItem[] { if (canRenameObject.value) { items.push({ label: t("contextMenu.renameObject"), action: openRenameObjectDialog, icon: Pencil }); } + if (node.type === "view") { + items.push({ + label: t("contextMenu.dropView"), + action: requestDropObject, + icon: Trash2, + variant: "destructive" as const, + }); + } items.push({ label: t("contextMenu.newQuery"), action: newQuery, icon: TerminalSquare }); if (canOpenDiagram.value) { items.push({ label: t("diagram.open"), action: openDiagram, icon: Network }); @@ -2236,9 +2400,34 @@ function treeItemMenuItems(): ContextMenuItem[] { // 7. Column if (node.type === "column") { + items.push({ label: t("contextMenu.copyName"), action: copyName, icon: Copy }); if (canOpenFieldLineage.value) { + items.push({ label: "", separator: true }); items.push({ label: t("lineage.open"), action: openFieldLineage, icon: Network }); } + if (canDropTableChildObject.value) { + items.push({ label: "", separator: true }); + items.push({ + label: dropTableChildObjectMenuLabel(), + action: requestDropTableChildObject, + icon: Trash2, + variant: "destructive" as const, + }); + } + return items; + } + + if (node.type === "index" || node.type === "fkey" || node.type === "trigger") { + items.push({ label: t("contextMenu.copyName"), action: copyName, icon: Copy }); + if (canDropTableChildObject.value) { + items.push({ label: "", separator: true }); + items.push({ + label: dropTableChildObjectMenuLabel(), + action: requestDropTableChildObject, + icon: Trash2, + variant: "destructive" as const, + }); + } return items; } @@ -2611,19 +2800,22 @@ function treeItemMenuItems(): ContextMenuItem[] { + + { + return api.buildDropTableChildObjectSql(options); +} + export function buildEmptyTableSql(options: TableAdminSqlOptions): Promise { return api.buildEmptyTableSql(options); } diff --git a/apps/desktop/src/lib/http.ts b/apps/desktop/src/lib/http.ts index 22922e0384..2dfe623abf 100644 --- a/apps/desktop/src/lib/http.ts +++ b/apps/desktop/src/lib/http.ts @@ -81,6 +81,7 @@ import type { BuildRenameObjectSqlOptions } from "@/lib/objectRenameSql"; import type { CreateDatabaseSqlOptions } from "@/lib/createDatabaseSql"; import type { DatabaseNameSqlOptions, + DropTableChildObjectSqlOptions, DropObjectSqlOptions, DuplicateTableStructureSqlOptions, SchemaNameSqlOptions, @@ -588,6 +589,10 @@ export async function buildDropTableSql(options: TableAdminSqlOptions): Promise< return post("/api/query/build-drop-table-sql", { options }); } +export async function buildDropTableChildObjectSql(options: DropTableChildObjectSqlOptions): Promise { + return post("/api/query/build-drop-table-child-object-sql", { options }); +} + export async function buildEmptyTableSql(options: TableAdminSqlOptions): Promise { return post("/api/query/build-empty-table-sql", { options }); } diff --git a/apps/desktop/src/lib/tauri.ts b/apps/desktop/src/lib/tauri.ts index 27acaf8a85..4a0d5daaeb 100644 --- a/apps/desktop/src/lib/tauri.ts +++ b/apps/desktop/src/lib/tauri.ts @@ -54,6 +54,7 @@ import type { BuildRenameObjectSqlOptions } from "@/lib/objectRenameSql"; import type { CreateDatabaseSqlOptions } from "@/lib/createDatabaseSql"; import type { DatabaseNameSqlOptions, + DropTableChildObjectSqlOptions, DropObjectSqlOptions, DuplicateTableStructureSqlOptions, SchemaNameSqlOptions, @@ -580,6 +581,10 @@ export async function buildDropTableSql(options: TableAdminSqlOptions): Promise< return invoke("build_drop_table_sql", { options }); } +export async function buildDropTableChildObjectSql(options: DropTableChildObjectSqlOptions): Promise { + return invoke("build_drop_table_child_object_sql", { options }); +} + export async function buildEmptyTableSql(options: TableAdminSqlOptions): Promise { return invoke("build_empty_table_sql", { options }); } diff --git a/apps/desktop/src/stores/connectionStore.ts b/apps/desktop/src/stores/connectionStore.ts index 8e37ac6f70..26ec0e0b57 100644 --- a/apps/desktop/src/stores/connectionStore.ts +++ b/apps/desktop/src/stores/connectionStore.ts @@ -1170,6 +1170,7 @@ export const useConnectionStore = defineStore("connection", () => { connectionId, database, schema, + tableName: table, meta: idx, })), ); @@ -1210,6 +1211,7 @@ export const useConnectionStore = defineStore("connection", () => { connectionId, database, schema, + tableName: table, meta: fk, })), ); @@ -1244,6 +1246,7 @@ export const useConnectionStore = defineStore("connection", () => { connectionId, database, schema, + tableName: table, meta: tr, })), ); diff --git a/crates/dbx-core/src/db_admin_sql.rs b/crates/dbx-core/src/db_admin_sql.rs index 515639eea0..c96c98d64d 100644 --- a/crates/dbx-core/src/db_admin_sql.rs +++ b/crates/dbx-core/src/db_admin_sql.rs @@ -12,6 +12,15 @@ pub enum DatabaseObjectType { Function, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum TableChildObjectType { + Column, + Index, + ForeignKey, + Trigger, +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RenameObjectSqlOptions { @@ -66,6 +75,18 @@ pub struct TableAdminSqlOptions { pub table_name: String, } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DropTableChildObjectSqlOptions { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub database_type: Option, + pub object_type: TableChildObjectType, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub schema: Option, + pub table_name: String, + pub name: String, +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct DatabaseNameSqlOptions { @@ -135,6 +156,75 @@ pub fn build_drop_table_sql(options: TableAdminSqlOptions) -> String { format!("DROP TABLE {};", qualified_name(options.database_type, options.schema.as_deref(), &options.table_name)) } +pub fn build_drop_table_child_object_sql(options: DropTableChildObjectSqlOptions) -> Result { + let database_type = options.database_type; + let table = qualified_name(database_type, options.schema.as_deref(), &options.table_name); + let name = quote_rename_identifier(database_type, &options.name); + match options.object_type { + TableChildObjectType::Column => Ok(format!("ALTER TABLE {table} DROP COLUMN {name};")), + TableChildObjectType::Index => { + if matches!(database_type, Some(DatabaseType::ClickHouse | DatabaseType::Redshift)) { + return Err(format!("Dropping indexes is not supported for {}.", database_label(database_type))); + } + if matches!(database_type, Some(DatabaseType::Mysql | DatabaseType::Goldendb | DatabaseType::SqlServer)) { + return Ok(format!("DROP INDEX {name} ON {table};")); + } + if matches!( + database_type, + Some( + DatabaseType::Postgres + | DatabaseType::Gaussdb + | DatabaseType::OpenGauss + | DatabaseType::Highgo + | DatabaseType::Vastbase + | DatabaseType::Kingbase + | DatabaseType::Oracle + | DatabaseType::Dameng + | DatabaseType::OceanbaseOracle + | DatabaseType::Iris + ) + ) && options.schema.as_deref().is_some_and(|schema| !schema.is_empty()) + { + let schema = quote_rename_identifier(database_type, options.schema.as_deref().unwrap()); + return Ok(format!("DROP INDEX {schema}.{name};")); + } + Ok(format!("DROP INDEX {name};")) + } + TableChildObjectType::ForeignKey => { + if matches!(database_type, Some(DatabaseType::Mysql | DatabaseType::Goldendb)) { + Ok(format!("ALTER TABLE {table} DROP FOREIGN KEY {name};")) + } else { + Ok(format!("ALTER TABLE {table} DROP CONSTRAINT {name};")) + } + } + TableChildObjectType::Trigger => { + if matches!( + database_type, + Some( + DatabaseType::Postgres + | DatabaseType::Gaussdb + | DatabaseType::OpenGauss + | DatabaseType::Highgo + | DatabaseType::Vastbase + | DatabaseType::Kingbase + ) + ) { + Ok(format!("DROP TRIGGER {name} ON {table};")) + } else if matches!(database_type, Some(DatabaseType::SqlServer)) { + Ok(format!("DROP TRIGGER {name};")) + } else if database_type.is_some_and(is_schema_aware) + && options.schema.as_deref().is_some_and(|schema| !schema.is_empty()) + && !matches!(database_type, Some(DatabaseType::Mysql | DatabaseType::Goldendb)) + { + let schema = quote_rename_identifier(database_type, options.schema.as_deref().unwrap()); + Ok(format!("DROP TRIGGER {schema}.{name};")) + } else { + Ok(format!("DROP TRIGGER {name};")) + } + } + } +} + pub fn build_empty_table_sql(options: TableAdminSqlOptions) -> String { let table = qualified_name(options.database_type, options.schema.as_deref(), &options.table_name); match options.database_type { @@ -481,6 +571,76 @@ mod tests { ); } + #[test] + fn builds_drop_table_child_object_sql() { + assert_eq!( + build_drop_table_child_object_sql(DropTableChildObjectSqlOptions { + database_type: Some(DatabaseType::Postgres), + object_type: TableChildObjectType::Column, + schema: Some("public".to_string()), + table_name: "orders".to_string(), + name: "status".to_string(), + }) + .unwrap(), + "ALTER TABLE \"public\".\"orders\" DROP COLUMN \"status\";" + ); + assert_eq!( + build_drop_table_child_object_sql(DropTableChildObjectSqlOptions { + database_type: Some(DatabaseType::Mysql), + object_type: TableChildObjectType::Index, + schema: None, + table_name: "orders".to_string(), + name: "idx_orders_status".to_string(), + }) + .unwrap(), + "DROP INDEX `idx_orders_status` ON `orders`;" + ); + assert_eq!( + build_drop_table_child_object_sql(DropTableChildObjectSqlOptions { + database_type: Some(DatabaseType::Postgres), + object_type: TableChildObjectType::Index, + schema: Some("public".to_string()), + table_name: "orders".to_string(), + name: "idx_orders_status".to_string(), + }) + .unwrap(), + "DROP INDEX \"public\".\"idx_orders_status\";" + ); + assert_eq!( + build_drop_table_child_object_sql(DropTableChildObjectSqlOptions { + database_type: Some(DatabaseType::Mysql), + object_type: TableChildObjectType::ForeignKey, + schema: None, + table_name: "orders".to_string(), + name: "fk_orders_user".to_string(), + }) + .unwrap(), + "ALTER TABLE `orders` DROP FOREIGN KEY `fk_orders_user`;" + ); + assert_eq!( + build_drop_table_child_object_sql(DropTableChildObjectSqlOptions { + database_type: Some(DatabaseType::SqlServer), + object_type: TableChildObjectType::ForeignKey, + schema: Some("dbo".to_string()), + table_name: "orders".to_string(), + name: "fk_orders_user".to_string(), + }) + .unwrap(), + "ALTER TABLE [dbo].[orders] DROP CONSTRAINT [fk_orders_user];" + ); + assert_eq!( + build_drop_table_child_object_sql(DropTableChildObjectSqlOptions { + database_type: Some(DatabaseType::Postgres), + object_type: TableChildObjectType::Trigger, + schema: Some("public".to_string()), + table_name: "orders".to_string(), + name: "orders_audit".to_string(), + }) + .unwrap(), + "DROP TRIGGER \"orders_audit\" ON \"public\".\"orders\";" + ); + } + #[test] fn builds_duplicate_table_structure_sql() { assert_eq!( diff --git a/crates/dbx-web/src/main.rs b/crates/dbx-web/src/main.rs index 7635b39654..60ce7091ff 100644 --- a/crates/dbx-web/src/main.rs +++ b/crates/dbx-web/src/main.rs @@ -152,6 +152,7 @@ async fn main() { .route("/query/build-duckdb-attach-database-sql", post(routes::query::build_duckdb_attach_database_sql)) .route("/query/build-drop-object-sql", post(routes::query::build_drop_object_sql)) .route("/query/build-drop-table-sql", post(routes::query::build_drop_table_sql)) + .route("/query/build-drop-table-child-object-sql", post(routes::query::build_drop_table_child_object_sql)) .route("/query/build-empty-table-sql", post(routes::query::build_empty_table_sql)) .route("/query/build-truncate-table-sql", post(routes::query::build_truncate_table_sql)) .route("/query/build-drop-database-sql", post(routes::query::build_drop_database_sql)) diff --git a/crates/dbx-web/src/routes/query.rs b/crates/dbx-web/src/routes/query.rs index 2602214540..8a94e73386 100644 --- a/crates/dbx-web/src/routes/query.rs +++ b/crates/dbx-web/src/routes/query.rs @@ -148,6 +148,12 @@ pub struct BuildTableAdminSqlRequest { pub options: dbx_core::db_admin_sql::TableAdminSqlOptions, } +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BuildDropTableChildObjectSqlRequest { + pub options: dbx_core::db_admin_sql::DropTableChildObjectSqlOptions, +} + #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct BuildDatabaseNameSqlRequest { @@ -489,6 +495,12 @@ pub async fn build_drop_table_sql(Json(req): Json) -> Json(dbx_core::db_admin_sql::build_drop_table_sql(req.options)) } +pub async fn build_drop_table_child_object_sql( + Json(req): Json, +) -> Result, AppError> { + dbx_core::db_admin_sql::build_drop_table_child_object_sql(req.options).map(Json).map_err(AppError) +} + pub async fn build_empty_table_sql(Json(req): Json) -> Json { Json(dbx_core::db_admin_sql::build_empty_table_sql(req.options)) } diff --git a/src-tauri/src/commands/query.rs b/src-tauri/src/commands/query.rs index 7e82d227d5..be87a33c02 100644 --- a/src-tauri/src/commands/query.rs +++ b/src-tauri/src/commands/query.rs @@ -286,6 +286,13 @@ pub fn build_drop_table_sql(options: dbx_core::db_admin_sql::TableAdminSqlOption Ok(dbx_core::db_admin_sql::build_drop_table_sql(options)) } +#[tauri::command] +pub fn build_drop_table_child_object_sql( + options: dbx_core::db_admin_sql::DropTableChildObjectSqlOptions, +) -> Result { + dbx_core::db_admin_sql::build_drop_table_child_object_sql(options) +} + #[tauri::command] pub fn build_empty_table_sql(options: dbx_core::db_admin_sql::TableAdminSqlOptions) -> Result { Ok(dbx_core::db_admin_sql::build_empty_table_sql(options)) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 32cef0f3a1..7a3a733364 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -413,6 +413,7 @@ pub fn run() { commands::query::build_duckdb_attach_database_sql, commands::query::build_drop_object_sql, commands::query::build_drop_table_sql, + commands::query::build_drop_table_child_object_sql, commands::query::build_empty_table_sql, commands::query::build_truncate_table_sql, commands::query::build_drop_database_sql,