use crate::automation::{Trigger, TriggerOperation, TriggerTiming};
use crate::engine::Database;
use crate::error::DbxResult;
pub struct TriggerExecutor {
triggers: Vec<Trigger>,
}
impl TriggerExecutor {
pub fn new() -> Self {
Self {
triggers: Vec::new(),
}
}
pub fn register(&mut self, trigger: Trigger) {
self.triggers.push(trigger);
}
pub fn register_all(&mut self, triggers: Vec<Trigger>) {
self.triggers.extend(triggers);
}
pub fn unregister(&mut self, name: &str) -> bool {
if let Some(pos) = self.triggers.iter().position(|t| t.name == name) {
self.triggers.remove(pos);
true
} else {
false
}
}
pub fn list_triggers(&self) -> &[Trigger] {
&self.triggers
}
pub fn fire_before(
&self,
db: &Database,
operation: TriggerOperation,
table: &str,
) -> DbxResult<()> {
self.fire_triggers(db, TriggerTiming::Before, operation, table)
}
pub fn fire_after(
&self,
db: &Database,
operation: TriggerOperation,
table: &str,
) -> DbxResult<()> {
self.fire_triggers(db, TriggerTiming::After, operation, table)
}
fn evaluate_condition_result(result: &[arrow::record_batch::RecordBatch]) -> bool {
use arrow::array::AsArray;
if result.is_empty() {
return false;
}
let batch = &result[0];
if batch.num_rows() == 0 || batch.num_columns() == 0 {
return false;
}
let column = batch.column(0);
use arrow::datatypes::DataType;
match column.data_type() {
DataType::Boolean => {
let bool_array = column.as_boolean();
bool_array.value(0)
}
DataType::Int64 | DataType::Int32 | DataType::Int16 | DataType::Int8 => {
let int_array = column.as_primitive::<arrow::datatypes::Int64Type>();
int_array.value(0) != 0
}
DataType::UInt64 | DataType::UInt32 | DataType::UInt16 | DataType::UInt8 => {
let uint_array = column.as_primitive::<arrow::datatypes::UInt64Type>();
uint_array.value(0) != 0
}
DataType::Float64 | DataType::Float32 => {
let float_array = column.as_primitive::<arrow::datatypes::Float64Type>();
float_array.value(0) != 0.0
}
DataType::Utf8 => {
let str_array = column.as_string::<i32>();
let val = str_array.value(0);
!val.is_empty() && val != "0" && val.to_lowercase() != "false"
}
_ => false, }
}
fn fire_triggers(
&self,
db: &Database,
timing: TriggerTiming,
operation: TriggerOperation,
table: &str,
) -> DbxResult<()> {
let matching_triggers: Vec<&Trigger> = self
.triggers
.iter()
.filter(|t| t.timing == timing && t.operation == operation && t.table == table)
.collect();
for trigger in matching_triggers {
if let Some(ref condition) = trigger.condition {
match db.execute_sql(condition) {
Ok(result) => {
if !Self::evaluate_condition_result(&result) {
#[cfg(debug_assertions)]
println!(
"[Trigger] Skipping '{}' - WHEN condition evaluated to false",
trigger.name
);
continue; }
}
Err(e) => {
eprintln!(
"[Trigger] Failed to evaluate WHEN condition for '{}': {}",
trigger.name, e
);
continue; }
}
}
for sql in &trigger.body {
match db.execute_sql(sql) {
Ok(_) => {
#[cfg(debug_assertions)]
println!(
"[Trigger] Successfully executed '{}' on {:?} {:?}: {}",
trigger.name, timing, operation, sql
);
}
Err(e) => {
eprintln!(
"[Trigger] Failed to execute '{}': {} (SQL: {})",
trigger.name, e, sql
);
}
}
}
}
Ok(())
}
}
impl Default for TriggerExecutor {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::automation::ForEachType;
#[test]
fn test_trigger_executor_register() {
let mut executor = TriggerExecutor::new();
let trigger = Trigger::new(
"test_trigger",
TriggerTiming::After,
TriggerOperation::Insert,
"users",
ForEachType::Row,
None,
vec!["INSERT INTO audit_logs VALUES (1, 'test')".to_string()],
);
executor.register(trigger);
assert_eq!(executor.list_triggers().len(), 1);
}
#[test]
fn test_trigger_executor_unregister() {
let mut executor = TriggerExecutor::new();
let trigger = Trigger::new(
"test_trigger",
TriggerTiming::After,
TriggerOperation::Insert,
"users",
ForEachType::Row,
None,
vec!["INSERT INTO audit_logs VALUES (1, 'test')".to_string()],
);
executor.register(trigger);
assert_eq!(executor.list_triggers().len(), 1);
let removed = executor.unregister("test_trigger");
assert!(removed);
assert_eq!(executor.list_triggers().len(), 0);
}
#[test]
fn test_trigger_executor_filter() {
let mut executor = TriggerExecutor::new();
executor.register(Trigger::new(
"after_insert",
TriggerTiming::After,
TriggerOperation::Insert,
"users",
ForEachType::Row,
None,
vec!["INSERT INTO logs VALUES (1)".to_string()],
));
executor.register(Trigger::new(
"before_update",
TriggerTiming::Before,
TriggerOperation::Update,
"users",
ForEachType::Row,
None,
vec!["UPDATE stats SET count = count + 1".to_string()],
));
assert_eq!(executor.list_triggers().len(), 2);
}
}