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
11macro_rules! delegate_storage {
13 ($method:ident(&self) -> $ret:ty) => {
15 fn $method(&self) -> $ret {
16 Storage::$method(self)
17 }
18 };
19 ($method:ident(&self, $a1:ident: $t1:ty) -> $ret:ty) => {
21 fn $method(&self, $a1: $t1) -> $ret {
22 Storage::$method(self, $a1)
23 }
24 };
25 ($method:ident(&self, $a1:ident: $t1:ty, $a2:ident: $t2:ty) -> $ret:ty) => {
27 fn $method(&self, $a1: $t1, $a2: $t2) -> $ret {
28 Storage::$method(self, $a1, $a2)
29 }
30 };
31 ($method:ident(&self, $a1:ident: $t1:ty, $a2:ident: $t2:ty, $a3:ident: $t3:ty) -> $ret:ty) => {
33 fn $method(&self, $a1: $t1, $a2: $t2, $a3: $t3) -> $ret {
34 Storage::$method(self, $a1, $a2, $a3)
35 }
36 };
37 ($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) => {
39 fn $method(&self, $a1: $t1, $a2: $t2, $a3: $t3, $a4: $t4, $a5: $t5) -> $ret {
40 Storage::$method(self, $a1, $a2, $a3, $a4, $a5)
41 }
42 };
43}
44
45impl StorageBackend for Storage {
46 delegate_storage!(insert_memory(&self, memory: &MemoryNode) -> Result<(), CodememError>);
49 delegate_storage!(get_memory(&self, id: &str) -> Result<Option<MemoryNode>, CodememError>);
50 delegate_storage!(get_memory_no_touch(&self, id: &str) -> Result<Option<MemoryNode>, CodememError>);
51 delegate_storage!(update_memory(&self, id: &str, content: &str, importance: Option<f64>) -> Result<(), CodememError>);
52 delegate_storage!(delete_memory(&self, id: &str) -> Result<bool, CodememError>);
53
54 fn delete_memory_cascade(&self, id: &str) -> Result<bool, CodememError> {
56 Storage::delete_memory_cascade(self, id)
59 }
60
61 delegate_storage!(list_memory_ids(&self) -> Result<Vec<String>, CodememError>);
62 delegate_storage!(list_memory_ids_for_namespace(&self, namespace: &str) -> Result<Vec<String>, CodememError>);
63 delegate_storage!(find_memory_ids_by_tag(&self, tag: &str, namespace: Option<&str>, exclude_id: &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_stale_memories_for_decay(
443 &self,
444 threshold_ts: i64,
445 ) -> Result<Vec<(String, f64, u32, i64)>, CodememError> {
446 let conn = self.conn()?;
447 let mut stmt = conn
448 .prepare(
449 "SELECT id, importance, access_count, last_accessed_at FROM memories WHERE last_accessed_at < ?1",
450 )
451 .map_err(|e| CodememError::Storage(e.to_string()))?;
452
453 let rows = stmt
454 .query_map(params![threshold_ts], |row| {
455 Ok((
456 row.get::<_, String>(0)?,
457 row.get::<_, f64>(1)?,
458 row.get::<_, u32>(2)?,
459 row.get::<_, i64>(3)?,
460 ))
461 })
462 .map_err(|e| CodememError::Storage(e.to_string()))?
463 .collect::<Result<Vec<_>, _>>()
464 .map_err(|e| CodememError::Storage(e.to_string()))?;
465
466 Ok(rows)
467 }
468
469 fn batch_update_importance(&self, updates: &[(String, f64)]) -> Result<usize, CodememError> {
470 if updates.is_empty() {
471 return Ok(0);
472 }
473 let conn = self.conn()?;
474 let tx = conn
475 .unchecked_transaction()
476 .map_err(|e| CodememError::Storage(e.to_string()))?;
477
478 let mut count = 0usize;
479 for (id, importance) in updates {
480 let rows = tx
481 .execute(
482 "UPDATE memories SET importance = ?1 WHERE id = ?2",
483 params![importance, id],
484 )
485 .map_err(|e| CodememError::Storage(e.to_string()))?;
486 count += rows;
487 }
488
489 tx.commit()
490 .map_err(|e| CodememError::Storage(e.to_string()))?;
491 Ok(count)
492 }
493
494 fn session_count(&self, namespace: Option<&str>) -> Result<usize, CodememError> {
495 let conn = self.conn()?;
496 let count: i64 = if let Some(ns) = namespace {
497 conn.query_row(
498 "SELECT COUNT(*) FROM sessions WHERE namespace = ?1",
499 params![ns],
500 |row| row.get(0),
501 )
502 .map_err(|e| CodememError::Storage(e.to_string()))?
503 } else {
504 conn.query_row("SELECT COUNT(*) FROM sessions", [], |row| row.get(0))
505 .map_err(|e| CodememError::Storage(e.to_string()))?
506 };
507 Ok(count as usize)
508 }
509
510 fn find_unembedded_memories(&self) -> Result<Vec<(String, String)>, CodememError> {
513 let conn = self.conn()?;
514 let mut stmt = conn
515 .prepare(
516 "SELECT m.id, m.content FROM memories m
517 LEFT JOIN memory_embeddings me ON m.id = me.memory_id
518 WHERE me.memory_id IS NULL",
519 )
520 .map_err(|e| CodememError::Storage(e.to_string()))?;
521
522 let rows = stmt
523 .query_map([], |row| {
524 Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
525 })
526 .map_err(|e| CodememError::Storage(e.to_string()))?
527 .collect::<Result<Vec<_>, _>>()
528 .map_err(|e| CodememError::Storage(e.to_string()))?;
529
530 Ok(rows)
531 }
532
533 fn search_graph_nodes(
534 &self,
535 query: &str,
536 namespace: Option<&str>,
537 limit: usize,
538 ) -> Result<Vec<GraphNode>, CodememError> {
539 let conn = self.conn()?;
540 let escaped = query
541 .to_lowercase()
542 .replace('\\', "\\\\")
543 .replace('%', "\\%")
544 .replace('_', "\\_");
545 let pattern = format!("%{escaped}%");
546
547 let (sql, params_vec): (String, Vec<Box<dyn rusqlite::types::ToSql>>) =
548 if let Some(ns) = namespace {
549 (
550 "SELECT id, kind, label, payload, centrality, memory_id, namespace \
551 FROM graph_nodes WHERE LOWER(label) LIKE ?1 ESCAPE '\\' AND namespace = ?2 \
552 ORDER BY centrality DESC LIMIT ?3"
553 .to_string(),
554 vec![
555 Box::new(pattern) as Box<dyn rusqlite::types::ToSql>,
556 Box::new(ns.to_string()),
557 Box::new(limit as i64),
558 ],
559 )
560 } else {
561 (
562 "SELECT id, kind, label, payload, centrality, memory_id, namespace \
563 FROM graph_nodes WHERE LOWER(label) LIKE ?1 ESCAPE '\\' \
564 ORDER BY centrality DESC LIMIT ?2"
565 .to_string(),
566 vec![
567 Box::new(pattern) as Box<dyn rusqlite::types::ToSql>,
568 Box::new(limit as i64),
569 ],
570 )
571 };
572
573 let refs: Vec<&dyn rusqlite::types::ToSql> =
574 params_vec.iter().map(|p| p.as_ref()).collect();
575 let mut stmt = conn
576 .prepare(&sql)
577 .map_err(|e| CodememError::Storage(e.to_string()))?;
578
579 let rows = stmt
580 .query_map(refs.as_slice(), |row| {
581 let kind_str: String = row.get(1)?;
582 let payload_str: String = row.get(3)?;
583 Ok(GraphNode {
584 id: row.get(0)?,
585 kind: kind_str.parse().unwrap_or(NodeKind::Memory),
586 label: row.get(2)?,
587 payload: serde_json::from_str(&payload_str).unwrap_or_default(),
588 centrality: row.get(4)?,
589 memory_id: row.get(5)?,
590 namespace: row.get(6)?,
591 })
592 })
593 .map_err(|e| CodememError::Storage(e.to_string()))?
594 .collect::<Result<Vec<_>, _>>()
595 .map_err(|e| CodememError::Storage(e.to_string()))?;
596
597 Ok(rows)
598 }
599
600 fn list_memories_filtered(
601 &self,
602 namespace: Option<&str>,
603 memory_type: Option<&str>,
604 ) -> Result<Vec<MemoryNode>, CodememError> {
605 let conn = self.conn()?;
606 let mut sql = "SELECT id, content, memory_type, importance, confidence, access_count, \
607 content_hash, tags, metadata, namespace, created_at, updated_at, \
608 last_accessed_at FROM memories WHERE 1=1"
609 .to_string();
610 let mut param_values: Vec<Box<dyn rusqlite::types::ToSql>> = Vec::new();
611
612 if let Some(ns) = namespace {
613 param_values.push(Box::new(ns.to_string()));
614 sql.push_str(&format!(" AND namespace = ?{}", param_values.len()));
615 }
616 if let Some(mt) = memory_type {
617 param_values.push(Box::new(mt.to_string()));
618 sql.push_str(&format!(" AND memory_type = ?{}", param_values.len()));
619 }
620 sql.push_str(" ORDER BY created_at DESC");
621
622 let refs: Vec<&dyn rusqlite::types::ToSql> =
623 param_values.iter().map(|p| p.as_ref()).collect();
624 let mut stmt = conn
625 .prepare(&sql)
626 .map_err(|e| CodememError::Storage(e.to_string()))?;
627
628 let rows = stmt
629 .query_map(refs.as_slice(), |row| {
630 Ok(MemoryRow {
631 id: row.get(0)?,
632 content: row.get(1)?,
633 memory_type: row.get(2)?,
634 importance: row.get(3)?,
635 confidence: row.get(4)?,
636 access_count: row.get(5)?,
637 content_hash: row.get(6)?,
638 tags: row.get(7)?,
639 metadata: row.get(8)?,
640 namespace: row.get(9)?,
641 created_at: row.get(10)?,
642 updated_at: row.get(11)?,
643 last_accessed_at: row.get(12)?,
644 })
645 })
646 .map_err(|e| CodememError::Storage(e.to_string()))?;
647
648 let mut result = Vec::new();
649 for row in rows {
650 let mr = row.map_err(|e| CodememError::Storage(e.to_string()))?;
651 result.push(mr.into_memory_node()?);
652 }
653
654 Ok(result)
655 }
656
657 delegate_storage!(record_session_activity(&self, session_id: &str, tool_name: &str, file_path: Option<&str>, directory: Option<&str>, pattern: Option<&str>) -> Result<(), CodememError>);
660 delegate_storage!(get_session_activity_summary(&self, session_id: &str) -> Result<codemem_core::SessionActivitySummary, CodememError>);
661 delegate_storage!(get_session_hot_directories(&self, session_id: &str, limit: usize) -> Result<Vec<(String, usize)>, CodememError>);
662 delegate_storage!(has_auto_insight(&self, session_id: &str, dedup_tag: &str) -> Result<bool, CodememError>);
663 delegate_storage!(count_directory_reads(&self, session_id: &str, directory: &str) -> Result<usize, CodememError>);
664 delegate_storage!(was_file_read_in_session(&self, session_id: &str, file_path: &str) -> Result<bool, CodememError>);
665 delegate_storage!(count_search_pattern_in_session(&self, session_id: &str, pattern: &str) -> Result<usize, CodememError>);
666
667 delegate_storage!(stats(&self) -> Result<StorageStats, CodememError>);
670}
671
672#[cfg(test)]
673#[path = "tests/backend_tests.rs"]
674mod tests;