fn reindex_file(db_path: &str, file: &str, content: &str) -> bool {
use rusqlite::{params, OptionalExtension};
let db = match rusqlite::Connection::open(db_path) {
Ok(d) => {
let _ = d.execute_batch("PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL; PRAGMA busy_timeout=5000;");
if reliary_search::schema::open_existing_db_safe(&d).is_err() {
eprintln!("reindex: schema mismatch in {}", db_path);
return false;
}
d
}
Err(e) => {
eprintln!("reindex open {}: {}", db_path, e);
return false;
}
};
let canonical = std::fs::canonicalize(file)
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|_| file.to_string());
if reliary_search::is_known_data_ext(std::path::Path::new(&canonical)) {
eprintln!("reindex: {} is a data/config format, not indexed", file);
return false;
}
if let Err(e) = db.execute(
"INSERT OR IGNORE INTO file_map (file_path) VALUES (?1)",
params![canonical],
) {
eprintln!("reindex file_map INSERT: {}", e);
return false;
}
let file_id: i64 = match db.query_row(
"SELECT id FROM file_map WHERE file_path = ?1",
params![canonical],
|row| row.get(0),
) {
Ok(id) => id,
Err(e) => { eprintln!("reindex file_map SELECT: {}", e); return false; }
};
if let Err(e) = db.execute(
"UPDATE file_map SET is_source = ?1 WHERE id = ?2",
params![reliary_search::lazy_occurrence::is_source_like(content) as i32, file_id],
) {
eprintln!("reindex is_source: {}", e);
return false;
}
let (phrase_locations, _line_count) =
reliary_search::ingest::extract_file_phrases_for_path(content, &canonical);
let token_len = phrase_locations.len() as i64;
let content_len = content.len() as i64;
if let Err(e) = db.execute_batch("BEGIN IMMEDIATE;") {
eprintln!("reindex BEGIN: {}", e);
return false;
}
struct TxGuard<'a> {
conn: &'a rusqlite::Connection,
committed: std::cell::Cell<bool>,
}
impl Drop for TxGuard<'_> {
fn drop(&mut self) {
if !self.committed.get() {
let _ = self.conn.execute_batch("ROLLBACK;");
}
}
}
let tx_guard = TxGuard { conn: &db, committed: std::cell::Cell::new(false) };
macro_rules! fail {
($($arg:tt)*) => {{
eprintln!($($arg)*);
return false; }};
}
if let Err(e) = db.execute("DELETE FROM occurrence WHERE file_id = ?1", params![file_id]) {
fail!("reindex DELETE occurrence: {}", e);
}
if let Err(e) = db.execute("DELETE FROM block WHERE file_id = ?1", params![file_id]) {
fail!("reindex DELETE block: {}", e);
}
for legacy in ["scope_binding", "method_occurrence"] {
let exists: i64 = db
.query_row(
"SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=?1",
params![legacy],
|r| r.get(0),
)
.unwrap_or(0);
if exists > 0 {
if let Err(e) = db.execute(&format!("DELETE FROM {} WHERE file_id = ?1", legacy), params![file_id]) {
fail!("reindex DELETE {}: {}", legacy, e);
}
}
}
{
let mut affected_phrases: Vec<i64> = Vec::new();
{
let mut stmt = match db.prepare("SELECT phrase_id FROM file_phrases WHERE file_id = ?1") {
Ok(s) => s,
Err(e) => fail!("reindex prepare file_phrases: {}", e),
};
let rows = match stmt.query_map(params![file_id], |r| r.get::<_, i64>(0)) {
Ok(rows) => rows,
Err(e) => fail!("reindex query file_phrases: {}", e),
};
for row in rows.flatten() { affected_phrases.push(row); }
}
for pid in &affected_phrases {
let blob: Option<Vec<u8>> = match db.query_row(
"SELECT CAST(file_blob AS BLOB) FROM phrase_occ WHERE phrase_id = ?1",
params![pid],
|r| r.get(0),
) {
Ok(b) => Some(b),
Err(rusqlite::Error::QueryReturnedNoRows) => None,
Err(e) => fail!("reindex read blob pid={}: {}", pid, e),
};
let blob = match blob {
Some(b) => b,
None => continue,
};
if !reliary_search::schema::blob_is_well_formed(&blob) {
eprintln!("reindex: malformed blob for phrase_id={} ({} bytes) — entries may be partial", pid, blob.len());
}
let entries: Vec<(i64, u8)> = reliary_search::schema::unpack_file_blob(&blob)
.filter(|(fid, _)| *fid != file_id)
.collect();
if entries.is_empty() {
if let Err(e) = db.execute("DELETE FROM phrase_occ WHERE phrase_id = ?1", params![pid]) {
fail!("reindex DELETE phrase_occ: {}", e);
}
} else {
let new_blob = reliary_search::schema::pack_file_blob(&entries);
if let Err(e) = db.execute(
"UPDATE phrase_occ SET file_blob = ?1 WHERE phrase_id = ?2",
params![&new_blob[..], pid],
) {
fail!("reindex UPDATE phrase_occ: {}", e);
}
}
}
}
if let Err(e) = db.execute("DELETE FROM file_phrases WHERE file_id = ?1", params![file_id]) {
fail!("reindex DELETE file_phrases: {}", e);
}
if content.is_empty() {
for table in ["file_stats", "count_overflow"] {
let exists: i64 = db
.query_row(
"SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=?1",
params![table],
|r| r.get(0),
)
.unwrap_or(0);
if exists > 0 {
if let Err(e) = db.execute(&format!("DELETE FROM {} WHERE file_id = ?1", table), params![file_id]) {
fail!("reindex DELETE {}: {}", table, e);
}
}
}
if let Err(e) = db.execute("DELETE FROM file_map WHERE id = ?1", params![file_id]) {
fail!("reindex DELETE file_map: {}", e);
}
if let Err(e) = db.execute_batch("COMMIT;") {
fail!("reindex COMMIT: {}", e);
}
tx_guard.committed.set(true);
reliary_search::lazy_occurrence::invalidate_all_phrase_gens();
reliary_search::file_meta::invalidate(&canonical);
if let Err(e) = reliary_search::schema::bump_index_gen(&db) {
eprintln!("reindex bump_index_gen: {}", e);
}
return true;
}
let mut affected_ids: std::collections::HashSet<i64> = std::collections::HashSet::new();
{
let mut insert_stmt = match db.prepare("INSERT OR IGNORE INTO phrases (phrase) VALUES (?1)") {
Ok(s) => s,
Err(e) => fail!("reindex prepare phrase INSERT: {}", e),
};
let mut select_stmt = match db.prepare("SELECT id FROM phrases WHERE phrase = ?1") {
Ok(s) => s,
Err(e) => fail!("reindex prepare phrase SELECT: {}", e),
};
for phrase in phrase_locations.keys() {
if let Err(e) = insert_stmt.execute(params![phrase]) {
fail!("reindex INSERT phrase {:?}: {}", phrase, e);
}
match select_stmt.query_row(params![phrase], |row| row.get::<_, i64>(0)) {
Ok(pid) => { affected_ids.insert(pid); }
Err(e) => fail!("reindex SELECT phrase {:?}: {}", phrase, e),
}
}
}
for pid in &affected_ids {
let phrase_text: Option<String> = match db
.query_row("SELECT phrase FROM phrases WHERE id = ?1", params![pid], |r| r.get(0))
.optional()
{
Ok(v) => v,
Err(e) => { eprintln!("[reindex] phrase read failed for id {}: {}", pid, e); None }
};
let phrase_text = match phrase_text {
Some(t) => t,
None => continue,
};
let empty: Vec<(usize, u8, usize, bool, usize, u8)> = Vec::new();
let locs = phrase_locations.get(&phrase_text).unwrap_or(&empty);
let num_locs = locs.len().max(1) as u32;
let avg_zone = locs.iter().map(|(_, z, _, _, _, _)| *z as u32).sum::<u32>() / num_locs;
let is_def_any = locs.iter().any(|(_, _, _, d, _, _)| *d);
let flags = reliary_search::schema::pack_flags(if is_def_any { 1 } else { 0 }, avg_zone as i32, num_locs);
let existing: Option<Vec<u8>> = match db.query_row(
"SELECT CAST(file_blob AS BLOB) FROM phrase_occ WHERE phrase_id = ?1",
params![pid],
|r| r.get(0),
) {
Ok(b) => Some(b),
Err(rusqlite::Error::QueryReturnedNoRows) => None,
Err(e) => fail!("reindex read blob pid={}: {}", pid, e),
};
let mut merged = existing.unwrap_or_default();
let mut entry: Vec<u8> = Vec::with_capacity(3);
reliary_search::schema::encode_varint(file_id, &mut entry);
entry.push(flags[0]);
merged.extend_from_slice(&entry);
if let Err(e) = db.execute(
"INSERT INTO phrase_occ (phrase_id, file_blob) VALUES (?1, ?2)
ON CONFLICT(phrase_id) DO UPDATE SET file_blob = excluded.file_blob",
params![pid, &merged[..]],
) {
fail!("reindex phrase_occ UPSERT pid={}: {}", pid, e);
}
if let Err(e) = db.execute(
"INSERT OR IGNORE INTO file_phrases (file_id, phrase_id) VALUES (?1, ?2)",
params![file_id, pid],
) {
fail!("reindex INSERT file_phrases pid={}: {}", pid, e);
}
}
if let Err(e) = db.execute(
"INSERT INTO file_stats (file_id, token_len, content_len) VALUES (?1, ?2, ?3)
ON CONFLICT(file_id) DO UPDATE SET token_len=excluded.token_len, content_len=excluded.content_len",
params![file_id, token_len, content_len],
) {
fail!("reindex file_stats: {}", e);
}
if let Err(e) = db.execute_batch("COMMIT;") {
fail!("reindex COMMIT: {}", e);
}
tx_guard.committed.set(true);
if let Err(e) = reliary_search::lazy_tables::ensure_all_for_file_with_content(&db, file_id, content) {
eprintln!("reindex lazy_tables: {}", e);
}
reliary_search::lazy_occurrence::invalidate_all_phrase_gens();
reliary_search::file_meta::invalidate(&canonical);
if let Err(e) = reliary_search::schema::bump_index_gen(&db) {
eprintln!("reindex bump_index_gen: {}", e);
}
true
}
pub fn reindex_single_file(db_path: &str, file: &str, content: &str) -> usize {
if !std::path::Path::new(db_path).exists() {
return 0;
}
if reindex_file(db_path, file, content) {
reliary_search::ingest::extract_file_phrases_for_path(content, file).0.len()
} else {
0
}
}
#[cfg(test)]
mod v86_reindex_tests {
use super::*;
#[test]
fn reindex_refuses_data_formats_and_writes_is_source() {
let d = tempfile::tempdir().unwrap();
let root = d.path().join("c");
std::fs::create_dir_all(root.join("src")).unwrap();
std::fs::write(root.join("src/a.rs"), "pub fn alpha() {}\n").unwrap();
let db_path = root.join(".reliary/index.sqlite");
std::fs::create_dir_all(root.join(".reliary")).unwrap();
let db = rusqlite::Connection::open(&db_path).unwrap();
reliary_search::schema::create_new_db(&db).unwrap();
reliary_search::ingest::index_directory(&db, root.to_str().unwrap()).unwrap();
drop(db);
let p = db_path.to_str().unwrap();
let md = root.join("pkg.json");
std::fs::write(&md, "{\"k\": \"pub fn ghost() {}\"}\n").unwrap();
assert!(!reindex_file(p, md.to_str().unwrap(), "{\"k\": 1}\n"));
let db = rusqlite::Connection::open(&db_path).unwrap();
let n: i64 = db
.query_row("SELECT COUNT(*) FROM file_map WHERE file_path LIKE '%pkg.json'", [], |r| r.get(0))
.unwrap();
assert_eq!(n, 0, "a data format must not get a file_map row");
let md2 = root.join("notes.md");
std::fs::write(&md2, "# Notes\n\n```rust\npub fn from_md() {}\n```\n").unwrap();
reindex_file(p, md2.to_str().unwrap(), "# Notes\n\n```rust\npub fn from_md() {}\n```\n");
let is_source: i64 = db
.query_row("SELECT is_source FROM file_map WHERE file_path LIKE '%notes.md'", [], |r| r.get(0))
.unwrap();
let expected = reliary_search::lazy_occurrence::is_source_like(
"# Notes\n\n```rust\npub fn from_md() {}\n```\n",
) as i64;
assert_eq!(is_source, expected, "reindex must store the computed is_source");
}
#[test]
fn reindex_with_empty_content_removes_the_file() {
let d = tempfile::tempdir().unwrap();
let root = d.path().join("c");
std::fs::create_dir_all(root.join("src")).unwrap();
let f = root.join("src/a.rs");
std::fs::write(&f, "pub fn alpha() -> i32 { 1 }\n").unwrap();
let db_path = root.join(".reliary/index.sqlite");
std::fs::create_dir_all(root.join(".reliary")).unwrap();
let db = rusqlite::Connection::open(&db_path).unwrap();
reliary_search::schema::create_new_db(&db).unwrap();
reliary_search::ingest::index_directory(&db, root.to_str().unwrap()).unwrap();
reliary_search::lazy_occurrence::build_all_occurrence(&db).unwrap();
drop(db);
assert!(reindex_file(db_path.to_str().unwrap(), f.to_str().unwrap(), ""));
let db = rusqlite::Connection::open(&db_path).unwrap();
for (table, sql) in [
("file_map", "SELECT COUNT(*) FROM file_map"),
("occurrence", "SELECT COUNT(*) FROM occurrence"),
("file_stats", "SELECT COUNT(*) FROM file_stats"),
("file_phrases", "SELECT COUNT(*) FROM file_phrases"),
("phrase_occ", "SELECT COUNT(*) FROM phrase_occ"),
] {
let n: i64 = db.query_row(sql, [], |r| r.get(0)).unwrap();
assert_eq!(n, 0, "{table} still has rows for a deleted file");
}
}
}