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