#![cfg(feature = "std")]
#![cfg(feature = "fts5")]
use graphitesql::{Connection, Value};
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-scale-{}-{}-{}.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_raw(path: &str, q: &str) -> String {
let o = Command::new("sqlite3").arg(path).arg(q).output().unwrap();
assert!(
o.status.success(),
"sqlite3 failed for {q:?}: {}",
String::from_utf8_lossy(&o.stderr)
);
String::from_utf8_lossy(&o.stdout).trim_end().to_string()
}
fn sqlite_sorted(path: &str, q: &str) -> String {
let mut v: Vec<String> = sqlite_raw(path, q).lines().map(str::to_string).collect();
v.sort();
v.join("\n")
}
fn sqlite_integrity_ok(path: &str) -> bool {
Command::new("sqlite3")
.arg(path)
.arg("INSERT INTO ft(ft) VALUES('integrity-check');")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
fn spaced_values(n: usize) -> String {
let mut s = String::new();
for i in 1..=n {
if i > 1 {
s.push(',');
}
s.push_str(&format!("({},'shared')", (i as i64) * 100_000_000));
}
s
}
fn build_pair(g: &str, s: &str, values: &str) {
{
let mut c = Connection::create(g).unwrap();
c.execute("CREATE VIRTUAL TABLE ft USING fts5(body)")
.unwrap();
c.execute("BEGIN").ok();
c.execute(&format!("INSERT INTO ft(rowid,body) VALUES {values}"))
.unwrap();
c.execute("COMMIT").ok();
}
Command::new("sqlite3")
.arg(s)
.arg("CREATE VIRTUAL TABLE ft USING fts5(body);")
.output()
.unwrap();
Command::new("sqlite3")
.arg(s)
.arg(format!("INSERT INTO ft(rowid,body) VALUES {values};"))
.output()
.unwrap();
Command::new("sqlite3")
.arg(s)
.arg("INSERT INTO ft(ft) VALUES('optimize');")
.output()
.unwrap();
}
#[test]
fn dlidx_corpus_is_byte_identical_and_integrity_clean() {
if !have_fts5_sqlite() {
eprintln!("sqlite3 with FTS5 not found; skipping");
return;
}
let g = tmp_path("dlidx-g");
let s = tmp_path("dlidx-s");
let vals = spaced_values(3000);
build_pair(&g, &s, &vals);
let g_dlidx = sqlite_raw(&g, "SELECT count(*) FROM ft_data WHERE (id & (1<<36))<>0;");
assert_eq!(g_dlidx, "1", "graphite must emit exactly one dlidx page");
let g_leaves = sqlite_raw(
&g,
"SELECT count(*) FROM ft_data WHERE id>10 AND (id & (1<<36))=0;",
);
assert_eq!(g_leaves, "5", "expected a five-leaf single-term doclist");
assert!(
sqlite_integrity_ok(&g),
"sqlite integrity-check rejected graphite's dlidx file"
);
assert_eq!(sqlite_raw(&g, "PRAGMA integrity_check;"), "ok");
let data = "SELECT id||':'||quote(block) FROM ft_data ORDER BY id;";
assert_eq!(
sqlite_raw(&g, data),
sqlite_raw(&s, data),
"%_data bytes diverge from sqlite"
);
let idx = "SELECT segid||':'||quote(term)||':'||pgno FROM ft_idx ORDER BY segid, term;";
assert_eq!(
sqlite_raw(&g, idx),
sqlite_raw(&s, idx),
"%_idx bytes diverge from sqlite"
);
assert_eq!(
sqlite_raw(
&g,
"SELECT count(*), sum(rowid) FROM ft WHERE ft MATCH 'shared';"
),
format!(
"3000|{}",
(1..=3000i64).map(|i| i * 100_000_000).sum::<i64>()
)
);
let _ = std::fs::remove_file(&g);
let _ = std::fs::remove_file(&s);
}
#[test]
fn delete_half_stays_integrity_clean() {
if !have_fts5_sqlite() {
eprintln!("sqlite3 with FTS5 not found; skipping");
return;
}
let g = tmp_path("del-g");
{
let mut c = Connection::create(&g).unwrap();
c.execute("CREATE VIRTUAL TABLE ft USING fts5(body)")
.unwrap();
c.execute("BEGIN").ok();
c.execute(&format!(
"INSERT INTO ft(rowid,body) VALUES {}",
spaced_values(3000)
))
.unwrap();
c.execute("COMMIT").ok();
c.execute("DELETE FROM ft WHERE (rowid/100000000) % 2 = 0")
.unwrap();
}
assert!(
sqlite_integrity_ok(&g),
"sqlite integrity-check rejected graphite's post-DELETE file"
);
assert_eq!(sqlite_raw(&g, "PRAGMA integrity_check;"), "ok");
assert_eq!(
sqlite_raw(&g, "SELECT count(*) FROM ft WHERE ft MATCH 'shared';"),
"1500"
);
assert_eq!(
sqlite_raw(
&g,
"SELECT count(*) FROM ft WHERE ft MATCH 'shared' AND rowid=200000000;"
),
"0"
);
assert_eq!(
sqlite_raw(
&g,
"SELECT count(*) FROM ft WHERE ft MATCH 'shared' AND rowid=100000000;"
),
"1"
);
let _ = std::fs::remove_file(&g);
}
#[test]
fn optimize_and_merge_are_noops_on_dlidx_index() {
if !have_fts5_sqlite() {
eprintln!("sqlite3 with FTS5 not found; skipping");
return;
}
let g = tmp_path("opt-g");
let before;
{
let mut c = Connection::create(&g).unwrap();
c.execute("CREATE VIRTUAL TABLE ft USING fts5(body)")
.unwrap();
c.execute("BEGIN").ok();
c.execute(&format!(
"INSERT INTO ft(rowid,body) VALUES {}",
spaced_values(3000)
))
.unwrap();
c.execute("COMMIT").ok();
before = sqlite_raw(&g, "SELECT id||':'||quote(block) FROM ft_data ORDER BY id;");
c.execute("INSERT INTO ft(ft) VALUES('optimize')").unwrap();
c.execute("INSERT INTO ft(ft, rank) VALUES('merge', 4)")
.unwrap();
}
let after = sqlite_raw(&g, "SELECT id||':'||quote(block) FROM ft_data ORDER BY id;");
assert_eq!(before, after, "optimize/merge changed the single segment");
assert!(sqlite_integrity_ok(&g));
let _ = std::fs::remove_file(&g);
}
#[test]
fn graphite_reads_sqlite_dlidx_file() {
if !have_fts5_sqlite() {
eprintln!("sqlite3 with FTS5 not found; skipping");
return;
}
let s = tmp_path("read-s");
Command::new("sqlite3")
.arg(&s)
.arg("CREATE VIRTUAL TABLE ft USING fts5(body);")
.output()
.unwrap();
Command::new("sqlite3")
.arg(&s)
.arg(format!(
"INSERT INTO ft(rowid,body) VALUES {};",
spaced_values(3000)
))
.output()
.unwrap();
Command::new("sqlite3")
.arg(&s)
.arg("INSERT INTO ft(ft) VALUES('optimize');")
.output()
.unwrap();
assert_eq!(
sqlite_raw(&s, "SELECT count(*) FROM ft_data WHERE (id & (1<<36))<>0;"),
"1"
);
let c = Connection::open(&s).unwrap();
let rows = c
.query("SELECT count(*) FROM ft WHERE ft MATCH 'shared'")
.unwrap();
let count = match rows.rows.first().and_then(|r| r.first()) {
Some(Value::Integer(i)) => *i,
other => panic!("expected an integer count, got {other:?}"),
};
assert_eq!(count, 3000, "graphite miscounted sqlite's dlidx doclist");
assert_eq!(
sqlite_sorted(&s, "SELECT count(*) FROM ft WHERE ft MATCH 'shared'"),
"3000"
);
let _ = std::fs::remove_file(&s);
}