lib.rs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. #![doc = include_str!("../README.md")]
  2. mod meta;
  3. pub mod model;
  4. pub mod query;
  5. use meta::Metaschema;
  6. use model::Entity;
  7. pub use microrm_macros::{make_index, Entity, Modelable};
  8. pub use query::{QueryInterface, WithID};
  9. #[macro_export]
  10. macro_rules! value_list {
  11. ( $( $element:expr ),* ) => {
  12. [ $( ($element) as &dyn $crate::model::Modelable ),* ]
  13. }
  14. }
  15. // no need to show the re-exports in the documentation
  16. #[doc(hidden)]
  17. pub mod re_export {
  18. pub use lazy_static;
  19. pub use serde;
  20. pub use serde_json;
  21. pub use sqlite;
  22. }
  23. #[derive(Debug)]
  24. pub enum DBError {
  25. ConnectFailure,
  26. EarlyFailure(sqlite::Error),
  27. NoSchema,
  28. DifferentSchema,
  29. DropFailure,
  30. CreateFailure,
  31. SanityCheckFailure,
  32. }
  33. #[derive(PartialEq, Debug)]
  34. pub enum CreateMode {
  35. /// The database must exist and have a valid schema already
  36. MustExist,
  37. /// It's fine if the database doesn't exist, but it must have a valid schema if it does
  38. AllowNewDatabase,
  39. /// Nuke the contents if need be, just get the database
  40. AllowSchemaUpdate,
  41. }
  42. impl std::fmt::Display for DBError {
  43. fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
  44. fmt.write_fmt(format_args!("Database error: {:?}", self))
  45. }
  46. }
  47. impl std::error::Error for DBError {}
  48. /// SQLite database connection
  49. pub struct DB {
  50. conn: sqlite::Connection,
  51. schema_hash: String,
  52. schema: model::SchemaModel,
  53. }
  54. impl DB {
  55. pub fn new(schema: model::SchemaModel, path: &str, mode: CreateMode) -> Result<Self, DBError> {
  56. Self::from_connection(
  57. sqlite::Connection::open(path).map_err(|_| DBError::ConnectFailure)?,
  58. schema,
  59. mode,
  60. )
  61. }
  62. /// Mostly for use in tests, but may be useful in some applications as well.
  63. pub fn new_in_memory(schema: model::SchemaModel) -> Result<Self, DBError> {
  64. Self::from_connection(
  65. sqlite::Connection::open(":memory:").map_err(|_| DBError::ConnectFailure)?,
  66. schema,
  67. CreateMode::AllowNewDatabase,
  68. )
  69. }
  70. /// Get a query interface for this DB connection
  71. pub fn query_interface(&self) -> query::QueryInterface {
  72. query::QueryInterface::new(self)
  73. }
  74. pub fn recreate_schema(&self) -> Result<(), DBError> {
  75. self.create_schema()
  76. }
  77. fn from_connection(
  78. conn: sqlite::Connection,
  79. schema: model::SchemaModel,
  80. mode: CreateMode,
  81. ) -> Result<Self, DBError> {
  82. let sig = Self::calculate_schema_hash(&schema);
  83. let ret = Self {
  84. conn,
  85. schema_hash: sig,
  86. schema: schema.add::<meta::Metaschema>(),
  87. };
  88. ret.check_schema(mode)?;
  89. Ok(ret)
  90. }
  91. fn calculate_schema_hash(schema: &model::SchemaModel) -> String {
  92. use sha2::Digest;
  93. let mut hasher = sha2::Sha256::new();
  94. schema
  95. .drop()
  96. .iter()
  97. .map(|sql| hasher.update(sql.as_bytes()))
  98. .count();
  99. schema
  100. .create()
  101. .iter()
  102. .map(|sql| hasher.update(sql.as_bytes()))
  103. .count();
  104. base64::encode(hasher.finalize())
  105. }
  106. fn check_schema(&self, mode: CreateMode) -> Result<(), DBError> {
  107. let mut has_metaschema = false;
  108. self.conn
  109. .iterate(
  110. format!(
  111. "SELECT * FROM \"sqlite_master\" WHERE \"type\"='table' AND \"name\"='{}'",
  112. Metaschema::table_name()
  113. ),
  114. |_row| {
  115. has_metaschema = true;
  116. true
  117. },
  118. )
  119. .map_err(DBError::EarlyFailure)?;
  120. if !has_metaschema && mode != CreateMode::MustExist {
  121. return self.create_schema();
  122. } else if !has_metaschema && mode == CreateMode::MustExist {
  123. return Err(DBError::NoSchema);
  124. }
  125. let qi = query::QueryInterface::new(self);
  126. let hash = qi.get_one_by(meta::MetaschemaColumns::Key, "schema_hash");
  127. if hash.is_none() {
  128. if mode == CreateMode::MustExist {
  129. return Err(DBError::NoSchema);
  130. }
  131. return self.create_schema();
  132. } else if hash.unwrap().value != self.schema_hash {
  133. if mode != CreateMode::AllowSchemaUpdate {
  134. return Err(DBError::DifferentSchema);
  135. }
  136. self.drop_schema()?;
  137. return self.create_schema();
  138. }
  139. Ok(())
  140. }
  141. fn drop_schema(&self) -> Result<(), DBError> {
  142. for ds in self.schema.drop() {
  143. self.conn.execute(ds).map_err(|_| DBError::DropFailure)?;
  144. }
  145. Ok(())
  146. }
  147. fn create_schema(&self) -> Result<(), DBError> {
  148. for cs in self.schema.create() {
  149. self.conn.execute(cs).map_err(|_| DBError::CreateFailure)?;
  150. }
  151. let qi = query::QueryInterface::new(self);
  152. let add_result = qi.add(&meta::Metaschema {
  153. key: "schema_hash".to_string(),
  154. value: self.schema_hash.clone(),
  155. });
  156. assert!(add_result.is_some());
  157. let sanity_check = qi.get_one_by(meta::MetaschemaColumns::Key, "schema_hash");
  158. assert!(sanity_check.is_some());
  159. assert_eq!(sanity_check.unwrap().value, self.schema_hash);
  160. Ok(())
  161. }
  162. }
  163. /// Add support for multi-threading to a `DB`.
  164. ///
  165. /// This is a thread-local cache that carefully maintains the property that no
  166. /// element of the cache will ever be accessed in any way from another thread. The only
  167. /// way to maintain this property is to leak all data, so this is best used
  168. /// in lightly-threaded programs (or at least a context where threads are long-lived).
  169. /// All cached values are assumed to use interior mutability where needed to maintain state.
  170. ///
  171. /// This approach ensures that all items can live for the provided lifetime `'l`.
  172. pub struct DBPool<'a> {
  173. // normally DB is not Send because the raw sqlite ptr is not Send
  174. // however we assume sqlite is operating in serialized mode, which means
  175. // that it is in fact both `Send` and `Sync`
  176. db: &'a DB,
  177. // we carefully maintain the invariant here that only the thread with the given `ThreadId`
  178. // accesses the QueryInterface part of the pair, which means that despite the fact that
  179. // QueryInterface is neither Send nor Sync can be dismissed in this Send and Sync container
  180. qi: std::sync::RwLock<Vec<(std::thread::ThreadId, &'a QueryInterface<'a>)>>,
  181. }
  182. impl<'a> DBPool<'a> {
  183. pub fn new(db: &'a DB) -> Self {
  184. Self {
  185. db,
  186. qi: std::sync::RwLock::new(Vec::new()),
  187. }
  188. }
  189. /// Get a query interface from this DB pool for the current thread
  190. pub fn query_interface(&self) -> &query::QueryInterface<'a> {
  191. let guard = self.qi.read().expect("Couldn't acquire read lock");
  192. let current_id = std::thread::current().id();
  193. if let Some(res) = guard
  194. .iter()
  195. .find_map(|x| if x.0 == current_id { Some(x.1) } else { None })
  196. {
  197. return res;
  198. }
  199. drop(guard);
  200. let mut guard = self.qi.write().expect("Couldn't acquire write lock");
  201. guard.push((current_id, Box::leak(Box::new(self.db.query_interface()))));
  202. drop(guard);
  203. self.query_interface()
  204. }
  205. }
  206. /// We carefully implement `DBPool` so that it is `Send`.
  207. unsafe impl<'a> Send for DBPool<'a> {}
  208. /// We carefully implement `DBPool` so that it is `Sync`.
  209. unsafe impl<'a> Sync for DBPool<'a> {}
  210. #[cfg(test)]
  211. mod pool_test {
  212. trait IsSend: Send {}
  213. impl IsSend for super::DB {}
  214. impl<'a> IsSend for super::DBPool<'a> {}
  215. // we make sure that DBPool is send / sync safe
  216. trait IsSendAndSync: Send + Sync {}
  217. impl<'a> IsSendAndSync for super::DBPool<'a> {}
  218. }
  219. #[cfg(test)]
  220. mod test {
  221. use super::DB;
  222. #[derive(serde::Serialize, serde::Deserialize, crate::Entity)]
  223. #[microrm_internal]
  224. pub struct S1 {
  225. an_id: i32,
  226. }
  227. fn simple_schema() -> super::model::SchemaModel {
  228. super::model::SchemaModel::new().add::<S1>()
  229. }
  230. #[test]
  231. fn in_memory_schema() {
  232. let _db = DB::new_in_memory(simple_schema());
  233. drop(_db);
  234. }
  235. #[derive(serde::Serialize, serde::Deserialize, crate::Entity)]
  236. #[microrm_internal]
  237. pub struct S2 {
  238. #[microrm_foreign]
  239. parent_id: S1ID,
  240. }
  241. #[test]
  242. fn simple_foreign_key() {
  243. let db = DB::new_in_memory(super::model::SchemaModel::new().add::<S1>().add::<S2>())
  244. .expect("Can't connect to in-memory DB");
  245. let qi = db.query_interface();
  246. let id = qi.add(&S1 { an_id: -1 }).expect("Can't add S1");
  247. let child_id = qi.add(&S2 { parent_id: id }).expect("Can't add S2");
  248. qi.get_one_by_id(child_id).expect("Can't get S2 instance");
  249. }
  250. microrm_macros::make_index_internal!(S2ParentIndex, S2Columns::ParentId);
  251. }
  252. #[cfg(test)]
  253. mod test2 {
  254. #[derive(Debug, crate::Entity, serde::Serialize, serde::Deserialize)]
  255. #[microrm_internal]
  256. pub struct KVStore {
  257. pub key: String,
  258. pub value: String,
  259. }
  260. // the !KVStoreIndex here means a type representing a unique index named KVStoreIndex
  261. microrm_macros::make_index_internal!(!KVStoreIndex, KVStoreColumns::Key);
  262. #[test]
  263. fn dump_test() {
  264. let schema = crate::model::SchemaModel::new()
  265. .add::<KVStore>()
  266. .index::<KVStoreIndex>();
  267. // dump the schema in case you want to inspect it manually
  268. for create_sql in schema.create() {
  269. println!("{};", create_sql);
  270. }
  271. let db = crate::DB::new_in_memory(schema).unwrap();
  272. let qi = db.query_interface();
  273. qi.add(&KVStore {
  274. key: "a_key".to_string(),
  275. value: "a_value".to_string(),
  276. });
  277. // because KVStoreIndex indexes key, this is a logarithmic lookup
  278. let qr = qi.get_one_by(KVStoreColumns::Key, "a_key");
  279. assert_eq!(qr.is_some(), true);
  280. assert_eq!(qr.as_ref().unwrap().key, "a_key");
  281. assert_eq!(qr.as_ref().unwrap().value, "a_value");
  282. }
  283. }
  284. #[cfg(test)]
  285. mod delete_test {
  286. #[derive(Debug, crate::Entity, serde::Serialize, serde::Deserialize)]
  287. #[microrm_internal]
  288. pub struct KVStore {
  289. pub key: String,
  290. pub value: String,
  291. }
  292. #[test]
  293. fn delete_test() {
  294. let schema = crate::model::SchemaModel::new()
  295. .entity::<KVStore>();
  296. let db = crate::DB::new_in_memory(schema).unwrap();
  297. let qi = db.query_interface();
  298. qi.add(&KVStore {
  299. key: "a".to_string(),
  300. value: "a_value".to_string()
  301. });
  302. let insert_two = || {
  303. qi.add(&KVStore {
  304. key: "a".to_string(),
  305. value: "a_value".to_string()
  306. });
  307. qi.add(&KVStore {
  308. key: "a".to_string(),
  309. value: "another_value".to_string()
  310. });
  311. };
  312. assert!(qi.get_one_by(KVStoreColumns::Key, "a").is_some());
  313. // is_some() implies no errors were encountered
  314. assert!(qi.delete_by(KVStoreColumns::Key, "a").is_some());
  315. assert!(qi.get_one_by(KVStoreColumns::Key, "a").is_none());
  316. insert_two();
  317. // this should fail as there is more than one thing matching key='a'
  318. assert!(qi.get_one_by(KVStoreColumns::Key, "a").is_none());
  319. let all = qi.get_all_by(KVStoreColumns::Key, "a");
  320. assert!(all.is_some());
  321. assert_eq!(all.unwrap().len(), 2);
  322. assert!(qi.delete_by(KVStoreColumns::Key, "b").is_some());
  323. let all = qi.get_all_by(KVStoreColumns::Key, "a");
  324. assert!(all.is_some());
  325. assert_eq!(all.unwrap().len(), 2);
  326. assert!(qi.delete_by_multi(&[KVStoreColumns::Key, KVStoreColumns::Value], &crate::value_list![&"a", &"another_value"]).is_some());
  327. let one = qi.get_one_by(KVStoreColumns::Key, "a");
  328. assert!(one.is_some());
  329. assert_eq!(one.unwrap().value, "a_value");
  330. }
  331. }