diff --git a/apps/desktop/src/components/structure/TableStructureEditor.vue b/apps/desktop/src/components/structure/TableStructureEditor.vue index c8073550f..0555e2c89 100644 --- a/apps/desktop/src/components/structure/TableStructureEditor.vue +++ b/apps/desktop/src/components/structure/TableStructureEditor.vue @@ -41,6 +41,7 @@ import type { TableInfoTab, TableStructureEditorDraft, TableStructureEditorTarge import { applyManticoreDdlColumnExtras, buildStructureTargetLabel, + canEditStructuredTriggerDraft, canEditManticoreColumnProperties, combineDataTypeForDatabase, combineDataTypeForDatabaseWithLengthUnit, @@ -2158,7 +2159,7 @@ function toggleDropTrigger(trigger: EditableStructureTrigger) { } function canEditTriggerDraft(trigger: EditableStructureTrigger): boolean { - return !triggersLoading.value && canEditTriggers.value && !trigger.markedForDrop; + return !triggersLoading.value && canEditTriggers.value && !trigger.markedForDrop && canEditStructuredTriggerDraft(databaseType.value, trigger); } function primarySqlOperation(sql: string): string { diff --git a/apps/desktop/src/lib/__tests__/table/tableStructureEditorState.spec.ts b/apps/desktop/src/lib/__tests__/table/tableStructureEditorState.spec.ts index c6f113040..8e403fc53 100644 --- a/apps/desktop/src/lib/__tests__/table/tableStructureEditorState.spec.ts +++ b/apps/desktop/src/lib/__tests__/table/tableStructureEditorState.spec.ts @@ -1,8 +1,10 @@ import { describe, expect, it } from "vitest"; import { + canEditStructuredTriggerDraft, combineDataTypeForDatabase, combineDataTypeForDatabaseWithLengthUnit, createColumnDrafts, + createTriggerDrafts, dataTypeLengthInputValue, dataTypeLengthUnitValue, DATA_TYPE_OPTIONS, @@ -24,6 +26,25 @@ import { } from "@/lib/table/tableStructureEditorState"; describe("tableStructureEditorState", () => { + it("keeps existing Oracle trigger drafts read-only until full source editing is available", () => { + const [existing] = createTriggerDrafts([{ name: "ORDERS_AUDIT", timing: "AFTER EACH ROW", event: "INSERT OR UPDATE", statement: "BEGIN NULL; END;" }]); + if (!existing) throw new Error("expected an existing trigger draft"); + + expect(canEditStructuredTriggerDraft("oracle", existing)).toBe(false); + expect(canEditStructuredTriggerDraft(undefined, existing)).toBe(false); + expect(canEditStructuredTriggerDraft("mysql", existing)).toBe(true); + expect( + canEditStructuredTriggerDraft("oracle", { + id: "new:trigger", + name: "ORDERS_AUDIT", + timing: "AFTER EACH ROW", + event: "INSERT", + statement: "BEGIN NULL; END;", + markedForDrop: false, + }), + ).toBe(true); + }); + it("hydrates Kingbase type parameters returned separately from the data type", () => { const columns = createColumnDrafts( [ diff --git a/apps/desktop/src/lib/table/tableStructureEditorState.ts b/apps/desktop/src/lib/table/tableStructureEditorState.ts index 64fa728fd..1c7ff3b20 100644 --- a/apps/desktop/src/lib/table/tableStructureEditorState.ts +++ b/apps/desktop/src/lib/table/tableStructureEditorState.ts @@ -816,6 +816,10 @@ export function createTriggerDrafts(triggers: TriggerInfo[]): EditableStructureT })); } +export function canEditStructuredTriggerDraft(databaseType: DatabaseType | undefined, trigger: EditableStructureTrigger): boolean { + return !trigger.original || (databaseType !== undefined && databaseType !== "oracle"); +} + export function toColumnNames(columns: string[]): string { return columns.join(", "); } diff --git a/crates/dbx-core/src/table_structure_sql/tests.rs b/crates/dbx-core/src/table_structure_sql/tests.rs index f08f701d0..83db210e3 100644 --- a/crates/dbx-core/src/table_structure_sql/tests.rs +++ b/crates/dbx-core/src/table_structure_sql/tests.rs @@ -3324,7 +3324,7 @@ fn builds_mysql_trigger_changes() { } #[test] -fn builds_oracle_multi_event_row_trigger_change() { +fn rejects_editing_existing_oracle_trigger_without_complete_source() { let mut existing = trigger( "DBX_TRIGGER_4320_AUDIT", "AFTER EACH ROW", @@ -3350,12 +3350,10 @@ fn builds_oracle_multi_event_row_trigger_change() { original_table_comment: None, }); - assert_eq!(result.warnings, Vec::::new()); + assert!(result.statements.is_empty()); assert_eq!( - result.statements, - vec![ - "CREATE OR REPLACE TRIGGER \"APP\".\"DBX_TRIGGER_4320_AUDIT\" AFTER INSERT OR UPDATE OR DELETE ON \"APP\".\"DBX_TRIGGER_4320\"\nFOR EACH ROW\nDECLARE\n v_event VARCHAR2(10);\nBEGIN\n v_event := CASE WHEN INSERTING THEN 'INSERT' WHEN UPDATING THEN 'UPDATE' ELSE 'DELETE' END;\nEND;", - ] + result.warnings, + vec!["Editing existing Oracle trigger \"DBX_TRIGGER_4320_AUDIT\" requires its complete source definition."] ); } @@ -3383,14 +3381,15 @@ fn builds_oracle_statement_trigger_without_row_clause() { } #[test] -fn renaming_oracle_trigger_drops_old_name_before_create() { - let mut existing = trigger("ORDERS_AUDIT_V2", "AFTER EACH ROW", "INSERT", "BEGIN\n NULL;\nEND;"); +fn drops_existing_oracle_trigger_without_reconstructing_it() { + let mut existing = trigger("ORDERS_AUDIT", "AFTER EACH ROW", "INSERT", "BEGIN\n NULL;\nEND;"); existing.original = Some(TriggerInfo { name: "ORDERS_AUDIT".to_string(), event: "INSERT".to_string(), timing: "AFTER EACH ROW".to_string(), statement: Some("BEGIN\n NULL;\nEND;".to_string()), }); + existing.marked_for_drop = true; let result = build_table_structure_change_sql(TableStructureSqlOptions { database_type: Some(DatabaseType::Oracle), @@ -3405,13 +3404,7 @@ fn renaming_oracle_trigger_drops_old_name_before_create() { }); assert_eq!(result.warnings, Vec::::new()); - assert_eq!( - result.statements, - vec![ - "DROP TRIGGER \"APP\".\"ORDERS_AUDIT\";", - "CREATE OR REPLACE TRIGGER \"APP\".\"ORDERS_AUDIT_V2\" AFTER INSERT ON \"APP\".\"ORDERS\"\nFOR EACH ROW\nBEGIN\n NULL;\nEND;", - ] - ); + assert_eq!(result.statements, vec!["DROP TRIGGER \"APP\".\"ORDERS_AUDIT\";"]); } #[test] diff --git a/crates/dbx-core/src/table_structure_sql/triggers.rs b/crates/dbx-core/src/table_structure_sql/triggers.rs index e38211d45..420bfac66 100644 --- a/crates/dbx-core/src/table_structure_sql/triggers.rs +++ b/crates/dbx-core/src/table_structure_sql/triggers.rs @@ -26,6 +26,15 @@ pub(super) fn build_trigger_sql(options: &TableStructureSqlOptions, warnings: &m } if let Some(original) = &trigger.original { + if dialect == StructureDialect::Oracle { + if has_trigger_change(trigger, original) { + warnings.push(format!( + "Editing existing Oracle trigger \"{}\" requires its complete source definition.", + original.name + )); + } + continue; + } if !has_trigger_change(trigger, original) { continue; }