#![cfg(feature = "std")]
use graphitesql::Connection;
use std::path::PathBuf;
use std::process::Command;
fn oracle() -> Option<String> {
if let Ok(p) = std::env::var("GRAPHITE_STAT4_ORACLE")
&& Command::new(&p)
.arg(":memory:")
.arg("SELECT 1")
.output()
.is_ok()
{
return Some(p);
}
let default = "/tmp/claude-1000/-home-magicaltux-projects-graphitesql/\
faf0a91b-ae7e-4ff2-9c4e-2e8b1eed5c39/scratchpad/sqlite-src/\
sqlite-amalgamation-3500400/sqlite3-oracle";
if Command::new(default)
.arg(":memory:")
.arg("SELECT 1")
.output()
.is_ok()
{
return Some(default.to_string());
}
None
}
fn oracle_has_stat4(orc: &str) -> bool {
let out = Command::new(orc)
.arg(":memory:")
.arg(
"CREATE TABLE t(a); INSERT INTO t VALUES(1),(2),(3); CREATE INDEX i ON t(a); \
ANALYZE; SELECT count(*) FROM sqlite_stat4;",
)
.output();
matches!(out, Ok(o) if String::from_utf8_lossy(&o.stdout).trim().parse::<i64>().unwrap_or(0) > 0)
}
fn orc_query(orc: &str, db: &str, sql: &str) -> String {
let o = Command::new(orc).arg(db).arg(sql).output().unwrap();
assert!(o.status.success(), "{}", String::from_utf8_lossy(&o.stderr));
String::from_utf8_lossy(&o.stdout).trim_end().to_string()
}
fn tmp(name: &str) -> String {
let mut p: PathBuf = std::env::temp_dir();
p.push(format!("gsql-stat4-{}-{}.db", std::process::id(), name));
let s = p.to_string_lossy().into_owned();
let _ = std::fs::remove_file(&s);
s
}
fn check(orc: &str, tag: &str, setup: &str) {
let script = format!("{setup}; ANALYZE;");
let odb = tmp(&format!("orc-{tag}"));
orc_query(orc, &odb, &script);
let gdb = tmp(&format!("gra-{tag}"));
{
let mut c = Connection::create(&gdb).unwrap();
for stmt in script.split(';') {
let s = stmt.trim();
if !s.is_empty() {
c.execute(s)
.unwrap_or_else(|e| panic!("[{tag}] graphite failed on `{s}`: {e:?}"));
}
}
}
let ic = orc_query(orc, &gdb, "PRAGMA integrity_check;");
assert_eq!(ic, "ok", "[{tag}] integrity_check on graphite DB");
let q1 = "SELECT tbl,idx,stat FROM sqlite_stat1 ORDER BY tbl,idx,stat";
let q4 = "SELECT tbl,idx,neq,nlt,ndlt,quote(sample) FROM sqlite_stat4 \
ORDER BY tbl,idx,nlt,ndlt";
assert_eq!(
orc_query(orc, &gdb, q1),
orc_query(orc, &odb, q1),
"[{tag}] sqlite_stat1 mismatch"
);
assert_eq!(
orc_query(orc, &gdb, q4),
orc_query(orc, &odb, q4),
"[{tag}] sqlite_stat4 mismatch"
);
let _ = std::fs::remove_file(&odb);
let _ = std::fs::remove_file(&gdb);
}
#[test]
fn stat4_matches_stat4_oracle() {
let Some(orc) = oracle() else {
eprintln!("STAT4 oracle not found; skipping");
return;
};
if !oracle_has_stat4(&orc) {
eprintln!("oracle lacks STAT4; skipping");
return;
}
let series = |lo: i64, hi: i64, expr: &str| {
format!(
"WITH RECURSIVE r(x) AS (SELECT {lo} UNION ALL SELECT x+1 FROM r WHERE x<{hi}) \
INSERT INTO t SELECT {expr} FROM r"
)
};
let cases: Vec<(&str, String)> = vec![
(
"multicol-small",
"CREATE TABLE t(a,b); \
INSERT INTO t VALUES(1,10),(1,20),(2,30),(2,40),(3,50); \
CREATE INDEX i ON t(a,b)"
.into(),
),
(
"reservoir-100",
format!(
"CREATE TABLE t(a); {}; CREATE INDEX i ON t(a)",
series(1, 100, "x")
),
),
(
"heavy-dup",
format!(
"CREATE TABLE t(a,b); {}; CREATE INDEX i ON t(a)",
series(1, 250, "x/5, x")
),
),
(
"nulls",
"CREATE TABLE t(a,b); INSERT INTO t VALUES(NULL,1),(NULL,2),(1,3),(1,4),(2,5); \
CREATE INDEX i ON t(a)"
.into(),
),
(
"desc-index",
"CREATE TABLE t(a,b); INSERT INTO t VALUES(1,1),(2,2),(3,3),(2,4); \
CREATE INDEX i ON t(a DESC)"
.into(),
),
(
"unique-notnull",
"CREATE TABLE t(a NOT NULL,b); INSERT INTO t VALUES(1,10),(2,20),(3,30); \
CREATE UNIQUE INDEX i ON t(a)"
.into(),
),
(
"text-nocase",
"CREATE TABLE t(a TEXT COLLATE NOCASE,b); \
INSERT INTO t VALUES('A',1),('a',2),('B',3),('b',4),('c',5); \
CREATE INDEX i ON t(a)"
.into(),
),
(
"two-indexes",
format!(
"CREATE TABLE t(a,b,c); {}; CREATE INDEX i ON t(a,b); CREATE INDEX j ON t(c)",
series(1, 60, "x%3, x%9, x")
),
),
(
"wr-pk-only",
"CREATE TABLE t(a,b,PRIMARY KEY(a,b)) WITHOUT ROWID; \
INSERT INTO t VALUES(1,10),(1,20),(2,30),(3,40)"
.into(),
),
(
"wr-pk-and-secondary",
"CREATE TABLE t(a,b,c,PRIMARY KEY(a,b)) WITHOUT ROWID; \
INSERT INTO t VALUES(1,2,100),(3,4,200),(5,6,100),(7,8,300); \
CREATE INDEX i ON t(c)"
.into(),
),
(
"wr-desc-pk",
"CREATE TABLE t(a,b,PRIMARY KEY(a DESC,b)) WITHOUT ROWID; \
INSERT INTO t VALUES(1,1),(2,2),(3,3),(4,4)"
.into(),
),
(
"wr-single-pk-large",
format!(
"CREATE TABLE t(k PRIMARY KEY, v) WITHOUT ROWID; {}",
series(1, 80, "x, x*2")
),
),
(
"empty-table",
"CREATE TABLE t(a,b); CREATE INDEX i ON t(a)".into(),
),
];
for (tag, setup) in &cases {
check(&orc, tag, setup);
}
}