Skip to main content

lean_ctx/core/property_graph/
edge.rs

1//! Edge types and CRUD operations for graph edges.
2
3use rusqlite::{params, Connection};
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    /// Implicit module/package/re-export relationship (from graph_index)
19    Module,
20    /// Git co-change correlation (files frequently changed together)
21    Cochange,
22    /// Sibling/orphan rescue edge (fallback connectivity)
23    Sibling,
24}
25
26impl EdgeKind {
27    pub fn as_str(&self) -> &'static str {
28        match self {
29            Self::Imports => "imports",
30            Self::Calls => "calls",
31            Self::Defines => "defines",
32            Self::Exports => "exports",
33            Self::TypeRef => "type_ref",
34            Self::TestedBy => "tested_by",
35            Self::ChangedIn => "changed_in",
36            Self::BuiltIn => "built_in",
37            Self::MentionedIn => "mentioned_in",
38            Self::Affects => "affects",
39            Self::Breaks => "breaks",
40            Self::Module => "module",
41            Self::Cochange => "cochange",
42            Self::Sibling => "sibling",
43        }
44    }
45
46    pub fn parse(s: &str) -> Self {
47        match s {
48            "calls" => Self::Calls,
49            "defines" => Self::Defines,
50            "exports" => Self::Exports,
51            "type_ref" => Self::TypeRef,
52            "tested_by" => Self::TestedBy,
53            "changed_in" => Self::ChangedIn,
54            "built_in" => Self::BuiltIn,
55            "mentioned_in" => Self::MentionedIn,
56            "affects" => Self::Affects,
57            "breaks" => Self::Breaks,
58            "module" => Self::Module,
59            "cochange" => Self::Cochange,
60            "sibling" => Self::Sibling,
61            _ => Self::Imports,
62        }
63    }
64}
65
66#[derive(Debug, Clone)]
67pub struct Edge {
68    pub id: Option<i64>,
69    pub source_id: i64,
70    pub target_id: i64,
71    pub kind: EdgeKind,
72    pub metadata: Option<String>,
73}
74
75impl Edge {
76    pub fn new(source_id: i64, target_id: i64, kind: EdgeKind) -> Self {
77        Self {
78            id: None,
79            source_id,
80            target_id,
81            kind,
82            metadata: None,
83        }
84    }
85
86    pub fn with_metadata(mut self, meta: &str) -> Self {
87        self.metadata = Some(meta.to_string());
88        self
89    }
90}
91
92pub(super) fn upsert(conn: &Connection, edge: &Edge) -> anyhow::Result<()> {
93    conn.execute(
94        "INSERT INTO edges (source_id, target_id, kind, metadata)
95         VALUES (?1, ?2, ?3, ?4)
96         ON CONFLICT(source_id, target_id, kind) DO UPDATE SET
97            metadata = excluded.metadata",
98        params![
99            edge.source_id,
100            edge.target_id,
101            edge.kind.as_str(),
102            edge.metadata,
103        ],
104    )?;
105    Ok(())
106}
107
108pub(super) fn from_node(conn: &Connection, node_id: i64) -> anyhow::Result<Vec<Edge>> {
109    let mut stmt = conn.prepare(
110        "SELECT id, source_id, target_id, kind, metadata
111         FROM edges WHERE source_id = ?1",
112    )?;
113    let edges = stmt
114        .query_map(params![node_id], |row| {
115            Ok(Edge {
116                id: Some(row.get(0)?),
117                source_id: row.get(1)?,
118                target_id: row.get(2)?,
119                kind: EdgeKind::parse(&row.get::<_, String>(3)?),
120                metadata: row.get(4)?,
121            })
122        })?
123        .filter_map(std::result::Result::ok)
124        .collect();
125    Ok(edges)
126}
127
128pub(super) fn to_node(conn: &Connection, node_id: i64) -> anyhow::Result<Vec<Edge>> {
129    let mut stmt = conn.prepare(
130        "SELECT id, source_id, target_id, kind, metadata
131         FROM edges WHERE target_id = ?1",
132    )?;
133    let edges = stmt
134        .query_map(params![node_id], |row| {
135            Ok(Edge {
136                id: Some(row.get(0)?),
137                source_id: row.get(1)?,
138                target_id: row.get(2)?,
139                kind: EdgeKind::parse(&row.get::<_, String>(3)?),
140                metadata: row.get(4)?,
141            })
142        })?
143        .filter_map(std::result::Result::ok)
144        .collect();
145    Ok(edges)
146}
147
148pub(super) fn count(conn: &Connection) -> anyhow::Result<usize> {
149    let c: i64 = conn.query_row("SELECT COUNT(*) FROM edges", [], |row| row.get(0))?;
150    Ok(c as usize)
151}