lean_ctx/core/property_graph/
edge.rs1use rusqlite::{Connection, params};
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub enum EdgeKind {
7 Imports,
8 Calls,
9 Defines,
10 Exports,
11 TypeRef,
12 TestedBy,
13 ChangedIn,
14 BuiltIn,
15 MentionedIn,
16 Affects,
17 Breaks,
18 Module,
20 Cochange,
22 Implements,
24 Extends,
26 References,
29 Sibling,
31}
32
33impl EdgeKind {
34 pub fn as_str(&self) -> &'static str {
35 match self {
36 Self::Imports => "imports",
37 Self::Calls => "calls",
38 Self::Defines => "defines",
39 Self::Exports => "exports",
40 Self::TypeRef => "type_ref",
41 Self::TestedBy => "tested_by",
42 Self::ChangedIn => "changed_in",
43 Self::BuiltIn => "built_in",
44 Self::MentionedIn => "mentioned_in",
45 Self::Affects => "affects",
46 Self::Breaks => "breaks",
47 Self::Module => "module",
48 Self::Cochange => "cochange",
49 Self::Sibling => "sibling",
50 Self::Implements => "implements",
51 Self::Extends => "extends",
52 Self::References => "references",
53 }
54 }
55
56 pub fn parse(s: &str) -> Self {
57 match s {
58 "calls" => Self::Calls,
59 "defines" => Self::Defines,
60 "exports" => Self::Exports,
61 "type_ref" => Self::TypeRef,
62 "tested_by" => Self::TestedBy,
63 "changed_in" => Self::ChangedIn,
64 "built_in" => Self::BuiltIn,
65 "mentioned_in" => Self::MentionedIn,
66 "affects" => Self::Affects,
67 "breaks" => Self::Breaks,
68 "module" => Self::Module,
69 "cochange" => Self::Cochange,
70 "sibling" => Self::Sibling,
71 "implements" => Self::Implements,
72 "extends" => Self::Extends,
73 "references" => Self::References,
74 _ => Self::Imports,
75 }
76 }
77}
78
79#[derive(Debug, Clone)]
80pub struct Edge {
81 pub id: Option<i64>,
82 pub source_id: i64,
83 pub target_id: i64,
84 pub kind: EdgeKind,
85 pub metadata: Option<String>,
86}
87
88impl Edge {
89 pub fn new(source_id: i64, target_id: i64, kind: EdgeKind) -> Self {
90 Self {
91 id: None,
92 source_id,
93 target_id,
94 kind,
95 metadata: None,
96 }
97 }
98
99 pub fn with_metadata(mut self, meta: &str) -> Self {
100 self.metadata = Some(meta.to_string());
101 self
102 }
103}
104
105pub(super) fn upsert(conn: &Connection, edge: &Edge) -> anyhow::Result<()> {
106 conn.execute(
107 "INSERT INTO edges (source_id, target_id, kind, metadata)
108 VALUES (?1, ?2, ?3, ?4)
109 ON CONFLICT(source_id, target_id, kind) DO UPDATE SET
110 metadata = excluded.metadata",
111 params![
112 edge.source_id,
113 edge.target_id,
114 edge.kind.as_str(),
115 edge.metadata,
116 ],
117 )?;
118 Ok(())
119}
120
121pub(super) fn from_node(conn: &Connection, node_id: i64) -> anyhow::Result<Vec<Edge>> {
122 let mut stmt = conn.prepare(
123 "SELECT id, source_id, target_id, kind, metadata
124 FROM edges WHERE source_id = ?1",
125 )?;
126 let edges = stmt
127 .query_map(params![node_id], |row| {
128 Ok(Edge {
129 id: Some(row.get(0)?),
130 source_id: row.get(1)?,
131 target_id: row.get(2)?,
132 kind: EdgeKind::parse(&row.get::<_, String>(3)?),
133 metadata: row.get(4)?,
134 })
135 })?
136 .filter_map(std::result::Result::ok)
137 .collect();
138 Ok(edges)
139}
140
141pub(super) fn to_node(conn: &Connection, node_id: i64) -> anyhow::Result<Vec<Edge>> {
142 let mut stmt = conn.prepare(
143 "SELECT id, source_id, target_id, kind, metadata
144 FROM edges WHERE target_id = ?1",
145 )?;
146 let edges = stmt
147 .query_map(params![node_id], |row| {
148 Ok(Edge {
149 id: Some(row.get(0)?),
150 source_id: row.get(1)?,
151 target_id: row.get(2)?,
152 kind: EdgeKind::parse(&row.get::<_, String>(3)?),
153 metadata: row.get(4)?,
154 })
155 })?
156 .filter_map(std::result::Result::ok)
157 .collect();
158 Ok(edges)
159}
160
161pub(super) fn count(conn: &Connection) -> anyhow::Result<usize> {
162 let c: i64 = conn.query_row("SELECT COUNT(*) FROM edges", [], |row| row.get(0))?;
163 Ok(c as usize)
164}
165
166pub(super) fn metadata_of(
167 conn: &Connection,
168 source_id: i64,
169 target_id: i64,
170 kind: &EdgeKind,
171) -> anyhow::Result<Option<String>> {
172 use rusqlite::OptionalExtension;
173 Ok(conn
175 .query_row(
176 "SELECT metadata FROM edges WHERE source_id = ?1 AND target_id = ?2 AND kind = ?3",
177 params![source_id, target_id, kind.as_str()],
178 |r| r.get::<_, Option<String>>(0),
179 )
180 .optional()?
181 .flatten())
182}
183
184pub(super) fn file_edges_of_kind(
185 conn: &Connection,
186 kind: &EdgeKind,
187) -> anyhow::Result<Vec<(String, String, Option<String>)>> {
188 let mut stmt = conn.prepare(
189 "SELECT ps.path, pt.path, e.metadata
190 FROM edges e
191 JOIN nodes ns ON ns.id = e.source_id
192 JOIN nodes nt ON nt.id = e.target_id
193 JOIN paths ps ON ps.id = ns.file_id
194 JOIN paths pt ON pt.id = nt.file_id
195 WHERE e.kind = ?1 AND ns.kind = 'file' AND nt.kind = 'file'
196 ORDER BY ps.path, pt.path",
197 )?;
198 let rows = stmt
199 .query_map(params![kind.as_str()], |r| {
200 Ok((r.get(0)?, r.get(1)?, r.get(2)?))
201 })?
202 .collect::<Result<Vec<_>, _>>()?;
203 Ok(rows)
204}
205
206pub(super) fn remove_file_edge(
207 conn: &Connection,
208 source: &str,
209 target: &str,
210 kind: &EdgeKind,
211) -> anyhow::Result<()> {
212 conn.execute(
213 "DELETE FROM edges WHERE kind = ?3
214 AND source_id IN (SELECT n.id FROM nodes n JOIN paths p ON p.id = n.file_id
215 WHERE n.kind = 'file' AND p.path = ?1)
216 AND target_id IN (SELECT n.id FROM nodes n JOIN paths p ON p.id = n.file_id
217 WHERE n.kind = 'file' AND p.path = ?2)",
218 params![source, target, kind.as_str()],
219 )?;
220 Ok(())
221}