#![cfg(feature = "std")]
use graphitesql::Connection;
use std::process::Command;
fn sqlite3_available() -> bool {
Command::new("sqlite3").arg("--version").output().is_ok()
}
const SCHEMA: &str = "CREATE TABLE o(x, y TEXT);\
INSERT INTO o VALUES(1,'a'),(2,'b'),(3,'c'),(NULL,'n'),(2,'b2'),(2.0,'real'),\
('2','txt'),('2abc','junk'),(x'32','blob'),(2.5,'half'),(99,'miss'),(-1,'neg'),\
(2,'two'),(5,'five');\
CREATE TABLE t(id INTEGER PRIMARY KEY, name TEXT);\
INSERT INTO t VALUES(1,'one'),(2,'two'),(5,'five');";
const VDBE_QUERIES: &[&str] = &[
"SELECT o.x, t.name FROM o JOIN t ON o.x = t.id ORDER BY o.y",
"SELECT o.x, t.name FROM o JOIN t ON t.id = o.x ORDER BY o.y", "SELECT o.y, t.name FROM o JOIN t ON o.x = t.id WHERE t.name <> 'one' ORDER BY o.y",
"SELECT * FROM o JOIN t ON o.x = t.id ORDER BY o.y",
"SELECT o.x * 100 + t.id AS s FROM o JOIN t ON o.x = t.id ORDER BY s",
"SELECT DISTINCT t.name FROM o JOIN t ON o.x = t.id ORDER BY t.name",
"SELECT o.x, t.name FROM o JOIN t ON o.x = t.id ORDER BY o.y LIMIT 2 OFFSET 1",
"SELECT count(*), sum(t.id) FROM o JOIN t ON o.x = t.id",
"SELECT t.name, count(*) FROM o JOIN t ON o.x = t.id GROUP BY t.name ORDER BY t.name",
"SELECT o.y FROM o JOIN t ON (o.x) = (t.id) ORDER BY o.y", "SELECT o.x, t.name FROM o LEFT JOIN t ON o.x = t.id ORDER BY o.y",
"SELECT o.x, t.name FROM o LEFT JOIN t ON t.id = o.x ORDER BY o.y",
"SELECT o.y, t.id, t.name FROM o LEFT JOIN t ON o.x = t.id ORDER BY o.y",
"SELECT o.y FROM o LEFT JOIN t ON o.x = t.id WHERE t.name IS NULL ORDER BY o.y",
"SELECT count(*), count(t.id) FROM o LEFT JOIN t ON o.x = t.id",
"SELECT o.x, t.name FROM o LEFT JOIN t ON o.x = t.id ORDER BY o.y LIMIT 3 OFFSET 2",
"SELECT o.x, t.name FROM o JOIN t ON o.x = t.id AND t.name = o.y ORDER BY o.y",
"SELECT o.x, t.name FROM o JOIN t ON t.name = o.y AND o.x = t.id ORDER BY o.y",
"SELECT o.x, t.name FROM o JOIN t ON o.x = t.id AND t.name <> 'one' ORDER BY o.y",
"SELECT o.x, t.name FROM o LEFT JOIN t ON o.x = t.id AND t.name = o.y ORDER BY o.y",
"SELECT count(*) FROM o JOIN t ON o.x = t.id AND o.y = t.name",
];
fn setup() -> Connection {
let mut c = Connection::open_memory().unwrap();
for stmt in SCHEMA.split(';') {
if !stmt.trim().is_empty() {
c.execute(stmt).unwrap();
}
}
c
}
#[test]
fn live_inner_cursor_routes_and_matches_tree_walker() {
let c = setup();
for q in VDBE_QUERIES {
let vdbe = c
.query_vdbe(q)
.unwrap_or_else(|e| panic!("expected VDBE routing for `{q}`: {e:?}"));
c.set_use_vdbe(false);
let tw = c.query(q).unwrap();
c.set_use_vdbe(true);
assert_eq!(vdbe.rows, tw.rows, "VDBE vs tree-walker rows for `{q}`");
assert_eq!(vdbe.columns, tw.columns, "columns for `{q}`");
}
}
fn cli_rows(bin: &str, sql: &str) -> String {
let full = format!("{SCHEMA} {sql}");
let out = Command::new(bin)
.arg(":memory:")
.arg(&full)
.output()
.unwrap();
String::from_utf8_lossy(&out.stdout).into_owned()
}
const SCHEMA3: &str = "CREATE TABLE o(x, z, y TEXT);\
INSERT INTO o VALUES(1,5,'a'),(2,1,'b'),(3,5,'c'),(NULL,2,'n'),(2,9,'b2'),\
(2.5,1,'h'),('2',2,'t'),(99,1,'m');\
CREATE TABLE t(id INTEGER PRIMARY KEY, tn TEXT);\
INSERT INTO t VALUES(1,'t1'),(2,'t2'),(5,'t5');\
CREATE TABLE u(uid INTEGER PRIMARY KEY, un TEXT);\
INSERT INTO u VALUES(1,'u1'),(2,'u2'),(5,'u5'),(9,'u9');";
const CHAIN_QUERIES: &[&str] = &[
"SELECT o.y, t.tn, u.un FROM o JOIN t ON o.x = t.id JOIN u ON o.z = u.uid ORDER BY o.y",
"SELECT o.y, t.tn, u.un FROM o JOIN t ON o.x = t.id JOIN u ON t.id = u.uid ORDER BY o.y",
"SELECT o.y, t.id, u.uid FROM o JOIN t ON o.x = t.id JOIN u ON u.uid = o.z ORDER BY o.y",
"SELECT o.y, u.un FROM o JOIN t ON o.x = t.id JOIN u ON o.z = u.uid WHERE u.un <> 'u5' ORDER BY o.y",
"SELECT count(*) FROM o JOIN t ON o.x = t.id JOIN u ON o.z = u.uid",
"SELECT o.y, t.tn, u.un FROM o JOIN t ON o.x = t.id AND t.tn <> 't9' JOIN u ON o.z = u.uid ORDER BY o.y",
];
fn setup3() -> Connection {
let mut c = Connection::open_memory().unwrap();
for stmt in SCHEMA3.split(';') {
if !stmt.trim().is_empty() {
c.execute(stmt).unwrap();
}
}
c
}
#[test]
fn chain_seek_routes_and_matches_tree_walker() {
let c = setup3();
for q in CHAIN_QUERIES {
let vdbe = c
.query_vdbe(q)
.unwrap_or_else(|e| panic!("expected VDBE routing for `{q}`: {e:?}"));
c.set_use_vdbe(false);
let tw = c.query(q).unwrap();
c.set_use_vdbe(true);
assert_eq!(vdbe.rows, tw.rows, "VDBE vs tree-walker rows for `{q}`");
assert_eq!(vdbe.columns, tw.columns, "columns for `{q}`");
}
}
#[test]
fn chain_seek_matches_sqlite3() {
if !sqlite3_available() {
eprintln!("sqlite3 CLI not found; skipping");
return;
}
let g = env!("CARGO_BIN_EXE_graphitesql");
for q in CHAIN_QUERIES {
let full_sq = format!("{SCHEMA3} {q}");
let full_g = format!("{SCHEMA3} {q}");
let sq = Command::new("sqlite3")
.arg(":memory:")
.arg(&full_sq)
.output()
.unwrap();
let gr = Command::new(g)
.arg(":memory:")
.arg(&full_g)
.output()
.unwrap();
assert_eq!(
String::from_utf8_lossy(&sq.stdout),
String::from_utf8_lossy(&gr.stdout),
"sqlite3 vs graphite `{q}`"
);
}
}
#[test]
fn live_inner_cursor_matches_sqlite3() {
if !sqlite3_available() {
eprintln!("sqlite3 CLI not found; skipping");
return;
}
let g = env!("CARGO_BIN_EXE_graphitesql");
for q in VDBE_QUERIES {
assert_eq!(
cli_rows("sqlite3", q),
cli_rows(g, q),
"sqlite3 vs graphite `{q}`"
);
}
}