#![cfg(feature = "std")]
use graphitesql::{Connection, Value};
use std::process::Command;
const SETUP: &str = "\
CREATE TABLE t(g INTEGER, a TEXT, n INTEGER);\n\
INSERT INTO t VALUES (1,'x',10),(2,'y',20),(2,'z',30),(3,'w',40);\n\
CREATE TABLE u(g INTEGER, m INTEGER);\n\
INSERT INTO u VALUES (1,100),(2,200),(2,201);\n\
CREATE VIEW vt AS SELECT g, n FROM t;\n\
CREATE VIEW vj AS SELECT t.g AS g, u.m AS m FROM t JOIN u ON t.g=u.g;\n\
CREATE VIEW vc AS SELECT g FROM t UNION SELECT m FROM u;\n\
CREATE VIEW vrowid AS SELECT rowid AS r, g FROM t;\n";
const QUERIES: &[&str] = &[
"SELECT g, sum(n) OVER (PARTITION BY g) FROM vt ORDER BY 1, 2",
"SELECT g, row_number() OVER (ORDER BY n DESC) FROM vt ORDER BY 1, 2",
"SELECT n, sum(n) OVER (ORDER BY n) FROM vt ORDER BY 1",
"SELECT g, count(*) OVER () FROM vt ORDER BY 1, 2",
"SELECT g, m, sum(m) OVER (PARTITION BY g) FROM vj ORDER BY 1, 2, 3",
"SELECT g, rank() OVER (ORDER BY g) FROM vc ORDER BY 1, 2",
"SELECT r, g, sum(g) OVER (ORDER BY r) FROM vrowid ORDER BY 1",
];
fn conn() -> Connection {
let mut c = Connection::open_memory().unwrap();
for stmt in SETUP.split(';') {
let s = stmt.trim();
if !s.is_empty() {
c.execute(s).unwrap();
}
}
c
}
fn render(v: &Value) -> String {
match v {
Value::Null => String::new(),
Value::Integer(i) => i.to_string(),
Value::Text(s) => s.clone(),
Value::Real(r) => graphitesql::exec::eval::format_real(*r),
Value::Blob(b) => b.iter().map(|x| format!("{x:02x}")).collect(),
}
}
#[test]
fn window_view_runs_on_vdbe_and_matches_tree_walker() {
let c = conn();
for q in QUERIES {
let got = c.query_vdbe(q).unwrap().rows;
let want = c.query(q).unwrap().rows;
assert_eq!(got, want, "VDBE vs tree-walker diverged on {q}");
}
}
#[test]
fn window_view_matches_sqlite3() {
if Command::new("sqlite3").arg("--version").output().is_err() {
eprintln!("sqlite3 not found; skipping");
return;
}
let c = conn();
for q in QUERIES {
let vdbe: Vec<Vec<String>> = c
.query_vdbe(q)
.unwrap()
.rows
.iter()
.map(|r| r.iter().map(render).collect())
.collect();
let out = Command::new("sqlite3")
.arg(":memory:")
.arg("-ascii")
.arg(format!("{SETUP}{q};"))
.output()
.unwrap();
assert!(out.status.success(), "sqlite3 failed on {q}");
let text = String::from_utf8(out.stdout).unwrap();
let want: Vec<Vec<String>> = text
.split('\u{1e}')
.filter(|r| !r.is_empty())
.map(|r| r.split('\u{1f}').map(|f| f.to_string()).collect())
.collect();
assert_eq!(vdbe, want, "VDBE vs sqlite3 diverged on {q}");
}
}
#[test]
fn nocase_view_column_window_defers() {
let mut c = conn();
c.execute("CREATE TABLE w(k TEXT COLLATE NOCASE, val INTEGER)")
.unwrap();
c.execute("INSERT INTO w VALUES ('A',1),('b',2)").unwrap();
c.execute("CREATE VIEW vw AS SELECT k, val FROM w").unwrap();
let q = "SELECT k, count(*) OVER () FROM vw ORDER BY 1";
assert!(c.query_vdbe(q).is_err(), "expected VDBE fallback for {q}");
assert!(c.query(q).is_ok(), "tree-walker should run {q}");
}
#[test]
fn rowid_over_view_window_defers() {
let c = conn();
let q = "SELECT g, sum(n) OVER () FROM vt WHERE rowid > 0";
assert!(c.query_vdbe(q).is_err(), "expected VDBE fallback for {q}");
}