tessera_codegraph/
engine.rs1use std::path::Path;
11
12use anyhow::Result;
13use rusqlite::Connection;
14
15use crate::types::GraphEngineKind;
16
17pub trait GraphEngine {
18 fn transitive_callers(&self, target: &str, depth: usize) -> Result<Vec<(i64, usize)>>;
21
22 fn kind(&self) -> GraphEngineKind;
23}
24
25pub struct SqliteEngine<'a> {
26 pub conn: &'a Connection,
27}
28
29impl<'a> SqliteEngine<'a> {
30 pub fn new(conn: &'a Connection) -> Self {
31 Self { conn }
32 }
33}
34
35impl<'a> GraphEngine for SqliteEngine<'a> {
36 fn transitive_callers(&self, target: &str, depth: usize) -> Result<Vec<(i64, usize)>> {
37 use std::collections::{HashSet, VecDeque};
38 let mut out: Vec<(i64, usize)> = Vec::new();
39 let mut seen: HashSet<i64> = HashSet::new();
40 let mut queue: VecDeque<(String, usize)> = VecDeque::new();
41 queue.push_back((target.to_string(), 1));
42
43 let mut stmt = self.conn.prepare(
44 "
45 SELECT DISTINCT s.id, s.name, s.qualified_name
46 FROM edges e
47 JOIN symbols s ON s.id = e.from_symbol_id
48 WHERE e.to_symbol_name = ?1 OR e.to_symbol_name LIKE ?2
49 LIMIT 500
50 ",
51 )?;
52
53 while let Some((current, current_depth)) = queue.pop_front() {
54 if current_depth > depth {
55 continue;
56 }
57 let like = format!("%.{}", current);
58 let rows = stmt
59 .query_map(rusqlite::params![current, like], |row| {
60 let id: i64 = row.get(0)?;
61 let name: String = row.get(1)?;
62 let qname: String = row.get(2)?;
63 Ok((id, name, qname))
64 })?
65 .collect::<rusqlite::Result<Vec<_>>>()?;
66
67 for (id, name, qname) in rows {
68 if !seen.insert(id) {
69 continue;
70 }
71 out.push((id, current_depth));
72 if current_depth < depth {
73 queue.push_back((name, current_depth + 1));
74 if qname != current {
75 queue.push_back((qname, current_depth + 1));
76 }
77 }
78 }
79 }
80 Ok(out)
81 }
82
83 fn kind(&self) -> GraphEngineKind {
84 GraphEngineKind::Sqlite
85 }
86}
87
88pub fn select_engine<'a>(
92 kind: GraphEngineKind,
93 conn: &'a Connection,
94 _db_path: &Path,
95) -> Result<Box<dyn GraphEngine + 'a>> {
96 match kind {
97 GraphEngineKind::Sqlite => Ok(Box::new(SqliteEngine::new(conn))),
98 GraphEngineKind::Cozo => cozo_engine(conn, _db_path),
99 }
100}
101
102fn cozo_engine<'a>(_conn: &'a Connection, _db_path: &Path) -> Result<Box<dyn GraphEngine + 'a>> {
103 Err(anyhow::anyhow!(
104 "Cozo backend is a v0.3 deliverable — the GraphEngine trait is in place \
105 and the call-edge relation is documented, but the embedded Cozo dependency \
106 is deliberately not wired up in v0.2. Use the default SQLite backend."
107 ))
108}