1use crate::{MemoryRow, Storage};
4use codemem_core::{CodememError, MemoryNode, Repository};
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 pub fn list_repos(&self) -> Result<Vec<Repository>, CodememError> {
207 let conn = self.conn();
208 let mut stmt = conn
209 .prepare(
210 "SELECT id, path, name, namespace, created_at, last_indexed_at, status FROM repositories ORDER BY created_at DESC",
211 )
212 .map_err(|e| CodememError::Storage(e.to_string()))?;
213
214 let repos = stmt
215 .query_map([], |row| {
216 Ok(Repository {
217 id: row.get(0)?,
218 path: row.get(1)?,
219 name: row.get(2)?,
220 namespace: row.get(3)?,
221 created_at: row.get(4)?,
222 last_indexed_at: row.get(5)?,
223 status: row
224 .get::<_, Option<String>>(6)?
225 .unwrap_or_else(|| "idle".to_string()),
226 })
227 })
228 .map_err(|e| CodememError::Storage(e.to_string()))?
229 .collect::<Result<Vec<Repository>, _>>()
230 .map_err(|e| CodememError::Storage(e.to_string()))?;
231
232 Ok(repos)
233 }
234
235 pub fn add_repo(&self, repo: &Repository) -> Result<(), CodememError> {
237 let conn = self.conn();
238 conn.execute(
239 "INSERT INTO repositories (id, path, name, namespace, created_at, last_indexed_at, status) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
240 params![
241 repo.id,
242 repo.path,
243 repo.name,
244 repo.namespace,
245 repo.created_at,
246 repo.last_indexed_at,
247 repo.status,
248 ],
249 )
250 .map_err(|e| CodememError::Storage(e.to_string()))?;
251 Ok(())
252 }
253
254 pub fn remove_repo(&self, id: &str) -> Result<bool, CodememError> {
256 let conn = self.conn();
257 let rows = conn
258 .execute("DELETE FROM repositories WHERE id = ?1", params![id])
259 .map_err(|e| CodememError::Storage(e.to_string()))?;
260 Ok(rows > 0)
261 }
262
263 pub fn get_repo(&self, id: &str) -> Result<Option<Repository>, CodememError> {
265 let conn = self.conn();
266 let result = conn
267 .query_row(
268 "SELECT id, path, name, namespace, created_at, last_indexed_at, status FROM repositories WHERE id = ?1",
269 params![id],
270 |row| {
271 Ok(Repository {
272 id: row.get(0)?,
273 path: row.get(1)?,
274 name: row.get(2)?,
275 namespace: row.get(3)?,
276 created_at: row.get(4)?,
277 last_indexed_at: row.get(5)?,
278 status: row.get::<_, Option<String>>(6)?.unwrap_or_else(|| "idle".to_string()),
279 })
280 },
281 )
282 .optional()
283 .map_err(|e| CodememError::Storage(e.to_string()))?;
284 Ok(result)
285 }
286
287 pub fn update_repo_status(
289 &self,
290 id: &str,
291 status: &str,
292 indexed_at: Option<&str>,
293 ) -> Result<(), CodememError> {
294 let conn = self.conn();
295 if let Some(ts) = indexed_at {
296 conn.execute(
297 "UPDATE repositories SET status = ?1, last_indexed_at = ?2 WHERE id = ?3",
298 params![status, ts, id],
299 )
300 .map_err(|e| CodememError::Storage(e.to_string()))?;
301 } else {
302 conn.execute(
303 "UPDATE repositories SET status = ?1 WHERE id = ?2",
304 params![status, id],
305 )
306 .map_err(|e| CodememError::Storage(e.to_string()))?;
307 }
308 Ok(())
309 }
310}
311
312#[cfg(test)]
313#[path = "tests/memory_tests.rs"]
314mod tests;