#![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_str(c: &Connection, sql: &str) -> String {
c.query(sql)
.unwrap()
.rows
.iter()
.map(|row| row.iter().map(render).collect::<Vec<_>>().join("|"))
.collect::<Vec<_>>()
.join("\n")
}
#[test]
fn against_sqlite3() {
if !sqlite3_available() {
eprintln!("sqlite3 not found; skipping");
return;
}
let setup = [
"CREATE TABLE t(a, b UNIQUE, c)",
"CREATE INDEX ix ON t(a, c)",
"CREATE INDEX ixp ON t(a) WHERE c > 0",
"CREATE INDEX ixe ON t(a || c, c)",
"CREATE TABLE wr(k TEXT PRIMARY KEY, v, w) WITHOUT ROWID",
"CREATE INDEX iwr ON wr(v)",
"CREATE TABLE p(id INTEGER PRIMARY KEY, k)",
"CREATE TABLE ch(x, y, FOREIGN KEY(x) REFERENCES p(id) ON DELETE CASCADE)",
"CREATE TABLE ch2(x REFERENCES p(id), y REFERENCES p(k) ON DELETE CASCADE)",
];
let queries = [
"PRAGMA index_list(t)",
"PRAGMA index_info(ix)",
"PRAGMA index_info(ixp)",
"PRAGMA index_info(ixe)",
"PRAGMA foreign_key_list(ch)",
"PRAGMA foreign_key_list(ch2)",
"PRAGMA freelist_count",
"PRAGMA application_id",
"PRAGMA table_info(t)",
"PRAGMA table_info(p)",
"PRAGMA table_xinfo(t)",
"PRAGMA table_xinfo(p)",
"PRAGMA index_xinfo(ix)",
"PRAGMA index_xinfo(ixp)",
"PRAGMA index_xinfo(ixe)",
"PRAGMA index_info(iwr)",
"PRAGMA index_xinfo(iwr)",
];
let path = std::env::temp_dir().join(format!("gsql-prag-{}.db", std::process::id()));
let path = path.to_string_lossy().into_owned();
let _ = std::fs::remove_file(&path);
let out = Command::new("sqlite3")
.arg(&path)
.arg(setup.join(";"))
.output()
.unwrap();
assert!(
out.status.success(),
"{}",
String::from_utf8_lossy(&out.stderr)
);
let mut g = Connection::open_memory().unwrap();
for s in setup {
g.execute(s).unwrap();
}
let mut failures = Vec::new();
{
let fkc = "CREATE TABLE pp(id INTEGER PRIMARY KEY);\
CREATE TABLE cc(x, y, FOREIGN KEY(x) REFERENCES pp(id));\
INSERT INTO pp VALUES (1);\
INSERT INTO cc VALUES (1,'a'),(2,'b'),(9,'c')";
let fpath = std::env::temp_dir().join(format!("gsql-fkc-{}.db", std::process::id()));
let fpath = fpath.to_string_lossy().into_owned();
let _ = std::fs::remove_file(&fpath);
Command::new("sqlite3")
.arg(&fpath)
.arg(fkc)
.output()
.unwrap();
let want = {
let o = Command::new("sqlite3")
.arg(&fpath)
.arg("PRAGMA foreign_key_check")
.output()
.unwrap();
String::from_utf8_lossy(&o.stdout).trim_end().to_string()
};
let _ = std::fs::remove_file(&fpath);
let mut gc = Connection::open_memory().unwrap();
for s in fkc.split(';') {
if !s.trim().is_empty() {
gc.execute(s).unwrap();
}
}
let got = rows_str(&gc, "PRAGMA foreign_key_check");
if got != want {
failures.push(format!(
" foreign_key_check\n sqlite: {want:?}\n graphite: {got:?}"
));
}
}
for q in queries {
let want = {
let o = Command::new("sqlite3").arg(&path).arg(q).output().unwrap();
String::from_utf8_lossy(&o.stdout).trim_end().to_string()
};
let got = rows_str(&g, q);
if got != want {
failures.push(format!(
" {q}\n sqlite: {want:?}\n graphite: {got:?}"
));
}
}
let _ = std::fs::remove_file(&path);
assert!(
failures.is_empty(),
"{} PRAGMA queries diverged:\n{}",
failures.len(),
failures.join("\n")
);
}
#[test]
fn writable_user_version_and_application_id_persist() {
let sqlite = Command::new("sqlite3").arg("--version").output().is_ok();
let path = std::env::temp_dir().join(format!("gsql-uv-{}.db", std::process::id()));
let path = path.to_string_lossy().into_owned();
let _ = std::fs::remove_file(&path);
{
let mut c = Connection::create(&path).unwrap();
c.execute("CREATE TABLE t(a)").unwrap();
c.execute("PRAGMA user_version = 42").unwrap();
c.execute("PRAGMA application_id = 0x1234").unwrap();
assert_eq!(
c.query("PRAGMA user_version").unwrap().rows[0][0],
Value::Integer(42)
);
c.execute("PRAGMA user_version = -5").unwrap();
assert_eq!(
c.query("PRAGMA user_version").unwrap().rows[0][0],
Value::Integer(-5) );
c.execute("PRAGMA user_version = 100").unwrap();
}
if sqlite {
let out = Command::new("sqlite3")
.arg(&path)
.arg("PRAGMA user_version; PRAGMA application_id; PRAGMA integrity_check;")
.output()
.unwrap();
let s = String::from_utf8_lossy(&out.stdout);
assert!(s.contains("100"), "user_version not persisted: {s}");
assert!(s.contains("4660"), "application_id not persisted: {s}"); assert!(s.contains("ok"));
}
let _ = std::fs::remove_file(&path);
}
#[test]
fn pragma_table_valued_functions() {
if !sqlite3_available() {
eprintln!("sqlite3 not found; skipping");
return;
}
let setup = "CREATE TABLE t(a INT, b TEXT, c INT); CREATE INDEX it ON t(a,c);";
let path = std::env::temp_dir().join(format!("gsql-ptvf-{}.db", std::process::id()));
let path = path.to_string_lossy().into_owned();
let _ = std::fs::remove_file(&path);
Command::new("sqlite3")
.arg(&path)
.arg(setup)
.output()
.unwrap();
let mut g = Connection::open_memory().unwrap();
for s in setup.split(';') {
if !s.trim().is_empty() {
g.execute(s).unwrap();
}
}
let queries = [
"SELECT group_concat(name) FROM pragma_table_info('t')",
"SELECT count(*) FROM pragma_index_info('it')",
"SELECT count(*) FROM pragma_index_xinfo('it')",
"SELECT name FROM pragma_table_info('t') WHERE pk = 0 ORDER BY cid",
"SELECT name, type FROM pragma_table_xinfo('t') ORDER BY cid",
"SELECT count(*) FROM pragma_index_list('t')",
];
for q in queries {
let want = {
let o = Command::new("sqlite3")
.arg(&path)
.arg(format!("{q};"))
.output()
.unwrap();
String::from_utf8_lossy(&o.stdout).trim_end().to_string()
};
let got = rows_str(&g, q);
assert_eq!(got, want, "pragma TVF diverged: {q}");
}
let _ = std::fs::remove_file(&path);
}
#[test]
fn table_info_default_value_is_sql_text() {
if !sqlite3_available() {
eprintln!("sqlite3 CLI not found; skipping");
return;
}
let ddl = "CREATE TABLE t(a DEFAULT 'x', b DEFAULT 5, c DEFAULT 3.14, e DEFAULT NULL, g)";
let want = {
let o = Command::new("sqlite3")
.arg(":memory:")
.arg(format!(
"{ddl}; SELECT name, dflt_value FROM pragma_table_info('t');"
))
.output()
.unwrap();
String::from_utf8_lossy(&o.stdout).trim().to_string()
};
let mut c = Connection::open_memory().unwrap();
c.execute(ddl).unwrap();
let got = rows_str(&c, "SELECT name, dflt_value FROM pragma_table_info('t')");
assert_eq!(got, want);
}
#[test]
fn analysis_limit_and_optimize() {
let c = Connection::open_memory().unwrap();
assert_eq!(
c.query("PRAGMA analysis_limit").unwrap().rows,
vec![vec![Value::Integer(0)]]
);
assert_eq!(
c.query("PRAGMA analysis_limit=100").unwrap().rows,
vec![vec![Value::Integer(100)]]
);
assert_eq!(
c.query("PRAGMA analysis_limit").unwrap().rows,
vec![vec![Value::Integer(100)]]
);
assert_eq!(
c.query("PRAGMA analysis_limit=-5").unwrap().rows,
vec![vec![Value::Integer(0)]]
);
assert!(c.query("PRAGMA optimize").unwrap().rows.is_empty());
if !sqlite3_available() {
return;
}
for (q, want) in [
("PRAGMA analysis_limit", "0"),
(
"PRAGMA analysis_limit=250; PRAGMA analysis_limit",
"250\n250",
),
("PRAGMA analysis_limit=-1; PRAGMA analysis_limit", "0\n0"),
("PRAGMA optimize", ""),
] {
let got = {
let o = Command::new("sqlite3")
.arg(":memory:")
.arg(q)
.output()
.unwrap();
String::from_utf8_lossy(&o.stdout).trim_end().to_string()
};
assert_eq!(got, want, "sqlite baseline drift for {q}");
}
}
#[test]
fn busy_timeout_roundtrip_and_wal_checkpoint() {
let c = Connection::open_memory().unwrap();
let r = c.query("PRAGMA busy_timeout").unwrap();
assert_eq!(r.columns, vec!["timeout"]);
assert_eq!(r.rows, vec![vec![Value::Integer(0)]]);
assert_eq!(
c.query("PRAGMA busy_timeout=5000").unwrap().rows,
vec![vec![Value::Integer(5000)]]
);
assert_eq!(
c.query("PRAGMA busy_timeout").unwrap().rows,
vec![vec![Value::Integer(5000)]]
);
assert_eq!(
c.query("PRAGMA busy_timeout=-3").unwrap().rows,
vec![vec![Value::Integer(0)]]
);
let mut c2 = Connection::open_memory().unwrap();
c2.execute("PRAGMA busy_timeout=250").unwrap();
assert_eq!(
c2.query("PRAGMA busy_timeout").unwrap().rows,
vec![vec![Value::Integer(250)]]
);
let wc = c.query("PRAGMA wal_checkpoint").unwrap();
assert_eq!(wc.columns, vec!["busy", "log", "checkpointed"]);
assert_eq!(
wc.rows,
vec![vec![
Value::Integer(0),
Value::Integer(-1),
Value::Integer(-1)
]]
);
assert_eq!(
c.query("PRAGMA wal_checkpoint(TRUNCATE)").unwrap().rows,
wc.rows
);
if !sqlite3_available() {
return;
}
for (q, want) in [
("PRAGMA busy_timeout", "0"),
(
"PRAGMA busy_timeout=5000; PRAGMA busy_timeout",
"5000\n5000",
),
("PRAGMA wal_checkpoint", "0|-1|-1"),
("PRAGMA wal_checkpoint(FULL)", "0|-1|-1"),
] {
let got = {
let o = Command::new("sqlite3")
.arg(":memory:")
.arg(q)
.output()
.unwrap();
String::from_utf8_lossy(&o.stdout).trim_end().to_string()
};
assert_eq!(got, want, "sqlite baseline drift for {q}");
}
}
#[test]
fn table_info_of_missing_table_is_empty() {
let mut c = Connection::open_memory().unwrap();
c.execute("CREATE TABLE real(x)").unwrap();
assert!(c.query("PRAGMA table_info(nope)").unwrap().rows.is_empty());
assert!(c.query("PRAGMA table_xinfo(nope)").unwrap().rows.is_empty());
assert_eq!(
c.query("SELECT count(*) FROM pragma_table_info('nope')")
.unwrap()
.rows[0][0],
Value::Integer(0)
);
assert_eq!(
c.query("SELECT count(*) FROM pragma_table_info('real')")
.unwrap()
.rows[0][0],
Value::Integer(1)
);
}