#![cfg(feature = "std")]
#![cfg(feature = "fts5")]
use graphitesql::Connection;
use std::process::Command;
use std::sync::atomic::{AtomicU64, Ordering};
fn tmp_path(tag: &str) -> String {
static SEQ: AtomicU64 = AtomicU64::new(0);
let p = std::env::temp_dir().join(format!(
"gsql-fts5-valid-{}-{}-{}.db",
tag,
std::process::id(),
SEQ.fetch_add(1, Ordering::Relaxed)
));
let p = p.to_string_lossy().into_owned();
let _ = std::fs::remove_file(&p);
p
}
fn have_fts5_sqlite() -> bool {
let o = Command::new("sqlite3")
.arg(":memory:")
.arg("CREATE VIRTUAL TABLE t USING fts5(a); SELECT 1;")
.output();
matches!(o, Ok(o) if o.status.success())
}
fn sqlite_quick_check(path: &str) -> String {
let o = Command::new("sqlite3")
.arg(path)
.arg("PRAGMA quick_check;")
.output()
.unwrap();
if o.status.success() {
String::from_utf8_lossy(&o.stdout).trim().to_string()
} else {
format!("ERR: {}", String::from_utf8_lossy(&o.stderr).trim())
}
}
fn graphite_scalar(c: &Connection, q: &str) -> String {
let r = c.query(q).unwrap();
match r.rows.first().and_then(|row| row.first()) {
Some(graphitesql::Value::Text(t)) => String::from(t.as_str()),
Some(graphitesql::Value::Integer(i)) => i.to_string(),
other => format!("{other:?}"),
}
}
fn assert_delete_heavy_valid(tag: &str, ins: i64, del: i64) {
let path = tmp_path(tag);
let mut c = Connection::create(&path).unwrap();
c.execute("CREATE VIRTUAL TABLE f USING fts5(a);").unwrap();
for i in 1..=ins {
c.execute(&format!(
"INSERT INTO f(rowid,a) VALUES({i},'doc{i} term{i} shared word{} extra{} fill{}');",
i % 7,
i % 13,
i % 5
))
.unwrap();
}
for i in 1..=del {
c.execute(&format!("DELETE FROM f WHERE rowid={i};"))
.unwrap();
}
drop(c);
let qc = sqlite_quick_check(&path);
assert_eq!(
qc, "ok",
"sqlite quick_check rejected graphite's file for {tag}"
);
let c = Connection::open(&path).unwrap();
assert_eq!(
graphite_scalar(&c, "PRAGMA integrity_check;"),
"ok",
"graphite integrity_check not ok for {tag}"
);
let g_count = graphite_scalar(&c, "SELECT count(*) FROM f;");
let s_count = Command::new("sqlite3")
.arg(&path)
.arg("SELECT count(*) FROM f;")
.output()
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
.unwrap();
assert_eq!(g_count, s_count, "row count mismatch for {tag}");
assert_eq!(
g_count,
(ins - del).to_string(),
"wrong surviving count for {tag}"
);
let _ = std::fs::remove_file(&path);
}
#[test]
fn delete_heavy_fts5_files_are_sqlite_valid() {
if !have_fts5_sqlite() {
eprintln!("skipping: sqlite3 with FTS5 not on PATH");
return;
}
assert_delete_heavy_valid("150-100", 150, 100);
assert_delete_heavy_valid("175-120", 175, 120);
assert_delete_heavy_valid("200-150", 200, 150);
assert_delete_heavy_valid("137-133", 137, 133);
assert_delete_heavy_valid("250-249", 250, 249);
assert_delete_heavy_valid("90-89", 90, 89);
}