1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
//! microrm is a simple object relational manager (ORM) for sqlite.
//!
//! Unlike many fancier ORM systems, microrm is designed to be lightweight, both in terms of
//! runtime overhead and developer LoC. By necessity, it sacrifices flexibility towards these
//! goals, and so can be thought of as more opinionated than, say,
//! [SeaORM](https://www.sea-ql.org/SeaORM/) or [Diesel](https://diesel.rs/). The major limitations
//! of microrm are clumsy migrations and somewhat limited vocabulary for describing
//! object-to-object relations. Despite this, microrm is usually powerful enough for most usecases
//! where sqlite is appropriate.
//!
//! There are three externally-facing components in microrm:
//! - Schema modelling (mostly by the [`Datum`](schema/datum/trait.Datum.html) and
//! [`Entity`](schema/entity/trait.Entity.html) traits)
//! - Database querying (via [`query::Queryable`], [`query::RelationInterface`] and
//! [`query::Insertable`] traits)
//! - Command-line interface generation via the [`clap`](https://docs.rs/clap/latest/clap/) crate
//! (see [`cli::Autogenerate`] and [`cli::EntityInterface`]; requires the optional crate feature `clap`)
//!
//! microrm pushes the Rust type system somewhat to provide better ergonomics, so the MSRV is
//! currently 1.82. Don't be scared off by the web of traits in the `schema` module --- you should
//! never need to interact with most of them unless you're doing schema reflection.
//!
//! ### A note on async support
//!
//! microrm does not currently support dedicated SQLite threads, something done by e.g. Python's
//! [aiosqlite](https://pypi.org/project/aiosqlite/). Instead, all SQLite processing is done
//! on-thread. Whether this will mix well with single-threaded async runtimes under heavy load is
//! application-dependent; in particular, running in an environment where I/O with the SQLite
//! database can hang (e.g. over NFS) this may result in stalling the worker thread.
//!
//! ### Book
//!
//! The [`_book`] module contains some extra tutorial-like documentation that may be of interest
//! (requires the `_book` feature).
//!
//! ### Examples
//! #### KV-store
//! For the simplest kind of database schema, a key-value store, one possible microrm
//! implementation of it might look like the following:
//!
//! ```
//! use microrm::prelude::*;
//!
//! #[derive(Entity)]
//! struct KVEntry {
//! #[key]
//! key: String,
//! value: String,
//! }
//!
//! #[derive(Schema)]
//! struct KVSchema {
//! kvs: microrm::IDMap<KVEntry>,
//! }
//!
//! # fn main() -> Result<(), microrm::Error> {
//! let (cpool, schema) = microrm::ConnectionPool::open::<KVSchema>(":memory:")?;
//! let mut txn = cpool.start()?;
//! schema.kvs.insert(&mut txn, KVEntry {
//! key: "example_key".to_string(),
//! value: "example_value".to_string()
//! })?;
//!
//! // can get with a String reference
//! assert_eq!(
//! schema.kvs.keyed(&String::from("example_key")).get(&mut txn)?.map(|v| v.value.clone()),
//! Some("example_value".to_string()));
//! // thanks to the QueryEquivalent trait, we can also just use a plain &str
//! assert_eq!(
//! schema.kvs.keyed("example_key").get(&mut txn)?.map(|v| v.value.clone()),
//! Some("example_value".to_string()));
//!
//! // obviously, if we get another KV entry with a missing key, it doesn't come back...
//! assert_eq!(schema.kvs.keyed("another_example_key").get(&mut txn)?.is_some(), false);
//!
//! // note that the above all return an Option<Stored<T>>. when using filters on arbitrary
//! // columns, a Vec<Stored<T>> is returned:
//! assert_eq!(
//! schema
//! .kvs
//! // note that the column constant uses CamelCase
//! .with(KVEntry::Value, "example_value")
//! .get(&mut txn)?
//! .into_iter()
//! .map(|v| v.wrapped().value).collect::<Vec<_>>(),
//! vec!["example_value".to_string()]);
//!
//! // if we're done with the transaction, commit it.
//! txn.commit()?;
//!
//! # Ok(())
//! # }
//! ```
//!
//! #### Simple e-commerce schema
//!
//! The following is an example of what a simple e-commerce website's schema might look like,
//! tracking products, users, and orders.
//!
//! ```rust
//! use microrm::prelude::*;
//!
//! #[derive(Entity)]
//! pub struct ProductImage {
//! // note that because this references an entity's autogenerated ID type,
//! // this is a foreign key. if the linked product is deleted, the linked
//! // ProductImages will also be deleted.
//! pub product: ProductID,
//! pub img_data: Vec<u8>,
//! }
//!
//! #[derive(Entity, Clone)]
//! pub struct Product {
//! #[key]
//! pub title: String,
//! pub longform_body: String,
//! pub images: microrm::RelationMap<ProductImage>,
//! pub cost: f64,
//! }
//!
//! // define a relation between customers and orders
//! pub struct CustomerOrders;
//! impl microrm::Relation for CustomerOrders {
//! type Domain = Customer;
//! type Range = Order;
//! const NAME: &'static str = "CustomerOrders";
//! // at most one customer per order
//! const INJECTIVE: bool = true;
//! }
//!
//! #[derive(Entity)]
//! pub struct Customer {
//! pub orders: microrm::RelationDomain<CustomerOrders>,
//! // mark as part of the primary key
//! #[key]
//! pub email: String,
//! // enforce uniqueness of legal names
//! #[unique]
//! pub legal_name: String,
//!
//! // elide the secrets from Debug output
//! #[elide]
//! pub password_salt: String,
//! #[elide]
//! pub password_hash: String,
//!
//! }
//!
//! #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
//! pub enum OrderState {
//! AwaitingPayment,
//! PaymentReceived { confirmation: String },
//! ProductsReserved,
//! Shipped { tracking_no: String },
//! OnHold { reason: String },
//! }
//!
//! #[derive(Entity)]
//! pub struct Order {
//! // use an ordinary type and do transparent JSON de/serialization
//! pub order_state: microrm::Serialized<Vec<OrderState>>,
//! pub customer: microrm::RelationRange<CustomerOrders>,
//! pub shipping_address: String,
//!
//! // we may not have a billing address
//! pub billing_address: Option<String>,
//!
//! // we'll assume for now that there's no product multiplicities, i.e. `contents` is not a multiset
//! pub contents: microrm::RelationMap<Product>,
//! }
//!
//! #[derive(Schema)]
//! pub struct ECommerceDB {
//! pub products: microrm::IDMap<Product>,
//! pub customers: microrm::IDMap<Customer>,
//! pub orders: microrm::IDMap<Order>,
//! }
//! # fn main() -> Result<(), microrm::Error> {
//! // open a database instance
//! let (cpool, schema) = microrm::ConnectionPool::open::<ECommerceDB>(":memory:")?;
//! let mut txn = cpool.start()?;
//!
//! // add an example product
//! let widget1 = schema.products.insert_and_return(&mut txn, Product {
//! title: "Widget Title Here".into(),
//! longform_body: "The first kind of widget that WidgetCo produces.".into(),
//! cost: 100.98,
//! images: Default::default()
//! })?;
//!
//! // add an image for the product
//! widget1.images.insert(&mut txn, ProductImage {
//! product: widget1.id(),
//! img_data: [/* image data goes here */].into(),
//! });
//!
//! // sign us up for this most excellent ecommerce website
//! let customer1 = schema.customers.insert_and_return(&mut txn, Customer {
//! email: "your@email.here".into(),
//! legal_name: "Douglas Adams".into(),
//! password_salt: "pepper".into(),
//! password_hash: "browns".into(),
//!
//! orders: Default::default(),
//! })?;
//!
//! // put in an order for the widget!
//! let mut order1 = schema.orders.insert_and_return(&mut txn, Order {
//! order_state: vec![OrderState::AwaitingPayment].into(),
//! customer: Default::default(),
//! shipping_address: "North Pole, Canada, H0H0H0".into(),
//! billing_address: None,
//! contents: Default::default(),
//! })?;
//! order1.contents.connect_to(&mut txn, widget1.id())?;
//! order1.customer.connect_to(&mut txn, customer1.id())?;
//!
//! // Now get all products that customer1 has ever ordered
//! let all_ordered = customer1.orders.join(Order::Contents).get(&mut txn)?;
//! assert_eq!(all_ordered, vec![widget1]);
//!
//! // process the payment for our order by updating the entity
//! order1.order_state.as_mut().push(
//! OrderState::PaymentReceived {
//! confirmation: "money received in full, i promise".into()
//! }
//! );
//!
//! // now synchronize the entity changes in the transaction
//! order1.sync(&mut txn)?;
//!
//! // commit the transaction
//! txn.commit()?;
//!
//! # Ok(())
//! # }
//! ```
// this requires clippy 1.78
// to make the proc_macros work inside the microrm crate; needed for tests and the metaschema.
extern crate self as microrm;
// add documentation book to docs.rs, but don't bother during normal builds ---
// unless specifically requested.
/// SQLite database interaction functions.
// re-exports
pub use ;
pub use ;
pub use ;
pub use ;
/// Re-exported traits and macros for easy access.
// ----------------------------------------------------------------------
// Generically-useful database types
// ----------------------------------------------------------------------
/// microrm error type, returned from most microrm methods.
/// Shorthand alias.
pub type DBResult<T> = ;