#![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 rows(bin: &str, sql: &str) -> String {
let mut child = Command::new(bin)
.arg(":memory:")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
child
.stdin
.take()
.unwrap()
.write_all(sql.as_bytes())
.unwrap();
let o = child.wait_with_output().unwrap();
let mut s = String::from_utf8_lossy(&o.stdout).into_owned();
let e = String::from_utf8_lossy(&o.stderr).into_owned();
let prefixes = [
"Parse error near line 1: ",
"Runtime error near line 1: ",
"Error: error: ",
"Error: ",
];
for line in e.lines() {
if let Some(p) = prefixes.iter().find(|p| line.starts_with(**p)) {
s.push_str(&line[p.len()..]);
s.push('\n');
}
}
s
}
#[test]
fn recursive_cte_order_by_matches_sqlite() {
if !sqlite3_available() {
eprintln!("sqlite3 CLI not found; skipping");
return;
}
let g = env!("CARGO_BIN_EXE_graphitesql");
let e = "CREATE TABLE e(a,b);INSERT INTO e VALUES(1,2),(1,3),(2,4),(3,5);";
let org = "CREATE TABLE org(name TEXT PRIMARY KEY, boss TEXT);\
INSERT INTO org VALUES('Alice',NULL),('Bob','Alice'),('Cindy','Alice'),\
('Dave','Bob'),('Emma','Bob'),('Fred','Cindy'),('Gail','Cindy');";
let p = "CREATE TABLE p(id,par,w);\
INSERT INTO p VALUES(1,NULL,5),(2,1,3),(3,1,9),(4,2,1),(5,2,7);";
let cases: &[&str] = &[
&format!(
"{e}WITH RECURSIVE r(x) AS (SELECT 1 UNION ALL SELECT b FROM e JOIN r ON a=x \
ORDER BY 1 DESC LIMIT 20) SELECT group_concat(x) FROM (SELECT x FROM r);"
),
&format!(
"{e}WITH RECURSIVE r(x) AS (SELECT 1 UNION ALL SELECT b FROM e JOIN r ON a=x \
ORDER BY 1 ASC LIMIT 20) SELECT group_concat(x) FROM (SELECT x FROM r);"
),
&format!(
"{e}WITH RECURSIVE r(x) AS (SELECT 1 UNION ALL SELECT b FROM e JOIN r ON a=x) \
SELECT group_concat(x) FROM (SELECT x FROM r);"
),
&format!(
"{org}WITH RECURSIVE u(n,lvl) AS (SELECT name,0 FROM org WHERE boss IS NULL \
UNION ALL SELECT org.name,u.lvl+1 FROM org,u WHERE org.boss=u.n) SELECT n FROM u;"
),
&format!(
"{org}WITH RECURSIVE u(n,lvl) AS (SELECT name,0 FROM org WHERE boss IS NULL \
UNION ALL SELECT org.name,u.lvl+1 FROM org,u WHERE org.boss=u.n ORDER BY 2 DESC) \
SELECT n FROM u;"
),
&format!(
"{org}WITH RECURSIVE u(name) AS (SELECT name FROM org WHERE boss IS NULL \
UNION ALL SELECT org.name FROM org,u WHERE org.boss=u.name ORDER BY 1 DESC) \
SELECT name FROM u;"
),
&format!(
"{e}WITH RECURSIVE r(x) AS (SELECT 1 UNION SELECT b FROM e JOIN r ON a=x \
ORDER BY 1 DESC LIMIT 30) SELECT group_concat(x) FROM (SELECT x FROM r);"
),
&format!(
"{p}WITH RECURSIVE t(id,w,d) AS (SELECT id,w,0 FROM p WHERE par IS NULL \
UNION ALL SELECT p.id,p.w,t.d+1 FROM p JOIN t ON p.par=t.id ORDER BY 3 DESC, 2 ASC) \
SELECT id FROM t;"
),
"WITH RECURSIVE c(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM c ORDER BY 1 DESC LIMIT 50) \
SELECT sum(n) FROM c;",
"WITH RECURSIVE c(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM c WHERE n<3 ORDER BY n) \
SELECT * FROM c;",
"WITH RECURSIVE c(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM c WHERE n<3 ORDER BY n+0 \
LIMIT 9) SELECT * FROM c;",
"CREATE TABLE t(v);INSERT INTO t VALUES(5),(3);\
WITH RECURSIVE c(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM c WHERE n<3 ORDER BY v \
LIMIT 9) SELECT * FROM c;",
"WITH RECURSIVE c(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM c WHERE n<3 ORDER BY 2 \
LIMIT 9) SELECT * FROM c;",
"WITH RECURSIVE c(n) AS (SELECT 1 AS x UNION ALL SELECT n+1 FROM c WHERE n<3 \
ORDER BY x LIMIT 9) SELECT * FROM c;",
"WITH RECURSIVE c(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM c WHERE n<5 \
ORDER BY 1 DESC LIMIT 20) SELECT * FROM c;",
"WITH RECURSIVE c(s) AS (SELECT 'a' UNION ALL SELECT CASE s WHEN 'a' THEN 'C' \
WHEN 'C' THEN 'b' ELSE 'Z' END FROM c WHERE s<'y' ORDER BY 1 COLLATE NOCASE LIMIT 6) \
SELECT s FROM c;",
];
for q in cases {
assert_eq!(rows("sqlite3", q), rows(g, q), "mismatch for `{q}`");
}
}
#[test]
fn recursive_cte_aggregate_window_rejected_like_sqlite() {
if !sqlite3_available() {
eprintln!("sqlite3 CLI not found; skipping");
return;
}
let g = env!("CARGO_BIN_EXE_graphitesql");
let cases: &[&str] = &[
"WITH RECURSIVE c(n) AS (SELECT 1 UNION ALL SELECT count(*) FROM c) SELECT * FROM c;",
"WITH RECURSIVE c(n) AS (SELECT 1 UNION ALL SELECT sum(n) FROM c) SELECT * FROM c;",
"WITH RECURSIVE c(n) AS (SELECT 1 UNION ALL SELECT max(n)+1 FROM c WHERE n<5) \
SELECT * FROM c;",
"WITH RECURSIVE c(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM c WHERE n<5 GROUP BY n) \
SELECT * FROM c;",
"WITH RECURSIVE c(n) AS (SELECT 1 UNION SELECT count(*) FROM c) SELECT * FROM c;",
"WITH RECURSIVE c(n) AS (SELECT 1 UNION ALL SELECT count(*) FROM c LIMIT 3) \
SELECT * FROM c;",
"WITH RECURSIVE c(n) AS (SELECT 1 UNION ALL SELECT row_number() OVER () FROM c \
WHERE n<3) SELECT * FROM c;",
"WITH RECURSIVE c(n) AS (SELECT 1 UNION ALL SELECT sum(n)+row_number() OVER () FROM c) \
SELECT * FROM c;",
"WITH RECURSIVE c(n) AS (SELECT 1 UNION ALL SELECT count(*) OVER () FROM c WHERE n<3) \
SELECT * FROM c;",
"WITH RECURSIVE c(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM c WHERE n<5 HAVING n>0) \
SELECT * FROM c;",
"CREATE TABLE t(x);INSERT INTO t VALUES(1),(2);\
WITH RECURSIVE c(n) AS (SELECT 1 UNION ALL SELECT n+(SELECT count(*) FROM t) FROM c \
WHERE n<9) SELECT * FROM c;",
"CREATE TABLE t(x);INSERT INTO t VALUES(1),(2),(3);\
WITH RECURSIVE c(n) AS (SELECT count(*) FROM t UNION ALL SELECT n+1 FROM c WHERE n<5) \
SELECT * FROM c;",
"WITH RECURSIVE c(n) AS (SELECT 1 UNION ALL SELECT DISTINCT n+1 FROM c WHERE n<5) \
SELECT * FROM c;",
];
for q in cases {
assert_eq!(rows("sqlite3", q), rows(g, q), "mismatch for `{q}`");
}
}