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
52#[derive(Debug, Clone)]
54pub struct EventChannelEntry {
55 pub id: String,
56 pub namespace: String,
57 pub channel: String,
58 pub direction: String,
59 pub protocol: String,
60 pub message_schema: String,
61 pub description: String,
62 pub handler: String,
63 pub spec_file: String,
64}
65
66impl Storage {
67 pub fn upsert_package_registry(
71 &self,
72 package_name: &str,
73 namespace: &str,
74 version: &str,
75 manifest: &str,
76 ) -> Result<(), CodememError> {
77 let conn = self.conn()?;
78 conn.execute(
79 "INSERT OR REPLACE INTO package_registry (package_name, namespace, version, manifest)
80 VALUES (?1, ?2, ?3, ?4)",
81 params![package_name, namespace, version, manifest],
82 )
83 .storage_err()?;
84 Ok(())
85 }
86
87 pub fn get_packages_for_namespace(
89 &self,
90 namespace: &str,
91 ) -> Result<Vec<PackageRegistryEntry>, CodememError> {
92 let conn = self.conn()?;
93 let mut stmt = conn
94 .prepare(
95 "SELECT package_name, namespace, version, manifest
96 FROM package_registry WHERE namespace = ?1",
97 )
98 .storage_err()?;
99 let rows = stmt
100 .query_map(params![namespace], |row| {
101 Ok(PackageRegistryEntry {
102 package_name: row.get(0)?,
103 namespace: row.get(1)?,
104 version: row.get(2)?,
105 manifest: row.get(3)?,
106 })
107 })
108 .storage_err()?;
109 let mut entries = Vec::new();
110 for row in rows {
111 entries.push(row.storage_err()?);
112 }
113 Ok(entries)
114 }
115
116 pub fn find_namespace_for_package(
118 &self,
119 package_name: &str,
120 ) -> Result<Vec<PackageRegistryEntry>, CodememError> {
121 let conn = self.conn()?;
122 let mut stmt = conn
123 .prepare(
124 "SELECT package_name, namespace, version, manifest
125 FROM package_registry WHERE package_name = ?1",
126 )
127 .storage_err()?;
128 let rows = stmt
129 .query_map(params![package_name], |row| {
130 Ok(PackageRegistryEntry {
131 package_name: row.get(0)?,
132 namespace: row.get(1)?,
133 version: row.get(2)?,
134 manifest: row.get(3)?,
135 })
136 })
137 .storage_err()?;
138 let mut entries = Vec::new();
139 for row in rows {
140 entries.push(row.storage_err()?);
141 }
142 Ok(entries)
143 }
144
145 pub fn delete_package_registry_for_namespace(
147 &self,
148 namespace: &str,
149 ) -> Result<usize, CodememError> {
150 let conn = self.conn()?;
151 let deleted = conn
152 .execute(
153 "DELETE FROM package_registry WHERE namespace = ?1",
154 params![namespace],
155 )
156 .storage_err()?;
157 Ok(deleted)
158 }
159
160 pub fn insert_unresolved_ref(&self, entry: &UnresolvedRefEntry) -> Result<(), CodememError> {
164 let conn = self.conn()?;
165 conn.execute(
166 "INSERT OR REPLACE INTO unresolved_refs
167 (id, namespace, source_node, target_name, package_hint, ref_kind, file_path, line, created_at)
168 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)",
169 params![
170 entry.id,
171 entry.namespace,
172 entry.source_node,
173 entry.target_name,
174 entry.package_hint,
175 entry.ref_kind,
176 entry.file_path,
177 entry.line,
178 entry.created_at,
179 ],
180 )
181 .storage_err()?;
182 Ok(())
183 }
184
185 pub fn insert_unresolved_refs_batch(
187 &self,
188 refs: &[UnresolvedRefEntry],
189 ) -> Result<(), CodememError> {
190 if refs.is_empty() {
191 return Ok(());
192 }
193 let conn = self.conn()?;
194 let tx = conn.unchecked_transaction().storage_err()?;
195
196 const COLS: usize = 9;
197 const BATCH: usize = 999 / COLS; for chunk in refs.chunks(BATCH) {
200 let mut placeholders = String::new();
201 for (r, _) in chunk.iter().enumerate() {
202 if r > 0 {
203 placeholders.push(',');
204 }
205 placeholders.push('(');
206 for c in 0..COLS {
207 if c > 0 {
208 placeholders.push(',');
209 }
210 placeholders.push('?');
211 placeholders.push_str(&(r * COLS + c + 1).to_string());
212 }
213 placeholders.push(')');
214 }
215
216 let sql = format!(
217 "INSERT OR REPLACE INTO unresolved_refs
218 (id, namespace, source_node, target_name, package_hint, ref_kind, file_path, line, created_at)
219 VALUES {placeholders}"
220 );
221
222 let mut param_values: Vec<Box<dyn rusqlite::types::ToSql>> = Vec::new();
223 for entry in chunk {
224 param_values.push(Box::new(entry.id.clone()));
225 param_values.push(Box::new(entry.namespace.clone()));
226 param_values.push(Box::new(entry.source_node.clone()));
227 param_values.push(Box::new(entry.target_name.clone()));
228 param_values.push(Box::new(entry.package_hint.clone()));
229 param_values.push(Box::new(entry.ref_kind.clone()));
230 param_values.push(Box::new(entry.file_path.clone()));
231 param_values.push(Box::new(entry.line));
232 param_values.push(Box::new(entry.created_at));
233 }
234 let param_refs: Vec<&dyn rusqlite::types::ToSql> =
235 param_values.iter().map(|p| p.as_ref()).collect();
236
237 tx.execute(&sql, param_refs.as_slice()).storage_err()?;
238 }
239
240 tx.commit().storage_err()?;
241 Ok(())
242 }
243
244 pub fn get_unresolved_refs_for_namespace(
246 &self,
247 namespace: &str,
248 ) -> Result<Vec<UnresolvedRefEntry>, CodememError> {
249 let conn = self.conn()?;
250 let mut stmt = conn
251 .prepare(
252 "SELECT id, namespace, source_node, target_name, package_hint, ref_kind, file_path, line, created_at
253 FROM unresolved_refs WHERE namespace = ?1",
254 )
255 .storage_err()?;
256 let rows = stmt
257 .query_map(params![namespace], |row| {
258 Ok(UnresolvedRefEntry {
259 id: row.get(0)?,
260 namespace: row.get(1)?,
261 source_node: row.get(2)?,
262 target_name: row.get(3)?,
263 package_hint: row.get(4)?,
264 ref_kind: row.get(5)?,
265 file_path: row.get(6)?,
266 line: row.get(7)?,
267 created_at: row.get(8)?,
268 })
269 })
270 .storage_err()?;
271 let mut entries = Vec::new();
272 for row in rows {
273 entries.push(row.storage_err()?);
274 }
275 Ok(entries)
276 }
277
278 pub fn get_unresolved_refs_for_package_hint(
280 &self,
281 package_hint: &str,
282 ) -> Result<Vec<UnresolvedRefEntry>, CodememError> {
283 let conn = self.conn()?;
284 let mut stmt = conn
285 .prepare(
286 "SELECT id, namespace, source_node, target_name, package_hint, ref_kind, file_path, line, created_at
287 FROM unresolved_refs WHERE package_hint = ?1",
288 )
289 .storage_err()?;
290 let rows = stmt
291 .query_map(params![package_hint], |row| {
292 Ok(UnresolvedRefEntry {
293 id: row.get(0)?,
294 namespace: row.get(1)?,
295 source_node: row.get(2)?,
296 target_name: row.get(3)?,
297 package_hint: row.get(4)?,
298 ref_kind: row.get(5)?,
299 file_path: row.get(6)?,
300 line: row.get(7)?,
301 created_at: row.get(8)?,
302 })
303 })
304 .storage_err()?;
305 let mut entries = Vec::new();
306 for row in rows {
307 entries.push(row.storage_err()?);
308 }
309 Ok(entries)
310 }
311
312 pub fn delete_unresolved_ref(&self, id: &str) -> Result<(), CodememError> {
314 let conn = self.conn()?;
315 conn.execute("DELETE FROM unresolved_refs WHERE id = ?1", params![id])
316 .storage_err()?;
317 Ok(())
318 }
319
320 pub fn delete_unresolved_refs_batch(&self, ids: &[String]) -> Result<(), CodememError> {
322 if ids.is_empty() {
323 return Ok(());
324 }
325 let conn = self.conn()?;
326 let tx = conn.unchecked_transaction().storage_err()?;
327
328 for chunk in ids.chunks(999) {
330 let placeholders: Vec<String> = (1..=chunk.len()).map(|i| format!("?{i}")).collect();
331 let sql = format!(
332 "DELETE FROM unresolved_refs WHERE id IN ({})",
333 placeholders.join(",")
334 );
335 let param_refs: Vec<&dyn rusqlite::types::ToSql> = chunk
336 .iter()
337 .map(|s| s as &dyn rusqlite::types::ToSql)
338 .collect();
339 tx.execute(&sql, param_refs.as_slice()).storage_err()?;
340 }
341
342 tx.commit().storage_err()?;
343 Ok(())
344 }
345
346 pub fn delete_unresolved_refs_for_namespace(
348 &self,
349 namespace: &str,
350 ) -> Result<usize, CodememError> {
351 let conn = self.conn()?;
352 let deleted = conn
353 .execute(
354 "DELETE FROM unresolved_refs WHERE namespace = ?1",
355 params![namespace],
356 )
357 .storage_err()?;
358 Ok(deleted)
359 }
360
361 pub fn upsert_api_endpoint(&self, endpoint: &ApiEndpointEntry) -> Result<(), CodememError> {
365 let conn = self.conn()?;
366 conn.execute(
367 "INSERT OR REPLACE INTO api_endpoints (id, namespace, method, path, handler, schema)
368 VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
369 params![
370 endpoint.id,
371 endpoint.namespace,
372 endpoint.method,
373 endpoint.path,
374 endpoint.handler,
375 endpoint.schema,
376 ],
377 )
378 .storage_err()?;
379 Ok(())
380 }
381
382 pub fn get_api_endpoints_for_namespace(
384 &self,
385 namespace: &str,
386 ) -> Result<Vec<ApiEndpointEntry>, CodememError> {
387 let conn = self.conn()?;
388 let mut stmt = conn
389 .prepare(
390 "SELECT id, namespace, method, path, handler, schema
391 FROM api_endpoints WHERE namespace = ?1",
392 )
393 .storage_err()?;
394 let rows = stmt
395 .query_map(params![namespace], |row| {
396 Ok(ApiEndpointEntry {
397 id: row.get(0)?,
398 namespace: row.get(1)?,
399 method: row.get(2)?,
400 path: row.get(3)?,
401 handler: row.get(4)?,
402 schema: row.get(5)?,
403 })
404 })
405 .storage_err()?;
406 let mut entries = Vec::new();
407 for row in rows {
408 entries.push(row.storage_err()?);
409 }
410 Ok(entries)
411 }
412
413 pub fn get_api_endpoints_for_path(
415 &self,
416 path: &str,
417 ) -> Result<Vec<ApiEndpointEntry>, CodememError> {
418 let conn = self.conn()?;
419 let mut stmt = conn
420 .prepare(
421 "SELECT id, namespace, method, path, handler, schema
422 FROM api_endpoints WHERE path = ?1",
423 )
424 .storage_err()?;
425 let rows = stmt
426 .query_map(params![path], |row| {
427 Ok(ApiEndpointEntry {
428 id: row.get(0)?,
429 namespace: row.get(1)?,
430 method: row.get(2)?,
431 path: row.get(3)?,
432 handler: row.get(4)?,
433 schema: row.get(5)?,
434 })
435 })
436 .storage_err()?;
437 let mut entries = Vec::new();
438 for row in rows {
439 entries.push(row.storage_err()?);
440 }
441 Ok(entries)
442 }
443
444 pub fn find_api_endpoints_by_path_pattern(
446 &self,
447 path_pattern: &str,
448 ) -> Result<Vec<ApiEndpointEntry>, CodememError> {
449 let conn = self.conn()?;
450 let mut stmt = conn
451 .prepare(
452 "SELECT id, namespace, method, path, handler, schema
453 FROM api_endpoints WHERE path LIKE ?1",
454 )
455 .storage_err()?;
456 let rows = stmt
457 .query_map(params![path_pattern], |row| {
458 Ok(ApiEndpointEntry {
459 id: row.get(0)?,
460 namespace: row.get(1)?,
461 method: row.get(2)?,
462 path: row.get(3)?,
463 handler: row.get(4)?,
464 schema: row.get(5)?,
465 })
466 })
467 .storage_err()?;
468 let mut entries = Vec::new();
469 for row in rows {
470 entries.push(row.storage_err()?);
471 }
472 Ok(entries)
473 }
474
475 pub fn delete_api_endpoints_for_namespace(
477 &self,
478 namespace: &str,
479 ) -> Result<usize, CodememError> {
480 let conn = self.conn()?;
481 let deleted = conn
482 .execute(
483 "DELETE FROM api_endpoints WHERE namespace = ?1",
484 params![namespace],
485 )
486 .storage_err()?;
487 Ok(deleted)
488 }
489
490 pub fn upsert_api_client_call(
494 &self,
495 id: &str,
496 namespace: &str,
497 method: Option<&str>,
498 target: &str,
499 caller: &str,
500 library: &str,
501 ) -> Result<(), CodememError> {
502 let conn = self.conn()?;
503 conn.execute(
504 "INSERT OR REPLACE INTO api_client_calls (id, namespace, method, target, caller, library)
505 VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
506 params![id, namespace, method, target, caller, library],
507 )
508 .storage_err()?;
509 Ok(())
510 }
511
512 pub fn get_api_client_calls_for_namespace(
514 &self,
515 namespace: &str,
516 ) -> Result<Vec<ApiClientCallEntry>, CodememError> {
517 let conn = self.conn()?;
518 let mut stmt = conn
519 .prepare(
520 "SELECT id, namespace, method, target, caller, library
521 FROM api_client_calls WHERE namespace = ?1",
522 )
523 .storage_err()?;
524 let rows = stmt
525 .query_map(params![namespace], |row| {
526 Ok(ApiClientCallEntry {
527 id: row.get(0)?,
528 namespace: row.get(1)?,
529 method: row.get(2)?,
530 target: row.get(3)?,
531 caller: row.get(4)?,
532 library: row.get(5)?,
533 })
534 })
535 .storage_err()?;
536 let mut entries = Vec::new();
537 for row in rows {
538 entries.push(row.storage_err()?);
539 }
540 Ok(entries)
541 }
542
543 pub fn upsert_event_channel(&self, entry: &EventChannelEntry) -> Result<(), CodememError> {
549 let conn = self.conn()?;
550 conn.execute(
551 "INSERT OR REPLACE INTO event_channels (id, namespace, channel, direction, protocol, message_schema, description, handler, spec_file)
552 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)",
553 params![
554 entry.id,
555 entry.namespace,
556 entry.channel,
557 entry.direction,
558 entry.protocol,
559 entry.message_schema,
560 entry.description,
561 entry.handler,
562 entry.spec_file,
563 ],
564 )
565 .storage_err()?;
566 Ok(())
567 }
568
569 pub fn get_event_channels_for_namespace(
571 &self,
572 namespace: &str,
573 ) -> Result<Vec<EventChannelEntry>, CodememError> {
574 let conn = self.conn()?;
575 let mut stmt = conn
576 .prepare(
577 "SELECT id, namespace, channel, direction, protocol, message_schema, description, handler, spec_file
578 FROM event_channels WHERE namespace = ?1",
579 )
580 .storage_err()?;
581 let rows = stmt
582 .query_map(params![namespace], |row| {
583 Ok(EventChannelEntry {
584 id: row.get(0)?,
585 namespace: row.get(1)?,
586 channel: row.get(2)?,
587 direction: row.get(3)?,
588 protocol: row.get(4)?,
589 message_schema: row.get(5)?,
590 description: row.get(6)?,
591 handler: row.get(7)?,
592 spec_file: row.get(8)?,
593 })
594 })
595 .storage_err()?;
596 let mut entries = Vec::new();
597 for row in rows {
598 entries.push(row.storage_err()?);
599 }
600 Ok(entries)
601 }
602
603 pub fn get_all_event_channels(&self) -> Result<Vec<EventChannelEntry>, CodememError> {
605 let conn = self.conn()?;
606 let mut stmt = conn
607 .prepare(
608 "SELECT id, namespace, channel, direction, protocol, message_schema, description, handler, spec_file
609 FROM event_channels",
610 )
611 .storage_err()?;
612 let rows = stmt
613 .query_map([], |row| {
614 Ok(EventChannelEntry {
615 id: row.get(0)?,
616 namespace: row.get(1)?,
617 channel: row.get(2)?,
618 direction: row.get(3)?,
619 protocol: row.get(4)?,
620 message_schema: row.get(5)?,
621 description: row.get(6)?,
622 handler: row.get(7)?,
623 spec_file: row.get(8)?,
624 })
625 })
626 .storage_err()?;
627 let mut entries = Vec::new();
628 for row in rows {
629 entries.push(row.storage_err()?);
630 }
631 Ok(entries)
632 }
633
634 pub fn get_cross_namespace_edges(&self, namespace: &str) -> Result<Vec<Edge>, CodememError> {
641 let all_edges = self.graph_edges_for_namespace_with_cross(namespace, true)?;
643 Ok(all_edges
644 .into_iter()
645 .filter(|e| {
646 e.properties
647 .get("cross_namespace")
648 .and_then(|v| v.as_bool())
649 .unwrap_or(false)
650 })
651 .collect())
652 }
653}
654
655#[cfg(test)]
656#[path = "tests/cross_repo_tests.rs"]
657mod tests;