#![cfg(feature = "std")]
use std::process::Command;
fn run(bin: &str, sql: &str) -> String {
let out = Command::new(bin)
.arg(":memory:")
.arg(sql)
.output()
.expect("spawn");
let stdout = String::from_utf8_lossy(&out.stdout);
for line in stdout.lines() {
if !line.is_empty() {
return line.to_string();
}
}
let stderr = String::from_utf8_lossy(&out.stderr);
for line in stderr.lines() {
if line.starts_with('^') {
continue;
}
let s = line
.strip_prefix("Error: in prepare, ")
.or_else(|| line.strip_prefix("Error: stepping, "))
.or_else(|| line.strip_prefix("Error: SQL error: "))
.or_else(|| line.strip_prefix("Error: "))
.unwrap_or(line);
let s = s.strip_prefix("error: ").unwrap_or(s);
let s = s.rsplit_once(" (").map_or(s, |(head, tail)| {
if tail
.trim_end_matches(')')
.chars()
.all(|c| c.is_ascii_digit())
{
head
} else {
s
}
});
return s.to_string();
}
String::new()
}
fn sqlite3_available() -> bool {
Command::new("sqlite3")
.arg("--version")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
fn same(sql: &str) {
let g = run(env!("CARGO_BIN_EXE_graphitesql"), sql);
let s = run("sqlite3", sql);
assert_eq!(g, s, "mismatch for SQL: {sql}");
}
#[test]
fn trigger_body_disallowed_leading_keyword() {
if !sqlite3_available() {
return;
}
let t = "CREATE TABLE t(a);";
for step in [
"PRAGMA foo",
"VACUUM",
"CREATE TABLE x(b)",
"DROP TABLE t",
"ALTER TABLE t ADD c",
"EXPLAIN SELECT 1",
"SAVEPOINT s",
"ANALYZE",
"REINDEX",
"BEGIN",
"COMMIT",
"RELEASE s",
"ATTACH 'x' AS y",
"DETACH y",
"ROLLBACK",
] {
same(&format!(
"{t} CREATE TRIGGER tr AFTER INSERT ON t BEGIN {step}; END;"
));
}
}
#[test]
fn trigger_body_with_prefixed_dml() {
if !sqlite3_available() {
return;
}
let t = "CREATE TABLE t(a);";
same(&format!(
"{t} CREATE TRIGGER tr AFTER INSERT ON t BEGIN WITH x AS (SELECT 1) INSERT INTO t SELECT * FROM x; END;"
));
same(&format!(
"{t} CREATE TRIGGER tr AFTER INSERT ON t BEGIN WITH x AS (SELECT 1) UPDATE t SET a=1; END;"
));
same(&format!(
"{t} CREATE TRIGGER tr AFTER INSERT ON t BEGIN WITH x AS (SELECT 1) DELETE FROM t; END;"
));
same(&format!(
"{t} CREATE TRIGGER tr AFTER INSERT ON t BEGIN WITH x AS (SELECT 1) SELECT * FROM x; END;"
));
same(&format!(
"{t} CREATE TRIGGER tr AFTER INSERT ON t BEGIN WITH x AS (SELECT 1) VALUES(1); END;"
));
}
#[test]
fn trigger_body_returning() {
if !sqlite3_available() {
return;
}
let t = "CREATE TABLE t(a);";
same(&format!(
"{t} CREATE TRIGGER tr AFTER INSERT ON t BEGIN INSERT INTO t VALUES(1) RETURNING a; END;"
));
same(&format!(
"{t} CREATE TRIGGER tr AFTER INSERT ON t BEGIN REPLACE INTO t VALUES(1) RETURNING a; END;"
));
same(&format!(
"{t} CREATE TRIGGER tr AFTER INSERT ON t BEGIN UPDATE t SET a=1 RETURNING a; END;"
));
same(&format!(
"{t} CREATE TRIGGER tr AFTER INSERT ON t BEGIN DELETE FROM t RETURNING a; END;"
));
same(&format!("{t} INSERT INTO t VALUES(1) RETURNING a;"));
same("CREATE TABLE t(a,b); INSERT INTO t VALUES(1,2); UPDATE t SET b=9 RETURNING a, b;");
same("CREATE TABLE t(a); INSERT INTO t VALUES(1),(2); DELETE FROM t RETURNING a;");
}
#[test]
fn trigger_body_qualified_dml_target() {
if !sqlite3_available() {
return;
}
let t = "CREATE TABLE t(a);";
for step in [
"UPDATE main.t SET a=1",
"DELETE FROM main.t",
"INSERT INTO main.t VALUES(1)",
] {
same(&format!(
"{t} CREATE TRIGGER tr AFTER INSERT ON t BEGIN {step}; END;"
));
}
same(&format!(
"{t} CREATE TRIGGER tr AFTER INSERT ON t BEGIN UPDATE t SET a=(SELECT a FROM main.t LIMIT 1); END; INSERT INTO t(a) VALUES(7); SELECT a FROM t;"
));
}
#[test]
fn trigger_body_target_resolution_outranks_body() {
if !sqlite3_available() {
return;
}
let t = "CREATE TABLE t(a);";
same(&format!(
"{t} CREATE TRIGGER tr AFTER INSERT ON nope BEGIN PRAGMA foo; END;"
));
same(&format!(
"{t} CREATE TRIGGER tr AFTER INSERT ON nope BEGIN INSERT INTO t VALUES(1) RETURNING a; END;"
));
same(&format!(
"{t} CREATE TRIGGER tr AFTER INSERT ON sqlite_master BEGIN PRAGMA foo; END;"
));
same(&format!(
"{t} CREATE VIEW v AS SELECT 1 a; CREATE TRIGGER tr BEFORE INSERT ON v BEGIN UPDATE t SET a=1 RETURNING a; END;"
));
same(&format!(
"{t} CREATE TRIGGER tr AFTER INSERT ON t BEGIN SELECT 1; END; CREATE TRIGGER tr AFTER INSERT ON t BEGIN PRAGMA foo; END;"
));
}
#[test]
fn trigger_body_first_violation_wins_in_source_order() {
if !sqlite3_available() {
return;
}
let t = "CREATE TABLE t(a);";
same(&format!(
"{t} CREATE TRIGGER tr AFTER INSERT ON t BEGIN INSERT INTO t VALUES(1) RETURNING a; PRAGMA foo; END;"
));
same(&format!(
"{t} CREATE TRIGGER tr AFTER INSERT ON t BEGIN PRAGMA foo; INSERT INTO t VALUES(1) RETURNING a; END;"
));
same(&format!(
"{t} CREATE TRIGGER tr AFTER INSERT ON t BEGIN UPDATE t SET a=1 ORDER BY a; INSERT INTO t VALUES(1) RETURNING a; END;"
));
same(&format!(
"{t} CREATE TRIGGER tr AFTER INSERT ON t BEGIN UPDATE main.t SET a=1; PRAGMA foo; END;"
));
same(&format!(
"{t} CREATE TRIGGER tr AFTER INSERT ON t BEGIN PRAGMA foo; UPDATE main.t SET a=1; END;"
));
}
#[test]
fn trigger_body_valid_steps_still_fire() {
if !sqlite3_available() {
return;
}
let t = "CREATE TABLE t(a); CREATE TABLE log(m);";
same(&format!(
"{t} CREATE TRIGGER tr AFTER INSERT ON t BEGIN INSERT INTO log VALUES('hit'); END; INSERT INTO t VALUES(1); SELECT m FROM log;"
));
same(&format!(
"{t} CREATE TRIGGER tr AFTER INSERT ON t BEGIN (SELECT 1); REPLACE INTO t VALUES(2); END; INSERT INTO t VALUES(1); SELECT count(*) FROM t;"
));
}