#![cfg(feature = "std")]
use std::io::Write;
use std::process::{Command, Stdio};
fn sqlite3_available() -> bool {
Command::new("sqlite3").arg("--version").output().is_ok()
}
fn stdin_then_query(bin: &str, script: &str, probe: &str) -> String {
let dir = std::env::temp_dir();
let db = dir.join(format!(
"graphite_mlt_{}_{}.db",
std::process::id(),
script.len() * 131 + probe.len()
));
let _ = std::fs::remove_file(&db);
let mut child = Command::new(bin)
.arg(&db)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("spawn");
child
.stdin
.take()
.unwrap()
.write_all(script.as_bytes())
.unwrap();
let out = child.wait_with_output().unwrap();
assert!(
out.status.success(),
"load failed: {}",
String::from_utf8_lossy(&out.stderr)
);
let q = Command::new(bin).arg(&db).arg(probe).output().unwrap();
let _ = std::fs::remove_file(&db);
String::from_utf8_lossy(&q.stdout).trim_end().to_owned()
}
const TRIGGER_SCHEMA: &str = "\
CREATE TABLE a(k TEXT PRIMARY KEY, v INT);
CREATE TABLE b(k TEXT, v INT);
CREATE TRIGGER t AFTER INSERT ON a BEGIN
INSERT INTO b(k,v) VALUES(NEW.k, NEW.v);
UPDATE b SET v = v + 1 WHERE k = NEW.k;
END;
INSERT INTO a VALUES('x', 10);
";
#[test]
fn multiline_trigger_over_stdin_is_created_and_fires() {
let g = env!("CARGO_BIN_EXE_graphitesql");
assert_eq!(
stdin_then_query(
g,
TRIGGER_SCHEMA,
"SELECT count(*) FROM sqlite_master WHERE type='trigger'"
),
"1",
"multi-line CREATE TRIGGER over stdin was not created"
);
assert_eq!(
stdin_then_query(g, TRIGGER_SCHEMA, "SELECT k || '=' || v FROM b"),
"x=11"
);
if sqlite3_available() {
assert_eq!(
stdin_then_query("sqlite3", TRIGGER_SCHEMA, "SELECT k || '=' || v FROM b"),
"x=11",
"oracle sanity"
);
}
}
const CASE_SCHEMA: &str = "\
CREATE TABLE t(id INTEGER PRIMARY KEY, s INT);
CREATE TABLE audit(id INT, note TEXT);
CREATE TRIGGER au AFTER UPDATE ON t BEGIN
INSERT INTO audit VALUES(NEW.id, CASE WHEN NEW.s > 10 THEN 'hi' ELSE 'lo' END);
SELECT 1;
END;
INSERT INTO t VALUES(1, 5);
UPDATE t SET s = 20 WHERE id = 1;
UPDATE t SET s = 3 WHERE id = 1;
";
#[test]
fn trigger_with_case_body_over_stdin_matches_sqlite() {
let g = env!("CARGO_BIN_EXE_graphitesql");
let probe = "SELECT id || ':' || note FROM audit ORDER BY rowid";
let got = stdin_then_query(g, CASE_SCHEMA, probe);
assert_eq!(got, "1:hi\n1:lo");
if sqlite3_available() {
assert_eq!(stdin_then_query("sqlite3", CASE_SCHEMA, probe), got);
}
}
#[test]
fn plain_multi_statement_batches_over_stdin_still_split() {
let g = env!("CARGO_BIN_EXE_graphitesql");
let script = "CREATE TABLE x(a);\nINSERT INTO x VALUES(1);\nINSERT INTO x VALUES(2); INSERT INTO x VALUES(3);\n";
assert_eq!(
stdin_then_query(g, script, "SELECT group_concat(a) FROM x"),
"1,2,3"
);
}
#[test]
fn comments_do_not_break_statement_splitting() {
let g = env!("CARGO_BIN_EXE_graphitesql");
let cases: &[(&str, &str, &str)] = &[
(
"comment before BEGIN",
"CREATE TABLE t(a);\n-- a comment\nBEGIN;\nINSERT INTO t VALUES(1);\nCOMMIT;\n",
"1",
),
(
"apostrophe in line comment",
"CREATE TABLE t(a);\n-- don't break on this\nINSERT INTO t VALUES(2);\n",
"2",
),
(
"semicolon in block comment",
"CREATE TABLE t(a);\n/* has ; inside */\nINSERT INTO t VALUES(3);\n",
"3",
),
(
"block comment before BEGIN",
"CREATE TABLE t(a);\n/* note */ BEGIN;\nINSERT INTO t VALUES(4);\nCOMMIT;\n",
"4",
),
];
for (name, script, expected) in cases {
assert_eq!(
&stdin_then_query(g, script, "SELECT group_concat(a) FROM t"),
expected,
"case: {name}"
);
}
let trig = "CREATE TABLE t(a);\nCREATE TABLE log(x);\n\
CREATE TRIGGER tr AFTER INSERT ON t BEGIN\n -- record it\n INSERT INTO log VALUES(NEW.a);\nEND;\n\
INSERT INTO t VALUES(7);\n";
assert_eq!(stdin_then_query(g, trig, "SELECT x FROM log"), "7");
}