walletdb.rs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2024 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use std::{path::PathBuf, sync::Arc};
  19. use log::{debug, error};
  20. use rusqlite::{
  21. types::{ToSql, Value},
  22. Connection,
  23. };
  24. use smol::lock::Mutex;
  25. use crate::error::{WalletDbError, WalletDbResult};
  26. pub type WalletPtr = Arc<WalletDb>;
  27. /// Structure representing base wallet database operations.
  28. pub struct WalletDb {
  29. /// Connection to the SQLite database
  30. pub conn: Mutex<Connection>,
  31. }
  32. impl WalletDb {
  33. /// Create a new wallet database handler. If `path` is `None`, create it in memory.
  34. pub fn new(path: Option<PathBuf>, password: Option<&str>) -> WalletDbResult<WalletPtr> {
  35. let Ok(conn) = (match path.clone() {
  36. Some(p) => Connection::open(p),
  37. None => Connection::open_in_memory(),
  38. }) else {
  39. return Err(WalletDbError::ConnectionFailed);
  40. };
  41. if let Some(password) = password {
  42. if let Err(e) = conn.pragma_update(None, "key", password) {
  43. error!(target: "walletdb::new", "[WalletDb] Pragma update failed: {e}");
  44. return Err(WalletDbError::PragmaUpdateError);
  45. };
  46. }
  47. if let Err(e) = conn.pragma_update(None, "foreign_keys", "ON") {
  48. error!(target: "walletdb::new", "[WalletDb] Pragma update failed: {e}");
  49. return Err(WalletDbError::PragmaUpdateError);
  50. };
  51. debug!(target: "walletdb::new", "[WalletDb] Opened Sqlite connection at \"{path:?}\"");
  52. Ok(Arc::new(Self { conn: Mutex::new(conn) }))
  53. }
  54. /// This function executes a given SQL query that contains multiple SQL statements,
  55. /// that don't contain any parameters.
  56. pub async fn exec_batch_sql(&self, query: &str) -> WalletDbResult<()> {
  57. debug!(target: "walletdb::exec_batch_sql", "[WalletDb] Executing batch SQL query:\n{query}");
  58. if let Err(e) = self.conn.lock().await.execute_batch(query) {
  59. error!(target: "walletdb::exec_batch_sql", "[WalletDb] Query failed: {e}");
  60. return Err(WalletDbError::QueryExecutionFailed)
  61. };
  62. Ok(())
  63. }
  64. /// This function executes a given SQL query, but isn't able to return anything.
  65. /// Therefore it's best to use it for initializing a table or similar things.
  66. pub async fn exec_sql(&self, query: &str, params: &[&dyn ToSql]) -> WalletDbResult<()> {
  67. debug!(target: "walletdb::exec_sql", "[WalletDb] Executing SQL query:\n{query}");
  68. // If no params are provided, execute directly
  69. if params.is_empty() {
  70. if let Err(e) = self.conn.lock().await.execute(query, ()) {
  71. error!(target: "walletdb::exec_sql", "[WalletDb] Query failed: {e}");
  72. return Err(WalletDbError::QueryExecutionFailed)
  73. };
  74. return Ok(())
  75. }
  76. // First we prepare the query
  77. let conn = self.conn.lock().await;
  78. let Ok(mut stmt) = conn.prepare(query) else {
  79. return Err(WalletDbError::QueryPreparationFailed)
  80. };
  81. // Execute the query using provided params
  82. if let Err(e) = stmt.execute(params) {
  83. error!(target: "walletdb::exec_sql", "[WalletDb] Query failed: {e}");
  84. return Err(WalletDbError::QueryExecutionFailed)
  85. };
  86. // Finalize query and drop connection lock
  87. if let Err(e) = stmt.finalize() {
  88. error!(target: "walletdb::exec_sql", "[WalletDb] Query finalization failed: {e}");
  89. return Err(WalletDbError::QueryFinalizationFailed)
  90. };
  91. drop(conn);
  92. Ok(())
  93. }
  94. /// Generate a `SELECT` query for provided table from selected column names and
  95. /// provided `WHERE` clauses. Named parameters are supported in the `WHERE` clauses,
  96. /// assuming they follow the normal formatting ":{column_name}".
  97. fn generate_select_query(
  98. &self,
  99. table: &str,
  100. col_names: &[&str],
  101. params: &[(&str, &dyn ToSql)],
  102. ) -> String {
  103. let mut query = if col_names.is_empty() {
  104. format!("SELECT * FROM {}", table)
  105. } else {
  106. format!("SELECT {} FROM {}", col_names.join(", "), table)
  107. };
  108. if params.is_empty() {
  109. return query
  110. }
  111. let mut where_str = Vec::with_capacity(params.len());
  112. for (k, _) in params {
  113. let col = &k[1..];
  114. where_str.push(format!("{col} = {k}"));
  115. }
  116. query.push_str(&format!(" WHERE {}", where_str.join(" AND ")));
  117. query
  118. }
  119. /// Query provided table from selected column names and provided `WHERE` clauses,
  120. /// for a single row.
  121. pub async fn query_single(
  122. &self,
  123. table: &str,
  124. col_names: &[&str],
  125. params: &[(&str, &dyn ToSql)],
  126. ) -> WalletDbResult<Vec<Value>> {
  127. // Generate `SELECT` query
  128. let query = self.generate_select_query(table, col_names, params);
  129. debug!(target: "walletdb::query_single", "[WalletDb] Executing SQL query:\n{query}");
  130. // First we prepare the query
  131. let conn = self.conn.lock().await;
  132. let Ok(mut stmt) = conn.prepare(&query) else {
  133. return Err(WalletDbError::QueryPreparationFailed)
  134. };
  135. // Execute the query using provided params
  136. let Ok(mut rows) = stmt.query(params) else {
  137. return Err(WalletDbError::QueryExecutionFailed)
  138. };
  139. // Check if row exists
  140. let Ok(next) = rows.next() else { return Err(WalletDbError::QueryExecutionFailed) };
  141. let row = match next {
  142. Some(row_result) => row_result,
  143. None => return Err(WalletDbError::RowNotFound),
  144. };
  145. // Grab returned values
  146. let mut result = vec![];
  147. for col in col_names {
  148. let Ok(value) = row.get(*col) else { return Err(WalletDbError::ParseColumnValueError) };
  149. result.push(value);
  150. }
  151. Ok(result)
  152. }
  153. /// Query provided table from selected column names and provided `WHERE` clauses,
  154. /// for multiple rows.
  155. pub async fn query_multiple(
  156. &self,
  157. table: &str,
  158. col_names: &[&str],
  159. params: &[(&str, &dyn ToSql)],
  160. ) -> WalletDbResult<Vec<Vec<Value>>> {
  161. // Generate `SELECT` query
  162. let query = self.generate_select_query(table, col_names, params);
  163. debug!(target: "walletdb::multiple", "[WalletDb] Executing SQL query:\n{query}");
  164. // First we prepare the query
  165. let conn = self.conn.lock().await;
  166. let Ok(mut stmt) = conn.prepare(&query) else {
  167. return Err(WalletDbError::QueryPreparationFailed)
  168. };
  169. // Execute the query using provided converted params
  170. let Ok(mut rows) = stmt.query(params) else {
  171. if let Err(e) = stmt.query(params) {
  172. println!("eeer: {e:?}");
  173. }
  174. return Err(WalletDbError::QueryExecutionFailed)
  175. };
  176. // Loop over returned rows and parse them
  177. let mut result = vec![];
  178. loop {
  179. // Check if an error occured
  180. let row = match rows.next() {
  181. Ok(r) => r,
  182. Err(_) => return Err(WalletDbError::QueryExecutionFailed),
  183. };
  184. // Check if no row was returned
  185. let row = match row {
  186. Some(r) => r,
  187. None => break,
  188. };
  189. // Grab row returned values
  190. let mut row_values = vec![];
  191. if col_names.is_empty() {
  192. let mut idx = 0;
  193. loop {
  194. let Ok(value) = row.get(idx) else { break };
  195. row_values.push(value);
  196. idx += 1;
  197. }
  198. } else {
  199. for col in col_names {
  200. let Ok(value) = row.get(*col) else {
  201. return Err(WalletDbError::ParseColumnValueError)
  202. };
  203. row_values.push(value);
  204. }
  205. }
  206. result.push(row_values);
  207. }
  208. Ok(result)
  209. }
  210. }
  211. /// Custom implementation of rusqlite::named_params! to use `expr` instead of `literal` as `$param_name`,
  212. /// and append the ":" named parameters prefix.
  213. #[macro_export]
  214. macro_rules! convert_named_params {
  215. () => {
  216. &[] as &[(&str, &dyn rusqlite::types::ToSql)]
  217. };
  218. ($(($param_name:expr, $param_val:expr)),+ $(,)?) => {
  219. &[$((format!(":{}", $param_name).as_str(), &$param_val as &dyn rusqlite::types::ToSql)),+] as &[(&str, &dyn rusqlite::types::ToSql)]
  220. };
  221. }
  222. #[cfg(test)]
  223. mod tests {
  224. use rusqlite::types::Value;
  225. use crate::walletdb::WalletDb;
  226. #[test]
  227. fn test_mem_wallet() {
  228. smol::block_on(async {
  229. let wallet = WalletDb::new(None, Some("foobar")).unwrap();
  230. wallet.exec_sql("CREATE TABLE mista ( numba INTEGER );", &[]).await.unwrap();
  231. wallet.exec_sql("INSERT INTO mista ( numba ) VALUES ( 42 );", &[]).await.unwrap();
  232. let ret = wallet.query_single("mista", &["numba"], &[]).await.unwrap();
  233. assert_eq!(ret.len(), 1);
  234. let numba: i64 = if let Value::Integer(numba) = ret[0] { numba } else { -1 };
  235. assert_eq!(numba, 42);
  236. });
  237. }
  238. #[test]
  239. fn test_query_single() {
  240. smol::block_on(async {
  241. let wallet = WalletDb::new(None, None).unwrap();
  242. wallet
  243. .exec_sql(
  244. "CREATE TABLE mista ( why INTEGER, are TEXT, you INTEGER, gae BLOB );",
  245. &[],
  246. )
  247. .await
  248. .unwrap();
  249. let why = 42;
  250. let are = "are".to_string();
  251. let you = 69;
  252. let gae = vec![42u8; 32];
  253. wallet
  254. .exec_sql(
  255. "INSERT INTO mista ( why, are, you, gae ) VALUES (?1, ?2, ?3, ?4);",
  256. rusqlite::params![why, are, you, gae],
  257. )
  258. .await
  259. .unwrap();
  260. let ret =
  261. wallet.query_single("mista", &["why", "are", "you", "gae"], &[]).await.unwrap();
  262. assert_eq!(ret.len(), 4);
  263. assert_eq!(ret[0], Value::Integer(why));
  264. assert_eq!(ret[1], Value::Text(are.clone()));
  265. assert_eq!(ret[2], Value::Integer(you));
  266. assert_eq!(ret[3], Value::Blob(gae.clone()));
  267. let ret = wallet
  268. .query_single(
  269. "mista",
  270. &["gae"],
  271. rusqlite::named_params! {":why": why, ":are": are, ":you": you},
  272. )
  273. .await
  274. .unwrap();
  275. assert_eq!(ret.len(), 1);
  276. assert_eq!(ret[0], Value::Blob(gae));
  277. });
  278. }
  279. #[test]
  280. fn test_query_multi() {
  281. smol::block_on(async {
  282. let wallet = WalletDb::new(None, None).unwrap();
  283. wallet
  284. .exec_sql(
  285. "CREATE TABLE mista ( why INTEGER, are TEXT, you INTEGER, gae BLOB );",
  286. &[],
  287. )
  288. .await
  289. .unwrap();
  290. let why = 42;
  291. let are = "are".to_string();
  292. let you = 69;
  293. let gae = vec![42u8; 32];
  294. wallet
  295. .exec_sql(
  296. "INSERT INTO mista ( why, are, you, gae ) VALUES (?1, ?2, ?3, ?4);",
  297. rusqlite::params![why, are, you, gae],
  298. )
  299. .await
  300. .unwrap();
  301. wallet
  302. .exec_sql(
  303. "INSERT INTO mista ( why, are, you, gae ) VALUES (?1, ?2, ?3, ?4);",
  304. rusqlite::params![why, are, you, gae],
  305. )
  306. .await
  307. .unwrap();
  308. let ret = wallet.query_multiple("mista", &[], &[]).await.unwrap();
  309. assert_eq!(ret.len(), 2);
  310. for row in ret {
  311. assert_eq!(row.len(), 4);
  312. assert_eq!(row[0], Value::Integer(why));
  313. assert_eq!(row[1], Value::Text(are.clone()));
  314. assert_eq!(row[2], Value::Integer(you));
  315. assert_eq!(row[3], Value::Blob(gae.clone()));
  316. }
  317. let ret = wallet
  318. .query_multiple(
  319. "mista",
  320. &["gae"],
  321. convert_named_params! {("why", why), ("are", are), ("you", you)},
  322. )
  323. .await
  324. .unwrap();
  325. assert_eq!(ret.len(), 2);
  326. for row in ret {
  327. assert_eq!(row.len(), 1);
  328. assert_eq!(row[0], Value::Blob(gae.clone()));
  329. }
  330. });
  331. }
  332. }