1use crate::{MapStorageErr, Storage};
4use codemem_core::{CodememError, Edge};
5use rusqlite::params;
6
7#[derive(Debug, Clone)]
9pub struct PackageRegistryEntry {
10 pub package_name: String,
11 pub namespace: String,
12 pub version: String,
13 pub manifest: String,
14}
15
16#[derive(Debug, Clone)]
18pub struct UnresolvedRefEntry {
19 pub id: String,
20 pub namespace: String,
21 pub source_node: String,
22 pub target_name: String,
23 pub package_hint: Option<String>,
24 pub ref_kind: String,
25 pub file_path: Option<String>,
26 pub line: Option<i64>,
27 pub created_at: i64,
28}
29
30#[derive(Debug, Clone)]
32pub struct ApiEndpointEntry {
33 pub id: String,
34 pub namespace: String,
35 pub method: Option<String>,
36 pub path: String,
37 pub handler: Option<String>,
38 pub schema: String,
39}
40
41#[derive(Debug, Clone)]
43pub struct ApiClientCallEntry {
44 pub id: String,
45 pub namespace: String,
46 pub method: Option<String>,
47 pub target: String,
48 pub caller: String,
49 pub library: String,
50}
51
52impl Storage {
53 pub fn upsert_package_registry(
57 &self,
58 package_name: &str,
59 namespace: &str,
60 version: &str,
61 manifest: &str,
62 ) -> Result<(), CodememError> {
63 let conn = self.conn()?;
64 conn.execute(
65 "INSERT OR REPLACE INTO package_registry (package_name, namespace, version, manifest)
66 VALUES (?1, ?2, ?3, ?4)",
67 params![package_name, namespace, version, manifest],
68 )
69 .storage_err()?;
70 Ok(())
71 }
72
73 pub fn get_packages_for_namespace(
75 &self,
76 namespace: &str,
77 ) -> Result<Vec<PackageRegistryEntry>, CodememError> {
78 let conn = self.conn()?;
79 let mut stmt = conn
80 .prepare(
81 "SELECT package_name, namespace, version, manifest
82 FROM package_registry WHERE namespace = ?1",
83 )
84 .storage_err()?;
85 let rows = stmt
86 .query_map(params![namespace], |row| {
87 Ok(PackageRegistryEntry {
88 package_name: row.get(0)?,
89 namespace: row.get(1)?,
90 version: row.get(2)?,
91 manifest: row.get(3)?,
92 })
93 })
94 .storage_err()?;
95 let mut entries = Vec::new();
96 for row in rows {
97 entries.push(row.storage_err()?);
98 }
99 Ok(entries)
100 }
101
102 pub fn find_namespace_for_package(
104 &self,
105 package_name: &str,
106 ) -> Result<Vec<PackageRegistryEntry>, CodememError> {
107 let conn = self.conn()?;
108 let mut stmt = conn
109 .prepare(
110 "SELECT package_name, namespace, version, manifest
111 FROM package_registry WHERE package_name = ?1",
112 )
113 .storage_err()?;
114 let rows = stmt
115 .query_map(params![package_name], |row| {
116 Ok(PackageRegistryEntry {
117 package_name: row.get(0)?,
118 namespace: row.get(1)?,
119 version: row.get(2)?,
120 manifest: row.get(3)?,
121 })
122 })
123 .storage_err()?;
124 let mut entries = Vec::new();
125 for row in rows {
126 entries.push(row.storage_err()?);
127 }
128 Ok(entries)
129 }
130
131 pub fn delete_package_registry_for_namespace(
133 &self,
134 namespace: &str,
135 ) -> Result<usize, CodememError> {
136 let conn = self.conn()?;
137 let deleted = conn
138 .execute(
139 "DELETE FROM package_registry WHERE namespace = ?1",
140 params![namespace],
141 )
142 .storage_err()?;
143 Ok(deleted)
144 }
145
146 pub fn insert_unresolved_ref(&self, entry: &UnresolvedRefEntry) -> Result<(), CodememError> {
150 let conn = self.conn()?;
151 conn.execute(
152 "INSERT OR REPLACE INTO unresolved_refs
153 (id, namespace, source_node, target_name, package_hint, ref_kind, file_path, line, created_at)
154 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)",
155 params![
156 entry.id,
157 entry.namespace,
158 entry.source_node,
159 entry.target_name,
160 entry.package_hint,
161 entry.ref_kind,
162 entry.file_path,
163 entry.line,
164 entry.created_at,
165 ],
166 )
167 .storage_err()?;
168 Ok(())
169 }
170
171 pub fn insert_unresolved_refs_batch(
173 &self,
174 refs: &[UnresolvedRefEntry],
175 ) -> Result<(), CodememError> {
176 if refs.is_empty() {
177 return Ok(());
178 }
179 let conn = self.conn()?;
180 let tx = conn.unchecked_transaction().storage_err()?;
181
182 const COLS: usize = 9;
183 const BATCH: usize = 999 / COLS; for chunk in refs.chunks(BATCH) {
186 let mut placeholders = String::new();
187 for (r, _) in chunk.iter().enumerate() {
188 if r > 0 {
189 placeholders.push(',');
190 }
191 placeholders.push('(');
192 for c in 0..COLS {
193 if c > 0 {
194 placeholders.push(',');
195 }
196 placeholders.push('?');
197 placeholders.push_str(&(r * COLS + c + 1).to_string());
198 }
199 placeholders.push(')');
200 }
201
202 let sql = format!(
203 "INSERT OR REPLACE INTO unresolved_refs
204 (id, namespace, source_node, target_name, package_hint, ref_kind, file_path, line, created_at)
205 VALUES {placeholders}"
206 );
207
208 let mut param_values: Vec<Box<dyn rusqlite::types::ToSql>> = Vec::new();
209 for entry in chunk {
210 param_values.push(Box::new(entry.id.clone()));
211 param_values.push(Box::new(entry.namespace.clone()));
212 param_values.push(Box::new(entry.source_node.clone()));
213 param_values.push(Box::new(entry.target_name.clone()));
214 param_values.push(Box::new(entry.package_hint.clone()));
215 param_values.push(Box::new(entry.ref_kind.clone()));
216 param_values.push(Box::new(entry.file_path.clone()));
217 param_values.push(Box::new(entry.line));
218 param_values.push(Box::new(entry.created_at));
219 }
220 let param_refs: Vec<&dyn rusqlite::types::ToSql> =
221 param_values.iter().map(|p| p.as_ref()).collect();
222
223 tx.execute(&sql, param_refs.as_slice()).storage_err()?;
224 }
225
226 tx.commit().storage_err()?;
227 Ok(())
228 }
229
230 pub fn get_unresolved_refs_for_namespace(
232 &self,
233 namespace: &str,
234 ) -> Result<Vec<UnresolvedRefEntry>, CodememError> {
235 let conn = self.conn()?;
236 let mut stmt = conn
237 .prepare(
238 "SELECT id, namespace, source_node, target_name, package_hint, ref_kind, file_path, line, created_at
239 FROM unresolved_refs WHERE namespace = ?1",
240 )
241 .storage_err()?;
242 let rows = stmt
243 .query_map(params![namespace], |row| {
244 Ok(UnresolvedRefEntry {
245 id: row.get(0)?,
246 namespace: row.get(1)?,
247 source_node: row.get(2)?,
248 target_name: row.get(3)?,
249 package_hint: row.get(4)?,
250 ref_kind: row.get(5)?,
251 file_path: row.get(6)?,
252 line: row.get(7)?,
253 created_at: row.get(8)?,
254 })
255 })
256 .storage_err()?;
257 let mut entries = Vec::new();
258 for row in rows {
259 entries.push(row.storage_err()?);
260 }
261 Ok(entries)
262 }
263
264 pub fn get_unresolved_refs_for_package_hint(
266 &self,
267 package_hint: &str,
268 ) -> Result<Vec<UnresolvedRefEntry>, CodememError> {
269 let conn = self.conn()?;
270 let mut stmt = conn
271 .prepare(
272 "SELECT id, namespace, source_node, target_name, package_hint, ref_kind, file_path, line, created_at
273 FROM unresolved_refs WHERE package_hint = ?1",
274 )
275 .storage_err()?;
276 let rows = stmt
277 .query_map(params![package_hint], |row| {
278 Ok(UnresolvedRefEntry {
279 id: row.get(0)?,
280 namespace: row.get(1)?,
281 source_node: row.get(2)?,
282 target_name: row.get(3)?,
283 package_hint: row.get(4)?,
284 ref_kind: row.get(5)?,
285 file_path: row.get(6)?,
286 line: row.get(7)?,
287 created_at: row.get(8)?,
288 })
289 })
290 .storage_err()?;
291 let mut entries = Vec::new();
292 for row in rows {
293 entries.push(row.storage_err()?);
294 }
295 Ok(entries)
296 }
297
298 pub fn delete_unresolved_ref(&self, id: &str) -> Result<(), CodememError> {
300 let conn = self.conn()?;
301 conn.execute("DELETE FROM unresolved_refs WHERE id = ?1", params![id])
302 .storage_err()?;
303 Ok(())
304 }
305
306 pub fn delete_unresolved_refs_batch(&self, ids: &[String]) -> Result<(), CodememError> {
308 if ids.is_empty() {
309 return Ok(());
310 }
311 let conn = self.conn()?;
312 let tx = conn.unchecked_transaction().storage_err()?;
313
314 for chunk in ids.chunks(999) {
316 let placeholders: Vec<String> = (1..=chunk.len()).map(|i| format!("?{i}")).collect();
317 let sql = format!(
318 "DELETE FROM unresolved_refs WHERE id IN ({})",
319 placeholders.join(",")
320 );
321 let param_refs: Vec<&dyn rusqlite::types::ToSql> = chunk
322 .iter()
323 .map(|s| s as &dyn rusqlite::types::ToSql)
324 .collect();
325 tx.execute(&sql, param_refs.as_slice()).storage_err()?;
326 }
327
328 tx.commit().storage_err()?;
329 Ok(())
330 }
331
332 pub fn delete_unresolved_refs_for_namespace(
334 &self,
335 namespace: &str,
336 ) -> Result<usize, CodememError> {
337 let conn = self.conn()?;
338 let deleted = conn
339 .execute(
340 "DELETE FROM unresolved_refs WHERE namespace = ?1",
341 params![namespace],
342 )
343 .storage_err()?;
344 Ok(deleted)
345 }
346
347 pub fn upsert_api_endpoint(&self, endpoint: &ApiEndpointEntry) -> Result<(), CodememError> {
351 let conn = self.conn()?;
352 conn.execute(
353 "INSERT OR REPLACE INTO api_endpoints (id, namespace, method, path, handler, schema)
354 VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
355 params![
356 endpoint.id,
357 endpoint.namespace,
358 endpoint.method,
359 endpoint.path,
360 endpoint.handler,
361 endpoint.schema,
362 ],
363 )
364 .storage_err()?;
365 Ok(())
366 }
367
368 pub fn get_api_endpoints_for_namespace(
370 &self,
371 namespace: &str,
372 ) -> Result<Vec<ApiEndpointEntry>, CodememError> {
373 let conn = self.conn()?;
374 let mut stmt = conn
375 .prepare(
376 "SELECT id, namespace, method, path, handler, schema
377 FROM api_endpoints WHERE namespace = ?1",
378 )
379 .storage_err()?;
380 let rows = stmt
381 .query_map(params![namespace], |row| {
382 Ok(ApiEndpointEntry {
383 id: row.get(0)?,
384 namespace: row.get(1)?,
385 method: row.get(2)?,
386 path: row.get(3)?,
387 handler: row.get(4)?,
388 schema: row.get(5)?,
389 })
390 })
391 .storage_err()?;
392 let mut entries = Vec::new();
393 for row in rows {
394 entries.push(row.storage_err()?);
395 }
396 Ok(entries)
397 }
398
399 pub fn get_api_endpoints_for_path(
401 &self,
402 path: &str,
403 ) -> Result<Vec<ApiEndpointEntry>, CodememError> {
404 let conn = self.conn()?;
405 let mut stmt = conn
406 .prepare(
407 "SELECT id, namespace, method, path, handler, schema
408 FROM api_endpoints WHERE path = ?1",
409 )
410 .storage_err()?;
411 let rows = stmt
412 .query_map(params![path], |row| {
413 Ok(ApiEndpointEntry {
414 id: row.get(0)?,
415 namespace: row.get(1)?,
416 method: row.get(2)?,
417 path: row.get(3)?,
418 handler: row.get(4)?,
419 schema: row.get(5)?,
420 })
421 })
422 .storage_err()?;
423 let mut entries = Vec::new();
424 for row in rows {
425 entries.push(row.storage_err()?);
426 }
427 Ok(entries)
428 }
429
430 pub fn find_api_endpoints_by_path_pattern(
432 &self,
433 path_pattern: &str,
434 ) -> Result<Vec<ApiEndpointEntry>, CodememError> {
435 let conn = self.conn()?;
436 let mut stmt = conn
437 .prepare(
438 "SELECT id, namespace, method, path, handler, schema
439 FROM api_endpoints WHERE path LIKE ?1",
440 )
441 .storage_err()?;
442 let rows = stmt
443 .query_map(params![path_pattern], |row| {
444 Ok(ApiEndpointEntry {
445 id: row.get(0)?,
446 namespace: row.get(1)?,
447 method: row.get(2)?,
448 path: row.get(3)?,
449 handler: row.get(4)?,
450 schema: row.get(5)?,
451 })
452 })
453 .storage_err()?;
454 let mut entries = Vec::new();
455 for row in rows {
456 entries.push(row.storage_err()?);
457 }
458 Ok(entries)
459 }
460
461 pub fn delete_api_endpoints_for_namespace(
463 &self,
464 namespace: &str,
465 ) -> Result<usize, CodememError> {
466 let conn = self.conn()?;
467 let deleted = conn
468 .execute(
469 "DELETE FROM api_endpoints WHERE namespace = ?1",
470 params![namespace],
471 )
472 .storage_err()?;
473 Ok(deleted)
474 }
475
476 pub fn upsert_api_client_call(
480 &self,
481 id: &str,
482 namespace: &str,
483 method: Option<&str>,
484 target: &str,
485 caller: &str,
486 library: &str,
487 ) -> Result<(), CodememError> {
488 let conn = self.conn()?;
489 conn.execute(
490 "INSERT OR REPLACE INTO api_client_calls (id, namespace, method, target, caller, library)
491 VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
492 params![id, namespace, method, target, caller, library],
493 )
494 .storage_err()?;
495 Ok(())
496 }
497
498 pub fn get_api_client_calls_for_namespace(
500 &self,
501 namespace: &str,
502 ) -> Result<Vec<ApiClientCallEntry>, CodememError> {
503 let conn = self.conn()?;
504 let mut stmt = conn
505 .prepare(
506 "SELECT id, namespace, method, target, caller, library
507 FROM api_client_calls WHERE namespace = ?1",
508 )
509 .storage_err()?;
510 let rows = stmt
511 .query_map(params![namespace], |row| {
512 Ok(ApiClientCallEntry {
513 id: row.get(0)?,
514 namespace: row.get(1)?,
515 method: row.get(2)?,
516 target: row.get(3)?,
517 caller: row.get(4)?,
518 library: row.get(5)?,
519 })
520 })
521 .storage_err()?;
522 let mut entries = Vec::new();
523 for row in rows {
524 entries.push(row.storage_err()?);
525 }
526 Ok(entries)
527 }
528
529 pub fn get_cross_namespace_edges(&self, namespace: &str) -> Result<Vec<Edge>, CodememError> {
536 let all_edges = self.graph_edges_for_namespace_with_cross(namespace, true)?;
538 Ok(all_edges
539 .into_iter()
540 .filter(|e| {
541 e.properties
542 .get("cross_namespace")
543 .and_then(|v| v.as_bool())
544 .unwrap_or(false)
545 })
546 .collect())
547 }
548}
549
550#[cfg(test)]
551#[path = "tests/cross_repo_tests.rs"]
552mod tests;