1use crate::graph_persistence::{edge_from_row, extract_edge_tuple};
4use crate::{MemoryRow, Storage};
5use codemem_core::{
6 CodememError, ConsolidationLogEntry, Edge, GraphNode, MemoryNode, NodeKind, Session,
7 StorageBackend, StorageStats,
8};
9use rusqlite::params;
10use std::collections::HashMap;
11
12macro_rules! delegate_storage {
14 ($method:ident(&self) -> $ret:ty) => {
16 fn $method(&self) -> $ret {
17 Storage::$method(self)
18 }
19 };
20 ($method:ident(&self, $a1:ident: $t1:ty) -> $ret:ty) => {
22 fn $method(&self, $a1: $t1) -> $ret {
23 Storage::$method(self, $a1)
24 }
25 };
26 ($method:ident(&self, $a1:ident: $t1:ty, $a2:ident: $t2:ty) -> $ret:ty) => {
28 fn $method(&self, $a1: $t1, $a2: $t2) -> $ret {
29 Storage::$method(self, $a1, $a2)
30 }
31 };
32 ($method:ident(&self, $a1:ident: $t1:ty, $a2:ident: $t2:ty, $a3:ident: $t3:ty) -> $ret:ty) => {
34 fn $method(&self, $a1: $t1, $a2: $t2, $a3: $t3) -> $ret {
35 Storage::$method(self, $a1, $a2, $a3)
36 }
37 };
38 ($method:ident(&self, $a1:ident: $t1:ty, $a2:ident: $t2:ty, $a3:ident: $t3:ty, $a4:ident: $t4:ty, $a5:ident: $t5:ty) -> $ret:ty) => {
40 fn $method(&self, $a1: $t1, $a2: $t2, $a3: $t3, $a4: $t4, $a5: $t5) -> $ret {
41 Storage::$method(self, $a1, $a2, $a3, $a4, $a5)
42 }
43 };
44}
45
46impl StorageBackend for Storage {
47 delegate_storage!(insert_memory(&self, memory: &MemoryNode) -> Result<(), CodememError>);
50 delegate_storage!(get_memory(&self, id: &str) -> Result<Option<MemoryNode>, CodememError>);
51 delegate_storage!(get_memory_no_touch(&self, id: &str) -> Result<Option<MemoryNode>, CodememError>);
52 delegate_storage!(update_memory(&self, id: &str, content: &str, importance: Option<f64>) -> Result<(), CodememError>);
53 delegate_storage!(delete_memory(&self, id: &str) -> Result<bool, CodememError>);
54
55 fn delete_memory_cascade(&self, id: &str) -> Result<bool, CodememError> {
57 Storage::delete_memory_cascade(self, id)
60 }
61
62 delegate_storage!(list_memory_ids(&self) -> Result<Vec<String>, CodememError>);
63 delegate_storage!(list_memory_ids_for_namespace(&self, namespace: &str) -> Result<Vec<String>, CodememError>);
64 delegate_storage!(list_namespaces(&self) -> Result<Vec<String>, CodememError>);
65 delegate_storage!(memory_count(&self) -> Result<usize, CodememError>);
66
67 fn get_memories_batch(&self, ids: &[&str]) -> Result<Vec<MemoryNode>, CodememError> {
68 if ids.is_empty() {
69 return Ok(Vec::new());
70 }
71 let conn = self.conn()?;
72
73 let placeholders: Vec<String> = (1..=ids.len()).map(|i| format!("?{i}")).collect();
74 let sql = format!(
75 "SELECT id, content, memory_type, importance, confidence, access_count, content_hash, tags, metadata, namespace, created_at, updated_at, last_accessed_at FROM memories WHERE id IN ({})",
76 placeholders.join(",")
77 );
78
79 let mut stmt = conn
80 .prepare(&sql)
81 .map_err(|e| CodememError::Storage(e.to_string()))?;
82
83 let params: Vec<&dyn rusqlite::types::ToSql> = ids
84 .iter()
85 .map(|id| id as &dyn rusqlite::types::ToSql)
86 .collect();
87
88 let rows = stmt
89 .query_map(params.as_slice(), |row| {
90 Ok(MemoryRow {
91 id: row.get(0)?,
92 content: row.get(1)?,
93 memory_type: row.get(2)?,
94 importance: row.get(3)?,
95 confidence: row.get(4)?,
96 access_count: row.get(5)?,
97 content_hash: row.get(6)?,
98 tags: row.get(7)?,
99 metadata: row.get(8)?,
100 namespace: row.get(9)?,
101 created_at: row.get(10)?,
102 updated_at: row.get(11)?,
103 last_accessed_at: row.get(12)?,
104 })
105 })
106 .map_err(|e| CodememError::Storage(e.to_string()))?;
107
108 let mut memories = Vec::new();
109 for row in rows {
110 let row = row.map_err(|e| CodememError::Storage(e.to_string()))?;
111 memories.push(row.into_memory_node()?);
112 }
113 Ok(memories)
114 }
115
116 delegate_storage!(store_embedding(&self, memory_id: &str, embedding: &[f32]) -> Result<(), CodememError>);
119 delegate_storage!(get_embedding(&self, memory_id: &str) -> Result<Option<Vec<f32>>, CodememError>);
120
121 fn delete_embedding(&self, memory_id: &str) -> Result<bool, CodememError> {
122 let conn = self.conn()?;
123 let deleted = conn
124 .execute(
125 "DELETE FROM memory_embeddings WHERE memory_id = ?1",
126 [memory_id],
127 )
128 .map_err(|e| CodememError::Storage(e.to_string()))?;
129 Ok(deleted > 0)
130 }
131
132 fn list_all_embeddings(&self) -> Result<Vec<(String, Vec<f32>)>, CodememError> {
133 let conn = self.conn()?;
134 let mut stmt = conn
135 .prepare("SELECT memory_id, embedding FROM memory_embeddings")
136 .map_err(|e| CodememError::Storage(e.to_string()))?;
137 let rows = stmt
138 .query_map([], |row| {
139 let id: String = row.get(0)?;
140 let blob: Vec<u8> = row.get(1)?;
141 Ok((id, blob))
142 })
143 .map_err(|e| CodememError::Storage(e.to_string()))?;
144 let mut result = Vec::new();
145 for row in rows {
146 let (id, blob) = row.map_err(|e| CodememError::Storage(e.to_string()))?;
147 let floats: Vec<f32> = blob
148 .chunks_exact(4)
149 .map(|chunk| f32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
150 .collect();
151 result.push((id, floats));
152 }
153 Ok(result)
154 }
155
156 delegate_storage!(insert_graph_node(&self, node: &GraphNode) -> Result<(), CodememError>);
159 delegate_storage!(get_graph_node(&self, id: &str) -> Result<Option<GraphNode>, CodememError>);
160 delegate_storage!(delete_graph_node(&self, id: &str) -> Result<bool, CodememError>);
161 delegate_storage!(all_graph_nodes(&self) -> Result<Vec<GraphNode>, CodememError>);
162 delegate_storage!(insert_graph_edge(&self, edge: &Edge) -> Result<(), CodememError>);
163 delegate_storage!(get_edges_for_node(&self, node_id: &str) -> Result<Vec<Edge>, CodememError>);
164 delegate_storage!(all_graph_edges(&self) -> Result<Vec<Edge>, CodememError>);
165 delegate_storage!(delete_graph_edges_for_node(&self, node_id: &str) -> Result<usize, CodememError>);
166 delegate_storage!(delete_graph_nodes_by_prefix(&self, prefix: &str) -> Result<usize, CodememError>);
167
168 delegate_storage!(start_session(&self, id: &str, namespace: Option<&str>) -> Result<(), CodememError>);
171 delegate_storage!(end_session(&self, id: &str, summary: Option<&str>) -> Result<(), CodememError>);
172
173 fn list_sessions(
174 &self,
175 namespace: Option<&str>,
176 limit: usize,
177 ) -> Result<Vec<Session>, CodememError> {
178 self.list_sessions_with_limit(namespace, limit)
179 }
180
181 delegate_storage!(insert_consolidation_log(&self, cycle_type: &str, affected_count: usize) -> Result<(), CodememError>);
184 delegate_storage!(last_consolidation_runs(&self) -> Result<Vec<ConsolidationLogEntry>, CodememError>);
185
186 delegate_storage!(get_repeated_searches(&self, min_count: usize, namespace: Option<&str>) -> Result<Vec<(String, usize, Vec<String>)>, CodememError>);
189 delegate_storage!(get_file_hotspots(&self, min_count: usize, namespace: Option<&str>) -> Result<Vec<(String, usize, Vec<String>)>, CodememError>);
190 delegate_storage!(get_tool_usage_stats(&self, namespace: Option<&str>) -> Result<Vec<(String, usize)>, CodememError>);
191 delegate_storage!(get_decision_chains(&self, min_count: usize, namespace: Option<&str>) -> Result<Vec<(String, usize, Vec<String>)>, CodememError>);
192
193 fn decay_stale_memories(
196 &self,
197 threshold_ts: i64,
198 decay_factor: f64,
199 ) -> Result<usize, CodememError> {
200 let conn = self.conn()?;
201 let rows = conn
202 .execute(
203 "UPDATE memories SET importance = importance * ?1 WHERE last_accessed_at < ?2",
204 params![decay_factor, threshold_ts],
205 )
206 .map_err(|e| CodememError::Storage(e.to_string()))?;
207 Ok(rows)
208 }
209
210 fn list_memories_for_creative(
211 &self,
212 ) -> Result<Vec<(String, String, Vec<String>)>, CodememError> {
213 let conn = self.conn()?;
214 let mut stmt = conn
215 .prepare("SELECT id, memory_type, tags FROM memories ORDER BY created_at DESC")
216 .map_err(|e| CodememError::Storage(e.to_string()))?;
217
218 let rows = stmt
219 .query_map([], |row| {
220 Ok((
221 row.get::<_, String>(0)?,
222 row.get::<_, String>(1)?,
223 row.get::<_, String>(2)?,
224 ))
225 })
226 .map_err(|e| CodememError::Storage(e.to_string()))?
227 .collect::<Result<Vec<_>, _>>()
228 .map_err(|e| CodememError::Storage(e.to_string()))?;
229
230 Ok(rows
231 .into_iter()
232 .map(|(id, mtype, tags_json)| {
233 let tags: Vec<String> = serde_json::from_str(&tags_json).unwrap_or_default();
234 (id, mtype, tags)
235 })
236 .collect())
237 }
238
239 fn find_hash_duplicates(&self) -> Result<Vec<(String, String, f64)>, CodememError> {
240 let conn = self.conn()?;
241 let mut stmt = conn
242 .prepare(
243 "SELECT a.id, b.id, 1.0 as similarity
244 FROM memories a
245 INNER JOIN memories b ON substr(a.content_hash, 1, 16) = substr(b.content_hash, 1, 16)
246 WHERE a.id < b.id",
247 )
248 .map_err(|e| CodememError::Storage(e.to_string()))?;
249
250 let rows = stmt
251 .query_map([], |row| {
252 Ok((
253 row.get::<_, String>(0)?,
254 row.get::<_, String>(1)?,
255 row.get::<_, f64>(2)?,
256 ))
257 })
258 .map_err(|e| CodememError::Storage(e.to_string()))?
259 .collect::<Result<Vec<_>, _>>()
260 .map_err(|e| CodememError::Storage(e.to_string()))?;
261
262 Ok(rows)
263 }
264
265 fn find_forgettable(&self, importance_threshold: f64) -> Result<Vec<String>, CodememError> {
266 let conn = self.conn()?;
267 let mut stmt = conn
268 .prepare(
269 "SELECT id FROM memories WHERE importance < ?1 AND access_count = 0 ORDER BY importance ASC, last_accessed_at ASC",
270 )
271 .map_err(|e| CodememError::Storage(e.to_string()))?;
272
273 let ids = stmt
274 .query_map(params![importance_threshold], |row| row.get(0))
275 .map_err(|e| CodememError::Storage(e.to_string()))?
276 .collect::<Result<Vec<String>, _>>()
277 .map_err(|e| CodememError::Storage(e.to_string()))?;
278
279 Ok(ids)
280 }
281
282 fn insert_memories_batch(&self, memories: &[MemoryNode]) -> Result<(), CodememError> {
285 let conn = self.conn()?;
286 let tx = conn
287 .unchecked_transaction()
288 .map_err(|e| CodememError::Storage(e.to_string()))?;
289
290 for memory in memories {
291 let tags_json = serde_json::to_string(&memory.tags)?;
292 let metadata_json = serde_json::to_string(&memory.metadata)?;
293
294 tx.execute(
295 "INSERT OR IGNORE INTO memories (id, content, memory_type, importance, confidence, access_count, content_hash, tags, metadata, namespace, created_at, updated_at, last_accessed_at)
296 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13)",
297 params![
298 memory.id,
299 memory.content,
300 memory.memory_type.to_string(),
301 memory.importance,
302 memory.confidence,
303 memory.access_count,
304 memory.content_hash,
305 tags_json,
306 metadata_json,
307 memory.namespace,
308 memory.created_at.timestamp(),
309 memory.updated_at.timestamp(),
310 memory.last_accessed_at.timestamp(),
311 ],
312 )
313 .map_err(|e| CodememError::Storage(e.to_string()))?;
314 }
315
316 tx.commit()
317 .map_err(|e| CodememError::Storage(e.to_string()))?;
318 Ok(())
319 }
320
321 fn store_embeddings_batch(&self, items: &[(&str, &[f32])]) -> Result<(), CodememError> {
322 let conn = self.conn()?;
323 let tx = conn
324 .unchecked_transaction()
325 .map_err(|e| CodememError::Storage(e.to_string()))?;
326
327 for (id, embedding) in items {
328 let blob: Vec<u8> = embedding.iter().flat_map(|f| f.to_le_bytes()).collect();
329 tx.execute(
330 "INSERT OR REPLACE INTO memory_embeddings (memory_id, embedding) VALUES (?1, ?2)",
331 params![id, blob],
332 )
333 .map_err(|e| CodememError::Storage(e.to_string()))?;
334 }
335
336 tx.commit()
337 .map_err(|e| CodememError::Storage(e.to_string()))?;
338 Ok(())
339 }
340
341 fn load_file_hashes(&self) -> Result<HashMap<String, String>, CodememError> {
342 let conn = self.conn()?;
343 let mut stmt = conn
344 .prepare("SELECT file_path, content_hash FROM file_hashes")
345 .map_err(|e| CodememError::Storage(e.to_string()))?;
346
347 let rows = stmt
348 .query_map([], |row| {
349 Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
350 })
351 .map_err(|e| CodememError::Storage(e.to_string()))?
352 .collect::<Result<Vec<_>, _>>()
353 .map_err(|e| CodememError::Storage(e.to_string()))?;
354
355 Ok(rows.into_iter().collect())
356 }
357
358 fn save_file_hashes(&self, hashes: &HashMap<String, String>) -> Result<(), CodememError> {
359 let conn = self.conn()?;
360 let tx = conn
361 .unchecked_transaction()
362 .map_err(|e| CodememError::Storage(e.to_string()))?;
363
364 tx.execute("DELETE FROM file_hashes", [])
365 .map_err(|e| CodememError::Storage(e.to_string()))?;
366
367 for (path, hash) in hashes {
368 tx.execute(
369 "INSERT INTO file_hashes (file_path, content_hash) VALUES (?1, ?2)",
370 params![path, hash],
371 )
372 .map_err(|e| CodememError::Storage(e.to_string()))?;
373 }
374
375 tx.commit()
376 .map_err(|e| CodememError::Storage(e.to_string()))?;
377 Ok(())
378 }
379
380 fn insert_graph_nodes_batch(&self, nodes: &[GraphNode]) -> Result<(), CodememError> {
381 let conn = self.conn()?;
382 let tx = conn
383 .unchecked_transaction()
384 .map_err(|e| CodememError::Storage(e.to_string()))?;
385
386 for node in nodes {
387 let payload_json =
388 serde_json::to_string(&node.payload).unwrap_or_else(|_| "{}".to_string());
389 tx.execute(
390 "INSERT OR REPLACE INTO graph_nodes (id, kind, label, payload, centrality, memory_id, namespace)
391 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
392 params![
393 node.id,
394 node.kind.to_string(),
395 node.label,
396 payload_json,
397 node.centrality,
398 node.memory_id,
399 node.namespace,
400 ],
401 )
402 .map_err(|e| CodememError::Storage(e.to_string()))?;
403 }
404
405 tx.commit()
406 .map_err(|e| CodememError::Storage(e.to_string()))?;
407 Ok(())
408 }
409
410 fn insert_graph_edges_batch(&self, edges: &[Edge]) -> Result<(), CodememError> {
411 let conn = self.conn()?;
412 let tx = conn
413 .unchecked_transaction()
414 .map_err(|e| CodememError::Storage(e.to_string()))?;
415
416 for edge in edges {
417 let props_json =
418 serde_json::to_string(&edge.properties).unwrap_or_else(|_| "{}".to_string());
419 tx.execute(
420 "INSERT OR REPLACE INTO graph_edges (id, src, dst, relationship, weight, properties, created_at, valid_from, valid_to)
421 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)",
422 params![
423 edge.id,
424 edge.src,
425 edge.dst,
426 edge.relationship.to_string(),
427 edge.weight,
428 props_json,
429 edge.created_at.timestamp(),
430 edge.valid_from.map(|dt| dt.timestamp()),
431 edge.valid_to.map(|dt| dt.timestamp()),
432 ],
433 )
434 .map_err(|e| CodememError::Storage(e.to_string()))?;
435 }
436
437 tx.commit()
438 .map_err(|e| CodememError::Storage(e.to_string()))?;
439 Ok(())
440 }
441
442 fn get_edges_at_time(&self, node_id: &str, timestamp: i64) -> Result<Vec<Edge>, CodememError> {
445 let conn = self.conn()?;
446 let mut stmt = conn
447 .prepare(
448 "SELECT id, src, dst, relationship, weight, properties, created_at, valid_from, valid_to
449 FROM graph_edges
450 WHERE (src = ?1 OR dst = ?1)
451 AND (valid_from IS NULL OR valid_from <= ?2)
452 AND (valid_to IS NULL OR valid_to > ?2)",
453 )
454 .map_err(|e| CodememError::Storage(e.to_string()))?;
455
456 let edges = stmt
457 .query_map(params![node_id, timestamp], extract_edge_tuple)
458 .map_err(|e| CodememError::Storage(e.to_string()))?
459 .filter_map(|r| match r {
460 Ok(v) => Some(v),
461 Err(e) => {
462 tracing::warn!("Failed to process edge row: {e}");
463 None
464 }
465 })
466 .filter_map(edge_from_row)
467 .collect();
468
469 Ok(edges)
470 }
471
472 fn get_stale_memories_for_decay(
473 &self,
474 threshold_ts: i64,
475 ) -> Result<Vec<(String, f64, u32, i64)>, CodememError> {
476 let conn = self.conn()?;
477 let mut stmt = conn
478 .prepare(
479 "SELECT id, importance, access_count, last_accessed_at FROM memories WHERE last_accessed_at < ?1",
480 )
481 .map_err(|e| CodememError::Storage(e.to_string()))?;
482
483 let rows = stmt
484 .query_map(params![threshold_ts], |row| {
485 Ok((
486 row.get::<_, String>(0)?,
487 row.get::<_, f64>(1)?,
488 row.get::<_, u32>(2)?,
489 row.get::<_, i64>(3)?,
490 ))
491 })
492 .map_err(|e| CodememError::Storage(e.to_string()))?
493 .collect::<Result<Vec<_>, _>>()
494 .map_err(|e| CodememError::Storage(e.to_string()))?;
495
496 Ok(rows)
497 }
498
499 fn batch_update_importance(&self, updates: &[(String, f64)]) -> Result<usize, CodememError> {
500 if updates.is_empty() {
501 return Ok(0);
502 }
503 let conn = self.conn()?;
504 let tx = conn
505 .unchecked_transaction()
506 .map_err(|e| CodememError::Storage(e.to_string()))?;
507
508 let mut count = 0usize;
509 for (id, importance) in updates {
510 let rows = tx
511 .execute(
512 "UPDATE memories SET importance = ?1 WHERE id = ?2",
513 params![importance, id],
514 )
515 .map_err(|e| CodememError::Storage(e.to_string()))?;
516 count += rows;
517 }
518
519 tx.commit()
520 .map_err(|e| CodememError::Storage(e.to_string()))?;
521 Ok(count)
522 }
523
524 fn session_count(&self, namespace: Option<&str>) -> Result<usize, CodememError> {
525 let conn = self.conn()?;
526 let count: i64 = if let Some(ns) = namespace {
527 conn.query_row(
528 "SELECT COUNT(*) FROM sessions WHERE namespace = ?1",
529 params![ns],
530 |row| row.get(0),
531 )
532 .map_err(|e| CodememError::Storage(e.to_string()))?
533 } else {
534 conn.query_row("SELECT COUNT(*) FROM sessions", [], |row| row.get(0))
535 .map_err(|e| CodememError::Storage(e.to_string()))?
536 };
537 Ok(count as usize)
538 }
539
540 fn find_unembedded_memories(&self) -> Result<Vec<(String, String)>, CodememError> {
543 let conn = self.conn()?;
544 let mut stmt = conn
545 .prepare(
546 "SELECT m.id, m.content FROM memories m
547 LEFT JOIN memory_embeddings me ON m.id = me.memory_id
548 WHERE me.memory_id IS NULL",
549 )
550 .map_err(|e| CodememError::Storage(e.to_string()))?;
551
552 let rows = stmt
553 .query_map([], |row| {
554 Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
555 })
556 .map_err(|e| CodememError::Storage(e.to_string()))?
557 .collect::<Result<Vec<_>, _>>()
558 .map_err(|e| CodememError::Storage(e.to_string()))?;
559
560 Ok(rows)
561 }
562
563 fn search_graph_nodes(
564 &self,
565 query: &str,
566 namespace: Option<&str>,
567 limit: usize,
568 ) -> Result<Vec<GraphNode>, CodememError> {
569 let conn = self.conn()?;
570 let escaped = query
571 .to_lowercase()
572 .replace('\\', "\\\\")
573 .replace('%', "\\%")
574 .replace('_', "\\_");
575 let pattern = format!("%{escaped}%");
576
577 let (sql, params_vec): (String, Vec<Box<dyn rusqlite::types::ToSql>>) =
578 if let Some(ns) = namespace {
579 (
580 "SELECT id, kind, label, payload, centrality, memory_id, namespace \
581 FROM graph_nodes WHERE LOWER(label) LIKE ?1 ESCAPE '\\' AND namespace = ?2 \
582 ORDER BY centrality DESC LIMIT ?3"
583 .to_string(),
584 vec![
585 Box::new(pattern) as Box<dyn rusqlite::types::ToSql>,
586 Box::new(ns.to_string()),
587 Box::new(limit as i64),
588 ],
589 )
590 } else {
591 (
592 "SELECT id, kind, label, payload, centrality, memory_id, namespace \
593 FROM graph_nodes WHERE LOWER(label) LIKE ?1 ESCAPE '\\' \
594 ORDER BY centrality DESC LIMIT ?2"
595 .to_string(),
596 vec![
597 Box::new(pattern) as Box<dyn rusqlite::types::ToSql>,
598 Box::new(limit as i64),
599 ],
600 )
601 };
602
603 let refs: Vec<&dyn rusqlite::types::ToSql> =
604 params_vec.iter().map(|p| p.as_ref()).collect();
605 let mut stmt = conn
606 .prepare(&sql)
607 .map_err(|e| CodememError::Storage(e.to_string()))?;
608
609 let rows = stmt
610 .query_map(refs.as_slice(), |row| {
611 let kind_str: String = row.get(1)?;
612 let payload_str: String = row.get(3)?;
613 Ok(GraphNode {
614 id: row.get(0)?,
615 kind: kind_str.parse().unwrap_or(NodeKind::Memory),
616 label: row.get(2)?,
617 payload: serde_json::from_str(&payload_str).unwrap_or_default(),
618 centrality: row.get(4)?,
619 memory_id: row.get(5)?,
620 namespace: row.get(6)?,
621 })
622 })
623 .map_err(|e| CodememError::Storage(e.to_string()))?
624 .collect::<Result<Vec<_>, _>>()
625 .map_err(|e| CodememError::Storage(e.to_string()))?;
626
627 Ok(rows)
628 }
629
630 fn list_memories_filtered(
631 &self,
632 namespace: Option<&str>,
633 memory_type: Option<&str>,
634 ) -> Result<Vec<MemoryNode>, CodememError> {
635 let conn = self.conn()?;
636 let mut sql = "SELECT id, content, memory_type, importance, confidence, access_count, \
637 content_hash, tags, metadata, namespace, created_at, updated_at, \
638 last_accessed_at FROM memories WHERE 1=1"
639 .to_string();
640 let mut param_values: Vec<Box<dyn rusqlite::types::ToSql>> = Vec::new();
641
642 if let Some(ns) = namespace {
643 param_values.push(Box::new(ns.to_string()));
644 sql.push_str(&format!(" AND namespace = ?{}", param_values.len()));
645 }
646 if let Some(mt) = memory_type {
647 param_values.push(Box::new(mt.to_string()));
648 sql.push_str(&format!(" AND memory_type = ?{}", param_values.len()));
649 }
650 sql.push_str(" ORDER BY created_at DESC");
651
652 let refs: Vec<&dyn rusqlite::types::ToSql> =
653 param_values.iter().map(|p| p.as_ref()).collect();
654 let mut stmt = conn
655 .prepare(&sql)
656 .map_err(|e| CodememError::Storage(e.to_string()))?;
657
658 let rows = stmt
659 .query_map(refs.as_slice(), |row| {
660 Ok(MemoryRow {
661 id: row.get(0)?,
662 content: row.get(1)?,
663 memory_type: row.get(2)?,
664 importance: row.get(3)?,
665 confidence: row.get(4)?,
666 access_count: row.get(5)?,
667 content_hash: row.get(6)?,
668 tags: row.get(7)?,
669 metadata: row.get(8)?,
670 namespace: row.get(9)?,
671 created_at: row.get(10)?,
672 updated_at: row.get(11)?,
673 last_accessed_at: row.get(12)?,
674 })
675 })
676 .map_err(|e| CodememError::Storage(e.to_string()))?;
677
678 let mut result = Vec::new();
679 for row in rows {
680 let mr = row.map_err(|e| CodememError::Storage(e.to_string()))?;
681 result.push(mr.into_memory_node()?);
682 }
683
684 Ok(result)
685 }
686
687 delegate_storage!(graph_edges_for_namespace(&self, namespace: &str) -> Result<Vec<Edge>, CodememError>);
688
689 delegate_storage!(record_session_activity(&self, session_id: &str, tool_name: &str, file_path: Option<&str>, directory: Option<&str>, pattern: Option<&str>) -> Result<(), CodememError>);
692 delegate_storage!(get_session_activity_summary(&self, session_id: &str) -> Result<codemem_core::SessionActivitySummary, CodememError>);
693 delegate_storage!(get_session_hot_directories(&self, session_id: &str, limit: usize) -> Result<Vec<(String, usize)>, CodememError>);
694 delegate_storage!(has_auto_insight(&self, session_id: &str, dedup_tag: &str) -> Result<bool, CodememError>);
695 delegate_storage!(count_directory_reads(&self, session_id: &str, directory: &str) -> Result<usize, CodememError>);
696 delegate_storage!(was_file_read_in_session(&self, session_id: &str, file_path: &str) -> Result<bool, CodememError>);
697 delegate_storage!(count_search_pattern_in_session(&self, session_id: &str, pattern: &str) -> Result<usize, CodememError>);
698
699 delegate_storage!(stats(&self) -> Result<StorageStats, CodememError>);
702}
703
704#[cfg(test)]
705#[path = "tests/backend_tests.rs"]
706mod tests;