pub mod lifecycle;
mod pcache2;
use std::collections::{BTreeMap, BTreeSet, HashMap, VecDeque};
use std::error::Error;
use std::fmt::{Display, Formatter};
use std::fs::{File, OpenOptions};
use std::io::{BufRead, BufReader, Seek, SeekFrom, Write};
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
use std::sync::mpsc::{self, Receiver, SyncSender};
#[cfg(debug_assertions)]
use std::sync::Barrier;
use std::sync::Once;
use std::sync::{Arc, Condvar, Mutex};
use std::thread::{self, JoinHandle};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use fathomdb_embedder::EmbedderEvent;
#[cfg(feature = "operator")]
use fathomdb_embedder::MeanRecomputeTrigger;
use fathomdb_embedder_api::{Embedder, EmbedderError as RuntimeEmbedderError, EmbedderIdentity};
use fathomdb_query::compile_text_query;
use fathomdb_schema::{
migrate_with_event_sink, MigrationError as SchemaMigrationError, MigrationStepReport,
LOCK_SUFFIX, MIGRATIONS, SCHEMA_VERSION,
};
#[cfg(feature = "operator")]
use fathomdb_schema::CANONICAL_TABLES;
use jsonschema::JSONSchema;
use rusqlite::{params, Connection, OptionalExtension};
use serde_json::Value;
#[cfg(feature = "operator")]
use sha2::Digest;
#[cfg(not(feature = "operator"))]
use sha2::Digest as _;
use sha2::Sha256;
use sqlite_vec::sqlite3_vec_init;
#[cfg(unix)]
use std::os::unix::fs::OpenOptionsExt;
const DEFAULT_EMBEDDER_NAME: &str = "fathomdb-bge-small-en-v1.5";
const DEFAULT_EMBEDDER_REVISION: &str = "5c38ec7c405ec4b44b94cc5a9bb96e735b38267a";
const DEFAULT_EMBEDDER_DIMENSION: u32 = 384;
const BGE_SMALL_EMBEDDER_NAME: &str = "fathomdb-bge-small-en-v1.5";
const DEFAULT_SLOW_THRESHOLD_MS: u64 = 100;
const DEFAULT_VECTOR_PROFILE: &str = "default";
const DEFAULT_VECTOR_PARTITION: &str = "vector_default";
const VECTOR_EQUIVALENCE_PROBE_FIXTURE: &str = include_str!("vector_equivalence_probes.txt");
const VECTOR_EQUIVALENCE_L2_EPSILON: f32 = 1e-5;
const VECTOR_EQUIVALENCE_P1_FLIP_FLOOR: u64 = 0;
const VECTOR_EQUIVALENCE_VERDICT_CACHE_KEY: &str = "vector_equivalence_verified_fingerprint";
const VECTOR_EQUIVALENCE_FINGERPRINT_RECIPE: &str = "fathomdb-veq-verdict-v1";
#[cfg(feature = "operator")]
const REBUILD_DRAIN_TIMEOUT_MS: u64 = 30_000;
const LIFECYCLE_DRAIN_TIMEOUT_MS: u64 = 30_000;
const SEARCH_INDEX_TOKENIZER_SCHEMA_VERSION: u32 = 11;
const SEARCH_INDEX_TOKENIZER_REPROJECT_MARKER_KEY: &str =
"search_index_tokenizer_reproject_complete";
const EDGE_TEMPORAL_EPOCH_SCHEMA_VERSION: u32 = 23;
const EDGE_VECTOR_PRUNE_MARKER_KEY: &str = "tc33_edge_vector_prune_complete";
const DEFAULT_PROVENANCE_ROW_CAP: u64 = 1_000_000;
const ERASURE_WAL_TRUNCATE_ATTEMPTS: u32 = 5;
const ERASURE_WAL_TRUNCATE_BACKOFF_MS: u64 = 25;
const REDACTED_STABLE_ID: &str = "[erased]";
const ERASURE_AUDIT_COLLECTIONS: &[&str] = &["excise_source_audit", "excise_record_audit"];
const ERASURE_PENDING_REDACTION_COLLECTION: &str = "erasure_pending_redaction";
#[cfg(feature = "operator")]
fn is_erasure_bookkeeping_collection(collection: &str) -> bool {
collection == ERASURE_PENDING_REDACTION_COLLECTION
|| ERASURE_AUDIT_COLLECTIONS.contains(&collection)
}
const PROJECTION_CURSOR_KEY: &str = "projection_cursor";
const PROJECTION_WORKERS: usize = 2;
const DEFAULT_EMBED_TIMEOUT_MS: u64 = 30_000;
const DEFAULT_EMBED_CIRCUIT_THRESHOLD: u64 = 8;
const PROJECTION_COMMIT_BATCH: usize = 16;
const PROJECTION_INFLIGHT_LIMIT: usize = PROJECTION_WORKERS * PROJECTION_COMMIT_BATCH;
const PROJECTION_SCAN_FETCH: usize = PROJECTION_INFLIGHT_LIMIT;
const DEFAULT_PROJECTION_RETRY_DELAYS_MS: [u64; 3] = [1_000, 4_000, 16_000];
const EDGE_FACT_KIND: &str = "edge_fact";
const READER_POOL_SIZE: usize = 8;
const READER_LOOKASIDE_SLOT_SIZE: std::os::raw::c_int = 1200;
const READER_LOOKASIDE_SLOT_COUNT: std::os::raw::c_int = 500;
pub struct Engine {
path: PathBuf,
next_cursor: AtomicU64,
closed: AtomicBool,
lock: Mutex<Option<File>>,
connection: Mutex<Option<Connection>>,
reader_pool: ReaderWorkerPool,
counters: lifecycle::Counters,
subscribers: Arc<lifecycle::SubscriberRegistry>,
profiling_enabled: Arc<AtomicBool>,
slow_threshold_ms: Arc<AtomicU64>,
runtime_embedder: Option<Arc<dyn Embedder>>,
runtime_embedder_identity: EmbedderIdentity,
projection_runtime: ProjectionRuntime,
provenance_row_cap: AtomicU64,
#[allow(clippy::vec_box)]
profile_contexts: Mutex<Vec<Box<ProfileContext>>>,
#[allow(dead_code)]
reader_lookaside_rcs: Vec<i32>,
telemetry: Mutex<Option<TelemetrySink>>,
telemetry_enabled: AtomicBool,
dense_disabled: AtomicBool,
dense_disabled_reason: Mutex<Option<String>>,
vector_equivalence_refusals: AtomicU64,
#[cfg(debug_assertions)]
force_next_commit_failure: AtomicBool,
}
struct TelemetrySink {
path: PathBuf,
base: Instant,
nonce: u64,
seq: u64,
last_query_id: Option<String>,
}
#[derive(Clone, Debug)]
struct ProjectionJob {
cursor: u64,
kind: String,
body: String,
}
#[derive(Debug, Default)]
struct ProjectionRuntimeState {
active_jobs: usize,
queued_jobs: usize,
frozen: bool,
pending_scan: bool,
stopping: bool,
in_flight: BTreeSet<u64>,
}
struct ProjectionRuntimeShared {
path: PathBuf,
embedder: Option<Arc<dyn Embedder>>,
embedder_identity: EmbedderIdentity,
subscribers: Arc<lifecycle::SubscriberRegistry>,
state: Mutex<ProjectionRuntimeState>,
state_cvar: Condvar,
queue: Mutex<VecDeque<ProjectionJob>>,
queue_cvar: Condvar,
retry_delays_ms: Mutex<Vec<u64>>,
embed_timeout_ms: AtomicU64,
embed_serialize: Mutex<()>,
live_embed_threads: Arc<AtomicU64>,
embed_circuit_open: AtomicBool,
embed_circuit_threshold: AtomicU64,
mean_accumulator: Mutex<Option<MeanAccumulator>>,
pending_events: Mutex<Vec<EmbedderEvent>>,
commit_gate: Mutex<()>,
search_limit_override: AtomicUsize,
recency_reweight_enabled: AtomicBool,
importance_reweight_enabled: AtomicBool,
vector_stage_only_for_test: AtomicBool,
#[cfg(debug_assertions)]
force_recompute_failure: AtomicBool,
#[cfg(debug_assertions)]
force_projection_commit_failure: AtomicUsize,
#[cfg(debug_assertions)]
projection_commit_failure_pause: Mutex<Option<(Arc<Barrier>, Arc<Barrier>)>>,
#[cfg(debug_assertions)]
projection_stop_ack: Mutex<Option<Arc<Barrier>>>,
}
impl std::fmt::Debug for ProjectionRuntimeShared {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ProjectionRuntimeShared")
.field("path", &self.path)
.field("embedder_identity", &self.embedder_identity)
.finish_non_exhaustive()
}
}
#[derive(Debug)]
struct ProjectionRuntime {
shared: Arc<ProjectionRuntimeShared>,
dispatcher: Mutex<Option<JoinHandle<()>>>,
workers: Mutex<Vec<JoinHandle<()>>>,
}
impl std::fmt::Debug for Engine {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Engine")
.field("path", &self.path)
.field("closed", &self.closed.load(Ordering::SeqCst))
.field("runtime_embedder_identity", &self.runtime_embedder_identity)
.finish_non_exhaustive()
}
}
#[derive(Debug)]
struct ProfileContext {
subscribers: Arc<lifecycle::SubscriberRegistry>,
profiling_enabled: Arc<AtomicBool>,
slow_threshold_ms: Arc<AtomicU64>,
}
struct ReaderWorkerPool {
senders: Vec<SyncSender<ReaderRequest>>,
handles: Mutex<Option<Vec<JoinHandle<()>>>>,
next: AtomicUsize,
shutdown: AtomicBool,
live_workers: Arc<AtomicUsize>,
}
impl std::fmt::Debug for ReaderWorkerPool {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ReaderWorkerPool")
.field("worker_count", &self.senders.len())
.field("live_workers", &self.live_workers.load(Ordering::Relaxed))
.field("shutdown", &self.shutdown.load(Ordering::Relaxed))
.finish()
}
}
enum ReaderRequest {
Search {
compiled: fathomdb_query::CompiledQuery,
query_vector: Option<String>,
query_vector_bin: Option<String>,
search_limit: usize,
filter: Option<Box<SearchFilter>>,
recency_enabled: bool,
importance_enabled: bool,
vector_stage_only: bool,
raw_query: Box<str>,
rerank_depth: usize,
use_graph_arm: bool,
alpha: f64,
pool_n: usize,
explain: bool,
view: ReadView,
respond: SyncSender<ReaderResponse>,
},
GetById {
logical_ids: Vec<String>,
view: ReadView,
respond: SyncSender<rusqlite::Result<Vec<Option<NodeRecord>>>>,
},
ReadCollection {
collection: String,
after_id: Option<i64>,
limit: usize,
respond: SyncSender<rusqlite::Result<Vec<OpStoreRow>>>,
},
ReadList {
kind: String,
predicates: Vec<Predicate>,
limit: usize,
view: ReadView,
respond: SyncSender<rusqlite::Result<Vec<NodeRecord>>>,
},
GraphNeighbors {
root_logical_id: String,
depth: u32,
direction: TraversalDirection,
view: ReadView,
respond: SyncSender<rusqlite::Result<Vec<NodeRecord>>>,
},
CrossedBoundarySince {
since: i64,
view: ReadView,
respond: SyncSender<rusqlite::Result<Vec<BoundaryCrossing>>>,
},
SearchExpand {
search_hits: Vec<SearchHit>,
depth: u32,
respond: SyncSender<rusqlite::Result<SearchExpandResult>>,
},
#[doc(hidden)]
ExplainGraphNeighbors {
root_logical_id: String,
depth: u32,
direction: TraversalDirection,
respond: SyncSender<rusqlite::Result<Vec<String>>>,
},
Shutdown,
#[cfg(debug_assertions)]
LookasideStatus {
respond: SyncSender<i32>,
},
#[cfg(debug_assertions)]
CacheStatus {
snapshot_label: String,
respond: SyncSender<(String, i32, i32, i32)>,
},
#[cfg(debug_assertions)]
SecureDeleteStatus {
respond: SyncSender<i64>,
},
}
type ReaderResponse = Result<
(u64, Option<SoftFallback>, Vec<SearchHit>, GraphFrontierStats, Option<Explanation>),
SearchReaderError,
>;
enum SearchReaderError {
Sqlite(rusqlite::Error),
InvalidFilter(String),
}
impl From<rusqlite::Error> for SearchReaderError {
fn from(err: rusqlite::Error) -> Self {
SearchReaderError::Sqlite(err)
}
}
#[cfg(debug_assertions)]
#[doc(hidden)]
#[derive(Clone, Debug)]
pub struct CacheStatusReply {
pub worker_idx: usize,
pub snapshot_label: String,
pub cache_hit: i32,
pub cache_miss: i32,
pub cache_used_bytes: i32,
}
const READER_WORKER_CHANNEL_CAPACITY: usize = 4;
impl ReaderWorkerPool {
fn new(connections: Vec<Connection>) -> Self {
let live_workers = Arc::new(AtomicUsize::new(0));
let mut senders = Vec::with_capacity(connections.len());
let mut handles = Vec::with_capacity(connections.len());
for (idx, connection) in connections.into_iter().enumerate() {
let (tx, rx) = mpsc::sync_channel::<ReaderRequest>(READER_WORKER_CHANNEL_CAPACITY);
let live = Arc::clone(&live_workers);
let handle = thread::Builder::new()
.name(format!("fathomdb-reader-{idx}"))
.spawn(move || reader_worker_loop(connection, rx, live))
.expect("spawn reader worker");
senders.push(tx);
handles.push(handle);
}
Self {
senders,
handles: Mutex::new(Some(handles)),
next: AtomicUsize::new(0),
shutdown: AtomicBool::new(false),
live_workers,
}
}
fn worker_count(&self) -> usize {
self.senders.len()
}
fn live_count(&self) -> usize {
self.live_workers.load(Ordering::SeqCst)
}
#[cfg(debug_assertions)]
fn lookaside_used_per_worker(&self) -> Vec<i32> {
let mut results = Vec::with_capacity(self.senders.len());
for sender in &self.senders {
let (tx, rx) = mpsc::sync_channel::<i32>(1);
if sender.send(ReaderRequest::LookasideStatus { respond: tx }).is_ok() {
results.push(rx.recv().unwrap_or(-1));
} else {
results.push(-1);
}
}
results
}
#[cfg(debug_assertions)]
fn cache_status_per_worker(&self, snapshot_label: &str) -> Vec<CacheStatusReply> {
let mut results = Vec::with_capacity(self.senders.len());
for (idx, sender) in self.senders.iter().enumerate() {
let (tx, rx) = mpsc::sync_channel::<(String, i32, i32, i32)>(1);
let request = ReaderRequest::CacheStatus {
snapshot_label: snapshot_label.to_string(),
respond: tx,
};
if sender.send(request).is_ok() {
if let Ok((label, hit, miss, used)) = rx.recv() {
results.push(CacheStatusReply {
worker_idx: idx,
snapshot_label: label,
cache_hit: hit,
cache_miss: miss,
cache_used_bytes: used,
});
continue;
}
}
results.push(CacheStatusReply {
worker_idx: idx,
snapshot_label: snapshot_label.to_string(),
cache_hit: -1,
cache_miss: -1,
cache_used_bytes: -1,
});
}
results
}
#[cfg(debug_assertions)]
fn secure_delete_per_worker(&self) -> Vec<i64> {
let mut results = Vec::with_capacity(self.senders.len());
for sender in &self.senders {
let (tx, rx) = mpsc::sync_channel::<i64>(1);
if sender.send(ReaderRequest::SecureDeleteStatus { respond: tx }).is_ok() {
results.push(rx.recv().unwrap_or(-1));
} else {
results.push(-1);
}
}
results
}
#[allow(clippy::result_large_err)]
fn dispatch(&self, request: ReaderRequest) -> Result<(), ReaderRequest> {
if self.shutdown.load(Ordering::Relaxed) {
return Err(request);
}
let n = self.senders.len();
if n == 0 {
return Err(request);
}
let idx = self.next.fetch_add(1, Ordering::Relaxed) % n;
self.senders[idx].send(request).map_err(|err| err.0)
}
fn shutdown(&self) {
if self.shutdown.swap(true, Ordering::SeqCst) {
return;
}
for sender in &self.senders {
let _ = sender.send(ReaderRequest::Shutdown);
}
if let Ok(mut slot) = self.handles.lock() {
if let Some(handles) = slot.take() {
for handle in handles {
let _ = handle.join();
}
}
}
}
}
impl Drop for ReaderWorkerPool {
fn drop(&mut self) {
self.shutdown();
}
}
fn reader_worker_loop(
mut connection: Connection,
rx: Receiver<ReaderRequest>,
live_workers: Arc<AtomicUsize>,
) {
live_workers.fetch_add(1, Ordering::SeqCst);
struct LiveGuard(Arc<AtomicUsize>);
impl Drop for LiveGuard {
fn drop(&mut self) {
self.0.fetch_sub(1, Ordering::SeqCst);
}
}
let _guard = LiveGuard(live_workers);
while let Ok(request) = rx.recv() {
match request {
ReaderRequest::Shutdown => break,
ReaderRequest::Search {
compiled,
query_vector,
query_vector_bin,
search_limit,
filter,
recency_enabled,
importance_enabled,
vector_stage_only,
raw_query,
rerank_depth,
use_graph_arm,
alpha,
pool_n,
explain,
view,
respond,
} => {
let result = read_search_in_tx(
&mut connection,
&compiled,
query_vector.as_deref(),
query_vector_bin.as_deref(),
search_limit,
filter.as_deref(),
recency_enabled,
importance_enabled,
vector_stage_only,
&raw_query,
rerank_depth,
use_graph_arm,
alpha,
pool_n,
explain,
view,
);
let _ = respond.send(result);
}
ReaderRequest::GetById { logical_ids, view, respond } => {
let result = read_get_by_id_in_tx(&mut connection, &logical_ids, &view);
let _ = respond.send(result);
}
ReaderRequest::ReadCollection { collection, after_id, limit, respond } => {
let result = read_collection_in_tx(&mut connection, &collection, after_id, limit);
let _ = respond.send(result);
}
ReaderRequest::ReadList { kind, predicates, limit, view, respond } => {
let result = read_list_in_tx(&mut connection, &kind, &predicates, limit, &view);
let _ = respond.send(result);
}
ReaderRequest::GraphNeighbors { root_logical_id, depth, direction, view, respond } => {
let result = graph_neighbors_in_tx(
&mut connection,
&root_logical_id,
depth,
direction,
&view,
);
let _ = respond.send(result);
}
ReaderRequest::CrossedBoundarySince { since, view, respond } => {
let result = crossed_boundary_since_in_tx(&mut connection, since, &view);
let _ = respond.send(result);
}
ReaderRequest::SearchExpand { search_hits, depth, respond } => {
let result = search_expand_in_tx(&mut connection, &search_hits, depth);
let _ = respond.send(result);
}
ReaderRequest::ExplainGraphNeighbors { root_logical_id, depth, direction, respond } => {
let result = explain_graph_neighbors_in_tx(
&mut connection,
&root_logical_id,
depth,
direction,
);
let _ = respond.send(result);
}
#[cfg(debug_assertions)]
ReaderRequest::LookasideStatus { respond } => {
let _ = respond.send(read_lookaside_used_hiwtr(&connection));
}
#[cfg(debug_assertions)]
ReaderRequest::CacheStatus { snapshot_label, respond } => {
let (hit, miss, used) = read_cache_status(&connection);
let _ = respond.send((snapshot_label, hit, miss, used));
}
#[cfg(debug_assertions)]
ReaderRequest::SecureDeleteStatus { respond } => {
let value: i64 =
connection.query_row("PRAGMA secure_delete", [], |r| r.get(0)).unwrap_or(-1);
let _ = respond.send(value);
}
}
}
uninstall_profile_callback(&connection);
drop(connection);
}
mod reader_search_hook {
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Mutex;
static ARMED: AtomicBool = AtomicBool::new(false);
#[allow(clippy::type_complexity)]
static HOOK: Mutex<Option<Box<dyn Fn() + Send>>> = Mutex::new(None);
pub(crate) fn arm(hook: Box<dyn Fn() + Send>) {
*HOOK.lock().expect("reader-search hook mutex") = Some(hook);
ARMED.store(true, Ordering::SeqCst);
}
pub(crate) fn clear() {
ARMED.store(false, Ordering::SeqCst);
*HOOK.lock().expect("reader-search hook mutex") = None;
}
pub(crate) fn fire() {
if !ARMED.load(Ordering::SeqCst) {
return;
}
ARMED.store(false, Ordering::SeqCst);
let hook = HOOK.lock().expect("reader-search hook mutex").take();
if let Some(hook) = hook {
hook();
}
}
}
#[doc(hidden)]
pub fn arm_reader_search_hook_for_test(hook: Box<dyn Fn() + Send>) {
reader_search_hook::arm(hook);
}
#[doc(hidden)]
pub fn clear_reader_search_hook_for_test() {
reader_search_hook::clear();
}
impl ProjectionRuntime {
fn new(
path: PathBuf,
embedder: Option<Arc<dyn Embedder>>,
embedder_identity: EmbedderIdentity,
mean_already_pinned: bool,
subscribers: Arc<lifecycle::SubscriberRegistry>,
) -> Self {
let mc_required = identity_requires_mean_centering(&embedder_identity);
let mean_accumulator = if mc_required && !mean_already_pinned {
Some(MeanAccumulator::new(embedder_identity.dimension as usize))
} else {
None
};
let shared = Arc::new(ProjectionRuntimeShared {
path,
embedder,
embedder_identity,
subscribers,
state: Mutex::new(ProjectionRuntimeState::default()),
state_cvar: Condvar::new(),
queue: Mutex::new(VecDeque::new()),
queue_cvar: Condvar::new(),
retry_delays_ms: Mutex::new(DEFAULT_PROJECTION_RETRY_DELAYS_MS.to_vec()),
embed_timeout_ms: AtomicU64::new(DEFAULT_EMBED_TIMEOUT_MS),
embed_serialize: Mutex::new(()),
live_embed_threads: Arc::new(AtomicU64::new(0)),
embed_circuit_open: AtomicBool::new(false),
embed_circuit_threshold: AtomicU64::new(DEFAULT_EMBED_CIRCUIT_THRESHOLD),
mean_accumulator: Mutex::new(mean_accumulator),
pending_events: Mutex::new(Vec::new()),
commit_gate: Mutex::new(()),
search_limit_override: AtomicUsize::new(SEARCH_RERANK_LIMIT),
recency_reweight_enabled: AtomicBool::new(false),
importance_reweight_enabled: AtomicBool::new(false),
vector_stage_only_for_test: AtomicBool::new(false),
#[cfg(debug_assertions)]
force_recompute_failure: AtomicBool::new(false),
#[cfg(debug_assertions)]
force_projection_commit_failure: AtomicUsize::new(0),
#[cfg(debug_assertions)]
projection_commit_failure_pause: Mutex::new(None),
#[cfg(debug_assertions)]
projection_stop_ack: Mutex::new(None),
});
let dispatcher_shared = Arc::clone(&shared);
let dispatcher = thread::spawn(move || projection_dispatcher_loop(dispatcher_shared));
let mut workers = Vec::with_capacity(PROJECTION_WORKERS);
for _ in 0..PROJECTION_WORKERS {
let worker_shared = Arc::clone(&shared);
workers.push(thread::spawn(move || projection_worker_loop(worker_shared)));
}
Self { shared, dispatcher: Mutex::new(Some(dispatcher)), workers: Mutex::new(workers) }
}
fn notify_new_work(&self) {
if let Ok(mut state) = self.shared.state.lock() {
state.pending_scan = true;
self.shared.state_cvar.notify_all();
}
}
fn set_frozen(&self, frozen: bool) {
if let Ok(mut state) = self.shared.state.lock() {
state.frozen = frozen;
if !frozen {
state.pending_scan = true;
}
self.shared.state_cvar.notify_all();
}
}
fn wait_for_idle(&self, timeout_ms: u64) -> bool {
let deadline = Instant::now() + Duration::from_millis(timeout_ms);
let mut state = match self.shared.state.lock() {
Ok(state) => state,
Err(_) => return false,
};
loop {
if state.active_jobs == 0 && state.queued_jobs == 0 {
drop(state);
if !database_has_pending_projection_work(&self.shared.path).unwrap_or(true) {
return true;
}
state = match self.shared.state.lock() {
Ok(state) => state,
Err(_) => return false,
};
}
let now = Instant::now();
if now >= deadline {
return false;
}
let wait = deadline.saturating_duration_since(now);
let Ok((next_state, _)) = self.shared.state_cvar.wait_timeout(state, wait) else {
return false;
};
state = next_state;
}
}
fn set_retry_delays_for_test(&self, delays_ms: &[u64]) {
if let Ok(mut delays) = self.shared.retry_delays_ms.lock() {
*delays = delays_ms.to_vec();
}
}
#[cfg(debug_assertions)]
fn force_next_projection_commit_failure_for_test(&self) {
self.shared.force_projection_commit_failure.store(1, Ordering::SeqCst);
}
#[cfg(debug_assertions)]
fn force_next_projection_storage_failure_for_test(&self) {
self.shared.force_projection_commit_failure.store(2, Ordering::SeqCst);
}
#[cfg(debug_assertions)]
fn pause_projection_commit_failure_cleanup_for_test(
&self,
reported: Arc<Barrier>,
release: Arc<Barrier>,
) {
*self
.shared
.projection_commit_failure_pause
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner()) = Some((reported, release));
}
#[cfg(debug_assertions)]
fn acknowledge_projection_stop_for_test(&self, acknowledged: Arc<Barrier>) {
*self.shared.projection_stop_ack.lock().unwrap_or_else(|poisoned| poisoned.into_inner()) =
Some(acknowledged);
}
fn set_embed_timeout_ms_for_test(&self, timeout_ms: u64) {
self.shared.embed_timeout_ms.store(timeout_ms, Ordering::Relaxed);
}
fn set_embed_circuit_threshold_for_test(&self, threshold: u64) {
self.shared.embed_circuit_threshold.store(threshold, Ordering::Relaxed);
}
fn embed_circuit_open_for_test(&self) -> bool {
self.shared.embed_circuit_open.load(Ordering::Relaxed)
}
fn stop(&self) {
if let Ok(mut state) = self.shared.state.lock() {
if state.stopping {
return;
}
state.stopping = true;
state.pending_scan = false;
self.shared.state_cvar.notify_all();
}
#[cfg(debug_assertions)]
if let Some(acknowledged) = self
.shared
.projection_stop_ack
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.take()
{
acknowledged.wait();
}
if let Ok(mut queue) = self.shared.queue.lock() {
queue.clear();
self.shared.queue_cvar.notify_all();
}
if let Ok(mut dispatcher) = self.dispatcher.lock() {
if let Some(handle) = dispatcher.take() {
let _ = handle.join();
}
}
if let Ok(mut workers) = self.workers.lock() {
for handle in workers.drain(..) {
let _ = handle.join();
}
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct OpenReport {
pub schema_version_before: u32,
pub schema_version_after: u32,
pub migration_steps: Vec<MigrationStepReport>,
pub embedder_warmup_ms: u64,
pub query_backend: &'static str,
pub default_embedder: EmbedderIdentity,
pub embedder_download_ms: Option<u64>,
pub embedder_events: Vec<EmbedderEvent>,
pub embedder_mean_centering_required: bool,
pub embedder_mean_vec_pinned: bool,
pub dense_disabled: bool,
pub dense_disabled_reason: Option<String>,
}
#[derive(Debug)]
pub struct OpenedEngine {
pub engine: Engine,
pub report: OpenReport,
}
#[derive(Clone, Debug)]
struct LoaderInfo {
download_ms: Option<u64>,
events: Vec<EmbedderEvent>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct WriteReceipt {
pub cursor: u64,
pub row_cursors: Vec<u64>,
pub dangling_edge_endpoints: u64,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SoftFallback {
pub branch: SoftFallbackBranch,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SoftFallbackBranch {
Vector,
Text,
TextEdge,
GraphArm,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum IdSpaceKind {
Logical,
Content,
Passage,
}
impl IdSpaceKind {
#[must_use]
pub fn prefix(self) -> &'static str {
match self {
Self::Logical => "l:",
Self::Content => "h:",
Self::Passage => "p:",
}
}
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Logical => "logical",
Self::Content => "content",
Self::Passage => "passage",
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct IdSpace {
pub space: IdSpaceKind,
pub value: String,
}
impl IdSpace {
pub fn logical(value: impl Into<String>) -> Self {
Self { space: IdSpaceKind::Logical, value: value.into() }
}
pub fn content(value: impl Into<String>) -> Self {
Self { space: IdSpaceKind::Content, value: value.into() }
}
pub fn passage(value: impl Into<String>) -> Self {
Self { space: IdSpaceKind::Passage, value: value.into() }
}
#[must_use]
pub fn to_prefixed(&self) -> String {
format!("{}{}", self.space.prefix(), self.value)
}
#[must_use]
pub fn parse(s: &str) -> Option<Self> {
if let Some(v) = s.strip_prefix("l:") {
Some(Self::logical(v))
} else if let Some(v) = s.strip_prefix("h:") {
Some(Self::content(v))
} else {
s.strip_prefix("p:").map(Self::passage)
}
}
}
impl std::fmt::Display for IdSpace {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}{}", self.space.prefix(), self.value)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct SearchHit {
pub id: IdSpace,
pub write_cursor: u64,
pub kind: String,
pub body: String,
pub score: f64,
pub branch: SoftFallbackBranch,
pub source_id: Option<String>,
pub ce_score: Option<f64>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct GraphFrontierStats {
pub seeds_considered: u32,
pub seeds_resolved: u32,
pub frontier_nonempty: bool,
pub graph_candidates_emitted: u32,
}
impl GraphFrontierStats {
pub fn resolved_seed_rate(&self) -> f64 {
if self.seeds_considered == 0 {
0.0
} else {
f64::from(self.seeds_resolved) / f64::from(self.seeds_considered)
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct NodeRecord {
pub logical_id: String,
pub kind: String,
pub body: String,
pub write_cursor: u64,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct ReadView {
pub include_superseded: bool,
pub include_inactive: bool,
pub include_out_of_window: bool,
pub valid_as_of: Option<i64>,
}
impl ReadView {
fn now_param(&self) -> Option<i64> {
if self.include_out_of_window {
return None;
}
Some(self.valid_as_of.unwrap_or_else(current_epoch_seconds))
}
fn existence_sql(&self, alias: &str) -> String {
let mut sql = String::new();
if !self.include_superseded {
sql.push_str(&format!(" AND {alias}.superseded_at IS NULL"));
}
if !self.include_inactive {
sql.push_str(&format!(" AND {alias}.state = 'active'"));
}
sql
}
fn validity_sql(&self, alias: &str, now_idx: usize) -> String {
if self.include_out_of_window {
return String::new();
}
format!(
" AND ({alias}.valid_from IS NULL OR {alias}.valid_from <= ?{now_idx}) \
AND ({alias}.valid_until IS NULL OR {alias}.valid_until > ?{now_idx})"
)
}
fn node_sql(&self, alias: &str, now_idx: usize) -> String {
format!("{}{}", self.existence_sql(alias), self.validity_sql(alias, now_idx))
}
fn freeze(self) -> FrozenView {
let resolved = self.valid_as_of.unwrap_or_else(current_epoch_seconds);
let now = if self.include_out_of_window { None } else { Some(resolved) };
FrozenView { view: self, now, edge_now: resolved }
}
fn reject_existence_relaxation_on_search(&self) -> Result<(), EngineError> {
let relaxed = match (self.include_superseded, self.include_inactive) {
(true, true) => "include_superseded + include_inactive",
(true, false) => "include_superseded",
(false, true) => "include_inactive",
(false, false) => return Ok(()),
};
Err(EngineError::InvalidArgument {
msg: format!(
"ReadView.{relaxed} is not supported on the search path; search hydrates from \
projection indexes that are not version-complete, so only the validity axis \
(valid_as_of / include_out_of_window) is honoured. Use read_list for history."
),
})
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct BoundaryCrossing {
pub node: NodeRecord,
pub became_valid_at: Option<i64>,
pub became_invalid_at: Option<i64>,
}
#[derive(Clone, Copy, Debug)]
struct FrozenView {
view: ReadView,
now: Option<i64>,
edge_now: i64,
}
impl FrozenView {
fn now_param(&self) -> Option<i64> {
self.now
}
fn edge_now(&self) -> i64 {
self.edge_now
}
fn validity_sql(&self, alias: &str, now_idx: usize) -> String {
self.view.validity_sql(alias, now_idx)
}
}
static CLOCK_READS: AtomicU64 = AtomicU64::new(0);
#[doc(hidden)]
#[must_use]
pub fn clock_reads_for_test() -> u64 {
CLOCK_READS.load(Ordering::Relaxed)
}
fn current_epoch_seconds() -> i64 {
CLOCK_READS.fetch_add(1, Ordering::Relaxed);
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| i64::try_from(d.as_secs()).unwrap_or(i64::MAX))
.unwrap_or(0)
}
fn edge_validity_sql(alias: &str, now_idx: usize) -> String {
format!(" AND ({alias}.t_invalid IS NULL OR {alias}.t_invalid > ?{now_idx})")
}
fn iso8601_to_epoch_seconds(connection: &Connection, raw: &str) -> Option<i64> {
if !is_iso8601_shape(raw) {
return None;
}
connection
.query_row(
"SELECT CAST(strftime('%s', ?1) AS INTEGER) \
WHERE strftime('%Y-%m-%d', substr(?1, 1, 10)) IS substr(?1, 1, 10)",
params![raw],
|r| r.get::<_, Option<i64>>(0),
)
.ok()
.flatten()
}
fn is_iso8601_shape(s: &str) -> bool {
let b = s.as_bytes();
let d = |c: u8| c.is_ascii_digit();
if b.len() < 10 {
return false;
}
if !(d(b[0])
&& d(b[1])
&& d(b[2])
&& d(b[3])
&& b[4] == b'-'
&& d(b[5])
&& d(b[6])
&& b[7] == b'-'
&& d(b[8])
&& d(b[9]))
{
return false;
}
if b.len() == 10 {
return true; }
if b[10] != b'T' && b[10] != b' ' {
return false;
}
if b.len() < 19 {
return false;
}
if !(d(b[11])
&& d(b[12])
&& b[13] == b':'
&& d(b[14])
&& d(b[15])
&& b[16] == b':'
&& d(b[17])
&& d(b[18]))
{
return false;
}
let mut i = 19;
if i < b.len() && b[i] == b'.' {
i += 1;
let start = i;
while i < b.len() && d(b[i]) {
i += 1;
}
if i == start {
return false; }
}
if i == b.len() {
return true; }
match b[i] {
b'Z' => i += 1,
b'+' | b'-' => {
i += 1;
if i + 2 > b.len() || !d(b[i]) || !d(b[i + 1]) {
return false;
}
i += 2;
if i < b.len() && b[i] == b':' {
i += 1;
}
if i + 2 > b.len() || !d(b[i]) || !d(b[i + 1]) {
return false;
}
i += 2;
}
_ => return false,
}
i == b.len() }
fn epoch_seconds_to_iso8601(connection: &Connection, epoch: i64) -> Option<String> {
connection
.query_row("SELECT strftime('%Y-%m-%dT%H:%M:%SZ', ?1, 'unixepoch')", params![epoch], |r| {
r.get::<_, Option<String>>(0)
})
.ok()
.flatten()
}
const MIN_RENDERABLE_EPOCH: i64 = -62_167_219_200; const MAX_RENDERABLE_EPOCH: i64 = 253_402_300_799;
fn reject_unrenderable_edge_epoch(field: &str, value: Option<i64>) -> Result<(), EngineError> {
if let Some(ts) = value {
if !(MIN_RENDERABLE_EPOCH..=MAX_RENDERABLE_EPOCH).contains(&ts) {
return Err(EngineError::InvalidArgument {
msg: format!(
"edge field `{field}` = {ts} is outside the epoch-seconds range SQLite can \
render to ISO-8601 ([{MIN_RENDERABLE_EPOCH}, {MAX_RENDERABLE_EPOCH}], i.e. \
years 0000..=9999). REJECTED rather than stored: such an epoch renders to a \
silent NULL (or a nonsensical out-of-range instant) on the consolidation \
wire, and a NULL `t_invalid` reads as \"still valid\" — resurrecting an \
invalidated edge."
),
});
}
}
Ok(())
}
fn json_type_name(value: &Value) -> &'static str {
match value {
Value::Null => "null",
Value::Bool(_) => "boolean",
Value::Number(_) => "number",
Value::String(_) => "string",
Value::Array(_) => "array",
Value::Object(_) => "object",
}
}
fn normalize_extractor_timestamp(
connection: &Connection,
field: &str,
raw: Option<&Value>,
) -> Result<Option<i64>, EngineError> {
match raw {
None | Some(Value::Null) => Ok(None),
Some(Value::String(text)) => match iso8601_to_epoch_seconds(connection, text) {
Some(epoch) => Ok(Some(epoch)),
None => Err(EngineError::InvalidArgument {
msg: format!(
"extractor edge field `{field}` must be a valid, calendar-real ISO-8601 \
timestamp; got {text:?}, which either `strftime('%s', ?)` resolves to NULL \
or fails the calendar round-trip (a shape-valid but impossible DAY like \
`2025-02-30` that SQLite would silently ROLL OVER to a different instant). \
REJECTED rather than stored: a NULL `t_invalid` reads as \"still valid\" and \
a rolled-over date stores the WRONG instant — both breach the hard-reject \
contract. Use JSON null for \"unknown\"."
),
}),
},
Some(other) => Err(EngineError::InvalidArgument {
msg: format!(
"extractor edge field `{field}` must be an ISO-8601 string or JSON null; got a \
JSON {kind} ({other}). The `fathomdb.extract.v1` wire format carries ISO-8601 at \
this boundary — INTEGER epoch seconds are the STORAGE representation, not the \
wire one. REJECTED rather than coerced to NULL, which reads as \"still valid\".",
kind = json_type_name(other)
),
}),
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct OpStoreRow {
pub id: i64,
pub collection: String,
pub record_key: String,
pub op_kind: String,
pub payload: String,
pub schema_id: Option<String>,
pub write_cursor: u64,
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct SearchResult {
pub projection_cursor: u64,
pub soft_fallback: Option<SoftFallback>,
pub results: Vec<SearchHit>,
pub explanation: Option<Explanation>,
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive] pub struct Explanation {
pub trace: QueryTrace,
pub per_hit: Vec<PerHitExplain>,
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct QueryTrace {
pub query_chars: u32,
pub k: u32,
pub rerank_depth: u32,
pub pool_n: u32,
pub alpha: f64,
pub use_graph_arm: bool,
pub recency: bool,
pub embedder_id: String,
pub ce_active: bool,
pub vector_hits: u32,
pub text_hits: u32,
pub graph_hits: u32,
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct PerHitExplain {
pub id: u64,
pub arm: SoftFallbackBranch,
pub vector_rank: Option<u32>,
pub text_rank: Option<u32>,
pub graph_rank: Option<u32>,
pub fused_score: f64,
pub ce_score: Option<f64>,
pub blended: f64,
pub importance: Option<f64>,
pub confidence: Option<f64>,
}
#[derive(Clone, Debug, PartialEq)]
pub enum ScalarValue {
Text(String),
Integer(i64),
Bool(bool),
}
#[derive(Clone, Debug, PartialEq)]
pub enum ComparisonOp {
Gt,
Gte,
Lt,
Lte,
}
const PREDICATE_PATH_ALLOWLIST: &[&str] =
&["$.status", "$.priority", "$.tags", "$.kind", "$.created_at", "$.action_kind"];
#[derive(Clone, Debug, PartialEq)]
pub enum Predicate {
JsonPathEq { path: String, value: ScalarValue },
JsonPathCompare { path: String, op: ComparisonOp, value: ScalarValue },
}
impl Predicate {
pub fn json_path_eq(path: impl Into<String>, value: ScalarValue) -> Result<Self, EngineError> {
let path = path.into();
if !PREDICATE_PATH_ALLOWLIST.contains(&path.as_str()) {
return Err(EngineError::InvalidFilter {
reason: format!("path '{path}' is not in the predicate path allowlist"),
});
}
Ok(Self::JsonPathEq { path, value })
}
pub fn json_path_compare(
path: impl Into<String>,
op: ComparisonOp,
value: ScalarValue,
) -> Result<Self, EngineError> {
let path = path.into();
if !PREDICATE_PATH_ALLOWLIST.contains(&path.as_str()) {
return Err(EngineError::InvalidFilter {
reason: format!("path '{path}' is not in the predicate path allowlist"),
});
}
Ok(Self::JsonPathCompare { path, op, value })
}
fn path(&self) -> &str {
match self {
Self::JsonPathEq { path, .. } => path.as_str(),
Self::JsonPathCompare { path, .. } => path.as_str(),
}
}
fn to_sql_clause(&self, param_idx: usize) -> String {
let path = self.path();
match self {
Self::JsonPathEq { value, .. } => match value {
ScalarValue::Bool(_) => format!(
"json_extract(body, '{path}') = ?{param_idx} \
AND json_type(body, '{path}') IN ('true', 'false')"
),
ScalarValue::Integer(_) => format!(
"json_extract(body, '{path}') = ?{param_idx} \
AND json_type(body, '{path}') = 'integer'"
),
ScalarValue::Text(_) => {
format!("json_extract(body, '{path}') = ?{param_idx}")
}
},
Self::JsonPathCompare { op, value, .. } => {
let op_str = match op {
ComparisonOp::Gt => ">",
ComparisonOp::Gte => ">=",
ComparisonOp::Lt => "<",
ComparisonOp::Lte => "<=",
};
match value {
ScalarValue::Bool(_) => format!(
"json_extract(body, '{path}') {op_str} ?{param_idx} \
AND json_type(body, '{path}') IN ('true', 'false')"
),
ScalarValue::Integer(_) => format!(
"json_extract(body, '{path}') {op_str} ?{param_idx} \
AND json_type(body, '{path}') = 'integer'"
),
ScalarValue::Text(_) => format!(
"json_extract(body, '{path}') {op_str} ?{param_idx} \
AND json_type(body, '{path}') = 'text'"
),
}
}
}
}
fn bind_value(&self) -> rusqlite::types::Value {
let value = match self {
Self::JsonPathEq { value, .. } => value,
Self::JsonPathCompare { value, .. } => value,
};
match value {
ScalarValue::Text(s) => rusqlite::types::Value::Text(s.clone()),
ScalarValue::Integer(i) => rusqlite::types::Value::Integer(*i),
ScalarValue::Bool(b) => rusqlite::types::Value::Integer(i64::from(*b)),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TraversalDirection {
Outgoing,
Incoming,
Both,
}
#[derive(Clone, Debug)]
pub struct SearchExpandResult {
pub search_hits: Vec<SearchHit>,
pub expanded: Vec<(NodeRecord, u32)>,
pub all_logical_ids: Vec<String>,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[non_exhaustive]
pub struct SearchFilter {
pub source_type: Option<String>,
pub kind: Option<String>,
pub created_after: Option<i64>,
pub status: Option<String>,
pub attributes: Vec<(String, String)>,
}
impl SearchFilter {
fn is_unfiltered(&self) -> bool {
self.source_type.is_none()
&& self.kind.is_none()
&& self.created_after.is_none()
&& self.status.is_none()
&& self.attributes.is_empty()
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum FilterTerm {
SourceType(String),
Kind(String),
CreatedAfter(i64),
Status(String),
Json(Predicate),
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Filter {
pub terms: Vec<FilterTerm>,
}
impl From<&SearchFilter> for Filter {
fn from(sf: &SearchFilter) -> Self {
let mut terms = Vec::new();
if let Some(s) = &sf.source_type {
terms.push(FilterTerm::SourceType(s.clone()));
}
if let Some(k) = &sf.kind {
terms.push(FilterTerm::Kind(k.clone()));
}
if let Some(c) = sf.created_after {
terms.push(FilterTerm::CreatedAfter(c));
}
if let Some(s) = &sf.status {
terms.push(FilterTerm::Status(s.clone()));
}
Filter { terms }
}
}
impl Filter {
pub fn to_search_filter(&self) -> Result<SearchFilter, EngineError> {
let mut sf = SearchFilter::default();
for term in &self.terms {
match term {
FilterTerm::SourceType(s) => sf.source_type = Some(s.clone()),
FilterTerm::Kind(k) => sf.kind = Some(k.clone()),
FilterTerm::CreatedAfter(c) => sf.created_after = Some(*c),
FilterTerm::Status(s) => sf.status = Some(s.clone()),
FilterTerm::Json(_) => {
return Err(EngineError::InvalidFilter {
reason: "arbitrary json-path predicate not supported on search_filtered; \
it would require a post-KNN json_extract that defeats the \
indexed pre-KNN filter (ADR-0.8.11 D3 no-demotion guarantee)"
.to_string(),
});
}
}
}
Ok(sf)
}
fn lower_for_read_list(&self, kind: &str) -> Result<Option<Vec<Predicate>>, EngineError> {
let mut preds = Vec::new();
for term in &self.terms {
match term {
FilterTerm::Json(p) => preds.push(p.clone()),
FilterTerm::Status(s) => {
preds.push(Predicate::json_path_eq("$.status", ScalarValue::Text(s.clone()))?);
}
FilterTerm::CreatedAfter(b) => {
preds.push(Predicate::json_path_compare(
"$.created_at",
ComparisonOp::Gte,
ScalarValue::Integer(*b),
)?);
}
FilterTerm::Kind(k) => {
if k != kind {
return Ok(None);
}
}
FilterTerm::SourceType(s) => {
match resolve_source_type(kind) {
Ok(resolved) if resolved == s.as_str() => {}
_ => return Ok(None),
}
}
}
}
Ok(Some(preds))
}
#[doc(hidden)]
pub fn to_search_filter_for_test(&self) -> Result<SearchFilter, EngineError> {
self.to_search_filter()
}
#[doc(hidden)]
pub fn lower_for_read_list_for_test(
&self,
kind: &str,
) -> Result<Option<Vec<Predicate>>, EngineError> {
self.lower_for_read_list(kind)
}
}
#[derive(Clone, Debug)]
pub struct ExtractDocument {
pub source_doc_id: String,
pub body: String,
}
#[derive(Clone, Debug, Default)]
pub struct IngestWithExtractorReceipt {
pub nodes_written: u64,
pub edges_written: u64,
pub docs_processed: u64,
}
#[derive(Clone, Debug)]
pub struct ConsolidateAxis {
pub subject_logical_id: String,
pub relation: String,
}
#[derive(Clone, Debug)]
pub struct ConsolidateCandidateEdge {
pub edge_ref: String,
pub body: Option<String>,
pub t_valid: Option<i64>,
pub t_invalid: Option<i64>,
pub confidence: Option<f64>,
pub source_doc_id: Option<String>,
pub extractor_model_id: Option<String>,
}
#[derive(Clone, Debug, Default)]
pub struct ConsolidateReceipt {
pub clusters_processed: u64,
pub edges_examined: u64,
pub edges_kept: u64,
pub edges_invalidated: u64,
pub edges_superseded: u64,
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct SourceId(String);
impl SourceId {
pub const ENGINE_PREFIX: &'static str = "_engine:";
pub const LEGACY_PRE_0_8_20: &'static str = "_legacy:pre-0.8.20";
pub fn new(id: impl Into<String>) -> Result<Self, EngineError> {
let id = id.into();
if id.trim().is_empty() || id.starts_with('_') {
return Err(EngineError::WriteValidation);
}
Ok(Self(id))
}
pub(crate) fn engine_derived(role: &str) -> Self {
Self(format!("{}{role}", Self::ENGINE_PREFIX))
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
#[must_use]
pub fn into_string(self) -> String {
self.0
}
}
impl AsRef<str> for SourceId {
fn as_ref(&self) -> &str {
&self.0
}
}
impl Display for SourceId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl TryFrom<String> for SourceId {
type Error = EngineError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl TryFrom<&str> for SourceId {
type Error = EngineError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::new(value)
}
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq)]
pub enum PreparedWrite {
Node {
kind: String,
body: String,
source_id: SourceId,
logical_id: Option<String>,
state: InitialState,
reason: Option<String>,
valid_from: Option<i64>,
valid_until: Option<i64>,
},
Edge {
kind: String,
from: String,
to: String,
source_id: SourceId,
logical_id: Option<String>,
body: Option<String>,
t_valid: Option<i64>,
t_invalid: Option<i64>,
confidence: Option<f64>,
extractor_model_id: Option<String>,
temporal_fallback: Option<bool>,
},
OpStore {
collection: String,
record_key: String,
schema_id: Option<String>,
body: String,
},
AdminSchema {
name: String,
kind: String,
schema_json: String,
retention_json: String,
},
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RowKind {
Leaf,
Coverage,
Graph,
}
impl RowKind {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
RowKind::Leaf => "leaf",
RowKind::Coverage => "coverage",
RowKind::Graph => "graph",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum LifecycleState {
Pending,
Active,
Deleted,
Purged,
}
impl LifecycleState {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
LifecycleState::Pending => "pending",
LifecycleState::Active => "active",
LifecycleState::Deleted => "deleted",
LifecycleState::Purged => "purged",
}
}
#[must_use]
pub fn from_str_opt(value: &str) -> Option<Self> {
match value {
"pending" => Some(LifecycleState::Pending),
"active" => Some(LifecycleState::Active),
"deleted" => Some(LifecycleState::Deleted),
"purged" => Some(LifecycleState::Purged),
_ => None,
}
}
#[must_use]
pub fn legal_next_states(self) -> Vec<LifecycleState> {
[
LifecycleState::Pending,
LifecycleState::Active,
LifecycleState::Deleted,
LifecycleState::Purged,
]
.into_iter()
.filter(|&to| is_legal_transition_move(self, to))
.collect()
}
}
#[must_use]
fn is_legal_transition_move(from: LifecycleState, to: LifecycleState) -> bool {
matches!(
(from, to),
(LifecycleState::Pending, LifecycleState::Active)
| (LifecycleState::Pending, LifecycleState::Deleted)
| (LifecycleState::Active, LifecycleState::Deleted)
| (LifecycleState::Deleted, LifecycleState::Active)
)
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Default)]
pub enum InitialState {
Pending,
#[default]
Active,
}
impl InitialState {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
InitialState::Pending => "pending",
InitialState::Active => "active",
}
}
#[must_use]
pub fn to_lifecycle_state(self) -> LifecycleState {
match self {
InitialState::Pending => LifecycleState::Pending,
InitialState::Active => LifecycleState::Active,
}
}
#[must_use]
pub fn from_create_str(value: &str) -> Option<Self> {
match value {
"pending" => Some(InitialState::Pending),
"active" => Some(InitialState::Active),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Bm25fFieldWeights {
pub kind: f64,
pub body: f64,
pub status: f64,
}
impl Default for Bm25fFieldWeights {
fn default() -> Self {
Self { kind: 1.0, body: 1.0, status: 1.0 }
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Bm25fQueryPlan {
pub weights: Bm25fFieldWeights,
pub b: f64,
pub k1: f64,
}
impl Default for Bm25fQueryPlan {
fn default() -> Self {
Self { weights: Bm25fFieldWeights::default(), b: 0.75, k1: 1.2 }
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct CounterSnapshot {
pub queries: u64,
pub writes: u64,
pub write_rows: u64,
pub errors_by_code: BTreeMap<String, u64>,
pub admin_ops: u64,
pub cache_hit: u64,
pub cache_miss: u64,
}
pub use lifecycle::Subscription;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CorruptionDetail {
pub kind: CorruptionKind,
pub stage: OpenStage,
pub locator: CorruptionLocator,
pub recovery_hint: RecoveryHint,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CorruptionKind {
WalReplayFailure,
HeaderMalformed,
SchemaInconsistent,
EmbedderIdentityDrift,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum OpenStage {
WalReplay,
HeaderProbe,
SchemaProbe,
EmbedderIdentity,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CorruptionLocator {
FileOffset { offset: u64 },
PageId { page: u32 },
TableRow { table: &'static str, rowid: i64 },
Vec0ShadowRow { partition: &'static str, rowid: i64 },
MigrationStep { from: u32, to: u32 },
OpaqueSqliteError { sqlite_extended_code: i32 },
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct RecoveryHint {
pub code: &'static str,
pub doc_anchor: &'static str,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum EngineOpenError {
DatabaseLocked {
holder_pid: Option<u32>,
},
Corruption(CorruptionDetail),
IncompatibleSchemaVersion {
seen: u32,
supported: u32,
},
MigrationError {
schema_version_before: u32,
schema_version_current: u32,
step_id: u32,
},
EmbedderIdentityMismatch {
stored: EmbedderIdentity,
supplied: EmbedderIdentity,
},
EmbedderDimensionMismatch {
stored: u32,
supplied: u32,
},
Embedder(RuntimeEmbedderError),
Io {
message: String,
},
}
#[derive(Clone)]
pub enum EmbedderChoice {
Default,
Caller(Arc<dyn Embedder>),
None,
}
impl Display for EngineOpenError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::DatabaseLocked { holder_pid } => match holder_pid {
Some(pid) => write!(f, "database is locked by process {pid}"),
None => write!(f, "database is locked by another engine instance"),
},
Self::Corruption(detail) => {
write!(
f,
"engine corruption at {:?} stage: {}",
detail.stage, detail.recovery_hint.code
)
}
Self::IncompatibleSchemaVersion { seen, supported } => write!(
f,
"database schema version {seen} is incompatible with supported version {supported}"
),
Self::MigrationError {
schema_version_before,
schema_version_current,
step_id,
} => write!(
f,
"schema migration failed at step {step_id}; schema version remained between {schema_version_before} and {schema_version_current}"
),
Self::EmbedderIdentityMismatch { stored, supplied } => write!(
f,
"embedder identity mismatch: stored {}@{}, supplied {}@{}",
stored.name, stored.revision, supplied.name, supplied.revision,
),
Self::EmbedderDimensionMismatch { stored, supplied } => write!(
f,
"embedder vector dimension mismatch: stored {stored}, supplied {supplied}",
),
Self::Embedder(err) => match err {
RuntimeEmbedderError::Timeout => write!(f, "embedder timeout during open"),
RuntimeEmbedderError::Failed { message } => {
write!(f, "embedder failure during open: {message}")
}
},
Self::Io { message } => write!(f, "database I/O error: {message}"),
}
}
}
impl Error for EngineOpenError {}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum EngineError {
Storage,
Projection,
Vector,
Embedder,
EmbedderNotConfigured,
KindNotVectorIndexed,
EmbedderDimensionMismatch {
expected: u32,
actual: u32,
},
Scheduler,
OpStore,
WriteValidation,
SchemaValidation,
Overloaded,
Closing,
Extractor,
Consolidator,
InvalidFilter {
reason: String,
},
InvalidArgument {
msg: String,
},
VectorEquivalenceMismatch {
reason: String,
},
IllegalTransition {
from_state: LifecycleState,
to_state: LifecycleState,
legal: Vec<LifecycleState>,
},
NotLifecycleAddressable {
id_space: IdSpaceKind,
},
ErasureIncomplete {
stage: String,
detail: String,
},
ProjectionDestructive {
name: String,
delta: String,
},
}
impl Display for EngineError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Storage => write!(f, "storage error"),
Self::Projection => write!(f, "projection error"),
Self::Vector => write!(f, "vector error"),
Self::Embedder => write!(f, "embedder error"),
Self::EmbedderNotConfigured => write!(f, "embedder is not configured"),
Self::KindNotVectorIndexed => write!(f, "kind is not configured for vector indexing"),
Self::EmbedderDimensionMismatch { expected, actual } => {
write!(f, "embedder dimension mismatch: expected {expected}, actual {actual}")
}
Self::Scheduler => write!(f, "scheduler error"),
Self::OpStore => write!(f, "op-store error"),
Self::WriteValidation => write!(f, "write validation error"),
Self::SchemaValidation => write!(f, "schema validation error"),
Self::Overloaded => write!(f, "engine overloaded"),
Self::Closing => write!(f, "engine is closing"),
Self::Extractor => write!(f, "extractor error"),
Self::Consolidator => write!(f, "consolidator error"),
Self::InvalidFilter { reason } => write!(f, "invalid filter: {reason}"),
Self::InvalidArgument { msg } => write!(f, "invalid argument: {msg}"),
Self::VectorEquivalenceMismatch { reason } => {
write!(f, "vector-equivalence self-check failed; dense retrieval refused: {reason}")
}
Self::IllegalTransition { from_state, to_state, legal } => {
let legal_list = legal.iter().map(|s| s.as_str()).collect::<Vec<_>>().join(", ");
write!(
f,
"illegal lifecycle transition {} -> {}; legal targets from {}: [{}]",
from_state.as_str(),
to_state.as_str(),
from_state.as_str(),
legal_list,
)
}
Self::NotLifecycleAddressable { id_space } => write!(
f,
"id space {:?} ({}) is not lifecycle-addressable; only the logical (l:) space is",
id_space,
id_space.prefix(),
),
Self::ErasureIncomplete { stage, detail } => write!(
f,
"erasure incomplete at stage '{stage}': the rows were deleted but the erasure \
could not be completed at rest ({detail})",
),
Self::ProjectionDestructive { name, delta } => write!(
f,
"configure_projections refused a destructive change to projection '{name}' \
without an explicit drop ({delta}); re-issue with drop: [\"{name}\"] to rebuild",
),
}
}
}
impl EngineError {
fn stable_code(&self) -> &'static str {
match self {
Self::Storage => "StorageError",
Self::Projection => "ProjectionError",
Self::Vector => "VectorError",
Self::Embedder => "EmbedderError",
Self::EmbedderNotConfigured => "EmbedderNotConfiguredError",
Self::KindNotVectorIndexed => "KindNotVectorIndexedError",
Self::EmbedderDimensionMismatch { .. } => "EmbedderDimensionMismatchError",
Self::Scheduler => "SchedulerError",
Self::OpStore => "OpStoreError",
Self::WriteValidation => "WriteValidationError",
Self::SchemaValidation => "SchemaValidationError",
Self::Overloaded => "OverloadedError",
Self::Closing => "ClosingError",
Self::Extractor => "ExtractorError",
Self::Consolidator => "ConsolidatorError",
Self::InvalidFilter { .. } => "InvalidFilterError",
Self::InvalidArgument { .. } => "InvalidArgumentError",
Self::VectorEquivalenceMismatch { .. } => "VectorEquivalenceMismatchError",
Self::IllegalTransition { .. } => "IllegalTransitionError",
Self::NotLifecycleAddressable { .. } => "NotLifecycleAddressableError",
Self::ErasureIncomplete { .. } => "ErasureIncompleteError",
Self::ProjectionDestructive { .. } => "ProjectionDestructiveError",
}
}
}
impl Error for EngineError {}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct CheckIntegrityOpts {
pub quick: bool,
pub full: bool,
pub round_trip: bool,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Section {
Clean,
Findings(Vec<Finding>),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Finding {
pub code: &'static str,
pub stage: &'static str,
pub locator: CorruptionLocator,
pub doc_anchor: &'static str,
pub detail: String,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct IntegrityReport {
pub physical: Section,
pub logical: Section,
pub semantic: Section,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SafeExportArtifact {
pub export_path: PathBuf,
pub manifest_path: PathBuf,
pub manifest_sha256: String,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TraceReport {
pub source_ref: String,
pub events: Vec<TraceEvent>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TraceEvent {
pub write_cursor: u64,
pub kind: String,
pub table: &'static str,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RebuildKind {
Projections,
Vec0,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RebuildReport {
pub kind: RebuildKind,
pub rows_invalidated: u64,
pub rows_rebuilt: u64,
pub projection_cursor_after: u64,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ExciseReport {
pub source_ref: String,
pub nodes_excised: u64,
pub edges_excised: u64,
pub projections_invalidated: u64,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum ProjectionRole {
Filterable,
Rankable,
Searchable,
}
impl ProjectionRole {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
ProjectionRole::Filterable => "filterable",
ProjectionRole::Rankable => "rankable",
ProjectionRole::Searchable => "searchable",
}
}
#[must_use]
pub fn from_str_opt(value: &str) -> Option<Self> {
match value {
"filterable" => Some(ProjectionRole::Filterable),
"rankable" => Some(ProjectionRole::Rankable),
"searchable" => Some(ProjectionRole::Searchable),
_ => None,
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ProjectionFts {
pub tokenizer: Option<String>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum DenseReadiness {
Ready,
Embedding,
}
impl DenseReadiness {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
DenseReadiness::Ready => "ready",
DenseReadiness::Embedding => "embedding",
}
}
#[must_use]
pub fn from_str_opt(value: &str) -> Option<Self> {
match value {
"ready" => Some(DenseReadiness::Ready),
"embedding" => Some(DenseReadiness::Embedding),
_ => None,
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ProjectionVector {
pub embedder: Option<String>,
pub dense_readiness: Option<DenseReadiness>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ProjectionSpec {
pub name: String,
pub roles: BTreeSet<ProjectionRole>,
pub fts: Option<ProjectionFts>,
pub vector: Option<ProjectionVector>,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ProjectionDelta {
pub built: Vec<String>,
pub dropped: Vec<String>,
pub deferred: Vec<String>,
pub unchanged: bool,
pub vector_unsupported_kinds: Vec<String>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ExciseRecordReport {
pub collection: String,
pub record_digest: String,
pub records_excised: u64,
pub state_rows_excised: u64,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum VerifyEmbedderStatus {
Match,
IdentityMismatch,
DimensionMismatch,
BothMismatch,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct VerifyEmbedderReport {
pub stored_identity: String,
pub stored_dimension: u32,
pub supplied_identity: String,
pub supplied_dimension: u32,
pub status: VerifyEmbedderStatus,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SchemaObject {
pub name: String,
pub sql: String,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DumpSchemaReport {
pub user_version: u32,
pub tables: Vec<SchemaObject>,
pub indexes: Vec<SchemaObject>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TableRowCount {
pub name: String,
pub rows: u64,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DumpRowCountsReport {
pub counts: Vec<TableRowCount>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct OrphanProvenanceSource {
pub source_id: Option<String>,
pub rows: u64,
pub governed_rows: u64,
pub reserved: bool,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct OrphanProvenanceReport {
pub sources: Vec<OrphanProvenanceSource>,
pub total_rows: u64,
pub unerasable_rows: u64,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DumpProfileReport {
pub embedder_identity: String,
pub embedder_dimension: u32,
pub vectorized_kinds: Vec<String>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct MeanRecomputeReport {
pub dim: u32,
pub old_doc_count: u64,
pub doc_count_requantized: u64,
pub drift_cos_before: f32,
pub mean_was_pinned: bool,
pub elapsed_ms: u64,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TruncateWalStatus {
Done,
Busy,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TruncateWalReport {
pub status: TruncateWalStatus,
pub busy: u32,
pub log_frames: u32,
pub checkpointed_frames: u32,
}
impl Drop for Engine {
fn drop(&mut self) {
let _ = self.close();
}
}
impl Engine {
pub fn open(path: impl Into<PathBuf>) -> Result<OpenedEngine, EngineOpenError> {
Self::open_with_embedder_and_subscriber(
path,
default_embedder_identity(),
None,
None,
None,
&mut |_| {},
)
}
pub fn open_with_choice(
path: impl Into<PathBuf>,
choice: EmbedderChoice,
) -> Result<OpenedEngine, EngineOpenError> {
match choice {
EmbedderChoice::Default => Self::open_default_embedder(path),
EmbedderChoice::Caller(embedder) => {
let identity = embedder.identity();
Self::open_with_embedder_and_subscriber(
path,
identity,
Some(embedder),
None,
None,
&mut |_| {},
)
}
EmbedderChoice::None => Self::open_with_embedder_and_subscriber(
path,
default_embedder_identity(),
None,
None,
None,
&mut |_| {},
),
}
}
#[cfg(feature = "default-embedder")]
fn open_default_embedder(path: impl Into<PathBuf>) -> Result<OpenedEngine, EngineOpenError> {
use std::time::Instant as DownloadInstant;
let download_start = DownloadInstant::now();
let weights = fathomdb_embedder::loader::load_pinned_default_embedder().map_err(|err| {
EngineOpenError::Embedder(RuntimeEmbedderError::Failed {
message: format!("default embedder loader: {err}"),
})
})?;
let events = weights.events.clone();
let download_ms = if weights.bytes_downloaded > 0 {
Some(u64::try_from(download_start.elapsed().as_millis()).unwrap_or(u64::MAX))
} else {
None
};
let embedder =
fathomdb_embedder::CandleBgeEmbedder::new_from_weights(weights).map_err(|err| {
EngineOpenError::Embedder(RuntimeEmbedderError::Failed {
message: format!("default embedder construct: {err}"),
})
})?;
let embedder: Arc<dyn Embedder> = Arc::new(embedder);
let identity = embedder.identity();
let loader_info = LoaderInfo { download_ms, events };
Self::open_with_embedder_and_subscriber(
path,
identity,
Some(embedder),
Some(loader_info),
None,
&mut |_| {},
)
}
#[cfg(not(feature = "default-embedder"))]
fn open_default_embedder(_path: impl Into<PathBuf>) -> Result<OpenedEngine, EngineOpenError> {
Err(EngineOpenError::Embedder(RuntimeEmbedderError::Failed {
message: "EmbedderChoice::Default requires the `default-embedder` Cargo feature"
.to_string(),
}))
}
pub fn open_with_migration_event_sink(
path: impl Into<PathBuf>,
mut emit_migration_event: impl FnMut(&MigrationStepReport),
) -> Result<OpenedEngine, EngineOpenError> {
Self::open_with_embedder_and_subscriber(
path,
default_embedder_identity(),
None,
None,
None,
&mut emit_migration_event,
)
}
#[cfg(debug_assertions)]
#[doc(hidden)]
pub fn open_with_migrations_for_test(
path: impl Into<PathBuf>,
migrations: &'static [fathomdb_schema::Migration],
mut emit_migration_event: impl FnMut(&MigrationStepReport),
) -> Result<OpenedEngine, EngineOpenError> {
Self::open_with_migrations(
path,
migrations,
default_embedder_identity(),
None,
None,
&mut emit_migration_event,
None,
)
}
#[doc(hidden)]
pub fn open_with_subscriber_for_test(
path: impl Into<PathBuf>,
subscriber: Arc<dyn lifecycle::Subscriber>,
) -> Result<OpenedEngine, EngineOpenError> {
Self::open_with_embedder_and_subscriber(
path,
default_embedder_identity(),
None,
None,
Some(subscriber),
&mut |_| {},
)
}
#[doc(hidden)]
pub fn open_without_embedder_for_test(
path: impl Into<PathBuf>,
) -> Result<OpenedEngine, EngineOpenError> {
Self::open_with_embedder_and_subscriber(
path,
default_embedder_identity(),
None,
None,
None,
&mut |_| {},
)
}
#[doc(hidden)]
pub fn open_with_embedder_for_test(
path: impl Into<PathBuf>,
embedder: Arc<dyn Embedder>,
) -> Result<OpenedEngine, EngineOpenError> {
let identity = embedder.identity();
Self::open_with_embedder_and_subscriber(
path,
identity,
Some(embedder),
None,
None,
&mut |_| {},
)
}
fn open_with_embedder_and_subscriber(
path: impl Into<PathBuf>,
embedder_identity: EmbedderIdentity,
runtime_embedder: Option<Arc<dyn Embedder>>,
loader_info: Option<LoaderInfo>,
initial_subscriber: Option<Arc<dyn lifecycle::Subscriber>>,
emit_migration_event: &mut impl FnMut(&MigrationStepReport),
) -> Result<OpenedEngine, EngineOpenError> {
Self::open_with_migrations(
path,
MIGRATIONS,
embedder_identity,
runtime_embedder,
loader_info,
emit_migration_event,
initial_subscriber,
)
}
fn open_with_migrations(
path: impl Into<PathBuf>,
migrations: &'static [fathomdb_schema::Migration],
embedder_identity: EmbedderIdentity,
runtime_embedder: Option<Arc<dyn Embedder>>,
loader_info: Option<LoaderInfo>,
emit_migration_event: &mut impl FnMut(&MigrationStepReport),
initial_subscriber: Option<Arc<dyn lifecycle::Subscriber>>,
) -> Result<OpenedEngine, EngineOpenError> {
let canonical_path = canonical_database_path(&path.into())?;
let lock = acquire_lock(&canonical_path)?;
let open_result = Self::open_locked(
canonical_path.clone(),
migrations,
&embedder_identity,
emit_migration_event,
);
match open_result {
Ok((connection, readers, mut report, reader_lookaside_rcs)) => {
if let Some(info) = loader_info {
if info.download_ms.is_some() {
report.embedder_download_ms = info.download_ms;
}
if !info.events.is_empty() {
report.embedder_events = info.events;
}
}
let veq = run_vector_equivalence_probe(
&connection,
runtime_embedder.as_deref(),
&embedder_identity,
report.embedder_mean_vec_pinned,
);
report.dense_disabled = veq.dense_disabled;
report.dense_disabled_reason = veq.reason.clone();
let next_cursor = load_next_cursor(&connection);
let subscribers = Arc::new(lifecycle::SubscriberRegistry::new());
let profiling_enabled = Arc::new(AtomicBool::new(false));
let slow_threshold_ms = Arc::new(AtomicU64::new(DEFAULT_SLOW_THRESHOLD_MS));
let mut profile_contexts: Vec<Box<ProfileContext>> = Vec::new();
let projection_runtime = ProjectionRuntime::new(
canonical_path.clone(),
runtime_embedder.clone(),
embedder_identity.clone(),
report.embedder_mean_vec_pinned,
Arc::clone(&subscribers),
);
install_profile_callback(
&connection,
&subscribers,
&profiling_enabled,
&slow_threshold_ms,
&mut profile_contexts,
);
for reader in &readers {
install_profile_callback(
reader,
&subscribers,
&profiling_enabled,
&slow_threshold_ms,
&mut profile_contexts,
);
}
let opened = OpenedEngine {
engine: Self {
path: canonical_path.clone(),
next_cursor: AtomicU64::new(next_cursor),
closed: AtomicBool::new(false),
lock: Mutex::new(Some(lock)),
connection: Mutex::new(Some(connection)),
reader_pool: ReaderWorkerPool::new(readers),
counters: lifecycle::Counters::new(),
subscribers,
profiling_enabled,
slow_threshold_ms,
runtime_embedder,
runtime_embedder_identity: embedder_identity,
projection_runtime,
provenance_row_cap: AtomicU64::new(DEFAULT_PROVENANCE_ROW_CAP),
profile_contexts: Mutex::new(profile_contexts),
reader_lookaside_rcs,
telemetry: Mutex::new(None),
telemetry_enabled: AtomicBool::new(false),
dense_disabled: AtomicBool::new(veq.dense_disabled),
dense_disabled_reason: Mutex::new(veq.reason),
vector_equivalence_refusals: AtomicU64::new(0),
#[cfg(debug_assertions)]
force_next_commit_failure: AtomicBool::new(false),
},
report,
};
if let Some(subscriber) = initial_subscriber {
opened.engine.subscribers.attach_persistent(subscriber);
}
if database_has_pending_projection_work(&canonical_path).unwrap_or(false) {
opened.engine.projection_runtime.notify_new_work();
}
Ok(opened)
}
Err(err) => {
if let Some(subscriber) = initial_subscriber {
emit_open_error_event(&subscriber, &err);
}
drop(lock);
Err(err)
}
}
}
fn open_locked(
path: PathBuf,
migrations: &'static [fathomdb_schema::Migration],
embedder_identity: &EmbedderIdentity,
emit_migration_event: &mut impl FnMut(&MigrationStepReport),
) -> Result<(Connection, Vec<Connection>, OpenReport, Vec<i32>), EngineOpenError> {
init_perf_experiments_runtime();
register_sqlite_vec_extension();
let mut connection = Connection::open(&path)
.map_err(|err| map_open_sqlite_error(err, OpenStage::HeaderProbe))?;
probe_database_header(&connection)?;
probe_open_integrity(&connection)?;
probe_wal_sidecar(&path)?;
apply_perf_experiment_writer_pragmas(&connection);
connection
.pragma_update(None, "secure_delete", "ON")
.map_err(|err| map_open_sqlite_error(err, OpenStage::WalReplay))?;
connection
.pragma_update(None, "journal_mode", "WAL")
.map_err(|err| map_open_sqlite_error(err, OpenStage::WalReplay))?;
reject_legacy_shape(&connection)?;
let migration = migrate_with_event_sink(&connection, migrations, emit_migration_event)
.map_err(map_migration_error)?;
if migration.schema_version_after >= SEARCH_INDEX_TOKENIZER_SCHEMA_VERSION
&& !search_index_tokenizer_reproject_complete(&connection).map_err(|_| {
EngineOpenError::Io {
message: "could not read search_index tokenizer reproject marker".to_string(),
}
})?
{
reproject_search_index_after_tokenizer_upgrade(&connection).map_err(|_| {
EngineOpenError::Io {
message: "could not re-tokenize search_index after tokenizer upgrade"
.to_string(),
}
})?;
}
let mut embedder_mean_vec_pinned = check_embedder_profile(&connection, embedder_identity)?;
ensure_vector_partition(&mut connection, embedder_identity.dimension).map_err(|_| {
EngineOpenError::Io { message: "could not initialize vector partition".to_string() }
})?;
if migration.schema_version_after >= EDGE_TEMPORAL_EPOCH_SCHEMA_VERSION
&& !edge_vector_prune_complete(&connection).map_err(|_| EngineOpenError::Io {
message: "could not read edge-vector prune marker".to_string(),
})?
{
prune_orphaned_edge_vectors(&connection).map_err(|_| EngineOpenError::Io {
message: "could not prune orphaned edge vector rows".to_string(),
})?;
}
rederive_projections_on_boot(&connection).map_err(|_| EngineOpenError::Io {
message: "could not re-derive projection registry on boot".to_string(),
})?;
reconcile_inert_vector_enrolments_on_boot(&connection).map_err(|_| {
EngineOpenError::Io {
message: "could not reconcile inert vector kind enrolments on boot".to_string(),
}
})?;
{
let tx = connection.transaction().map_err(|_| EngineOpenError::Io {
message: "could not begin vector-attr reconcile on boot".to_string(),
})?;
reconcile_vector_attr_columns(&tx, embedder_identity.dimension).map_err(|_| {
EngineOpenError::Io {
message: "could not reconcile vector attribute columns on boot".to_string(),
}
})?;
tx.commit().map_err(|_| EngineOpenError::Io {
message: "could not commit vector-attr reconcile on boot".to_string(),
})?;
}
if identity_requires_mean_centering(embedder_identity) && !embedder_mean_vec_pinned {
let row_count: u64 = connection
.query_row("SELECT COUNT(*) FROM vector_default", [], |row| row.get(0))
.unwrap_or(0);
if row_count >= MEAN_VEC_PIN_THRESHOLD {
recover_mean_vec_pin(&mut connection, embedder_identity).map_err(|_| {
EngineOpenError::Io {
message: "could not recover mean-centering pin".to_string(),
}
})?;
embedder_mean_vec_pinned = true;
}
}
let warmup_started = Instant::now();
let embedder_mean_centering_required = embedder_identity.name == BGE_SMALL_EMBEDDER_NAME;
let report = OpenReport {
schema_version_before: migration.schema_version_before,
schema_version_after: migration.schema_version_after,
migration_steps: migration.migration_steps,
embedder_warmup_ms: u64::try_from(warmup_started.elapsed().as_millis())
.unwrap_or(u64::MAX),
query_backend: "fathomdb-query + sqlite-vec",
default_embedder: embedder_identity.clone(),
embedder_download_ms: None,
embedder_events: Vec::new(),
embedder_mean_centering_required,
embedder_mean_vec_pinned,
dense_disabled: false,
dense_disabled_reason: None,
};
let mut readers = Vec::with_capacity(READER_POOL_SIZE);
let mut lookaside_rcs: Vec<i32> = Vec::with_capacity(READER_POOL_SIZE);
for _ in 0..READER_POOL_SIZE {
let reader = Connection::open(&path)
.map_err(|err| map_open_sqlite_error(err, OpenStage::HeaderProbe))?;
let rc: i32 = configure_reader_lookaside(&reader);
debug_assert_eq!(
rc,
rusqlite::ffi::SQLITE_OK,
"sqlite3_db_config(LOOKASIDE) must return SQLITE_OK on a freshly opened reader",
);
lookaside_rcs.push(rc);
reader
.pragma_update(None, "journal_mode", "WAL")
.map_err(|err| map_open_sqlite_error(err, OpenStage::WalReplay))?;
reader
.pragma_update(None, "secure_delete", "ON")
.map_err(|err| map_open_sqlite_error(err, OpenStage::WalReplay))?;
reader
.pragma_update(None, "query_only", "ON")
.map_err(|err| map_open_sqlite_error(err, OpenStage::SchemaProbe))?;
apply_perf_experiment_reader_pragmas(&reader);
readers.push(reader);
}
Ok((connection, readers, report, lookaside_rcs))
}
#[must_use]
pub fn path(&self) -> &Path {
&self.path
}
pub fn write(&self, batch: &[PreparedWrite]) -> Result<WriteReceipt, EngineError> {
let category = if batch_is_admin(batch) {
lifecycle::EventCategory::Admin
} else {
lifecycle::EventCategory::Writer
};
self.emit_event(lifecycle::Phase::Started, category, None);
let started = Instant::now();
let outcome = self.write_inner(batch);
self.detect_slow(started, category);
match outcome {
Ok(receipt) => {
let rows = u64::try_from(batch.len()).unwrap_or(u64::MAX);
if batch_is_admin(batch) {
self.counters.record_admin();
} else {
self.counters.record_write(rows);
}
self.emit_event(lifecycle::Phase::Finished, category, None);
Ok(receipt)
}
Err(err) => {
let code = err.stable_code();
self.counters.record_error(code);
self.emit_event(lifecycle::Phase::Failed, category, Some(code));
self.emit_event(
lifecycle::Phase::Failed,
lifecycle::EventCategory::Error,
Some(code),
);
Err(err)
}
}
}
fn enrol_batch_vector_kinds(
&self,
connection: &Connection,
batch: &[PreparedWrite],
) -> Result<bool, EngineError> {
if self.runtime_embedder.is_none() {
return Ok(false);
}
let mut to_enrol: Vec<&str> = Vec::new();
for write in batch {
let PreparedWrite::Node { kind, .. } = write else { continue };
if to_enrol.contains(&kind.as_str()) {
continue;
}
if self.vector_kind_needs_enrolment(connection, kind, RowKind::Leaf)? {
to_enrol.push(kind);
}
}
if to_enrol.is_empty() {
return Ok(false);
}
self.enrol_and_unstrand(connection, &to_enrol)
}
fn vector_kind_needs_enrolment(
&self,
connection: &Connection,
kind: &str,
row_kind: RowKind,
) -> Result<bool, EngineError> {
if !index_targets_for_row_kind(row_kind).vector {
return Ok(false);
}
if !kind_is_vector_committable(kind) {
return Ok(false);
}
if kind_is_vector_indexed(connection, kind)? {
return Ok(false);
}
if !vector_projection_declared(connection).map_err(|_| EngineError::Storage)? {
return Ok(false);
}
Ok(true)
}
fn enrol_and_unstrand(
&self,
connection: &Connection,
kinds: &[&str],
) -> Result<bool, EngineError> {
connection.execute_batch("BEGIN IMMEDIATE").map_err(|_| EngineError::Storage)?;
let result = (|| -> rusqlite::Result<bool> {
for kind in kinds {
register_vector_kind(connection, kind)?;
}
reenqueue_stranded_vector_rows(connection)
})();
match result {
Ok(enqueued) => {
connection.execute_batch("COMMIT").map_err(|_| EngineError::Storage)?;
Ok(enqueued)
}
Err(_) => {
let _ = connection.execute_batch("ROLLBACK");
Err(EngineError::Storage)
}
}
}
fn write_inner(&self, batch: &[PreparedWrite]) -> Result<WriteReceipt, EngineError> {
self.ensure_open()?;
if batch.is_empty() {
return Err(EngineError::WriteValidation);
}
let mut connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_mut().ok_or(EngineError::Closing)?;
let plans = validate_batch(connection, batch)?;
let unstranded = self.enrol_batch_vector_kinds(connection, batch)?;
let projection_jobs = collect_projection_jobs(connection, batch)?;
#[cfg(debug_assertions)]
if self.force_next_commit_failure.swap(false, Ordering::SeqCst) {
return Err(EngineError::Storage);
}
let base_cursor = self.next_cursor.load(Ordering::SeqCst);
let increment = u64::try_from(batch.len()).unwrap_or(u64::MAX);
let last_cursor = base_cursor.saturating_add(increment);
let has_edge_body_work =
batch.iter().any(|w| matches!(w, PreparedWrite::Edge { body: Some(_), .. }));
let pending_projection = !projection_jobs.is_empty() || has_edge_body_work || unstranded;
let dangling_edge_endpoints = match commit_batch(
connection,
batch,
&plans,
base_cursor,
self.provenance_row_cap.load(Ordering::Relaxed),
) {
Ok(count) => count,
Err(err) => {
self.emit_sqlite_internal_error(&err);
return Err(EngineError::Storage);
}
};
self.next_cursor.store(last_cursor, Ordering::SeqCst);
if pending_projection {
self.projection_runtime.notify_new_work();
}
let row_cursors = (0..batch.len())
.map(|i| base_cursor.saturating_add((i as u64).saturating_add(1)))
.collect();
Ok(WriteReceipt { cursor: last_cursor, row_cursors, dangling_edge_endpoints })
}
pub fn ingest_with_extractor(
&self,
cmd: &[&str],
documents: &[ExtractDocument],
) -> Result<IngestWithExtractorReceipt, EngineError> {
let mut session = self.provider_session(ProviderTask::Extract, cmd)?;
self.run_extract_session(&mut session, documents)
}
fn provider_session(
&self,
task: ProviderTask,
cmd: &[&str],
) -> Result<ProviderSession, EngineError> {
let (program, args) = cmd.split_first().ok_or(EngineError::Extractor)?;
let mut child = Command::new(program)
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.spawn()
.map_err(|_| EngineError::Extractor)?;
let child_stdin = match child.stdin.take() {
Some(s) => s,
None => {
let _ = child.kill();
let _ = child.wait();
return Err(EngineError::Extractor);
}
};
let child_stdout = match child.stdout.take() {
Some(s) => s,
None => {
let _ = child.kill();
let _ = child.wait();
return Err(EngineError::Extractor);
}
};
let io_timeout = extractor_io_timeout();
let (line_tx, line_rx) = mpsc::channel::<std::io::Result<String>>();
thread::spawn(move || {
let mut reader = BufReader::new(child_stdout);
loop {
let mut buf = String::new();
match reader.read_line(&mut buf) {
Ok(0) => break,
Ok(_) => {
if line_tx.send(Ok(buf)).is_err() {
break;
}
}
Err(e) => {
let _ = line_tx.send(Err(e));
break;
}
}
}
});
let mut session = ProviderSession {
task,
child,
writer: std::io::BufWriter::new(child_stdin),
line_rx,
io_timeout,
model: None,
max_docs_per_request: 8,
};
session.handshake()?;
Ok(session)
}
fn run_extract_session(
&self,
session: &mut ProviderSession,
documents: &[ExtractDocument],
) -> Result<IngestWithExtractorReceipt, EngineError> {
let extractor_model_id = session.model.clone();
let max_docs = session.max_docs_per_request;
let mut nodes_written: u64 = 0;
let mut edges_written: u64 = 0;
let docs_processed = documents.len() as u64;
for (batch_idx, batch) in documents.chunks(max_docs).enumerate() {
let request_id = format!("req-{batch_idx}");
let docs_json: Vec<Value> = batch
.iter()
.map(|d| {
serde_json::json!({
"source_doc_id": d.source_doc_id,
"body": d.body,
})
})
.collect();
let result = session
.request(&request_id, vec![("documents".to_string(), Value::Array(docs_json))])?;
let batch_provenance = batch
.iter()
.map(|d| SourceId::new(d.source_doc_id.clone()))
.collect::<Result<Vec<_>, _>>()?;
let resolve_provenance = |echo: Option<&str>| -> Result<SourceId, EngineError> {
if let [only] = batch_provenance.as_slice() {
return Ok(only.clone());
}
let echo = echo.ok_or(EngineError::Extractor)?;
batch_provenance
.iter()
.find(|caller_id| caller_id.as_str() == echo)
.cloned()
.ok_or(EngineError::Extractor)
};
let entities =
result.get("entities").and_then(|v| v.as_array()).cloned().unwrap_or_default();
let raw_edges =
result.get("edges").and_then(|v| v.as_array()).cloned().unwrap_or_default();
let raw_fallback_dates: Vec<&Value> = result
.get("warnings")
.and_then(|v| v.as_array())
.map(|ws| {
ws.iter()
.filter(|w| {
w.get("kind").and_then(|k| k.as_str()) == Some("temporal_fallback")
})
.filter_map(|w| w.get("substituted_t_valid"))
.collect()
})
.unwrap_or_default();
if !entities.is_empty() {
let node_batch: Vec<PreparedWrite> = entities
.iter()
.map(|entity| -> Result<PreparedWrite, EngineError> {
let name = entity.get("name").and_then(|v| v.as_str()).unwrap_or("");
let kind = entity.get("type").and_then(|v| v.as_str()).unwrap_or("entity");
let source_doc_id = resolve_provenance(
entity.get("source_doc_id").and_then(|v| v.as_str()),
)?;
let logical_id = derive_logical_id(kind, name)?;
Ok(PreparedWrite::Node {
kind: kind.to_string(),
body: name.to_string(),
source_id: source_doc_id,
logical_id: Some(logical_id),
state: InitialState::Active,
reason: None,
valid_from: None,
valid_until: None,
})
})
.collect::<Result<Vec<_>, _>>()?;
let node_batch = dedup_prepared_by_logical_id(node_batch);
let ids: Vec<String> = node_batch
.iter()
.filter_map(|w| {
if let PreparedWrite::Node { logical_id: Some(id), .. } = w {
Some(id.clone())
} else {
None
}
})
.collect();
let existing: std::collections::HashSet<String> = self
.read_get_many(&ids, &ReadView::default())?
.into_iter()
.zip(ids)
.filter_map(|(opt, id)| opt.map(|_| id))
.collect();
let new_nodes: Vec<PreparedWrite> = node_batch
.into_iter()
.filter(|w| {
if let PreparedWrite::Node { logical_id: Some(id), .. } = w {
!existing.contains(id)
} else {
true
}
})
.collect();
if !new_nodes.is_empty() {
let n = new_nodes.len() as u64;
self.write(&new_nodes)?;
nodes_written = nodes_written.saturating_add(n);
}
}
if !raw_edges.is_empty() {
let mut entity_index: std::collections::HashMap<String, (String, String)> =
std::collections::HashMap::new();
for entity in &entities {
let name = entity.get("name").and_then(|v| v.as_str()).unwrap_or("");
if name.is_empty() {
continue;
}
let kind =
entity.get("type").and_then(|v| v.as_str()).unwrap_or("entity").to_string();
entity_index
.entry(name.to_lowercase())
.or_insert_with(|| (name.to_string(), kind));
}
for entity in &entities {
let name = entity.get("name").and_then(|v| v.as_str()).unwrap_or("");
if name.is_empty() {
continue;
}
let kind =
entity.get("type").and_then(|v| v.as_str()).unwrap_or("entity").to_string();
if let Some(aliases) = entity.get("aliases").and_then(|v| v.as_array()) {
for alias in aliases.iter().filter_map(|a| a.as_str()) {
if !alias.is_empty() {
entity_index
.entry(alias.to_lowercase())
.or_insert_with(|| (name.to_string(), kind.clone()));
}
}
}
}
type EdgeTimes = Vec<(Option<i64>, Option<i64>)>;
let (edge_times, fallback_epochs): (EdgeTimes, std::collections::HashSet<i64>) = {
let connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_ref().ok_or(EngineError::Closing)?;
let mut times = Vec::with_capacity(raw_edges.len());
for edge in &raw_edges {
times.push((
normalize_extractor_timestamp(
connection,
"t_valid",
edge.get("t_valid"),
)?,
normalize_extractor_timestamp(
connection,
"t_invalid",
edge.get("t_invalid"),
)?,
));
}
let mut epochs = std::collections::HashSet::new();
for raw in &raw_fallback_dates {
if let Some(epoch) = normalize_extractor_timestamp(
connection,
"substituted_t_valid",
Some(raw),
)? {
epochs.insert(epoch);
}
}
(times, epochs)
};
let edge_batch: Vec<PreparedWrite> = raw_edges
.iter()
.zip(&edge_times)
.map(|(edge, &(t_valid, t_invalid))| -> Result<PreparedWrite, EngineError> {
let from_entity =
edge.get("from_entity").and_then(|v| v.as_str()).unwrap_or("");
let to_entity =
edge.get("to_entity").and_then(|v| v.as_str()).unwrap_or("");
let relation =
edge.get("relation").and_then(|v| v.as_str()).unwrap_or("related_to");
let body = edge.get("body").and_then(|v| v.as_str()).map(str::to_string);
let confidence = match edge.get("confidence").and_then(|v| v.as_f64()) {
Some(c) if !(0.0..=1.0).contains(&c) => {
return Err(EngineError::Extractor);
}
c => c,
};
let source_doc_id =
resolve_provenance(edge.get("source_doc_id").and_then(|v| v.as_str()))?;
let (from_name, from_kind) = entity_index
.get(&from_entity.to_lowercase())
.cloned()
.unwrap_or_else(|| (from_entity.to_string(), "entity".to_string()));
let (to_name, to_kind) = entity_index
.get(&to_entity.to_lowercase())
.cloned()
.unwrap_or_else(|| (to_entity.to_string(), "entity".to_string()));
let from_lid = derive_logical_id(&from_kind, &from_name)?;
let to_lid = derive_logical_id(&to_kind, &to_name)?;
let edge_key = format!("{from_lid}:{to_lid}:{relation}");
let edge_lid = derive_logical_id("edge", &edge_key)?;
let is_temporal_fallback =
t_valid.is_some_and(|tv| fallback_epochs.contains(&tv));
Ok(PreparedWrite::Edge {
kind: relation.to_string(),
from: from_lid,
to: to_lid,
source_id: source_doc_id,
logical_id: Some(edge_lid),
body,
t_valid,
t_invalid,
confidence,
extractor_model_id: extractor_model_id.clone(),
temporal_fallback: if is_temporal_fallback { Some(true) } else { None },
})
})
.collect::<Result<Vec<_>, _>>()?;
let edge_batch = dedup_prepared_by_logical_id(edge_batch);
let n = edge_batch.len() as u64;
self.write(&edge_batch)?;
edges_written = edges_written.saturating_add(n);
}
}
Ok(IngestWithExtractorReceipt { nodes_written, edges_written, docs_processed })
}
pub fn consolidate_with_provider(
&self,
cmd: &[&str],
axes: &[ConsolidateAxis],
) -> Result<ConsolidateReceipt, EngineError> {
let mut session = self
.provider_session(ProviderTask::Consolidate, cmd)
.map_err(|_| EngineError::Consolidator)?;
self.run_consolidate_session(&mut session, axes)
}
fn run_consolidate_session(
&self,
session: &mut ProviderSession,
axes: &[ConsolidateAxis],
) -> Result<ConsolidateReceipt, EngineError> {
let mut receipt = ConsolidateReceipt::default();
for (i, axis) in axes.iter().enumerate() {
let cluster = self.assemble_consolidate_cluster(axis)?;
if cluster.is_empty() {
continue;
}
receipt.clusters_processed = receipt.clusters_processed.saturating_add(1);
receipt.edges_examined = receipt.edges_examined.saturating_add(cluster.len() as u64);
let request_id = format!("req-{i}");
let edges_json: Vec<Value> = {
let connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_ref().ok_or(EngineError::Closing)?;
let render = |field: &str, value: Option<i64>| -> Result<Value, EngineError> {
match value {
None => Ok(Value::Null),
Some(ts) => match epoch_seconds_to_iso8601(connection, ts) {
Some(iso) => Ok(Value::from(iso)),
None => Err(EngineError::InvalidArgument {
msg: format!(
"INVARIANT VIOLATION (TC-33 fix-1): stored edge `{field}` = \
{ts} is unrenderable to ISO-8601 and would have gone to the \
consolidation wire as a silent null (\"still valid\"). The \
write boundary should have made this unstorable."
),
}),
},
}
};
cluster
.iter()
.map(|e| {
Ok::<Value, EngineError>(serde_json::json!({
"edge_ref": e.edge_ref,
"body": e.body,
"t_valid": render("t_valid", e.t_valid)?,
"t_invalid": render("t_invalid", e.t_invalid)?,
"confidence": e.confidence,
"source_doc_id": e.source_doc_id,
"extractor_model_id": e.extractor_model_id,
}))
})
.collect::<Result<Vec<Value>, EngineError>>()?
};
let cluster_json = serde_json::json!({
"subject": axis.subject_logical_id,
"relation": axis.relation,
"edges": edges_json,
});
let result = session
.request(&request_id, vec![("cluster".to_string(), cluster_json)])
.map_err(|_| EngineError::Consolidator)?;
let verdicts = result
.get("verdicts")
.and_then(|v| v.as_array())
.ok_or(EngineError::Consolidator)?
.clone();
self.apply_consolidate_verdicts(&cluster, &verdicts, &mut receipt)?;
}
Ok(receipt)
}
fn assemble_consolidate_cluster(
&self,
axis: &ConsolidateAxis,
) -> Result<Vec<ConsolidateCandidateEdge>, EngineError> {
self.ensure_open()?;
let connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_ref().ok_or(EngineError::Closing)?;
let mut stmt = connection
.prepare(
"SELECT logical_id, body, t_valid, t_invalid, confidence, source_id, \
extractor_model_id \
FROM canonical_edges \
WHERE from_id = ?1 AND kind = ?2 AND superseded_at IS NULL \
ORDER BY write_cursor",
)
.map_err(|_| EngineError::Storage)?;
let rows = stmt
.query_map(params![axis.subject_logical_id, axis.relation], |r| {
Ok(ConsolidateCandidateEdge {
edge_ref: r.get::<_, Option<String>>(0)?.unwrap_or_default(),
body: r.get(1)?,
t_valid: r.get(2)?,
t_invalid: r.get(3)?,
confidence: r.get(4)?,
source_doc_id: r.get(5)?,
extractor_model_id: r.get(6)?,
})
})
.map_err(|_| EngineError::Storage)?;
let out: rusqlite::Result<Vec<ConsolidateCandidateEdge>> = rows.collect();
Ok(out
.map_err(|_| EngineError::Storage)?
.into_iter()
.filter(|e| !e.edge_ref.is_empty())
.collect())
}
fn apply_consolidate_verdicts(
&self,
cluster: &[ConsolidateCandidateEdge],
verdicts: &[Value],
receipt: &mut ConsolidateReceipt,
) -> Result<(), EngineError> {
let known: std::collections::HashSet<&str> =
cluster.iter().map(|e| e.edge_ref.as_str()).collect();
let mut seen: std::collections::HashSet<&str> = std::collections::HashSet::new();
let mut connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_mut().ok_or(EngineError::Closing)?;
let tx = connection.transaction().map_err(|_| EngineError::Storage)?;
for v in verdicts {
let edge_ref =
v.get("edge_ref").and_then(|x| x.as_str()).ok_or(EngineError::Consolidator)?;
if !known.contains(edge_ref) {
return Err(EngineError::Consolidator);
}
if !seen.insert(edge_ref) {
return Err(EngineError::Consolidator);
}
let verdict =
v.get("verdict").and_then(|x| x.as_str()).ok_or(EngineError::Consolidator)?;
let active_cursor = Self::active_edge_write_cursor(&tx, edge_ref)?;
match verdict {
"keep" => {
receipt.edges_kept = receipt.edges_kept.saturating_add(1);
}
"invalidate" => {
let ts = normalize_extractor_timestamp(
&tx,
"t_invalid",
Some(v.get("t_invalid").ok_or(EngineError::Consolidator)?),
)
.map_err(|_| EngineError::Consolidator)?
.ok_or(EngineError::Consolidator)?;
tx.execute(
"UPDATE canonical_edges SET t_invalid = ?1 \
WHERE logical_id = ?2 AND superseded_at IS NULL",
params![ts, edge_ref],
)
.map_err(|_| EngineError::Storage)?;
if let Some(cursor) = active_cursor {
let ended = ts <= current_epoch_seconds();
if ended {
Self::prune_edge_projection_shadows(&tx, cursor, true)?;
}
}
receipt.edges_invalidated = receipt.edges_invalidated.saturating_add(1);
}
"supersede" | "merge" => {
let cursor = self.next_cursor.fetch_add(1, Ordering::SeqCst).saturating_add(1);
tx.execute(
"UPDATE canonical_edges SET superseded_at = ?1 \
WHERE logical_id = ?2 AND superseded_at IS NULL",
params![cursor, edge_ref],
)
.map_err(|_| EngineError::Storage)?;
if let Some(active_cursor) = active_cursor {
Self::prune_edge_projection_shadows(&tx, active_cursor, false)?;
}
receipt.edges_superseded = receipt.edges_superseded.saturating_add(1);
}
_ => return Err(EngineError::Consolidator),
}
}
if seen.len() != known.len() {
return Err(EngineError::Consolidator);
}
tx.commit().map_err(|_| EngineError::Storage)?;
Ok(())
}
fn active_edge_write_cursor(
tx: &rusqlite::Transaction<'_>,
edge_ref: &str,
) -> Result<Option<i64>, EngineError> {
tx.query_row(
"SELECT write_cursor FROM canonical_edges \
WHERE logical_id = ?1 AND superseded_at IS NULL",
params![edge_ref],
|r| r.get::<_, i64>(0),
)
.optional()
.map_err(|_| EngineError::Storage)
}
fn prune_edge_projection_shadows(
tx: &rusqlite::Transaction<'_>,
cursor: i64,
keep_terminal: bool,
) -> Result<(), EngineError> {
tx.execute("DELETE FROM search_index_edges WHERE write_cursor = ?1", [cursor])
.map_err(|_| EngineError::Storage)?;
delete_vector_partition_row(tx, cursor).map_err(|_| EngineError::Storage)?;
tx.execute("DELETE FROM _fathomdb_vector_rows WHERE write_cursor = ?1", [cursor])
.map_err(|_| EngineError::Storage)?;
if !keep_terminal {
tx.execute(
"DELETE FROM _fathomdb_projection_terminal WHERE write_cursor = ?1",
[cursor],
)
.map_err(|_| EngineError::Storage)?;
}
Ok(())
}
pub fn search(&self, query: &str) -> Result<SearchResult, EngineError> {
self.search_filtered(query, None)
}
pub fn search_view(&self, query: &str, view: &ReadView) -> Result<SearchResult, EngineError> {
self.search_reranked_with_explain(query, None, 0, false, 0.3, 0, false, *view)
}
#[allow(clippy::too_many_arguments)] pub fn search_reranked_view(
&self,
query: &str,
filter: Option<SearchFilter>,
rerank_depth: usize,
use_graph_arm: bool,
alpha: f64,
pool_n: usize,
explain: bool,
view: &ReadView,
) -> Result<SearchResult, EngineError> {
self.search_reranked_with_explain(
query,
filter,
rerank_depth,
use_graph_arm,
alpha,
pool_n,
explain,
*view,
)
}
pub fn search_filtered(
&self,
query: &str,
filter: Option<SearchFilter>,
) -> Result<SearchResult, EngineError> {
let lowered = filter
.map(|sf| {
let attributes = sf.attributes.clone();
Filter::from(&sf).to_search_filter().map(|mut lo| {
lo.attributes = attributes;
lo
})
})
.transpose()?;
self.search_reranked(query, lowered, 0, false, 0.3, 0)
}
pub fn search_filter(&self, query: &str, filter: &Filter) -> Result<SearchResult, EngineError> {
let sf = filter.to_search_filter()?;
self.search_reranked(query, Some(sf), 0, false, 0.3, 0)
}
pub fn search_reranked(
&self,
query: &str,
filter: Option<SearchFilter>,
rerank_depth: usize,
use_graph_arm: bool,
alpha: f64,
pool_n: usize,
) -> Result<SearchResult, EngineError> {
self.search_reranked_with_explain(
query,
filter,
rerank_depth,
use_graph_arm,
alpha,
pool_n,
false,
ReadView::default(),
)
}
pub fn search_explained(
&self,
query: &str,
filter: Option<SearchFilter>,
rerank_depth: usize,
use_graph_arm: bool,
alpha: f64,
pool_n: usize,
) -> Result<SearchResult, EngineError> {
self.search_reranked_with_explain(
query,
filter,
rerank_depth,
use_graph_arm,
alpha,
pool_n,
true,
ReadView::default(),
)
}
pub fn search_text_only(&self, query: &str) -> Result<SearchResult, EngineError> {
self.search_text_only_view(query, &ReadView::default())
}
pub fn search_text_only_view(
&self,
query: &str,
view: &ReadView,
) -> Result<SearchResult, EngineError> {
self.ensure_open()?;
view.reject_existence_relaxation_on_search()?;
if query.trim().is_empty() {
return Err(EngineError::WriteValidation);
}
let compiled = compile_text_query(query);
let search_limit = self
.projection_runtime
.shared
.search_limit_override
.load(Ordering::SeqCst)
.max(SEARCH_RERANK_LIMIT);
let (response_tx, response_rx) = mpsc::sync_channel::<ReaderResponse>(1);
let request = ReaderRequest::Search {
compiled,
query_vector: None,
query_vector_bin: None,
search_limit,
filter: None,
recency_enabled: false,
importance_enabled: false,
vector_stage_only: false,
raw_query: Box::from(query),
rerank_depth: 0,
use_graph_arm: false,
alpha: 0.3,
pool_n: 0,
explain: false,
view: *view,
respond: response_tx,
};
if self.reader_pool.dispatch(request).is_err() {
return Err(EngineError::Closing);
}
let search_result = response_rx.recv().map_err(|_| EngineError::Storage)?;
let (cursor, soft_fallback, results, _graph_stats, explanation) = match search_result {
Ok(result) => result,
Err(SearchReaderError::InvalidFilter(reason)) => {
return Err(EngineError::InvalidFilter { reason });
}
Err(SearchReaderError::Sqlite(err)) => {
self.emit_sqlite_internal_error(&err);
return Err(EngineError::Storage);
}
};
Ok(SearchResult { projection_cursor: cursor, soft_fallback, results, explanation })
}
#[must_use]
pub fn dense_disabled(&self) -> bool {
self.dense_disabled.load(Ordering::Acquire)
}
#[must_use]
pub fn dense_disabled_reason(&self) -> Option<String> {
self.dense_disabled_reason.lock().ok().and_then(|g| g.clone())
}
#[must_use]
pub fn vector_equivalence_refusal_count(&self) -> u64 {
self.vector_equivalence_refusals.load(Ordering::Relaxed)
}
#[allow(clippy::too_many_arguments)] fn search_reranked_with_explain(
&self,
query: &str,
filter: Option<SearchFilter>,
rerank_depth: usize,
use_graph_arm: bool,
alpha: f64,
pool_n: usize,
explain: bool,
view: ReadView,
) -> Result<SearchResult, EngineError> {
view.reject_existence_relaxation_on_search()?;
self.emit_event(lifecycle::Phase::Started, lifecycle::EventCategory::Search, None);
let started = Instant::now();
let outcome = self.search_inner(
query,
filter,
rerank_depth,
use_graph_arm,
alpha,
pool_n,
explain,
view,
);
self.detect_slow(started, lifecycle::EventCategory::Search);
match outcome {
Ok(result) => {
self.counters.record_query();
self.capture_telemetry(query, &result);
self.emit_event(lifecycle::Phase::Finished, lifecycle::EventCategory::Search, None);
Ok(result)
}
Err(err) => {
let code = err.stable_code();
self.counters.record_error(code);
self.emit_event(
lifecycle::Phase::Failed,
lifecycle::EventCategory::Search,
Some(code),
);
self.emit_event(
lifecycle::Phase::Failed,
lifecycle::EventCategory::Error,
Some(code),
);
Err(err)
}
}
}
pub fn enable_telemetry(&self, sink_path: &str) -> Result<(), EngineError> {
std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(sink_path)
.map_err(|_| EngineError::Storage)?;
let mut guard = self.telemetry.lock().map_err(|_| EngineError::Storage)?;
*guard = Some(TelemetrySink {
path: PathBuf::from(sink_path),
base: Instant::now(),
nonce: 0,
seq: 0,
last_query_id: None,
});
self.telemetry_enabled.store(true, Ordering::Release);
Ok(())
}
pub fn last_telemetry_query_id(&self) -> Option<String> {
self.telemetry.lock().ok()?.as_ref().and_then(|s| s.last_query_id.clone())
}
fn capture_telemetry(&self, query: &str, result: &SearchResult) {
if !self.telemetry_enabled.load(Ordering::Acquire) {
return;
}
let Ok(mut guard) = self.telemetry.lock() else { return };
let Some(sink) = guard.as_mut() else { return };
let query_id = format!("q{}-{}", sink.nonce, sink.seq);
let ts_monotonic_ms = sink.base.elapsed().as_millis() as u64;
let mut arm_of = serde_json::Map::new();
for h in &result.results {
arm_of
.insert(h.write_cursor.to_string(), serde_json::Value::from(branch_str(h.branch)));
}
let event = serde_json::json!({
"type": "event",
"schema_version": 1,
"ts_monotonic_ms": ts_monotonic_ms,
"query_id": query_id,
"query_chars": query.chars().count() as u64,
"result_ids": result.results.iter().map(|h| h.write_cursor).collect::<Vec<u64>>(),
"result_stable_ids": result
.results
.iter()
.map(|h| h.id.to_prefixed())
.collect::<Vec<String>>(),
"arm_of": arm_of,
});
let _ = append_jsonl(&sink.path, &event);
sink.seq += 1;
sink.last_query_id = Some(query_id);
}
pub fn record_feedback(
&self,
query_id: &str,
relevant_ids: &[u64],
irrelevant_ids: &[u64],
label_source: &str,
) -> Result<(), EngineError> {
let guard = self.telemetry.lock().map_err(|_| EngineError::Storage)?;
let sink = guard
.as_ref()
.ok_or(EngineError::InvalidArgument { msg: "telemetry is not enabled".to_string() })?;
let is_issued_id = query_id
.strip_prefix('q')
.and_then(|rest| rest.split_once('-'))
.and_then(|(nonce, seq)| Some((nonce.parse::<u64>().ok()?, seq.parse::<u64>().ok()?)))
.is_some_and(|(nonce, seq)| nonce == sink.nonce && seq < sink.seq);
if !is_issued_id {
return Err(EngineError::InvalidArgument { msg: "unknown query_id".to_string() });
}
let record = serde_json::json!({
"type": "feedback",
"schema_version": 1,
"query_id": query_id,
"relevant_ids": relevant_ids,
"irrelevant_ids": irrelevant_ids,
"label_source": label_source,
});
append_jsonl(&sink.path, &record).map_err(|_| EngineError::Storage)
}
fn detect_slow(&self, started: Instant, category: lifecycle::EventCategory) {
let elapsed = started.elapsed();
let threshold = self.slow_threshold_ms.load(Ordering::Relaxed);
let threshold_duration = std::time::Duration::from_millis(threshold);
if elapsed > threshold_duration {
self.emit_event(lifecycle::Phase::Slow, category, None);
}
}
fn emit_event(
&self,
phase: lifecycle::Phase,
category: lifecycle::EventCategory,
code: Option<&'static str>,
) {
let event =
lifecycle::Event { phase, source: lifecycle::EventSource::Engine, category, code };
self.subscribers.dispatch(&event);
}
fn emit_sqlite_internal_error(&self, err: &rusqlite::Error) {
if let Some(code) = sqlite_extended_code_name(err) {
let event = lifecycle::Event {
phase: lifecycle::Phase::Failed,
source: lifecycle::EventSource::SqliteInternal,
category: lifecycle::EventCategory::Error,
code: Some(code),
};
self.subscribers.dispatch(&event);
}
}
#[allow(clippy::too_many_arguments)] fn search_inner(
&self,
query: &str,
filter: Option<SearchFilter>,
rerank_depth: usize,
use_graph_arm: bool,
alpha: f64,
pool_n: usize,
explain: bool,
view: ReadView,
) -> Result<SearchResult, EngineError> {
self.search_inner_with_stats(
query,
filter,
rerank_depth,
use_graph_arm,
alpha,
pool_n,
explain,
view,
)
.map(|(result, _stats)| result)
}
#[allow(clippy::too_many_arguments)] fn search_inner_with_stats(
&self,
query: &str,
filter: Option<SearchFilter>,
rerank_depth: usize,
use_graph_arm: bool,
alpha: f64,
pool_n: usize,
explain: bool,
view: ReadView,
) -> Result<(SearchResult, GraphFrontierStats), EngineError> {
self.ensure_open()?;
if self.dense_disabled.load(Ordering::Acquire) {
self.vector_equivalence_refusals.fetch_add(1, Ordering::Relaxed);
let reason =
self.dense_disabled_reason.lock().ok().and_then(|g| g.clone()).unwrap_or_else(
|| "open-time #5 vector-equivalence self-check failed".to_string(),
);
return Err(EngineError::VectorEquivalenceMismatch { reason });
}
if query.trim().is_empty() {
return Err(EngineError::WriteValidation);
}
let compiled = compile_text_query(query);
let raw_query_vector =
self.runtime_embedder.as_ref().and_then(|embedder| embedder.embed(query).ok());
let query_vector_bin = match raw_query_vector.as_ref() {
Some(vector) if identity_requires_mean_centering(&self.runtime_embedder_identity) => {
let pinned = {
let connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_ref().ok_or(EngineError::Closing)?;
read_pinned_mean_vec(connection, self.runtime_embedder_identity.dimension)?
};
match pinned {
Some(mean) => serde_json::to_string(&subtract_mean(vector, &mean)).ok(),
None => serde_json::to_string(vector).ok(),
}
}
Some(vector) => serde_json::to_string(vector).ok(),
None => None,
};
let query_vector = raw_query_vector.and_then(|vector| serde_json::to_string(&vector).ok());
let search_limit = self
.projection_runtime
.shared
.search_limit_override
.load(Ordering::SeqCst)
.max(SEARCH_RERANK_LIMIT);
let recency_enabled =
self.projection_runtime.shared.recency_reweight_enabled.load(Ordering::SeqCst);
let importance_enabled =
self.projection_runtime.shared.importance_reweight_enabled.load(Ordering::SeqCst);
let vector_stage_only =
self.projection_runtime.shared.vector_stage_only_for_test.load(Ordering::SeqCst);
let (response_tx, response_rx) = mpsc::sync_channel::<ReaderResponse>(1);
let request = ReaderRequest::Search {
compiled,
query_vector,
query_vector_bin,
search_limit,
filter: filter.map(Box::new),
recency_enabled,
importance_enabled,
vector_stage_only,
raw_query: Box::from(query), rerank_depth,
use_graph_arm,
alpha,
pool_n,
explain,
view,
respond: response_tx,
};
if self.reader_pool.dispatch(request).is_err() {
return Err(EngineError::Closing);
}
let search_result = response_rx.recv().map_err(|_| EngineError::Storage)?;
let (cursor, soft_fallback, results, graph_stats, explanation) = match search_result {
Ok(result) => result,
Err(SearchReaderError::InvalidFilter(reason)) => {
return Err(EngineError::InvalidFilter { reason });
}
Err(SearchReaderError::Sqlite(err)) => {
self.emit_sqlite_internal_error(&err);
return Err(EngineError::Storage);
}
};
let explanation = explanation.map(|mut exp| {
let id = &self.runtime_embedder_identity;
exp.trace.embedder_id = format!("{}@{} (dim={})", id.name, id.revision, id.dimension);
exp
});
Ok((
SearchResult { projection_cursor: cursor, soft_fallback, results, explanation },
graph_stats,
))
}
pub fn _graph_frontier_stats_for_test(
&self,
query: &str,
) -> Result<GraphFrontierStats, EngineError> {
self.search_inner_with_stats(query, None, 0, true, 0.3, 0, false, ReadView::default())
.map(|(_result, stats)| stats)
}
pub fn read_get(
&self,
logical_id: &str,
view: &ReadView,
) -> Result<Option<NodeRecord>, EngineError> {
let ids = [logical_id.to_string()];
let rows = self.read_get_many(&ids, view)?;
Ok(rows.into_iter().next().flatten())
}
pub fn read_get_many(
&self,
logical_ids: &[String],
view: &ReadView,
) -> Result<Vec<Option<NodeRecord>>, EngineError> {
self.ensure_open()?;
if logical_ids.is_empty() {
return Ok(Vec::new());
}
let (response_tx, response_rx) = mpsc::sync_channel(1);
let request = ReaderRequest::GetById {
logical_ids: logical_ids.to_vec(),
view: *view,
respond: response_tx,
};
if self.reader_pool.dispatch(request).is_err() {
return Err(EngineError::Closing);
}
match response_rx.recv().map_err(|_| EngineError::Storage)? {
Ok(rows) => Ok(rows),
Err(err) => {
self.emit_sqlite_internal_error(&err);
Err(EngineError::Storage)
}
}
}
pub fn graph_neighbors(
&self,
root_logical_id: &str,
depth: u32,
direction: TraversalDirection,
view: &ReadView,
) -> Result<Vec<NodeRecord>, EngineError> {
self.ensure_open()?;
if depth == 0 || depth > 3 {
return Err(EngineError::InvalidArgument {
msg: format!("traversal depth {depth} is out of range; must be 1, 2, or 3"),
});
}
let (response_tx, response_rx) = mpsc::sync_channel(1);
let request = ReaderRequest::GraphNeighbors {
root_logical_id: root_logical_id.to_string(),
depth,
direction,
view: *view,
respond: response_tx,
};
if self.reader_pool.dispatch(request).is_err() {
return Err(EngineError::Closing);
}
match response_rx.recv().map_err(|_| EngineError::Storage)? {
Ok(nodes) => Ok(nodes),
Err(err) => {
self.emit_sqlite_internal_error(&err);
Err(EngineError::Storage)
}
}
}
pub fn search_expand(
&self,
query: &str,
filter: Option<SearchFilter>,
depth: u32,
) -> Result<SearchExpandResult, EngineError> {
self.ensure_open()?;
if depth > 3 {
return Err(EngineError::InvalidArgument {
msg: format!("traversal depth {depth} exceeds the SDK ceiling of 3"),
});
}
let search_result =
self.search_inner(query, filter, 0, false, 0.3, 0, false, ReadView::default())?;
if search_result.results.is_empty() {
return Ok(SearchExpandResult {
search_hits: Vec::new(),
expanded: Vec::new(),
all_logical_ids: Vec::new(),
});
}
let (response_tx, response_rx) = mpsc::sync_channel(1);
let request = ReaderRequest::SearchExpand {
search_hits: search_result.results,
depth,
respond: response_tx,
};
if self.reader_pool.dispatch(request).is_err() {
return Err(EngineError::Closing);
}
match response_rx.recv().map_err(|_| EngineError::Storage)? {
Ok(result) => Ok(result),
Err(err) => {
self.emit_sqlite_internal_error(&err);
Err(EngineError::Storage)
}
}
}
#[doc(hidden)]
pub fn explain_graph_neighbors_for_test(
&self,
root_logical_id: &str,
depth: u32,
direction: TraversalDirection,
) -> Result<Vec<String>, EngineError> {
self.ensure_open()?;
let (response_tx, response_rx) = mpsc::sync_channel(1);
let request = ReaderRequest::ExplainGraphNeighbors {
root_logical_id: root_logical_id.to_string(),
depth,
direction,
respond: response_tx,
};
if self.reader_pool.dispatch(request).is_err() {
return Err(EngineError::Closing);
}
match response_rx.recv().map_err(|_| EngineError::Storage)? {
Ok(plan) => Ok(plan),
Err(err) => {
self.emit_sqlite_internal_error(&err);
Err(EngineError::Storage)
}
}
}
pub fn read_collection(
&self,
collection: &str,
after_id: Option<i64>,
limit: usize,
) -> Result<Vec<OpStoreRow>, EngineError> {
self.read_collection_dispatch(collection, after_id, limit)
}
pub fn read_mutations(
&self,
collection: &str,
after_id: Option<i64>,
limit: usize,
) -> Result<Vec<OpStoreRow>, EngineError> {
self.read_collection_dispatch(collection, after_id, limit)
}
fn read_collection_dispatch(
&self,
collection: &str,
after_id: Option<i64>,
limit: usize,
) -> Result<Vec<OpStoreRow>, EngineError> {
self.ensure_open()?;
let (response_tx, response_rx) = mpsc::sync_channel(1);
let request = ReaderRequest::ReadCollection {
collection: collection.to_string(),
after_id,
limit,
respond: response_tx,
};
if self.reader_pool.dispatch(request).is_err() {
return Err(EngineError::Closing);
}
match response_rx.recv().map_err(|_| EngineError::Storage)? {
Ok(rows) => Ok(rows),
Err(err) => {
self.emit_sqlite_internal_error(&err);
Err(EngineError::Storage)
}
}
}
pub fn read_list(
&self,
kind: &str,
predicates: &[Predicate],
limit: usize,
view: &ReadView,
) -> Result<Vec<NodeRecord>, EngineError> {
self.ensure_open()?;
for pred in predicates {
let path = pred.path();
if !PREDICATE_PATH_ALLOWLIST.contains(&path) {
return Err(EngineError::InvalidFilter {
reason: format!("path '{path}' is not in the predicate path allowlist"),
});
}
}
let (response_tx, response_rx) = mpsc::sync_channel(1);
let request = ReaderRequest::ReadList {
kind: kind.to_string(),
predicates: predicates.to_vec(),
limit,
view: *view,
respond: response_tx,
};
if self.reader_pool.dispatch(request).is_err() {
return Err(EngineError::Closing);
}
match response_rx.recv().map_err(|_| EngineError::Storage)? {
Ok(rows) => Ok(rows),
Err(err) => {
self.emit_sqlite_internal_error(&err);
Err(EngineError::Storage)
}
}
}
pub fn read_list_filter(
&self,
kind: &str,
filter: &Filter,
limit: usize,
view: &ReadView,
) -> Result<Vec<NodeRecord>, EngineError> {
self.ensure_open()?;
match filter.lower_for_read_list(kind)? {
None => Ok(Vec::new()),
Some(preds) => self.read_list(kind, &preds, limit, view),
}
}
pub fn crossed_boundary_since(
&self,
since: i64,
view: &ReadView,
) -> Result<Vec<BoundaryCrossing>, EngineError> {
self.ensure_open()?;
let (response_tx, response_rx) = mpsc::sync_channel(1);
let request =
ReaderRequest::CrossedBoundarySince { since, view: *view, respond: response_tx };
if self.reader_pool.dispatch(request).is_err() {
return Err(EngineError::Closing);
}
match response_rx.recv().map_err(|_| EngineError::Storage)? {
Ok(rows) => Ok(rows),
Err(err) => {
self.emit_sqlite_internal_error(&err);
Err(EngineError::Storage)
}
}
}
pub fn close(&self) -> Result<(), EngineError> {
self.closed.store(true, Ordering::SeqCst);
self.projection_runtime.stop();
self.reader_pool.shutdown();
if let Ok(mut connection) = self.connection.lock() {
if let Some(conn) = connection.as_ref() {
uninstall_profile_callback(conn);
}
connection.take();
}
if let Ok(mut contexts) = self.profile_contexts.lock() {
contexts.clear();
}
if let Ok(mut lock) = self.lock.lock() {
lock.take();
}
Ok(())
}
pub fn drain(&self, timeout_ms: u64) -> Result<(), EngineError> {
self.ensure_open()?;
if self.projection_runtime.wait_for_idle(timeout_ms) {
Ok(())
} else {
Err(EngineError::Scheduler)
}
}
#[must_use]
pub fn counters(&self) -> CounterSnapshot {
self.counters.snapshot()
}
pub fn set_profiling(&self, enabled: bool) -> Result<(), EngineError> {
self.profiling_enabled.store(enabled, Ordering::Relaxed);
Ok(())
}
pub fn set_slow_threshold_ms(&self, value: u64) -> Result<(), EngineError> {
self.slow_threshold_ms.store(value, Ordering::Relaxed);
Ok(())
}
#[must_use]
pub fn subscribe(&self, subscriber: Arc<dyn lifecycle::Subscriber>) -> Subscription {
self.subscribers.attach(subscriber)
}
#[cfg(debug_assertions)]
#[doc(hidden)]
pub fn reader_worker_count_for_test(&self) -> usize {
self.reader_pool.worker_count()
}
#[cfg(debug_assertions)]
#[doc(hidden)]
pub fn live_reader_worker_count_for_test(&self) -> usize {
self.reader_pool.live_count()
}
#[cfg(debug_assertions)]
#[doc(hidden)]
pub fn reader_lookaside_config_rcs_for_test(&self) -> Vec<i32> {
self.reader_lookaside_rcs.clone()
}
#[cfg(debug_assertions)]
#[doc(hidden)]
pub fn reader_lookaside_used_per_worker_for_test(&self) -> Vec<i32> {
self.reader_pool.lookaside_used_per_worker()
}
#[cfg(debug_assertions)]
#[doc(hidden)]
pub fn cache_status_per_worker_for_test(&self, label: &str) -> Vec<CacheStatusReply> {
self.reader_pool.cache_status_per_worker(label)
}
#[cfg(debug_assertions)]
#[doc(hidden)]
pub fn force_next_commit_failure_for_test(&self) {
self.force_next_commit_failure.store(true, Ordering::SeqCst);
}
#[cfg(debug_assertions)]
#[doc(hidden)]
pub fn force_next_projection_commit_failure_for_test(&self) {
self.projection_runtime.force_next_projection_commit_failure_for_test();
}
#[cfg(debug_assertions)]
#[doc(hidden)]
pub fn force_next_projection_storage_failure_for_test(&self) {
self.projection_runtime.force_next_projection_storage_failure_for_test();
}
#[cfg(debug_assertions)]
#[doc(hidden)]
pub fn pause_projection_commit_failure_cleanup_for_test(
&self,
reported: Arc<Barrier>,
release: Arc<Barrier>,
) {
self.projection_runtime.pause_projection_commit_failure_cleanup_for_test(reported, release);
}
#[cfg(debug_assertions)]
#[doc(hidden)]
pub fn acknowledge_projection_stop_for_test(&self, acknowledged: Arc<Barrier>) {
self.projection_runtime.acknowledge_projection_stop_for_test(acknowledged);
}
#[cfg(debug_assertions)]
#[doc(hidden)]
pub fn execute_for_test(&self, sql: &str) -> Result<(), EngineError> {
self.ensure_open()?;
let started = Instant::now();
{
let mut connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_mut().ok_or(EngineError::Closing)?;
connection.execute_batch(sql).map_err(|_| EngineError::Storage)?;
}
self.detect_slow(started, lifecycle::EventCategory::Search);
Ok(())
}
#[doc(hidden)]
#[cfg(debug_assertions)]
pub fn run_one_thread_poison_for_test(&self) -> Result<(), EngineError> {
self.ensure_open()?;
self.write(&[PreparedWrite::Node {
kind: "doc".to_string(),
body: "poison-fixture-seed".to_string(),
source_id: SourceId::engine_derived("poison-fixture"),
logical_id: None,
state: InitialState::Active,
reason: None,
valid_from: None,
valid_until: None,
}])?;
let poison_outcome: Mutex<Option<EngineError>> = Mutex::new(None);
let poison_thread_id: AtomicU64 = AtomicU64::new(0);
thread::scope(|scope| {
for _ in 0..4 {
scope.spawn(|| {
for _ in 0..4 {
let _ = self.search("poison-fixture-seed");
}
});
}
scope.spawn(|| {
let _ = self.write(&[PreparedWrite::Node {
kind: "doc".to_string(),
body: "writer-progress".to_string(),
source_id: SourceId::engine_derived("poison-fixture"),
logical_id: None,
state: InitialState::Active,
reason: None,
valid_from: None,
valid_until: None,
}]);
});
scope.spawn(|| {
poison_thread_id.store(1, Ordering::SeqCst);
if let Err(err) = self.write(&[]) {
*poison_outcome.lock().expect("poison_outcome lock") = Some(err);
}
});
});
let err = poison_outcome
.into_inner()
.expect("poison_outcome lock")
.expect("poison thread must produce a deterministic error");
let projection_state = match self.projection_status_for_test("doc") {
Ok(lifecycle::ProjectionStatus::Pending) => "Pending",
Ok(lifecycle::ProjectionStatus::Failed) => "Failed",
Ok(lifecycle::ProjectionStatus::UpToDate) => "UpToDate",
Err(_) => "UpToDate",
};
let context = lifecycle::StressFailureContext {
thread_group_id: poison_thread_id.load(Ordering::SeqCst),
op_kind: "write".to_string(),
last_error_chain: vec![err.stable_code().to_string(), err.to_string()],
projection_state: projection_state.to_string(),
};
self.subscribers.dispatch_stress_failure(&context);
Ok(())
}
#[doc(hidden)]
pub fn set_projection_scheduler_frozen_for_test(&self, frozen: bool) {
self.projection_runtime.set_frozen(frozen);
}
#[doc(hidden)]
pub fn set_projection_retry_delays_for_test(&self, delays_ms: &[u64]) {
self.projection_runtime.set_retry_delays_for_test(delays_ms);
}
#[doc(hidden)]
pub fn set_embed_timeout_ms_for_test(&self, timeout_ms: u64) {
self.projection_runtime.set_embed_timeout_ms_for_test(timeout_ms);
}
#[doc(hidden)]
pub fn set_embed_circuit_threshold_for_test(&self, threshold: u64) {
self.projection_runtime.set_embed_circuit_threshold_for_test(threshold);
}
#[doc(hidden)]
pub fn embed_circuit_open_for_test(&self) -> bool {
self.projection_runtime.embed_circuit_open_for_test()
}
#[doc(hidden)]
pub fn projection_status_for_test(
&self,
kind: &str,
) -> Result<lifecycle::ProjectionStatus, EngineError> {
self.ensure_open()?;
let connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_ref().ok_or(EngineError::Closing)?;
projection_status(connection, kind)
}
#[doc(hidden)]
pub fn has_vector_for_cursor_for_test(&self, cursor: u64) -> Result<bool, EngineError> {
self.ensure_open()?;
let connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_ref().ok_or(EngineError::Closing)?;
terminal_state_for_cursor(connection, cursor)
.map(|state| matches!(state.as_deref(), Some("up_to_date")))
.map_err(|_| EngineError::Storage)
}
#[doc(hidden)]
pub fn projection_failure_count_for_test(&self, cursor: u64) -> Result<u64, EngineError> {
self.ensure_open()?;
let connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_ref().ok_or(EngineError::Closing)?;
connection
.query_row(
"SELECT COUNT(*) FROM operational_mutations
WHERE collection_name = 'projection_failures'
AND record_key = ?1",
[cursor.to_string()],
|row| row.get::<_, u64>(0),
)
.map_err(|_| EngineError::Storage)
}
#[doc(hidden)]
pub fn set_provenance_row_cap_for_test(&self, cap: Option<u64>) {
self.provenance_row_cap.store(cap.unwrap_or(0), Ordering::Relaxed);
}
#[doc(hidden)]
pub fn provenance_row_count_for_test(&self) -> Result<u64, EngineError> {
self.ensure_open()?;
let connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_ref().ok_or(EngineError::Closing)?;
connection
.query_row("SELECT COUNT(*) FROM operational_mutations", [], |row| row.get::<_, u64>(0))
.map_err(|_| EngineError::Storage)
}
#[doc(hidden)]
pub fn oldest_provenance_record_key_for_test(
&self,
collection: &str,
) -> Result<Option<String>, EngineError> {
self.ensure_open()?;
let connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_ref().ok_or(EngineError::Closing)?;
connection
.query_row(
"SELECT record_key FROM operational_mutations
WHERE collection_name = ?1
ORDER BY id
LIMIT 1",
[collection],
|row| row.get::<_, String>(0),
)
.map(Some)
.or_else(|err| match err {
rusqlite::Error::QueryReturnedNoRows => Ok(None),
_ => Err(EngineError::Storage),
})
}
#[doc(hidden)]
pub fn configure_vector_kind_for_test(&self, kind: &str) -> Result<(), EngineError> {
self.ensure_open()?;
let mut connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_mut().ok_or(EngineError::Closing)?;
connection
.execute(
"INSERT OR REPLACE INTO _fathomdb_vector_kinds(kind, profile, created_at)
VALUES(?1, ?2, 0)",
params![kind, DEFAULT_VECTOR_PROFILE],
)
.map_err(|_| EngineError::Storage)?;
Ok(())
}
#[doc(hidden)]
pub fn secure_delete_enabled_for_test(&self) -> Result<bool, EngineError> {
self.ensure_open()?;
let connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_ref().ok_or(EngineError::Closing)?;
let value: i64 = connection
.query_row("PRAGMA secure_delete", [], |r| r.get(0))
.map_err(|_| EngineError::Storage)?;
Ok(value != 0)
}
#[cfg(debug_assertions)]
#[doc(hidden)]
pub fn reader_secure_delete_enabled_for_test(&self) -> Result<bool, EngineError> {
self.ensure_open()?;
let per_worker = self.reader_pool.secure_delete_per_worker();
if per_worker.is_empty() {
return Err(EngineError::Storage);
}
Ok(per_worker.iter().all(|&v| v == 1))
}
#[doc(hidden)]
pub fn runtime_secure_delete_enabled_for_test(&self) -> Result<bool, EngineError> {
self.ensure_open()?;
let connection = open_runtime_connection(&self.path).map_err(|_| EngineError::Storage)?;
let value: i64 = connection
.query_row("PRAGMA secure_delete", [], |r| r.get(0))
.map_err(|_| EngineError::Storage)?;
Ok(value != 0)
}
#[doc(hidden)]
pub fn write_canonical_row_with_kind_for_test(
&self,
kind: &str,
body: &str,
row_kind: RowKind,
) -> Result<WriteReceipt, EngineError> {
self.ensure_open()?;
let mut connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_mut().ok_or(EngineError::Closing)?;
let engine_provenance = SourceId::engine_derived(row_kind.as_str());
let unstranded = if self.runtime_embedder.is_some()
&& self.vector_kind_needs_enrolment(connection, kind, row_kind)?
{
self.enrol_and_unstrand(connection, &[kind])?
} else {
false
};
let cursor = self.next_cursor.load(Ordering::SeqCst).saturating_add(1);
let enqueued = {
let tx = connection.transaction().map_err(|_| EngineError::Storage)?;
tx.execute(
"INSERT INTO canonical_nodes(write_cursor, kind, body, source_id, logical_id, row_kind)
VALUES(?1, ?2, ?3, ?4, NULL, ?5)",
params![cursor, kind, body, engine_provenance.as_str(), row_kind.as_str()],
)
.map_err(|_| EngineError::Storage)?;
let enqueued = project_canonical_node_row(
&tx,
cursor,
kind,
body,
row_kind,
ProjectionPass::Write,
true,
)
.map_err(|_| EngineError::Storage)?;
advance_projection_cursor(&tx).map_err(|_| EngineError::Storage)?;
tx.commit().map_err(|_| EngineError::Storage)?;
enqueued
};
self.next_cursor.store(cursor, Ordering::SeqCst);
if enqueued || unstranded {
self.projection_runtime.notify_new_work();
}
Ok(WriteReceipt { cursor, row_cursors: vec![cursor], dangling_edge_endpoints: 0 })
}
#[doc(hidden)]
pub fn canonical_rows_with_row_kind_for_test(
&self,
row_kind: RowKind,
) -> Result<Vec<u64>, EngineError> {
self.ensure_open()?;
let connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_ref().ok_or(EngineError::Closing)?;
let mut stmt = connection
.prepare(
"SELECT write_cursor FROM canonical_nodes
WHERE row_kind = ?1 AND superseded_at IS NULL
ORDER BY write_cursor",
)
.map_err(|_| EngineError::Storage)?;
let cursors = stmt
.query_map(params![row_kind.as_str()], |row| row.get::<_, u64>(0))
.map_err(|_| EngineError::Storage)?
.collect::<rusqlite::Result<Vec<u64>>>()
.map_err(|_| EngineError::Storage)?;
Ok(cursors)
}
#[doc(hidden)]
pub fn bm25f_search(
&self,
query: &str,
plan: &Bm25fQueryPlan,
) -> Result<Vec<(u64, f64)>, EngineError> {
self.ensure_open()?;
let connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_ref().ok_or(EngineError::Closing)?;
bm25f_search_inner(connection, query, plan).map_err(|_| EngineError::Storage)
}
pub fn embed_text(&self, text: &str) -> Result<Vec<f32>, EngineError> {
self.ensure_open()?;
let embedder =
self.runtime_embedder.as_ref().cloned().ok_or(EngineError::EmbedderNotConfigured)?;
embedder.embed(text).map_err(map_runtime_embedder_error)
}
#[doc(hidden)]
pub fn write_vector_for_test(
&self,
kind: &str,
text: &str,
) -> Result<WriteReceipt, EngineError> {
self.ensure_open()?;
let embedder =
self.runtime_embedder.as_ref().cloned().ok_or(EngineError::EmbedderNotConfigured)?;
let mut connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_mut().ok_or(EngineError::Closing)?;
if !kind_is_vector_indexed(connection, kind)? {
return Err(EngineError::KindNotVectorIndexed);
}
let expected = default_profile_dimension(connection)?;
ensure_vector_partition(connection, expected).map_err(|_| EngineError::Storage)?;
let vector = embedder.embed(text).map_err(map_runtime_embedder_error)?;
let actual = u32::try_from(vector.len()).unwrap_or(u32::MAX);
if actual != expected {
return Err(EngineError::EmbedderDimensionMismatch { expected, actual });
}
let cursor = self.next_cursor.load(Ordering::SeqCst).saturating_add(1);
let blob = encode_vector_blob(&vector);
let bin_blob = if identity_requires_mean_centering(&self.runtime_embedder_identity) {
match read_pinned_mean_vec(connection, self.runtime_embedder_identity.dimension)? {
Some(mean) => encode_vector_blob(&subtract_mean(&vector, &mean)),
None => blob.clone(),
}
} else {
blob.clone()
};
let source_type = resolve_source_type(kind)?;
let now_unix =
SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs() as i64;
let pin_event = {
let runtime = &self.projection_runtime.shared;
let mut accumulator =
runtime.mean_accumulator.lock().map_err(|_| EngineError::Storage)?;
if let Some(acc) = accumulator.as_mut() {
acc.add(&vector);
if acc.count() >= MEAN_VEC_PIN_THRESHOLD {
let mean = acc.materialize();
*accumulator = None;
Some(mean)
} else {
None
}
} else {
None
}
};
let tx = connection.transaction().map_err(|_| EngineError::Storage)?;
tx.execute(
"INSERT INTO _fathomdb_vector_rows(rowid, kind, write_cursor) VALUES(?1, ?2, ?3)",
params![cursor, kind, cursor],
)
.map_err(|_| EngineError::Storage)?;
let (cols_sql, ph_sql, attr_vals) =
vector_attr_insert_fragments(&tx, "", 7).map_err(|_| EngineError::Storage)?;
let sql = format!(
"INSERT INTO vector_default(
rowid, embedding, embedding_bin, source_type, kind, created_at, status{cols_sql}
) VALUES(?1, ?2, vec_quantize_binary(?3), ?4, ?5, ?6, ''{ph_sql})"
);
let mut pv: Vec<rusqlite::types::Value> = vec![
rusqlite::types::Value::Integer(cursor as i64),
rusqlite::types::Value::Blob(blob.clone()),
rusqlite::types::Value::Blob(bin_blob.clone()),
rusqlite::types::Value::Text(source_type.to_string()),
rusqlite::types::Value::Text(kind.to_string()),
rusqlite::types::Value::Integer(now_unix),
];
pv.extend(attr_vals);
tx.execute(&sql, rusqlite::params_from_iter(pv.iter()))
.map_err(|_| EngineError::Storage)?;
let mut emitted_event: Option<EmbedderEvent> = None;
if let Some(mean_vec) = pin_event {
let mean_bytes = encode_vector_blob(&mean_vec);
tx.execute(
"UPDATE _fathomdb_embedder_profiles SET mean_vec = ?1 WHERE profile = 'default'",
params![mean_bytes],
)
.map_err(|_| EngineError::Storage)?;
let rows: Vec<(i64, Vec<u8>)> = {
let mut statement = tx
.prepare("SELECT rowid, embedding FROM vector_default ORDER BY rowid")
.map_err(|_| EngineError::Storage)?;
let mapped = statement
.query_map([], |row| Ok((row.get::<_, i64>(0)?, row.get::<_, Vec<u8>>(1)?)))
.map_err(|_| EngineError::Storage)?;
let mut out = Vec::new();
for r in mapped {
out.push(r.map_err(|_| EngineError::Storage)?);
}
out
};
let (doc_count, _) = run_pin_and_requantize_pass(&tx, &rows, &mean_vec)?;
emitted_event = Some(EmbedderEvent::MeanVecPinned {
dim: u32::try_from(mean_vec.len()).unwrap_or(u32::MAX),
doc_count,
});
}
tx.commit().map_err(|_| EngineError::Storage)?;
if let Some(ev) = emitted_event {
if let Ok(mut events) = self.projection_runtime.shared.pending_events.lock() {
events.push(ev);
}
}
self.next_cursor.store(cursor, Ordering::SeqCst);
Ok(WriteReceipt { cursor, row_cursors: vec![cursor], dangling_edge_endpoints: 0 })
}
#[doc(hidden)]
pub fn drain_mean_centering_events_for_test(&self) -> Result<Vec<EmbedderEvent>, EngineError> {
self.ensure_open()?;
let mut events = self
.projection_runtime
.shared
.pending_events
.lock()
.map_err(|_| EngineError::Storage)?;
let out = std::mem::take(&mut *events);
Ok(out)
}
pub fn drain_embedder_events(&self) -> Result<Vec<EmbedderEvent>, EngineError> {
self.ensure_open()?;
let mut events = self
.projection_runtime
.shared
.pending_events
.lock()
.map_err(|_| EngineError::Storage)?;
Ok(std::mem::take(&mut *events))
}
#[cfg(feature = "operator")]
pub fn recompute_mean(&self) -> Result<MeanRecomputeReport, EngineError> {
self.ensure_open()?;
let identity = self.runtime_embedder_identity.clone();
if !identity_requires_mean_centering(&identity) {
return Err(EngineError::EmbedderNotConfigured);
}
let report = {
let _gate = self
.projection_runtime
.shared
.commit_gate
.lock()
.unwrap_or_else(|p| p.into_inner());
let mut connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_mut().ok_or(EngineError::Closing)?;
let tx = connection.transaction().map_err(|_| EngineError::Storage)?;
#[cfg(debug_assertions)]
let fail = self
.projection_runtime
.shared
.force_recompute_failure
.swap(false, Ordering::SeqCst);
#[cfg(not(debug_assertions))]
let fail = false;
let report = recompute_mean_in_tx_inner(&tx, &identity, fail)?;
tx.commit().map_err(|_| EngineError::Storage)?;
report
};
if let Ok(mut events) = self.projection_runtime.shared.pending_events.lock() {
events.push(EmbedderEvent::MeanVecRecomputed {
dim: report.dim,
doc_count: report.doc_count_requantized,
trigger: MeanRecomputeTrigger::Manual,
});
}
Ok(report)
}
#[doc(hidden)]
pub fn set_search_limit_for_test(&self, limit: usize) {
self.projection_runtime.shared.search_limit_override.store(limit, Ordering::SeqCst);
}
#[doc(hidden)]
pub fn set_recency_reweight_enabled_for_test(&self, enabled: bool) {
self.projection_runtime.shared.recency_reweight_enabled.store(enabled, Ordering::SeqCst);
}
#[doc(hidden)]
pub fn set_importance_reweight_enabled_for_test(&self, enabled: bool) {
self.projection_runtime.shared.importance_reweight_enabled.store(enabled, Ordering::SeqCst);
}
pub fn write_node_importance(
&self,
write_cursor: u64,
importance: f64,
) -> Result<(), EngineError> {
if !importance.is_finite() || !(0.0..=1.0).contains(&importance) {
return Err(EngineError::WriteValidation);
}
self.ensure_open()?;
let mut connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_mut().ok_or(EngineError::Closing)?;
connection
.execute(
"UPDATE canonical_nodes SET importance = ?1 WHERE write_cursor = ?2",
params![importance, write_cursor],
)
.map_err(|_| EngineError::Storage)?;
Ok(())
}
pub fn node_importance(&self, write_cursor: u64) -> Result<Option<f64>, EngineError> {
self.ensure_open()?;
let connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_ref().ok_or(EngineError::Closing)?;
connection
.query_row(
"SELECT importance FROM canonical_nodes WHERE write_cursor = ?1 LIMIT 1",
params![write_cursor],
|r| r.get::<_, Option<f64>>(0),
)
.map_err(|_| EngineError::Storage)
}
#[doc(hidden)]
pub fn set_vector_stage_only_for_test(&self, enabled: bool) {
self.projection_runtime.shared.vector_stage_only_for_test.store(enabled, Ordering::SeqCst);
}
#[doc(hidden)]
#[cfg(debug_assertions)]
pub fn force_next_recompute_failure_for_test(&self) {
self.projection_runtime.shared.force_recompute_failure.store(true, Ordering::SeqCst);
}
#[doc(hidden)]
pub fn vector_row_count_for_test(&self) -> Result<u64, EngineError> {
self.ensure_open()?;
let connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_ref().ok_or(EngineError::Closing)?;
connection
.query_row("SELECT COUNT(*) FROM vector_default", [], |row| row.get::<_, u64>(0))
.map_err(|_| EngineError::Storage)
}
#[doc(hidden)]
pub fn read_vector_blob_for_test(&self, rowid: i64) -> Result<Vec<u8>, EngineError> {
self.ensure_open()?;
let connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_ref().ok_or(EngineError::Closing)?;
connection
.query_row("SELECT embedding FROM vector_default WHERE rowid = ?1", [rowid], |row| {
row.get::<_, Vec<u8>>(0)
})
.map_err(|_| EngineError::Storage)
}
#[doc(hidden)]
pub fn read_vector_bin_for_test(&self, rowid: i64) -> Result<Vec<u8>, EngineError> {
self.ensure_open()?;
let connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_ref().ok_or(EngineError::Closing)?;
connection
.query_row(
"SELECT embedding_bin FROM vector_default WHERE rowid = ?1",
[rowid],
|row| row.get::<_, Vec<u8>>(0),
)
.map_err(|_| EngineError::Storage)
}
#[doc(hidden)]
pub fn query_i64_col_for_test(&self, sql: &str) -> Result<Vec<i64>, EngineError> {
self.ensure_open()?;
let connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_ref().ok_or(EngineError::Closing)?;
let mut stmt = connection.prepare(sql).map_err(|_| EngineError::Storage)?;
let rows =
stmt.query_map([], |row| row.get::<_, i64>(0)).map_err(|_| EngineError::Storage)?;
rows.collect::<rusqlite::Result<Vec<i64>>>().map_err(|_| EngineError::Storage)
}
#[doc(hidden)]
pub fn query_text_col_for_test(&self, sql: &str) -> Result<Vec<String>, EngineError> {
self.ensure_open()?;
let connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_ref().ok_or(EngineError::Closing)?;
let mut stmt = connection.prepare(sql).map_err(|_| EngineError::Storage)?;
let rows =
stmt.query_map([], |row| row.get::<_, String>(0)).map_err(|_| EngineError::Storage)?;
rows.collect::<rusqlite::Result<Vec<String>>>().map_err(|_| EngineError::Storage)
}
#[doc(hidden)]
pub fn default_embedder_profile_for_test(&self) -> Result<EmbedderIdentity, EngineError> {
self.ensure_open()?;
let connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_ref().ok_or(EngineError::Closing)?;
load_default_profile(connection).map_err(|_| EngineError::Storage)
}
#[cfg(feature = "operator")]
pub fn check_integrity(
&self,
opts: CheckIntegrityOpts,
) -> Result<IntegrityReport, EngineError> {
self.ensure_open()?;
let connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_ref().ok_or(EngineError::Closing)?;
Ok(IntegrityReport {
physical: physical_section(connection, opts.full),
logical: logical_section(connection),
semantic: semantic_section(connection),
})
}
#[cfg(feature = "operator")]
pub fn safe_export(
&self,
out: &Path,
manifest: &Path,
) -> Result<SafeExportArtifact, EngineError> {
self.ensure_open()?;
{
let connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_ref().ok_or(EngineError::Closing)?;
let target = out.to_string_lossy().to_string();
connection
.execute("VACUUM INTO ?1", params![target])
.map_err(|_| EngineError::Storage)?;
}
let bytes = std::fs::read(out).map_err(|_| EngineError::Storage)?;
let digest = sha2::Sha256::digest(&bytes);
let sha256_hex = hex_encode(digest.as_slice());
let export_abs = out.canonicalize().unwrap_or_else(|_| out.to_path_buf());
let manifest_json = serde_json::json!({
"export_path": export_abs.to_string_lossy(),
"sha256": sha256_hex,
"byte_count": bytes.len() as u64,
});
let manifest_bytes =
serde_json::to_vec_pretty(&manifest_json).map_err(|_| EngineError::Storage)?;
std::fs::write(manifest, &manifest_bytes).map_err(|_| EngineError::Storage)?;
Ok(SafeExportArtifact {
export_path: out.to_path_buf(),
manifest_path: manifest.to_path_buf(),
manifest_sha256: sha256_hex,
})
}
#[cfg(feature = "operator")]
pub fn rebuild_projections(&self) -> Result<RebuildReport, EngineError> {
self.ensure_open()?;
self.run_rebuild(true, RebuildKind::Projections)
}
#[cfg(feature = "operator")]
pub fn rebuild_vec0(&self) -> Result<RebuildReport, EngineError> {
self.ensure_open()?;
self.run_rebuild(false, RebuildKind::Vec0)
}
#[cfg(feature = "operator")]
pub fn trace_source_ref(&self, source_id: &str) -> Result<TraceReport, EngineError> {
self.ensure_open()?;
if source_id.is_empty() {
return Err(EngineError::WriteValidation);
}
let connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_ref().ok_or(EngineError::Closing)?;
let mut events: Vec<TraceEvent> = Vec::new();
let mut nodes = connection
.prepare(
"SELECT write_cursor, kind FROM canonical_nodes WHERE source_id = ?1
ORDER BY write_cursor",
)
.map_err(|_| EngineError::Storage)?;
let node_rows = nodes
.query_map([source_id], |row| {
Ok(TraceEvent {
write_cursor: row.get::<_, i64>(0)? as u64,
kind: row.get::<_, String>(1)?,
table: "canonical_nodes",
})
})
.map_err(|_| EngineError::Storage)?;
for row in node_rows {
events.push(row.map_err(|_| EngineError::Storage)?);
}
let mut edges = connection
.prepare(
"SELECT write_cursor, kind FROM canonical_edges WHERE source_id = ?1
ORDER BY write_cursor",
)
.map_err(|_| EngineError::Storage)?;
let edge_rows = edges
.query_map([source_id], |row| {
Ok(TraceEvent {
write_cursor: row.get::<_, i64>(0)? as u64,
kind: row.get::<_, String>(1)?,
table: "canonical_edges",
})
})
.map_err(|_| EngineError::Storage)?;
for row in edge_rows {
events.push(row.map_err(|_| EngineError::Storage)?);
}
events.sort_by_key(|e| e.write_cursor);
Ok(TraceReport { source_ref: source_id.to_string(), events })
}
fn resolve_lifecycle_target(id: &str) -> Result<String, EngineError> {
match IdSpace::parse(id) {
Some(parsed) => match parsed.space {
IdSpaceKind::Logical => Ok(parsed.value),
other => Err(EngineError::NotLifecycleAddressable { id_space: other }),
},
None => Ok(id.to_string()),
}
}
pub fn transition(
&self,
logical_id: &str,
to_state: LifecycleState,
reason: Option<String>,
) -> Result<(), EngineError> {
self.ensure_open()?;
let lid = Self::resolve_lifecycle_target(logical_id)?;
self.drain(LIFECYCLE_DRAIN_TIMEOUT_MS)?;
let mut connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_mut().ok_or(EngineError::Closing)?;
let tx = connection.transaction().map_err(|_| EngineError::Storage)?;
let current: Option<(String, i64, String)> = tx
.query_row(
"SELECT state, write_cursor, body FROM canonical_nodes \
WHERE logical_id = ?1 AND superseded_at IS NULL",
params![lid],
|r| Ok((r.get::<_, String>(0)?, r.get::<_, i64>(1)?, r.get::<_, String>(2)?)),
)
.optional()
.map_err(|_| EngineError::Storage)?;
let from_state = match ¤t {
Some((s, _, _)) => LifecycleState::from_str_opt(s).ok_or(EngineError::Storage)?,
None => LifecycleState::Purged,
};
if !is_legal_transition_move(from_state, to_state) {
return Err(EngineError::IllegalTransition {
from_state,
to_state,
legal: from_state.legal_next_states(),
});
}
let new_reason: Option<String> = match to_state {
LifecycleState::Active => None,
_ => reason,
};
tx.execute(
"UPDATE canonical_nodes SET state = ?1, reason = ?2 \
WHERE logical_id = ?3 AND superseded_at IS NULL",
params![to_state.as_str(), new_reason, lid],
)
.map_err(|_| EngineError::Storage)?;
if let Some((cursor, body)) = current.as_ref().map(|(_, c, b)| (*c, b.as_str())) {
purge_row_projections_for_cursor_in(
&tx,
cursor,
&[ProjectionClass::Attribute, ProjectionClass::PropertyFts],
)
.map_err(|_| EngineError::Storage)?;
if matches!(to_state, LifecycleState::Active) {
project_node_attributes(&tx, cursor, body).map_err(|_| EngineError::Storage)?;
}
}
tx.commit().map_err(|_| EngineError::Storage)?;
self.counters.record_admin();
Ok(())
}
pub fn configure_projections(
&self,
specs: &[ProjectionSpec],
drop: &[String],
) -> Result<ProjectionDelta, EngineError> {
self.ensure_open()?;
self.drain(LIFECYCLE_DRAIN_TIMEOUT_MS)?;
let dense_arm_live = self.runtime_embedder.is_some();
let (delta, enqueued_backfill) = {
let mut connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_mut().ok_or(EngineError::Closing)?;
let tx = connection.transaction().map_err(|_| EngineError::Storage)?;
let applied = apply_projection_config(&tx, specs, drop, dense_arm_live)?;
tx.commit().map_err(|_| EngineError::Storage)?;
applied
};
if enqueued_backfill {
self.projection_runtime.notify_new_work();
}
self.counters.record_admin();
Ok(delta)
}
pub fn read_projections(&self) -> Result<Vec<ProjectionSpec>, EngineError> {
self.ensure_open()?;
let connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_ref().ok_or(EngineError::Closing)?;
let registry = load_projection_registry(connection).map_err(|_| EngineError::Storage)?;
let mut readiness: Option<DenseReadiness> = None;
let mut specs: Vec<ProjectionSpec> =
registry.iter().map(|(name, stored)| stored.to_spec(name)).collect();
for spec in &mut specs {
if let Some(vector) = spec.vector.as_mut() {
let value = match readiness {
Some(value) => value,
None => {
let value = derive_dense_readiness(connection)?;
readiness = Some(value);
value
}
};
vector.dense_readiness = Some(value);
}
}
Ok(specs)
}
pub fn purge(&self, logical_id: &str) -> Result<(), EngineError> {
self.ensure_open()?;
let lid = Self::resolve_lifecycle_target(logical_id)?;
self.drain(LIFECYCLE_DRAIN_TIMEOUT_MS)?;
self.projection_runtime.set_frozen(true);
let outcome = self.drain(LIFECYCLE_DRAIN_TIMEOUT_MS).and_then(|()| self.purge_inner(&lid));
self.projection_runtime.set_frozen(false);
outcome?;
self.complete_erasure_at_rest("purge")
}
fn purge_inner(&self, lid: &str) -> Result<(), EngineError> {
let mut connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_mut().ok_or(EngineError::Closing)?;
let tx = connection.transaction().map_err(|_| EngineError::Storage)?;
let current: Option<String> = tx
.query_row(
"SELECT state FROM canonical_nodes \
WHERE logical_id = ?1 AND superseded_at IS NULL",
params![lid],
|r| r.get::<_, String>(0),
)
.optional()
.map_err(|_| EngineError::Storage)?;
let from_state = match current {
None => {
tx.commit().map_err(|_| EngineError::Storage)?;
return Ok(());
}
Some(s) => LifecycleState::from_str_opt(&s).ok_or(EngineError::Storage)?,
};
if from_state != LifecycleState::Deleted {
return Err(EngineError::IllegalTransition {
from_state,
to_state: LifecycleState::Purged,
legal: from_state.legal_next_states(),
});
}
let node_cursors: Vec<i64> = {
let mut stmt = tx
.prepare("SELECT write_cursor FROM canonical_nodes WHERE logical_id = ?1")
.map_err(|_| EngineError::Storage)?;
let rows = stmt
.query_map(params![lid], |row| row.get::<_, i64>(0))
.map_err(|_| EngineError::Storage)?;
rows.collect::<rusqlite::Result<Vec<_>>>().map_err(|_| EngineError::Storage)?
};
let edge_cursors: Vec<i64> = {
let mut stmt = tx
.prepare(
"SELECT write_cursor FROM canonical_edges \
WHERE from_id = ?1 OR to_id = ?1",
)
.map_err(|_| EngineError::Storage)?;
let rows = stmt
.query_map(params![lid], |row| row.get::<_, i64>(0))
.map_err(|_| EngineError::Storage)?;
rows.collect::<rusqlite::Result<Vec<_>>>().map_err(|_| EngineError::Storage)?
};
let erased_stable_ids = collect_erased_stable_ids(
&tx,
"SELECT logical_id, body FROM canonical_nodes WHERE logical_id = ?1",
"SELECT logical_id, body FROM canonical_edges WHERE from_id = ?1 OR to_id = ?1",
lid,
)?;
for cursor in node_cursors.iter().chain(edge_cursors.iter()) {
erase_row_projections(&tx, *cursor).map_err(|_| EngineError::Storage)?;
}
tx.execute("DELETE FROM canonical_nodes WHERE logical_id = ?1", params![lid])
.map_err(|_| EngineError::Storage)?;
tx.execute("DELETE FROM canonical_edges WHERE from_id = ?1 OR to_id = ?1", params![lid])
.map_err(|_| EngineError::Storage)?;
let pending_cursor = self.next_cursor.load(Ordering::SeqCst).saturating_add(1);
let enqueued =
self.telemetry_enabled.load(Ordering::Acquire) && !erased_stable_ids.is_empty();
if enqueued {
enqueue_pending_redaction(&tx, "purge", &erased_stable_ids, pending_cursor)?;
}
tx.commit().map_err(|_| EngineError::Storage)?;
if enqueued {
self.next_cursor.store(pending_cursor, Ordering::SeqCst);
}
self.counters.record_admin();
Ok(())
}
pub fn erase_source(&self, source_id: &str) -> Result<ExciseReport, EngineError> {
let _validated = SourceId::new(source_id)?;
self.erase_source_shared("erase_source", source_id)
}
#[cfg(feature = "operator")]
pub fn excise_source(&self, source_id: &str) -> Result<ExciseReport, EngineError> {
if source_id.is_empty() {
self.ensure_open()?;
return Err(EngineError::WriteValidation);
}
self.erase_source_shared("excise_source", source_id)
}
fn erase_source_shared(
&self,
verb: &'static str,
source_id: &str,
) -> Result<ExciseReport, EngineError> {
self.ensure_open()?;
self.drain(LIFECYCLE_DRAIN_TIMEOUT_MS)?;
self.projection_runtime.set_frozen(true);
let drain_result = self.drain(LIFECYCLE_DRAIN_TIMEOUT_MS);
let outcome = drain_result.and_then(|()| self.excise_source_inner(verb, source_id));
self.projection_runtime.set_frozen(false);
let report = outcome?;
self.complete_erasure_at_rest(verb)?;
Ok(report)
}
#[cfg(feature = "operator")]
pub fn verify_embedder(
&self,
supplied_identity: &str,
supplied_dimension: u32,
) -> Result<VerifyEmbedderReport, EngineError> {
self.ensure_open()?;
let connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_ref().ok_or(EngineError::Closing)?;
let stored = load_default_profile(connection).map_err(|_| EngineError::Storage)?;
let stored_identity = format!("{}:{}", stored.name, stored.revision);
let identity_match = stored_identity == supplied_identity;
let dimension_match = stored.dimension == supplied_dimension;
let status = match (identity_match, dimension_match) {
(true, true) => VerifyEmbedderStatus::Match,
(false, true) => VerifyEmbedderStatus::IdentityMismatch,
(true, false) => VerifyEmbedderStatus::DimensionMismatch,
(false, false) => VerifyEmbedderStatus::BothMismatch,
};
Ok(VerifyEmbedderReport {
stored_identity,
stored_dimension: stored.dimension,
supplied_identity: supplied_identity.to_string(),
supplied_dimension,
status,
})
}
#[cfg(feature = "operator")]
pub fn dump_schema(&self) -> Result<DumpSchemaReport, EngineError> {
self.ensure_open()?;
let connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_ref().ok_or(EngineError::Closing)?;
let user_version: u32 = connection
.query_row("PRAGMA user_version", [], |row| row.get(0))
.map_err(|_| EngineError::Storage)?;
let tables = read_schema_objects(connection, "table")?;
let indexes = read_schema_objects(connection, "index")?;
Ok(DumpSchemaReport { user_version, tables: order_canonical_first(tables), indexes })
}
#[cfg(feature = "operator")]
pub fn dump_row_counts(&self) -> Result<DumpRowCountsReport, EngineError> {
self.ensure_open()?;
let connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_ref().ok_or(EngineError::Closing)?;
let mut counts = Vec::with_capacity(CANONICAL_TABLES.len());
for name in CANONICAL_TABLES {
let rows: u64 = connection
.query_row(&format!("SELECT COUNT(*) FROM {name}"), [], |row| row.get(0))
.map_err(|_| EngineError::Storage)?;
counts.push(TableRowCount { name: (*name).to_string(), rows });
}
Ok(DumpRowCountsReport { counts })
}
#[cfg(feature = "operator")]
pub fn orphan_provenance(&self) -> Result<OrphanProvenanceReport, EngineError> {
self.ensure_open()?;
let connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_ref().ok_or(EngineError::Closing)?;
let mut stmt = connection
.prepare(
"SELECT source_id,
COUNT(*) AS rows_total,
SUM(CASE WHEN logical_id IS NOT NULL THEN 1 ELSE 0 END) AS governed,
SUM(purge_addressable) AS purge_addressable
FROM (SELECT source_id,
logical_id,
CASE WHEN logical_id IS NOT NULL THEN 1 ELSE 0 END
AS purge_addressable
FROM canonical_nodes
UNION ALL
SELECT source_id, logical_id, 0 AS purge_addressable
FROM canonical_edges)
GROUP BY source_id
ORDER BY rows_total DESC, source_id",
)
.map_err(|_| EngineError::Storage)?;
let rows = stmt
.query_map([], |row| {
let source_id: Option<String> = row.get(0)?;
let rows: i64 = row.get(1)?;
let governed: i64 = row.get(2)?;
let purge_addressable: i64 = row.get(3)?;
Ok((source_id, rows, governed, purge_addressable))
})
.map_err(|_| EngineError::Storage)?;
let mut sources = Vec::new();
let mut total_rows: u64 = 0;
let mut unerasable_rows: u64 = 0;
for row in rows {
let (source_id, rows, governed, purge_addressable) =
row.map_err(|_| EngineError::Storage)?;
let rows = u64::try_from(rows).unwrap_or(0);
let governed_rows = u64::try_from(governed).unwrap_or(0);
let purge_addressable = u64::try_from(purge_addressable).unwrap_or(0);
total_rows = total_rows.saturating_add(rows);
if source_id.is_none() {
unerasable_rows =
unerasable_rows.saturating_add(rows - purge_addressable.min(rows));
}
let reserved = source_id.as_deref().is_some_and(|s| s.starts_with('_'));
sources.push(OrphanProvenanceSource { source_id, rows, governed_rows, reserved });
}
Ok(OrphanProvenanceReport { sources, total_rows, unerasable_rows })
}
#[cfg(feature = "operator")]
pub fn dump_profile(&self) -> Result<DumpProfileReport, EngineError> {
self.ensure_open()?;
let connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_ref().ok_or(EngineError::Closing)?;
let stored = load_default_profile(connection).map_err(|_| EngineError::Storage)?;
let mut stmt = connection
.prepare("SELECT kind FROM _fathomdb_vector_kinds ORDER BY kind")
.map_err(|_| EngineError::Storage)?;
let rows =
stmt.query_map([], |row| row.get::<_, String>(0)).map_err(|_| EngineError::Storage)?;
let mut vectorized_kinds = Vec::new();
for row in rows {
vectorized_kinds.push(row.map_err(|_| EngineError::Storage)?);
}
Ok(DumpProfileReport {
embedder_identity: format!("{}:{}", stored.name, stored.revision),
embedder_dimension: stored.dimension,
vectorized_kinds,
})
}
#[cfg(feature = "operator")]
pub fn truncate_wal(&self) -> Result<TruncateWalReport, EngineError> {
self.ensure_open()?;
self.wal_checkpoint_truncate_once(true)
}
fn wal_checkpoint_truncate_once(
&self,
honor_busy_timeout: bool,
) -> Result<TruncateWalReport, EngineError> {
let connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_ref().ok_or(EngineError::Closing)?;
let restore_timeout_ms: Option<i64> = if honor_busy_timeout {
None
} else {
let previous: i64 = connection
.query_row("PRAGMA busy_timeout", [], |row| row.get(0))
.map_err(|_| EngineError::Storage)?;
connection.busy_timeout(Duration::ZERO).map_err(|_| EngineError::Storage)?;
Some(previous)
};
let checkpoint: rusqlite::Result<(i64, i64, i64)> =
connection.query_row("PRAGMA wal_checkpoint(TRUNCATE)", [], |row| {
Ok((row.get(0)?, row.get(1)?, row.get(2)?))
});
if let Some(previous) = restore_timeout_ms {
let previous = u64::try_from(previous.max(0)).unwrap_or(0);
connection
.busy_timeout(Duration::from_millis(previous))
.map_err(|_| EngineError::Storage)?;
}
let (busy, log_frames, checkpointed_frames) =
checkpoint.map_err(|_| EngineError::Storage)?;
let status = if busy == 0 { TruncateWalStatus::Done } else { TruncateWalStatus::Busy };
Ok(TruncateWalReport {
status,
busy: busy.max(0) as u32,
log_frames: log_frames.max(0) as u32,
checkpointed_frames: checkpointed_frames.max(0) as u32,
})
}
fn complete_erasure_at_rest(&self, verb: &'static str) -> Result<(), EngineError> {
self.discharge_pending_redactions(verb)?;
let mut last: Option<TruncateWalReport> = None;
for attempt in 0..ERASURE_WAL_TRUNCATE_ATTEMPTS {
let report = self.wal_checkpoint_truncate_once(false)?;
if report.status == TruncateWalStatus::Done {
return Ok(());
}
last = Some(report);
if attempt + 1 < ERASURE_WAL_TRUNCATE_ATTEMPTS {
std::thread::sleep(Duration::from_millis(ERASURE_WAL_TRUNCATE_BACKOFF_MS));
}
}
let frames = last.map_or(0, |r| r.log_frames);
Err(EngineError::ErasureIncomplete {
stage: "wal_checkpoint".to_string(),
detail: format!(
"`{verb}` deleted its rows, but `wal_checkpoint(TRUNCATE)` reported BUSY on all \
{ERASURE_WAL_TRUNCATE_ATTEMPTS} attempts ({frames} frames still in the log) — a \
concurrent reader is pinning a WAL snapshot, so the erased bytes remain readable \
in the `-wal` file. Retry once the reader has finished."
),
})
}
fn discharge_pending_redactions(&self, verb: &'static str) -> Result<(), EngineError> {
let pending = self.load_pending_redactions()?;
if pending.is_empty() {
return Ok(());
}
let mut ids: Vec<String> =
pending.iter().flat_map(|(_, ids)| ids.iter().cloned()).collect();
ids.sort_unstable();
ids.dedup();
if !self.telemetry_enabled.load(Ordering::Acquire) {
return Err(EngineError::ErasureIncomplete {
stage: "telemetry_redaction".to_string(),
detail: format!(
"`{verb}` has {} outstanding telemetry redaction(s) covering {} erased \
stable id(s), but no telemetry sink is attached to this engine — the ids \
cannot be removed from the sink file. Re-enable telemetry on the same sink \
path and retry.",
pending.len(),
ids.len()
),
});
}
self.redact_telemetry_stable_ids(verb, &ids)?;
let row_ids: Vec<i64> = pending.iter().map(|(row_id, _)| *row_id).collect();
self.clear_pending_redactions(&row_ids)
}
fn load_pending_redactions(&self) -> Result<Vec<(i64, Vec<String>)>, EngineError> {
let connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_ref().ok_or(EngineError::Closing)?;
let mut stmt = connection
.prepare(
"SELECT id, payload_json FROM operational_mutations \
WHERE collection_name = ?1 ORDER BY id",
)
.map_err(|_| EngineError::Storage)?;
let rows = stmt
.query_map([ERASURE_PENDING_REDACTION_COLLECTION], |row| {
Ok((row.get::<_, i64>(0)?, row.get::<_, String>(1)?))
})
.map_err(|_| EngineError::Storage)?;
let mut pending = Vec::new();
for row in rows {
let (row_id, payload) = row.map_err(|_| EngineError::Storage)?;
let ids = serde_json::from_str::<serde_json::Value>(&payload)
.ok()
.and_then(|v| v.get("erased_stable_ids").cloned())
.and_then(|v| serde_json::from_value::<Vec<String>>(v).ok())
.unwrap_or_default();
pending.push((row_id, ids));
}
Ok(pending)
}
fn clear_pending_redactions(&self, row_ids: &[i64]) -> Result<(), EngineError> {
if row_ids.is_empty() {
return Ok(());
}
let connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_ref().ok_or(EngineError::Closing)?;
let mut stmt = connection
.prepare("DELETE FROM operational_mutations WHERE id = ?1")
.map_err(|_| EngineError::Storage)?;
for row_id in row_ids {
stmt.execute([row_id]).map_err(|_| EngineError::Storage)?;
}
Ok(())
}
fn redact_telemetry_stable_ids(
&self,
verb: &'static str,
erased_stable_ids: &[String],
) -> Result<(), EngineError> {
if erased_stable_ids.is_empty() || !self.telemetry_enabled.load(Ordering::Acquire) {
return Ok(());
}
let guard = self.telemetry.lock().map_err(|_| EngineError::Storage)?;
let Some(sink) = guard.as_ref() else { return Ok(()) };
let erased: std::collections::HashSet<&str> =
erased_stable_ids.iter().map(String::as_str).collect();
match redact_jsonl_stable_ids(&sink.path, &erased) {
Ok(()) => Ok(()),
Err(err) => Err(EngineError::ErasureIncomplete {
stage: "telemetry_redaction".to_string(),
detail: if err.kind() == std::io::ErrorKind::NotFound {
format!(
"`{verb}` deleted its rows, but the telemetry sink {} no longer exists, \
so the erased stable ids could not be redacted from it. A missing path \
does NOT prove the sink was deleted — if it was rotated or moved aside, \
the erased ids are still readable under its new name. The pending \
redaction is durable: restore the sink at this path and retry (if the \
sink really was destroyed, an empty file at this path discharges the \
obligation).",
sink.path.display()
)
} else {
format!(
"`{verb}` deleted its rows, but the erased stable ids could not be \
redacted from the telemetry sink {}: {err}",
sink.path.display()
)
},
}),
}
}
fn excise_source_inner(
&self,
verb: &'static str,
source_id: &str,
) -> Result<ExciseReport, EngineError> {
let mut connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_mut().ok_or(EngineError::Closing)?;
let tx = connection.transaction().map_err(|_| EngineError::Storage)?;
let node_cursors: Vec<i64> = {
let mut stmt = tx
.prepare("SELECT write_cursor FROM canonical_nodes WHERE source_id = ?1")
.map_err(|_| EngineError::Storage)?;
let rows = stmt
.query_map([source_id], |row| row.get::<_, i64>(0))
.map_err(|_| EngineError::Storage)?;
rows.collect::<rusqlite::Result<Vec<_>>>().map_err(|_| EngineError::Storage)?
};
let edge_cursors: Vec<i64> = {
let mut stmt = tx
.prepare("SELECT write_cursor FROM canonical_edges WHERE source_id = ?1")
.map_err(|_| EngineError::Storage)?;
let rows = stmt
.query_map([source_id], |row| row.get::<_, i64>(0))
.map_err(|_| EngineError::Storage)?;
rows.collect::<rusqlite::Result<Vec<_>>>().map_err(|_| EngineError::Storage)?
};
let erased_stable_ids = collect_erased_stable_ids(
&tx,
"SELECT logical_id, body FROM canonical_nodes WHERE source_id = ?1",
"SELECT logical_id, body FROM canonical_edges WHERE source_id = ?1",
source_id,
)?;
let mut shadow_invalidated: u64 = 0;
for cursor in node_cursors.iter().chain(edge_cursors.iter()) {
shadow_invalidated = shadow_invalidated.saturating_add(
erase_row_projections(&tx, *cursor).map_err(|_| EngineError::Storage)?,
);
}
let nodes_excised = tx
.execute("DELETE FROM canonical_nodes WHERE source_id = ?1", [source_id])
.map_err(|_| EngineError::Storage)? as u64;
let edges_excised = tx
.execute("DELETE FROM canonical_edges WHERE source_id = ?1", [source_id])
.map_err(|_| EngineError::Storage)? as u64;
let excised_at = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs();
let payload = serde_json::json!({
"source_id": source_id,
"excised_at": excised_at,
"nodes_excised": nodes_excised,
"edges_excised": edges_excised,
"projections_invalidated": shadow_invalidated,
})
.to_string();
let audit_cursor = self.next_cursor.load(Ordering::SeqCst).saturating_add(1);
tx.execute(
"INSERT INTO operational_mutations(
collection_name, record_key, op_kind, payload_json, schema_id, write_cursor
) VALUES('excise_source_audit', ?1, 'append', ?2, NULL, ?3)",
params![source_id, payload, audit_cursor],
)
.map_err(|_| EngineError::Storage)?;
if self.telemetry_enabled.load(Ordering::Acquire) {
enqueue_pending_redaction(&tx, verb, &erased_stable_ids, audit_cursor)?;
}
tx.commit().map_err(|_| EngineError::Storage)?;
self.next_cursor.store(audit_cursor, Ordering::SeqCst);
Ok(ExciseReport {
source_ref: source_id.to_string(),
nodes_excised,
edges_excised,
projections_invalidated: shadow_invalidated,
})
}
#[cfg(feature = "operator")]
pub fn excise_collection_record(
&self,
collection: &str,
record_key: &str,
) -> Result<ExciseRecordReport, EngineError> {
self.ensure_open()?;
if collection.is_empty() || record_key.is_empty() {
return Err(EngineError::WriteValidation);
}
if is_erasure_bookkeeping_collection(collection) {
return Err(EngineError::InvalidArgument {
msg: format!(
"`{collection}` is engine-internal erasure bookkeeping and cannot be excised \
by `excise_collection_record`. The pending-redaction queue records an \
erasure the engine still owes (deleting it would let a later verb report \
success on an incomplete erasure, R-20-E5), and the erasure-audit \
collections are the auditable record of the deletion event. Pending \
redactions retire themselves once performed; retry the erasure verb instead."
),
});
}
let report = self.excise_collection_record_inner(collection, record_key)?;
self.complete_erasure_at_rest("excise_collection_record")?;
Ok(report)
}
#[cfg(feature = "operator")]
fn excise_collection_record_inner(
&self,
collection: &str,
record_key: &str,
) -> Result<ExciseRecordReport, EngineError> {
let mut connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_mut().ok_or(EngineError::Closing)?;
let tx = connection.transaction().map_err(|_| EngineError::Storage)?;
let records_excised = tx
.execute(
"DELETE FROM operational_mutations
WHERE collection_name = ?1 AND record_key = ?2",
params![collection, record_key],
)
.map_err(|_| EngineError::Storage)? as u64;
let state_rows_excised = tx
.execute(
"DELETE FROM operational_state
WHERE collection_name = ?1 AND record_key = ?2",
params![collection, record_key],
)
.map_err(|_| EngineError::Storage)? as u64;
let record_digest = digest_record_identity(collection, record_key);
let excised_at = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs();
let payload = serde_json::json!({
"collection": collection,
"record_digest": record_digest,
"excised_at": excised_at,
"records_excised": records_excised,
"state_rows_excised": state_rows_excised,
})
.to_string();
let audit_cursor = self.next_cursor.load(Ordering::SeqCst).saturating_add(1);
tx.execute(
"INSERT INTO operational_mutations(
collection_name, record_key, op_kind, payload_json, schema_id, write_cursor
) VALUES('excise_record_audit', ?1, 'append', ?2, NULL, ?3)",
params![record_digest, payload, audit_cursor],
)
.map_err(|_| EngineError::Storage)?;
tx.commit().map_err(|_| EngineError::Storage)?;
self.next_cursor.store(audit_cursor, Ordering::SeqCst);
self.counters.record_admin();
Ok(ExciseRecordReport {
collection: collection.to_string(),
record_digest,
records_excised,
state_rows_excised,
})
}
#[cfg(feature = "operator")]
fn run_rebuild(
&self,
include_fts: bool,
kind: RebuildKind,
) -> Result<RebuildReport, EngineError> {
self.projection_runtime.set_frozen(true);
let drain_result = self.drain(REBUILD_DRAIN_TIMEOUT_MS);
let result = drain_result.and_then(|()| self.rebuild_shadow_state(include_fts, kind));
self.projection_runtime.set_frozen(false);
result
}
#[cfg(feature = "operator")]
fn rebuild_shadow_state(
&self,
include_fts: bool,
kind: RebuildKind,
) -> Result<RebuildReport, EngineError> {
let mut connection = self.connection.lock().map_err(|_| EngineError::Storage)?;
let connection = connection.as_mut().ok_or(EngineError::Closing)?;
let tx = connection.transaction().map_err(|_| EngineError::Storage)?;
let rows_invalidated = if include_fts {
truncate_all_row_projections(&tx).map_err(|_| EngineError::Storage)?
} else {
truncate_row_projections_in(&tx, &[ProjectionClass::Vector, ProjectionClass::Readiness])
.map_err(|_| EngineError::Storage)?
};
store_projection_cursor(&tx, 0).map_err(|_| EngineError::Storage)?;
let pass = if include_fts { ProjectionPass::Write } else { ProjectionPass::VectorOnly };
let mut rows_rebuilt: u64 = 0;
for row in canonical_node_rows(&tx).map_err(|_| EngineError::Storage)? {
project_canonical_node_row(
&tx,
row.cursor,
&row.kind,
&row.body,
row.row_kind,
pass,
row.attr_projected,
)
.map_err(|_| EngineError::Storage)?;
if include_fts {
rows_rebuilt = rows_rebuilt.saturating_add(1);
}
}
let edge_rows: Vec<(i64, String, Option<String>)> = {
let edge_sql = format!(
"SELECT write_cursor, kind, body FROM canonical_edges \
WHERE superseded_at IS NULL{}",
edge_validity_sql("canonical_edges", 1)
);
let mut edge_stmt = tx.prepare(&edge_sql).map_err(|_| EngineError::Storage)?;
let rows = edge_stmt
.query_map(params![current_epoch_seconds()], |row| {
Ok((
row.get::<_, i64>(0)?,
row.get::<_, String>(1)?,
row.get::<_, Option<String>>(2)?,
))
})
.map_err(|_| EngineError::Storage)?
.collect::<rusqlite::Result<_>>()
.map_err(|_| EngineError::Storage)?;
rows
};
for (cursor, kind, body) in edge_rows {
let has_body = body.is_some();
project_canonical_edge_row(&tx, cursor as u64, &kind, body.as_deref(), pass)
.map_err(|_| EngineError::Storage)?;
if include_fts && has_body {
rows_rebuilt = rows_rebuilt.saturating_add(1);
}
}
let projection_cursor_after =
load_projection_cursor(&tx).map_err(|_| EngineError::Storage)?;
tx.commit().map_err(|_| EngineError::Storage)?;
Ok(RebuildReport { kind, rows_invalidated, rows_rebuilt, projection_cursor_after })
}
fn ensure_open(&self) -> Result<(), EngineError> {
if self.closed.load(Ordering::SeqCst) {
return Err(EngineError::Closing);
}
Ok(())
}
}
fn batch_is_admin(batch: &[PreparedWrite]) -> bool {
!batch.is_empty() && batch.iter().all(|w| matches!(w, PreparedWrite::AdminSchema { .. }))
}
pub const TOP_K_BIT_CANDIDATES: usize = 192;
pub const MEAN_VEC_PIN_THRESHOLD: u64 = 256;
pub const SEARCH_RERANK_LIMIT: usize = 10;
#[derive(Clone, Debug)]
struct MeanAccumulator {
sum: Vec<f64>,
count: u64,
}
impl MeanAccumulator {
fn new(dim: usize) -> Self {
Self { sum: vec![0.0; dim], count: 0 }
}
fn add(&mut self, v: &[f32]) {
debug_assert_eq!(v.len(), self.sum.len(), "accumulator dim mismatch");
for (slot, value) in self.sum.iter_mut().zip(v.iter()) {
*slot += f64::from(*value);
}
self.count = self.count.saturating_add(1);
}
fn materialize(&self) -> Vec<f32> {
if self.count == 0 {
return vec![0.0; self.sum.len()];
}
let denom = self.count as f64;
self.sum.iter().map(|s| (s / denom) as f32).collect()
}
fn count(&self) -> u64 {
self.count
}
}
fn cosine_similarity(a: &[f32], b: &[f32]) -> f32 {
if a.len() != b.len() {
return 1.0;
}
let mut dot = 0.0f64;
let mut na = 0.0f64;
let mut nb = 0.0f64;
for (x, y) in a.iter().zip(b.iter()) {
dot += f64::from(*x) * f64::from(*y);
na += f64::from(*x) * f64::from(*x);
nb += f64::from(*y) * f64::from(*y);
}
if na == 0.0 || nb == 0.0 {
return 1.0;
}
(dot / (na.sqrt() * nb.sqrt())) as f32
}
fn run_pin_and_requantize_pass(
tx: &rusqlite::Transaction<'_>,
rows: &[(i64, Vec<u8>)],
mean: &[f32],
) -> Result<(u64, Vec<EmbedderEvent>), EngineError> {
let mut updated: u64 = 0;
let dim = mean.len();
for (rowid, blob) in rows {
if blob.len() != dim * 4 {
return Err(EngineError::Storage);
}
let un_centered = decode_vector_blob(blob);
let centered = subtract_mean(&un_centered, mean);
let centered_blob = encode_vector_blob(¢ered);
let (source_type, kind, created_at): (String, String, i64) = tx
.query_row(
"SELECT source_type, kind, created_at FROM vector_default WHERE rowid = ?1",
params![rowid],
|row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
)
.map_err(|_| EngineError::Storage)?;
let attr_cols = actual_vector_attr_columns(tx).map_err(|_| EngineError::Storage)?;
let attr_vals: Vec<String> = if attr_cols.is_empty() {
Vec::new()
} else {
let select = attr_cols.join(", ");
tx.query_row(
&format!("SELECT {select} FROM vector_default WHERE rowid = ?1"),
params![rowid],
|row| {
let mut vals = Vec::with_capacity(attr_cols.len());
for i in 0..attr_cols.len() {
vals.push(row.get::<_, String>(i)?);
}
Ok(vals)
},
)
.map_err(|_| EngineError::Storage)?
};
delete_vector_partition_row(tx, *rowid).map_err(|_| EngineError::Storage)?;
let mut cols_sql = String::new();
let mut ph_sql = String::new();
for (i, col) in attr_cols.iter().enumerate() {
cols_sql.push_str(&format!(", {col}"));
ph_sql.push_str(&format!(", ?{}", 7 + i));
}
let sql = format!(
"INSERT INTO vector_default(
rowid, embedding, embedding_bin, source_type, kind, created_at, status{cols_sql}
) VALUES(?1, ?2, vec_quantize_binary(?3), ?4, ?5, ?6, ''{ph_sql})"
);
let mut pv: Vec<rusqlite::types::Value> = vec![
rusqlite::types::Value::Integer(*rowid),
rusqlite::types::Value::Blob(blob.clone()),
rusqlite::types::Value::Blob(centered_blob),
rusqlite::types::Value::Text(source_type),
rusqlite::types::Value::Text(kind),
rusqlite::types::Value::Integer(created_at),
];
for v in attr_vals {
pv.push(rusqlite::types::Value::Text(v));
}
tx.execute(&sql, rusqlite::params_from_iter(pv.iter()))
.map_err(|_| EngineError::Storage)?;
updated = updated.saturating_add(1);
}
let events = vec![EmbedderEvent::MeanVecPinned {
dim: u32::try_from(dim).unwrap_or(u32::MAX),
doc_count: updated,
}];
Ok((updated, events))
}
fn run_requantize_pass(rows: &[(i64, Vec<u8>)], mean: &[f32]) -> (u64, Vec<EmbedderEvent>) {
let mut updated: u64 = 0;
let dim = mean.len();
for (_rowid, blob) in rows {
if blob.len() != dim * 4 {
continue;
}
updated = updated.saturating_add(1);
}
let events = vec![EmbedderEvent::MeanVecPinned {
dim: u32::try_from(dim).unwrap_or(u32::MAX),
doc_count: updated,
}];
(updated, events)
}
#[doc(hidden)]
pub mod mean_centering_internals_for_test {
use super::{EmbedderEvent, MeanAccumulator};
pub struct AccumulatorHandle(MeanAccumulator);
#[must_use]
pub fn new_mean_accumulator(dim: usize) -> AccumulatorHandle {
AccumulatorHandle(MeanAccumulator::new(dim))
}
pub fn accumulator_add(handle: &mut AccumulatorHandle, v: &[f32]) {
handle.0.add(v);
}
#[must_use]
pub fn accumulator_materialize(handle: &AccumulatorHandle) -> Vec<f32> {
handle.0.materialize()
}
#[must_use]
pub fn accumulator_count(handle: &AccumulatorHandle) -> u64 {
handle.0.count()
}
#[must_use]
pub fn run_requantize_pass(rows: &[(i64, Vec<u8>)], mean: &[f32]) -> (u64, Vec<EmbedderEvent>) {
super::run_requantize_pass(rows, mean)
}
}
pub const RRF_K: f64 = 30.0;
pub const RRF_WEIGHT_VECTOR: f64 = 1.0;
pub const RRF_WEIGHT_TEXT: f64 = 3.0;
pub const RRF_WEIGHT_GRAPH: f64 = 1.0;
pub const RECENCY_WEIGHT: f64 = 0.002;
fn branch_str(branch: SoftFallbackBranch) -> &'static str {
match branch {
SoftFallbackBranch::Vector => "vector",
SoftFallbackBranch::Text => "text",
SoftFallbackBranch::TextEdge => "text_edge",
SoftFallbackBranch::GraphArm => "graph_arm",
}
}
fn append_jsonl(path: &Path, value: &serde_json::Value) -> std::io::Result<()> {
let mut file = std::fs::OpenOptions::new().create(true).append(true).open(path)?;
writeln!(file, "{value}")?;
Ok(())
}
fn redact_jsonl_stable_ids(
path: &Path,
erased: &std::collections::HashSet<&str>,
) -> std::io::Result<()> {
const MAX_TAIL_FOLDS: usize = 8;
let mut source = std::fs::read(path)?;
let mut redacted = redact_jsonl_bytes(&source, erased);
for _ in 0..MAX_TAIL_FOLDS {
let current = std::fs::read(path)?;
if current.len() == source.len() {
let mut tmp_name = path.file_name().unwrap_or_default().to_os_string();
tmp_name.push(".redact.tmp");
let tmp = path.with_file_name(tmp_name);
{
let mut file = std::fs::File::create(&tmp)?;
file.write_all(&redacted)?;
file.sync_all()?;
}
std::fs::rename(&tmp, path)?;
return Ok(());
}
if current.len() > source.len() && current.starts_with(&source) {
redacted.extend_from_slice(&redact_jsonl_bytes(¤t[source.len()..], erased));
} else {
redacted = redact_jsonl_bytes(¤t, erased);
}
source = current;
}
Err(std::io::Error::other(format!(
"telemetry sink {} is being appended to faster than it can be redacted",
path.display()
)))
}
fn redact_jsonl_bytes(bytes: &[u8], erased: &std::collections::HashSet<&str>) -> Vec<u8> {
let mut out = Vec::with_capacity(bytes.len());
let mut rest = bytes;
while !rest.is_empty() {
let (line, tail) = match rest.iter().position(|b| *b == b'\n') {
Some(idx) => (&rest[..idx], &rest[idx + 1..]),
None => {
out.extend_from_slice(rest);
break;
}
};
match redact_jsonl_line(line, erased) {
Some(replacement) => out.extend_from_slice(replacement.as_bytes()),
None => out.extend_from_slice(line),
}
out.push(b'\n');
rest = tail;
}
out
}
fn redact_jsonl_line(line: &[u8], erased: &std::collections::HashSet<&str>) -> Option<String> {
let text = std::str::from_utf8(line).ok()?;
let mut value: serde_json::Value = serde_json::from_str(text).ok()?;
let ids = value.get_mut("result_stable_ids")?.as_array_mut()?;
let mut touched = false;
for id in ids.iter_mut() {
if id.as_str().is_some_and(|s| erased.contains(s)) {
*id = serde_json::Value::from(REDACTED_STABLE_ID);
touched = true;
}
}
touched.then(|| value.to_string())
}
#[doc(hidden)]
#[must_use]
pub fn fuse_rrf(vector_hits: Vec<SearchHit>, text_hits: Vec<SearchHit>) -> Vec<SearchHit> {
fuse_three_arms(vector_hits, text_hits, vec![])
}
#[doc(hidden)]
#[must_use]
pub fn fuse_three_arms(
vector_hits: Vec<SearchHit>,
text_hits: Vec<SearchHit>,
graph_hits: Vec<SearchHit>,
) -> Vec<SearchHit> {
struct Entry {
hit: SearchHit,
score: f64,
in_vector: bool,
order: usize,
}
let mut entries: Vec<Entry> = Vec::new();
let mut accumulate = |hit: SearchHit, rank0: usize, in_vector: bool, weight: f64| {
let contrib = weight / (RRF_K + (rank0 as f64 + 1.0));
if let Some(existing) = entries.iter_mut().find(|e| e.hit.body == hit.body) {
existing.score += contrib;
} else {
let order = entries.len();
entries.push(Entry { hit, score: contrib, in_vector, order });
}
};
for (rank0, hit) in vector_hits.into_iter().enumerate() {
accumulate(hit, rank0, true, RRF_WEIGHT_VECTOR);
}
for (rank0, hit) in text_hits.into_iter().enumerate() {
accumulate(hit, rank0, false, RRF_WEIGHT_TEXT);
}
for (rank0, hit) in graph_hits.into_iter().enumerate() {
accumulate(hit, rank0, false, RRF_WEIGHT_GRAPH);
}
entries.sort_by(|a, b| {
b.score
.partial_cmp(&a.score)
.unwrap_or(std::cmp::Ordering::Equal)
.then_with(|| b.in_vector.cmp(&a.in_vector))
.then_with(|| a.order.cmp(&b.order))
});
entries
.into_iter()
.map(|mut e| {
e.hit.score = e.score;
e.hit
})
.collect()
}
#[doc(hidden)]
#[must_use]
pub fn apply_recency_reweight(hits: Vec<SearchHit>, enabled: bool) -> Vec<SearchHit> {
if !enabled || hits.len() < 2 {
return hits;
}
let min_id = hits.iter().map(|h| h.write_cursor).min().unwrap_or(0);
let max_id = hits.iter().map(|h| h.write_cursor).max().unwrap_or(0);
if max_id == min_id {
return hits;
}
let span = (max_id - min_id) as f64;
let mut reweighted: Vec<SearchHit> = hits
.into_iter()
.map(|mut h| {
let norm = (h.write_cursor - min_id) as f64 / span;
h.score += RECENCY_WEIGHT * norm;
h
})
.collect();
reweighted.sort_by(|a, b| b.score.partial_cmp(&a.score).unwrap_or(std::cmp::Ordering::Equal));
reweighted
}
#[must_use]
pub fn apply_importance_reweight(
hits: Vec<SearchHit>,
importance_by_id: &HashMap<u64, f64>,
confidence_by_id: &HashMap<u64, f64>,
enabled: bool,
) -> Vec<SearchHit> {
if !enabled {
return hits;
}
let any_weighted = hits.iter().any(|h| {
importance_by_id.contains_key(&h.write_cursor)
|| confidence_by_id.contains_key(&h.write_cursor)
});
if !any_weighted {
return hits;
}
let mut reweighted: Vec<SearchHit> = hits
.into_iter()
.map(|mut h| {
let importance = importance_by_id.get(&h.write_cursor).copied().unwrap_or(1.0);
let confidence = confidence_by_id.get(&h.write_cursor).copied().unwrap_or(1.0);
h.score *= importance * confidence;
h
})
.collect();
reweighted.sort_by(|a, b| b.score.partial_cmp(&a.score).unwrap_or(std::cmp::Ordering::Equal));
reweighted
}
fn build_importance_confidence_maps(
tx: &rusqlite::Connection,
hits: &[SearchHit],
) -> rusqlite::Result<(HashMap<u64, f64>, HashMap<u64, f64>)> {
let mut importance_by_id: HashMap<u64, f64> = HashMap::new();
let mut confidence_by_id: HashMap<u64, f64> = HashMap::new();
if let Ok(mut stmt) =
tx.prepare("SELECT importance FROM canonical_nodes WHERE write_cursor = ?1 LIMIT 1")
{
for h in hits {
if let Ok(Some(v)) = stmt.query_row([h.write_cursor], |r| r.get::<_, Option<f64>>(0)) {
importance_by_id.insert(h.write_cursor, v);
}
}
}
if let Ok(mut stmt) = tx.prepare(
"SELECT confidence FROM canonical_edges \
WHERE write_cursor = ?1 AND superseded_at IS NULL LIMIT 1",
) {
for h in hits {
if let Ok(Some(v)) = stmt.query_row([h.write_cursor], |r| r.get::<_, Option<f64>>(0)) {
confidence_by_id.insert(h.write_cursor, v);
}
}
}
Ok((importance_by_id, confidence_by_id))
}
#[doc(hidden)]
#[must_use]
pub fn rerank_fused(
_query: &str,
hits: Vec<SearchHit>,
rerank_depth: usize,
alpha: f64,
pool_n: usize,
) -> Vec<SearchHit> {
if rerank_depth == 0 {
return hits;
}
#[cfg(feature = "default-reranker")]
{
if let Some(reranked) = ce_rerank(_query, &hits, rerank_depth, alpha, pool_n) {
return reranked;
}
}
#[cfg(not(feature = "default-reranker"))]
let _ = (alpha, pool_n);
hits
}
pub fn rerank_passages(
query: &str,
passages: Vec<(u64, String, f64)>,
rerank_depth: usize,
alpha: f64,
pool_n: usize,
) -> Result<Vec<(u64, f64, Option<f64>)>, String> {
for (id, _, score) in &passages {
if !score.is_finite() {
return Err(format!(
"rerank: non-finite score for passage id={id}: {score} \
(NaN/\u{00b1}inf must not reach the normalization/sort step)"
));
}
}
let hits: Vec<SearchHit> = passages
.into_iter()
.map(|(id, body, score)| SearchHit {
id: IdSpace::passage(id.to_string()),
write_cursor: id,
kind: "passage".to_string(),
body,
score,
branch: SoftFallbackBranch::Vector,
source_id: None,
ce_score: None,
})
.collect();
Ok(rerank_fused(query, hits, rerank_depth, alpha, pool_n)
.into_iter()
.map(|h| (h.write_cursor, h.score, h.ce_score))
.collect())
}
#[cfg(feature = "default-reranker")]
fn ce_rerank(
_query: &str,
hits: &[SearchHit], _rerank_depth: usize, alpha: f64,
pool_n: usize,
) -> Option<Vec<SearchHit>> {
let alpha = if alpha.is_finite() { alpha.clamp(0.0, 1.0) } else { 0.3 };
if hits.is_empty() {
return Some(vec![]);
}
let model = CandleCrossEncoder::try_get_loaded()?;
let n = pool_n.min(hits.len());
let top = &hits[..n]; let rest = &hits[n..];
let rrf_min = top.iter().map(|h| h.score).fold(f64::INFINITY, f64::min);
let rrf_max = top.iter().map(|h| h.score).fold(f64::NEG_INFINITY, f64::max);
let rrf_span = rrf_max - rrf_min;
let bodies: Vec<&str> = top.iter().map(|h| h.body.as_str()).collect();
let raw_logits = model.score_batch(_query, &bodies);
let mut scored: Vec<(f64, SearchHit)> = top
.iter()
.zip(raw_logits)
.map(|(h, raw_logit)| {
let rrf_norm = if rrf_span > 0.0 { (h.score - rrf_min) / rrf_span } else { 1.0 };
let ce_norm = 1.0 / (1.0 + (-raw_logit).exp());
let blended = alpha * ce_norm + (1.0 - alpha) * rrf_norm;
let mut hit = h.clone();
hit.ce_score = Some(ce_norm);
(blended, hit)
})
.collect();
scored.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
let mut result: Vec<SearchHit> = scored
.into_iter()
.map(|(score, mut h)| {
h.score = score;
h
})
.collect();
result.extend_from_slice(rest);
Some(result)
}
#[cfg(feature = "default-reranker")]
struct CandleCrossEncoder {
inner: &'static fathomdb_embedder::CandleTinyBertReranker,
}
#[cfg(feature = "default-reranker")]
fn reranker_singleton() -> Option<&'static fathomdb_embedder::CandleTinyBertReranker> {
static CELL: std::sync::OnceLock<Option<fathomdb_embedder::CandleTinyBertReranker>> =
std::sync::OnceLock::new();
CELL.get_or_init(|| fathomdb_embedder::CandleTinyBertReranker::try_load().ok()).as_ref()
}
#[cfg(feature = "default-reranker")]
impl CandleCrossEncoder {
fn try_get_loaded() -> Option<Self> {
Some(Self { inner: reranker_singleton()? })
}
fn score(&self, query: &str, passage: &str) -> f64 {
self.inner.score(query, passage).map(f64::from).unwrap_or(0.0)
}
fn score_batch(&self, query: &str, passages: &[&str]) -> Vec<f64> {
match self.inner.score_batch(query, passages) {
Ok(logits) => logits.into_iter().map(f64::from).collect(),
Err(_) => passages.iter().map(|p| self.score(query, p)).collect(),
}
}
}
fn validate_filter_attributes_on_snapshot(
conn: &Connection,
filter: &SearchFilter,
) -> Result<(), SearchReaderError> {
if filter.attributes.is_empty() {
return Ok(());
}
let registry = load_projection_registry(conn)?;
for (name, _value) in &filter.attributes {
let declared_filterable =
registry.get(name).is_some_and(|s| s.roles.contains(&ProjectionRole::Filterable));
if !declared_filterable {
return Err(SearchReaderError::InvalidFilter(format!(
"filter attribute {name:?} is not a declared `filterable` projection; \
declare it via configure_projections before filtering on it"
)));
}
}
Ok(())
}
fn vector_filter_clause(filter: Option<&SearchFilter>) -> String {
let Some(filter) = filter else {
return String::new();
};
if filter.is_unfiltered() {
return String::new();
}
let mut cols: Vec<(String, &str)> = Vec::new();
if filter.source_type.is_some() {
cols.push(("source_type".to_string(), "="));
}
if filter.kind.is_some() {
cols.push(("kind".to_string(), "="));
}
if filter.created_after.is_some() {
cols.push(("created_at".to_string(), ">="));
}
if filter.status.is_some() {
cols.push(("status".to_string(), "="));
}
for (name, _value) in &filter.attributes {
cols.push((attr_vec0_column(name), "="));
}
let mut clause = String::new();
for (i, (col, op)) in cols.iter().enumerate() {
clause.push_str(&format!(" AND {col}{op}?{}", i + 3));
}
clause
}
fn vector_filter_values(filter: Option<&SearchFilter>) -> Vec<rusqlite::types::Value> {
use rusqlite::types::Value;
let mut out = Vec::new();
let Some(filter) = filter else {
return out;
};
if filter.is_unfiltered() {
return out;
}
if let Some(s) = &filter.source_type {
out.push(Value::Text(s.clone()));
}
if let Some(s) = &filter.kind {
out.push(Value::Text(s.clone()));
}
if let Some(c) = filter.created_after {
out.push(Value::Integer(c));
}
if let Some(s) = &filter.status {
out.push(Value::Text(s.clone()));
}
for (_name, value) in &filter.attributes {
out.push(Value::Text(encode_attr_vec0_present(value)));
}
out
}
fn build_vector_phase1_sql(filter: Option<&SearchFilter>, final_limit: usize) -> String {
let filter_clause = vector_filter_clause(filter);
format!(
"WITH candidates AS (
SELECT rowid
FROM vector_default
WHERE embedding_bin MATCH vec_quantize_binary(vec_f32(?1)){filter_clause}
ORDER BY distance
LIMIT {top_k}
)
SELECT c.rowid, vec_distance_l2(v.embedding, vec_f32(?2)) AS l2
FROM candidates c
JOIN vector_default v ON v.rowid = c.rowid
ORDER BY l2
LIMIT {final_limit}",
top_k = TOP_K_BIT_CANDIDATES,
)
}
#[doc(hidden)]
#[must_use]
pub fn vector_phase1_sql_for_test(filter: Option<&SearchFilter>) -> String {
build_vector_phase1_sql(filter, SEARCH_RERANK_LIMIT)
}
fn text_hit_passes_filter(
tx: &rusqlite::Transaction<'_>,
id: u64,
kind: &str,
filter: Option<&SearchFilter>,
) -> rusqlite::Result<bool> {
let Some(filter) = filter else {
return Ok(true);
};
if filter.is_unfiltered() {
return Ok(true);
}
if let Some(k) = &filter.kind {
if kind != k {
return Ok(false);
}
}
if let Some(st) = &filter.source_type {
match resolve_source_type(kind) {
Ok(resolved) if resolved == st.as_str() => {}
_ => return Ok(false),
}
}
if filter.created_after.is_some() || filter.status.is_some() {
let meta: Option<(i64, Option<String>)> = tx
.query_row(
"SELECT created_at, status FROM vector_default WHERE rowid = ?1 LIMIT 1",
[id as i64],
|row| Ok((row.get::<_, i64>(0)?, row.get::<_, Option<String>>(1)?)),
)
.optional()?;
let Some((created_at, status)) = meta else {
return Ok(false);
};
if let Some(bound) = filter.created_after {
if created_at < bound {
return Ok(false);
}
}
if let Some(want) = &filter.status {
if status.as_deref() != Some(want.as_str()) {
return Ok(false);
}
}
}
if !hit_attributes_pass_filter(tx, id, filter)? {
return Ok(false);
}
Ok(true)
}
fn hit_attributes_pass_filter(
tx: &rusqlite::Transaction<'_>,
id: u64,
filter: &SearchFilter,
) -> rusqlite::Result<bool> {
for (name, want) in &filter.attributes {
let stored: Option<Option<String>> = tx
.query_row(
"SELECT attr_value FROM canonical_attributes \
WHERE write_cursor = ?1 AND attr_name = ?2 LIMIT 1",
params![id as i64, name],
|row| row.get::<_, Option<String>>(0),
)
.optional()?;
match stored {
Some(Some(v)) if v.as_str() == want.as_str() => {}
_ => return Ok(false),
}
}
Ok(true)
}
fn edge_fts_hit_passes_filter(
tx: &rusqlite::Transaction<'_>,
write_cursor: u64,
row_kind: &str,
filter: Option<&SearchFilter>,
) -> rusqlite::Result<bool> {
let Some(filter) = filter else {
return Ok(true);
};
if filter.is_unfiltered() {
return Ok(true);
}
if let Some(ref st) = filter.source_type {
if st != "edge_fact" {
return Ok(false); }
}
if let Some(ref k) = filter.kind {
if k != row_kind {
return Ok(false); }
}
if filter.created_after.is_some() || filter.status.is_some() {
let meta: Option<(i64, Option<String>)> = tx
.query_row(
"SELECT created_at, status FROM vector_default WHERE rowid = ?1 LIMIT 1",
[write_cursor as i64],
|row| Ok((row.get::<_, i64>(0)?, row.get::<_, Option<String>>(1)?)),
)
.optional()?;
let Some((created_at, status)) = meta else {
return Ok(false);
};
if let Some(bound) = filter.created_after {
if created_at < bound {
return Ok(false);
}
}
if let Some(want) = &filter.status {
if status.as_deref() != Some(want.as_str()) {
return Ok(false);
}
}
}
if !hit_attributes_pass_filter(tx, write_cursor, filter)? {
return Ok(false);
}
Ok(true)
}
#[allow(clippy::too_many_arguments)]
fn read_search_in_tx(
reader: &mut Connection,
compiled: &fathomdb_query::CompiledQuery,
query_vector: Option<&str>,
query_vector_bin: Option<&str>,
final_limit: usize,
filter: Option<&SearchFilter>,
recency_enabled: bool,
importance_enabled: bool,
vector_stage_only: bool,
raw_query: &str,
rerank_depth: usize,
use_graph_arm: bool,
alpha: f64,
pool_n: usize,
explain: bool,
view: ReadView,
) -> ReaderResponse {
let view = view.freeze();
let now_param = view.now_param();
reader_search_hook::fire();
let tx = reader.transaction_with_behavior(rusqlite::TransactionBehavior::Deferred)?;
let cursor = load_projection_cursor(&tx)?;
if let Some(filter) = filter {
validate_filter_attributes_on_snapshot(&tx, filter)?;
}
let vector_results = if let Some(query_vector) = query_vector {
let mut rowids = Vec::new();
let bin_vector = query_vector_bin.unwrap_or(query_vector);
{
let candidate_limit = final_limit.max(TOP_K_BIT_CANDIDATES);
let sql = build_vector_phase1_sql(filter, candidate_limit);
let mut params: Vec<rusqlite::types::Value> = vec![
rusqlite::types::Value::Text(bin_vector.to_string()),
rusqlite::types::Value::Text(query_vector.to_string()),
];
params.extend(vector_filter_values(filter));
let mut statement = tx.prepare(&sql)?;
let rows = statement.query_map(rusqlite::params_from_iter(params.iter()), |row| {
Ok((row.get::<_, i64>(0)?, row.get::<_, f64>(1)?))
})?;
for row in rows.flatten() {
rowids.push(row);
}
}
let mut results = Vec::new();
let node_validity = view.validity_sql("canonical_nodes", 2);
let mut node_stmt = tx.prepare(&format!(
"SELECT kind, body, logical_id, source_id FROM canonical_nodes \
WHERE write_cursor = ?1 AND superseded_at IS NULL AND state = 'active'\
{node_validity} LIMIT 1"
))?;
let edge_validity = edge_validity_sql("canonical_edges", 2);
let mut edge_stmt = tx.prepare(&format!(
"SELECT body, logical_id, source_id FROM canonical_edges \
WHERE write_cursor = ?1 AND superseded_at IS NULL AND body IS NOT NULL\
{edge_validity} LIMIT 1"
))?;
let node_params = |rowid: i64| -> Vec<rusqlite::types::Value> {
let mut p = vec![rusqlite::types::Value::Integer(rowid)];
if let Some(now) = now_param {
p.push(rusqlite::types::Value::Integer(now));
}
p
};
for (rowid, score) in rowids {
if results.len() >= final_limit {
break;
}
if let Ok((kind, body, logical_id, source_id)) =
node_stmt.query_row(rusqlite::params_from_iter(node_params(rowid)), |row| {
Ok((
row.get::<_, String>(0)?,
row.get::<_, String>(1)?,
row.get::<_, Option<String>>(2)?,
row.get::<_, Option<String>>(3)?,
))
})
{
let id = derive_stable_id(logical_id.as_deref(), &body);
results.push(SearchHit {
id,
write_cursor: rowid as u64,
kind,
body,
score,
branch: SoftFallbackBranch::Vector,
source_id,
ce_score: None,
});
} else if let Ok((body, logical_id, source_id)) =
edge_stmt.query_row(rusqlite::params![rowid, view.edge_now()], |row| {
Ok((
row.get::<_, String>(0)?,
row.get::<_, Option<String>>(1)?,
row.get::<_, Option<String>>(2)?,
))
})
{
let id = derive_stable_id(logical_id.as_deref(), &body);
results.push(SearchHit {
id,
write_cursor: rowid as u64,
kind: "edge_fact".to_string(),
body,
score,
branch: SoftFallbackBranch::TextEdge,
source_id,
ce_score: None,
});
}
}
results
} else {
Vec::new()
};
let vector_rows_visible = !vector_results.is_empty();
let soft_fallback = if query_vector.is_some() && !vector_rows_visible {
tx.query_row(
"SELECT 1
FROM search_index
JOIN _fathomdb_vector_kinds ON _fathomdb_vector_kinds.kind = search_index.kind
LEFT JOIN _fathomdb_projection_terminal
ON _fathomdb_projection_terminal.write_cursor = search_index.write_cursor
WHERE search_index MATCH ?1
AND _fathomdb_projection_terminal.write_cursor IS NULL
LIMIT 1",
[compiled.match_expression.as_str()],
|_row| Ok(SoftFallback { branch: SoftFallbackBranch::Vector }),
)
.ok()
} else {
None
};
let text_candidates: Vec<SearchHit> = {
let perf_limit: Option<usize> = if std::env::var_os("FATHOMDB_PERF_EXPERIMENTS").is_some() {
std::env::var("FATHOMDB_PERF_SEARCH_LIMIT").ok().and_then(|s| s.parse().ok())
} else {
None
};
let limit_clause = perf_limit.map(|k| format!(" LIMIT {k}")).unwrap_or_default();
let text_validity = view.validity_sql("cn", 2);
let join_sql = format!(
"SELECT search_index.body, search_index.kind, search_index.write_cursor, \
bm25(search_index), cn.logical_id, cn.source_id FROM search_index \
LEFT JOIN canonical_nodes cn ON cn.write_cursor = search_index.write_cursor \
WHERE search_index MATCH ?1 \
AND cn.superseded_at IS NULL \
AND (cn.state = 'active' OR cn.state IS NULL)\
{text_validity} \
ORDER BY bm25(search_index), search_index.write_cursor{limit_clause}"
);
let mut text_params: Vec<rusqlite::types::Value> =
vec![rusqlite::types::Value::Text(compiled.match_expression.clone())];
if let Some(now) = now_param {
text_params.push(rusqlite::types::Value::Integer(now));
}
if let Ok(mut statement) = tx.prepare(&join_sql) {
let rows =
statement.query_map(rusqlite::params_from_iter(text_params.iter()), |row| {
let body = row.get::<_, String>(0)?;
let logical_id = row.get::<_, Option<String>>(4)?;
Ok(SearchHit {
id: derive_stable_id(logical_id.as_deref(), &body),
body,
kind: row.get::<_, String>(1)?,
write_cursor: row.get::<_, i64>(2)? as u64,
score: row.get::<_, f64>(3)?,
branch: SoftFallbackBranch::Text,
source_id: row.get::<_, Option<String>>(5)?,
ce_score: None,
})
})?;
rows.flatten().collect()
} else {
let source_sql = format!(
"SELECT search_index.body, search_index.kind, search_index.write_cursor, \
bm25(search_index), cn.source_id FROM search_index \
LEFT JOIN canonical_nodes cn ON cn.write_cursor = search_index.write_cursor \
WHERE search_index MATCH ?1 \
ORDER BY bm25(search_index), search_index.write_cursor{limit_clause}"
);
if let Ok(mut statement) = tx.prepare(&source_sql) {
let rows = statement.query_map([compiled.match_expression.as_str()], |row| {
let body = row.get::<_, String>(0)?;
Ok(SearchHit {
id: derive_stable_id(None, &body),
body,
kind: row.get::<_, String>(1)?,
write_cursor: row.get::<_, i64>(2)? as u64,
score: row.get::<_, f64>(3)?,
branch: SoftFallbackBranch::Text,
source_id: row.get::<_, Option<String>>(4)?,
ce_score: None,
})
})?;
rows.flatten().collect()
} else {
let plain_sql = format!(
"SELECT body, kind, write_cursor, bm25(search_index) FROM search_index \
WHERE search_index MATCH ?1 \
ORDER BY bm25(search_index), write_cursor{limit_clause}"
);
let mut statement = tx.prepare(&plain_sql)?;
let rows = statement.query_map([compiled.match_expression.as_str()], |row| {
let body = row.get::<_, String>(0)?;
Ok(SearchHit {
id: derive_stable_id(None, &body),
body,
kind: row.get::<_, String>(1)?,
write_cursor: row.get::<_, i64>(2)? as u64,
score: row.get::<_, f64>(3)?,
branch: SoftFallbackBranch::Text,
source_id: None,
ce_score: None,
})
})?;
rows.flatten().collect()
}
}
};
let mut text_results: Vec<SearchHit> = Vec::with_capacity(text_candidates.len());
for hit in text_candidates {
if text_hit_passes_filter(&tx, hit.write_cursor, &hit.kind, filter)? {
text_results.push(hit);
}
}
let edge_candidates: Vec<SearchHit> = {
let edge_validity = edge_validity_sql("ce", 2);
let edge_sql = format!(
"SELECT sei.body, sei.kind, sei.write_cursor, bm25(search_index_edges), \
ce.logical_id, ce.source_id \
FROM search_index_edges sei \
JOIN canonical_edges ce ON ce.write_cursor = sei.write_cursor \
WHERE search_index_edges MATCH ?1 \
AND ce.superseded_at IS NULL{edge_validity} \
ORDER BY bm25(search_index_edges), sei.write_cursor"
);
if let Ok(mut stmt) = tx.prepare(&edge_sql) {
if let Ok(rows) = stmt.query_map(
rusqlite::params![compiled.match_expression.as_str(), view.edge_now()],
|row| {
let body = row.get::<_, String>(0)?;
let logical_id = row.get::<_, Option<String>>(4)?;
Ok(SearchHit {
id: derive_stable_id(logical_id.as_deref(), &body),
body,
kind: row.get::<_, String>(1)?,
write_cursor: row.get::<_, i64>(2)? as u64,
score: row.get::<_, f64>(3)?,
branch: SoftFallbackBranch::TextEdge,
source_id: row.get::<_, Option<String>>(5)?,
ce_score: None,
})
},
) {
rows.flatten().collect()
} else {
Vec::new()
}
} else {
Vec::new()
}
};
for row in edge_candidates {
if edge_fts_hit_passes_filter(&tx, row.write_cursor, &row.kind, filter)? {
text_results.push(row);
}
}
tx.commit()?;
let mut graph_stats = GraphFrontierStats::default();
let body_rank_map = |hits: &[SearchHit]| -> HashMap<String, u32> {
let mut m: HashMap<String, u32> = HashMap::new();
for (i, h) in hits.iter().enumerate() {
m.entry(h.body.clone()).or_insert(i as u32);
}
m
};
let body_score_map = |hits: &[SearchHit]| -> HashMap<String, f64> {
hits.iter().map(|h| (h.body.clone(), h.score)).collect()
};
let (exp_vector_ranks, exp_text_ranks, exp_vector_n, exp_text_n) = if explain {
(
Some(body_rank_map(&vector_results)),
Some(body_rank_map(&text_results)),
vector_results.len() as u32,
text_results.len() as u32,
)
} else {
(None, None, 0, 0)
};
let mut exp_graph_ranks: Option<HashMap<String, u32>> = None;
let mut exp_fused_scores: Option<HashMap<String, f64>> = None;
let mut exp_graph_n: u32 = 0;
let mut exp_importance: Option<HashMap<u64, f64>> = None;
let mut exp_confidence: Option<HashMap<u64, f64>> = None;
let results = if vector_stage_only {
vector_results
} else if use_graph_arm {
let two_arm_fused = fuse_rrf(vector_results, text_results);
let (graph_candidates, stats, graph_edge_confidence) = bfs_graph_arm_candidates(
reader,
&two_arm_fused,
compiled.match_expression.as_str(),
3,
50,
view,
)?;
graph_stats = stats;
if explain {
exp_graph_ranks = Some(body_rank_map(&graph_candidates));
exp_graph_n = graph_candidates.len() as u32;
}
let fused = apply_recency_reweight(
fuse_three_arms(two_arm_fused, vec![], graph_candidates),
recency_enabled,
);
let (imp_map, mut conf_map) = if importance_enabled || explain {
build_importance_confidence_maps(reader, &fused).unwrap_or_default()
} else {
(HashMap::new(), HashMap::new())
};
if importance_enabled || explain {
for (cursor, conf) in &graph_edge_confidence {
conf_map.entry(*cursor).or_insert(*conf);
}
}
let fused = apply_importance_reweight(fused, &imp_map, &conf_map, importance_enabled);
if explain {
exp_importance = Some(imp_map);
exp_confidence = Some(conf_map);
exp_fused_scores = Some(body_score_map(&fused));
}
rerank_fused(raw_query, fused, rerank_depth, alpha, pool_n)
} else {
let fused = apply_recency_reweight(fuse_rrf(vector_results, text_results), recency_enabled);
let (imp_map, conf_map) = if importance_enabled || explain {
build_importance_confidence_maps(reader, &fused).unwrap_or_default()
} else {
(HashMap::new(), HashMap::new())
};
let fused = apply_importance_reweight(fused, &imp_map, &conf_map, importance_enabled);
if explain {
exp_importance = Some(imp_map);
exp_confidence = Some(conf_map);
exp_fused_scores = Some(body_score_map(&fused));
}
rerank_fused(raw_query, fused, rerank_depth, alpha, pool_n)
};
let explanation = if explain {
let fused_scores = exp_fused_scores.unwrap_or_default();
let per_hit: Vec<PerHitExplain> = results
.iter()
.map(|h| PerHitExplain {
id: h.write_cursor,
arm: h.branch,
vector_rank: exp_vector_ranks.as_ref().and_then(|m| m.get(&h.body).copied()),
text_rank: exp_text_ranks.as_ref().and_then(|m| m.get(&h.body).copied()),
graph_rank: exp_graph_ranks.as_ref().and_then(|m| m.get(&h.body).copied()),
fused_score: fused_scores.get(&h.body).copied().unwrap_or(h.score),
ce_score: h.ce_score,
blended: h.score,
importance: exp_importance.as_ref().and_then(|m| m.get(&h.write_cursor).copied()),
confidence: exp_confidence.as_ref().and_then(|m| m.get(&h.write_cursor).copied()),
})
.collect();
let ce_active = rerank_depth > 0 && per_hit.iter().any(|p| p.ce_score.is_some());
Some(Explanation {
trace: QueryTrace {
query_chars: raw_query.chars().count() as u32,
k: final_limit as u32,
rerank_depth: rerank_depth as u32,
pool_n: pool_n as u32,
alpha,
use_graph_arm,
recency: recency_enabled,
embedder_id: String::new(),
ce_active,
vector_hits: exp_vector_n,
text_hits: exp_text_n,
graph_hits: exp_graph_n,
},
per_hit,
})
} else {
None
};
Ok((cursor, soft_fallback, results, graph_stats, explanation))
}
fn bfs_graph_arm_candidates(
reader: &mut Connection,
fused_hits: &[SearchHit],
match_expression: &str,
max_depth: u32,
cap: usize,
view: FrozenView,
) -> rusqlite::Result<(Vec<SearchHit>, GraphFrontierStats, HashMap<u64, f64>)> {
let now_param = view.now_param();
const SEED_FTS_N: usize = 10;
const SYNTHESIZED_PENALTY: f64 = 0.3;
let seed_bodies: std::collections::HashSet<&str> =
fused_hits.iter().map(|h| h.body.as_str()).collect();
let tx = reader.transaction_with_behavior(rusqlite::TransactionBehavior::Deferred)?;
let mut frontier: VecDeque<(String, u32)> = VecDeque::new(); let mut visited: std::collections::HashSet<String> = std::collections::HashSet::new();
let mut candidates: Vec<SearchHit> = Vec::new();
let mut edge_confidence_by_cursor: HashMap<u64, f64> = HashMap::new();
let mut stats = GraphFrontierStats::default();
{
let mut candidate_seeds: Vec<(String, Option<String>, Option<f64>)> = Vec::new();
let mut seen_candidates: std::collections::HashSet<String> =
std::collections::HashSet::new();
let push_candidate =
|lid: String,
source_id: Option<String>,
confidence: Option<f64>,
seen: &mut std::collections::HashSet<String>,
out: &mut Vec<(String, Option<String>, Option<f64>)>| {
if seen.insert(lid.clone()) {
out.push((lid, source_id, confidence));
}
};
if let Ok(mut edge_seed_stmt) = tx.prepare(&format!(
"SELECT ce.from_id, ce.to_id, ce.source_id, ce.confidence \
FROM search_index_edges sei \
JOIN canonical_edges ce ON ce.write_cursor = sei.write_cursor \
WHERE search_index_edges MATCH ?1 \
AND ce.superseded_at IS NULL{} \
AND (ce.temporal_fallback IS NULL OR ce.temporal_fallback = 0) \
ORDER BY bm25(search_index_edges), sei.write_cursor \
LIMIT ?2",
edge_validity_sql("ce", 3)
)) {
let rows = edge_seed_stmt.query_map(
rusqlite::params![match_expression, SEED_FTS_N as i64, view.edge_now()],
|row| {
Ok((
row.get::<_, String>(0)?,
row.get::<_, String>(1)?,
row.get::<_, Option<String>>(2)?,
row.get::<_, Option<f64>>(3)?,
))
},
)?;
for quad in rows {
let (from_id, to_id, source_id, confidence) = quad?;
push_candidate(
from_id,
source_id.clone(),
confidence,
&mut seen_candidates,
&mut candidate_seeds,
);
push_candidate(
to_id,
source_id,
confidence,
&mut seen_candidates,
&mut candidate_seeds,
);
}
}
{
let seed_validity = view.validity_sql("cn", 3);
let mut node_seed_stmt = tx.prepare(&format!(
"SELECT cn.logical_id, cn.source_id \
FROM search_index si \
JOIN canonical_nodes cn ON cn.write_cursor = si.write_cursor \
WHERE search_index MATCH ?1 \
AND cn.superseded_at IS NULL \
AND cn.state = 'active' \
AND cn.logical_id IS NOT NULL\
{seed_validity} \
ORDER BY bm25(search_index), si.write_cursor \
LIMIT ?2"
))?;
let mut seed_params: Vec<rusqlite::types::Value> = vec![
rusqlite::types::Value::Text(match_expression.to_string()),
rusqlite::types::Value::Integer(SEED_FTS_N as i64),
];
if let Some(now) = now_param {
seed_params.push(rusqlite::types::Value::Integer(now));
}
let rows = node_seed_stmt
.query_map(rusqlite::params_from_iter(seed_params.iter()), |row| {
Ok((row.get::<_, String>(0)?, row.get::<_, Option<String>>(1)?))
})?;
for pair in rows {
let (lid, source_id) = pair?;
push_candidate(lid, source_id, None, &mut seen_candidates, &mut candidate_seeds);
}
}
let active_validity = view.validity_sql("canonical_nodes", 2);
let mut active_stmt = tx.prepare(&format!(
"SELECT kind, body, write_cursor FROM canonical_nodes \
WHERE logical_id = ?1 AND superseded_at IS NULL AND state = 'active'\
{active_validity} LIMIT 1"
))?;
for (lid, source_id, seed_confidence) in candidate_seeds {
stats.seeds_considered += 1;
let mut active_params: Vec<rusqlite::types::Value> =
vec![rusqlite::types::Value::Text(lid.clone())];
if let Some(now) = now_param {
active_params.push(rusqlite::types::Value::Integer(now));
}
let row: Option<(String, String, i64)> = active_stmt
.query_row(rusqlite::params_from_iter(active_params.iter()), |r| {
Ok((r.get::<_, String>(0)?, r.get::<_, String>(1)?, r.get::<_, i64>(2)?))
})
.optional()?;
if let Some((kind, body, write_cursor)) = row {
stats.seeds_resolved += 1;
if visited.insert(lid.clone()) {
let id = derive_stable_id(Some(&lid), &body);
frontier.push_back((lid, 0));
if !seed_bodies.contains(body.as_str()) && candidates.len() < cap {
let score = if kind == "unknown" { SYNTHESIZED_PENALTY } else { 1.0 };
if let Some(c) = seed_confidence {
edge_confidence_by_cursor.insert(write_cursor as u64, c);
}
candidates.push(SearchHit {
id,
write_cursor: write_cursor as u64,
kind,
body,
score,
branch: SoftFallbackBranch::GraphArm,
source_id,
ce_score: None,
});
}
}
}
}
}
stats.frontier_nonempty = !frontier.is_empty();
let mut edge_stmt = tx.prepare(
&format!(
"SELECT e.from_id, e.to_id, e.source_id, e.confidence \
FROM canonical_edges e \
WHERE (e.from_id = ?1 OR e.to_id = ?1) \
AND e.superseded_at IS NULL{} \
AND (e.temporal_fallback IS NULL OR e.temporal_fallback = 0) \
ORDER BY e.write_cursor \
LIMIT 64",
edge_validity_sql("e", 2)
),
)?;
let body_validity = view.validity_sql("canonical_nodes", 2);
let mut body_stmt = tx.prepare(&format!(
"SELECT kind, body, write_cursor FROM canonical_nodes \
WHERE logical_id = ?1 AND superseded_at IS NULL AND state = 'active'\
{body_validity} \
LIMIT 1"
))?;
while let Some((lid, depth)) = frontier.pop_front() {
if candidates.len() >= cap {
break;
}
if depth >= max_depth {
continue;
}
let neighbors: Vec<(String, Option<String>, Option<f64>)> = {
let rows = edge_stmt.query_map(params![&lid, view.edge_now()], |row| {
Ok((
row.get::<_, String>(0)?,
row.get::<_, String>(1)?,
row.get::<_, Option<String>>(2)?,
row.get::<_, Option<f64>>(3)?,
))
})?;
rows.flatten()
.map(|(from_id, to_id, source_id, confidence)| {
let neighbor = if from_id == lid { to_id } else { from_id };
(neighbor, source_id, confidence)
})
.collect()
};
for (neighbor, edge_source_id, edge_confidence) in neighbors {
if visited.contains(&neighbor) {
continue;
}
visited.insert(neighbor.clone());
let mut body_params: Vec<rusqlite::types::Value> =
vec![rusqlite::types::Value::Text(neighbor.clone())];
if let Some(now) = now_param {
body_params.push(rusqlite::types::Value::Integer(now));
}
let row: Option<(String, String, i64)> = body_stmt
.query_row(rusqlite::params_from_iter(body_params.iter()), |row| {
Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?, row.get::<_, i64>(2)?))
})
.optional()?;
if let Some((kind, body, write_cursor)) = row {
if !seed_bodies.contains(body.as_str()) {
let hop_score = 1.0 / (1.0 + (depth + 1) as f64);
let score =
if kind == "unknown" { hop_score * SYNTHESIZED_PENALTY } else { hop_score };
let id = derive_stable_id(Some(&neighbor), &body);
if let Some(c) = edge_confidence {
edge_confidence_by_cursor.insert(write_cursor as u64, c);
}
candidates.push(SearchHit {
id,
write_cursor: write_cursor as u64,
kind,
body,
score,
branch: SoftFallbackBranch::GraphArm,
source_id: edge_source_id.clone(),
ce_score: None,
});
if candidates.len() >= cap {
break;
}
}
frontier.push_back((neighbor, depth + 1));
}
}
}
drop(edge_stmt);
drop(body_stmt);
tx.commit()?;
stats.graph_candidates_emitted = candidates.len() as u32;
Ok((candidates, stats, edge_confidence_by_cursor))
}
const READ_COLLECTION_MAX_LIMIT: usize = 1_000_000;
fn read_get_by_id_in_tx(
reader: &mut Connection,
logical_ids: &[String],
view: &ReadView,
) -> rusqlite::Result<Vec<Option<NodeRecord>>> {
if logical_ids.is_empty() {
return Ok(Vec::new());
}
let tx = reader.transaction_with_behavior(rusqlite::TransactionBehavior::Deferred)?;
let mut found: HashMap<String, NodeRecord> = HashMap::new();
{
let unique: Vec<&String> = {
let mut seen = std::collections::HashSet::new();
logical_ids.iter().filter(|id| seen.insert((*id).clone())).collect()
};
let placeholders = std::iter::repeat_n("?", unique.len()).collect::<Vec<_>>().join(", ");
let now_idx = unique.len() + 1;
let node_sql = view.node_sql("canonical_nodes", now_idx);
let sql = format!(
"SELECT logical_id, kind, body, write_cursor
FROM canonical_nodes
WHERE logical_id IN ({placeholders}){node_sql}
ORDER BY write_cursor"
);
let mut statement = tx.prepare(&sql)?;
let mut binds: Vec<rusqlite::types::Value> =
unique.iter().map(|s| rusqlite::types::Value::Text((*s).clone())).collect();
if let Some(now) = view.now_param() {
binds.push(rusqlite::types::Value::Integer(now));
}
let rows = statement.query_map(rusqlite::params_from_iter(binds.iter()), |row| {
let logical_id: String = row.get(0)?;
Ok(NodeRecord {
logical_id,
kind: row.get(1)?,
body: row.get(2)?,
write_cursor: row.get::<_, i64>(3)? as u64,
})
})?;
for row in rows {
let record = row?;
found.insert(record.logical_id.clone(), record);
}
}
let out = logical_ids.iter().map(|id| found.get(id).cloned()).collect();
Ok(out)
}
fn read_collection_in_tx(
reader: &mut Connection,
collection: &str,
after_id: Option<i64>,
limit: usize,
) -> rusqlite::Result<Vec<OpStoreRow>> {
if limit == 0 {
return Ok(Vec::new());
}
let clamped = limit.min(READ_COLLECTION_MAX_LIMIT) as i64;
let after = after_id.unwrap_or(0).max(0);
let tx = reader.transaction_with_behavior(rusqlite::TransactionBehavior::Deferred)?;
let mut statement = tx.prepare(
"SELECT id, collection_name, record_key, op_kind, payload_json, schema_id, write_cursor
FROM operational_mutations
WHERE collection_name = ?1 AND id > ?2
ORDER BY id
LIMIT ?3",
)?;
let rows = statement.query_map(params![collection, after, clamped], |row| {
Ok(OpStoreRow {
id: row.get(0)?,
collection: row.get(1)?,
record_key: row.get(2)?,
op_kind: row.get(3)?,
payload: row.get(4)?,
schema_id: row.get(5)?,
write_cursor: row.get::<_, i64>(6)? as u64,
})
})?;
let mut out = Vec::new();
for row in rows {
out.push(row?);
}
Ok(out)
}
fn read_list_in_tx(
reader: &mut Connection,
kind: &str,
predicates: &[Predicate],
limit: usize,
view: &ReadView,
) -> rusqlite::Result<Vec<NodeRecord>> {
if limit == 0 {
return Ok(Vec::new());
}
let json_valid_guard = if predicates.is_empty() { "" } else { " AND json_valid(body)" };
let now_idx = predicates.len() + 2;
let node_sql = view.node_sql("canonical_nodes", now_idx);
let mut sql = format!(
"SELECT logical_id, kind, body, write_cursor \
FROM canonical_nodes \
WHERE kind = ?1{node_sql} \
AND logical_id IS NOT NULL{json_valid_guard}"
);
for (i, pred) in predicates.iter().enumerate() {
let param_idx = i + 2; sql.push_str(" AND ");
sql.push_str(&pred.to_sql_clause(param_idx));
}
sql.push_str(&format!(" LIMIT {limit}"));
let tx = reader.transaction_with_behavior(rusqlite::TransactionBehavior::Deferred)?;
let mut statement = tx.prepare(&sql)?;
let mut params: Vec<rusqlite::types::Value> = Vec::with_capacity(2 + predicates.len());
params.push(rusqlite::types::Value::Text(kind.to_string()));
for pred in predicates {
params.push(pred.bind_value());
}
if let Some(now) = view.now_param() {
params.push(rusqlite::types::Value::Integer(now));
}
let rows = statement.query_map(rusqlite::params_from_iter(params.iter()), |row| {
Ok(NodeRecord {
logical_id: row.get(0)?,
kind: row.get(1)?,
body: row.get(2)?,
write_cursor: row.get::<_, i64>(3)? as u64,
})
})?;
let mut out = Vec::new();
for row in rows {
out.push(row?);
}
Ok(out)
}
const GRAPH_NEIGHBORS_HARD_CAP: usize = 50;
fn build_bfs_sql(direction: TraversalDirection, view: &ReadView) -> String {
let cap = GRAPH_NEIGHBORS_HARD_CAP;
let cte_cap = cap * cap;
const NOW_IDX: usize = 3;
const EDGE_NOW_IDX: usize = 4;
let (edge_join, target_expr) = match direction {
TraversalDirection::Outgoing => ("e.from_id = t.logical_id", "e.to_id"),
TraversalDirection::Incoming => ("e.to_id = t.logical_id", "e.from_id"),
TraversalDirection::Both => (
"(e.from_id = t.logical_id OR e.to_id = t.logical_id)",
"CASE WHEN e.from_id = t.logical_id THEN e.to_id ELSE e.from_id END",
),
};
let anchor_node = view.node_sql("n", NOW_IDX);
let next_node = view.node_sql("next_n", NOW_IDX);
let projection_node = view.node_sql("n", NOW_IDX);
let edge_valid = edge_validity_sql("e", EDGE_NOW_IDX);
format!(
"WITH RECURSIVE
traversal(logical_id, depth, visited) AS (
SELECT n.logical_id, 0, char(30) || n.logical_id || char(30)
FROM canonical_nodes n
WHERE n.logical_id = ?1{anchor_node}
UNION ALL
SELECT {target_expr}, t.depth + 1, t.visited || {target_expr} || char(30)
FROM traversal t
JOIN canonical_edges e ON {edge_join}
JOIN canonical_nodes next_n ON next_n.logical_id = {target_expr}{next_node}
WHERE t.depth < ?2
AND e.superseded_at IS NULL{edge_valid}
AND instr(t.visited, char(30) || {target_expr} || char(30)) = 0
LIMIT {cte_cap}
)
SELECT DISTINCT n.logical_id, n.kind, n.body, n.write_cursor
FROM traversal tr
JOIN canonical_nodes n ON n.logical_id = tr.logical_id
WHERE tr.logical_id != ?1{projection_node}
LIMIT {cap}"
)
}
fn build_bfs_with_depth_sql() -> String {
let cap = GRAPH_NEIGHBORS_HARD_CAP;
let cte_cap = cap * cap; let edge_valid = edge_validity_sql("e", 3);
format!(
"WITH RECURSIVE
traversal(logical_id, depth, visited) AS (
SELECT n.logical_id, 0, char(30) || n.logical_id || char(30)
FROM canonical_nodes n
WHERE n.logical_id = ?1 AND n.superseded_at IS NULL AND n.state = 'active'
UNION ALL
SELECT
CASE WHEN e.from_id = t.logical_id THEN e.to_id ELSE e.from_id END,
t.depth + 1,
t.visited || CASE WHEN e.from_id = t.logical_id THEN e.to_id ELSE e.from_id END || char(30)
FROM traversal t
JOIN canonical_edges e ON (e.from_id = t.logical_id OR e.to_id = t.logical_id)
JOIN canonical_nodes next_n
ON next_n.logical_id = CASE WHEN e.from_id = t.logical_id THEN e.to_id ELSE e.from_id END
AND next_n.superseded_at IS NULL AND next_n.state = 'active'
WHERE t.depth < ?2
AND e.superseded_at IS NULL{edge_valid}
AND instr(t.visited,
char(30) || CASE WHEN e.from_id = t.logical_id THEN e.to_id ELSE e.from_id END || char(30)) = 0
LIMIT {cte_cap}
)
SELECT n.logical_id, n.kind, n.body, n.write_cursor, MIN(tr.depth) AS min_depth
FROM traversal tr
JOIN canonical_nodes n ON n.logical_id = tr.logical_id
WHERE n.superseded_at IS NULL AND n.state = 'active'
AND tr.logical_id != ?1
GROUP BY n.logical_id
LIMIT {cap}"
)
}
fn crossed_boundary_since_in_tx(
reader: &mut Connection,
since: i64,
view: &ReadView,
) -> rusqlite::Result<Vec<BoundaryCrossing>> {
let upper = view.now_param().unwrap_or(i64::MAX);
let existence = view.existence_sql("canonical_nodes");
let sql = format!(
"SELECT logical_id, kind, body, write_cursor, valid_from, valid_until \
FROM canonical_nodes \
WHERE 1 = 1{existence} \
AND logical_id IS NOT NULL \
AND ( (valid_from IS NOT NULL AND valid_from > ?1 AND valid_from <= ?2) \
OR (valid_until IS NOT NULL AND valid_until > ?1 AND valid_until <= ?2) ) \
ORDER BY write_cursor"
);
let tx = reader.transaction_with_behavior(rusqlite::TransactionBehavior::Deferred)?;
let mut statement = tx.prepare(&sql)?;
let rows = statement.query_map(params![since, upper], |row| {
let valid_from: Option<i64> = row.get(4)?;
let valid_until: Option<i64> = row.get(5)?;
Ok(BoundaryCrossing {
node: NodeRecord {
logical_id: row.get(0)?,
kind: row.get(1)?,
body: row.get(2)?,
write_cursor: row.get::<_, i64>(3)? as u64,
},
became_valid_at: valid_from.filter(|t| *t > since && *t <= upper),
became_invalid_at: valid_until.filter(|t| *t > since && *t <= upper),
})
})?;
let mut out = Vec::new();
for row in rows {
out.push(row?);
}
Ok(out)
}
fn graph_neighbors_in_tx(
reader: &mut Connection,
root_logical_id: &str,
depth: u32,
direction: TraversalDirection,
view: &ReadView,
) -> rusqlite::Result<Vec<NodeRecord>> {
let sql = build_bfs_sql(direction, view);
let tx = reader.transaction_with_behavior(rusqlite::TransactionBehavior::Deferred)?;
let depth_i64 = depth as i64;
let mut statement = tx.prepare(&sql)?;
let frozen = (*view).freeze();
let binds: Vec<rusqlite::types::Value> = vec![
rusqlite::types::Value::Text(root_logical_id.to_string()),
rusqlite::types::Value::Integer(depth_i64),
rusqlite::types::Value::Integer(frozen.now_param().unwrap_or_default()),
rusqlite::types::Value::Integer(frozen.edge_now()),
];
let rows = statement.query_map(rusqlite::params_from_iter(binds.iter()), |row| {
Ok(NodeRecord {
logical_id: row.get(0)?,
kind: row.get(1)?,
body: row.get(2)?,
write_cursor: row.get::<_, i64>(3)? as u64,
})
})?;
let mut out = Vec::new();
for row in rows {
out.push(row?);
}
Ok(out)
}
fn search_expand_in_tx(
reader: &mut Connection,
search_hits: &[SearchHit],
depth: u32,
) -> rusqlite::Result<SearchExpandResult> {
let tx = reader.transaction_with_behavior(rusqlite::TransactionBehavior::Deferred)?;
let mut hit_logical_ids: Vec<Option<String>> = Vec::with_capacity(search_hits.len());
{
let mut node_stmt = tx.prepare(
"SELECT logical_id FROM canonical_nodes
WHERE write_cursor = ?1 AND superseded_at IS NULL AND state = 'active'
LIMIT 1",
)?;
let mut edge_stmt = tx.prepare(
"SELECT 1 FROM canonical_edges
WHERE write_cursor = ?1 AND superseded_at IS NULL
LIMIT 1",
)?;
for hit in search_hits {
if hit.branch == SoftFallbackBranch::TextEdge {
let cursor_i64 = hit.write_cursor as i64;
let active: Option<i32> =
edge_stmt.query_row([cursor_i64], |row| row.get(0)).optional()?;
if active.is_some() {
hit_logical_ids.push(Some(String::new())); } else {
hit_logical_ids.push(None); }
} else {
let cursor_i64 = hit.write_cursor as i64;
let resolved = node_stmt
.query_row([cursor_i64], |row| row.get::<_, Option<String>>(0))
.optional()?;
match resolved {
None => hit_logical_ids.push(None), Some(None) => hit_logical_ids.push(Some(String::new())), Some(Some(lid)) => hit_logical_ids.push(Some(lid)), }
}
}
}
let hit_id_set: std::collections::HashSet<String> =
hit_logical_ids.iter().filter_map(|id| id.clone()).filter(|s| !s.is_empty()).collect();
let bfs_sql = build_bfs_with_depth_sql();
let depth_i64 = depth as i64;
let mut nearest_hop: std::collections::HashMap<String, (NodeRecord, u32)> =
std::collections::HashMap::new();
if depth > 0 {
let mut bfs_stmt = tx.prepare(&bfs_sql)?;
let edge_now = current_epoch_seconds();
for root_id in hit_logical_ids.iter().flatten().filter(|s| !s.is_empty()) {
let neighbor_rows =
bfs_stmt.query_map(params![root_id, depth_i64, edge_now], |row| {
let node = NodeRecord {
logical_id: row.get(0)?,
kind: row.get(1)?,
body: row.get(2)?,
write_cursor: row.get::<_, i64>(3)? as u64,
};
let min_depth: i64 = row.get(4)?;
Ok((node, min_depth as u32))
})?;
for row_result in neighbor_rows {
let (node, hop_count) = row_result?;
if hit_id_set.contains(&node.logical_id) {
continue;
}
nearest_hop
.entry(node.logical_id.clone())
.and_modify(|(_, prev_hop)| {
if hop_count < *prev_hop {
*prev_hop = hop_count;
}
})
.or_insert((node, hop_count));
}
}
}
let mut expanded: Vec<(NodeRecord, u32)> = nearest_hop.into_values().collect();
expanded.sort_by(|(a, _), (b, _)| a.logical_id.cmp(&b.logical_id));
let resolved_hits: Vec<SearchHit> = search_hits
.iter()
.zip(hit_logical_ids.iter())
.filter_map(|(hit, lid)| lid.as_ref().map(|_| hit.clone()))
.collect();
let mut all_logical_ids: Vec<String> =
hit_logical_ids.into_iter().flatten().filter(|s| !s.is_empty()).collect();
for (node, _) in &expanded {
if !all_logical_ids.contains(&node.logical_id) {
all_logical_ids.push(node.logical_id.clone());
}
}
Ok(SearchExpandResult { search_hits: resolved_hits, expanded, all_logical_ids })
}
fn explain_graph_neighbors_in_tx(
reader: &mut Connection,
root_logical_id: &str,
depth: u32,
direction: TraversalDirection,
) -> rusqlite::Result<Vec<String>> {
let view = ReadView::default();
let bfs_sql = build_bfs_sql(direction, &view);
let explain_sql = format!("EXPLAIN QUERY PLAN {bfs_sql}");
let tx = reader.transaction_with_behavior(rusqlite::TransactionBehavior::Deferred)?;
let depth_i64 = depth as i64;
let mut statement = tx.prepare(&explain_sql)?;
let frozen = view.freeze();
let now = frozen.now_param().expect("the strict view always binds a validity instant");
let rows = statement
.query_map(params![root_logical_id, depth_i64, now, frozen.edge_now()], |row| {
row.get::<_, String>(3)
})?;
let mut out = Vec::new();
for row in rows {
out.push(row?);
}
Ok(out)
}
fn projection_dispatcher_loop(shared: Arc<ProjectionRuntimeShared>) {
let connection = match open_runtime_connection(&shared.path) {
Ok(connection) => connection,
Err(_) => return,
};
let dense_arm_live = shared.embedder.is_some();
loop {
let in_flight = {
let mut state = match shared.state.lock() {
Ok(state) => state,
Err(_) => return,
};
while !state.stopping
&& (!state.pending_scan
|| state.frozen
|| state.active_jobs + state.queued_jobs >= PROJECTION_INFLIGHT_LIMIT)
{
state = match shared.state_cvar.wait(state) {
Ok(state) => state,
Err(_) => return,
};
}
if state.stopping {
return;
}
state.pending_scan = false;
state.in_flight.clone()
};
let budget = {
let state = match shared.state.lock() {
Ok(state) => state,
Err(_) => return,
};
PROJECTION_INFLIGHT_LIMIT.saturating_sub(state.active_jobs + state.queued_jobs)
};
let fetch_cap = budget.clamp(1, PROJECTION_SCAN_FETCH);
let fetched =
next_pending_projection_jobs(&connection, &in_flight, fetch_cap, dense_arm_live);
debug_assert!(
fetched
.as_ref()
.map(|jobs| dense_arm_live || jobs.iter().all(|job| job.kind == EDGE_FACT_KIND))
.unwrap_or(true),
"no-embedder scan returned a NODE job: the exclusion must be in the scan's SQL"
);
match fetched {
Ok(jobs) if !jobs.is_empty() => {
if let Ok(mut state) = shared.state.lock() {
state.queued_jobs = state.queued_jobs.saturating_add(jobs.len());
for job in &jobs {
state.in_flight.insert(job.cursor);
}
state.pending_scan = true;
shared.state_cvar.notify_all();
}
if let Ok(mut queue) = shared.queue.lock() {
for job in jobs {
queue.push_back(job);
}
shared.queue_cvar.notify_all();
}
}
Ok(_) => {}
Err(_) => {
if let Ok(mut state) = shared.state.lock() {
state.pending_scan = false;
shared.state_cvar.notify_all();
}
}
}
}
}
fn projection_worker_loop(shared: Arc<ProjectionRuntimeShared>) {
let mut connection = match open_runtime_connection(&shared.path) {
Ok(connection) => connection,
Err(_) => return,
};
if ensure_vector_partition(&mut connection, shared.embedder_identity.dimension).is_err() {
return;
}
loop {
let jobs = {
let mut queue = match shared.queue.lock() {
Ok(queue) => queue,
Err(_) => return,
};
loop {
let stopping = shared.state.lock().map(|state| state.stopping).unwrap_or(true);
if stopping && queue.is_empty() {
return;
}
if let Some(job) = queue.pop_front() {
let mut jobs = vec![job];
while jobs.len() < PROJECTION_COMMIT_BATCH {
let Some(job) = queue.pop_front() else {
break;
};
jobs.push(job);
}
if let Ok(mut state) = shared.state.lock() {
state.queued_jobs = state.queued_jobs.saturating_sub(jobs.len());
state.active_jobs = state.active_jobs.saturating_add(jobs.len());
shared.state_cvar.notify_all();
}
break jobs;
}
queue = match shared.queue_cvar.wait(queue) {
Ok(queue) => queue,
Err(_) => return,
};
}
};
let commit_result = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
run_projection_jobs(&shared, &mut connection, &jobs)
})) {
Ok(result) => result,
Err(_) => commit_projection_panic_failures(&shared, &mut connection, &jobs),
};
if let Err(err) = commit_result {
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
report_projection_commit_failure(&shared, &err);
}));
#[cfg(debug_assertions)]
if let Some((reported, release)) = shared
.projection_commit_failure_pause
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.take()
{
reported.wait();
release.wait();
}
}
if let Ok(mut state) = shared.state.lock() {
state.active_jobs = state.active_jobs.saturating_sub(jobs.len());
for job in &jobs {
state.in_flight.remove(&job.cursor);
}
if !state.stopping {
state.pending_scan = true;
}
shared.state_cvar.notify_all();
}
}
}
enum ProjectionOutcome {
Success {
cursor: u64,
kind: String,
blob: Vec<u8>,
bin_blob: Vec<u8>,
},
Failure {
cursor: u64,
failure_code: &'static str,
},
Deferred,
}
fn run_projection_jobs(
shared: &ProjectionRuntimeShared,
connection: &mut Connection,
jobs: &[ProjectionJob],
) -> rusqlite::Result<()> {
let outcomes = embed_projection_batch(shared, jobs);
commit_projection_outcomes(connection, &outcomes, shared)
}
fn projection_batch_enabled() -> bool {
matches!(
std::env::var("FATHOMDB_PROJECTION_BATCH").ok().as_deref(),
Some("1") | Some("true") | Some("on")
)
}
fn embed_projection_batch(
shared: &ProjectionRuntimeShared,
jobs: &[ProjectionJob],
) -> Vec<ProjectionOutcome> {
let per_job = || jobs.iter().map(|job| run_projection_job(shared, job)).collect();
let Some(embedder) = shared.embedder.as_ref() else {
return per_job();
};
if jobs.len() < 2
|| shared.embed_circuit_open.load(Ordering::Relaxed)
|| !projection_batch_enabled()
{
return per_job();
}
let bodies: Vec<String> = jobs.iter().map(|job| job.body.clone()).collect();
let embed_timeout = Duration::from_millis(shared.embed_timeout_ms.load(Ordering::Relaxed));
let batch_timeout = embed_timeout.saturating_mul(jobs.len() as u32);
let vectors = {
let _embed_permit =
shared.embed_serialize.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
let threshold = shared.embed_circuit_threshold.load(Ordering::Relaxed);
if shared.embed_circuit_open.load(Ordering::Relaxed)
|| (threshold != 0 && shared.live_embed_threads.load(Ordering::Relaxed) >= threshold)
{
shared.embed_circuit_open.store(true, Ordering::Relaxed);
return per_job();
}
match embed_batch_with_watchdog(
embedder,
&bodies,
batch_timeout,
&shared.live_embed_threads,
) {
Ok(vectors) => vectors,
Err(_) => return per_job(),
}
};
if vectors.len() != jobs.len() {
return per_job();
}
let mut outcomes = Vec::with_capacity(jobs.len());
for (job, vector) in jobs.iter().zip(vectors) {
if u32::try_from(vector.len()).unwrap_or(u32::MAX) != shared.embedder_identity.dimension {
return per_job();
}
let blob = encode_vector_blob(&vector);
let bin_blob = blob.clone();
outcomes.push(ProjectionOutcome::Success {
cursor: job.cursor,
kind: job.kind.clone(),
blob,
bin_blob,
});
}
outcomes
}
fn commit_projection_panic_failures(
shared: &ProjectionRuntimeShared,
connection: &mut Connection,
jobs: &[ProjectionJob],
) -> rusqlite::Result<()> {
let outcomes: Vec<ProjectionOutcome> = jobs
.iter()
.map(|job| ProjectionOutcome::Failure {
cursor: job.cursor,
failure_code: "ProjectionPanic",
})
.collect();
commit_projection_outcomes(connection, &outcomes, shared)
}
fn report_projection_commit_failure(shared: &ProjectionRuntimeShared, err: &rusqlite::Error) {
let event = if let Some(code) = sqlite_extended_code_name(err) {
lifecycle::Event {
phase: lifecycle::Phase::Failed,
source: lifecycle::EventSource::SqliteInternal,
category: lifecycle::EventCategory::Error,
code: Some(code),
}
} else {
lifecycle::Event {
phase: lifecycle::Phase::Failed,
source: lifecycle::EventSource::Engine,
category: lifecycle::EventCategory::Error,
code: Some("StorageError"),
}
};
shared.subscribers.dispatch(&event);
}
fn embed_with_watchdog(
embedder: &Arc<dyn Embedder>,
body: &str,
timeout: Duration,
live: &Arc<AtomicU64>,
) -> Result<Vec<f32>, RuntimeEmbedderError> {
let (tx, rx) = mpsc::channel();
let embedder = Arc::clone(embedder);
let body = body.to_string();
live.fetch_add(1, Ordering::Relaxed);
let live_thread = Arc::clone(live);
thread::spawn(move || {
let outcome =
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| embedder.embed(&body)));
let _ = tx.send(outcome);
live_thread.fetch_sub(1, Ordering::Relaxed);
});
match rx.recv_timeout(timeout) {
Ok(Ok(result)) => result,
Ok(Err(panic_payload)) => std::panic::resume_unwind(panic_payload),
Err(mpsc::RecvTimeoutError::Timeout) => Err(RuntimeEmbedderError::Timeout),
Err(mpsc::RecvTimeoutError::Disconnected) => Err(RuntimeEmbedderError::Failed {
message: "embed watchdog thread dropped its result channel".to_string(),
}),
}
}
fn embed_batch_with_watchdog(
embedder: &Arc<dyn Embedder>,
bodies: &[String],
timeout: Duration,
live: &Arc<AtomicU64>,
) -> Result<Vec<Vec<f32>>, RuntimeEmbedderError> {
let (tx, rx) = mpsc::channel();
let embedder = Arc::clone(embedder);
let bodies = bodies.to_vec();
live.fetch_add(1, Ordering::Relaxed);
let live_thread = Arc::clone(live);
thread::spawn(move || {
let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let refs: Vec<&str> = bodies.iter().map(String::as_str).collect();
embedder.embed_batch(&refs)
}));
let _ = tx.send(outcome);
live_thread.fetch_sub(1, Ordering::Relaxed);
});
match rx.recv_timeout(timeout) {
Ok(Ok(result)) => result,
Ok(Err(panic_payload)) => std::panic::resume_unwind(panic_payload),
Err(mpsc::RecvTimeoutError::Timeout) => Err(RuntimeEmbedderError::Timeout),
Err(mpsc::RecvTimeoutError::Disconnected) => Err(RuntimeEmbedderError::Failed {
message: "embed batch watchdog thread dropped its result channel".to_string(),
}),
}
}
fn run_projection_job(shared: &ProjectionRuntimeShared, job: &ProjectionJob) -> ProjectionOutcome {
if shared.embedder.is_none() && job.kind != EDGE_FACT_KIND {
return ProjectionOutcome::Deferred;
}
if shared.embed_circuit_open.load(Ordering::Relaxed) {
return ProjectionOutcome::Failure { cursor: job.cursor, failure_code: "EmbedderError" };
}
let delays = shared.retry_delays_ms.lock().map(|delays| delays.clone()).unwrap_or_default();
let mut last_code = "EmbedderError";
for (attempt, delay_ms) in std::iter::once(0_u64).chain(delays.iter().copied()).enumerate() {
if attempt > 0 {
if shared.state.lock().map(|state| state.stopping).unwrap_or(true) {
return ProjectionOutcome::Failure { cursor: job.cursor, failure_code: last_code };
}
thread::sleep(Duration::from_millis(delay_ms));
}
if shared.embed_circuit_open.load(Ordering::Relaxed) {
return ProjectionOutcome::Failure { cursor: job.cursor, failure_code: last_code };
}
let embed_timeout = Duration::from_millis(shared.embed_timeout_ms.load(Ordering::Relaxed));
let vector = match shared.embedder.as_ref() {
Some(embedder) => {
let _embed_permit =
shared.embed_serialize.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
let threshold = shared.embed_circuit_threshold.load(Ordering::Relaxed);
if shared.embed_circuit_open.load(Ordering::Relaxed)
|| (threshold != 0
&& shared.live_embed_threads.load(Ordering::Relaxed) >= threshold)
{
shared.embed_circuit_open.store(true, Ordering::Relaxed);
return ProjectionOutcome::Failure {
cursor: job.cursor,
failure_code: last_code,
};
}
match embed_with_watchdog(
embedder,
&job.body,
embed_timeout,
&shared.live_embed_threads,
) {
Ok(vector) => vector,
Err(RuntimeEmbedderError::Timeout) => {
last_code = "EmbedderError";
continue;
}
Err(RuntimeEmbedderError::Failed { .. }) => {
last_code = "EmbedderError";
continue;
}
}
}
None => {
last_code = "EmbedderNotConfiguredError";
continue;
}
};
if u32::try_from(vector.len()).unwrap_or(u32::MAX) != shared.embedder_identity.dimension {
last_code = "EmbedderDimensionMismatchError";
continue;
}
let blob = encode_vector_blob(&vector);
let bin_blob = blob.clone();
return ProjectionOutcome::Success {
cursor: job.cursor,
kind: job.kind.clone(),
blob,
bin_blob,
};
}
ProjectionOutcome::Failure { cursor: job.cursor, failure_code: last_code }
}
fn pending_edge_projection_from_where(now_idx: usize) -> String {
format!(
"FROM canonical_edges ce
JOIN _fathomdb_vector_kinds
ON _fathomdb_vector_kinds.kind = 'edge_fact'
LEFT JOIN _fathomdb_projection_terminal pt
ON pt.write_cursor = ce.write_cursor
WHERE ce.body IS NOT NULL
AND ce.superseded_at IS NULL{}
AND pt.write_cursor IS NULL",
edge_validity_sql("ce", now_idx)
)
}
fn next_pending_projection_jobs(
connection: &Connection,
in_flight: &BTreeSet<u64>,
max_jobs: usize,
dense_arm_live: bool,
) -> rusqlite::Result<Vec<ProjectionJob>> {
if max_jobs == 0 {
return Ok(Vec::new());
}
let cursor = load_projection_cursor(connection)?;
let sql_limit = max_jobs.saturating_add(in_flight.len()).min(256);
let node_arm = if dense_arm_live {
"SELECT canonical_nodes.write_cursor AS write_cursor,
canonical_nodes.kind AS kind,
canonical_nodes.body AS body
FROM canonical_nodes
JOIN _fathomdb_vector_kinds
ON _fathomdb_vector_kinds.kind = canonical_nodes.kind
LEFT JOIN _fathomdb_projection_terminal
ON _fathomdb_projection_terminal.write_cursor = canonical_nodes.write_cursor
WHERE canonical_nodes.write_cursor > ?1
AND _fathomdb_projection_terminal.write_cursor IS NULL
UNION ALL
"
} else {
""
};
let sql = format!(
"SELECT write_cursor, kind, body FROM (
{node_arm}SELECT ce.write_cursor AS write_cursor,
'edge_fact' AS kind,
ce.body AS body
{edge_arm}
AND ce.write_cursor > ?1
) ORDER BY write_cursor
LIMIT {sql_limit}",
edge_arm = pending_edge_projection_from_where(2)
);
let mut statement = connection.prepare_cached(&sql)?;
let rows = statement.query_map(params![cursor, current_epoch_seconds()], |row| {
Ok(ProjectionJob { cursor: row.get(0)?, kind: row.get(1)?, body: row.get(2)? })
})?;
let mut jobs = Vec::with_capacity(max_jobs);
for row in rows {
let job = row?;
if in_flight.contains(&job.cursor) {
continue;
}
jobs.push(job);
if jobs.len() >= max_jobs {
break;
}
}
Ok(jobs)
}
fn database_has_pending_projection_work(path: &Path) -> rusqlite::Result<bool> {
let connection = open_runtime_connection(path)?;
connection_has_pending_projection_work(&connection)
}
fn connection_has_pending_projection_work(connection: &Connection) -> rusqlite::Result<bool> {
let cursor = load_projection_cursor(connection)?;
let has_node_work: bool = connection
.query_row(
"SELECT 1
FROM canonical_nodes
JOIN _fathomdb_vector_kinds ON _fathomdb_vector_kinds.kind = canonical_nodes.kind
LEFT JOIN _fathomdb_projection_terminal
ON _fathomdb_projection_terminal.write_cursor = canonical_nodes.write_cursor
WHERE canonical_nodes.write_cursor > ?1
AND _fathomdb_projection_terminal.write_cursor IS NULL
LIMIT 1",
[cursor],
|_row| Ok(true),
)
.or_else(|err| match err {
rusqlite::Error::QueryReturnedNoRows => Ok(false),
_ => Err(err),
})?;
if has_node_work {
return Ok(true);
}
connection
.query_row(
&format!("SELECT 1 {} LIMIT 1", pending_edge_projection_from_where(1)),
params![current_epoch_seconds()],
|_row| Ok(true),
)
.or_else(|err| match err {
rusqlite::Error::QueryReturnedNoRows => Ok(false),
_ => Err(err),
})
}
fn derive_dense_readiness(connection: &Connection) -> Result<DenseReadiness, EngineError> {
if connection_has_pending_projection_work(connection).map_err(|_| EngineError::Storage)? {
Ok(DenseReadiness::Embedding)
} else {
Ok(DenseReadiness::Ready)
}
}
struct CanonicalNodeRow {
cursor: u64,
kind: String,
body: String,
row_kind: RowKind,
attr_projected: bool,
}
fn reproject_search_index_after_tokenizer_upgrade(connection: &Connection) -> rusqlite::Result<()> {
let rows = canonical_node_rows(connection)?;
connection.execute_batch("BEGIN IMMEDIATE")?;
let result = (|| {
truncate_row_projections_in(connection, &[ProjectionClass::NodeFts])?;
for row in &rows {
project_canonical_node_row(
connection,
row.cursor,
&row.kind,
&row.body,
row.row_kind,
ProjectionPass::FtsOnly,
row.attr_projected,
)?;
}
connection.execute(
"INSERT INTO _fathomdb_open_state(key, value) VALUES(?1, ?2)
ON CONFLICT(key) DO UPDATE SET value = excluded.value",
params![SEARCH_INDEX_TOKENIZER_REPROJECT_MARKER_KEY, "1"],
)?;
Ok(())
})();
match result {
Ok(()) => connection.execute_batch("COMMIT"),
Err(err) => {
let _ = connection.execute_batch("ROLLBACK");
Err(err)
}
}
}
fn search_index_tokenizer_reproject_complete(connection: &Connection) -> rusqlite::Result<bool> {
match connection.query_row(
"SELECT value FROM _fathomdb_open_state WHERE key = ?1",
[SEARCH_INDEX_TOKENIZER_REPROJECT_MARKER_KEY],
|row| row.get::<_, String>(0),
) {
Ok(value) => Ok(value == "1"),
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(false),
Err(rusqlite::Error::SqliteFailure(_, Some(ref message)))
if message.contains("no such table") =>
{
Ok(true)
}
Err(err) => Err(err),
}
}
fn edge_vector_prune_complete(connection: &Connection) -> rusqlite::Result<bool> {
match connection.query_row(
"SELECT value FROM _fathomdb_open_state WHERE key = ?1",
[EDGE_VECTOR_PRUNE_MARKER_KEY],
|row| row.get::<_, String>(0),
) {
Ok(value) => Ok(value == "1"),
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(false),
Err(rusqlite::Error::SqliteFailure(_, Some(ref message)))
if message.contains("no such table") =>
{
Ok(true)
}
Err(err) => Err(err),
}
}
fn prune_orphaned_edge_vectors(connection: &Connection) -> rusqlite::Result<()> {
connection.execute_batch("BEGIN IMMEDIATE")?;
let result = (|| {
let sidecar: std::collections::HashSet<i64> = {
let mut statement =
connection.prepare("SELECT write_cursor FROM _fathomdb_vector_rows")?;
let rows = statement.query_map([], |row| row.get::<_, i64>(0))?;
let mut set = std::collections::HashSet::new();
for r in rows {
set.insert(r?);
}
set
};
let vec_rowids: Vec<i64> = {
let mut statement = connection.prepare("SELECT rowid FROM vector_default")?;
let rows = statement.query_map([], |row| row.get::<_, i64>(0))?;
let mut out = Vec::new();
for r in rows {
out.push(r?);
}
out
};
for rowid in vec_rowids {
if !sidecar.contains(&rowid) {
delete_vector_partition_row(connection, rowid)?;
}
}
connection.execute(
"INSERT INTO _fathomdb_open_state(key, value) VALUES(?1, ?2)
ON CONFLICT(key) DO UPDATE SET value = excluded.value",
params![EDGE_VECTOR_PRUNE_MARKER_KEY, "1"],
)?;
Ok(())
})();
match result {
Ok(()) => connection.execute_batch("COMMIT"),
Err(err) => {
let _ = connection.execute_batch("ROLLBACK");
Err(err)
}
}
}
fn canonical_node_rows(connection: &Connection) -> rusqlite::Result<Vec<CanonicalNodeRow>> {
let mut statement = connection.prepare(
"SELECT write_cursor, kind, body, row_kind, state, superseded_at \
FROM canonical_nodes ORDER BY write_cursor",
)?;
let rows = statement.query_map([], |row| {
let state: Option<String> = row.get::<_, Option<String>>(4)?;
let superseded_at: Option<i64> = row.get::<_, Option<i64>>(5)?;
Ok(CanonicalNodeRow {
cursor: row.get::<_, u64>(0)?,
kind: row.get::<_, String>(1)?,
body: row.get::<_, String>(2)?,
row_kind: row_kind_from_column(&row.get::<_, String>(3)?),
attr_projected: state.as_deref() == Some("active") && superseded_at.is_none(),
})
})?;
rows.collect()
}
fn row_kind_from_column(value: &str) -> RowKind {
match value {
"coverage" => RowKind::Coverage,
"graph" => RowKind::Graph,
_ => RowKind::Leaf,
}
}
#[cfg(feature = "operator")]
fn hex_encode(bytes: &[u8]) -> String {
let mut out = String::with_capacity(bytes.len() * 2);
for byte in bytes {
out.push(hex_nibble(byte >> 4));
out.push(hex_nibble(byte & 0x0f));
}
out
}
#[cfg(feature = "operator")]
fn hex_nibble(value: u8) -> char {
match value {
0..=9 => (b'0' + value) as char,
10..=15 => (b'a' + value - 10) as char,
_ => unreachable!(),
}
}
#[cfg(feature = "operator")]
fn physical_section(connection: &Connection, full: bool) -> Section {
let mut findings = Vec::new();
if let Err(err) = connection.query_row("PRAGMA page_count", [], |row| row.get::<_, i64>(0)) {
findings.push(Finding {
code: "E_CORRUPT_HEADER",
stage: "PhysicalProbe",
locator: locator_from_rusqlite_error(&err),
doc_anchor: "design/recovery.md#header-malformed",
detail: format!("page_count probe failed: {err}"),
});
}
if full {
match collect_integrity_check_findings(connection) {
Ok(rows) => findings.extend(rows),
Err(err) => findings.push(Finding {
code: "E_CORRUPT_INTEGRITY_CHECK",
stage: "IntegrityCheck",
locator: locator_from_rusqlite_error(&err),
doc_anchor: "design/recovery.md#integrity-check-full-findings",
detail: format!("PRAGMA integrity_check failed: {err}"),
}),
}
}
if findings.is_empty() {
Section::Clean
} else {
Section::Findings(findings)
}
}
#[cfg(feature = "operator")]
fn logical_section(connection: &Connection) -> Section {
let mut findings = Vec::new();
if let Err(err) = connection.query_row("PRAGMA schema_version", [], |row| row.get::<_, i64>(0))
{
findings.push(Finding {
code: "E_CORRUPT_SCHEMA",
stage: "SchemaProbe",
locator: locator_from_rusqlite_error(&err),
doc_anchor: "design/recovery.md#schema-inconsistent",
detail: format!("schema_version probe failed: {err}"),
});
}
match connection.query_row("PRAGMA user_version", [], |row| row.get::<_, u32>(0)) {
Ok(0) => findings.push(Finding {
code: "E_CORRUPT_SCHEMA",
stage: "SchemaProbe",
locator: CorruptionLocator::MigrationStep { from: 0, to: 0 },
doc_anchor: "design/recovery.md#schema-inconsistent",
detail: "user_version is zero".to_string(),
}),
Ok(_) => {}
Err(err) => findings.push(Finding {
code: "E_CORRUPT_SCHEMA",
stage: "SchemaProbe",
locator: locator_from_rusqlite_error(&err),
doc_anchor: "design/recovery.md#schema-inconsistent",
detail: format!("user_version probe failed: {err}"),
}),
}
if findings.is_empty() {
Section::Clean
} else {
Section::Findings(findings)
}
}
#[cfg(feature = "operator")]
fn semantic_section(connection: &Connection) -> Section {
match load_default_profile(connection) {
Ok(_) => Section::Clean,
Err(rusqlite::Error::QueryReturnedNoRows) => Section::Findings(vec![Finding {
code: "E_CORRUPT_EMBEDDER_IDENTITY",
stage: "EmbedderIdentity",
locator: CorruptionLocator::OpaqueSqliteError { sqlite_extended_code: 0 },
doc_anchor: "design/recovery.md#embedder-identity-drift",
detail: "default embedder profile row is missing".to_string(),
}]),
Err(err) => Section::Findings(vec![Finding {
code: "E_CORRUPT_EMBEDDER_IDENTITY",
stage: "EmbedderIdentity",
locator: locator_from_rusqlite_error(&err),
doc_anchor: "design/recovery.md#embedder-identity-drift",
detail: format!("default embedder profile probe failed: {err}"),
}]),
}
}
#[cfg(feature = "operator")]
fn collect_integrity_check_findings(connection: &Connection) -> rusqlite::Result<Vec<Finding>> {
let mut statement = connection.prepare("PRAGMA integrity_check")?;
let rows = statement.query_map([], |row| row.get::<_, String>(0))?;
let mut findings = Vec::new();
for row in rows {
let message = row?;
if message == "ok" {
continue;
}
findings.push(Finding {
code: "E_CORRUPT_INTEGRITY_CHECK",
stage: "IntegrityCheck",
locator: CorruptionLocator::OpaqueSqliteError {
sqlite_extended_code: rusqlite::ffi::SQLITE_CORRUPT,
},
doc_anchor: "design/recovery.md#integrity-check-full-findings",
detail: message,
});
}
Ok(findings)
}
#[cfg(feature = "operator")]
fn locator_from_rusqlite_error(err: &rusqlite::Error) -> CorruptionLocator {
let extended = err.sqlite_error().map(|inner| inner.extended_code).unwrap_or(0);
CorruptionLocator::OpaqueSqliteError { sqlite_extended_code: extended }
}
fn open_runtime_connection(path: &Path) -> rusqlite::Result<Connection> {
let connection = Connection::open(path)?;
connection.pragma_update(None, "journal_mode", "WAL")?;
connection.pragma_update(None, "secure_delete", "ON")?;
Ok(connection)
}
fn load_projection_cursor(connection: &Connection) -> rusqlite::Result<u64> {
connection
.query_row(
"SELECT value FROM _fathomdb_open_state WHERE key = ?1",
[PROJECTION_CURSOR_KEY],
|row| row.get::<_, String>(0),
)
.map(|value| value.parse::<u64>().unwrap_or(0))
.or_else(|err| match err {
rusqlite::Error::QueryReturnedNoRows => Ok(0),
_ => Err(err),
})
}
fn store_projection_cursor(connection: &Connection, cursor: u64) -> rusqlite::Result<()> {
connection.execute(
"INSERT INTO _fathomdb_open_state(key, value) VALUES(?1, ?2)
ON CONFLICT(key) DO UPDATE SET value = excluded.value",
params![PROJECTION_CURSOR_KEY, cursor.to_string()],
)?;
Ok(())
}
fn record_projection_terminal(
connection: &Connection,
cursor: u64,
state: &str,
) -> rusqlite::Result<()> {
connection.execute(
"INSERT OR IGNORE INTO _fathomdb_projection_terminal(write_cursor, state) VALUES(?1, ?2)",
params![cursor, state],
)?;
Ok(())
}
fn terminal_state_for_cursor(
connection: &Connection,
cursor: u64,
) -> rusqlite::Result<Option<String>> {
connection
.query_row(
"SELECT state FROM _fathomdb_projection_terminal WHERE write_cursor = ?1",
[cursor],
|row| row.get::<_, String>(0),
)
.map(Some)
.or_else(|err| match err {
rusqlite::Error::QueryReturnedNoRows => Ok(None),
_ => Err(err),
})
}
fn advance_projection_cursor(connection: &Connection) -> rusqlite::Result<u64> {
let mut cursor = load_projection_cursor(connection)?;
loop {
let next = cursor.saturating_add(1);
if terminal_state_for_cursor(connection, next)?.is_some() {
cursor = next;
} else {
break;
}
}
store_projection_cursor(connection, cursor)?;
Ok(cursor)
}
fn commit_projection_outcomes(
connection: &mut Connection,
outcomes: &[ProjectionOutcome],
shared: &ProjectionRuntimeShared,
) -> rusqlite::Result<()> {
let embedder_identity = &shared.embedder_identity;
let mc = identity_requires_mean_centering(embedder_identity);
let _gate = shared.commit_gate.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
let tx = connection.transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)?;
let mut shared_accumulator =
shared.mean_accumulator.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
let mut candidate_accumulator = shared_accumulator.clone();
let mut current_mean: Option<Vec<f32>> = if mc {
tx.query_row(
"SELECT mean_vec FROM _fathomdb_embedder_profiles WHERE profile = 'default'",
[],
|row| row.get::<_, Option<Vec<u8>>>(0),
)
.ok()
.flatten()
.map(|bytes| decode_vector_blob(&bytes))
} else {
None
};
let mut staged_events: Vec<EmbedderEvent> = Vec::new();
for outcome in outcomes {
match outcome {
ProjectionOutcome::Success { cursor, kind, blob, bin_blob } => {
if terminal_state_for_cursor(&tx, *cursor)?.is_some() {
continue;
}
let pin_mean: Option<Vec<f32>> = if mc && current_mean.is_none() {
match candidate_accumulator.as_mut() {
Some(a) => {
a.add(&decode_vector_blob(bin_blob));
if a.count() >= MEAN_VEC_PIN_THRESHOLD {
let mean = a.materialize();
candidate_accumulator = None;
Some(mean)
} else {
None
}
}
None => None,
}
} else {
None
};
let source_type = resolve_source_type(kind).map_err(|_| {
rusqlite::Error::SqliteFailure(
rusqlite::ffi::Error::new(rusqlite::ffi::SQLITE_CONSTRAINT),
Some(format!("unknown kind for source_type mapping: {kind}")),
)
})?;
let now_unix =
SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs()
as i64;
tx.execute(
"INSERT OR IGNORE INTO _fathomdb_vector_rows(rowid, kind, write_cursor) VALUES(?1, ?2, ?3)",
params![cursor, kind, cursor],
)?;
let centered_blob: Vec<u8> = match ¤t_mean {
Some(mean) if mean.len() * 4 == bin_blob.len() => {
encode_vector_blob(&subtract_mean(&decode_vector_blob(bin_blob), mean))
}
_ => bin_blob.clone(),
};
if actual_vector_attr_columns(&tx)?.is_empty() {
tx.execute(
"INSERT OR IGNORE INTO vector_default(
rowid, embedding, embedding_bin, source_type, kind, created_at, status
) VALUES(?1, ?2, vec_quantize_binary(?3), ?4, ?5, ?6, '')",
params![cursor, blob, centered_blob, source_type, kind, now_unix],
)?;
} else {
let body: String = tx
.query_row(
"SELECT body FROM canonical_nodes WHERE write_cursor = ?1 LIMIT 1",
[*cursor as i64],
|row| row.get(0),
)
.optional()?
.unwrap_or_default();
let (cols_sql, ph_sql, attr_vals) =
vector_attr_insert_fragments(&tx, &body, 7)?;
let sql = format!(
"INSERT OR IGNORE INTO vector_default(
rowid, embedding, embedding_bin, source_type, kind, created_at, status{cols_sql}
) VALUES(?1, ?2, vec_quantize_binary(?3), ?4, ?5, ?6, ''{ph_sql})"
);
let mut pv: Vec<rusqlite::types::Value> = vec![
rusqlite::types::Value::Integer(*cursor as i64),
rusqlite::types::Value::Blob(blob.clone()),
rusqlite::types::Value::Blob(centered_blob.clone()),
rusqlite::types::Value::Text(source_type.to_string()),
rusqlite::types::Value::Text(kind.to_string()),
rusqlite::types::Value::Integer(now_unix),
];
pv.extend(attr_vals);
tx.execute(&sql, rusqlite::params_from_iter(pv.iter()))?;
}
record_projection_terminal(&tx, *cursor, "up_to_date")?;
if let Some(mean) = pin_mean {
tx.execute(
"UPDATE _fathomdb_embedder_profiles SET mean_vec = ?1 WHERE profile = 'default'",
params![encode_vector_blob(&mean)],
)?;
let rows: Vec<(i64, Vec<u8>)> = {
let mut statement = tx.prepare(
"SELECT rowid, embedding FROM vector_default ORDER BY rowid",
)?;
let mapped = statement.query_map([], |row| {
Ok((row.get::<_, i64>(0)?, row.get::<_, Vec<u8>>(1)?))
})?;
let mut out = Vec::new();
for r in mapped {
out.push(r?);
}
out
};
let (doc_count, _) =
run_pin_and_requantize_pass(&tx, &rows, &mean).map_err(|_| {
rusqlite::Error::SqliteFailure(
rusqlite::ffi::Error::new(rusqlite::ffi::SQLITE_ERROR),
Some("mean-centering re-quantize pass failed".to_string()),
)
})?;
staged_events.push(EmbedderEvent::MeanVecPinned {
dim: u32::try_from(mean.len()).unwrap_or(u32::MAX),
doc_count,
});
current_mean = Some(mean);
}
}
ProjectionOutcome::Failure { cursor, failure_code } => {
if terminal_state_for_cursor(&tx, *cursor)?.is_some() {
continue;
}
let existing: u64 = tx.query_row(
"SELECT COUNT(*) FROM operational_mutations
WHERE collection_name = 'projection_failures'
AND json_extract(payload_json, '$.write_cursor') = ?1",
[cursor],
|row| row.get(0),
)?;
if existing == 0 {
let payload = format!(
r#"{{"write_cursor":{cursor},"failure_code":"{failure_code}","recorded_at":0}}"#
);
tx.execute(
"INSERT INTO operational_mutations(
collection_name, record_key, op_kind, payload_json, schema_id, write_cursor
) VALUES('projection_failures', ?1, 'append', ?2, NULL, ?3)",
params![cursor.to_string(), payload, cursor],
)?;
}
record_projection_terminal(&tx, *cursor, "failed")?;
}
ProjectionOutcome::Deferred => {}
}
}
advance_projection_cursor(&tx)?;
#[cfg(debug_assertions)]
match shared.force_projection_commit_failure.swap(0, Ordering::SeqCst) {
1 => {
return Err(rusqlite::Error::SqliteFailure(
rusqlite::ffi::Error::new(rusqlite::ffi::SQLITE_BUSY),
Some("forced projection commit failure".to_string()),
));
}
2 => return Err(rusqlite::Error::InvalidQuery),
_ => {}
}
tx.commit()?;
*shared_accumulator = candidate_accumulator;
if !staged_events.is_empty() {
if let Ok(mut events) = shared.pending_events.lock() {
events.extend(staged_events);
}
}
Ok(())
}
fn recover_mean_vec_pin(
connection: &mut Connection,
identity: &EmbedderIdentity,
) -> Result<(), EngineError> {
let tx = connection.transaction().map_err(|_| EngineError::Storage)?;
recompute_mean_in_tx(&tx, identity)?;
tx.commit().map_err(|_| EngineError::Storage)?;
Ok(())
}
fn recompute_mean_in_tx(
tx: &rusqlite::Transaction<'_>,
identity: &EmbedderIdentity,
) -> Result<MeanRecomputeReport, EngineError> {
recompute_mean_in_tx_inner(tx, identity, false)
}
fn recompute_mean_in_tx_inner(
tx: &rusqlite::Transaction<'_>,
identity: &EmbedderIdentity,
fail_after_mean_update: bool,
) -> Result<MeanRecomputeReport, EngineError> {
let started = Instant::now();
let dim = identity.dimension as usize;
let old_mean = read_pinned_mean_vec(tx, identity.dimension)?;
let rows: Vec<(i64, Vec<u8>)> = {
let mut statement = tx
.prepare("SELECT rowid, embedding FROM vector_default ORDER BY rowid")
.map_err(|_| EngineError::Storage)?;
let mapped = statement
.query_map([], |row| Ok((row.get::<_, i64>(0)?, row.get::<_, Vec<u8>>(1)?)))
.map_err(|_| EngineError::Storage)?;
let mut out = Vec::new();
for r in mapped {
out.push(r.map_err(|_| EngineError::Storage)?);
}
out
};
let mut accumulator = MeanAccumulator::new(dim);
for (_rowid, blob) in &rows {
if blob.len() != dim * 4 {
return Err(EngineError::Storage);
}
accumulator.add(&decode_vector_blob(blob));
}
let old_doc_count = accumulator.count();
let mean = accumulator.materialize();
let drift_cos_before = match &old_mean {
Some(old) => cosine_similarity(&mean, old),
None => 1.0,
};
tx.execute(
"UPDATE _fathomdb_embedder_profiles SET mean_vec = ?1 WHERE profile = 'default'",
params![encode_vector_blob(&mean)],
)
.map_err(|_| EngineError::Storage)?;
if fail_after_mean_update {
return Err(EngineError::Storage);
}
let (doc_count, _) = run_pin_and_requantize_pass(tx, &rows, &mean)?;
Ok(MeanRecomputeReport {
dim: u32::try_from(dim).unwrap_or(u32::MAX),
old_doc_count,
doc_count_requantized: doc_count,
drift_cos_before,
mean_was_pinned: old_mean.is_some(),
elapsed_ms: u64::try_from(started.elapsed().as_millis()).unwrap_or(u64::MAX),
})
}
fn enforce_provenance_retention(connection: &Connection, cap: u64) -> rusqlite::Result<()> {
if cap == 0 {
return Ok(());
}
let exempt = ERASURE_AUDIT_COLLECTIONS
.iter()
.copied()
.chain(std::iter::once(ERASURE_PENDING_REDACTION_COLLECTION))
.map(|name| format!("'{name}'"))
.collect::<Vec<_>>()
.join(", ");
let slack = cap.max(20) / 20;
let upper = cap.saturating_add(slack.max(1));
let count: u64 = connection.query_row(
&format!(
"SELECT COUNT(*) FROM operational_mutations
WHERE collection_name NOT IN ({exempt})"
),
[],
|row| row.get(0),
)?;
if count <= upper {
return Ok(());
}
let to_delete = count.saturating_sub(cap);
connection.execute(
&format!(
"DELETE FROM operational_mutations
WHERE id IN (
SELECT id FROM operational_mutations
WHERE collection_name NOT IN ({exempt})
ORDER BY id
LIMIT ?1
)"
),
[to_delete],
)?;
Ok(())
}
fn collect_erased_stable_ids(
tx: &Connection,
node_sql: &str,
edge_sql: &str,
bind: &str,
) -> Result<Vec<String>, EngineError> {
let mut ids = Vec::new();
for sql in [node_sql, edge_sql] {
let mut stmt = tx.prepare(sql).map_err(|_| EngineError::Storage)?;
let rows = stmt
.query_map(params![bind], |row| {
Ok((row.get::<_, Option<String>>(0)?, row.get::<_, Option<String>>(1)?))
})
.map_err(|_| EngineError::Storage)?;
for row in rows {
let (logical_id, body) = row.map_err(|_| EngineError::Storage)?;
ids.push(
derive_stable_id(logical_id.as_deref(), body.as_deref().unwrap_or(""))
.to_prefixed(),
);
}
}
ids.sort_unstable();
ids.dedup();
Ok(ids)
}
fn enqueue_pending_redaction(
tx: &Connection,
verb: &str,
erased_stable_ids: &[String],
write_cursor: u64,
) -> Result<(), EngineError> {
if erased_stable_ids.is_empty() {
return Ok(());
}
let payload =
serde_json::json!({ "verb": verb, "erased_stable_ids": erased_stable_ids }).to_string();
tx.execute(
"INSERT INTO operational_mutations(
collection_name, record_key, op_kind, payload_json, schema_id, write_cursor
) VALUES(?1, ?2, 'append', ?3, NULL, ?4)",
params![ERASURE_PENDING_REDACTION_COLLECTION, verb, payload, write_cursor],
)
.map_err(|_| EngineError::Storage)?;
Ok(())
}
#[cfg(feature = "operator")]
fn digest_record_identity(collection: &str, record_key: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(collection.as_bytes());
hasher.update([0x1f_u8]);
hasher.update(record_key.as_bytes());
hasher.finalize().iter().map(|b| format!("{b:02x}")).collect()
}
fn projection_status(
connection: &Connection,
kind: &str,
) -> Result<lifecycle::ProjectionStatus, EngineError> {
let latest = connection
.query_row(
"SELECT COALESCE(MAX(write_cursor), 0) FROM canonical_nodes WHERE kind = ?1",
[kind],
|row| row.get::<_, u64>(0),
)
.map_err(|_| EngineError::Storage)?;
if latest == 0 {
return Ok(lifecycle::ProjectionStatus::UpToDate);
}
let pending: u64 = connection
.query_row(
"SELECT COUNT(*)
FROM canonical_nodes
LEFT JOIN _fathomdb_projection_terminal
ON _fathomdb_projection_terminal.write_cursor = canonical_nodes.write_cursor
WHERE canonical_nodes.kind = ?1
AND _fathomdb_projection_terminal.write_cursor IS NULL",
[kind],
|row| row.get(0),
)
.map_err(|_| EngineError::Storage)?;
if pending > 0 {
return Ok(lifecycle::ProjectionStatus::Pending);
}
match terminal_state_for_cursor(connection, latest).map_err(|_| EngineError::Storage)? {
Some(state) if state == "failed" => Ok(lifecycle::ProjectionStatus::Failed),
_ => Ok(lifecycle::ProjectionStatus::UpToDate),
}
}
fn canonical_database_path(path: &Path) -> Result<PathBuf, EngineOpenError> {
let parent = path
.parent()
.filter(|parent| !parent.as_os_str().is_empty())
.unwrap_or_else(|| Path::new("."));
let canonical_parent = parent.canonicalize().map_err(|_| EngineOpenError::Io {
message: "database parent directory is not accessible".to_string(),
})?;
let file_name = path.file_name().ok_or_else(|| EngineOpenError::Io {
message: "database path has no file name".to_string(),
})?;
Ok(canonical_parent.join(file_name))
}
fn acquire_lock(path: &Path) -> Result<File, EngineOpenError> {
let lock_path = lock_path(path);
let mut options = OpenOptions::new();
options.read(true).write(true).create(true);
#[cfg(unix)]
options.mode(0o600);
let mut file = options.open(&lock_path).map_err(|_| EngineOpenError::Io {
message: "could not open database lock file".to_string(),
})?;
match file.try_lock() {
Ok(()) => {
let pid = std::process::id().to_string();
let _ = file.set_len(0);
let _ = file.seek(SeekFrom::Start(0));
let _ = file.write_all(pid.as_bytes());
Ok(file)
}
Err(std::fs::TryLockError::WouldBlock) => {
Err(EngineOpenError::DatabaseLocked { holder_pid: read_holder_pid(&lock_path) })
}
Err(_) => {
Err(EngineOpenError::Io { message: "could not acquire database lock".to_string() })
}
}
}
fn lock_path(path: &Path) -> PathBuf {
let mut lock_path = path.as_os_str().to_os_string();
lock_path.push(LOCK_SUFFIX);
PathBuf::from(lock_path)
}
fn read_holder_pid(path: &Path) -> Option<u32> {
std::fs::read_to_string(path).ok()?.trim().parse().ok()
}
fn map_migration_error(err: SchemaMigrationError) -> EngineOpenError {
match err {
SchemaMigrationError::IncompatibleSchemaVersion { seen, supported } => {
EngineOpenError::IncompatibleSchemaVersion { seen, supported }
}
SchemaMigrationError::MigrationError(report) => EngineOpenError::MigrationError {
schema_version_before: report.schema_version_before,
schema_version_current: report.schema_version_current,
step_id: report.migration_steps.last().map_or(0, |step| step.step_id),
},
SchemaMigrationError::Storage { message } => {
EngineOpenError::Io { message: message.to_string() }
}
}
}
fn init_perf_experiments_runtime() {
static INIT: Once = Once::new();
INIT.call_once(|| {
if std::env::var_os("FATHOMDB_PERF_EXPERIMENTS").is_none() {
return;
}
let memstatus_off =
std::env::var_os("FATHOMDB_PERF_SQLITE_MEMSTATUS_OFF").is_some_and(|v| v == "1");
let pagecache = std::env::var("FATHOMDB_PERF_SQLITE_PAGECACHE").ok();
let pcache2_on =
std::env::var_os("FATHOMDB_PERF_SQLITE_PCACHE2").is_some_and(|v| v == "1");
if !memstatus_off && pagecache.is_none() && !pcache2_on {
return;
}
unsafe {
let rc_shutdown = rusqlite::ffi::sqlite3_shutdown();
let rc_memstatus = if memstatus_off {
rusqlite::ffi::sqlite3_config(rusqlite::ffi::SQLITE_CONFIG_MEMSTATUS, 0_i32)
} else {
-1
};
let rc_pagecache = if let Some(spec) = pagecache.as_ref() {
let mut parts = spec.split(':');
let sz = parts.next().and_then(|s| s.parse::<i32>().ok()).unwrap_or(0);
let n = parts.next().and_then(|s| s.parse::<i32>().ok()).unwrap_or(0);
if sz > 0 && n > 0 {
rusqlite::ffi::sqlite3_config(
7, std::ptr::null_mut::<std::ffi::c_void>(),
sz,
n,
)
} else {
eprintln!(
"perf-experiment: bad FATHOMDB_PERF_SQLITE_PAGECACHE spec '{spec}' (expect '<bytes>:<count>')"
);
-1
}
} else {
-1
};
let rc_pcache2 = if pcache2_on {
rusqlite::ffi::sqlite3_config(
rusqlite::ffi::SQLITE_CONFIG_PCACHE2,
&raw const pcache2::PCACHE2_METHODS.0,
)
} else {
-1
};
let rc_init = rusqlite::ffi::sqlite3_initialize();
eprintln!(
"perf-experiment: runtime-config rcs shutdown={rc_shutdown} \
memstatus={rc_memstatus} pagecache={rc_pagecache} pcache2={rc_pcache2} \
initialize={rc_init} (0=SQLITE_OK; 21=SQLITE_MISUSE; -1=not configured)"
);
}
});
}
fn register_sqlite_vec_extension() {
static REGISTER: Once = Once::new();
REGISTER.call_once(|| unsafe {
let entrypoint: unsafe extern "C" fn(
*mut rusqlite::ffi::sqlite3,
*mut *const std::os::raw::c_char,
*const rusqlite::ffi::sqlite3_api_routines,
) -> std::os::raw::c_int = std::mem::transmute(sqlite3_vec_init as *const ());
rusqlite::ffi::sqlite3_auto_extension(Some(entrypoint));
});
}
fn probe_open_integrity(connection: &Connection) -> Result<(), EngineOpenError> {
connection
.query_row("SELECT COUNT(*) FROM sqlite_schema", [], |row| row.get::<_, i64>(0))
.map(|_| ())
.map_err(|err| map_open_sqlite_error(err, OpenStage::SchemaProbe))
}
fn probe_database_header(connection: &Connection) -> Result<(), EngineOpenError> {
connection
.query_row("PRAGMA application_id", [], |row| row.get::<_, i64>(0))
.map(|_| ())
.map_err(|err| map_open_sqlite_error(err, OpenStage::HeaderProbe))
}
fn probe_wal_sidecar(db_path: &Path) -> Result<(), EngineOpenError> {
let mut wal_path = db_path.as_os_str().to_owned();
wal_path.push("-wal");
let wal_path = PathBuf::from(wal_path);
use std::io::Read;
let mut file = match std::fs::File::open(&wal_path) {
Ok(file) => file,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(()),
Err(_) => return Ok(()),
};
let mut bytes = [0u8; 32];
if file.read_exact(&mut bytes).is_err() {
return Ok(());
}
let magic = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
let page_size = u32::from_be_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
const WAL_MAGIC_MASK: u32 = 0xFFFF_FFFE;
const WAL_MAGIC: u32 = 0x377F_0682;
const SQLITE_MAX_PAGE_SIZE: u32 = 65536;
let magic_ok = (magic & WAL_MAGIC_MASK) == WAL_MAGIC;
let page_size_ok =
page_size.is_power_of_two() && (512..=SQLITE_MAX_PAGE_SIZE).contains(&page_size);
if magic_ok && page_size_ok {
return Ok(());
}
Err(EngineOpenError::Corruption(CorruptionDetail {
kind: CorruptionKind::WalReplayFailure,
stage: OpenStage::WalReplay,
locator: CorruptionLocator::FileOffset { offset: if !magic_ok { 0 } else { 8 } },
recovery_hint: RecoveryHint {
code: "E_CORRUPT_WAL_REPLAY",
doc_anchor: "design/recovery.md#wal-replay-failures",
},
}))
}
fn reject_legacy_shape(connection: &Connection) -> Result<(), EngineOpenError> {
let has_legacy_table = table_exists(connection, "fathom_nodes")
|| table_exists(connection, "fathom_edges")
|| table_exists(connection, "fathom_chunks");
if !has_legacy_table {
return Ok(());
}
let seen =
connection.query_row("PRAGMA user_version", [], |row| row.get::<_, u32>(0)).unwrap_or(0);
Err(EngineOpenError::IncompatibleSchemaVersion { seen, supported: SCHEMA_VERSION })
}
fn table_exists(connection: &Connection, table: &str) -> bool {
connection
.query_row(
"SELECT 1 FROM sqlite_schema WHERE type = 'table' AND name = ?1",
[table],
|_row| Ok(()),
)
.is_ok()
}
#[cfg(feature = "operator")]
fn read_schema_objects(
connection: &Connection,
obj_type: &str,
) -> Result<Vec<SchemaObject>, EngineError> {
let mut stmt = connection
.prepare(
"SELECT name, sql FROM sqlite_schema
WHERE type = ?1 AND name NOT LIKE 'sqlite_%' AND sql IS NOT NULL
ORDER BY name",
)
.map_err(|_| EngineError::Storage)?;
let rows = stmt
.query_map([obj_type], |row| {
Ok(SchemaObject { name: row.get::<_, String>(0)?, sql: row.get::<_, String>(1)? })
})
.map_err(|_| EngineError::Storage)?;
let mut out = Vec::new();
for row in rows {
out.push(row.map_err(|_| EngineError::Storage)?);
}
Ok(out)
}
#[cfg(feature = "operator")]
fn order_canonical_first(mut objects: Vec<SchemaObject>) -> Vec<SchemaObject> {
let mut canonical: Vec<SchemaObject> = Vec::new();
for name in CANONICAL_TABLES {
if let Some(pos) = objects.iter().position(|o| o.name == *name) {
canonical.push(objects.remove(pos));
}
}
canonical.extend(objects);
canonical
}
fn load_default_profile(connection: &Connection) -> rusqlite::Result<EmbedderIdentity> {
connection.query_row(
"SELECT name, revision, dimension FROM _fathomdb_embedder_profiles WHERE profile = ?1",
[DEFAULT_VECTOR_PROFILE],
|row| {
Ok(EmbedderIdentity::new(
row.get::<_, String>(0)?,
row.get::<_, String>(1)?,
row.get::<_, u32>(2)?,
))
},
)
}
fn default_profile_dimension(connection: &Connection) -> Result<u32, EngineError> {
load_default_profile(connection)
.map(|identity| identity.dimension)
.map_err(|_| EngineError::Storage)
}
fn kind_is_vector_indexed(connection: &Connection, kind: &str) -> Result<bool, EngineError> {
connection
.query_row("SELECT 1 FROM _fathomdb_vector_kinds WHERE kind = ?1", [kind], |_row| Ok(()))
.map(|_| true)
.or_else(|err| match err {
rusqlite::Error::QueryReturnedNoRows => Ok(false),
_ => Err(EngineError::Storage),
})
}
fn ensure_vector_partition(connection: &mut Connection, dimension: u32) -> rusqlite::Result<()> {
let existing_sql: Option<String> = connection
.query_row(
"SELECT sql FROM sqlite_master WHERE type='table' AND name=?1",
[DEFAULT_VECTOR_PARTITION],
|row| row.get::<_, String>(0),
)
.optional()?;
match existing_sql {
None => create_vector_partition(connection, dimension),
Some(sql) if sql.contains("status") => Ok(()),
Some(sql) if sql.contains("embedding_bin") => {
migrate_vector_partition_pack1_to_pack2(connection, dimension)
}
Some(_) => migrate_vector_partition_to_pack1(connection, dimension),
}
}
fn vector_partition_create_sql(
dimension: u32,
if_not_exists: bool,
attr_cols: &[String],
) -> String {
let guard = if if_not_exists { "IF NOT EXISTS " } else { "" };
let mut attrs = String::new();
for col in attr_cols {
attrs.push_str(&format!(",{col} TEXT"));
}
format!(
"CREATE VIRTUAL TABLE {guard}{DEFAULT_VECTOR_PARTITION} USING vec0(\
embedding float[{dimension}],\
embedding_bin bit[{dimension}],\
source_type TEXT partition key,\
kind TEXT,\
created_at INTEGER,\
status TEXT{attrs}\
)"
)
}
fn create_vector_partition(connection: &Connection, dimension: u32) -> rusqlite::Result<()> {
connection.execute_batch(&vector_partition_create_sql(dimension, true, &[]))
}
fn attr_vec0_column(name: &str) -> String {
let mut s = String::from("attr_");
for b in name.as_bytes() {
s.push_str(&format!("{b:02x}"));
}
s
}
fn decode_attr_vec0_column(col: &str) -> Option<String> {
let hex = col.strip_prefix("attr_")?;
if hex.is_empty() || hex.len() % 2 != 0 || !hex.bytes().all(|b| b.is_ascii_hexdigit()) {
return None;
}
let mut bytes = Vec::with_capacity(hex.len() / 2);
let raw = hex.as_bytes();
let mut i = 0;
while i < raw.len() {
let hi = (raw[i] as char).to_digit(16)?;
let lo = (raw[i + 1] as char).to_digit(16)?;
bytes.push((hi * 16 + lo) as u8);
i += 2;
}
String::from_utf8(bytes).ok()
}
fn desired_vector_attr_columns(conn: &Connection) -> rusqlite::Result<Vec<String>> {
let registry = load_projection_registry(conn)?;
let mut cols: Vec<String> = registry
.iter()
.filter(|(_, stored)| stored.roles.contains(&ProjectionRole::Filterable))
.map(|(name, _)| attr_vec0_column(name))
.collect();
cols.sort();
Ok(cols)
}
fn actual_vector_attr_columns(conn: &Connection) -> rusqlite::Result<Vec<String>> {
let sql: Option<String> = conn
.query_row(
"SELECT sql FROM sqlite_master WHERE type='table' AND name=?1",
[DEFAULT_VECTOR_PARTITION],
|row| row.get::<_, String>(0),
)
.optional()?;
let Some(sql) = sql else {
return Ok(Vec::new());
};
let mut cols: Vec<String> = Vec::new();
let mut token = String::new();
let flush = |token: &mut String, cols: &mut Vec<String>| {
if !token.is_empty() {
if decode_attr_vec0_column(token).is_some() && !cols.contains(token) {
cols.push(token.clone());
}
token.clear();
}
};
for ch in sql.chars() {
if ch.is_ascii_alphanumeric() || ch == '_' {
token.push(ch);
} else {
flush(&mut token, &mut cols);
}
}
flush(&mut token, &mut cols);
cols.sort();
Ok(cols)
}
fn delete_vector_partition_row(conn: &Connection, rowid: i64) -> rusqlite::Result<usize> {
neutralize_vector_partition_attr_values(conn, Some(rowid))?;
conn.execute(&format!("DELETE FROM {DEFAULT_VECTOR_PARTITION} WHERE rowid = ?1"), [rowid])
}
fn neutralize_vector_partition_attr_values(
conn: &Connection,
rowid: Option<i64>,
) -> rusqlite::Result<()> {
let attr_cols = actual_vector_attr_columns(conn)?;
if attr_cols.is_empty() {
return Ok(());
}
let sets = attr_cols.iter().map(|c| format!("{c}=''")).collect::<Vec<_>>().join(", ");
match rowid {
Some(rowid) => {
conn.execute(
&format!("UPDATE {DEFAULT_VECTOR_PARTITION} SET {sets} WHERE rowid = ?1"),
[rowid],
)?;
}
None => {
conn.execute(&format!("UPDATE {DEFAULT_VECTOR_PARTITION} SET {sets}"), [])?;
}
}
Ok(())
}
fn reconcile_vector_attr_columns(conn: &Connection, dimension: u32) -> rusqlite::Result<bool> {
let table_exists: bool = conn
.query_row(
"SELECT 1 FROM sqlite_master WHERE type='table' AND name=?1",
[DEFAULT_VECTOR_PARTITION],
|_| Ok(true),
)
.optional()?
.unwrap_or(false);
if !table_exists {
return Ok(false);
}
let desired = desired_vector_attr_columns(conn)?;
let actual = actual_vector_attr_columns(conn)?;
if desired == actual {
return Ok(false);
}
reshape_vector_partition_nondestructive(conn, dimension, &desired, &actual)?;
Ok(true)
}
fn reshape_vector_partition_nondestructive(
conn: &Connection,
dimension: u32,
desired_cols: &[String],
actual_cols: &[String],
) -> rusqlite::Result<()> {
let mut stage_defs = String::new();
let mut stage_names =
String::from("rowid, embedding, embedding_bin, source_type, kind, created_at, status");
for col in actual_cols {
stage_defs.push_str(&format!(",\n {col} TEXT"));
stage_names.push_str(&format!(", {col}"));
}
conn.execute_batch(&format!(
"CREATE TABLE _fathomdb_vector_reshape_stage (
rowid INTEGER PRIMARY KEY,
embedding BLOB NOT NULL,
embedding_bin BLOB NOT NULL,
source_type TEXT,
kind TEXT,
created_at INTEGER,
status TEXT{stage_defs}
);
INSERT INTO _fathomdb_vector_reshape_stage({stage_names})
SELECT {stage_names} FROM {DEFAULT_VECTOR_PARTITION};
DROP TABLE {DEFAULT_VECTOR_PARTITION};"
))?;
conn.execute_batch(&vector_partition_create_sql(dimension, false, desired_cols))?;
let mut insert_cols =
String::from("rowid, embedding, embedding_bin, source_type, kind, created_at, status");
let mut select_exprs = String::from(
"rowid, embedding, vec_bit(embedding_bin), source_type, kind, created_at, status",
);
for col in desired_cols {
insert_cols.push_str(&format!(", {col}"));
if actual_cols.iter().any(|a| a == col) {
select_exprs.push_str(&format!(", COALESCE({col}, '')"));
} else {
match decode_attr_vec0_column(col) {
Some(name) => {
let escaped = name.replace('\'', "''");
select_exprs.push_str(&format!(
", COALESCE((SELECT char(1) || ca.attr_value FROM canonical_attributes ca \
WHERE ca.write_cursor = _fathomdb_vector_reshape_stage.rowid \
AND ca.attr_name = '{escaped}' LIMIT 1), '')"
));
}
None => select_exprs.push_str(", ''"),
}
}
}
conn.execute_batch(&format!(
"INSERT INTO {DEFAULT_VECTOR_PARTITION}({insert_cols})
SELECT {select_exprs} FROM _fathomdb_vector_reshape_stage;
DROP TABLE _fathomdb_vector_reshape_stage;"
))?;
Ok(())
}
fn migrate_vector_partition_pack1_to_pack2(
connection: &mut Connection,
dimension: u32,
) -> rusqlite::Result<()> {
let tx = connection.transaction()?;
tx.execute_batch(
"CREATE TABLE _fathomdb_vector_pack2_stage (
rowid INTEGER PRIMARY KEY,
embedding BLOB NOT NULL,
embedding_bin BLOB NOT NULL,
source_type TEXT,
kind TEXT,
created_at INTEGER
);
INSERT INTO _fathomdb_vector_pack2_stage(
rowid, embedding, embedding_bin, source_type, kind, created_at
)
SELECT rowid, embedding, embedding_bin, source_type, kind, created_at
FROM vector_default;
DROP TABLE vector_default;",
)?;
tx.execute_batch(&vector_partition_create_sql(dimension, false, &[]))?;
tx.execute_batch(
"INSERT INTO vector_default(
rowid, embedding, embedding_bin, source_type, kind, created_at, status
)
SELECT rowid, embedding, vec_bit(embedding_bin), source_type, kind, created_at, ''
FROM _fathomdb_vector_pack2_stage;
DROP TABLE _fathomdb_vector_pack2_stage;",
)?;
tx.commit()
}
const KIND_TO_SOURCE_TYPE_CASE_SQL: &str = "CASE s.kind
WHEN 'email' THEN 'email'
WHEN 'article' THEN 'article'
WHEN 'paper' THEN 'paper'
WHEN 'meeting' THEN 'meeting'
WHEN 'note' THEN 'note'
WHEN 'todo' THEN 'todo'
WHEN 'doc' THEN 'article'
ELSE 'article'
END";
fn migrate_vector_partition_to_pack1(
connection: &mut Connection,
dimension: u32,
) -> rusqlite::Result<()> {
let tx = connection.transaction()?;
tx.execute_batch(
"CREATE TABLE _fathomdb_vector_migration_v0_7_0 (
rowid INTEGER PRIMARY KEY,
embedding BLOB NOT NULL,
kind TEXT NOT NULL
);
INSERT INTO _fathomdb_vector_migration_v0_7_0(rowid, embedding, kind)
SELECT v.rowid, v.embedding, r.kind
FROM vector_default v
JOIN _fathomdb_vector_rows r ON r.rowid = v.rowid;
DROP TABLE vector_default;",
)?;
tx.execute_batch(&vector_partition_create_sql(dimension, false, &[]))?;
let repopulate_sql = format!(
"INSERT INTO vector_default(
rowid, embedding, embedding_bin, source_type, kind, created_at, status
)
SELECT
s.rowid,
s.embedding,
vec_quantize_binary(s.embedding),
{KIND_TO_SOURCE_TYPE_CASE_SQL},
s.kind,
strftime('%s', 'now'),
''
FROM _fathomdb_vector_migration_v0_7_0 s;
DROP TABLE _fathomdb_vector_migration_v0_7_0;"
);
tx.execute_batch(&repopulate_sql)?;
tx.commit()
}
fn encode_vector_blob(vector: &[f32]) -> Vec<u8> {
vector.iter().flat_map(|value| value.to_le_bytes()).collect()
}
fn decode_vector_blob(bytes: &[u8]) -> Vec<f32> {
debug_assert_eq!(bytes.len() % 4, 0, "f32 BLOB length must be multiple of 4");
bytes.chunks_exact(4).map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]])).collect()
}
fn identity_requires_mean_centering(identity: &EmbedderIdentity) -> bool {
identity.name == BGE_SMALL_EMBEDDER_NAME
}
fn read_pinned_mean_vec(
connection: &Connection,
dimension: u32,
) -> Result<Option<Vec<f32>>, EngineError> {
let bytes: Option<Vec<u8>> = connection
.query_row(
"SELECT mean_vec FROM _fathomdb_embedder_profiles WHERE profile = 'default'",
[],
|row| row.get::<_, Option<Vec<u8>>>(0),
)
.or_else(|err| match err {
rusqlite::Error::QueryReturnedNoRows => Ok(None),
other => Err(other),
})
.map_err(|_| EngineError::Storage)?;
let Some(bytes) = bytes else { return Ok(None) };
let expected_len = (dimension as usize).saturating_mul(4);
if bytes.len() != expected_len {
return Err(EngineError::Storage);
}
let mut out = Vec::with_capacity(dimension as usize);
for chunk in bytes.chunks_exact(4) {
let arr = [chunk[0], chunk[1], chunk[2], chunk[3]];
out.push(f32::from_le_bytes(arr));
}
Ok(Some(out))
}
fn subtract_mean(v: &[f32], mean: &[f32]) -> Vec<f32> {
debug_assert_eq!(v.len(), mean.len(), "subtract_mean dim mismatch");
v.iter().zip(mean.iter()).map(|(a, b)| *a - *b).collect()
}
fn vector_equivalence_probes() -> Vec<&'static str> {
VECTOR_EQUIVALENCE_PROBE_FIXTURE
.lines()
.map(str::trim_end)
.filter(|line| {
let t = line.trim_start();
!t.is_empty() && !t.starts_with('#')
})
.collect()
}
struct VectorEquivalenceOutcome {
dense_disabled: bool,
reason: Option<String>,
}
fn probe_embed(embedder: &dyn Embedder, text: &str, dimension: usize) -> Option<Vec<f32>> {
let embedded = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| embedder.embed(text)));
match embedded {
Ok(Ok(vector)) if vector.len() == dimension => Some(vector),
_ => None,
}
}
fn run_vector_equivalence_probe(
connection: &Connection,
embedder: Option<&dyn Embedder>,
identity: &EmbedderIdentity,
mean_pinned: bool,
) -> VectorEquivalenceOutcome {
let not_disabled = VectorEquivalenceOutcome { dense_disabled: false, reason: None };
let Some(embedder) = embedder else { return not_disabled };
let vector_kind_registered: bool = connection
.query_row("SELECT EXISTS(SELECT 1 FROM _fathomdb_vector_kinds)", [], |r| r.get(0))
.unwrap_or(false);
if !vector_kind_registered {
return not_disabled;
}
match probe_populate_or_check(connection, embedder, identity, mean_pinned) {
Ok(()) => not_disabled,
Err(reason) => VectorEquivalenceOutcome { dense_disabled: true, reason: Some(reason) },
}
}
fn probe_populate_or_check(
connection: &Connection,
embedder: &dyn Embedder,
identity: &EmbedderIdentity,
mean_pinned: bool,
) -> Result<(), String> {
let probes = vector_equivalence_probes();
if probes.is_empty() {
return Err(
"vector-equivalence probe fixture is empty; cannot verify the dense arm".to_string()
);
}
let existing: i64 = connection
.query_row("SELECT COUNT(*) FROM _fathomdb_embed_probe", [], |r| r.get(0))
.map_err(|e| format!("could not read the probe reference table: {e}; cannot verify"))?;
if existing == 0 {
probe_populate_baseline(connection, embedder, identity, &probes)?;
probe_check_against_baseline(connection, embedder, identity, mean_pinned, &probes)
} else {
probe_check_against_baseline(connection, embedder, identity, mean_pinned, &probes)
}
}
fn probe_populate_baseline(
connection: &Connection,
embedder: &dyn Embedder,
identity: &EmbedderIdentity,
probes: &[&str],
) -> Result<(), String> {
let dimension = identity.dimension as usize;
let mut rows: Vec<(i64, &str, Vec<f32>)> = Vec::with_capacity(probes.len());
for (ordinal, probe) in probes.iter().enumerate() {
match probe_embed(embedder, probe, dimension) {
Some(vec) => rows.push((ordinal as i64, probe, vec)),
None => {
return Err(format!(
"embedder failed to produce a reference vector for probe {ordinal}; \
cannot establish a vector-equivalence baseline (dense arm refused)"
));
}
}
}
let tx = connection
.unchecked_transaction()
.map_err(|e| format!("could not open the probe-baseline transaction: {e}"))?;
for (ordinal, probe, vec) in &rows {
let blob = encode_vector_blob(vec);
tx.execute(
"INSERT OR REPLACE INTO _fathomdb_embed_probe(
probe_ordinal, probe_text, reference_vec,
embedder_name, embedder_revision, dim
) VALUES(?1, ?2, ?3, ?4, ?5, ?6)",
params![ordinal, probe, blob, identity.name, identity.revision, identity.dimension],
)
.map_err(|e| format!("could not persist the probe baseline: {e}"))?;
}
tx.commit().map_err(|e| format!("could not commit the probe baseline: {e}"))?;
Ok(())
}
type StoredProbeRow = (i64, String, Vec<u8>, String, String, i64);
fn hash_fingerprint_field(hasher: &mut Sha256, bytes: &[u8]) {
hasher.update((bytes.len() as u64).to_le_bytes());
hasher.update(bytes);
}
fn probe_verification_fingerprint(
identity: &EmbedderIdentity,
mean_vec: Option<&[f32]>,
stored: &[StoredProbeRow],
) -> String {
let mut hasher = Sha256::new();
hash_fingerprint_field(&mut hasher, VECTOR_EQUIVALENCE_FINGERPRINT_RECIPE.as_bytes());
hash_fingerprint_field(&mut hasher, identity.name.as_bytes());
hash_fingerprint_field(&mut hasher, identity.revision.as_bytes());
hash_fingerprint_field(&mut hasher, &identity.dimension.to_le_bytes());
match mean_vec {
Some(mean) => {
hash_fingerprint_field(&mut hasher, b"mean-centered");
hash_fingerprint_field(&mut hasher, &encode_vector_blob(mean));
}
None => hash_fingerprint_field(&mut hasher, b"un-centered"),
}
hash_fingerprint_field(&mut hasher, VECTOR_EQUIVALENCE_PROBE_FIXTURE.as_bytes());
hash_fingerprint_field(&mut hasher, &VECTOR_EQUIVALENCE_P1_FLIP_FLOOR.to_le_bytes());
hash_fingerprint_field(&mut hasher, &VECTOR_EQUIVALENCE_L2_EPSILON.to_le_bytes());
hash_fingerprint_field(&mut hasher, &(stored.len() as u64).to_le_bytes());
for (ordinal, probe_text, reference_vec, name, revision, dim) in stored {
hash_fingerprint_field(&mut hasher, &ordinal.to_le_bytes());
hash_fingerprint_field(&mut hasher, probe_text.as_bytes());
hash_fingerprint_field(&mut hasher, reference_vec);
hash_fingerprint_field(&mut hasher, name.as_bytes());
hash_fingerprint_field(&mut hasher, revision.as_bytes());
hash_fingerprint_field(&mut hasher, &dim.to_le_bytes());
}
hasher.finalize().iter().map(|b| format!("{b:02x}")).collect()
}
fn probe_verification_is_cached(connection: &Connection, fingerprint: &str) -> bool {
connection
.query_row(
"SELECT value FROM _fathomdb_open_state WHERE key = ?1",
[VECTOR_EQUIVALENCE_VERDICT_CACHE_KEY],
|row| row.get::<_, String>(0),
)
.map(|cached| cached == fingerprint)
.unwrap_or(false)
}
fn record_probe_verification(connection: &Connection, fingerprint: &str) {
let _ = connection.execute(
"INSERT INTO _fathomdb_open_state(key, value) VALUES(?1, ?2)
ON CONFLICT(key) DO UPDATE SET value = excluded.value",
params![VECTOR_EQUIVALENCE_VERDICT_CACHE_KEY, fingerprint],
);
}
fn clear_probe_verification(connection: &Connection) {
let _ = connection.execute(
"DELETE FROM _fathomdb_open_state WHERE key = ?1",
[VECTOR_EQUIVALENCE_VERDICT_CACHE_KEY],
);
}
fn probe_check_against_baseline(
connection: &Connection,
embedder: &dyn Embedder,
identity: &EmbedderIdentity,
mean_pinned: bool,
probes: &[&str],
) -> Result<(), String> {
let outcome =
probe_check_against_baseline_inner(connection, embedder, identity, mean_pinned, probes);
if outcome.is_err() {
clear_probe_verification(connection);
}
outcome
}
fn probe_check_against_baseline_inner(
connection: &Connection,
embedder: &dyn Embedder,
identity: &EmbedderIdentity,
mean_pinned: bool,
probes: &[&str],
) -> Result<(), String> {
let dimension = identity.dimension as usize;
let mean_vec = if identity_requires_mean_centering(identity) && mean_pinned {
match read_pinned_mean_vec(connection, identity.dimension) {
Ok(Some(mean)) => Some(mean),
Ok(None) => {
return Err("mean-centering is required and pinned but mean_vec is absent; \
cannot verify P1 (dense arm refused)"
.to_string());
}
Err(_) => {
return Err(
"could not read the pinned mean_vec; cannot verify P1 (dense arm refused)"
.to_string(),
);
}
}
} else {
None
};
let mut stmt = connection
.prepare(
"SELECT probe_ordinal, probe_text, reference_vec, embedder_name, embedder_revision, dim \
FROM _fathomdb_embed_probe ORDER BY probe_ordinal",
)
.map_err(|e| format!("could not read the stored probe references: {e}; cannot verify"))?;
let stored: Vec<StoredProbeRow> = stmt
.query_map([], |row| {
Ok((
row.get::<_, i64>(0)?,
row.get::<_, String>(1)?,
row.get::<_, Vec<u8>>(2)?,
row.get::<_, String>(3)?,
row.get::<_, String>(4)?,
row.get::<_, i64>(5)?,
))
})
.and_then(|rows| rows.collect::<rusqlite::Result<Vec<_>>>())
.map_err(|e| format!("could not read the stored probe references: {e}; cannot verify"))?;
if stored.len() != probes.len() {
return Err(format!(
"the probe reference table has {} rows but the committed fixture defines {}; \
the stored baseline is incomplete or corrupt — cannot verify the dense arm (refused)",
stored.len(),
probes.len()
));
}
for (idx, (ordinal, probe_text, ref_blob, name, revision, dim)) in stored.iter().enumerate() {
if *ordinal != idx as i64 {
return Err(format!(
"probe reference ordinals are non-contiguous (row {idx} carries ordinal {ordinal}); \
the stored baseline is corrupt — cannot verify the dense arm (refused)"
));
}
if probe_text != probes[idx] {
return Err(format!(
"probe reference {ordinal} text does not match the committed fixture; \
the stored baseline is tampered or corrupt — cannot verify the dense arm (refused)"
));
}
if ref_blob.len() != dimension * 4 {
return Err(format!(
"probe reference {ordinal} is malformed (len {} != {}); \
cannot verify the dense arm (refused)",
ref_blob.len(),
dimension * 4
));
}
if *dim != identity.dimension as i64
|| name != &identity.name
|| revision != &identity.revision
{
return Err(format!(
"probe reference {ordinal} was captured under embedder {name}/{revision}/dim={dim} \
but the current embedder is {}/{}/dim={}; the stored baseline does not match — \
cannot verify the dense arm (refused)",
identity.name, identity.revision, identity.dimension
));
}
}
let fingerprint = probe_verification_fingerprint(identity, mean_vec.as_deref(), &stored);
if probe_verification_is_cached(connection, &fingerprint) {
return Ok(());
}
let mut total_flips: u64 = 0;
let mut max_l2: f32 = 0.0;
let mut worst_probe: Option<String> = None;
for (ordinal, probe_text, ref_blob, _, _, _) in &stored {
let reference = decode_vector_blob(ref_blob);
let reembed = probe_embed(embedder, probe_text, dimension).ok_or_else(|| {
format!(
"embedder failed/panicked re-embedding probe {ordinal}; \
cannot verify the dense arm (refused)"
)
})?;
let l2 = l2_distance(&reembed, &reference);
if l2 > max_l2 {
max_l2 = l2;
worst_probe = Some(probe_text.clone());
}
let (ref_c, reembed_c) = match &mean_vec {
Some(mean) => (subtract_mean(&reference, mean), subtract_mean(&reembed, mean)),
None => (reference.clone(), reembed.clone()),
};
let ref_bits = quantize_binary_via_sql(connection, &ref_c).ok_or_else(|| {
format!("vec_quantize_binary SQL failed for probe {ordinal}; cannot verify P1")
})?;
let reembed_bits = quantize_binary_via_sql(connection, &reembed_c).ok_or_else(|| {
format!("vec_quantize_binary SQL failed for probe {ordinal}; cannot verify P1")
})?;
total_flips = total_flips.saturating_add(hamming_bytes(&ref_bits, &reembed_bits));
}
let p1_tripped = total_flips > VECTOR_EQUIVALENCE_P1_FLIP_FLOOR;
let p2_tripped = max_l2 > VECTOR_EQUIVALENCE_L2_EPSILON;
if p1_tripped || p2_tripped {
let probe_hint = worst_probe.as_deref().unwrap_or("<unknown>");
return Err(format!(
"P1 mean-centered embedding_bin flips={total_flips} (floor={VECTOR_EQUIVALENCE_P1_FLIP_FLOOR}), \
P2 max un-centered L2={max_l2:.3e} (epsilon={VECTOR_EQUIVALENCE_L2_EPSILON:.3e}); \
worst probe {probe_hint:?}"
));
}
record_probe_verification(connection, &fingerprint);
Ok(())
}
fn l2_distance(a: &[f32], b: &[f32]) -> f32 {
a.iter().zip(b.iter()).map(|(x, y)| (x - y) * (x - y)).sum::<f32>().sqrt()
}
fn quantize_binary_via_sql(connection: &Connection, vector: &[f32]) -> Option<Vec<u8>> {
let json = serde_json::to_string(vector).ok()?;
connection
.query_row("SELECT vec_quantize_binary(vec_f32(?1))", [json], |row| {
row.get::<_, Vec<u8>>(0)
})
.ok()
}
fn hamming_bytes(a: &[u8], b: &[u8]) -> u64 {
let common = a.len().min(b.len());
let mut flips: u64 = 0;
for i in 0..common {
flips += u64::from((a[i] ^ b[i]).count_ones());
}
let extra = a.len().abs_diff(b.len());
flips + (extra as u64) * 8
}
fn resolve_source_type(kind: &str) -> Result<&'static str, EngineError> {
Ok(match kind {
"email" => "email",
"article" => "article",
"paper" => "paper",
"meeting" => "meeting",
"note" => "note",
"todo" => "todo",
"doc" => "article",
"edge_fact" => "edge_fact",
_ => return Err(EngineError::Storage),
})
}
fn kind_is_vector_committable(kind: &str) -> bool {
resolve_source_type(kind).is_ok()
}
fn derive_logical_id(kind: &str, name: &str) -> Result<String, EngineError> {
if kind.contains(':') || name.is_empty() {
return Err(EngineError::Extractor);
}
let input = format!("{}:{}", kind.to_lowercase(), name.to_lowercase());
let mut hasher = Sha256::new();
hasher.update(input.as_bytes());
Ok(hasher.finalize().iter().map(|b| format!("{b:02x}")).collect())
}
fn derive_stable_id(logical_id: Option<&str>, body: &str) -> IdSpace {
match logical_id {
Some(lid) if !lid.is_empty() => IdSpace::logical(lid),
_ => {
let mut hasher = Sha256::new();
hasher.update(body.as_bytes());
IdSpace::content(
hasher.finalize().iter().map(|b| format!("{b:02x}")).collect::<String>(),
)
}
}
}
fn dedup_prepared_by_logical_id(batch: Vec<PreparedWrite>) -> Vec<PreparedWrite> {
let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
batch
.into_iter()
.filter(|w| match w {
PreparedWrite::Node { logical_id: Some(id), .. }
| PreparedWrite::Edge { logical_id: Some(id), .. } => seen.insert(id.clone()),
_ => true,
})
.collect()
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ProviderTask {
Extract,
Consolidate,
}
impl ProviderTask {
fn name(self) -> &'static str {
match self {
ProviderTask::Extract => "extract",
ProviderTask::Consolidate => "consolidate",
}
}
fn protocol(self) -> &'static str {
match self {
ProviderTask::Extract => "fathomdb.extract.v1",
ProviderTask::Consolidate => "fathomdb.consolidate.v1",
}
}
}
struct ProviderSession {
task: ProviderTask,
child: std::process::Child,
writer: std::io::BufWriter<std::process::ChildStdin>,
line_rx: Receiver<std::io::Result<String>>,
io_timeout: Duration,
model: Option<String>,
max_docs_per_request: usize,
}
impl Drop for ProviderSession {
fn drop(&mut self) {
let _ = self.child.kill();
let _ = self.child.wait();
}
}
impl ProviderSession {
fn handshake(&mut self) -> Result<(), EngineError> {
let protocol = self.task.protocol();
let hello = serde_json::json!({
"protocol": protocol,
"type": "hello",
"schema_version": 1,
});
let hello_line = serde_json::to_string(&hello).map_err(|_| EngineError::Extractor)?;
writeln!(self.writer, "{hello_line}").map_err(|_| EngineError::Extractor)?;
self.writer.flush().map_err(|_| EngineError::Extractor)?;
let line = recv_extractor_line(&self.line_rx, self.io_timeout)?;
let ready: Value = serde_json::from_str(line.trim()).map_err(|_| EngineError::Extractor)?;
if ready.get("type").and_then(|v| v.as_str()) != Some("ready")
|| ready.get("protocol").and_then(|v| v.as_str()) != Some(protocol)
|| ready.get("schema_version").and_then(|v| v.as_u64()) != Some(1)
{
return Err(EngineError::Extractor);
}
if let Some(supported) = ready.get("supported_tasks").and_then(|v| v.as_array()) {
let task_name = self.task.name();
let advertised = supported.iter().any(|t| t.as_str() == Some(task_name));
if !advertised {
return Err(EngineError::Extractor);
}
}
self.model = ready.get("model").and_then(|v| v.as_str()).map(|s| s.to_string());
let max_docs =
ready.get("max_docs_per_request").and_then(|v| v.as_u64()).unwrap_or(8) as usize;
if max_docs == 0 {
return Err(EngineError::Extractor);
}
self.max_docs_per_request = max_docs;
Ok(())
}
fn request(
&mut self,
request_id: &str,
payload: Vec<(String, Value)>,
) -> Result<Value, EngineError> {
let mut req = serde_json::Map::new();
req.insert("protocol".to_string(), Value::from(self.task.protocol()));
req.insert("type".to_string(), Value::from(self.task.name()));
req.insert("request_id".to_string(), Value::from(request_id));
for (k, v) in payload {
req.insert(k, v);
}
let req_line =
serde_json::to_string(&Value::Object(req)).map_err(|_| EngineError::Extractor)?;
writeln!(self.writer, "{req_line}").map_err(|_| EngineError::Extractor)?;
self.writer.flush().map_err(|_| EngineError::Extractor)?;
let result_line = recv_extractor_line(&self.line_rx, self.io_timeout)?;
let result: Value =
serde_json::from_str(result_line.trim()).map_err(|_| EngineError::Extractor)?;
let resp_type = result.get("type").and_then(|v| v.as_str());
let resp_id = result.get("request_id").and_then(|v| v.as_str());
if resp_type != Some("result") || resp_id != Some(request_id) {
return Err(EngineError::Extractor);
}
Ok(result)
}
}
fn extractor_io_timeout() -> Duration {
std::env::var("FATHOMDB_EXTRACTOR_TIMEOUT_MS")
.ok()
.and_then(|s| s.parse::<u64>().ok())
.map(Duration::from_millis)
.unwrap_or_else(|| Duration::from_secs(300))
}
fn recv_extractor_line(
rx: &Receiver<std::io::Result<String>>,
timeout: Duration,
) -> Result<String, EngineError> {
match rx.recv_timeout(timeout) {
Ok(Ok(line)) => Ok(line),
_ => Err(EngineError::Extractor),
}
}
fn map_runtime_embedder_error(err: RuntimeEmbedderError) -> EngineError {
match err {
RuntimeEmbedderError::Failed { .. } | RuntimeEmbedderError::Timeout => {
EngineError::Embedder
}
}
}
fn default_embedder_identity() -> EmbedderIdentity {
EmbedderIdentity::new(
DEFAULT_EMBEDDER_NAME,
DEFAULT_EMBEDDER_REVISION,
DEFAULT_EMBEDDER_DIMENSION,
)
}
fn check_embedder_profile(
connection: &Connection,
supplied: &EmbedderIdentity,
) -> Result<bool, EngineOpenError> {
let mut statement = match connection.prepare(
"SELECT name, revision, dimension, mean_vec FROM _fathomdb_embedder_profiles WHERE profile = 'default'",
) {
Ok(statement) => statement,
Err(_) => return Ok(false),
};
let mut rows = statement.query([]).map_err(|_| {
EngineOpenError::Corruption(CorruptionDetail {
kind: CorruptionKind::EmbedderIdentityDrift,
stage: OpenStage::EmbedderIdentity,
locator: CorruptionLocator::OpaqueSqliteError { sqlite_extended_code: 0 },
recovery_hint: RecoveryHint {
code: "E_CORRUPT_EMBEDDER_IDENTITY",
doc_anchor: "design/recovery.md#embedder-identity-drift",
},
})
})?;
let Some(row) = rows.next().map_err(|_| {
EngineOpenError::Corruption(CorruptionDetail {
kind: CorruptionKind::EmbedderIdentityDrift,
stage: OpenStage::EmbedderIdentity,
locator: CorruptionLocator::OpaqueSqliteError { sqlite_extended_code: 0 },
recovery_hint: RecoveryHint {
code: "E_CORRUPT_EMBEDDER_IDENTITY",
doc_anchor: "design/recovery.md#embedder-identity-drift",
},
})
})?
else {
connection
.execute(
"INSERT INTO _fathomdb_embedder_profiles(profile, name, revision, dimension)
VALUES(?1, ?2, ?3, ?4)",
params![
DEFAULT_VECTOR_PROFILE,
supplied.name,
supplied.revision,
supplied.dimension
],
)
.map_err(|_| EngineOpenError::Io {
message: "could not persist embedder profile".to_string(),
})?;
return Ok(false);
};
let stored_name = row.get::<_, String>(0).map_err(|_| {
EngineOpenError::Corruption(CorruptionDetail {
kind: CorruptionKind::EmbedderIdentityDrift,
stage: OpenStage::EmbedderIdentity,
locator: CorruptionLocator::TableRow { table: "_fathomdb_embedder_profiles", rowid: 0 },
recovery_hint: RecoveryHint {
code: "E_CORRUPT_EMBEDDER_IDENTITY",
doc_anchor: "design/recovery.md#embedder-identity-drift",
},
})
})?;
let stored_revision = row.get::<_, String>(1).map_err(|_| {
EngineOpenError::Corruption(CorruptionDetail {
kind: CorruptionKind::EmbedderIdentityDrift,
stage: OpenStage::EmbedderIdentity,
locator: CorruptionLocator::TableRow { table: "_fathomdb_embedder_profiles", rowid: 0 },
recovery_hint: RecoveryHint {
code: "E_CORRUPT_EMBEDDER_IDENTITY",
doc_anchor: "design/recovery.md#embedder-identity-drift",
},
})
})?;
let dimension = row.get::<_, u32>(2).map_err(|_| {
EngineOpenError::Corruption(CorruptionDetail {
kind: CorruptionKind::EmbedderIdentityDrift,
stage: OpenStage::EmbedderIdentity,
locator: CorruptionLocator::TableRow { table: "_fathomdb_embedder_profiles", rowid: 0 },
recovery_hint: RecoveryHint {
code: "E_CORRUPT_EMBEDDER_IDENTITY",
doc_anchor: "design/recovery.md#embedder-identity-drift",
},
})
})?;
let stored = EmbedderIdentity::new(stored_name, stored_revision, dimension);
if stored.name != supplied.name || stored.revision != supplied.revision {
return Err(EngineOpenError::EmbedderIdentityMismatch {
stored,
supplied: supplied.clone(),
});
}
if dimension != supplied.dimension {
return Err(EngineOpenError::EmbedderDimensionMismatch {
stored: dimension,
supplied: supplied.dimension,
});
}
let mean_vec: Option<Vec<u8>> = row.get::<_, Option<Vec<u8>>>(3).map_err(|_| {
EngineOpenError::Corruption(CorruptionDetail {
kind: CorruptionKind::EmbedderIdentityDrift,
stage: OpenStage::EmbedderIdentity,
locator: CorruptionLocator::TableRow { table: "_fathomdb_embedder_profiles", rowid: 0 },
recovery_hint: RecoveryHint {
code: "E_CORRUPT_EMBEDDER_IDENTITY",
doc_anchor: "design/recovery.md#embedder-identity-drift",
},
})
})?;
let pinned = match mean_vec {
Some(bytes) => {
let expected_len = (dimension as usize).saturating_mul(4);
if bytes.len() != expected_len {
return Err(EngineOpenError::EmbedderIdentityMismatch {
stored,
supplied: supplied.clone(),
});
}
true
}
None => false,
};
Ok(pinned)
}
#[derive(Clone, Debug, Eq, PartialEq)]
enum WritePlan {
Node,
Edge,
AppendOnlyLog,
LatestState,
AdminSchema,
}
fn validate_batch(
connection: &Connection,
batch: &[PreparedWrite],
) -> Result<Vec<WritePlan>, EngineError> {
batch.iter().map(|write| validate_write(connection, write)).collect()
}
fn collect_projection_jobs(
connection: &Connection,
batch: &[PreparedWrite],
) -> Result<Vec<ProjectionJob>, EngineError> {
let mut jobs = Vec::new();
for write in batch {
if let PreparedWrite::Node { kind, body, .. } = write {
if kind_is_vector_indexed(connection, kind)? {
jobs.push(ProjectionJob { cursor: 0, kind: kind.clone(), body: body.clone() });
}
}
}
Ok(jobs)
}
fn validate_write(
connection: &Connection,
write: &PreparedWrite,
) -> Result<WritePlan, EngineError> {
match write {
PreparedWrite::Node { kind, body, logical_id, valid_from, valid_until, .. } => {
if kind.trim().is_empty() || body.trim().is_empty() {
return Err(EngineError::WriteValidation);
}
if let (Some(from), Some(until)) = (valid_from, valid_until) {
if from >= until {
return Err(EngineError::WriteValidation);
}
}
if let Some(logical_id) = logical_id {
if logical_id.is_empty() || logical_id.contains('\x1e') {
return Err(EngineError::WriteValidation);
}
}
Ok(WritePlan::Node)
}
PreparedWrite::Edge { kind, from, to, logical_id, t_valid, t_invalid, .. } => {
if kind.trim().is_empty() || from.trim().is_empty() || to.trim().is_empty() {
return Err(EngineError::WriteValidation);
}
if from.contains('\x1e') || to.contains('\x1e') {
return Err(EngineError::WriteValidation);
}
if let Some(logical_id) = logical_id {
if logical_id.is_empty() || logical_id.contains('\x1e') {
return Err(EngineError::WriteValidation);
}
}
reject_unrenderable_edge_epoch("t_valid", *t_valid)?;
reject_unrenderable_edge_epoch("t_invalid", *t_invalid)?;
Ok(WritePlan::Edge)
}
PreparedWrite::AdminSchema { name, kind, schema_json, retention_json } => {
if name.trim().is_empty()
|| !matches!(kind.as_str(), "append_only_log" | "latest_state")
|| serde_json::from_str::<Value>(schema_json).is_err()
|| serde_json::from_str::<Value>(retention_json).is_err()
|| contains_external_ref(schema_json)
{
return Err(EngineError::SchemaValidation);
}
Ok(WritePlan::AdminSchema)
}
PreparedWrite::OpStore { collection, record_key, schema_id, body } => {
if collection.trim().is_empty() || record_key.trim().is_empty() {
return Err(EngineError::WriteValidation);
}
let (kind, schema_json) = collection_metadata(connection, collection)?;
if let Some(schema_id) = schema_id {
if schema_id != collection {
return Err(EngineError::SchemaValidation);
}
validate_payload(&schema_json, body)?;
} else if serde_json::from_str::<Value>(body).is_err() {
return Err(EngineError::SchemaValidation);
}
match kind.as_str() {
"append_only_log" => Ok(WritePlan::AppendOnlyLog),
"latest_state" => Ok(WritePlan::LatestState),
_ => Err(EngineError::OpStore),
}
}
}
}
fn collection_metadata(
connection: &Connection,
collection: &str,
) -> Result<(String, String), EngineError> {
connection
.query_row(
"SELECT kind, schema_json FROM operational_collections WHERE name = ?1",
[collection],
|row| Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?)),
)
.map_err(|_| EngineError::OpStore)
}
fn validate_payload(schema_json: &str, body: &str) -> Result<(), EngineError> {
let schema =
serde_json::from_str::<Value>(schema_json).map_err(|_| EngineError::SchemaValidation)?;
let payload = serde_json::from_str::<Value>(body).map_err(|_| EngineError::SchemaValidation)?;
let compiled = JSONSchema::compile(&schema).map_err(|_| EngineError::SchemaValidation)?;
compiled.validate(&payload).map_err(|_| EngineError::SchemaValidation)?;
Ok(())
}
fn contains_external_ref(schema_json: &str) -> bool {
let Ok(value) = serde_json::from_str::<Value>(schema_json) else {
return false;
};
value_contains_external_ref(&value)
}
fn value_contains_external_ref(value: &Value) -> bool {
match value {
Value::Object(object) => object.iter().any(|(key, value)| {
if key == "$ref" {
return value.as_str().is_some_and(|uri| !uri.starts_with('#'));
}
value_contains_external_ref(value)
}),
Value::Array(values) => values.iter().any(value_contains_external_ref),
_ => false,
}
}
fn prior_edge_cursors_by_logical_id(
tx: &rusqlite::Transaction<'_>,
logical_id: &str,
) -> rusqlite::Result<Vec<i64>> {
let mut s = tx.prepare_cached(
"SELECT write_cursor FROM canonical_edges \
WHERE logical_id = ?1 AND superseded_at IS NULL",
)?;
let rows = s.query_map(params![logical_id], |r| r.get(0))?;
rows.collect()
}
fn prior_node_cursors_by_logical_id(
tx: &rusqlite::Transaction<'_>,
logical_id: &str,
) -> rusqlite::Result<Vec<i64>> {
let mut s = tx.prepare_cached(
"SELECT write_cursor FROM canonical_nodes \
WHERE logical_id = ?1 AND superseded_at IS NULL",
)?;
let rows = s.query_map(params![logical_id], |r| r.get(0))?;
rows.collect()
}
fn prior_edge_cursors_by_triple(
tx: &rusqlite::Transaction<'_>,
from: &str,
to: &str,
kind: &str,
) -> rusqlite::Result<Vec<i64>> {
let mut s = tx.prepare_cached(
"SELECT write_cursor FROM canonical_edges \
WHERE from_id = ?1 AND to_id = ?2 AND kind = ?3 AND superseded_at IS NULL",
)?;
let rows = s.query_map(params![from, to, kind], |r| r.get(0))?;
rows.collect()
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct IndexTargetSet {
fts: bool,
vector: bool,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum ProjectionClass {
NodeFts,
EdgeFts,
Vector,
Readiness,
Attribute,
PropertyFts,
}
#[derive(Clone, Copy, Debug)]
struct RowOwnedProjection {
table: &'static str,
cursor_column: &'static str,
class: ProjectionClass,
}
const ROW_OWNED_PROJECTIONS: &[RowOwnedProjection] = &[
RowOwnedProjection {
table: "search_index",
cursor_column: "write_cursor",
class: ProjectionClass::NodeFts,
},
RowOwnedProjection {
table: "search_index_v2",
cursor_column: "write_cursor",
class: ProjectionClass::NodeFts,
},
RowOwnedProjection {
table: "search_index_edges",
cursor_column: "write_cursor",
class: ProjectionClass::EdgeFts,
},
RowOwnedProjection {
table: "vector_default",
cursor_column: "rowid",
class: ProjectionClass::Vector,
},
RowOwnedProjection {
table: "_fathomdb_vector_rows",
cursor_column: "write_cursor",
class: ProjectionClass::Vector,
},
RowOwnedProjection {
table: "_fathomdb_projection_terminal",
cursor_column: "write_cursor",
class: ProjectionClass::Readiness,
},
RowOwnedProjection {
table: "canonical_attributes",
cursor_column: "write_cursor",
class: ProjectionClass::Attribute,
},
RowOwnedProjection {
table: "property_search_index",
cursor_column: "write_cursor",
class: ProjectionClass::PropertyFts,
},
];
fn erase_row_projections(tx: &Connection, write_cursor: i64) -> rusqlite::Result<u64> {
let mut deleted: u64 = 0;
for projection in ROW_OWNED_PROJECTIONS {
deleted =
saturating_add_u64(deleted, delete_row_owned_projection(tx, projection, write_cursor)?);
}
Ok(deleted)
}
fn delete_row_owned_projection(
tx: &Connection,
projection: &RowOwnedProjection,
write_cursor: i64,
) -> rusqlite::Result<usize> {
if projection.table == DEFAULT_VECTOR_PARTITION {
return delete_vector_partition_row(tx, write_cursor);
}
let sql = format!("DELETE FROM {} WHERE {} = ?1", projection.table, projection.cursor_column);
tx.execute(&sql, [write_cursor])
}
fn saturating_add_u64(acc: u64, n: usize) -> u64 {
acc.saturating_add(n as u64)
}
fn purge_row_projections_for_cursor_in(
tx: &Connection,
write_cursor: i64,
classes: &[ProjectionClass],
) -> rusqlite::Result<u64> {
let mut deleted: u64 = 0;
for projection in ROW_OWNED_PROJECTIONS.iter().filter(|p| classes.contains(&p.class)) {
deleted =
saturating_add_u64(deleted, delete_row_owned_projection(tx, projection, write_cursor)?);
}
Ok(deleted)
}
fn truncate_row_projections_in(
tx: &Connection,
classes: &[ProjectionClass],
) -> rusqlite::Result<u64> {
let mut deleted: u64 = 0;
for projection in ROW_OWNED_PROJECTIONS.iter().filter(|p| classes.contains(&p.class)) {
if projection.table == DEFAULT_VECTOR_PARTITION {
neutralize_vector_partition_attr_values(tx, None)?;
}
let sql = format!("DELETE FROM {}", projection.table);
deleted = deleted.saturating_add(tx.execute(&sql, [])? as u64);
}
Ok(deleted)
}
#[cfg(feature = "operator")]
fn truncate_all_row_projections(tx: &Connection) -> rusqlite::Result<u64> {
truncate_row_projections_in(
tx,
&[
ProjectionClass::NodeFts,
ProjectionClass::EdgeFts,
ProjectionClass::Vector,
ProjectionClass::Readiness,
ProjectionClass::Attribute,
ProjectionClass::PropertyFts,
],
)
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum ProjectionPass {
Write,
FtsOnly,
#[cfg_attr(not(feature = "operator"), allow(dead_code))]
VectorOnly,
}
impl ProjectionPass {
fn writes_fts(self) -> bool {
matches!(self, ProjectionPass::Write | ProjectionPass::FtsOnly)
}
fn writes_vector_state(self) -> bool {
matches!(self, ProjectionPass::Write | ProjectionPass::VectorOnly)
}
fn writes_attributes(self) -> bool {
matches!(self, ProjectionPass::Write)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct StoredProjection {
roles: BTreeSet<ProjectionRole>,
fts_present: bool,
fts_tokenizer: Option<String>,
vector_declared: bool,
vector_embedder: Option<String>,
}
impl StoredProjection {
fn wants_eav(&self) -> bool {
self.roles.contains(&ProjectionRole::Filterable)
|| self.roles.contains(&ProjectionRole::Searchable)
}
fn wants_property_fts(&self) -> bool {
self.roles.contains(&ProjectionRole::Searchable) && self.fts_present
}
fn wants_vector(&self) -> bool {
self.roles.contains(&ProjectionRole::Searchable) && self.vector_declared
}
fn fts_column(&self) -> Option<String> {
if self.fts_present {
Some(self.fts_tokenizer.clone().unwrap_or_default())
} else {
None
}
}
fn from_spec(spec: &ProjectionSpec) -> Self {
StoredProjection {
roles: spec.roles.clone(),
fts_present: spec.fts.is_some(),
fts_tokenizer: spec
.fts
.as_ref()
.and_then(|f| f.tokenizer.clone())
.filter(|t| !t.is_empty()),
vector_declared: spec.vector.is_some(),
vector_embedder: spec
.vector
.as_ref()
.and_then(|v| v.embedder.clone())
.filter(|e| !e.is_empty()),
}
}
fn to_spec(&self, name: &str) -> ProjectionSpec {
ProjectionSpec {
name: name.to_string(),
roles: self.roles.clone(),
fts: if self.fts_present {
Some(ProjectionFts { tokenizer: self.fts_tokenizer.clone() })
} else {
None
},
vector: if self.vector_declared {
Some(ProjectionVector {
embedder: self.vector_embedder.clone(),
dense_readiness: None,
})
} else {
None
},
}
}
fn has_deferred(&self) -> bool {
self.roles.contains(&ProjectionRole::Rankable) || self.vector_declared
}
}
fn is_valid_attribute_name(name: &str) -> bool {
!name.is_empty()
&& !name.contains('"')
&& !name.contains('\\')
&& !name.chars().any(|c| c.is_control())
}
fn attribute_json_path(name: &str) -> String {
format!("$.\"{name}\"")
}
fn load_projection_registry(
conn: &Connection,
) -> rusqlite::Result<BTreeMap<String, StoredProjection>> {
let mut out = BTreeMap::new();
let table_exists: bool = conn
.query_row(
"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = '_fathomdb_projection_registry'",
[],
|_| Ok(true),
)
.optional()?
.unwrap_or(false);
if !table_exists {
return Ok(out);
}
let mut stmt = conn.prepare(
"SELECT name, roles, fts_tokenizer, vector_embedder, vector_declared
FROM _fathomdb_projection_registry",
)?;
let rows = stmt.query_map([], |row| {
let name: String = row.get(0)?;
let roles_json: String = row.get(1)?;
let fts_tokenizer: Option<String> = row.get(2)?;
let vector_embedder: Option<String> = row.get(3)?;
let vector_declared: i64 = row.get(4)?;
Ok((name, roles_json, fts_tokenizer, vector_embedder, vector_declared))
})?;
for row in rows {
let (name, roles_json, fts_col, vector_embedder, vector_declared) = row?;
let roles: BTreeSet<ProjectionRole> = parse_roles_json(&roles_json);
let fts_present = fts_col.is_some();
let fts_tokenizer = fts_col.filter(|t| !t.is_empty());
out.insert(
name,
StoredProjection {
roles,
fts_present,
fts_tokenizer,
vector_declared: vector_declared != 0,
vector_embedder,
},
);
}
Ok(out)
}
fn parse_roles_json(s: &str) -> BTreeSet<ProjectionRole> {
s.split(',').filter_map(|t| ProjectionRole::from_str_opt(t.trim())).collect()
}
fn roles_to_storage(roles: &BTreeSet<ProjectionRole>) -> String {
roles.iter().map(|r| r.as_str()).collect::<Vec<_>>().join(",")
}
fn persist_projection_row(
tx: &Connection,
name: &str,
stored: &StoredProjection,
) -> rusqlite::Result<()> {
tx.execute(
"INSERT INTO _fathomdb_projection_registry
(name, roles, fts_tokenizer, vector_embedder, vector_declared)
VALUES(?1, ?2, ?3, ?4, ?5)
ON CONFLICT(name) DO UPDATE SET
roles = excluded.roles,
fts_tokenizer = excluded.fts_tokenizer,
vector_embedder = excluded.vector_embedder,
vector_declared = excluded.vector_declared",
params![
name,
roles_to_storage(&stored.roles),
stored.fts_column(),
stored.vector_embedder,
i64::from(stored.vector_declared),
],
)?;
Ok(())
}
fn remove_projection_row(tx: &Connection, name: &str) -> rusqlite::Result<()> {
tx.execute("DELETE FROM _fathomdb_projection_registry WHERE name = ?1", params![name])?;
Ok(())
}
fn clear_attribute_projection(tx: &Connection, name: &str) -> rusqlite::Result<()> {
tx.execute("DELETE FROM property_search_index WHERE attr_name = ?1", params![name])?;
tx.execute("DELETE FROM canonical_attributes WHERE attr_name = ?1", params![name])?;
Ok(())
}
fn project_one_attribute(
tx: &Connection,
cursor: i64,
body: &str,
name: &str,
stored: &StoredProjection,
) -> rusqlite::Result<()> {
if !stored.wants_eav() {
return Ok(());
}
let Some(value) = extract_scalar_attribute(tx, body, name)? else {
return Ok(());
};
tx.execute(
"INSERT INTO canonical_attributes(write_cursor, attr_name, attr_value)
VALUES(?1, ?2, ?3)",
params![cursor, name, value],
)?;
if stored.wants_property_fts() {
tx.execute(
"INSERT INTO property_search_index(attr_value, attr_name, write_cursor)
VALUES(?1, ?2, ?3)",
params![value, name, cursor],
)?;
}
Ok(())
}
const ATTR_VEC0_PRESENT_MARKER: char = '\u{1}';
fn encode_attr_vec0_present(value: &str) -> String {
let mut s = String::with_capacity(value.len() + 1);
s.push(ATTR_VEC0_PRESENT_MARKER);
s.push_str(value);
s
}
fn extract_scalar_attribute(
conn: &Connection,
body: &str,
name: &str,
) -> rusqlite::Result<Option<String>> {
let path = attribute_json_path(name);
let value: Option<String> = conn
.query_row(
"SELECT CASE WHEN json_valid(?1) THEN
CASE json_type(?1, ?2)
WHEN 'true' THEN 'true'
WHEN 'false' THEN 'false'
WHEN 'null' THEN NULL
WHEN 'object' THEN NULL
WHEN 'array' THEN NULL
ELSE CAST(json_extract(?1, ?2) AS TEXT)
END
END",
params![body, path],
|row| row.get::<_, Option<String>>(0),
)
.unwrap_or(None);
Ok(value)
}
fn vector_attr_insert_fragments(
conn: &Connection,
body: &str,
start_idx: usize,
) -> rusqlite::Result<(String, String, Vec<rusqlite::types::Value>)> {
let cols = actual_vector_attr_columns(conn)?;
let mut col_sql = String::new();
let mut ph_sql = String::new();
let mut values: Vec<rusqlite::types::Value> = Vec::new();
for (i, col) in cols.iter().enumerate() {
let name = decode_attr_vec0_column(col).unwrap_or_default();
let value = match extract_scalar_attribute(conn, body, &name)? {
Some(v) => encode_attr_vec0_present(&v),
None => String::new(),
};
col_sql.push_str(&format!(", {col}"));
ph_sql.push_str(&format!(", ?{}", start_idx + i));
values.push(rusqlite::types::Value::Text(value));
}
Ok((col_sql, ph_sql, values))
}
fn project_node_attributes(tx: &Connection, cursor: i64, body: &str) -> rusqlite::Result<()> {
let registry = load_projection_registry(tx)?;
for (name, stored) in ®istry {
project_one_attribute(tx, cursor, body, name, stored)?;
}
Ok(())
}
fn backfill_attribute(
tx: &Connection,
name: &str,
stored: &StoredProjection,
) -> rusqlite::Result<()> {
if !stored.wants_eav() {
return Ok(());
}
let rows: Vec<(i64, String)> = {
let mut stmt = tx.prepare(
"SELECT write_cursor, body FROM canonical_nodes
WHERE superseded_at IS NULL AND state = 'active'",
)?;
let collected = stmt
.query_map([], |row| Ok((row.get::<_, i64>(0)?, row.get::<_, String>(1)?)))?
.collect::<rusqlite::Result<Vec<_>>>()?;
collected
};
for (cursor, body) in rows {
project_one_attribute(tx, cursor, &body, name, stored)?;
}
Ok(())
}
fn is_destructive_projection_change(
existing: &StoredProjection,
desired: &StoredProjection,
) -> bool {
if existing.roles.iter().any(|r| !desired.roles.contains(r)) {
return true;
}
if existing.fts_present
&& (!desired.fts_present || existing.fts_tokenizer != desired.fts_tokenizer)
{
return true;
}
if existing.vector_declared
&& (!desired.vector_declared || existing.vector_embedder != desired.vector_embedder)
{
return true;
}
false
}
fn describe_projection_delta(existing: &StoredProjection, desired: &StoredProjection) -> String {
let mut parts: Vec<String> = Vec::new();
for r in &existing.roles {
if !desired.roles.contains(r) {
parts.push(format!("role '{}' removed", r.as_str()));
}
}
if existing.fts_present && !desired.fts_present {
parts.push("fts sub-target removed".to_string());
} else if existing.fts_present && existing.fts_tokenizer != desired.fts_tokenizer {
parts.push("fts tokenizer changed".to_string());
}
if existing.vector_declared && !desired.vector_declared {
parts.push("vector sub-target removed".to_string());
} else if existing.vector_declared && existing.vector_embedder != desired.vector_embedder {
parts.push("vector embedder changed".to_string());
}
if parts.is_empty() {
"incompatible change".to_string()
} else {
parts.join("; ")
}
}
fn apply_projection_config(
tx: &Connection,
specs: &[ProjectionSpec],
drop: &[String],
dense_arm_live: bool,
) -> Result<(ProjectionDelta, bool), EngineError> {
let mut seen_spec_names: BTreeSet<&str> = BTreeSet::new();
for spec in specs {
if !is_valid_attribute_name(&spec.name) {
return Err(EngineError::InvalidArgument {
msg: format!("invalid projection attribute name: {:?}", spec.name),
});
}
if spec.roles.is_empty() {
return Err(EngineError::InvalidArgument {
msg: format!("projection '{}' declares no roles", spec.name),
});
}
if !seen_spec_names.insert(spec.name.as_str()) {
return Err(EngineError::InvalidArgument {
msg: format!("duplicate projection name in one request: '{}'", spec.name),
});
}
}
let mut seen_drop_names: BTreeSet<&str> = BTreeSet::new();
for name in drop {
if !is_valid_attribute_name(name) {
return Err(EngineError::InvalidArgument {
msg: format!("invalid projection drop name: {name:?}"),
});
}
if !seen_drop_names.insert(name.as_str()) {
return Err(EngineError::InvalidArgument {
msg: format!("duplicate projection drop in one request: '{name}'"),
});
}
}
for spec in specs {
if spec.roles.contains(&ProjectionRole::Searchable) {
continue;
}
if spec.fts.is_some() || spec.vector.is_some() {
return Err(EngineError::WriteValidation);
}
}
let mut delta = ProjectionDelta::default();
let vector_declared_before =
vector_projection_declared(tx).map_err(|_| EngineError::Storage)?;
let before_drop = load_projection_registry(tx).map_err(|_| EngineError::Storage)?;
for name in drop {
if before_drop.contains_key(name) {
clear_attribute_projection(tx, name).map_err(|_| EngineError::Storage)?;
remove_projection_row(tx, name).map_err(|_| EngineError::Storage)?;
delta.dropped.push(name.clone());
}
}
let current = load_projection_registry(tx).map_err(|_| EngineError::Storage)?;
for spec in specs {
let desired = StoredProjection::from_spec(spec);
match current.get(&spec.name) {
Some(existing) if existing == &desired => {
}
Some(existing) => {
if is_destructive_projection_change(existing, &desired) {
return Err(EngineError::ProjectionDestructive {
name: spec.name.clone(),
delta: describe_projection_delta(existing, &desired),
});
}
persist_projection_row(tx, &spec.name, &desired)
.map_err(|_| EngineError::Storage)?;
clear_attribute_projection(tx, &spec.name).map_err(|_| EngineError::Storage)?;
backfill_attribute(tx, &spec.name, &desired).map_err(|_| EngineError::Storage)?;
if desired.wants_eav() {
delta.built.push(spec.name.clone());
}
if desired.has_deferred() {
delta.deferred.push(spec.name.clone());
}
}
None => {
persist_projection_row(tx, &spec.name, &desired)
.map_err(|_| EngineError::Storage)?;
clear_attribute_projection(tx, &spec.name).map_err(|_| EngineError::Storage)?;
backfill_attribute(tx, &spec.name, &desired).map_err(|_| EngineError::Storage)?;
if desired.wants_eav() {
delta.built.push(spec.name.clone());
}
if desired.has_deferred() {
delta.deferred.push(spec.name.clone());
}
}
}
}
if let Ok(dimension) = default_profile_dimension(tx) {
reconcile_vector_attr_columns(tx, dimension).map_err(|_| EngineError::Storage)?;
}
delta.unchanged =
delta.built.is_empty() && delta.dropped.is_empty() && delta.deferred.is_empty();
let vector_declared_after = vector_projection_declared(tx).map_err(|_| EngineError::Storage)?;
let enqueued = if vector_declared_after {
delta.vector_unsupported_kinds =
unsupported_vector_kinds(tx).map_err(|_| EngineError::Storage)?;
if dense_arm_live {
enqueue_declared_vector_backfill(tx).map_err(|_| EngineError::Storage)?
} else {
false
}
} else {
if vector_declared_before
|| registry_governs_an_inert_dense_arm(tx).map_err(|_| EngineError::Storage)?
{
unenrol_registry_vector_node_kinds(tx).map_err(|_| EngineError::Storage)?;
}
false
};
Ok((delta, enqueued))
}
fn unenrol_registry_vector_node_kinds(tx: &Connection) -> rusqlite::Result<()> {
tx.execute("DELETE FROM _fathomdb_vector_kinds WHERE kind <> 'edge_fact'", [])?;
Ok(())
}
fn registry_governs_an_inert_dense_arm(conn: &Connection) -> rusqlite::Result<bool> {
let table_exists: bool = conn
.prepare_cached(
"SELECT EXISTS(
SELECT 1 FROM sqlite_master
WHERE type = 'table' AND name = '_fathomdb_projection_registry'
)",
)?
.query_row([], |row| row.get(0))?;
if !table_exists {
return Ok(false);
}
let any_vector_subobject: bool = conn
.prepare_cached(
"SELECT EXISTS(SELECT 1 FROM _fathomdb_projection_registry WHERE vector_declared = 1)",
)?
.query_row([], |row| row.get(0))?;
if !any_vector_subobject {
return Ok(false);
}
Ok(!vector_projection_declared(conn)?)
}
fn reconcile_inert_vector_enrolments_on_boot(conn: &Connection) -> rusqlite::Result<bool> {
if !registry_governs_an_inert_dense_arm(conn)? {
return Ok(false);
}
let any_node_kind: bool = conn
.prepare_cached(
"SELECT EXISTS(SELECT 1 FROM _fathomdb_vector_kinds WHERE kind <> 'edge_fact')",
)?
.query_row([], |row| row.get(0))?;
if !any_node_kind {
return Ok(false);
}
unenrol_registry_vector_node_kinds(conn)?;
Ok(true)
}
fn vector_projection_declared(conn: &Connection) -> rusqlite::Result<bool> {
let table_exists: bool = conn
.prepare_cached(
"SELECT EXISTS(
SELECT 1 FROM sqlite_master
WHERE type = 'table' AND name = '_fathomdb_projection_registry'
)",
)?
.query_row([], |row| row.get(0))?;
if !table_exists {
return Ok(false);
}
let any_vector_subobject: bool = conn
.prepare_cached(
"SELECT EXISTS(SELECT 1 FROM _fathomdb_projection_registry WHERE vector_declared = 1)",
)?
.query_row([], |row| row.get(0))?;
if !any_vector_subobject {
return Ok(false);
}
Ok(load_projection_registry(conn)?.values().any(StoredProjection::wants_vector))
}
fn register_vector_kind(tx: &Connection, kind: &str) -> rusqlite::Result<()> {
tx.execute(
"INSERT OR IGNORE INTO _fathomdb_vector_kinds(kind, profile, created_at)
VALUES(?1, ?2, 0)",
params![kind, DEFAULT_VECTOR_PROFILE],
)?;
Ok(())
}
fn vector_eligible_node_kinds(tx: &Connection) -> rusqlite::Result<Vec<String>> {
let mut stmt = tx.prepare(
"SELECT DISTINCT kind FROM canonical_nodes
WHERE row_kind IN ('leaf', 'coverage')
ORDER BY kind",
)?;
let rows = stmt.query_map([], |row| row.get::<_, String>(0))?;
rows.collect::<rusqlite::Result<Vec<String>>>()
}
fn unsupported_vector_kinds(tx: &Connection) -> rusqlite::Result<Vec<String>> {
Ok(vector_eligible_node_kinds(tx)?
.into_iter()
.filter(|kind| !kind_is_vector_committable(kind))
.collect())
}
fn enqueue_declared_vector_backfill(tx: &Connection) -> rusqlite::Result<bool> {
if !vector_projection_declared(tx)? {
return Ok(false);
}
let kinds = vector_eligible_node_kinds(tx)?;
for kind in kinds.iter().filter(|kind| kind_is_vector_committable(kind)) {
register_vector_kind(tx, kind)?;
}
reenqueue_stranded_vector_rows(tx)
}
fn reenqueue_stranded_vector_rows(tx: &Connection) -> rusqlite::Result<bool> {
let lowest_stranded: Option<u64> = tx.query_row(
"SELECT MIN(n.write_cursor)
FROM canonical_nodes n
JOIN _fathomdb_vector_kinds k ON k.kind = n.kind
JOIN _fathomdb_projection_terminal t ON t.write_cursor = n.write_cursor
LEFT JOIN _fathomdb_vector_rows v ON v.write_cursor = n.write_cursor
WHERE n.row_kind IN ('leaf', 'coverage')
AND t.state = 'up_to_date'
AND v.write_cursor IS NULL",
[],
|row| row.get::<_, Option<u64>>(0),
)?;
let Some(lowest_stranded) = lowest_stranded else {
return Ok(false);
};
tx.execute(
"DELETE FROM _fathomdb_projection_terminal
WHERE write_cursor IN (
SELECT n.write_cursor
FROM canonical_nodes n
JOIN _fathomdb_vector_kinds k ON k.kind = n.kind
JOIN _fathomdb_projection_terminal t ON t.write_cursor = n.write_cursor
LEFT JOIN _fathomdb_vector_rows v ON v.write_cursor = n.write_cursor
WHERE n.row_kind IN ('leaf', 'coverage')
AND t.state = 'up_to_date'
AND v.write_cursor IS NULL
)",
[],
)?;
let rewind_to = lowest_stranded.saturating_sub(1);
if load_projection_cursor(tx)? > rewind_to {
store_projection_cursor(tx, rewind_to)?;
}
Ok(true)
}
fn rederive_projections_on_boot(conn: &Connection) -> rusqlite::Result<()> {
let registry = load_projection_registry(conn)?;
if registry.is_empty() {
return Ok(());
}
conn.execute_batch("BEGIN IMMEDIATE")?;
let result = (|| {
for (name, stored) in ®istry {
clear_attribute_projection(conn, name)?;
backfill_attribute(conn, name, stored)?;
}
Ok(())
})();
match result {
Ok(()) => conn.execute_batch("COMMIT"),
Err(err) => {
let _ = conn.execute_batch("ROLLBACK");
Err(err)
}
}
}
fn index_targets_for_row_kind(row_kind: RowKind) -> IndexTargetSet {
match row_kind {
RowKind::Leaf => IndexTargetSet { fts: true, vector: true },
RowKind::Coverage => IndexTargetSet { fts: true, vector: true },
RowKind::Graph => IndexTargetSet { fts: true, vector: false },
}
}
fn project_canonical_node_row(
tx: &Connection,
cursor: u64,
kind: &str,
body: &str,
row_kind: RowKind,
pass: ProjectionPass,
node_active: bool,
) -> rusqlite::Result<bool> {
let targets = index_targets_for_row_kind(row_kind);
if targets.fts && pass.writes_fts() {
tx.execute(
"INSERT INTO search_index(body, kind, write_cursor) VALUES(?1, ?2, ?3)",
params![body, kind, cursor],
)?;
tx.execute(
"INSERT INTO search_index_v2(kind, body, status, write_cursor)
VALUES(
?1,
?2,
CASE WHEN json_valid(?2)
THEN COALESCE(json_extract(?2, '$.status'), '')
ELSE '' END,
?3
)",
params![kind, body, cursor],
)?;
}
if pass.writes_attributes() && node_active {
project_node_attributes(tx, cursor as i64, body)?;
}
let enqueue_vector = targets.vector && kind_is_vector_indexed(tx, kind).unwrap_or(false);
if pass.writes_vector_state() {
if enqueue_vector {
tx.execute(
"INSERT INTO _fathomdb_projection_state(kind, last_enqueued_cursor, updated_at)
VALUES(?1, ?2, 0)
ON CONFLICT(kind) DO UPDATE SET last_enqueued_cursor = excluded.last_enqueued_cursor",
params![kind, cursor],
)?;
} else {
record_projection_terminal(tx, cursor, "up_to_date")?;
}
}
Ok(enqueue_vector)
}
fn project_canonical_edge_row(
tx: &Connection,
cursor: u64,
kind: &str,
body: Option<&str>,
pass: ProjectionPass,
) -> rusqlite::Result<bool> {
if pass.writes_fts() {
if let Some(edge_body) = body {
tx.execute(
"INSERT INTO search_index_edges(body, kind, write_cursor)
VALUES(?1, ?2, ?3)",
params![edge_body, kind, cursor],
)?;
}
}
let enqueue_vector = body.is_some();
if pass.writes_vector_state() {
if enqueue_vector {
let now_unix =
SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs() as i64;
tx.execute(
"INSERT OR IGNORE INTO _fathomdb_vector_kinds(kind, profile, created_at)
VALUES('edge_fact', 'default', ?1)",
params![now_unix],
)?;
tx.execute(
"INSERT INTO _fathomdb_projection_state(
kind, last_enqueued_cursor, updated_at
) VALUES('edge_fact', ?1, 0)
ON CONFLICT(kind) DO UPDATE
SET last_enqueued_cursor = excluded.last_enqueued_cursor",
params![cursor],
)?;
} else {
record_projection_terminal(tx, cursor, "up_to_date")?;
}
}
Ok(enqueue_vector)
}
fn fts5_tokenize(connection: &Connection, text: &str) -> rusqlite::Result<Vec<String>> {
connection.execute_batch(
"CREATE VIRTUAL TABLE IF NOT EXISTS temp.bm25f_tok
USING fts5(t, tokenize = 'porter unicode61 remove_diacritics 2');
CREATE VIRTUAL TABLE IF NOT EXISTS temp.bm25f_tok_vocab
USING fts5vocab('bm25f_tok', 'instance');
DELETE FROM temp.bm25f_tok;",
)?;
connection.execute("INSERT INTO temp.bm25f_tok(t) VALUES(?1)", params![text])?;
let mut stmt =
connection.prepare("SELECT term FROM temp.bm25f_tok_vocab ORDER BY \"offset\"")?;
let rows = stmt.query_map([], |r| r.get::<_, String>(0))?;
rows.collect()
}
fn bm25f_match_expression(terms: &[String]) -> String {
terms.iter().map(|t| format!("\"{t}\"")).collect::<Vec<_>>().join(" OR ")
}
fn bm25f_score_doc(
plan: &Bm25fQueryPlan,
query_terms: &[String],
fields: &[(f64, f64, f64, &HashMap<String, u32>)],
doc_count: usize,
df: &HashMap<String, usize>,
) -> f64 {
let mut score = 0.0_f64;
for term in query_terms {
let mut weighted_tf = 0.0_f64;
for (weight, len_f, avglen_f, tf_map) in fields {
if *weight == 0.0 || *avglen_f <= 0.0 {
continue;
}
let tf = *tf_map.get(term).unwrap_or(&0) as f64;
if tf == 0.0 {
continue;
}
let norm = 1.0 - plan.b + plan.b * (len_f / avglen_f);
if norm <= 0.0 {
continue;
}
weighted_tf += weight * tf / norm;
}
if weighted_tf <= 0.0 {
continue;
}
let dfq = *df.get(term).unwrap_or(&0);
if dfq == 0 {
continue;
}
let n = doc_count as f64;
let idf = ((n - dfq as f64 + 0.5) / (dfq as f64 + 0.5) + 1.0).ln();
score += idf * (weighted_tf * (plan.k1 + 1.0)) / (plan.k1 + weighted_tf);
}
score
}
fn bm25f_search_inner(
connection: &Connection,
query: &str,
plan: &Bm25fQueryPlan,
) -> rusqlite::Result<Vec<(u64, f64)>> {
let query_terms: Vec<String> = {
let mut seen = BTreeSet::new();
fts5_tokenize(connection, query)?.into_iter().filter(|t| seen.insert(t.clone())).collect()
};
if query_terms.is_empty() {
return Ok(Vec::new());
}
let mut doc_count: usize = 0;
let mut total_len = [0.0_f64; 3]; let mut df: HashMap<String, usize> = HashMap::new();
{
let mut stmt = connection.prepare(
"SELECT v.kind, v.body, v.status
FROM search_index_v2 v
JOIN canonical_nodes cn ON cn.write_cursor = v.write_cursor
WHERE cn.superseded_at IS NULL AND cn.state = 'active'",
)?;
let mut rows = stmt.query([])?;
while let Some(row) = rows.next()? {
let fields =
[row.get::<_, String>(0)?, row.get::<_, String>(1)?, row.get::<_, String>(2)?];
doc_count += 1;
let mut present: BTreeSet<String> = BTreeSet::new();
for (i, field) in fields.iter().enumerate() {
let toks = fts5_tokenize(connection, field)?;
total_len[i] += toks.len() as f64;
for tok in toks {
if query_terms.contains(&tok) {
present.insert(tok);
}
}
}
for term in present {
*df.entry(term).or_insert(0) += 1;
}
}
}
if doc_count == 0 {
return Ok(Vec::new());
}
let avglen = [
total_len[0] / doc_count as f64,
total_len[1] / doc_count as f64,
total_len[2] / doc_count as f64,
];
let active: BTreeSet<i64> = {
let mut stmt = connection
.prepare("SELECT write_cursor FROM canonical_nodes WHERE superseded_at IS NULL AND state = 'active'")?;
let rows = stmt.query_map([], |r| r.get::<_, i64>(0))?;
rows.collect::<rusqlite::Result<BTreeSet<i64>>>()?
};
let match_expr = bm25f_match_expression(&query_terms);
let mut scored: Vec<(u64, f64)> = Vec::new();
{
let mut stmt = connection.prepare(
"SELECT write_cursor, kind, body, status
FROM search_index_v2
WHERE search_index_v2 MATCH ?1",
)?;
let mut rows = stmt.query([match_expr.as_str()])?;
while let Some(row) = rows.next()? {
let wc = row.get::<_, i64>(0)?;
if !active.contains(&wc) {
continue;
}
let kind = row.get::<_, String>(1)?;
let body = row.get::<_, String>(2)?;
let status = row.get::<_, String>(3)?;
let mut tf_kind: HashMap<String, u32> = HashMap::new();
let mut len_kind = 0.0_f64;
for tok in fts5_tokenize(connection, &kind)? {
len_kind += 1.0;
*tf_kind.entry(tok).or_insert(0) += 1;
}
let mut tf_body: HashMap<String, u32> = HashMap::new();
let mut len_body = 0.0_f64;
for tok in fts5_tokenize(connection, &body)? {
len_body += 1.0;
*tf_body.entry(tok).or_insert(0) += 1;
}
let mut tf_status: HashMap<String, u32> = HashMap::new();
let mut len_status = 0.0_f64;
for tok in fts5_tokenize(connection, &status)? {
len_status += 1.0;
*tf_status.entry(tok).or_insert(0) += 1;
}
let fields = [
(plan.weights.kind, len_kind, avglen[0], &tf_kind),
(plan.weights.body, len_body, avglen[1], &tf_body),
(plan.weights.status, len_status, avglen[2], &tf_status),
];
let score = bm25f_score_doc(plan, &query_terms, &fields, doc_count, &df);
scored.push((wc as u64, score));
}
}
scored.sort_by(|a, b| {
b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal).then(a.0.cmp(&b.0))
});
Ok(scored)
}
fn commit_batch(
connection: &mut Connection,
batch: &[PreparedWrite],
plans: &[WritePlan],
base_cursor: u64,
provenance_row_cap: u64,
) -> rusqlite::Result<u64> {
let tx = connection.transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)?;
for (i, (write, plan)) in batch.iter().zip(plans).enumerate() {
let cursor = base_cursor.saturating_add((i as u64).saturating_add(1));
match (write, plan) {
(
PreparedWrite::Node {
kind,
body,
source_id,
logical_id,
state,
reason,
valid_from,
valid_until,
},
WritePlan::Node,
) => {
if let Some(logical_id) = logical_id {
let prior_g0 = prior_node_cursors_by_logical_id(&tx, logical_id)?;
tx.execute(
"UPDATE canonical_nodes SET superseded_at = ?1
WHERE logical_id = ?2 AND superseded_at IS NULL",
params![cursor, logical_id],
)?;
for sc in &prior_g0 {
purge_row_projections_for_cursor_in(
&tx,
*sc,
&[ProjectionClass::Attribute, ProjectionClass::PropertyFts],
)?;
}
}
tx.execute(
"INSERT INTO canonical_nodes(write_cursor, kind, body, source_id, logical_id, row_kind, state, reason, valid_from, valid_until)
VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)",
params![cursor, kind, body, source_id.as_str(), logical_id, RowKind::Leaf.as_str(), state.as_str(), reason, valid_from, valid_until],
)?;
project_canonical_node_row(
&tx,
cursor,
kind,
body,
RowKind::Leaf,
ProjectionPass::Write,
matches!(state, InitialState::Active),
)?;
}
(
PreparedWrite::Edge {
kind,
from,
to,
source_id,
logical_id,
body,
t_valid,
t_invalid,
confidence,
extractor_model_id,
temporal_fallback,
},
WritePlan::Edge,
) => {
if let Some(logical_id) = logical_id {
let prior_g0 = prior_edge_cursors_by_logical_id(&tx, logical_id)?;
tx.execute(
"UPDATE canonical_edges SET superseded_at = ?1
WHERE logical_id = ?2 AND superseded_at IS NULL",
params![cursor, logical_id],
)?;
for sc in &prior_g0 {
delete_vector_partition_row(&tx, *sc)?;
tx.execute(
"DELETE FROM _fathomdb_vector_rows WHERE write_cursor = ?1",
[sc],
)?;
record_projection_terminal(&tx, *sc as u64, "up_to_date")?;
}
}
if body.is_some() {
let prior_g11 = prior_edge_cursors_by_triple(&tx, from, to, kind)?;
tx.execute(
"UPDATE canonical_edges SET superseded_at = ?1
WHERE from_id = ?2 AND to_id = ?3 AND kind = ?4 AND superseded_at IS NULL",
params![cursor, from, to, kind],
)?;
for sc in &prior_g11 {
delete_vector_partition_row(&tx, *sc)?;
tx.execute(
"DELETE FROM _fathomdb_vector_rows WHERE write_cursor = ?1",
[sc],
)?;
record_projection_terminal(&tx, *sc as u64, "up_to_date")?;
}
}
let temporal_fallback_i: Option<i64> =
temporal_fallback.and_then(|f| if f { Some(1) } else { None });
tx.execute(
"INSERT INTO canonical_edges(
write_cursor, kind, from_id, to_id, source_id, logical_id,
body, t_valid, t_invalid, confidence, extractor_model_id,
temporal_fallback
) VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)",
params![
cursor,
kind,
from,
to,
source_id.as_str(),
logical_id,
body,
t_valid,
t_invalid,
confidence,
extractor_model_id,
temporal_fallback_i
],
)?;
project_canonical_edge_row(
&tx,
cursor,
kind,
body.as_deref(),
ProjectionPass::Write,
)?;
}
(
PreparedWrite::AdminSchema { name, kind, schema_json, retention_json },
WritePlan::AdminSchema,
) => {
tx.execute(
"INSERT INTO operational_collections(
name, kind, schema_json, retention_json, format_version, created_at
) VALUES(?1, ?2, ?3, ?4, 1, 0)
ON CONFLICT(name) DO UPDATE SET
schema_json = excluded.schema_json,
retention_json = excluded.retention_json",
params![name, kind, schema_json, retention_json],
)?;
record_projection_terminal(&tx, cursor, "up_to_date")?;
}
(
PreparedWrite::OpStore { collection, record_key, schema_id, body },
WritePlan::AppendOnlyLog,
) => {
tx.execute(
"INSERT INTO operational_mutations(
collection_name, record_key, op_kind, payload_json, schema_id, write_cursor
) VALUES(?1, ?2, 'append', ?3, ?4, ?5)",
params![collection, record_key, body, schema_id, cursor],
)?;
record_projection_terminal(&tx, cursor, "up_to_date")?;
}
(
PreparedWrite::OpStore { collection, record_key, schema_id, body },
WritePlan::LatestState,
) => {
tx.execute(
"INSERT INTO operational_state(
collection_name, record_key, payload_json, schema_id, write_cursor
) VALUES(?1, ?2, ?3, ?4, ?5)
ON CONFLICT(collection_name, record_key) DO UPDATE SET
payload_json = excluded.payload_json,
schema_id = excluded.schema_id,
write_cursor = excluded.write_cursor",
params![collection, record_key, body, schema_id, cursor],
)?;
record_projection_terminal(&tx, cursor, "up_to_date")?;
}
_ => return Err(rusqlite::Error::InvalidQuery),
}
}
let dangling_edge_endpoints = {
let mut last_index: HashMap<&str, usize> = HashMap::new();
for (i, write) in batch.iter().enumerate() {
if let PreparedWrite::Edge { logical_id: Some(lid), .. } = write {
last_index.insert(lid.as_str(), i);
}
}
let mut probe = tx.prepare(
"SELECT 1 FROM canonical_nodes WHERE logical_id = ?1 AND superseded_at IS NULL LIMIT 1",
)?;
let mut count: u64 = 0;
for (i, write) in batch.iter().enumerate() {
if let PreparedWrite::Edge { from, to, logical_id, .. } = write {
if let Some(lid) = logical_id {
let superseded_in_batch =
last_index.get(lid.as_str()).is_some_and(|&last| last > i);
if superseded_in_batch {
continue;
}
}
for endpoint in [from, to] {
if !probe.exists(params![endpoint])? {
count = count.saturating_add(1);
}
}
}
}
count
};
enforce_provenance_retention(&tx, provenance_row_cap)?;
advance_projection_cursor(&tx)?;
tx.commit()?;
Ok(dangling_edge_endpoints)
}
fn load_next_cursor(connection: &Connection) -> u64 {
let nodes = max_cursor(connection, "canonical_nodes").unwrap_or(0);
let edges = max_cursor(connection, "canonical_edges").unwrap_or(0);
let mutations = max_cursor(connection, "operational_mutations").unwrap_or(0);
let state = max_cursor(connection, "operational_state").unwrap_or(0);
let reserved = reserved_write_cursor(connection);
nodes.max(edges).max(mutations).max(state).max(reserved)
}
fn reserved_write_cursor(connection: &Connection) -> u64 {
connection
.query_row(
"SELECT value FROM _fathomdb_open_state WHERE key = ?1",
params![fathomdb_schema::RESERVED_WRITE_CURSOR_KEY],
|row| row.get::<_, String>(0),
)
.ok()
.and_then(|raw| raw.parse::<u64>().ok())
.unwrap_or(0)
}
fn max_cursor(connection: &Connection, table: &str) -> rusqlite::Result<u64> {
let sql = format!("SELECT COALESCE(MAX(write_cursor), 0) FROM {table}");
connection.query_row(&sql, [], |row| row.get::<_, u64>(0))
}
fn sqlite_extended_code_name(err: &rusqlite::Error) -> Option<&'static str> {
let sqlite_error = err.sqlite_error()?;
let extended = sqlite_error.extended_code;
Some(match extended {
rusqlite::ffi::SQLITE_SCHEMA => "SQLITE_SCHEMA",
rusqlite::ffi::SQLITE_BUSY => "SQLITE_BUSY",
rusqlite::ffi::SQLITE_LOCKED => "SQLITE_LOCKED",
rusqlite::ffi::SQLITE_CORRUPT => "SQLITE_CORRUPT",
rusqlite::ffi::SQLITE_NOTADB => "SQLITE_NOTADB",
rusqlite::ffi::SQLITE_IOERR => "SQLITE_IOERR",
rusqlite::ffi::SQLITE_FULL => "SQLITE_FULL",
rusqlite::ffi::SQLITE_READONLY => "SQLITE_READONLY",
rusqlite::ffi::SQLITE_CONSTRAINT => "SQLITE_CONSTRAINT",
rusqlite::ffi::SQLITE_MISUSE => "SQLITE_MISUSE",
rusqlite::ffi::SQLITE_INTERRUPT => "SQLITE_INTERRUPT",
rusqlite::ffi::SQLITE_NOMEM => "SQLITE_NOMEM",
rusqlite::ffi::SQLITE_PERM => "SQLITE_PERM",
rusqlite::ffi::SQLITE_ABORT => "SQLITE_ABORT",
rusqlite::ffi::SQLITE_PROTOCOL => "SQLITE_PROTOCOL",
rusqlite::ffi::SQLITE_RANGE => "SQLITE_RANGE",
rusqlite::ffi::SQLITE_TOOBIG => "SQLITE_TOOBIG",
rusqlite::ffi::SQLITE_MISMATCH => "SQLITE_MISMATCH",
rusqlite::ffi::SQLITE_AUTH => "SQLITE_AUTH",
rusqlite::ffi::SQLITE_NOTFOUND => "SQLITE_NOTFOUND",
rusqlite::ffi::SQLITE_CANTOPEN => "SQLITE_CANTOPEN",
_ => "SQLITE_UNKNOWN",
})
}
fn sqlite_extended_code_name_from_int(extended: i32) -> &'static str {
match extended {
rusqlite::ffi::SQLITE_SCHEMA => "SQLITE_SCHEMA",
rusqlite::ffi::SQLITE_BUSY => "SQLITE_BUSY",
rusqlite::ffi::SQLITE_LOCKED => "SQLITE_LOCKED",
rusqlite::ffi::SQLITE_CORRUPT => "SQLITE_CORRUPT",
rusqlite::ffi::SQLITE_NOTADB => "SQLITE_NOTADB",
rusqlite::ffi::SQLITE_IOERR => "SQLITE_IOERR",
rusqlite::ffi::SQLITE_FULL => "SQLITE_FULL",
rusqlite::ffi::SQLITE_READONLY => "SQLITE_READONLY",
rusqlite::ffi::SQLITE_CONSTRAINT => "SQLITE_CONSTRAINT",
rusqlite::ffi::SQLITE_MISUSE => "SQLITE_MISUSE",
rusqlite::ffi::SQLITE_INTERRUPT => "SQLITE_INTERRUPT",
rusqlite::ffi::SQLITE_NOMEM => "SQLITE_NOMEM",
rusqlite::ffi::SQLITE_PERM => "SQLITE_PERM",
rusqlite::ffi::SQLITE_ABORT => "SQLITE_ABORT",
rusqlite::ffi::SQLITE_PROTOCOL => "SQLITE_PROTOCOL",
rusqlite::ffi::SQLITE_RANGE => "SQLITE_RANGE",
rusqlite::ffi::SQLITE_TOOBIG => "SQLITE_TOOBIG",
rusqlite::ffi::SQLITE_MISMATCH => "SQLITE_MISMATCH",
rusqlite::ffi::SQLITE_AUTH => "SQLITE_AUTH",
rusqlite::ffi::SQLITE_NOTFOUND => "SQLITE_NOTFOUND",
rusqlite::ffi::SQLITE_CANTOPEN => "SQLITE_CANTOPEN",
_ => "SQLITE_UNKNOWN",
}
}
fn map_open_sqlite_error(err: rusqlite::Error, stage: OpenStage) -> EngineOpenError {
let Some(sqlite_error) = err.sqlite_error() else {
return EngineOpenError::Io { message: "could not open database".to_string() };
};
match sqlite_error.extended_code {
rusqlite::ffi::SQLITE_CORRUPT | rusqlite::ffi::SQLITE_NOTADB => {
EngineOpenError::Corruption(CorruptionDetail {
kind: match stage {
OpenStage::WalReplay => CorruptionKind::WalReplayFailure,
OpenStage::HeaderProbe => CorruptionKind::HeaderMalformed,
OpenStage::SchemaProbe => CorruptionKind::SchemaInconsistent,
OpenStage::EmbedderIdentity => CorruptionKind::EmbedderIdentityDrift,
},
stage,
locator: CorruptionLocator::OpaqueSqliteError {
sqlite_extended_code: sqlite_error.extended_code,
},
recovery_hint: RecoveryHint {
code: match stage {
OpenStage::WalReplay => "E_CORRUPT_WAL_REPLAY",
OpenStage::HeaderProbe => "E_CORRUPT_HEADER",
OpenStage::SchemaProbe => "E_CORRUPT_SCHEMA",
OpenStage::EmbedderIdentity => "E_CORRUPT_EMBEDDER_IDENTITY",
},
doc_anchor: match stage {
OpenStage::WalReplay => "design/recovery.md#wal-replay-failures",
OpenStage::HeaderProbe => "design/recovery.md#header-malformed",
OpenStage::SchemaProbe => "design/recovery.md#schema-inconsistent",
OpenStage::EmbedderIdentity => "design/recovery.md#embedder-identity-drift",
},
},
})
}
_ => EngineOpenError::Io { message: "could not open database".to_string() },
}
}
fn emit_open_error_event(subscriber: &Arc<dyn lifecycle::Subscriber>, err: &EngineOpenError) {
if let EngineOpenError::Corruption(detail) = err {
let code = match detail.locator {
CorruptionLocator::OpaqueSqliteError { sqlite_extended_code } => {
Some(sqlite_extended_code_name_from_int(sqlite_extended_code))
}
_ => None,
};
let event = lifecycle::Event {
phase: lifecycle::Phase::Failed,
source: lifecycle::EventSource::SqliteInternal,
category: lifecycle::EventCategory::Corruption,
code,
};
subscriber.on_event(&event);
}
}
#[allow(clippy::vec_box)]
fn install_profile_callback(
connection: &Connection,
subscribers: &Arc<lifecycle::SubscriberRegistry>,
profiling_enabled: &Arc<AtomicBool>,
slow_threshold_ms: &Arc<AtomicU64>,
contexts: &mut Vec<Box<ProfileContext>>,
) {
let mut ctx = Box::new(ProfileContext {
subscribers: Arc::clone(subscribers),
profiling_enabled: Arc::clone(profiling_enabled),
slow_threshold_ms: Arc::clone(slow_threshold_ms),
});
let ctx_ptr: *mut ProfileContext = &mut *ctx;
unsafe {
rusqlite::ffi::sqlite3_profile(
connection.handle(),
Some(profile_callback_trampoline),
ctx_ptr.cast::<std::ffi::c_void>(),
);
}
contexts.push(ctx);
}
fn uninstall_profile_callback(connection: &Connection) {
unsafe {
rusqlite::ffi::sqlite3_profile(connection.handle(), None, std::ptr::null_mut());
}
}
fn apply_perf_experiment_writer_pragmas(connection: &Connection) {
if std::env::var_os("FATHOMDB_PERF_EXPERIMENTS").is_none() {
return;
}
let raw = match std::env::var("FATHOMDB_PERF_WRITER_PRAGMAS") {
Ok(s) if !s.is_empty() => s,
_ => return,
};
for entry in raw.split(',') {
let entry = entry.trim();
if entry.is_empty() {
continue;
}
let (name, value) = match entry.split_once('=') {
Some((n, v)) => (n.trim(), v.trim()),
None => {
eprintln!("perf-experiment: bad writer pragma entry (expect name=value): {entry}");
continue;
}
};
if name.is_empty() {
eprintln!("perf-experiment: empty pragma name in writer entry: {entry}");
continue;
}
match connection.pragma_update(None, name, value) {
Ok(()) => {
eprintln!(
"perf-experiment: applied PRAGMA {name}={value} on writer (pre-migration)"
);
}
Err(err) => {
eprintln!("perf-experiment: writer PRAGMA {name}={value} failed: {err}");
}
}
}
}
fn apply_perf_experiment_reader_pragmas(connection: &Connection) {
if std::env::var_os("FATHOMDB_PERF_EXPERIMENTS").is_none() {
return;
}
let raw = match std::env::var("FATHOMDB_PERF_READER_PRAGMAS") {
Ok(s) if !s.is_empty() => s,
_ => return,
};
for entry in raw.split(',') {
let entry = entry.trim();
if entry.is_empty() {
continue;
}
let (name, value) = match entry.split_once('=') {
Some((n, v)) => (n.trim(), v.trim()),
None => {
eprintln!("perf-experiment: bad pragma entry (expect name=value): {entry}");
continue;
}
};
if name.is_empty() {
eprintln!("perf-experiment: empty pragma name in entry: {entry}");
continue;
}
match connection.pragma_update(None, name, value) {
Ok(()) => {
eprintln!("perf-experiment: applied PRAGMA {name}={value} on reader");
}
Err(err) => {
eprintln!("perf-experiment: PRAGMA {name}={value} failed: {err}");
}
}
}
}
fn configure_reader_lookaside(connection: &Connection) -> std::os::raw::c_int {
unsafe {
rusqlite::ffi::sqlite3_db_config(
connection.handle(),
rusqlite::ffi::SQLITE_DBCONFIG_LOOKASIDE,
std::ptr::null_mut::<std::ffi::c_void>(),
READER_LOOKASIDE_SLOT_SIZE,
READER_LOOKASIDE_SLOT_COUNT,
)
}
}
#[cfg(debug_assertions)]
fn read_lookaside_used_hiwtr(connection: &Connection) -> std::os::raw::c_int {
let mut current: std::os::raw::c_int = 0;
let mut hiwtr: std::os::raw::c_int = 0;
unsafe {
rusqlite::ffi::sqlite3_db_status(
connection.handle(),
rusqlite::ffi::SQLITE_DBSTATUS_LOOKASIDE_USED,
&mut current,
&mut hiwtr,
0,
);
}
hiwtr
}
#[cfg(debug_assertions)]
fn read_cache_status(
connection: &Connection,
) -> (std::os::raw::c_int, std::os::raw::c_int, std::os::raw::c_int) {
let mut hit_current: std::os::raw::c_int = 0;
let mut hit_hiwtr: std::os::raw::c_int = 0;
let mut miss_current: std::os::raw::c_int = 0;
let mut miss_hiwtr: std::os::raw::c_int = 0;
let mut used_current: std::os::raw::c_int = 0;
let mut used_hiwtr: std::os::raw::c_int = 0;
unsafe {
rusqlite::ffi::sqlite3_db_status(
connection.handle(),
rusqlite::ffi::SQLITE_DBSTATUS_CACHE_HIT,
&mut hit_current,
&mut hit_hiwtr,
0,
);
rusqlite::ffi::sqlite3_db_status(
connection.handle(),
rusqlite::ffi::SQLITE_DBSTATUS_CACHE_MISS,
&mut miss_current,
&mut miss_hiwtr,
0,
);
rusqlite::ffi::sqlite3_db_status(
connection.handle(),
rusqlite::ffi::SQLITE_DBSTATUS_CACHE_USED,
&mut used_current,
&mut used_hiwtr,
0,
);
}
(hit_current, miss_current, used_current)
}
unsafe extern "C" fn profile_callback_trampoline(
user_data: *mut std::ffi::c_void,
sql: *const std::os::raw::c_char,
nanoseconds: u64,
) {
if user_data.is_null() || sql.is_null() {
return;
}
let ctx = unsafe { &*(user_data.cast::<ProfileContext>()) };
let sql_text = match unsafe { std::ffi::CStr::from_ptr(sql) }.to_str() {
Ok(s) => s,
Err(_) => return,
};
let wall_clock_ms = nanoseconds / 1_000_000;
if ctx.profiling_enabled.load(Ordering::Relaxed) {
let record = lifecycle::ProfileRecord {
wall_clock_ms,
step_count: 0,
cache_delta: 0,
};
ctx.subscribers.dispatch_profile(&record);
}
let threshold = ctx.slow_threshold_ms.load(Ordering::Relaxed);
if wall_clock_ms > threshold {
let signal = lifecycle::SlowStatement { statement: sql_text.to_string(), wall_clock_ms };
ctx.subscribers.dispatch_slow_statement(&signal);
}
}
#[cfg(test)]
mod tests {
use super::{
derive_stable_id, resolve_source_type, Engine, IdSpace, IdSpaceKind, PreparedWrite,
KIND_TO_SOURCE_TYPE_CASE_SQL, ROW_OWNED_PROJECTIONS,
};
use rusqlite::Connection;
use tempfile::TempDir;
#[test]
fn guard_row_owned_registry() {
const NON_PROJECTION_CURSOR_TABLES: &[&str] =
&["canonical_nodes", "canonical_edges", "operational_mutations", "operational_state"];
let dir = TempDir::new().unwrap();
let path = dir.path().join("registry_guard.fathomdb");
Engine::open(&path).expect("open").engine.close().expect("close");
let conn = Connection::open(&path).expect("open sqlite");
let table_names: Vec<String> = conn
.prepare(
"SELECT name FROM sqlite_master
WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name",
)
.expect("prepare")
.query_map([], |row| row.get::<_, String>(0))
.expect("query")
.collect::<rusqlite::Result<Vec<_>>>()
.expect("collect");
assert!(table_names.len() > 5, "sqlite_master introspection returned nothing useful");
let has_write_cursor = |table: &str| -> bool {
conn.prepare(&format!("PRAGMA table_info({table})"))
.and_then(|mut stmt| {
let names = stmt
.query_map([], |row| row.get::<_, String>(1))?
.collect::<rusqlite::Result<Vec<_>>>()?;
Ok(names.iter().any(|n| n == "write_cursor"))
})
.unwrap_or(false)
};
let registered: Vec<&str> = ROW_OWNED_PROJECTIONS.iter().map(|p| p.table).collect();
for table in &table_names {
if !has_write_cursor(table) {
continue;
}
assert!(
registered.contains(&table.as_str())
|| NON_PROJECTION_CURSOR_TABLES.contains(&table.as_str()),
"table `{table}` is keyed by write_cursor but is neither registered in \
ROW_OWNED_PROJECTIONS nor listed as a non-projection source of truth. \
If it is a projection, register it — otherwise erasure will leave its \
rows on disk (the `search_index_v2` defect)."
);
}
for projection in ROW_OWNED_PROJECTIONS {
assert!(
table_names.iter().any(|t| t == projection.table),
"registered projection `{}` does not exist in the schema",
projection.table
);
conn.query_row(
&format!(
"SELECT COUNT(*) FROM {} WHERE {} = 0",
projection.table, projection.cursor_column
),
[],
|row| row.get::<_, u64>(0),
)
.unwrap_or_else(|err| {
panic!(
"registered projection `{}` is not erasable by `{}`: {err}",
projection.table, projection.cursor_column
)
});
}
assert!(
!has_write_cursor("_fathomdb_projection_state"),
"_fathomdb_projection_state gained a write_cursor column — re-decide its ownership \
class before treating it as kind-owned"
);
assert!(
!registered.contains(&"_fathomdb_projection_state"),
"_fathomdb_projection_state is KIND-owned (per-kind enqueue watermark) and must \
never be deleted per-cursor: erasing one row would rewind a whole kind's watermark"
);
}
#[test]
fn slice15d_attribute_projections_registered_and_guard_bites() {
const NON_PROJECTION_CURSOR_TABLES: &[&str] =
&["canonical_nodes", "canonical_edges", "operational_mutations", "operational_state"];
let registered: Vec<&str> = ROW_OWNED_PROJECTIONS.iter().map(|p| p.table).collect();
for table in ["canonical_attributes", "property_search_index"] {
assert!(
registered.contains(&table),
"{table} holds attribute values at rest and MUST be in ROW_OWNED_PROJECTIONS \
so purge/excise_source reach it"
);
}
for hidden in ["canonical_attributes", "property_search_index"] {
let as_if_unregistered: Vec<&str> =
registered.iter().copied().filter(|t| *t != hidden).collect();
let accepted = as_if_unregistered.contains(&hidden)
|| NON_PROJECTION_CURSOR_TABLES.contains(&hidden);
assert!(
!accepted,
"if {hidden} were unregistered the guard would still (incorrectly) accept it — \
the guard does not actually bite"
);
}
}
#[test]
fn derive_stable_id_id_space_contract() {
assert_eq!(derive_stable_id(Some("alice-1"), "any body"), IdSpace::logical("alice-1"));
assert_eq!(
derive_stable_id(Some("alice-1"), "a different body"),
IdSpace::logical("alice-1")
);
assert_eq!(derive_stable_id(Some("alice-1"), "any body").to_prefixed(), "l:alice-1");
let h1 = derive_stable_id(None, "stable body text");
let h2 = derive_stable_id(None, "stable body text");
assert_eq!(h1, h2, "content-hash is deterministic");
assert_eq!(h1.space, IdSpaceKind::Content);
let h1s = h1.to_prefixed();
assert!(h1s.starts_with("h:"));
assert_eq!(h1s.len(), 2 + 64, "h: + sha256 hex");
assert!(h1s["h:".len()..].chars().all(|c| c.is_ascii_hexdigit()));
assert_eq!(derive_stable_id(Some(""), "stable body text"), h1);
assert_ne!(derive_stable_id(None, "body A"), derive_stable_id(None, "body B"));
}
#[test]
fn id_space_parse_format_round_trip() {
let cases = [
IdSpace::logical("alice-1"),
IdSpace::content("a".repeat(64)),
IdSpace::passage("7"),
IdSpace::logical("l:weird:value"), ];
for id in cases {
assert_eq!(IdSpace::parse(&id.to_prefixed()), Some(id.clone()), "round-trip {id:?}");
}
assert_eq!(IdSpace::logical("x").to_prefixed(), "l:x");
assert_eq!(IdSpace::content("y").to_prefixed(), "h:y");
assert_eq!(IdSpace::passage("3").to_prefixed(), "p:3");
assert_eq!(IdSpace::parse("untagged"), None);
}
#[test]
fn resolve_source_type_drift_check() {
let kinds = ["email", "article", "paper", "meeting", "note", "todo", "doc"];
let want: &[(&str, &str)] = &[
("email", "email"),
("article", "article"),
("paper", "paper"),
("meeting", "meeting"),
("note", "note"),
("todo", "todo"),
("doc", "article"),
];
for (kind, expected) in want {
let got = resolve_source_type(kind).unwrap_or_else(|_| {
panic!("resolve_source_type({kind}) returned Err; want Ok({expected})")
});
assert_eq!(got, *expected, "Rust helper drift for kind={kind}");
}
assert!(
resolve_source_type("banana").is_err(),
"unknown kind must surface as writer error"
);
let conn = Connection::open_in_memory().expect("in-memory sqlite");
conn.execute_batch("CREATE TABLE s(kind TEXT NOT NULL)").expect("create s");
for kind in &kinds {
conn.execute("INSERT INTO s(kind) VALUES (?1)", [kind]).expect("insert kind");
}
let sql = format!("SELECT s.kind, {KIND_TO_SOURCE_TYPE_CASE_SQL} FROM s");
let mut stmt = conn.prepare(&sql).expect("prepare CASE");
let rows: Vec<(String, String)> = stmt
.query_map([], |row| Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?)))
.expect("query")
.map(|r| r.expect("row"))
.collect();
assert_eq!(rows.len(), kinds.len(), "row count drift");
for (kind, sql_result) in &rows {
let rust_result = resolve_source_type(kind).expect("known kind");
assert_eq!(
sql_result, rust_result,
"SQL CASE vs Rust helper drift for kind={kind}: SQL={sql_result}, Rust={rust_result}"
);
}
}
#[test]
fn write_advances_cursor() {
let dir = TempDir::new().unwrap();
let opened = Engine::open(dir.path().join("rewrite.sqlite")).expect("engine should open");
let receipt = opened
.engine
.write(&[PreparedWrite::Node {
kind: "doc".to_string(),
body: "hello".to_string(),
source_id: crate::SourceId::new("test:fixture").expect("test source id"),
logical_id: None,
state: crate::InitialState::Active,
reason: None,
valid_from: None,
valid_until: None,
}])
.expect("write should succeed");
assert_eq!(receipt.cursor, 1);
}
}