Added support for SQLite triggers (#2037)
Some checks failed
license / Release Audit Tool (RAT) (push) Has been cancelled
Rust / codestyle (push) Has been cancelled
Rust / lint (push) Has been cancelled
Rust / benchmark-lint (push) Has been cancelled
Rust / compile (push) Has been cancelled
Rust / docs (push) Has been cancelled
Rust / compile-no-std (push) Has been cancelled
Rust / test (beta) (push) Has been cancelled
Rust / test (nightly) (push) Has been cancelled
Rust / test (stable) (push) Has been cancelled

Co-authored-by: Ifeanyi Ubah <ify1992@yahoo.com>
This commit is contained in:
Luca Cappelletti 2025-10-11 13:16:56 +02:00 committed by GitHub
parent cc595cfd84
commit 4490c8c55c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 374 additions and 46 deletions

View file

@ -610,6 +610,281 @@ fn test_update_delete_limit() {
}
}
#[test]
fn test_create_trigger() {
let statement1 = "CREATE TRIGGER trg_inherit_asset_models AFTER INSERT ON assets FOR EACH ROW BEGIN INSERT INTO users (name) SELECT pam.name FROM users AS pam; END";
match sqlite().verified_stmt(statement1) {
Statement::CreateTrigger(CreateTrigger {
or_alter,
temporary,
or_replace,
is_constraint,
name,
period,
period_before_table,
events,
table_name,
referenced_table_name,
referencing,
trigger_object,
condition,
exec_body: _,
statements_as,
statements: _,
characteristics,
}) => {
assert!(!or_alter);
assert!(!temporary);
assert!(!or_replace);
assert!(!is_constraint);
assert_eq!(name.to_string(), "trg_inherit_asset_models");
assert_eq!(period, TriggerPeriod::After);
assert!(period_before_table);
assert_eq!(events, vec![TriggerEvent::Insert]);
assert_eq!(table_name.to_string(), "assets");
assert!(referenced_table_name.is_none());
assert!(referencing.is_empty());
assert_eq!(
trigger_object,
Some(TriggerObjectKind::ForEach(TriggerObject::Row))
);
assert!(condition.is_none());
assert!(!statements_as);
assert!(characteristics.is_none());
}
_ => unreachable!("Expected CREATE TRIGGER statement"),
}
// Here we check that the variant of CREATE TRIGGER that omits the `FOR EACH ROW` clause,
// which in SQLite may be implicitly assumed, is parsed correctly.
let statement2 = "CREATE TRIGGER log_new_user AFTER INSERT ON users BEGIN INSERT INTO user_log (user_id, action, timestamp) VALUES (NEW.id, 'created', datetime('now')); END";
match sqlite().verified_stmt(statement2) {
Statement::CreateTrigger(CreateTrigger {
or_alter,
temporary,
or_replace,
is_constraint,
name,
period,
period_before_table,
events,
table_name,
referenced_table_name,
referencing,
trigger_object,
condition,
exec_body: _,
statements_as,
statements: _,
characteristics,
}) => {
assert!(!or_alter);
assert!(!temporary);
assert!(!or_replace);
assert!(!is_constraint);
assert_eq!(name.to_string(), "log_new_user");
assert_eq!(period, TriggerPeriod::After);
assert!(period_before_table);
assert_eq!(events, vec![TriggerEvent::Insert]);
assert_eq!(table_name.to_string(), "users");
assert!(referenced_table_name.is_none());
assert!(referencing.is_empty());
assert!(trigger_object.is_none());
assert!(condition.is_none());
assert!(!statements_as);
assert!(characteristics.is_none());
}
_ => unreachable!("Expected CREATE TRIGGER statement"),
}
let statement3 = "CREATE TRIGGER cleanup_orders AFTER DELETE ON customers BEGIN DELETE FROM orders WHERE customer_id = OLD.id; DELETE FROM invoices WHERE customer_id = OLD.id; END";
match sqlite().verified_stmt(statement3) {
Statement::CreateTrigger(CreateTrigger {
or_alter,
temporary,
or_replace,
is_constraint,
name,
period,
period_before_table,
events,
table_name,
referenced_table_name,
referencing,
trigger_object,
condition,
exec_body: _,
statements_as,
statements: _,
characteristics,
}) => {
assert!(!or_alter);
assert!(!temporary);
assert!(!or_replace);
assert!(!is_constraint);
assert_eq!(name.to_string(), "cleanup_orders");
assert_eq!(period, TriggerPeriod::After);
assert!(period_before_table);
assert_eq!(events, vec![TriggerEvent::Delete]);
assert_eq!(table_name.to_string(), "customers");
assert!(referenced_table_name.is_none());
assert!(referencing.is_empty());
assert!(trigger_object.is_none());
assert!(condition.is_none());
assert!(!statements_as);
assert!(characteristics.is_none());
}
_ => unreachable!("Expected CREATE TRIGGER statement"),
}
let statement4 = "CREATE TRIGGER trg_before_update BEFORE UPDATE ON products FOR EACH ROW WHEN NEW.price < 0 BEGIN SELECT RAISE(ABORT, 'Price cannot be negative'); END";
match sqlite().verified_stmt(statement4) {
Statement::CreateTrigger(CreateTrigger {
or_alter,
temporary,
or_replace,
is_constraint,
name,
period,
period_before_table,
events,
table_name,
referenced_table_name,
referencing,
trigger_object,
condition,
exec_body: _,
statements_as,
statements: _,
characteristics,
}) => {
assert!(!or_alter);
assert!(!temporary);
assert!(!or_replace);
assert!(!is_constraint);
assert_eq!(name.to_string(), "trg_before_update");
assert_eq!(period, TriggerPeriod::Before);
assert!(period_before_table);
assert_eq!(events, vec![TriggerEvent::Update(Vec::new())]);
assert_eq!(table_name.to_string(), "products");
assert!(referenced_table_name.is_none());
assert!(referencing.is_empty());
assert_eq!(
trigger_object,
Some(TriggerObjectKind::ForEach(TriggerObject::Row))
);
assert!(condition.is_some());
assert!(!statements_as);
assert!(characteristics.is_none());
}
_ => unreachable!("Expected CREATE TRIGGER statement"),
}
// We test a INSTEAD OF trigger on a view
let statement5 = "CREATE TRIGGER trg_instead_of_insert INSTEAD OF INSERT ON my_view BEGIN INSERT INTO my_table (col1, col2) VALUES (NEW.col1, NEW.col2); END";
match sqlite().verified_stmt(statement5) {
Statement::CreateTrigger(CreateTrigger {
or_alter,
temporary,
or_replace,
is_constraint,
name,
period,
period_before_table,
events,
table_name,
referenced_table_name,
referencing,
trigger_object,
condition,
exec_body: _,
statements_as,
statements: _,
characteristics,
}) => {
assert!(!or_alter);
assert!(!temporary);
assert!(!or_replace);
assert!(!is_constraint);
assert_eq!(name.to_string(), "trg_instead_of_insert");
assert_eq!(period, TriggerPeriod::InsteadOf);
assert!(period_before_table);
assert_eq!(events, vec![TriggerEvent::Insert]);
assert_eq!(table_name.to_string(), "my_view");
assert!(referenced_table_name.is_none());
assert!(referencing.is_empty());
assert!(trigger_object.is_none());
assert!(condition.is_none());
assert!(!statements_as);
assert!(characteristics.is_none());
}
_ => unreachable!("Expected CREATE TRIGGER statement"),
}
// We test a temporary trigger
let statement6 = "CREATE TEMPORARY TRIGGER temp_trigger AFTER INSERT ON temp_table BEGIN UPDATE log_table SET count = count + 1; END";
match sqlite().verified_stmt(statement6) {
Statement::CreateTrigger(CreateTrigger {
or_alter,
temporary,
or_replace,
is_constraint,
name,
period,
period_before_table,
events,
table_name,
referenced_table_name,
referencing,
trigger_object,
condition,
exec_body: _,
statements_as,
statements: _,
characteristics,
}) => {
assert!(!or_alter);
assert!(temporary);
assert!(!or_replace);
assert!(!is_constraint);
assert_eq!(name.to_string(), "temp_trigger");
assert_eq!(period, TriggerPeriod::After);
assert!(period_before_table);
assert_eq!(events, vec![TriggerEvent::Insert]);
assert_eq!(table_name.to_string(), "temp_table");
assert!(referenced_table_name.is_none());
assert!(referencing.is_empty());
assert!(trigger_object.is_none());
assert!(condition.is_none());
assert!(!statements_as);
assert!(characteristics.is_none());
}
_ => unreachable!("Expected CREATE TRIGGER statement"),
}
}
#[test]
fn test_drop_trigger() {
let statement = "DROP TRIGGER IF EXISTS trg_inherit_asset_models";
match sqlite().verified_stmt(statement) {
Statement::DropTrigger(DropTrigger {
if_exists,
trigger_name,
table_name,
option,
}) => {
assert!(if_exists);
assert_eq!(trigger_name.to_string(), "trg_inherit_asset_models");
assert!(table_name.is_none());
assert!(option.is_none());
}
_ => unreachable!("Expected DROP TRIGGER statement"),
}
}
fn sqlite() -> TestedDialects {
TestedDialects::new(vec![Box::new(SQLiteDialect {})])
}