#[cfg(test)]
mod trigger_tests {
use heliosdb_nano::{EmbeddedDatabase, Value, DataType};
fn create_test_db() -> EmbeddedDatabase {
EmbeddedDatabase::new_in_memory().expect("Failed to create test database")
}
#[test]
fn test_create_trigger_basic_syntax() {
let db = create_test_db();
db.execute("CREATE TABLE test_table (id INT, value TEXT)").unwrap();
let result = db.execute(
"CREATE TRIGGER test_trigger
AFTER INSERT ON test_table
FOR EACH ROW
BEGIN
SELECT 1;
END"
);
match result {
Ok(_) => println!("CREATE TRIGGER succeeded"),
Err(e) => println!("CREATE TRIGGER not yet implemented: {}", e),
}
}
#[test]
fn test_create_trigger_if_not_exists() {
let db = create_test_db();
db.execute("CREATE TABLE test_table (id INT, value TEXT)").unwrap();
let result1 = db.execute(
"CREATE TRIGGER IF NOT EXISTS test_trigger
AFTER INSERT ON test_table
FOR EACH ROW
BEGIN
SELECT 1;
END"
);
let result2 = db.execute(
"CREATE TRIGGER IF NOT EXISTS test_trigger
AFTER INSERT ON test_table
FOR EACH ROW
BEGIN
SELECT 1;
END"
);
match (result1, result2) {
(Ok(_), Ok(_)) => println!("IF NOT EXISTS works correctly"),
(Err(e1), _) => println!("CREATE TRIGGER not implemented: {}", e1),
(_, Err(e2)) => println!("IF NOT EXISTS may not be working: {}", e2),
}
}
#[test]
fn test_drop_trigger_basic() {
let db = create_test_db();
db.execute("CREATE TABLE test_table (id INT, value TEXT)").unwrap();
let result = db.execute("DROP TRIGGER IF EXISTS test_trigger");
match result {
Ok(_) => println!("DROP TRIGGER succeeded"),
Err(e) => println!("DROP TRIGGER not yet implemented: {}", e),
}
}
#[test]
fn test_drop_trigger_with_table_name() {
let db = create_test_db();
db.execute("CREATE TABLE test_table (id INT, value TEXT)").unwrap();
let result = db.execute("DROP TRIGGER IF EXISTS test_trigger ON test_table");
match result {
Ok(_) => println!("DROP TRIGGER with table name succeeded"),
Err(e) => println!("DROP TRIGGER syntax variation not supported: {}", e),
}
}
#[test]
fn test_before_insert_trigger() {
let db = create_test_db();
db.execute("CREATE TABLE products (id INT, name TEXT, price DECIMAL, created_at TEXT)").unwrap();
db.execute("CREATE TABLE audit_log (action TEXT, timestamp TEXT)").unwrap();
let result = db.execute(
"CREATE TRIGGER before_product_insert
BEFORE INSERT ON products
FOR EACH ROW
BEGIN
INSERT INTO audit_log VALUES ('BEFORE INSERT', datetime('now'));
END"
);
if result.is_ok() {
db.execute("INSERT INTO products VALUES (1, 'Widget', 19.99, datetime('now'))").unwrap();
let rows = db.query("SELECT COUNT(*) FROM audit_log WHERE action = 'BEFORE INSERT'", &[]).unwrap();
assert!(rows.len() > 0, "BEFORE INSERT trigger should have fired");
} else {
println!("BEFORE INSERT trigger not implemented: {}", result.unwrap_err());
}
}
#[test]
fn test_before_update_trigger() {
let db = create_test_db();
db.execute("CREATE TABLE products (id INT, name TEXT, price DECIMAL)").unwrap();
db.execute("CREATE TABLE price_history (product_id INT, old_price DECIMAL, new_price DECIMAL)").unwrap();
let result = db.execute(
"CREATE TRIGGER before_product_update
BEFORE UPDATE ON products
FOR EACH ROW
BEGIN
INSERT INTO price_history VALUES (OLD.id, OLD.price, NEW.price);
END"
);
if result.is_ok() {
db.execute("INSERT INTO products VALUES (1, 'Widget', 19.99)").unwrap();
db.execute("UPDATE products SET price = 24.99 WHERE id = 1").unwrap();
let rows = db.query("SELECT COUNT(*) FROM price_history", &[]).unwrap();
assert!(rows.len() > 0, "BEFORE UPDATE trigger should have fired");
} else {
println!("BEFORE UPDATE trigger not implemented: {}", result.unwrap_err());
}
}
#[test]
fn test_before_delete_trigger() {
let db = create_test_db();
db.execute("CREATE TABLE products (id INT, name TEXT, price DECIMAL)").unwrap();
db.execute("CREATE TABLE deleted_products (id INT, name TEXT, deleted_at TEXT)").unwrap();
let result = db.execute(
"CREATE TRIGGER before_product_delete
BEFORE DELETE ON products
FOR EACH ROW
BEGIN
INSERT INTO deleted_products VALUES (OLD.id, OLD.name, datetime('now'));
END"
);
if result.is_ok() {
db.execute("INSERT INTO products VALUES (1, 'Widget', 19.99)").unwrap();
db.execute("DELETE FROM products WHERE id = 1").unwrap();
let rows = db.query("SELECT COUNT(*) FROM deleted_products", &[]).unwrap();
assert!(rows.len() > 0, "BEFORE DELETE trigger should have fired");
} else {
println!("BEFORE DELETE trigger not implemented: {}", result.unwrap_err());
}
}
#[test]
fn test_after_insert_trigger() {
let db = create_test_db();
db.execute("CREATE TABLE orders (id INT, customer_id INT, total DECIMAL)").unwrap();
db.execute("CREATE TABLE order_stats (total_orders INT, total_revenue DECIMAL)").unwrap();
db.execute("INSERT INTO order_stats VALUES (0, 0.0)").unwrap();
let result = db.execute(
"CREATE TRIGGER after_order_insert
AFTER INSERT ON orders
FOR EACH ROW
BEGIN
UPDATE order_stats SET total_orders = total_orders + 1,
total_revenue = total_revenue + NEW.total;
END"
);
if result.is_ok() {
db.execute("INSERT INTO orders VALUES (1, 100, 250.50)").unwrap();
let rows = db.query("SELECT total_orders FROM order_stats", &[]).unwrap();
assert!(rows.len() > 0, "AFTER INSERT trigger should have updated stats");
} else {
println!("AFTER INSERT trigger not implemented: {}", result.unwrap_err());
}
}
#[test]
fn test_after_update_trigger() {
let db = create_test_db();
db.execute("CREATE TABLE accounts (id INT, balance DECIMAL, updated_at TEXT)").unwrap();
db.execute("CREATE TABLE balance_log (account_id INT, old_balance DECIMAL, new_balance DECIMAL, changed_at TEXT)").unwrap();
let result = db.execute(
"CREATE TRIGGER after_account_update
AFTER UPDATE ON accounts
FOR EACH ROW
BEGIN
INSERT INTO balance_log VALUES (NEW.id, OLD.balance, NEW.balance, datetime('now'));
END"
);
if result.is_ok() {
db.execute("INSERT INTO accounts VALUES (1, 1000.00, datetime('now'))").unwrap();
db.execute("UPDATE accounts SET balance = 1500.00 WHERE id = 1").unwrap();
let rows = db.query("SELECT COUNT(*) FROM balance_log", &[]).unwrap();
assert!(rows.len() > 0, "AFTER UPDATE trigger should have logged change");
} else {
println!("AFTER UPDATE trigger not implemented: {}", result.unwrap_err());
}
}
#[test]
fn test_after_delete_trigger() {
let db = create_test_db();
db.execute("CREATE TABLE users (id INT, name TEXT, email TEXT)").unwrap();
db.execute("CREATE TABLE user_count (count INT)").unwrap();
db.execute("INSERT INTO user_count VALUES (0)").unwrap();
let result = db.execute(
"CREATE TRIGGER after_user_delete
AFTER DELETE ON users
FOR EACH ROW
BEGIN
UPDATE user_count SET count = count - 1;
END"
);
if result.is_ok() {
db.execute("INSERT INTO users VALUES (1, 'Alice', 'alice@example.com')").unwrap();
db.execute("DELETE FROM users WHERE id = 1").unwrap();
let rows = db.query("SELECT count FROM user_count", &[]).unwrap();
assert!(rows.len() > 0, "AFTER DELETE trigger should have updated count");
} else {
println!("AFTER DELETE trigger not implemented: {}", result.unwrap_err());
}
}
#[test]
fn test_instead_of_insert_trigger() {
let db = create_test_db();
db.execute("CREATE TABLE base_table (id INT, data TEXT)").unwrap();
let result = db.execute("CREATE VIEW view_table AS SELECT id, data FROM base_table");
if result.is_ok() {
let trigger_result = db.execute(
"CREATE TRIGGER instead_of_view_insert
INSTEAD OF INSERT ON view_table
FOR EACH ROW
BEGIN
INSERT INTO base_table VALUES (NEW.id, NEW.data);
END"
);
if trigger_result.is_ok() {
db.execute("INSERT INTO view_table VALUES (1, 'test')").unwrap();
let rows = db.query("SELECT COUNT(*) FROM base_table", &[]).unwrap();
assert!(rows.len() > 0, "INSTEAD OF INSERT should have inserted into base table");
} else {
println!("INSTEAD OF trigger not implemented: {}", trigger_result.unwrap_err());
}
} else {
println!("CREATE VIEW not supported, skipping INSTEAD OF test");
}
}
#[test]
fn test_instead_of_update_trigger() {
let db = create_test_db();
db.execute("CREATE TABLE base_table (id INT, data TEXT)").unwrap();
let result = db.execute("CREATE VIEW view_table AS SELECT id, data FROM base_table");
if result.is_ok() {
let trigger_result = db.execute(
"CREATE TRIGGER instead_of_view_update
INSTEAD OF UPDATE ON view_table
FOR EACH ROW
BEGIN
UPDATE base_table SET data = NEW.data WHERE id = OLD.id;
END"
);
if trigger_result.is_ok() {
db.execute("INSERT INTO base_table VALUES (1, 'original')").unwrap();
db.execute("UPDATE view_table SET data = 'updated' WHERE id = 1").unwrap();
let rows = db.query("SELECT data FROM base_table WHERE id = 1", &[]).unwrap();
assert!(rows.len() > 0, "INSTEAD OF UPDATE should have updated base table");
} else {
println!("INSTEAD OF UPDATE trigger not implemented: {}", trigger_result.unwrap_err());
}
}
}
#[test]
fn test_instead_of_delete_trigger() {
let db = create_test_db();
db.execute("CREATE TABLE base_table (id INT, data TEXT, deleted INT DEFAULT 0)").unwrap();
let result = db.execute("CREATE VIEW active_items AS SELECT id, data FROM base_table WHERE deleted = 0");
if result.is_ok() {
let trigger_result = db.execute(
"CREATE TRIGGER instead_of_view_delete
INSTEAD OF DELETE ON active_items
FOR EACH ROW
BEGIN
UPDATE base_table SET deleted = 1 WHERE id = OLD.id;
END"
);
if trigger_result.is_ok() {
db.execute("INSERT INTO base_table VALUES (1, 'test', 0)").unwrap();
db.execute("DELETE FROM active_items WHERE id = 1").unwrap();
let rows = db.query("SELECT deleted FROM base_table WHERE id = 1", &[]).unwrap();
assert!(rows.len() > 0, "INSTEAD OF DELETE should have soft-deleted");
} else {
println!("INSTEAD OF DELETE trigger not implemented: {}", trigger_result.unwrap_err());
}
}
}
#[test]
fn test_for_each_row_trigger() {
let db = create_test_db();
db.execute("CREATE TABLE items (id INT, name TEXT)").unwrap();
db.execute("CREATE TABLE row_count (count INT DEFAULT 0)").unwrap();
db.execute("INSERT INTO row_count VALUES (0)").unwrap();
let result = db.execute(
"CREATE TRIGGER count_rows
AFTER INSERT ON items
FOR EACH ROW
BEGIN
UPDATE row_count SET count = count + 1;
END"
);
if result.is_ok() {
db.execute("INSERT INTO items VALUES (1, 'A')").unwrap();
db.execute("INSERT INTO items VALUES (2, 'B')").unwrap();
db.execute("INSERT INTO items VALUES (3, 'C')").unwrap();
let rows = db.query("SELECT count FROM row_count", &[]).unwrap();
assert!(rows.len() > 0, "FOR EACH ROW trigger should fire for each inserted row");
} else {
println!("FOR EACH ROW trigger not implemented: {}", result.unwrap_err());
}
}
#[test]
fn test_for_each_statement_trigger() {
let db = create_test_db();
db.execute("CREATE TABLE items (id INT, name TEXT)").unwrap();
db.execute("CREATE TABLE statement_count (count INT DEFAULT 0)").unwrap();
db.execute("INSERT INTO statement_count VALUES (0)").unwrap();
let result = db.execute(
"CREATE TRIGGER count_statements
AFTER INSERT ON items
FOR EACH STATEMENT
BEGIN
UPDATE statement_count SET count = count + 1;
END"
);
if result.is_ok() {
db.execute("INSERT INTO items VALUES (1, 'A')").unwrap();
db.execute("INSERT INTO items VALUES (2, 'B')").unwrap();
db.execute("INSERT INTO items VALUES (3, 'C')").unwrap();
let rows = db.query("SELECT count FROM statement_count", &[]).unwrap();
assert!(rows.len() > 0, "FOR EACH STATEMENT trigger should fire once per statement");
} else {
println!("FOR EACH STATEMENT trigger not implemented: {}", result.unwrap_err());
}
}
#[test]
fn test_trigger_with_when_clause() {
let db = create_test_db();
db.execute("CREATE TABLE products (id INT, name TEXT, price DECIMAL)").unwrap();
db.execute("CREATE TABLE expensive_products (id INT, name TEXT, price DECIMAL)").unwrap();
let result = db.execute(
"CREATE TRIGGER track_expensive
AFTER INSERT ON products
FOR EACH ROW
WHEN (NEW.price > 100.0)
BEGIN
INSERT INTO expensive_products VALUES (NEW.id, NEW.name, NEW.price);
END"
);
if result.is_ok() {
db.execute("INSERT INTO products VALUES (1, 'Cheap Widget', 10.00)").unwrap();
db.execute("INSERT INTO products VALUES (2, 'Expensive Gadget', 250.00)").unwrap();
db.execute("INSERT INTO products VALUES (3, 'Luxury Item', 500.00)").unwrap();
let rows = db.query("SELECT COUNT(*) FROM expensive_products", &[]).unwrap();
assert!(rows.len() > 0, "WHEN clause should filter trigger execution");
} else {
println!("WHEN clause in triggers not implemented: {}", result.unwrap_err());
}
}
#[test]
fn test_trigger_when_clause_with_old_new() {
let db = create_test_db();
db.execute("CREATE TABLE accounts (id INT, balance DECIMAL)").unwrap();
db.execute("CREATE TABLE balance_increases (account_id INT, amount DECIMAL)").unwrap();
let result = db.execute(
"CREATE TRIGGER track_increases
AFTER UPDATE ON accounts
FOR EACH ROW
WHEN (NEW.balance > OLD.balance)
BEGIN
INSERT INTO balance_increases VALUES (NEW.id, NEW.balance - OLD.balance);
END"
);
if result.is_ok() {
db.execute("INSERT INTO accounts VALUES (1, 1000.00)").unwrap();
db.execute("UPDATE accounts SET balance = 1500.00 WHERE id = 1").unwrap();
db.execute("UPDATE accounts SET balance = 900.00 WHERE id = 1").unwrap();
let rows = db.query("SELECT COUNT(*) FROM balance_increases", &[]).unwrap();
assert!(rows.len() > 0, "WHEN clause should handle OLD and NEW references");
} else {
println!("WHEN clause with OLD/NEW not implemented: {}", result.unwrap_err());
}
}
#[test]
fn test_new_context_in_insert_trigger() {
let db = create_test_db();
db.execute("CREATE TABLE orders (id INT, total DECIMAL)").unwrap();
db.execute("CREATE TABLE order_log (order_id INT, amount DECIMAL)").unwrap();
let result = db.execute(
"CREATE TRIGGER log_new_order
AFTER INSERT ON orders
FOR EACH ROW
BEGIN
INSERT INTO order_log VALUES (NEW.id, NEW.total);
END"
);
if result.is_ok() {
db.execute("INSERT INTO orders VALUES (1, 99.99)").unwrap();
let rows = db.query("SELECT amount FROM order_log WHERE order_id = 1", &[]).unwrap();
assert!(rows.len() > 0, "NEW context should provide inserted values");
} else {
println!("NEW context variable not implemented: {}", result.unwrap_err());
}
}
#[test]
fn test_old_context_in_delete_trigger() {
let db = create_test_db();
db.execute("CREATE TABLE products (id INT, name TEXT)").unwrap();
db.execute("CREATE TABLE deletion_log (deleted_id INT, deleted_name TEXT)").unwrap();
let result = db.execute(
"CREATE TRIGGER log_deletions
BEFORE DELETE ON products
FOR EACH ROW
BEGIN
INSERT INTO deletion_log VALUES (OLD.id, OLD.name);
END"
);
if result.is_ok() {
db.execute("INSERT INTO products VALUES (1, 'Widget')").unwrap();
db.execute("DELETE FROM products WHERE id = 1").unwrap();
let rows = db.query("SELECT deleted_name FROM deletion_log WHERE deleted_id = 1", &[]).unwrap();
assert!(rows.len() > 0, "OLD context should provide deleted values");
} else {
println!("OLD context variable not implemented: {}", result.unwrap_err());
}
}
#[test]
fn test_old_new_context_in_update_trigger() {
let db = create_test_db();
db.execute("CREATE TABLE inventory (id INT, quantity INT)").unwrap();
db.execute("CREATE TABLE inventory_changes (item_id INT, old_qty INT, new_qty INT)").unwrap();
let result = db.execute(
"CREATE TRIGGER track_inventory_changes
AFTER UPDATE ON inventory
FOR EACH ROW
BEGIN
INSERT INTO inventory_changes VALUES (NEW.id, OLD.quantity, NEW.quantity);
END"
);
if result.is_ok() {
db.execute("INSERT INTO inventory VALUES (1, 100)").unwrap();
db.execute("UPDATE inventory SET quantity = 150 WHERE id = 1").unwrap();
let rows = db.query("SELECT old_qty, new_qty FROM inventory_changes WHERE item_id = 1", &[]).unwrap();
assert!(rows.len() > 0, "Both OLD and NEW contexts should be available in UPDATE triggers");
} else {
println!("OLD/NEW context in UPDATE not implemented: {}", result.unwrap_err());
}
}
#[test]
fn test_cascading_triggers_basic() {
let db = create_test_db();
db.execute("CREATE TABLE table1 (id INT, value TEXT)").unwrap();
db.execute("CREATE TABLE table2 (id INT, value TEXT)").unwrap();
db.execute("CREATE TABLE table3 (id INT, value TEXT)").unwrap();
let result1 = db.execute(
"CREATE TRIGGER cascade1
AFTER INSERT ON table1
FOR EACH ROW
BEGIN
INSERT INTO table2 VALUES (NEW.id, NEW.value);
END"
);
let result2 = db.execute(
"CREATE TRIGGER cascade2
AFTER INSERT ON table2
FOR EACH ROW
BEGIN
INSERT INTO table3 VALUES (NEW.id, NEW.value);
END"
);
if result1.is_ok() && result2.is_ok() {
db.execute("INSERT INTO table1 VALUES (1, 'cascade test')").unwrap();
let rows2 = db.query("SELECT COUNT(*) FROM table2", &[]).unwrap();
let rows3 = db.query("SELECT COUNT(*) FROM table3", &[]).unwrap();
assert!(rows2.len() > 0 && rows3.len() > 0, "Cascading triggers should work");
} else {
println!("Cascading triggers not implemented");
}
}
#[test]
fn test_cascading_triggers_depth() {
let db = create_test_db();
for i in 1..=20 {
db.execute(&format!("CREATE TABLE level{} (id INT)", i)).unwrap();
}
for i in 1..=19 {
let trigger_sql = format!(
"CREATE TRIGGER cascade_level{}
AFTER INSERT ON level{}
FOR EACH ROW
BEGIN
INSERT INTO level{} VALUES (NEW.id);
END",
i, i, i + 1
);
let _ = db.execute(&trigger_sql);
}
let result = db.execute("INSERT INTO level1 VALUES (1)");
if result.is_ok() {
println!("Deep cascading triggers may be supported");
} else {
println!("Deep cascading not supported or limited: {}", result.unwrap_err());
}
}
#[test]
fn test_trigger_depth_limit_enforcement() {
let db = create_test_db();
for i in 1..=20 {
db.execute(&format!("CREATE TABLE depth{} (id INT, level INT)", i)).unwrap();
}
for i in 1..=19 {
let trigger_sql = format!(
"CREATE TRIGGER depth_trigger_{}
AFTER INSERT ON depth{}
FOR EACH ROW
BEGIN
INSERT INTO depth{} VALUES (NEW.id, {});
END",
i, i, i + 1, i + 1
);
let _ = db.execute(&trigger_sql);
}
let result = db.execute("INSERT INTO depth1 VALUES (1, 1)");
match result {
Ok(_) => {
let mut max_level = 1;
for i in 1..=20 {
let count_result = db.query(&format!("SELECT COUNT(*) FROM depth{}", i), &[]);
if let Ok(rows) = count_result {
if rows.len() > 0 {
max_level = i;
} else {
break;
}
} else {
break;
}
}
println!("Cascade reached level {}, depth limit may be enforced", max_level);
}
Err(e) => {
println!("Deep cascade prevented or depth limit enforced: {}", e);
}
}
}
#[test]
fn test_trigger_with_invalid_sql() {
let db = create_test_db();
db.execute("CREATE TABLE test_table (id INT)").unwrap();
let result = db.execute(
"CREATE TRIGGER invalid_trigger
AFTER INSERT ON test_table
FOR EACH ROW
BEGIN
SELECT * FROM nonexistent_table;
END"
);
match result {
Ok(_) => println!("Trigger created but may fail at runtime"),
Err(e) => println!("Invalid trigger rejected: {}", e),
}
}
#[test]
fn test_trigger_runtime_error_rollback() {
let db = create_test_db();
db.execute("CREATE TABLE test_table (id INT NOT NULL)").unwrap();
db.execute("CREATE TABLE log_table (id INT)").unwrap();
let result = db.execute(
"CREATE TRIGGER error_trigger
BEFORE INSERT ON test_table
FOR EACH ROW
BEGIN
INSERT INTO log_table VALUES (NULL);
END"
);
if result.is_ok() {
let insert_result = db.execute("INSERT INTO test_table VALUES (1)");
match insert_result {
Ok(_) => {
let rows = db.query("SELECT COUNT(*) FROM test_table", &[]).unwrap();
println!("Error handling test completed, rows inserted: {:?}", rows.len());
}
Err(e) => println!("Trigger error properly prevented insert: {}", e),
}
} else {
println!("Trigger with potential error not created: {}", result.unwrap_err());
}
}
#[test]
fn test_trigger_constraint_violation() {
let db = create_test_db();
db.execute("CREATE TABLE parent (id INT PRIMARY KEY)").unwrap();
db.execute("CREATE TABLE child (id INT, parent_id INT)").unwrap();
let result = db.execute(
"CREATE TRIGGER enforce_parent
BEFORE INSERT ON child
FOR EACH ROW
WHEN (NEW.parent_id IS NOT NULL)
BEGIN
SELECT CASE
WHEN NOT EXISTS (SELECT 1 FROM parent WHERE id = NEW.parent_id)
THEN RAISE(ABORT, 'Parent does not exist')
END;
END"
);
if result.is_ok() {
db.execute("INSERT INTO parent VALUES (1)").unwrap();
let valid_result = db.execute("INSERT INTO child VALUES (1, 1)");
let invalid_result = db.execute("INSERT INTO child VALUES (2, 999)");
match invalid_result {
Ok(_) => println!("Constraint not enforced by trigger"),
Err(e) => println!("Trigger correctly enforced constraint: {}", e),
}
} else {
println!("Constraint enforcement trigger not implemented: {}", result.unwrap_err());
}
}
#[test]
fn test_multiple_triggers_same_event() {
let db = create_test_db();
db.execute("CREATE TABLE test_table (id INT)").unwrap();
db.execute("CREATE TABLE execution_order (trigger_name TEXT, execution_time TEXT)").unwrap();
let result1 = db.execute(
"CREATE TRIGGER trigger_a
AFTER INSERT ON test_table
FOR EACH ROW
BEGIN
INSERT INTO execution_order VALUES ('trigger_a', datetime('now'));
END"
);
let result2 = db.execute(
"CREATE TRIGGER trigger_b
AFTER INSERT ON test_table
FOR EACH ROW
BEGIN
INSERT INTO execution_order VALUES ('trigger_b', datetime('now'));
END"
);
let result3 = db.execute(
"CREATE TRIGGER trigger_c
AFTER INSERT ON test_table
FOR EACH ROW
BEGIN
INSERT INTO execution_order VALUES ('trigger_c', datetime('now'));
END"
);
if result1.is_ok() && result2.is_ok() && result3.is_ok() {
db.execute("INSERT INTO test_table VALUES (1)").unwrap();
let rows = db.query("SELECT trigger_name FROM execution_order ORDER BY execution_time", &[]).unwrap();
println!("Trigger execution order: {:?}", rows.len());
assert!(rows.len() > 0, "All triggers should have executed");
} else {
println!("Multiple triggers on same event not fully supported");
}
}
#[test]
fn test_before_after_trigger_order() {
let db = create_test_db();
db.execute("CREATE TABLE test_table (id INT, value TEXT)").unwrap();
db.execute("CREATE TABLE trigger_log (phase TEXT, value TEXT)").unwrap();
let before_result = db.execute(
"CREATE TRIGGER before_trigger
BEFORE INSERT ON test_table
FOR EACH ROW
BEGIN
INSERT INTO trigger_log VALUES ('BEFORE', NEW.value);
END"
);
let after_result = db.execute(
"CREATE TRIGGER after_trigger
AFTER INSERT ON test_table
FOR EACH ROW
BEGIN
INSERT INTO trigger_log VALUES ('AFTER', NEW.value);
END"
);
if before_result.is_ok() && after_result.is_ok() {
db.execute("INSERT INTO test_table VALUES (1, 'test')").unwrap();
let rows = db.query("SELECT phase FROM trigger_log ORDER BY rowid", &[]).unwrap();
println!("Trigger phases executed: {:?}", rows.len());
assert!(rows.len() > 0, "Both BEFORE and AFTER triggers should execute");
} else {
println!("BEFORE/AFTER trigger ordering not fully supported");
}
}
#[test]
fn test_trigger_with_multiple_events() {
let db = create_test_db();
db.execute("CREATE TABLE test_table (id INT, value TEXT)").unwrap();
db.execute("CREATE TABLE event_log (event_type TEXT)").unwrap();
let result = db.execute(
"CREATE TRIGGER multi_event_trigger
AFTER INSERT OR UPDATE OR DELETE ON test_table
FOR EACH ROW
BEGIN
INSERT INTO event_log VALUES ('CHANGE');
END"
);
if result.is_ok() {
db.execute("INSERT INTO test_table VALUES (1, 'test')").unwrap();
db.execute("UPDATE test_table SET value = 'updated' WHERE id = 1").unwrap();
db.execute("DELETE FROM test_table WHERE id = 1").unwrap();
let rows = db.query("SELECT COUNT(*) FROM event_log", &[]).unwrap();
assert!(rows.len() > 0, "Multi-event trigger should fire on all events");
} else {
println!("Multi-event triggers not supported: {}", result.unwrap_err());
}
}
#[test]
fn test_trigger_referencing_multiple_tables() {
let db = create_test_db();
db.execute("CREATE TABLE orders (id INT, customer_id INT, amount DECIMAL)").unwrap();
db.execute("CREATE TABLE customers (id INT, total_spent DECIMAL)").unwrap();
db.execute("INSERT INTO customers VALUES (1, 0.0)").unwrap();
let result = db.execute(
"CREATE TRIGGER update_customer_total
AFTER INSERT ON orders
FOR EACH ROW
BEGIN
UPDATE customers
SET total_spent = total_spent + NEW.amount
WHERE id = NEW.customer_id;
END"
);
if result.is_ok() {
db.execute("INSERT INTO orders VALUES (1, 1, 100.00)").unwrap();
db.execute("INSERT INTO orders VALUES (2, 1, 50.00)").unwrap();
let rows = db.query("SELECT total_spent FROM customers WHERE id = 1", &[]).unwrap();
assert!(rows.len() > 0, "Trigger should update related table");
} else {
println!("Triggers referencing multiple tables not implemented: {}", result.unwrap_err());
}
}
#[test]
fn test_trigger_with_subquery() {
let db = create_test_db();
db.execute("CREATE TABLE products (id INT, category_id INT, price DECIMAL)").unwrap();
db.execute("CREATE TABLE categories (id INT, max_price DECIMAL)").unwrap();
db.execute("INSERT INTO categories VALUES (1, 100.0)").unwrap();
let result = db.execute(
"CREATE TRIGGER validate_price
BEFORE INSERT ON products
FOR EACH ROW
BEGIN
SELECT CASE
WHEN NEW.price > (SELECT max_price FROM categories WHERE id = NEW.category_id)
THEN RAISE(ABORT, 'Price exceeds category maximum')
END;
END"
);
if result.is_ok() {
let valid = db.execute("INSERT INTO products VALUES (1, 1, 50.0)");
let invalid = db.execute("INSERT INTO products VALUES (2, 1, 150.0)");
match invalid {
Ok(_) => println!("Price validation not enforced"),
Err(e) => println!("Subquery in trigger worked: {}", e),
}
} else {
println!("Triggers with subqueries not implemented: {}", result.unwrap_err());
}
}
#[test]
fn test_trigger_with_transaction() {
let db = create_test_db();
db.execute("CREATE TABLE test_table (id INT)").unwrap();
db.execute("CREATE TABLE log_table (id INT)").unwrap();
let result = db.execute(
"CREATE TRIGGER log_insert
AFTER INSERT ON test_table
FOR EACH ROW
BEGIN
INSERT INTO log_table VALUES (NEW.id);
END"
);
if result.is_ok() {
let _ = db.execute("BEGIN TRANSACTION");
db.execute("INSERT INTO test_table VALUES (1)").unwrap();
let _ = db.execute("ROLLBACK");
let test_rows = db.query("SELECT COUNT(*) FROM test_table", &[]).unwrap();
let log_rows = db.query("SELECT COUNT(*) FROM log_table", &[]).unwrap();
println!("Transaction rollback test: test_table rows: {:?}, log_table rows: {:?}",
test_rows.len(), log_rows.len());
} else {
println!("Triggers in transactions not implemented: {}", result.unwrap_err());
}
}
#[test]
fn test_trigger_update_of_specific_columns() {
let db = create_test_db();
db.execute("CREATE TABLE products (id INT, name TEXT, price DECIMAL, description TEXT)").unwrap();
db.execute("CREATE TABLE price_changes (product_id INT, old_price DECIMAL, new_price DECIMAL)").unwrap();
let result = db.execute(
"CREATE TRIGGER track_price_updates
AFTER UPDATE OF price ON products
FOR EACH ROW
BEGIN
INSERT INTO price_changes VALUES (NEW.id, OLD.price, NEW.price);
END"
);
if result.is_ok() {
db.execute("INSERT INTO products VALUES (1, 'Widget', 10.0, 'A widget')").unwrap();
db.execute("UPDATE products SET name = 'Super Widget' WHERE id = 1").unwrap();
db.execute("UPDATE products SET price = 15.0 WHERE id = 1").unwrap();
let rows = db.query("SELECT COUNT(*) FROM price_changes", &[]).unwrap();
println!("Column-specific trigger test: {:?} price changes logged", rows.len());
} else {
println!("UPDATE OF column triggers not implemented: {}", result.unwrap_err());
}
}
}