use rusqlite::Connection;
use crate::data::store::search_endpoints;
use crate::services::embedding_service::embed;
fn warn_if_embeddings_incomplete(conn: &Connection) -> bool {
let counts: rusqlite::Result<(i64, i64)> = conn.query_row(
"SELECT (SELECT COUNT(*) FROM endpoints), (SELECT COUNT(*) FROM semantic_endpoints)",
[],
|row| Ok((row.get(0)?, row.get(1)?)),
);
let incomplete = matches!(&counts, Ok((endpoints_count, semantic_count)) if semantic_count < endpoints_count);
if let Ok((endpoints_count, semantic_count)) = counts
&& incomplete
{
tracing::warn!(
endpoints_count,
semantic_count,
"semantic_endpoints is missing {} row(s) compared to endpoints — search results \
may be incomplete; re-run populate-embeddings for this store",
endpoints_count - semantic_count
);
}
incomplete
}
pub fn search_operations(
conn: &Connection,
query: &str,
limit: usize,
) -> anyhow::Result<serde_json::Value> {
warn_if_embeddings_incomplete(conn);
let query_embedding = embed(query)?;
let results = search_endpoints(conn, &query_embedding, limit)?;
Ok(serde_json::to_value(results)?)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn incomplete_embedding_catalog_diagnostic_handles_every_count_shape() {
let conn = Connection::open_in_memory().unwrap();
conn.execute_batch(
"CREATE TABLE endpoints (operation_id TEXT); \
CREATE TABLE semantic_endpoints (operation_id TEXT); \
INSERT INTO endpoints VALUES ('missing');",
)
.unwrap();
assert!(warn_if_embeddings_incomplete(&conn));
conn.execute("INSERT INTO semantic_endpoints VALUES ('missing')", [])
.unwrap();
assert!(!warn_if_embeddings_incomplete(&conn));
}
}