vibesql_executor/
trigger_ddl.rs

1//! Trigger DDL execution module
2//!
3//! Handles CREATE TRIGGER, ALTER TRIGGER, and DROP TRIGGER statements
4
5use vibesql_ast::{
6    AlterTriggerAction, AlterTriggerStmt, CreateTriggerStmt, DropTriggerStmt, TriggerTiming,
7};
8use vibesql_catalog::TriggerDefinition;
9use vibesql_storage::Database;
10
11use crate::errors::ExecutorError;
12
13/// Executor for trigger DDL operations
14pub struct TriggerExecutor;
15
16impl TriggerExecutor {
17    /// Execute a CREATE TRIGGER statement
18    pub fn create_trigger(
19        db: &mut Database,
20        stmt: &CreateTriggerStmt,
21    ) -> Result<String, ExecutorError> {
22        // INSTEAD OF triggers can only be created on views
23        // BEFORE and AFTER triggers can only be created on tables
24        if stmt.timing == TriggerTiming::InsteadOf {
25            // Verify the target view exists
26            if db.catalog.get_view(&stmt.table_name).is_none() {
27                return Err(ExecutorError::Other(format!(
28                    "INSTEAD OF trigger requires a view, but '{}' is not a view",
29                    stmt.table_name
30                )));
31            }
32        } else {
33            // Verify the target table exists
34            if !db.catalog.table_exists(&stmt.table_name) {
35                return Err(ExecutorError::TableNotFound(stmt.table_name.clone()));
36            }
37        }
38
39        // Create trigger definition from statement
40        let trigger = TriggerDefinition::new(
41            stmt.trigger_name.clone(),
42            stmt.timing.clone(),
43            stmt.event.clone(),
44            stmt.table_name.clone(),
45            stmt.granularity.clone(),
46            stmt.when_condition.clone(),
47            stmt.triggered_action.clone(),
48        );
49
50        // Store in catalog
51        db.catalog.create_trigger(trigger)?;
52
53        Ok(format!("Trigger '{}' created successfully", stmt.trigger_name))
54    }
55
56    /// Execute an ALTER TRIGGER statement
57    pub fn alter_trigger(
58        db: &mut Database,
59        stmt: &AlterTriggerStmt,
60    ) -> Result<String, ExecutorError> {
61        // Get the trigger (verify it exists)
62        let mut trigger = db
63            .catalog
64            .get_trigger(&stmt.trigger_name)
65            .ok_or_else(|| ExecutorError::TriggerNotFound(stmt.trigger_name.clone()))?
66            .clone();
67
68        // Apply the action
69        match stmt.action {
70            AlterTriggerAction::Enable => {
71                trigger.enable();
72                db.catalog.update_trigger(trigger)?;
73                Ok(format!("Trigger '{}' enabled successfully", stmt.trigger_name))
74            }
75            AlterTriggerAction::Disable => {
76                trigger.disable();
77                db.catalog.update_trigger(trigger)?;
78                Ok(format!("Trigger '{}' disabled successfully", stmt.trigger_name))
79            }
80        }
81    }
82
83    /// Execute a DROP TRIGGER statement
84    pub fn drop_trigger(
85        db: &mut Database,
86        stmt: &DropTriggerStmt,
87    ) -> Result<String, ExecutorError> {
88        // Check if trigger exists
89        if db.catalog.get_trigger(&stmt.trigger_name).is_none() {
90            return Err(ExecutorError::TriggerNotFound(stmt.trigger_name.clone()));
91        }
92
93        // Remove from catalog
94        db.catalog.drop_trigger(&stmt.trigger_name)?;
95
96        // Note: CASCADE is not yet implemented
97        // When CASCADE is implemented, we would need to also drop any
98        // dependent objects (though triggers typically don't have dependents)
99
100        Ok(format!("Trigger '{}' dropped successfully", stmt.trigger_name))
101    }
102}