22 releases
0.4.4 | Oct 2, 2024 |
---|---|
0.4.2 | Sep 24, 2024 |
0.4.0-rc.2 | Apr 14, 2024 |
0.3.12 | Nov 23, 2023 |
0.3.5 | Jul 29, 2022 |
#1684 in Database interfaces
353 downloads per month
185KB
4.5K
SLoC
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 or Diesel. Major limitations of microrm are:
- lack of database migration support
- limited vocabulary for describing object-to-object relations
microrm pushes the Rust type system somewhat to provide better ergonomics, so
the MSRV is currently 1.75. Don't be scared off by the web of traits in the
schema
module --- you should never need to interact with any of them!
lib.rs
:
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 or Diesel. Major limitations of microrm are:
- lack of database migration support
- limited vocabulary for describing object-to-object relations
There are three externally-facing components in microrm:
- Object modelling (via the
Datum
andEntity
traits) - Database querying (via
Queryable
,RelationInterface
andInsertable
traits) - Command-line interface generation via the
clap
crate (seecli::Autogenerate
andcli::EntityInterface
; requires the optional crate featureclap
)
microrm pushes the Rust type system somewhat to provide better ergonomics, so the MSRV is
currently 1.75. Don't be scared off by the web of traits in the schema
module --- you should
never need to interact with any of them!
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(Database)]
struct KVDB {
kvs: microrm::IDMap<KVEntry>,
}
let db = KVDB::open_path(":memory:")?;
db.kvs.insert(KVEntry {
key: "example_key".to_string(),
value: "example_value".to_string()
})?;
// can get with a String reference
assert_eq!(
db.kvs.keyed(&String::from("example_key")).get()?.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!(
db.kvs.keyed("example_key").get()?.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!(db.kvs.keyed("another_example_key").get()?.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!(
db
.kvs
// note that the column constant uses CamelCase
.with(KVEntry::Value, "example_value")
.get()?
.into_iter()
.map(|v| v.wrapped().value).collect::<Vec<_>>(),
vec!["example_value".to_string()]);
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.
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>,
#[key]
pub email: String,
#[unique]
pub legal_name: String,
#[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 {
pub order_state: microrm::Serialized<Vec<OrderState>>,
pub customer: microrm::RelationRange<CustomerOrders>,
pub shipping_address: String,
pub billing_address: Option<String>,
// we'll assume for now that there's no product multiplicities, i.e. this is not a multiset
pub contents: microrm::RelationMap<Product>,
}
#[derive(Database)]
pub struct ECommerceDB {
pub products: microrm::IDMap<Product>,
pub customers: microrm::IDMap<Customer>,
pub orders: microrm::IDMap<Order>,
}
// open a database instance
let db = ECommerceDB::open_path(":memory:")?;
// add an example product
let widget1 = db.products.insert_and_return(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(ProductImage {
product: widget1.id(),
img_data: [/* image data goes here */].into(),
});
// sign us up for this most excellent ecommerce website
let customer1 = db.customers.insert_and_return(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 = db.orders.insert_and_return(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(widget1.id())?;
order1.customer.connect_to(customer1.id())?;
// Now get all products that customer1 has ever ordered
let all_ordered = customer1.orders.join(Order::Contents).get()?;
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 back into the database
order1.sync()?;
Dependencies
~23MB
~455K SLoC