use spg_engine::{Engine, QueryResult};
fn setup(e: &mut Engine) {
e.execute("CREATE TABLE base(id int, v int)").unwrap();
e.execute("CREATE VIEW jv AS SELECT id, v, v*2 AS dbl FROM base")
.unwrap();
e.execute(
"CREATE FUNCTION jv_ins() RETURNS trigger AS $x$ BEGIN \
INSERT INTO base(id,v) VALUES(NEW.id, NEW.v); RETURN NEW; END; $x$ LANGUAGE plpgsql",
)
.unwrap();
e.execute(
"CREATE TRIGGER jv_ins_t INSTEAD OF INSERT ON jv FOR EACH ROW EXECUTE FUNCTION jv_ins()",
)
.unwrap();
}
fn base_pairs(e: &mut Engine) -> Vec<(i32, i32)> {
match e.execute("SELECT id, v FROM base ORDER BY id").unwrap() {
QueryResult::Rows { rows, .. } => rows
.iter()
.map(|r| match (&r.values[0], &r.values[1]) {
(spg_storage::Value::Int(a), spg_storage::Value::Int(b)) => (*a, *b),
other => panic!("{other:?}"),
})
.collect(),
other => panic!("{other:?}"),
}
}
#[test]
fn instead_of_insert_fires_and_writes_base() {
let mut e = Engine::new();
setup(&mut e);
match e
.execute("INSERT INTO jv(id,v) VALUES(1,10),(2,20)")
.unwrap()
{
QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 2),
other => panic!("{other:?}"),
}
assert_eq!(base_pairs(&mut e), vec![(1, 10), (2, 20)]);
}
#[test]
fn instead_of_insert_column_order() {
let mut e = Engine::new();
setup(&mut e);
e.execute("INSERT INTO jv(v,id) VALUES(30,3)").unwrap();
assert_eq!(base_pairs(&mut e), vec![(3, 30)]);
}
#[test]
fn instead_of_insert_positional() {
let mut e = Engine::new();
setup(&mut e);
e.execute("INSERT INTO jv VALUES(4,40,999)").unwrap();
assert_eq!(base_pairs(&mut e), vec![(4, 40)]);
}
#[test]
fn instead_of_on_table_rejected() {
let mut e = Engine::new();
e.execute("CREATE TABLE t(id int)").unwrap();
e.execute(
"CREATE FUNCTION f() RETURNS trigger AS $x$ BEGIN RETURN NEW; END; $x$ LANGUAGE plpgsql",
)
.unwrap();
let m = match e
.execute("CREATE TRIGGER x INSTEAD OF INSERT ON t FOR EACH ROW EXECUTE FUNCTION f()")
{
Err(x) => format!("{x}"),
Ok(_) => panic!("expected error"),
};
assert!(m.contains("\"t\" is a table"), "{m}");
assert!(m.contains("Tables cannot have INSTEAD OF triggers"), "{m}");
}
#[test]
fn statement_level_instead_of_rejected() {
let mut e = Engine::new();
e.execute("CREATE TABLE base(id int, v int)").unwrap();
e.execute("CREATE VIEW jv AS SELECT id, v FROM base")
.unwrap();
e.execute(
"CREATE FUNCTION f() RETURNS trigger AS $x$ BEGIN RETURN NEW; END; $x$ LANGUAGE plpgsql",
)
.unwrap();
let m = match e
.execute("CREATE TRIGGER x INSTEAD OF INSERT ON jv FOR EACH STATEMENT EXECUTE FUNCTION f()")
{
Err(x) => format!("{x}"),
Ok(_) => panic!("expected error"),
};
assert!(
m.contains("INSTEAD OF triggers must be FOR EACH ROW"),
"{m}"
);
}
#[test]
fn before_on_view_rejected() {
let mut e = Engine::new();
e.execute("CREATE TABLE base(id int, v int)").unwrap();
e.execute("CREATE VIEW jv AS SELECT id, v FROM base")
.unwrap();
e.execute(
"CREATE FUNCTION f() RETURNS trigger AS $x$ BEGIN RETURN NEW; END; $x$ LANGUAGE plpgsql",
)
.unwrap();
let m =
match e.execute("CREATE TRIGGER y BEFORE INSERT ON jv FOR EACH ROW EXECUTE FUNCTION f()") {
Err(x) => format!("{x}"),
Ok(_) => panic!("expected error"),
};
assert!(m.contains("\"jv\" is a view"), "{m}");
}