1use super::rows::*;
2use super::*;
3impl Store {
4 pub fn latest_repo_snapshot(&self, workspace: &str) -> Result<Option<RepoSnapshotRecord>> {
5 let mut stmt = self.conn.prepare(
6 "SELECT id, workspace, head_commit, dirty_fingerprint, analyzer_version,
7 indexed_at_ms, dirty, graph_path
8 FROM repo_snapshots WHERE workspace = ?1
9 ORDER BY indexed_at_ms DESC LIMIT 1",
10 )?;
11 let mut rows = stmt.query_map(params![workspace], |row| {
12 Ok(RepoSnapshotRecord {
13 id: row.get(0)?,
14 workspace: row.get(1)?,
15 head_commit: row.get(2)?,
16 dirty_fingerprint: row.get(3)?,
17 analyzer_version: row.get(4)?,
18 indexed_at_ms: row.get::<_, i64>(5)? as u64,
19 dirty: row.get::<_, i64>(6)? != 0,
20 graph_path: row.get(7)?,
21 })
22 })?;
23 Ok(rows.next().transpose()?)
24 }
25
26 pub fn save_repo_snapshot(
27 &self,
28 snapshot: &RepoSnapshotRecord,
29 facts: &[FileFact],
30 edges: &[RepoEdge],
31 ) -> Result<()> {
32 self.conn.execute(
33 "INSERT INTO repo_snapshots (
34 id, workspace, head_commit, dirty_fingerprint, analyzer_version,
35 indexed_at_ms, dirty, graph_path
36 ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)
37 ON CONFLICT(id) DO UPDATE SET
38 workspace=excluded.workspace,
39 head_commit=excluded.head_commit,
40 dirty_fingerprint=excluded.dirty_fingerprint,
41 analyzer_version=excluded.analyzer_version,
42 indexed_at_ms=excluded.indexed_at_ms,
43 dirty=excluded.dirty,
44 graph_path=excluded.graph_path",
45 params![
46 snapshot.id,
47 snapshot.workspace,
48 snapshot.head_commit,
49 snapshot.dirty_fingerprint,
50 snapshot.analyzer_version,
51 snapshot.indexed_at_ms as i64,
52 bool_to_i64(snapshot.dirty),
53 snapshot.graph_path,
54 ],
55 )?;
56 self.conn.execute(
57 "DELETE FROM file_facts WHERE snapshot_id = ?1",
58 params![snapshot.id],
59 )?;
60 self.conn.execute(
61 "DELETE FROM repo_edges WHERE snapshot_id = ?1",
62 params![snapshot.id],
63 )?;
64 for fact in facts {
65 self.conn.execute(
66 "INSERT INTO file_facts (
67 snapshot_id, path, language, bytes, loc, sloc, complexity_total,
68 max_fn_complexity, symbol_count, import_count, fan_in, fan_out,
69 churn_30d, churn_90d, authors_90d, last_changed_ms
70 ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16)",
71 params![
72 fact.snapshot_id,
73 fact.path,
74 fact.language,
75 fact.bytes as i64,
76 fact.loc as i64,
77 fact.sloc as i64,
78 fact.complexity_total as i64,
79 fact.max_fn_complexity as i64,
80 fact.symbol_count as i64,
81 fact.import_count as i64,
82 fact.fan_in as i64,
83 fact.fan_out as i64,
84 fact.churn_30d as i64,
85 fact.churn_90d as i64,
86 fact.authors_90d as i64,
87 fact.last_changed_ms.map(|v| v as i64),
88 ],
89 )?;
90 }
91 for edge in edges {
92 self.conn.execute(
93 "INSERT INTO repo_edges (snapshot_id, from_id, to_id, kind, weight)
94 VALUES (?1, ?2, ?3, ?4, ?5)
95 ON CONFLICT(snapshot_id, from_id, to_id, kind)
96 DO UPDATE SET weight = weight + excluded.weight",
97 params![
98 snapshot.id,
99 edge.from_path,
100 edge.to_path,
101 edge.kind,
102 edge.weight as i64,
103 ],
104 )?;
105 }
106 Ok(())
107 }
108
109 pub fn file_facts_for_snapshot(&self, snapshot_id: &str) -> Result<Vec<FileFact>> {
110 let mut stmt = self.conn.prepare(
111 "SELECT snapshot_id, path, language, bytes, loc, sloc, complexity_total,
112 max_fn_complexity, symbol_count, import_count, fan_in, fan_out,
113 churn_30d, churn_90d, authors_90d, last_changed_ms
114 FROM file_facts WHERE snapshot_id = ?1 ORDER BY path ASC",
115 )?;
116 let rows = stmt.query_map(params![snapshot_id], |row| {
117 Ok(FileFact {
118 snapshot_id: row.get(0)?,
119 path: row.get(1)?,
120 language: row.get(2)?,
121 bytes: row.get::<_, i64>(3)? as u64,
122 loc: row.get::<_, i64>(4)? as u32,
123 sloc: row.get::<_, i64>(5)? as u32,
124 complexity_total: row.get::<_, i64>(6)? as u32,
125 max_fn_complexity: row.get::<_, i64>(7)? as u32,
126 symbol_count: row.get::<_, i64>(8)? as u32,
127 import_count: row.get::<_, i64>(9)? as u32,
128 fan_in: row.get::<_, i64>(10)? as u32,
129 fan_out: row.get::<_, i64>(11)? as u32,
130 churn_30d: row.get::<_, i64>(12)? as u32,
131 churn_90d: row.get::<_, i64>(13)? as u32,
132 authors_90d: row.get::<_, i64>(14)? as u32,
133 last_changed_ms: row.get::<_, Option<i64>>(15)?.map(|v| v as u64),
134 })
135 })?;
136 Ok(rows.filter_map(|row| row.ok()).collect())
137 }
138 pub fn repo_edges_for_snapshot(&self, snapshot_id: &str) -> Result<Vec<RepoEdge>> {
139 let mut stmt = self.conn.prepare(
140 "SELECT from_id, to_id, kind, weight
141 FROM repo_edges WHERE snapshot_id = ?1
142 ORDER BY kind, from_id, to_id",
143 )?;
144 let rows = stmt.query_map(params![snapshot_id], |row| {
145 Ok(RepoEdge {
146 from_path: row.get(0)?,
147 to_path: row.get(1)?,
148 kind: row.get(2)?,
149 weight: row.get::<_, i64>(3)? as u32,
150 })
151 })?;
152 Ok(rows.filter_map(|row| row.ok()).collect())
153 }
154 pub fn hottest_files_for_snapshot(&self, snapshot_id: &str) -> Result<Vec<RankedFile>> {
155 self.ranked_files_for_snapshot(snapshot_id, "churn_30d * complexity_total")
156 }
157
158 pub fn most_changed_files_for_snapshot(&self, snapshot_id: &str) -> Result<Vec<RankedFile>> {
159 self.ranked_files_for_snapshot(snapshot_id, "churn_30d")
160 }
161
162 pub fn most_complex_files_for_snapshot(&self, snapshot_id: &str) -> Result<Vec<RankedFile>> {
163 self.ranked_files_for_snapshot(snapshot_id, "complexity_total")
164 }
165
166 pub fn highest_risk_files_for_snapshot(&self, snapshot_id: &str) -> Result<Vec<RankedFile>> {
167 self.ranked_files_for_snapshot(snapshot_id, "churn_30d * authors_90d * complexity_total")
168 }
169
170 pub(super) fn ranked_files_for_snapshot(
171 &self,
172 snapshot_id: &str,
173 value_sql: &str,
174 ) -> Result<Vec<RankedFile>> {
175 let sql = format!(
176 "SELECT path, {value_sql}, complexity_total, churn_30d
177 FROM file_facts WHERE snapshot_id = ?1
178 ORDER BY {value_sql} DESC, path ASC LIMIT 10"
179 );
180 let mut stmt = self.conn.prepare(&sql)?;
181 let rows = stmt.query_map(params![snapshot_id], ranked_file_row)?;
182 rows.map(|r| r.map_err(anyhow::Error::from)).collect()
183 }
184
185 pub fn pain_hotspots_for_snapshot(
186 &self,
187 snapshot_id: &str,
188 workspace: &str,
189 start_ms: u64,
190 end_ms: u64,
191 ) -> Result<Vec<RankedFile>> {
192 let mut stmt = self.conn.prepare(PAIN_HOTSPOTS_SQL)?;
193 let rows = stmt.query_map(
194 params![snapshot_id, workspace, start_ms as i64, end_ms as i64],
195 ranked_file_row,
196 )?;
197 rows.map(|r| r.map_err(anyhow::Error::from)).collect()
198 }
199}