1use rusqlite::params;
2
3use crate::sqlite::SqliteGraphStore;
4use crate::store::GraphStore;
5use crate::types::{GraphError, GraphNode};
6
7impl SqliteGraphStore {
8 pub fn load_circuit_breaker(&self) -> Result<Vec<(String, String, u32, bool)>, GraphError> {
10 let mut stmt = self.conn.prepare(
11 "SELECT error_code, hash, consecutive_failures, downgraded FROM circuit_breaker",
12 )?;
13 let rows = stmt
14 .query_map([], |row| {
15 Ok((
16 row.get::<_, String>(0)?,
17 row.get::<_, String>(1)?,
18 row.get::<_, u32>(2)?,
19 row.get::<_, i32>(3)? != 0,
20 ))
21 })?
22 .filter_map(|r| r.ok())
23 .collect();
24 Ok(rows)
25 }
26
27 pub fn save_circuit_breaker(
29 &self,
30 state: &[(String, String, u32, bool)],
31 ) -> Result<(), GraphError> {
32 self.conn.execute("DELETE FROM circuit_breaker", [])?;
33 let mut stmt = self.conn.prepare(
34 "INSERT INTO circuit_breaker (error_code, hash, consecutive_failures, downgraded) \
35 VALUES (?1, ?2, ?3, ?4)",
36 )?;
37 for (code, hash, consecutive, downgraded) in state {
38 stmt.execute(params![code, hash, consecutive, *downgraded as i32])?;
39 }
40 Ok(())
41 }
42
43 pub fn search_nodes(
46 &self,
47 query: &str,
48 kind_filter: Option<&str>,
49 limit: usize,
50 ) -> Vec<GraphNode> {
51 let pattern = format!("%{}%", query);
52 let sql = match kind_filter {
53 Some(_) => {
54 "SELECT id, hash, kind, name, signature, file_path, line_start, line_end, \
55 docstring, is_public, type_hints_present, has_docstring, module_id \
56 FROM nodes WHERE LOWER(name) LIKE LOWER(?1) AND kind = ?2 \
57 ORDER BY name LIMIT ?3"
58 }
59 None => {
60 "SELECT id, hash, kind, name, signature, file_path, line_start, line_end, \
61 docstring, is_public, type_hints_present, has_docstring, module_id \
62 FROM nodes WHERE LOWER(name) LIKE LOWER(?1) \
63 ORDER BY name LIMIT ?2"
64 }
65 };
66
67 let result = match kind_filter {
68 Some(kind) => {
69 let mut stmt = match self.conn.prepare(sql) {
70 Ok(s) => s,
71 Err(_) => return vec![],
72 };
73 stmt.query_map(params![pattern, kind, limit as u32], Self::row_to_node)
74 .ok()
75 .map(|rows| rows.filter_map(|r| r.ok()).collect())
76 .unwrap_or_default()
77 }
78 None => {
79 let mut stmt = match self.conn.prepare(sql) {
80 Ok(s) => s,
81 Err(_) => return vec![],
82 };
83 stmt.query_map(params![pattern, limit as u32], Self::row_to_node)
84 .ok()
85 .map(|rows| rows.filter_map(|r| r.ok()).collect())
86 .unwrap_or_default()
87 }
88 };
89 result
90 }
91
92 pub fn insert_node(&self, node: &GraphNode) -> Result<(), GraphError> {
94 self.conn.execute(
95 "INSERT INTO nodes (id, hash, kind, name, signature, file_path, line_start, line_end, docstring, is_public, type_hints_present, has_docstring, module_id)
96 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13)
97 ON CONFLICT(hash) DO UPDATE SET
98 kind = excluded.kind,
99 name = excluded.name,
100 signature = excluded.signature,
101 file_path = excluded.file_path,
102 line_start = excluded.line_start,
103 line_end = excluded.line_end,
104 docstring = excluded.docstring,
105 is_public = excluded.is_public,
106 type_hints_present = excluded.type_hints_present,
107 has_docstring = excluded.has_docstring,
108 module_id = excluded.module_id,
109 updated_at = datetime('now')",
110 params![
111 node.id,
112 node.hash,
113 node.kind.as_str(),
114 node.name,
115 node.signature,
116 node.file_path,
117 node.line_start,
118 node.line_end,
119 node.docstring,
120 node.is_public as i32,
121 node.type_hints_present as i32,
122 node.has_docstring as i32,
123 if node.module_id == 0 { None } else { Some(node.module_id) },
124 ],
125 )?;
126
127 self.conn.execute(
130 "DELETE FROM external_endpoints WHERE node_id = ?1",
131 params![node.id],
132 )?;
133 for ep in &node.external_endpoints {
134 self.conn.execute(
135 "INSERT INTO external_endpoints (node_id, kind, method, path, direction) VALUES (?1, ?2, ?3, ?4, ?5)",
136 params![node.id, ep.kind, ep.method, ep.path, ep.direction],
137 )?;
138 }
139
140 for ph in &node.previous_hashes {
142 self.conn.execute(
143 "INSERT OR IGNORE INTO previous_hashes (node_id, hash) VALUES (?1, ?2)",
144 params![node.id, ph],
145 )?;
146 }
147
148 Ok(())
149 }
150
151 pub fn update_node_in_db(&self, node: &GraphNode) -> Result<(), GraphError> {
153 if let Some(old) = self.get_node_by_id(node.id) {
155 if old.hash != node.hash {
156 self.conn.execute(
157 "INSERT OR IGNORE INTO previous_hashes (node_id, hash) VALUES (?1, ?2)",
158 params![node.id, old.hash],
159 )?;
160 self.conn.execute(
162 "DELETE FROM previous_hashes WHERE node_id = ?1 AND hash NOT IN (SELECT hash FROM previous_hashes WHERE node_id = ?1 ORDER BY created_at DESC LIMIT 3)",
163 params![node.id],
164 )?;
165 }
166 }
167
168 self.conn.execute(
169 "UPDATE nodes SET hash = ?1, kind = ?2, name = ?3, signature = ?4, file_path = ?5, line_start = ?6, line_end = ?7, docstring = ?8, is_public = ?9, type_hints_present = ?10, has_docstring = ?11, module_id = ?12, updated_at = datetime('now') WHERE id = ?13",
170 params![
171 node.hash,
172 node.kind.as_str(),
173 node.name,
174 node.signature,
175 node.file_path,
176 node.line_start,
177 node.line_end,
178 node.docstring,
179 node.is_public as i32,
180 node.type_hints_present as i32,
181 node.has_docstring as i32,
182 if node.module_id == 0 { None } else { Some(node.module_id) },
183 node.id,
184 ],
185 )?;
186
187 self.conn.execute(
189 "DELETE FROM external_endpoints WHERE node_id = ?1",
190 params![node.id],
191 )?;
192 for ep in &node.external_endpoints {
193 self.conn.execute(
194 "INSERT INTO external_endpoints (node_id, kind, method, path, direction) VALUES (?1, ?2, ?3, ?4, ?5)",
195 params![node.id, ep.kind, ep.method, ep.path, ep.direction],
196 )?;
197 }
198
199 Ok(())
200 }
201}