fn vector_search(&self, query: &VectorQuery) -> Result<Vec<VectorSearchResult>> {
let conn = self.lock_conn()?;
// Brute-force cosine scan: load all rows with embeddings, compute similarity
let mut stmt = conn.prepare(
"SELECT id, content, tags, priority, source, created_at, updated_at, embedding, embedding_model
FROM memory_entries
WHERE embedding IS NOT NULL"
).map_err(|e| OneClawError::Memory(format!("Prepare vector search: {}", e)))?;
let query_values = &query.embedding.values;
let mut scored: Vec<VectorSearchResult> = Vec::new();
let rows = stmt.query_map([], |row| {
let emb_bytes: Vec<u8> = row.get(7)?;
let emb_model: String = row.get(8)?;
Ok((RawEntry { /* ... */ }, emb_bytes, emb_model))
}).map_err(|e| OneClawError::Memory(format!("Vector search query: {}", e)))?;
for row in rows {
let (raw, emb_bytes, emb_model) = row.map_err(|e| OneClawError::Memory(format!("Row read: {}", e)))?;
if let Some(stored_emb) = Embedding::from_bytes(&emb_bytes, emb_model) {
let sim = cosine_similarity(query_values, &stored_emb.values);
if sim >= query.min_similarity {
scored.push(VectorSearchResult {
entry: raw.into_memory_entry()?,
similarity: sim,
});
}
}
}
// Sort by similarity descending, take limit
scored.sort_by(|a, b| b.similarity.partial_cmp(&a.similarity).unwrap_or(std::cmp::Ordering::Equal));
scored.truncate(query.limit);
Ok(scored)
}