Skip to main content

codemem_storage/
backend.rs

1//! `StorageBackend` trait implementation for Storage.
2
3use 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
11/// Macro to delegate pure-forwarding trait methods to `Storage` inherent methods.
12macro_rules! delegate_storage {
13    // &self, no args
14    ($method:ident(&self) -> $ret:ty) => {
15        fn $method(&self) -> $ret {
16            Storage::$method(self)
17        }
18    };
19    // &self, one arg
20    ($method:ident(&self, $a1:ident: $t1:ty) -> $ret:ty) => {
21        fn $method(&self, $a1: $t1) -> $ret {
22            Storage::$method(self, $a1)
23        }
24    };
25    // &self, two args
26    ($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    // &self, three args
32    ($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    // &self, five args
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) => {
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    // ── Memory CRUD (delegated) ───────────────────────────────────────
47
48    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    /// M1: Override with transactional cascade delete.
55    fn delete_memory_cascade(&self, id: &str) -> Result<bool, CodememError> {
56        // Delegates to Storage::delete_memory_cascade which wraps all
57        // deletes (memory + graph nodes/edges + embedding) in a single transaction.
58        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    // ── Embedding Persistence (delegated where possible) ──────────────
117
118    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    // ── Graph Node/Edge Persistence (delegated) ───────────────────────
157
158    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    // ── Sessions (delegated where possible) ───────────────────────────
169
170    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    // ── Consolidation (delegated) ─────────────────────────────────────
182
183    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    // ── Pattern Detection (delegated) ─────────────────────────────────
187
188    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    // ── Bulk Operations ───────────────────────────────────────────────
194
195    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    // ── Batch Operations ──────────────────────────────────────────────
283
284    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    // ── Query Helpers ─────────────────────────────────────────────────
511
512    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    // ── Session Activity (delegated) ──────────────────────────────────
658
659    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    // ── Stats (delegated) ─────────────────────────────────────────────
668
669    delegate_storage!(stats(&self) -> Result<StorageStats, CodememError>);
670}
671
672#[cfg(test)]
673#[path = "tests/backend_tests.rs"]
674mod tests;