dbx_core/automation/
trigger_parser.rs1use crate::automation::{ForEachType, Trigger, TriggerOperation, TriggerTiming};
6use crate::error::{DbxError, DbxResult};
7use crate::sql::StringCaseExt;
8
9pub fn parse_create_trigger(sql: &str) -> DbxResult<Trigger> {
24 let sql = sql.trim();
26
27 if !sql.starts_with_ignore_ascii_case("CREATE TRIGGER") {
29 return Err(DbxError::InvalidOperation {
30 message: format!("Expected CREATE TRIGGER, got: {}", sql),
31 context: "SQL parsing".to_string(),
32 });
33 }
34
35 let tokens: Vec<&str> = sql.split_whitespace().collect();
37 if tokens.len() < 7 {
38 return Err(DbxError::InvalidOperation {
39 message: "Invalid CREATE TRIGGER syntax".to_string(),
40 context: "SQL parsing".to_string(),
41 });
42 }
43
44 let name = tokens[2].to_string();
46
47 let timing = match tokens[3].to_uppercase().as_str() {
49 "BEFORE" => TriggerTiming::Before,
50 "AFTER" => TriggerTiming::After,
51 _ => {
52 return Err(DbxError::InvalidOperation {
53 message: format!("Invalid timing: {}", tokens[3]),
54 context: "CREATE TRIGGER parsing".to_string(),
55 });
56 }
57 };
58
59 let operation = match tokens[4].to_uppercase().as_str() {
61 "INSERT" => TriggerOperation::Insert,
62 "UPDATE" => TriggerOperation::Update,
63 "DELETE" => TriggerOperation::Delete,
64 _ => {
65 return Err(DbxError::InvalidOperation {
66 message: format!("Invalid operation: {}", tokens[4]),
67 context: "CREATE TRIGGER parsing".to_string(),
68 });
69 }
70 };
71
72 if tokens[5].to_uppercase() != "ON" {
74 return Err(DbxError::InvalidOperation {
75 message: format!("Expected ON, got: {}", tokens[5]),
76 context: "CREATE TRIGGER parsing".to_string(),
77 });
78 }
79
80 let table = tokens[6].to_string();
82
83 let mut for_each = ForEachType::Row; let mut body_start_idx = 7;
86
87 if tokens.len() > 9 && tokens[7].to_uppercase() == "FOR" && tokens[8].to_uppercase() == "EACH" {
88 for_each = match tokens[9].to_uppercase().as_str() {
89 "ROW" => ForEachType::Row,
90 "STATEMENT" => ForEachType::Statement,
91 _ => {
92 return Err(DbxError::InvalidOperation {
93 message: format!("Invalid FOR EACH type: {}", tokens[9]),
94 context: "CREATE TRIGGER parsing".to_string(),
95 });
96 }
97 };
98 body_start_idx = 10;
99 }
100
101 let mut condition = None;
103 if tokens.len() > body_start_idx && tokens[body_start_idx].to_uppercase() == "WHEN" {
104 let when_start = sql.find("WHEN").unwrap();
106 let begin_pos = sql
107 .find("BEGIN")
108 .ok_or_else(|| DbxError::InvalidOperation {
109 message: "Missing BEGIN".to_string(),
110 context: "CREATE TRIGGER parsing".to_string(),
111 })?;
112 let condition_str = sql[when_start + 4..begin_pos].trim();
113
114 let condition_str = condition_str
116 .trim_start_matches('(')
117 .trim_end_matches(')')
118 .trim();
119 condition = Some(condition_str.to_string());
120 }
121
122 let begin_pos = sql
124 .find("BEGIN")
125 .ok_or_else(|| DbxError::InvalidOperation {
126 message: "Missing BEGIN".to_string(),
127 context: "CREATE TRIGGER parsing".to_string(),
128 })?;
129 let end_pos = sql.rfind("END").ok_or_else(|| DbxError::InvalidOperation {
130 message: "Missing END".to_string(),
131 context: "CREATE TRIGGER parsing".to_string(),
132 })?;
133
134 let body_sql = sql[begin_pos + 5..end_pos].trim();
135
136 let body: Vec<String> = body_sql
138 .split(';')
139 .map(|s| s.trim().to_string())
140 .filter(|s| !s.is_empty())
141 .collect();
142
143 if body.is_empty() {
144 return Err(DbxError::InvalidOperation {
145 message: "Trigger body is empty".to_string(),
146 context: "CREATE TRIGGER parsing".to_string(),
147 });
148 }
149
150 Ok(Trigger::new(
151 name, timing, operation, table, for_each, condition, body,
152 ))
153}
154
155pub fn parse_drop_trigger(sql: &str) -> DbxResult<String> {
162 let sql = sql.trim();
163
164 if !sql.starts_with_ignore_ascii_case("DROP TRIGGER") {
165 return Err(DbxError::InvalidOperation {
166 message: format!("Expected DROP TRIGGER, got: {}", sql),
167 context: "SQL parsing".to_string(),
168 });
169 }
170
171 let tokens: Vec<&str> = sql.split_whitespace().collect();
172 if tokens.len() < 3 {
173 return Err(DbxError::InvalidOperation {
174 message: "Invalid DROP TRIGGER syntax".to_string(),
175 context: "SQL parsing".to_string(),
176 });
177 }
178
179 let name = tokens[2].trim_end_matches(';').to_string();
180 Ok(name)
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[test]
188 fn test_parse_create_trigger_basic() {
189 let sql = r#"
190 CREATE TRIGGER audit_trigger
191 AFTER INSERT ON users
192 BEGIN
193 INSERT INTO audit_logs VALUES (NEW.id, 'INSERT');
194 END;
195 "#;
196
197 let trigger = parse_create_trigger(sql).unwrap();
198 assert_eq!(trigger.name, "audit_trigger");
199 assert_eq!(trigger.timing, TriggerTiming::After);
200 assert_eq!(trigger.operation, TriggerOperation::Insert);
201 assert_eq!(trigger.table, "users");
202 assert_eq!(trigger.for_each, ForEachType::Row);
203 assert!(trigger.condition.is_none());
204 assert_eq!(trigger.body.len(), 1);
205 }
206
207 #[test]
208 fn test_parse_create_trigger_with_condition() {
209 let sql = r#"
210 CREATE TRIGGER check_age
211 BEFORE INSERT ON users
212 FOR EACH ROW
213 WHEN (NEW.age > 18)
214 BEGIN
215 INSERT INTO adult_users VALUES (NEW.id);
216 END;
217 "#;
218
219 let trigger = parse_create_trigger(sql).unwrap();
220 assert_eq!(trigger.name, "check_age");
221 assert_eq!(trigger.timing, TriggerTiming::Before);
222 assert_eq!(trigger.for_each, ForEachType::Row);
223 assert!(trigger.condition.is_some());
224 assert_eq!(trigger.condition.unwrap(), "NEW.age > 18");
225 }
226
227 #[test]
228 fn test_parse_create_trigger_multiple_statements() {
229 let sql = r#"
230 CREATE TRIGGER multi_action
231 AFTER UPDATE ON products
232 BEGIN
233 UPDATE logs SET count = count + 1;
234 INSERT INTO history VALUES (OLD.id, NEW.id);
235 END;
236 "#;
237
238 let trigger = parse_create_trigger(sql).unwrap();
239 assert_eq!(trigger.body.len(), 2);
240 }
241
242 #[test]
243 fn test_parse_drop_trigger() {
244 let sql = "DROP TRIGGER audit_trigger;";
245 let name = parse_drop_trigger(sql).unwrap();
246 assert_eq!(name, "audit_trigger");
247 }
248
249 #[test]
250 fn test_parse_create_trigger_invalid() {
251 let sql = "CREATE TABLE users (id INT);";
252 assert!(parse_create_trigger(sql).is_err());
253 }
254}