#![cfg(feature = "std")]
use graphitesql::{Connection, Value};
use std::process::Command;
fn sqlite3_available() -> bool {
Command::new("sqlite3").arg("--version").output().is_ok()
}
fn tmp_path(tag: &str) -> String {
let mut p = std::env::temp_dir();
p.push(format!("graphite-ser-{}-{tag}.db", std::process::id()));
p.to_string_lossy().into_owned()
}
fn sqlite3(db: &str, sql: &str) -> String {
let out = Command::new("sqlite3").arg(db).arg(sql).output().unwrap();
String::from_utf8_lossy(&out.stdout).trim().to_string()
}
const SETUP: &str = "CREATE TABLE t(a INTEGER PRIMARY KEY, b TEXT, c REAL, d BLOB);
INSERT INTO t VALUES(1,'hello',1.5,x'aabb'),(2,'world',2.25,NULL),(3,NULL,3.75,x'00ff');
CREATE INDEX it ON t(b);
CREATE TABLE u(x, y, PRIMARY KEY(x,y)) WITHOUT ROWID;
INSERT INTO u VALUES('p','q'),('r','s');
CREATE VIEW v AS SELECT a, b FROM t WHERE c > 2;";
#[test]
fn serialize_is_valid_sqlite_file() {
if !sqlite3_available() {
eprintln!("sqlite3 CLI not found; skipping");
return;
}
let mut conn = Connection::open_memory().unwrap();
conn.execute_batch(SETUP).unwrap();
let bytes = conn.serialize().unwrap();
let path = tmp_path("valid");
let _ = std::fs::remove_file(&path);
std::fs::write(&path, &bytes).unwrap();
assert_eq!(sqlite3(&path, "PRAGMA integrity_check;"), "ok");
assert_eq!(
sqlite3(&path, "SELECT a,b,c,quote(d) FROM t ORDER BY a;"),
"1|hello|1.5|X'AABB'\n2|world|2.25|NULL\n3||3.75|X'00FF'"
);
assert_eq!(sqlite3(&path, "SELECT * FROM u ORDER BY x;"), "p|q\nr|s");
assert_eq!(sqlite3(&path, "SELECT * FROM v ORDER BY a;"), "2|world\n3|");
assert_eq!(
sqlite3(
&path,
"SELECT count(*) FROM sqlite_master WHERE name IN ('t','it','u','v');"
),
"4"
);
let _ = std::fs::remove_file(&path);
}
#[test]
fn serialize_round_trips_in_graphite() {
let mut conn = Connection::open_memory().unwrap();
conn.execute_batch(SETUP).unwrap();
let bytes = conn.serialize().unwrap();
let path = tmp_path("roundtrip");
let _ = std::fs::remove_file(&path);
std::fs::write(&path, &bytes).unwrap();
let re = Connection::open(&path).unwrap();
let r = re.query("SELECT count(*) FROM t").unwrap();
assert_eq!(r.rows[0][0], Value::Integer(3));
let r = re.query("SELECT b FROM t WHERE a=1").unwrap();
assert_eq!(r.rows[0][0], Value::Text("hello".into()));
let r = re.query("SELECT count(*) FROM u").unwrap();
assert_eq!(r.rows[0][0], Value::Integer(2));
let _ = std::fs::remove_file(&path);
}
#[test]
fn serialize_wal_mode_is_standalone() {
if !sqlite3_available() {
eprintln!("sqlite3 CLI not found; skipping");
return;
}
let src = tmp_path("wal-src");
for suffix in ["", "-wal", "-shm", "-journal"] {
let _ = std::fs::remove_file(format!("{src}{suffix}"));
}
{
let mut conn = Connection::create(&src).unwrap();
conn.execute_batch("PRAGMA journal_mode=WAL;").unwrap();
conn.execute_batch(SETUP).unwrap();
let bytes = conn.serialize().unwrap();
assert_eq!(bytes[18], 1, "read version normalized");
assert_eq!(bytes[19], 1, "write version normalized");
let out = tmp_path("wal-out");
let _ = std::fs::remove_file(&out);
std::fs::write(&out, &bytes).unwrap();
assert_eq!(sqlite3(&out, "PRAGMA integrity_check;"), "ok");
assert_eq!(sqlite3(&out, "SELECT count(*) FROM t;"), "3");
let _ = std::fs::remove_file(&out);
}
for suffix in ["", "-wal", "-shm", "-journal"] {
let _ = std::fs::remove_file(format!("{src}{suffix}"));
}
}
#[test]
fn cli_backup_writes_valid_file() {
if !sqlite3_available() {
eprintln!("sqlite3 CLI not found; skipping");
return;
}
let out = tmp_path("cli-backup");
let _ = std::fs::remove_file(&out);
let g = env!("CARGO_BIN_EXE_graphitesql");
let script = format!("{SETUP}\n.backup {out}\n");
let child = Command::new(g)
.arg(":memory:")
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::null())
.spawn()
.unwrap();
use std::io::Write;
child
.stdin
.as_ref()
.unwrap()
.write_all(script.as_bytes())
.unwrap();
child.wait_with_output().unwrap();
assert_eq!(sqlite3(&out, "PRAGMA integrity_check;"), "ok");
assert_eq!(sqlite3(&out, "SELECT count(*) FROM t;"), "3");
let _ = std::fs::remove_file(&out);
}
#[test]
fn deserialize_round_trip() {
let mut conn = Connection::open_memory().unwrap();
conn.execute_batch(SETUP).unwrap();
let bytes = conn.serialize().unwrap();
let mut re = Connection::deserialize(&bytes).unwrap();
assert_eq!(
re.query("SELECT count(*) FROM t").unwrap().rows[0][0],
Value::Integer(3)
);
assert_eq!(
re.query("SELECT b FROM t WHERE a=1").unwrap().rows[0][0],
Value::Text("hello".into())
);
assert_eq!(
re.query("SELECT y FROM u WHERE x='r'").unwrap().rows[0][0],
Value::Text("s".into())
);
re.execute_batch("INSERT INTO t(a,b) VALUES(4,'four');")
.unwrap();
assert_eq!(
re.query("SELECT count(*) FROM t").unwrap().rows[0][0],
Value::Integer(4)
);
assert_eq!(
conn.query("SELECT count(*) FROM t").unwrap().rows[0][0],
Value::Integer(3)
);
}
#[test]
fn deserialize_sqlite3_written_image() {
if !sqlite3_available() {
eprintln!("sqlite3 CLI not found; skipping");
return;
}
let path = tmp_path("s3-written");
let _ = std::fs::remove_file(&path);
sqlite3(
&path,
"CREATE TABLE q(n); INSERT INTO q VALUES(10),(20),(30);",
);
let bytes = std::fs::read(&path).unwrap();
let re = Connection::deserialize(&bytes).unwrap();
assert_eq!(
re.query("SELECT sum(n) FROM q").unwrap().rows[0][0],
Value::Integer(60)
);
let _ = std::fs::remove_file(&path);
}
#[test]
fn deserialize_rejects_garbage() {
assert!(Connection::deserialize(b"not a database").is_err());
}