1use crate::errors::AppError;
9use crate::parsers::normalize_entity_name;
10use rusqlite::{params, Connection};
11use serde::Serialize;
12pub fn link_memory_entity(
18 conn: &Connection,
19 memory_id: i64,
20 entity_id: i64,
21) -> Result<(), AppError> {
22 conn.execute(
23 "INSERT OR IGNORE INTO memory_entities (memory_id, entity_id) VALUES (?1, ?2)",
24 params![memory_id, entity_id],
25 )?;
26 Ok(())
27}
28
29pub fn link_memory_relationship(
35 conn: &Connection,
36 memory_id: i64,
37 rel_id: i64,
38) -> Result<(), AppError> {
39 conn.execute(
40 "INSERT OR IGNORE INTO memory_relationships (memory_id, relationship_id) VALUES (?1, ?2)",
41 params![memory_id, rel_id],
42 )?;
43 Ok(())
44}
45
46pub fn unlink_memory_entity(
56 conn: &Connection,
57 memory_id: i64,
58 entity_id: i64,
59) -> Result<u64, AppError> {
60 let affected = conn.execute(
61 "DELETE FROM memory_entities WHERE memory_id = ?1 AND entity_id = ?2",
62 params![memory_id, entity_id],
63 )?;
64 Ok(affected as u64)
65}
66
67pub fn clear_memory_graph_bindings(
77 conn: &Connection,
78 memory_id: i64,
79) -> Result<(u64, u64), AppError> {
80 let entities_removed = conn.execute(
81 "DELETE FROM memory_entities WHERE memory_id = ?1",
82 params![memory_id],
83 )? as u64;
84 let rels_removed = conn.execute(
85 "DELETE FROM memory_relationships WHERE memory_id = ?1",
86 params![memory_id],
87 )? as u64;
88 Ok((entities_removed, rels_removed))
89}
90
91pub fn increment_degree(conn: &Connection, entity_id: i64) -> Result<(), AppError> {
97 conn.execute(
98 "UPDATE entities SET degree = degree + 1 WHERE id = ?1",
99 params![entity_id],
100 )?;
101 Ok(())
102}
103
104pub fn find_entity_id(
110 conn: &Connection,
111 namespace: &str,
112 name: &str,
113) -> Result<Option<i64>, AppError> {
114 let name = normalize_entity_name(name);
120 let mut stmt =
121 conn.prepare_cached("SELECT id FROM entities WHERE namespace = ?1 AND name = ?2")?;
122 match stmt.query_row(params![namespace, &name], |r| r.get::<_, i64>(0)) {
123 Ok(id) => Ok(Some(id)),
124 Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
125 Err(e) => Err(AppError::Database(e)),
126 }
127}
128
129#[derive(Debug, Serialize)]
131pub struct RelationshipRow {
132 pub id: i64,
134 pub namespace: String,
136 pub source_id: i64,
138 pub target_id: i64,
140 pub relation: String,
142 pub weight: f64,
144 pub description: Option<String>,
146}
147
148pub fn find_relationship(
154 conn: &Connection,
155 source_id: i64,
156 target_id: i64,
157 relation: &str,
158) -> Result<Option<RelationshipRow>, AppError> {
159 let mut stmt = conn.prepare_cached(
160 "SELECT id, namespace, source_id, target_id, relation, weight, description
161 FROM relationships
162 WHERE source_id = ?1 AND target_id = ?2 AND relation = ?3",
163 )?;
164 match stmt.query_row(params![source_id, target_id, relation], |r| {
165 Ok(RelationshipRow {
166 id: r.get(0)?,
167 namespace: r.get(1)?,
168 source_id: r.get(2)?,
169 target_id: r.get(3)?,
170 relation: r.get(4)?,
171 weight: r.get(5)?,
172 description: r.get(6)?,
173 })
174 }) {
175 Ok(row) => Ok(Some(row)),
176 Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
177 Err(e) => Err(AppError::Database(e)),
178 }
179}
180
181pub fn create_or_fetch_relationship(
189 conn: &Connection,
190 namespace: &str,
191 source_id: i64,
192 target_id: i64,
193 relation: &str,
194 weight: f64,
195 description: Option<&str>,
196) -> Result<(i64, bool), AppError> {
197 let relation = &crate::parsers::map_to_canonical_relation(relation);
209 let existing = find_relationship(conn, source_id, target_id, relation)?;
211 if let Some(row) = existing {
212 if (row.weight - weight).abs() > f64::EPSILON {
213 conn.execute(
214 "UPDATE relationships SET weight = ?1 WHERE id = ?2",
215 params![weight, row.id],
216 )?;
217 }
218 return Ok((row.id, false));
219 }
220 conn.execute(
221 "INSERT INTO relationships (namespace, source_id, target_id, relation, weight, description)
222 VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
223 params![
224 namespace,
225 source_id,
226 target_id,
227 relation,
228 weight,
229 description
230 ],
231 )?;
232 let id: i64 = conn.query_row(
233 "SELECT id FROM relationships WHERE source_id = ?1 AND target_id = ?2 AND relation = ?3",
234 params![source_id, target_id, relation],
235 |r| r.get(0),
236 )?;
237 Ok((id, true))
238}
239
240pub fn delete_relationship_by_id(conn: &Connection, relationship_id: i64) -> Result<(), AppError> {
246 conn.execute(
247 "DELETE FROM memory_relationships WHERE relationship_id = ?1",
248 params![relationship_id],
249 )?;
250 conn.execute(
251 "DELETE FROM relationships WHERE id = ?1",
252 params![relationship_id],
253 )?;
254 Ok(())
255}
256
257pub fn recalculate_degree(conn: &Connection, entity_id: i64) -> Result<(), AppError> {
263 conn.execute(
264 "UPDATE entities
265 SET degree = (SELECT COUNT(*) FROM relationships
266 WHERE source_id = entities.id OR target_id = entities.id)
267 WHERE id = ?1",
268 params![entity_id],
269 )?;
270 Ok(())
271}
272
273pub fn find_orphan_entity_ids(
279 conn: &Connection,
280 namespace: Option<&str>,
281) -> Result<Vec<i64>, AppError> {
282 if let Some(ns) = namespace {
283 let mut stmt = conn.prepare_cached(
284 "SELECT e.id FROM entities e
285 WHERE e.namespace = ?1
286 AND NOT EXISTS (SELECT 1 FROM memory_entities me WHERE me.entity_id = e.id)
287 AND NOT EXISTS (
288 SELECT 1 FROM relationships r
289 WHERE r.source_id = e.id OR r.target_id = e.id
290 )",
291 )?;
292 let ids = stmt
293 .query_map(params![ns], |r| r.get::<_, i64>(0))?
294 .collect::<Result<Vec<_>, _>>()?;
295 Ok(ids)
296 } else {
297 let mut stmt = conn.prepare_cached(
298 "SELECT e.id FROM entities e
299 WHERE NOT EXISTS (SELECT 1 FROM memory_entities me WHERE me.entity_id = e.id)
300 AND NOT EXISTS (
301 SELECT 1 FROM relationships r
302 WHERE r.source_id = e.id OR r.target_id = e.id
303 )",
304 )?;
305 let ids = stmt
306 .query_map([], |r| r.get::<_, i64>(0))?
307 .collect::<Result<Vec<_>, _>>()?;
308 Ok(ids)
309 }
310}
311
312pub fn find_dangling_relationship_ids(
332 conn: &Connection,
333 namespace: Option<&str>,
334) -> Result<Vec<i64>, AppError> {
335 let mut stmt = conn.prepare_cached(
336 "SELECT r.id FROM relationships r
337 WHERE (?1 IS NULL OR r.namespace = ?1)
338 AND (NOT EXISTS (SELECT 1 FROM entities e WHERE e.id = r.source_id)
339 OR NOT EXISTS (SELECT 1 FROM entities e WHERE e.id = r.target_id))",
340 )?;
341 let ids = stmt
342 .query_map(params![namespace], |r| r.get::<_, i64>(0))?
343 .collect::<Result<Vec<_>, _>>()?;
344 Ok(ids)
345}
346
347pub fn delete_relationships_by_ids(
353 conn: &Connection,
354 relationship_ids: &[i64],
355) -> Result<usize, AppError> {
356 let mut removed = 0usize;
357 for id in relationship_ids {
358 removed += conn.execute("DELETE FROM relationships WHERE id = ?1", params![id])?;
359 }
360 Ok(removed)
361}
362
363pub fn delete_entities_by_ids(conn: &Connection, entity_ids: &[i64]) -> Result<usize, AppError> {
369 if entity_ids.is_empty() {
370 return Ok(0);
371 }
372 let mut removed = 0usize;
373 for id in entity_ids {
374 let _ = conn.execute("DELETE FROM vec_entities WHERE entity_id = ?1", params![id]);
376 let affected = conn.execute("DELETE FROM entities WHERE id = ?1", params![id])?;
377 removed += affected;
378 }
379 Ok(removed)
380}
381
382pub fn count_relationships_by_relation(
391 conn: &Connection,
392 namespace: &str,
393 relation: &str,
394) -> Result<usize, AppError> {
395 let count: i64 = conn.query_row(
396 "SELECT COUNT(*) FROM relationships WHERE namespace = ?1 AND relation = ?2",
397 params![namespace, relation],
398 |r| r.get(0),
399 )?;
400 Ok(count as usize)
401}
402
403pub fn list_entity_names_by_relation(
412 conn: &Connection,
413 namespace: &str,
414 relation: &str,
415) -> Result<Vec<String>, AppError> {
416 let mut stmt = conn.prepare_cached(
417 "SELECT DISTINCT e.name FROM entities e
418 INNER JOIN relationships r ON (e.id = r.source_id OR e.id = r.target_id)
419 WHERE r.namespace = ?1 AND r.relation = ?2
420 ORDER BY e.name",
421 )?;
422 let names: Vec<String> = stmt
423 .query_map(params![namespace, relation], |row| row.get(0))?
424 .collect::<Result<Vec<_>, _>>()?;
425 Ok(names)
426}
427
428pub fn delete_relationships_by_relation(
439 conn: &Connection,
440 namespace: &str,
441 relation: &str,
442) -> Result<(usize, Vec<i64>), AppError> {
443 let mut stmt = conn.prepare_cached(
445 "SELECT DISTINCT source_id FROM relationships WHERE namespace = ?1 AND relation = ?2
446 UNION
447 SELECT DISTINCT target_id FROM relationships WHERE namespace = ?1 AND relation = ?2",
448 )?;
449 let entity_ids: Vec<i64> = stmt
450 .query_map(params![namespace, relation], |r| r.get::<_, i64>(0))?
451 .collect::<Result<Vec<_>, _>>()?;
452
453 let mut id_stmt =
455 conn.prepare_cached("SELECT id FROM relationships WHERE namespace = ?1 AND relation = ?2")?;
456 let rel_ids: Vec<i64> = id_stmt
457 .query_map(params![namespace, relation], |r| r.get::<_, i64>(0))?
458 .collect::<Result<Vec<_>, _>>()?;
459
460 let mut total_deleted: usize = 0;
462 for chunk in rel_ids.chunks(1000) {
463 for &rel_id in chunk {
464 conn.execute(
465 "DELETE FROM memory_relationships WHERE relationship_id = ?1",
466 params![rel_id],
467 )?;
468 let affected =
469 conn.execute("DELETE FROM relationships WHERE id = ?1", params![rel_id])?;
470 total_deleted += affected;
471 }
472 }
473
474 for &eid in &entity_ids {
476 recalculate_degree(conn, eid)?;
477 }
478
479 Ok((total_deleted, entity_ids))
480}