chainlink/db/
dependencies.rs1use anyhow::Result;
2use rusqlite::params;
3
4use super::{issue_from_row, Database};
5use crate::models::Issue;
6
7impl Database {
8 pub fn add_dependency(&self, blocked_id: i64, blocker_id: i64) -> Result<bool> {
9 if blocked_id == blocker_id {
10 anyhow::bail!("An issue cannot block itself");
11 }
12
13 if self.would_create_cycle(blocked_id, blocker_id)? {
14 anyhow::bail!("Adding this dependency would create a circular dependency chain");
15 }
16
17 let result = self.conn.execute(
18 "INSERT OR IGNORE INTO dependencies (blocker_id, blocked_id) VALUES (?1, ?2)",
19 params![blocker_id, blocked_id],
20 )?;
21 Ok(result > 0)
22 }
23
24 fn would_create_cycle(&self, blocked_id: i64, blocker_id: i64) -> Result<bool> {
26 let mut visited = std::collections::HashSet::new();
27 let mut stack = vec![blocked_id];
28
29 while let Some(current) = stack.pop() {
30 if current == blocker_id {
31 return Ok(true);
32 }
33
34 if visited.insert(current) {
35 let blocking = self.get_blocking(current)?;
36 for next in blocking {
37 if !visited.contains(&next) {
38 stack.push(next);
39 }
40 }
41 }
42 }
43
44 Ok(false)
45 }
46
47 pub fn remove_dependency(&self, blocked_id: i64, blocker_id: i64) -> Result<bool> {
48 let rows = self.conn.execute(
49 "DELETE FROM dependencies WHERE blocker_id = ?1 AND blocked_id = ?2",
50 params![blocker_id, blocked_id],
51 )?;
52 Ok(rows > 0)
53 }
54
55 pub fn get_blockers(&self, issue_id: i64) -> Result<Vec<i64>> {
56 let mut stmt = self
57 .conn
58 .prepare("SELECT blocker_id FROM dependencies WHERE blocked_id = ?1")?;
59 let blockers = stmt
60 .query_map([issue_id], |row| row.get(0))?
61 .collect::<std::result::Result<Vec<i64>, _>>()?;
62 Ok(blockers)
63 }
64
65 pub fn get_blocking(&self, issue_id: i64) -> Result<Vec<i64>> {
66 let mut stmt = self
67 .conn
68 .prepare("SELECT blocked_id FROM dependencies WHERE blocker_id = ?1")?;
69 let blocking = stmt
70 .query_map([issue_id], |row| row.get(0))?
71 .collect::<std::result::Result<Vec<i64>, _>>()?;
72 Ok(blocking)
73 }
74
75 pub fn list_blocked_issues(&self) -> Result<Vec<Issue>> {
76 let mut stmt = self.conn.prepare(
77 r#"
78 SELECT DISTINCT i.id, i.title, i.description, i.status, i.priority, i.parent_id, i.created_at, i.updated_at, i.closed_at
79 FROM issues i
80 JOIN dependencies d ON i.id = d.blocked_id
81 JOIN issues blocker ON d.blocker_id = blocker.id
82 WHERE i.status = 'open' AND blocker.status = 'open'
83 ORDER BY i.id
84 "#,
85 )?;
86
87 let issues = stmt
88 .query_map([], issue_from_row)?
89 .collect::<std::result::Result<Vec<_>, _>>()?;
90
91 Ok(issues)
92 }
93
94 pub fn list_ready_issues(&self) -> Result<Vec<Issue>> {
95 let mut stmt = self.conn.prepare(
96 r#"
97 SELECT i.id, i.title, i.description, i.status, i.priority, i.parent_id, i.created_at, i.updated_at, i.closed_at
98 FROM issues i
99 WHERE i.status = 'open'
100 AND NOT EXISTS (
101 SELECT 1 FROM dependencies d
102 JOIN issues blocker ON d.blocker_id = blocker.id
103 WHERE d.blocked_id = i.id AND blocker.status = 'open'
104 )
105 ORDER BY i.id
106 "#,
107 )?;
108
109 let issues = stmt
110 .query_map([], issue_from_row)?
111 .collect::<std::result::Result<Vec<_>, _>>()?;
112
113 Ok(issues)
114 }
115}