Skip to main content

chainlink/db/
dependencies.rs

1use 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    /// Check if adding blocker_id -> blocked_id would create a cycle.
25    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}