use super::HybridSearchArgs;
use crate::errors::AppError;
use crate::storage::memories;
use rusqlite::Connection;
pub(super) use crate::query_embedding::QueryEmbedding;
pub(super) type FtsCandidates = (Vec<memories::MemoryRow>, bool, Option<String>, bool);
pub(super) fn resolve_query_embedding(
args: &HybridSearchArgs,
models_dir: &std::path::Path,
backends: crate::cli::BackendChoice,
) -> QueryEmbedding {
crate::query_embedding::resolve_query_embedding(
args.fallback_fts_only,
models_dir,
&args.query,
backends,
"hybrid_search",
)
}
pub(super) fn vector_candidates(
conn: &Connection,
embedding: Option<&Vec<f32>>,
namespaces: &[String],
memory_type: Option<&str>,
k: usize,
) -> Result<Vec<(i64, f32)>, AppError> {
match embedding {
Some(emb) => memories::knn_search(conn, emb, namespaces, memory_type, k * 2),
None => Ok(Vec::new()),
}
}
pub(super) fn fts_candidates(
conn: &Connection,
args: &HybridSearchArgs,
namespace: &str,
memory_type: Option<&str>,
) -> FtsCandidates {
if args.weight_fts == 0.0 {
return (vec![], false, None, false);
}
match memories::fts_search(conn, &args.query, namespace, memory_type, args.k * 2) {
Ok(r) => (r, false, None, false),
Err(e) => {
let err_msg = e.to_string();
let is_malformed = err_msg.contains("malformed") || err_msg.contains("corrupt");
if is_malformed {
tracing::warn!(target: "hybrid_search", "FTS5 index corrupted, attempting auto-rebuild");
if conn
.execute_batch("INSERT INTO fts_memories(fts_memories) VALUES('rebuild');")
.is_ok()
{
match memories::fts_search(
conn,
&args.query,
namespace,
memory_type,
args.k * 2,
) {
Ok(r) => (r, false, None, true),
Err(e2) => {
tracing::error!(target: "hybrid_search", error = %e2, "FTS5 auto-rebuild failed to recover");
(vec![], true, Some(e2.to_string()), true)
}
}
} else {
(vec![], true, Some(err_msg), false)
}
} else {
tracing::warn!(target: "hybrid_search", error = %e, "FTS5 query failed, falling back to vec-only");
(vec![], true, Some(err_msg), false)
}
}
}
}