#[cfg(any(any(), feature = "python"))]
use oxibase::core::Value;
use oxibase::executor::Executor;
use oxibase::storage::mvcc::engine::MVCCEngine;
use std::sync::Arc;
fn setup_executor() -> Executor {
let engine = MVCCEngine::in_memory();
engine.open_engine().unwrap();
Executor::new(Arc::new(engine))
}
#[test]
fn test_tutorial_triggers_rhai() {
let executor = setup_executor();
let res = executor.execute(
"CREATE TABLE accounts (
id INTEGER PRIMARY KEY,
owner_name TEXT,
balance FLOAT
);",
);
assert!(res.is_ok());
let res = executor.execute(
r#"
CREATE TRIGGER ensure_positive_balance
BEFORE INSERT ON accounts
FOR EACH ROW
LANGUAGE rhai
AS '
if oxibase.ctx["new"].balance < 0.0 {
throw "Account balance cannot be negative!";
}
';
"#,
);
if let Err(e) = &res {
println!("Error creating Rhai trigger: {:?}", e);
}
assert!(res.is_ok());
let res = executor
.execute("INSERT INTO accounts (id, owner_name, balance) VALUES (1, 'Alice', 100.0);");
assert!(res.is_ok());
let res = executor
.execute("INSERT INTO accounts (id, owner_name, balance) VALUES (2, 'Bob', -50.0);");
assert!(res.is_err());
let err_msg = res.err().unwrap().to_string();
assert!(err_msg.contains("Account balance cannot be negative!"));
}
#[test]
#[cfg(any())]
fn test_tutorial_triggers_js() {
let executor = setup_executor();
let res = executor.execute(
"CREATE TABLE accounts (
id INTEGER PRIMARY KEY,
owner_name TEXT,
balance FLOAT
);",
);
assert!(res.is_ok());
let _ = executor
.execute("INSERT INTO accounts (id, owner_name, balance) VALUES (1, 'Alice', 100.0);");
let res = executor.execute(
r#"
CREATE TRIGGER normalize_owner_name
BEFORE UPDATE ON accounts
FOR EACH ROW
LANGUAGE js
AS '
// Force the owner name to be uppercase before saving
oxibase.ctx.new.owner_name = oxibase.ctx.new.owner_name.toUpperCase();
';
"#,
);
assert!(res.is_ok());
let res = executor.execute("UPDATE accounts SET owner_name = 'alice lowercase' WHERE id = 1;");
assert!(res.is_ok());
let mut res = executor
.execute("SELECT owner_name FROM accounts WHERE id = 1;")
.unwrap();
assert!(res.next());
assert_eq!(res.row().get(0), Some(&Value::text("ALICE LOWERCASE")));
}
#[test]
#[cfg(feature = "python")]
fn test_tutorial_triggers_python() {
let executor = setup_executor();
let res = executor.execute(
"CREATE TABLE accounts (
id INTEGER PRIMARY KEY,
owner_name TEXT,
balance FLOAT
);",
);
assert!(res.is_ok());
let _ = executor
.execute("INSERT INTO accounts (id, owner_name, balance) VALUES (1, 'Alice', 100.0);");
let res = executor.execute(
"CREATE TABLE audit_log (
account_id INTEGER,
old_balance FLOAT,
new_balance FLOAT,
changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);",
);
assert!(res.is_ok());
let res = executor.execute(
r#"
CREATE TRIGGER log_balance_changes
AFTER UPDATE ON accounts
FOR EACH ROW
LANGUAGE python
AS '
import oxibase
# Only log if the balance actually changed
if oxibase.ctx.old["balance"] != oxibase.ctx.new["balance"]:
# Use oxibase.execute to run DML side-effects
stmt = "INSERT INTO audit_log (account_id, old_balance, new_balance) VALUES (" + str(oxibase.ctx.old["id"]) + ", " + str(oxibase.ctx.old["balance"]) + ", " + str(oxibase.ctx.new["balance"]) + ")"
oxibase.execute(stmt)
';
"#
);
if let Err(e) = &res {
println!("Error creating Python trigger: {:?}", e);
}
assert!(res.is_ok());
let res = executor.execute("UPDATE accounts SET balance = 150.0 WHERE id = 1;");
if let Err(e) = &res {
println!("Error executing UPDATE for Python trigger: {:?}", e);
}
assert!(res.is_ok());
let mut res = executor
.execute("SELECT account_id, old_balance, new_balance FROM audit_log;")
.unwrap();
assert!(res.next());
assert_eq!(res.row().get(0), Some(&Value::Integer(1)));
assert_eq!(res.row().get(1), Some(&Value::Float(100.0)));
assert_eq!(res.row().get(2), Some(&Value::Float(150.0)));
let res = executor.execute("DROP TRIGGER IF EXISTS log_balance_changes ON accounts;");
assert!(res.is_ok());
}