use super::walk::{GraphWalk, SqlNeighbors};
use crate::errors::AppError;
use rusqlite::{params, Connection};
use std::collections::HashSet;
fn seed_entities_of(conn: &Connection, seed_memory_ids: &[i64]) -> Result<Vec<i64>, AppError> {
let mut seed_entities: Vec<i64> = Vec::with_capacity(seed_memory_ids.len());
for &mem_id in seed_memory_ids {
let mut stmt =
conn.prepare_cached("SELECT entity_id FROM memory_entities WHERE memory_id = ?1")?;
let ids: Vec<i64> = stmt
.query_map(params![mem_id], |r| r.get(0))?
.filter_map(std::result::Result::ok)
.collect();
seed_entities.extend(ids);
}
seed_entities.sort_unstable();
seed_entities.dedup();
Ok(seed_entities)
}
pub fn traverse_from_memories_with_hops(
conn: &Connection,
seed_memory_ids: &[i64],
namespace: &str,
min_weight: f64,
max_hops: u32,
) -> Result<Vec<(i64, u32)>, AppError> {
traverse_from_memories_with_hops_capped(
conn,
seed_memory_ids,
namespace,
min_weight,
max_hops,
None,
)
}
pub fn traverse_from_memories_with_hops_capped(
conn: &Connection,
seed_memory_ids: &[i64],
namespace: &str,
min_weight: f64,
max_hops: u32,
max_neighbors_per_hop: Option<usize>,
) -> Result<Vec<(i64, u32)>, AppError> {
if seed_memory_ids.is_empty() || max_hops == 0 {
return Ok(vec![]);
}
let seed_entities = seed_entities_of(conn, seed_memory_ids)?;
if seed_entities.is_empty() {
return Ok(vec![]);
}
let walk = GraphWalk::directed(min_weight, max_hops).with_neighbor_cap(max_neighbors_per_hop);
let outcome = walk.run(&SqlNeighbors::new(conn, namespace), &seed_entities)?;
let seed_entity_set: HashSet<i64> = seed_entities.iter().copied().collect();
let mut discovered: Vec<(i64, u32)> = outcome
.depth
.into_iter()
.filter(|(id, _)| !seed_entity_set.contains(id))
.collect();
discovered.sort_unstable_by_key(|&(id, hop)| (hop, id));
let seed_set: HashSet<i64> = seed_memory_ids.iter().copied().collect();
let mut result: Vec<(i64, u32)> = Vec::with_capacity(discovered.len());
let mut seen_memories: HashSet<i64> = HashSet::with_capacity(discovered.len());
for (entity_id, hop) in discovered {
let mut stmt = conn.prepare_cached(
"SELECT DISTINCT me.memory_id
FROM memory_entities me
JOIN memories m ON m.id = me.memory_id
WHERE me.entity_id = ?1 AND m.deleted_at IS NULL",
)?;
let mem_ids: Vec<i64> = stmt
.query_map(params![entity_id], |r| r.get(0))?
.filter_map(std::result::Result::ok)
.filter(|id| !seed_set.contains(id) && !seen_memories.contains(id))
.collect();
for mem_id in mem_ids {
seen_memories.insert(mem_id);
result.push((mem_id, hop));
}
}
result.sort_unstable_by_key(|&(id, _)| id);
Ok(result)
}