radicle/
sql.rs

1use std::ops::Deref;
2use std::str::FromStr;
3
4use sqlite as sql;
5use sqlite::Value;
6
7use crate::identity::RepoId;
8use crate::node;
9use crate::node::{Address, UserAgent};
10
11/// Run an SQL query inside a transaction.
12/// Commits the transaction on success, and rolls back on error.
13pub fn transaction<T, E: From<sql::Error>>(
14    db: &sql::Connection,
15    query: impl FnOnce(&sql::Connection) -> Result<T, E>,
16) -> Result<T, E> {
17    db.execute("BEGIN")?;
18
19    match query(db) {
20        Ok(result) => {
21            db.execute("COMMIT")?;
22            Ok(result)
23        }
24        Err(err) => {
25            db.execute("ROLLBACK")?;
26            Err(err)
27        }
28    }
29}
30
31impl TryFrom<&Value> for RepoId {
32    type Error = sql::Error;
33
34    fn try_from(value: &Value) -> Result<Self, Self::Error> {
35        match value {
36            Value::String(id) => RepoId::from_urn(id).map_err(|e| sql::Error {
37                code: None,
38                message: Some(e.to_string()),
39            }),
40            _ => Err(sql::Error {
41                code: None,
42                message: Some(format!("sql: invalid type `{:?}` for id", value.kind())),
43            }),
44        }
45    }
46}
47
48impl sqlite::BindableWithIndex for &RepoId {
49    fn bind<I: sql::ParameterIndex>(self, stmt: &mut sql::Statement<'_>, i: I) -> sql::Result<()> {
50        self.urn().as_str().bind(stmt, i)
51    }
52}
53
54impl sql::BindableWithIndex for node::Features {
55    fn bind<I: sql::ParameterIndex>(self, stmt: &mut sql::Statement<'_>, i: I) -> sql::Result<()> {
56        (*self.deref() as i64).bind(stmt, i)
57    }
58}
59
60impl TryFrom<&Value> for node::Features {
61    type Error = sql::Error;
62
63    fn try_from(value: &Value) -> Result<Self, Self::Error> {
64        match value {
65            Value::Integer(bits) => Ok(node::Features::from(*bits as u64)),
66            _ => Err(sql::Error {
67                code: None,
68                message: Some(format!(
69                    "sql: invalid type `{:?}` for node features",
70                    value.kind()
71                )),
72            }),
73        }
74    }
75}
76
77impl TryFrom<&sql::Value> for Address {
78    type Error = sql::Error;
79
80    fn try_from(value: &sql::Value) -> Result<Self, Self::Error> {
81        match value {
82            sql::Value::String(s) => Address::from_str(s.as_str()).map_err(|e| sql::Error {
83                code: None,
84                message: Some(e.to_string()),
85            }),
86            _ => Err(sql::Error {
87                code: None,
88                message: Some(format!(
89                    "sql: invalid type `{:?}` for address",
90                    value.kind()
91                )),
92            }),
93        }
94    }
95}
96
97impl sql::BindableWithIndex for &Address {
98    fn bind<I: sql::ParameterIndex>(self, stmt: &mut sql::Statement<'_>, i: I) -> sql::Result<()> {
99        self.to_string().bind(stmt, i)
100    }
101}
102
103impl TryFrom<&Value> for UserAgent {
104    type Error = sql::Error;
105
106    fn try_from(value: &Value) -> Result<Self, Self::Error> {
107        match value {
108            Value::String(ua) => UserAgent::from_str(ua).map_err(|e| sql::Error {
109                code: None,
110                message: Some(e.to_string()),
111            }),
112            _ => Err(sql::Error {
113                code: None,
114                message: Some(format!(
115                    "sql: invalid type `{:?}` for user-agent",
116                    value.kind()
117                )),
118            }),
119        }
120    }
121}