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
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;