1use crate::{MemoryRow, Storage};
4use codemem_core::{CodememError, MemoryNode};
5use rusqlite::{params, OptionalExtension};
6
7impl Storage {
8 pub fn insert_memory(&self, memory: &MemoryNode) -> Result<(), CodememError> {
10 let conn = self.conn();
11
12 let existing: Option<String> = conn
14 .query_row(
15 "SELECT id FROM memories WHERE content_hash = ?1",
16 params![memory.content_hash],
17 |row| row.get(0),
18 )
19 .optional()
20 .map_err(|e| CodememError::Storage(e.to_string()))?;
21
22 if let Some(_existing_id) = existing {
23 return Err(CodememError::Duplicate(memory.content_hash.clone()));
24 }
25
26 let tags_json = serde_json::to_string(&memory.tags)?;
27 let metadata_json = serde_json::to_string(&memory.metadata)?;
28
29 conn.execute(
30 "INSERT INTO memories (id, content, memory_type, importance, confidence, access_count, content_hash, tags, metadata, namespace, created_at, updated_at, last_accessed_at)
31 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13)",
32 params![
33 memory.id,
34 memory.content,
35 memory.memory_type.to_string(),
36 memory.importance,
37 memory.confidence,
38 memory.access_count,
39 memory.content_hash,
40 tags_json,
41 metadata_json,
42 memory.namespace,
43 memory.created_at.timestamp(),
44 memory.updated_at.timestamp(),
45 memory.last_accessed_at.timestamp(),
46 ],
47 )
48 .map_err(|e| CodememError::Storage(e.to_string()))?;
49
50 Ok(())
51 }
52
53 pub fn get_memory(&self, id: &str) -> Result<Option<MemoryNode>, CodememError> {
55 let conn = self.conn();
56
57 let updated = conn
59 .execute(
60 "UPDATE memories SET access_count = access_count + 1, last_accessed_at = ?1 WHERE id = ?2",
61 params![chrono::Utc::now().timestamp(), id],
62 )
63 .map_err(|e| CodememError::Storage(e.to_string()))?;
64
65 if updated == 0 {
66 return Ok(None);
67 }
68
69 let result = conn
70 .query_row(
71 "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 = ?1",
72 params![id],
73 |row| {
74 Ok(MemoryRow {
75 id: row.get(0)?,
76 content: row.get(1)?,
77 memory_type: row.get(2)?,
78 importance: row.get(3)?,
79 confidence: row.get(4)?,
80 access_count: row.get(5)?,
81 content_hash: row.get(6)?,
82 tags: row.get(7)?,
83 metadata: row.get(8)?,
84 namespace: row.get(9)?,
85 created_at: row.get(10)?,
86 updated_at: row.get(11)?,
87 last_accessed_at: row.get(12)?,
88 })
89 },
90 )
91 .optional()
92 .map_err(|e| CodememError::Storage(e.to_string()))?;
93
94 match result {
95 Some(row) => Ok(Some(row.into_memory_node()?)),
96 None => Ok(None),
97 }
98 }
99
100 pub fn update_memory(
102 &self,
103 id: &str,
104 content: &str,
105 importance: Option<f64>,
106 ) -> Result<(), CodememError> {
107 let conn = self.conn();
108 let hash = Self::content_hash(content);
109 let now = chrono::Utc::now().timestamp();
110
111 if let Some(imp) = importance {
112 conn.execute(
113 "UPDATE memories SET content = ?1, content_hash = ?2, updated_at = ?3, importance = ?4 WHERE id = ?5",
114 params![content, hash, now, imp, id],
115 )
116 .map_err(|e| CodememError::Storage(e.to_string()))?;
117 } else {
118 conn.execute(
119 "UPDATE memories SET content = ?1, content_hash = ?2, updated_at = ?3 WHERE id = ?4",
120 params![content, hash, now, id],
121 )
122 .map_err(|e| CodememError::Storage(e.to_string()))?;
123 }
124
125 Ok(())
126 }
127
128 pub fn delete_memory(&self, id: &str) -> Result<bool, CodememError> {
130 let conn = self.conn();
131 let rows = conn
132 .execute("DELETE FROM memories WHERE id = ?1", params![id])
133 .map_err(|e| CodememError::Storage(e.to_string()))?;
134 Ok(rows > 0)
135 }
136
137 pub fn list_memory_ids(&self) -> Result<Vec<String>, CodememError> {
139 let conn = self.conn();
140 let mut stmt = conn
141 .prepare("SELECT id FROM memories ORDER BY created_at DESC")
142 .map_err(|e| CodememError::Storage(e.to_string()))?;
143
144 let ids = stmt
145 .query_map([], |row| row.get(0))
146 .map_err(|e| CodememError::Storage(e.to_string()))?
147 .collect::<Result<Vec<String>, _>>()
148 .map_err(|e| CodememError::Storage(e.to_string()))?;
149
150 Ok(ids)
151 }
152
153 pub fn list_memory_ids_for_namespace(
155 &self,
156 namespace: &str,
157 ) -> Result<Vec<String>, CodememError> {
158 let conn = self.conn();
159 let mut stmt = conn
160 .prepare("SELECT id FROM memories WHERE namespace = ?1 ORDER BY created_at DESC")
161 .map_err(|e| CodememError::Storage(e.to_string()))?;
162
163 let ids = stmt
164 .query_map(params![namespace], |row| row.get(0))
165 .map_err(|e| CodememError::Storage(e.to_string()))?
166 .collect::<Result<Vec<String>, _>>()
167 .map_err(|e| CodememError::Storage(e.to_string()))?;
168
169 Ok(ids)
170 }
171
172 pub fn list_namespaces(&self) -> Result<Vec<String>, CodememError> {
174 let conn = self.conn();
175 let mut stmt = conn
176 .prepare(
177 "SELECT DISTINCT namespace FROM (
178 SELECT namespace FROM memories WHERE namespace IS NOT NULL
179 UNION
180 SELECT namespace FROM graph_nodes WHERE namespace IS NOT NULL
181 ) ORDER BY namespace",
182 )
183 .map_err(|e| CodememError::Storage(e.to_string()))?;
184
185 let namespaces = stmt
186 .query_map([], |row| row.get(0))
187 .map_err(|e| CodememError::Storage(e.to_string()))?
188 .collect::<Result<Vec<String>, _>>()
189 .map_err(|e| CodememError::Storage(e.to_string()))?;
190
191 Ok(namespaces)
192 }
193
194 pub fn memory_count(&self) -> Result<usize, CodememError> {
196 let conn = self.conn();
197 let count: i64 = conn
198 .query_row("SELECT COUNT(*) FROM memories", [], |row| row.get(0))
199 .map_err(|e| CodememError::Storage(e.to_string()))?;
200 Ok(count as usize)
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use crate::Storage;
207 use codemem_core::{CodememError, MemoryNode, MemoryType};
208 use std::collections::HashMap;
209
210 fn test_memory() -> MemoryNode {
211 let now = chrono::Utc::now();
212 let content = "Test memory content";
213 MemoryNode {
214 id: uuid::Uuid::new_v4().to_string(),
215 content: content.to_string(),
216 memory_type: MemoryType::Context,
217 importance: 0.7,
218 confidence: 1.0,
219 access_count: 0,
220 content_hash: Storage::content_hash(content),
221 tags: vec!["test".to_string()],
222 metadata: HashMap::new(),
223 namespace: None,
224 created_at: now,
225 updated_at: now,
226 last_accessed_at: now,
227 }
228 }
229
230 #[test]
231 fn insert_and_get_memory() {
232 let storage = Storage::open_in_memory().unwrap();
233 let memory = test_memory();
234 storage.insert_memory(&memory).unwrap();
235
236 let retrieved = storage.get_memory(&memory.id).unwrap().unwrap();
237 assert_eq!(retrieved.id, memory.id);
238 assert_eq!(retrieved.content, memory.content);
239 assert_eq!(retrieved.access_count, 1); }
241
242 #[test]
243 fn dedup_by_content_hash() {
244 let storage = Storage::open_in_memory().unwrap();
245 let m1 = test_memory();
246 storage.insert_memory(&m1).unwrap();
247
248 let mut m2 = test_memory();
249 m2.id = uuid::Uuid::new_v4().to_string();
250 m2.content_hash = m1.content_hash.clone(); assert!(matches!(
253 storage.insert_memory(&m2),
254 Err(CodememError::Duplicate(_))
255 ));
256 }
257
258 #[test]
259 fn delete_memory() {
260 let storage = Storage::open_in_memory().unwrap();
261 let memory = test_memory();
262 storage.insert_memory(&memory).unwrap();
263 assert!(storage.delete_memory(&memory.id).unwrap());
264 assert!(storage.get_memory(&memory.id).unwrap().is_none());
265 }
266}