walletdb.rs 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  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 no params are provided, execute directly
  59. if let Err(e) = self.conn.lock().await.execute_batch(query) {
  60. error!(target: "walletdb::exec_batch_sql", "[WalletDb] Query failed: {e}");
  61. return Err(WalletDbError::QueryExecutionFailed)
  62. };
  63. Ok(())
  64. }
  65. /// This function executes a given SQL query, but isn't able to return anything.
  66. /// Therefore it's best to use it for initializing a table or similar things.
  67. pub async fn exec_sql(&self, query: &str, params: &[&dyn ToSql]) -> WalletDbResult<()> {
  68. debug!(target: "walletdb::exec_sql", "[WalletDb] Executing SQL query:\n{query}");
  69. // If no params are provided, execute directly
  70. if params.is_empty() {
  71. if let Err(e) = self.conn.lock().await.execute(query, ()) {
  72. error!(target: "walletdb::exec_sql", "[WalletDb] Query failed: {e}");
  73. return Err(WalletDbError::QueryExecutionFailed)
  74. };
  75. return Ok(())
  76. }
  77. // First we prepare the query
  78. let conn = self.conn.lock().await;
  79. let Ok(mut stmt) = conn.prepare(query) else {
  80. eprintln!("Error: {:?}", conn.prepare(query));
  81. return Err(WalletDbError::QueryPreparationFailed)
  82. };
  83. // Execute the query using provided params
  84. if let Err(e) = stmt.execute(params) {
  85. error!(target: "walletdb::exec_sql", "[WalletDb] Query failed: {e}");
  86. return Err(WalletDbError::QueryExecutionFailed)
  87. };
  88. // Finalize query and drop connection lock
  89. if let Err(e) = stmt.finalize() {
  90. error!(target: "walletdb::exec_sql", "[WalletDb] Query finalization failed: {e}");
  91. return Err(WalletDbError::QueryFinalizationFailed)
  92. };
  93. drop(conn);
  94. Ok(())
  95. }
  96. /// Query provided table from selected column names and provided `WHERE` clauses.
  97. /// Named parameters are supported in the `WHERE` clauses, assuming they follow the
  98. /// normal formatting ":{column_name}"
  99. pub async fn query_single(
  100. &self,
  101. table: &str,
  102. col_names: Vec<&str>,
  103. params: &[(&str, &dyn ToSql)],
  104. ) -> WalletDbResult<Vec<Value>> {
  105. // Generate `SELECT` query
  106. let mut query = format!("SELECT {} FROM {}", col_names.join(", "), table);
  107. if !params.is_empty() {
  108. let mut where_str = Vec::with_capacity(params.len());
  109. for (k, _) in params {
  110. let col = &k[1..];
  111. where_str.push(format!("{col} = {k}"));
  112. }
  113. query.push_str(&format!(" WHERE {}", where_str.join(" AND ")));
  114. };
  115. debug!(target: "walletdb::query_single", "[WalletDb] Executing SQL query:\n{query}");
  116. // First we prepare the query
  117. let conn = self.conn.lock().await;
  118. let Ok(mut stmt) = conn.prepare(&query) else {
  119. return Err(WalletDbError::QueryPreparationFailed)
  120. };
  121. // Execute the query using provided params
  122. let Ok(mut rows) = stmt.query(params) else {
  123. return Err(WalletDbError::QueryExecutionFailed)
  124. };
  125. // Check if row exists
  126. let Ok(next) = rows.next() else { return Err(WalletDbError::QueryExecutionFailed) };
  127. let row = match next {
  128. Some(row_result) => row_result,
  129. None => return Err(WalletDbError::RowNotFound),
  130. };
  131. // Grab returned values
  132. let mut result = vec![];
  133. for col in col_names {
  134. let Ok(value) = row.get(col) else { return Err(WalletDbError::ParseColumnValueError) };
  135. result.push(value);
  136. }
  137. Ok(result)
  138. }
  139. }
  140. #[cfg(test)]
  141. mod tests {
  142. use rusqlite::types::Value;
  143. use crate::walletdb::WalletDb;
  144. #[test]
  145. fn test_mem_wallet() {
  146. smol::block_on(async {
  147. let wallet = WalletDb::new(None, Some("foobar")).unwrap();
  148. wallet.exec_sql("CREATE TABLE mista ( numba INTEGER );", &[]).await.unwrap();
  149. wallet.exec_sql("INSERT INTO mista ( numba ) VALUES ( 42 );", &[]).await.unwrap();
  150. let ret = wallet.query_single("mista", vec!["numba"], &[]).await.unwrap();
  151. assert_eq!(ret.len(), 1);
  152. let numba: i64 = if let Value::Integer(numba) = ret[0] { numba } else { -1 };
  153. assert_eq!(numba, 42);
  154. });
  155. }
  156. #[test]
  157. fn test_query_single() {
  158. smol::block_on(async {
  159. let wallet = WalletDb::new(None, None).unwrap();
  160. wallet
  161. .exec_sql(
  162. "CREATE TABLE mista ( why INTEGER, are TEXT, you INTEGER, gae BLOB );",
  163. &[],
  164. )
  165. .await
  166. .unwrap();
  167. let why = 42;
  168. let are = "are".to_string();
  169. let you = 69;
  170. let gae = vec![42u8; 32];
  171. wallet
  172. .exec_sql(
  173. "INSERT INTO mista ( why, are, you, gae ) VALUES (?1, ?2, ?3, ?4);",
  174. rusqlite::params![why, are, you, gae],
  175. )
  176. .await
  177. .unwrap();
  178. let ret =
  179. wallet.query_single("mista", vec!["why", "are", "you", "gae"], &[]).await.unwrap();
  180. assert_eq!(ret.len(), 4);
  181. assert_eq!(ret[0], Value::Integer(why));
  182. assert_eq!(ret[1], Value::Text(are.clone()));
  183. assert_eq!(ret[2], Value::Integer(you));
  184. assert_eq!(ret[3], Value::Blob(gae.clone()));
  185. let ret = wallet
  186. .query_single(
  187. "mista",
  188. vec!["gae"],
  189. rusqlite::named_params! {":why" : why, ":are" : are, ":you" : you},
  190. )
  191. .await
  192. .unwrap();
  193. assert_eq!(ret.len(), 1);
  194. assert_eq!(ret[0], Value::Blob(gae));
  195. });
  196. }
  197. }