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 existing = find_relationship(conn, source_id, target_id, relation)?;
199 if let Some(row) = existing {
200 if (row.weight - weight).abs() > f64::EPSILON {
201 conn.execute(
202 "UPDATE relationships SET weight = ?1 WHERE id = ?2",
203 params![weight, row.id],
204 )?;
205 }
206 return Ok((row.id, false));
207 }
208 conn.execute(
209 "INSERT INTO relationships (namespace, source_id, target_id, relation, weight, description)
210 VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
211 params![
212 namespace,
213 source_id,
214 target_id,
215 relation,
216 weight,
217 description
218 ],
219 )?;
220 let id: i64 = conn.query_row(
221 "SELECT id FROM relationships WHERE source_id = ?1 AND target_id = ?2 AND relation = ?3",
222 params![source_id, target_id, relation],
223 |r| r.get(0),
224 )?;
225 Ok((id, true))
226}
227
228pub fn delete_relationship_by_id(conn: &Connection, relationship_id: i64) -> Result<(), AppError> {
234 conn.execute(
235 "DELETE FROM memory_relationships WHERE relationship_id = ?1",
236 params![relationship_id],
237 )?;
238 conn.execute(
239 "DELETE FROM relationships WHERE id = ?1",
240 params![relationship_id],
241 )?;
242 Ok(())
243}
244
245pub fn recalculate_degree(conn: &Connection, entity_id: i64) -> Result<(), AppError> {
251 conn.execute(
252 "UPDATE entities
253 SET degree = (SELECT COUNT(*) FROM relationships
254 WHERE source_id = entities.id OR target_id = entities.id)
255 WHERE id = ?1",
256 params![entity_id],
257 )?;
258 Ok(())
259}
260
261pub fn find_orphan_entity_ids(
267 conn: &Connection,
268 namespace: Option<&str>,
269) -> Result<Vec<i64>, AppError> {
270 if let Some(ns) = namespace {
271 let mut stmt = conn.prepare_cached(
272 "SELECT e.id FROM entities e
273 WHERE e.namespace = ?1
274 AND NOT EXISTS (SELECT 1 FROM memory_entities me WHERE me.entity_id = e.id)
275 AND NOT EXISTS (
276 SELECT 1 FROM relationships r
277 WHERE r.source_id = e.id OR r.target_id = e.id
278 )",
279 )?;
280 let ids = stmt
281 .query_map(params![ns], |r| r.get::<_, i64>(0))?
282 .collect::<Result<Vec<_>, _>>()?;
283 Ok(ids)
284 } else {
285 let mut stmt = conn.prepare_cached(
286 "SELECT e.id FROM entities e
287 WHERE NOT EXISTS (SELECT 1 FROM memory_entities me WHERE me.entity_id = e.id)
288 AND NOT EXISTS (
289 SELECT 1 FROM relationships r
290 WHERE r.source_id = e.id OR r.target_id = e.id
291 )",
292 )?;
293 let ids = stmt
294 .query_map([], |r| r.get::<_, i64>(0))?
295 .collect::<Result<Vec<_>, _>>()?;
296 Ok(ids)
297 }
298}
299
300pub fn delete_entities_by_ids(conn: &Connection, entity_ids: &[i64]) -> Result<usize, AppError> {
306 if entity_ids.is_empty() {
307 return Ok(0);
308 }
309 let mut removed = 0usize;
310 for id in entity_ids {
311 let _ = conn.execute("DELETE FROM vec_entities WHERE entity_id = ?1", params![id]);
313 let affected = conn.execute("DELETE FROM entities WHERE id = ?1", params![id])?;
314 removed += affected;
315 }
316 Ok(removed)
317}
318
319pub fn count_relationships_by_relation(
328 conn: &Connection,
329 namespace: &str,
330 relation: &str,
331) -> Result<usize, AppError> {
332 let count: i64 = conn.query_row(
333 "SELECT COUNT(*) FROM relationships WHERE namespace = ?1 AND relation = ?2",
334 params![namespace, relation],
335 |r| r.get(0),
336 )?;
337 Ok(count as usize)
338}
339
340pub fn list_entity_names_by_relation(
349 conn: &Connection,
350 namespace: &str,
351 relation: &str,
352) -> Result<Vec<String>, AppError> {
353 let mut stmt = conn.prepare_cached(
354 "SELECT DISTINCT e.name FROM entities e
355 INNER JOIN relationships r ON (e.id = r.source_id OR e.id = r.target_id)
356 WHERE r.namespace = ?1 AND r.relation = ?2
357 ORDER BY e.name",
358 )?;
359 let names: Vec<String> = stmt
360 .query_map(params![namespace, relation], |row| row.get(0))?
361 .collect::<Result<Vec<_>, _>>()?;
362 Ok(names)
363}
364
365pub fn delete_relationships_by_relation(
376 conn: &Connection,
377 namespace: &str,
378 relation: &str,
379) -> Result<(usize, Vec<i64>), AppError> {
380 let mut stmt = conn.prepare_cached(
382 "SELECT DISTINCT source_id FROM relationships WHERE namespace = ?1 AND relation = ?2
383 UNION
384 SELECT DISTINCT target_id FROM relationships WHERE namespace = ?1 AND relation = ?2",
385 )?;
386 let entity_ids: Vec<i64> = stmt
387 .query_map(params![namespace, relation], |r| r.get::<_, i64>(0))?
388 .collect::<Result<Vec<_>, _>>()?;
389
390 let mut id_stmt =
392 conn.prepare_cached("SELECT id FROM relationships WHERE namespace = ?1 AND relation = ?2")?;
393 let rel_ids: Vec<i64> = id_stmt
394 .query_map(params![namespace, relation], |r| r.get::<_, i64>(0))?
395 .collect::<Result<Vec<_>, _>>()?;
396
397 let mut total_deleted: usize = 0;
399 for chunk in rel_ids.chunks(1000) {
400 for &rel_id in chunk {
401 conn.execute(
402 "DELETE FROM memory_relationships WHERE relationship_id = ?1",
403 params![rel_id],
404 )?;
405 let affected =
406 conn.execute("DELETE FROM relationships WHERE id = ?1", params![rel_id])?;
407 total_deleted += affected;
408 }
409 }
410
411 for &eid in &entity_ids {
413 recalculate_degree(conn, eid)?;
414 }
415
416 Ok((total_deleted, entity_ids))
417}