| Crates.io | lorm |
| lib.rs | lorm |
| version | 0.2.2 |
| created_at | 2025-02-16 14:52:36.274776+00 |
| updated_at | 2025-12-15 13:20:20.710599+00 |
| description | A zero cost and lightweight ORM operations for SQLx. |
| homepage | |
| repository | https://github.com/remysaissy/lorm.git |
| max_upload_size | |
| id | 1557806 |
| size | 122,213 |
Lorm is an async and lightweight ORM for SQLx that uses derive macros to generate type-safe database operations at compile time.
created_at and updated_at fieldsAdd Lorm to your Cargo.toml:
[dependencies]
lorm = "0.2"
sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite"] }
tokio = { version = "1", features = ["full"] }
uuid = { version = "1", features = ["v4"] }
chrono = "0.4"
Note: Replace sqlite with your preferred database driver (postgres, mysql).
Lorm supports almost all databases that SQLx supports:
features = ["postgres"] in sqlxfeatures = ["mysql"] in sqlxfeatures = ["sqlite"] in sqlxAll features work consistently across database backends.
Define your model by adding #[derive(ToLOrm)] alongside SQLx's #[derive(FromRow)]:
use sqlx::{FromRow, SqlitePool};
use lorm::ToLOrm;
use uuid::Uuid;
#[derive(Debug, Default, FromRow, ToLOrm)]
struct User {
#[lorm(pk, new = "Uuid::new_v4()")]
pub id: Uuid,
#[lorm(by)]
pub email: String,
#[lorm(created_at, new = "chrono::Utc::now().fixed_offset()")]
pub created_at: chrono::DateTime<chrono::FixedOffset>,
#[lorm(updated_at, new = "chrono::Utc::now().fixed_offset()")]
pub updated_at: chrono::DateTime<chrono::FixedOffset>,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let pool = SqlitePool::connect("sqlite::memory:").await?;
// Create a user
let mut user = User::default();
user.email = "alice@example.com".to_string();
user.save(&pool).await?;
// Find by email (generated from #[lorm(by)])
let found = User::by_email(&pool, "alice@example.com").await?;
println!("Found user: {}", found.email);
// Update the user
user.email = "alice.updated@example.com".to_string();
user.save(&pool).await?;
// Delete the user
user.delete(&pool).await?;
Ok(())
}
Lorm works seamlessly with both Pool and Transaction connections. Check the tests directory for more examples.
Lorm provides several attributes to customize code generation. Attributes can be applied at struct level or field level.
| Attribute | Description | Example | Generated Methods |
|---|---|---|---|
#[lorm(pk)] |
Marks field as primary key. Automatically includes by functionality. Can only be set at creation time unless combined with readonly. |
#[lorm(pk)]pub id: Uuid |
by_id(), delete(), save() |
#[lorm(by)] |
Generates query and utility methods for this field | #[lorm(by)]pub email: String |
by_<field>(), with_<field>(), where_<field>(), order_by_<field>(), group_by_<field>() |
#[lorm(readonly)] |
Field cannot be updated by application code. Database handles the value. | #[lorm(readonly)]pub count: i32 |
Excluded from UPDATE queries |
#[lorm(skip)] |
Field is ignored for all persistence operations. Use with #[sqlx(skip)] |
#[lorm(skip)]#[sqlx(skip)]pub tmp: String |
Excluded from all queries |
#[lorm(created_at)] |
Marks field as creation timestamp | #[lorm(created_at)]pub created_at: DateTime |
Auto-set on INSERT |
#[lorm(updated_at)] |
Marks field as update timestamp | #[lorm(updated_at)]pub updated_at: DateTime |
Auto-set on INSERT and UPDATE |
#[lorm(new="expr")] |
Custom expression to generate field value | #[lorm(new="Uuid::new_v4()")] |
Used in INSERT queries |
#[lorm(is_set="fn()")] |
Custom function to check if field has a value | #[lorm(is_set="is_nil()")] |
Used to determine INSERT vs UPDATE |
#[lorm(rename="name")] |
Renames field to specific column name | #[lorm(rename="user_email")] |
Uses custom column name |
| Attribute | Description | Example |
|---|---|---|
#[lorm(rename="name")] |
Sets custom table name | #[lorm(rename="app_users")]struct User |
User → usersUserDetail → user_detailsuserId → user_idcreatedAt → created_atCommon attribute combinations:
// Auto-generated UUID primary key
#[lorm(pk, new = "Uuid::new_v4()")]
pub id: Uuid
// Timestamp managed by application
#[lorm(created_at, new = "chrono::Utc::now().fixed_offset()")]
pub created_at: DateTime<FixedOffset>
// Timestamp managed by database
#[lorm(created_at, readonly)]
pub created_at: DateTime<FixedOffset>
Lorm generates a fluent query builder using ::select(). The builder supports filtering, ordering, grouping, and pagination.
Filtering (available for #[lorm(by)] fields):
where_{field}(Where::Eq, value) - Equals comparisonwhere_{field}(Where::NotEq, value) - Not equals comparisonwhere_{field}(Where::GreaterThan, value) - Greater thanwhere_{field}(Where::GreaterOrEqualTo, value) - Greater than or equalwhere_{field}(Where::LesserThan, value) - Less thanwhere_{field}(Where::LesserOrEqualTo, value) - Less than or equalwhere_between_{field}(start, end) - Between two values (inclusive)Ordering (available for #[lorm(by)] fields):
order_by_{field}().asc() - Ascending orderorder_by_{field}().desc() - Descending orderGrouping (available for #[lorm(by)] fields):
group_by_{field}() - Group results by fieldPagination:
limit(n) - Limit number of resultsoffset(n) - Skip first n resultsuse lorm::predicates::Where;
// Simple query with exact match
let users = User::select()
.where_email(Where::Eq, "alice@example.com")
.build(&pool)
.await?;
// Filtering and ordering
let recent_users = User::select()
.where_created_at(Where::GreaterOrEqualTo, yesterday)
.order_by_created_at()
.desc()
.build(&pool)
.await?;
// Pagination
let page_2 = User::select()
.order_by_email()
.asc()
.limit(10)
.offset(10)
.build(&pool)
.await?;
// Complex query combining multiple conditions
let results = User::select()
.where_between_id(100, 200)
.where_email(Where::NotEq, "banned@example.com")
.order_by_created_at()
.desc()
.limit(20)
.build(&pool)
.await?;
// Grouping with ordering
let grouped = User::select()
.group_by_email()
.group_by_id()
.order_by_email()
.asc()
.build(&pool)
.await?;
For fields marked with #[lorm(by)], convenience methods are generated:
// Find single record by field (returns first match)
let user = User::by_email(&pool, "alice@example.com").await?;
// Find all records matching field value
let users = User::with_email(&pool, "alice@example.com").await?;
// Delete a specific record (by primary key)
user.delete(&pool).await?;
Complete, runnable examples are available in the examples/ directory:
Run an example with:
cargo run --example basic_crud -p lorm
Additional examples are documented in the test cases at tests/main.rs.
Lorm is significantly lighter and simpler than Diesel:
Choose Lorm for simple CRUD with SQLx, choose Diesel for comprehensive ORM features.
Lorm is better for simple use cases; SeaORM is better for complex applications with relationships.
Yes! Lorm is built on SQLx, so you can mix Lorm-generated methods with raw SQLx queries:
// Use Lorm for simple operations
let user = User::by_email(&pool, "alice@example.com").await?;
// Use SQLx for complex queries
let results = sqlx::query_as::<_, User>(
"SELECT * FROM users WHERE created_at > ? AND status = ?"
)
.bind(yesterday)
.bind("active")
.fetch_all(&pool)
.await?;
No. Lorm focuses on single-table CRUD operations. For relationships and joins, use SQLx directly.
Lorm doesn't provide migrations. Use:
sqlx migrateLorm uses proc macros which add to compile time, but the impact is minimal for small to medium projects. The generated code is optimized and adds no runtime overhead.
Yes! Use cargo expand to see exactly what code Lorm generates:
cargo install cargo-expand
cargo expand --test main
Yes! Lorm works seamlessly with both:
Pool)Transaction)The same methods work with both.
Lorm currently supports single-field primary keys only. For composite keys, use SQLx directly.
No. Lorm generates standard CRUD operations. For custom queries, use SQLx alongside Lorm.
Lorm is in early development (0.x.y versions). The API may change. Use with caution in production and pin your version.
Lorm is designed to be:
cargo expandDefault trait on structs for most operationsUse Lorm when:
Consider alternatives when:
Licensed under Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
Unless you explicitly state otherwise, any Contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be licensed as above, without any additional terms or conditions.