walletdb.rs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2023 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::{any::Any, path::PathBuf, sync::Arc};
  19. use log::{debug, info};
  20. use rusqlite::Connection;
  21. use smol::lock::Mutex;
  22. use crate::Result;
  23. pub type WalletPtr = Arc<WalletDb>;
  24. /// Types we want to allow to query from the SQL wallet
  25. pub enum QueryType {
  26. /// Integer gets decoded into `u64`
  27. Integer = 0x00,
  28. /// Blob gets decoded into `Vec<u8>`
  29. Blob = 0x01,
  30. /// OptionInteger gets decoded into `Option<u64>`
  31. OptionInteger = 0x02,
  32. /// OptionBlob gets decoded into `Option<Vec<u8>>`
  33. OptionBlob = 0x03,
  34. /// Text gets decoded into `String`
  35. Text = 0x04,
  36. /// Last type, increment this when you add new types.
  37. Last = 0x05,
  38. }
  39. impl From<u8> for QueryType {
  40. fn from(x: u8) -> Self {
  41. match x {
  42. 0x00 => Self::Integer,
  43. 0x01 => Self::Blob,
  44. 0x02 => Self::OptionInteger,
  45. 0x03 => Self::OptionBlob,
  46. 0x04 => Self::Text,
  47. _ => unimplemented!(),
  48. }
  49. }
  50. }
  51. #[derive(Debug)]
  52. pub enum SqlType {
  53. Integer(i64),
  54. Text(String),
  55. Blob(Vec<u8>),
  56. Null,
  57. }
  58. impl SqlType {
  59. pub fn inner<T: 'static>(&self) -> Option<&T> {
  60. match self {
  61. SqlType::Integer(v) => (v as &dyn Any).downcast_ref::<T>(),
  62. SqlType::Text(v) => (v as &dyn Any).downcast_ref::<T>(),
  63. SqlType::Blob(v) => (v as &dyn Any).downcast_ref::<T>(),
  64. SqlType::Null => None,
  65. }
  66. }
  67. }
  68. /// Structure representing base wallet operations.
  69. /// Additional operations can be implemented by trait extensions.
  70. pub struct WalletDb {
  71. pub conn: Mutex<Connection>,
  72. }
  73. impl WalletDb {
  74. /// Create a new wallet. If `path` is `None`, create it in memory.
  75. pub fn new(path: Option<PathBuf>, password: Option<&str>) -> Result<WalletPtr> {
  76. let conn = match path.clone() {
  77. Some(p) => Connection::open(p)?,
  78. None => Connection::open_in_memory()?,
  79. };
  80. if let Some(password) = password {
  81. conn.pragma_update(None, "key", password)?;
  82. }
  83. conn.pragma_update(None, "foreign_keys", "ON")?;
  84. info!(target: "wallet::walletdb", "[WalletDb] Opened Sqlite connection at \"{:?}\"", path);
  85. Ok(Arc::new(Self { conn: Mutex::new(conn) }))
  86. }
  87. /// This function executes a given SQL query, but isn't able to return anything.
  88. /// Therefore it's best to use it for initializing a table or similar things.
  89. pub async fn exec_sql(&self, query: &str) -> Result<()> {
  90. info!(target: "wallet::walletdb", "[WalletDb] Executing SQL query");
  91. debug!(target: "wallet::walletdb", "[WalletDb] Query:\n{}", query);
  92. let _ = self.conn.lock().await.execute(query, ())?;
  93. Ok(())
  94. }
  95. pub async fn query_single(
  96. &self,
  97. table: &str,
  98. col_names: Vec<&str>,
  99. where_queries: Option<Vec<(&str, SqlType)>>,
  100. ) -> Result<Vec<SqlType>> {
  101. let mut query = format!("SELECT {} FROM {}", col_names.join(", "), table);
  102. if let Some(wq) = where_queries.as_ref() {
  103. let where_str: Vec<String> = wq.iter().map(|(k, _)| format!("{} = ?", k)).collect();
  104. query.push_str(&format!(" WHERE {}", where_str.join(" AND ")));
  105. }
  106. let params: Vec<rusqlite::types::ToSqlOutput> = where_queries.map_or(Vec::new(), |wq| {
  107. wq.into_iter()
  108. .map(|(_, v)| match v {
  109. SqlType::Integer(i) => rusqlite::types::ToSqlOutput::from(i),
  110. SqlType::Text(t) => rusqlite::types::ToSqlOutput::from(t),
  111. SqlType::Blob(b) => rusqlite::types::ToSqlOutput::from(b),
  112. SqlType::Null => rusqlite::types::ToSqlOutput::from(rusqlite::types::Null),
  113. })
  114. .collect::<Vec<_>>()
  115. });
  116. let wallet_conn = self.conn.lock().await;
  117. let mut stmt = wallet_conn.prepare(&query)?;
  118. let params_as_slice: Vec<&dyn rusqlite::ToSql> =
  119. params.iter().map(|x| x as &dyn rusqlite::ToSql).collect();
  120. let mut rows = stmt.query(params_as_slice.as_slice())?;
  121. let row = match rows.next()? {
  122. Some(row_result) => row_result,
  123. None => return Ok(vec![]),
  124. };
  125. let mut result = vec![];
  126. for (idx, _) in col_names.iter().enumerate() {
  127. let value: SqlType = match row.get_ref(idx)?.data_type() {
  128. rusqlite::types::Type::Integer => SqlType::Integer(row.get(idx)?),
  129. rusqlite::types::Type::Text => SqlType::Text(row.get(idx)?),
  130. rusqlite::types::Type::Blob => SqlType::Blob(row.get(idx)?),
  131. rusqlite::types::Type::Null => SqlType::Null,
  132. _ => unimplemented!(),
  133. };
  134. result.push(value);
  135. }
  136. Ok(result)
  137. }
  138. }
  139. #[cfg(test)]
  140. mod tests {
  141. use super::*;
  142. #[test]
  143. fn test_mem_wallet() {
  144. smol::block_on(async {
  145. let wallet = WalletDb::new(None, Some("foobar")).unwrap();
  146. wallet.exec_sql("CREATE TABLE mista ( numba INTEGER );").await.unwrap();
  147. wallet.exec_sql("INSERT INTO mista ( numba ) VALUES ( 42 );").await.unwrap();
  148. let conn = wallet.conn.lock().await;
  149. let mut stmt = conn.prepare("SELECT numba FROM mista").unwrap();
  150. let numba: u64 = stmt.query_row((), |row| Ok(row.get("numba").unwrap())).unwrap();
  151. stmt.finalize().unwrap();
  152. assert!(numba == 42);
  153. });
  154. }
  155. #[test]
  156. fn test_query_single() {
  157. smol::block_on(async {
  158. let wallet = WalletDb::new(None, None).unwrap();
  159. wallet
  160. .exec_sql("CREATE TABLE mista ( why INTEGER, are TEXT, you INTEGER, gae BLOB );")
  161. .await
  162. .unwrap();
  163. let why = 42;
  164. let are = "are".to_string();
  165. let you = 69;
  166. let gae = vec![42u8; 32];
  167. let query_str = "INSERT INTO mista ( why, are, you, gae ) VALUES (?1, ?2, ?3, ?4);";
  168. let wallet_conn = wallet.conn.lock().await;
  169. let mut stmt = wallet_conn.prepare(query_str).unwrap();
  170. stmt.execute(rusqlite::params![why, are, you, gae]).unwrap();
  171. stmt.finalize().unwrap();
  172. drop(wallet_conn);
  173. let ret =
  174. wallet.query_single("mista", vec!["why", "are", "you", "gae"], None).await.unwrap();
  175. assert!(ret.len() == 4);
  176. assert!(ret[0].inner::<i64>().unwrap() == &why);
  177. assert!(ret[1].inner::<String>().unwrap() == &are);
  178. assert!(ret[2].inner::<i64>().unwrap() == &you);
  179. assert!(ret[3].inner::<Vec<u8>>().unwrap() == &gae);
  180. });
  181. }
  182. }