#![cfg(feature = "std")]
use graphitesql::{Connection, Value};
use std::process::Command;
fn sqlite3_available() -> bool {
Command::new("sqlite3").arg("--version").output().is_ok()
}
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(),
}
}
fn rows(c: &Connection, sql: &str) -> String {
c.query(sql)
.unwrap()
.rows
.iter()
.map(|r| r.iter().map(render).collect::<Vec<_>>().join("|"))
.collect::<Vec<_>>()
.join("\n")
}
fn check(tag: &str, script: &str) {
if !sqlite3_available() {
eprintln!("sqlite3 not found; skipping");
return;
}
const Q: &str = "SELECT name, path, pageno, pagetype, ncell, payload, unused, mx_payload, pgoffset, pgsize FROM dbstat ORDER BY pageno";
let dir = std::env::temp_dir();
let pid = std::process::id();
let spath = dir.join(format!("gsql-dbstat-{pid}-{tag}.db"));
let spath = spath.to_string_lossy().into_owned();
let _ = std::fs::remove_file(&spath);
let o = Command::new("sqlite3")
.arg(&spath)
.arg(format!("{script};"))
.output()
.unwrap();
assert!(o.status.success(), "sqlite build failed: {o:?}");
let want = {
let o = Command::new("sqlite3").arg(&spath).arg(Q).output().unwrap();
String::from_utf8_lossy(&o.stdout).trim_end().to_string()
};
let c = Connection::open(&spath).unwrap();
let got = rows(&c, Q);
assert_eq!(got, want, "dbstat diverged for script: {script}");
let _ = std::fs::remove_file(&spath);
}
#[test]
fn dbstat_single_small_table() {
check(
"small",
"CREATE TABLE t(a, b); INSERT INTO t VALUES(1,'x'),(2,'y'),(3,'z')",
);
}
#[test]
fn dbstat_table_with_index() {
check(
"index",
"CREATE TABLE t(a INTEGER PRIMARY KEY, b TEXT); \
INSERT INTO t VALUES(1,'aa'),(2,'bb'),(3,'cc'); \
CREATE INDEX i ON t(b)",
);
}
#[test]
fn dbstat_multipage_btree() {
let mut s = String::from("CREATE TABLE t(a INTEGER PRIMARY KEY, b TEXT); ");
s.push_str("INSERT INTO t(b) VALUES('payload-string-number-0')");
for i in 1..400 {
s.push_str(&format!(",('payload-string-number-{i}')"));
}
check("multipage", &s);
}
#[test]
fn dbstat_overflow_pages() {
check(
"overflow",
"CREATE TABLE t(a INTEGER PRIMARY KEY, b TEXT); \
INSERT INTO t(b) VALUES(hex(zeroblob(8000))), (hex(zeroblob(20000)))",
);
}
#[test]
fn dbstat_empty_and_schema_only() {
check("empty", "CREATE TABLE t(a)");
}
#[test]
fn dbstat_works_in_memory_and_aggregates() {
let mut c = Connection::open_memory().unwrap();
c.execute("CREATE TABLE t(a INTEGER PRIMARY KEY, b)")
.unwrap();
c.execute("INSERT INTO t(b) VALUES('x'),('y'),('z')")
.unwrap();
assert_eq!(rows(&c, "SELECT count(*) FROM dbstat WHERE pageno=1"), "1");
assert_eq!(
rows(
&c,
"SELECT count(*) FROM dbstat WHERE pgsize<=0 OR pagetype NOT IN('leaf','internal','overflow')"
),
"0"
);
assert_eq!(
rows(&c, "SELECT count(*) FROM dbstat d WHERE d.pagetype='leaf'"),
rows(&c, "SELECT count(*) FROM dbstat WHERE pagetype='leaf'"),
);
}
#[test]
fn dbstat_select_star_column_shape() {
if !sqlite3_available() {
eprintln!("sqlite3 not found; skipping");
return;
}
let dir = std::env::temp_dir();
let spath = dir
.join(format!("gsql-dbstat-star-{}.db", std::process::id()))
.to_string_lossy()
.into_owned();
let _ = std::fs::remove_file(&spath);
let o = Command::new("sqlite3")
.arg(&spath)
.arg("CREATE TABLE t(a); INSERT INTO t VALUES(1);")
.output()
.unwrap();
assert!(o.status.success());
let c = Connection::open(&spath).unwrap();
let names = c.query("SELECT * FROM dbstat").unwrap().columns;
assert_eq!(
names,
[
"name",
"path",
"pageno",
"pagetype",
"ncell",
"payload",
"unused",
"mx_payload",
"pgoffset",
"pgsize",
]
);
let _ = std::fs::remove_file(&spath);
}
#[test]
fn dbstat_real_table_shadows_eponymous() {
let mut c = Connection::open_memory().unwrap();
c.execute("CREATE TABLE dbstat(x, y)").unwrap();
c.execute("INSERT INTO dbstat VALUES(1,2),(3,4)").unwrap();
assert_eq!(rows(&c, "SELECT x, y FROM dbstat ORDER BY x"), "1|2\n3|4");
}