Important
This page documents the unreleased v12 source. Use a path dependency from this checkout until the planned
12.0.0-rc.1is published.
🚀 Visit the Official Website & Documentation Hub 🚀
Built on top of sqlx and procedural macros, Rullst ORM brings the delightful, fluent syntax of Active Record frameworks directly to the high-performance Rust ecosystem.
🛡️ Security Engineering
Rullst ORM uses SQLx bindings, validated identifiers, typed errors, and layered CI checks. Workflow badges are scoped test results, not a guarantee for an application or deployment.
🚀 Why Rullst ORM?
Rullst ORM generates Active Record operations and a fluent query builder from
#[derive(Orm)]. SQLx remains available for queries that do not fit the
generated API.
Key Features:
- Generated CRUD: Insert, update, delete, restore, and find operations for supported model shapes.
- Fluent Query Builder: Chain methods such as
.where_eq(),.limit(), and.order_by(); values are bound and structural identifiers are validated. - Relationships and eager loading:
has_many,has_one,belongs_to, and polymorphic relationship helpers, with explicit eager-load methods. - Opt-in tenant scope:
#[orm(tenant_column = "account_id")]adds the configured task-local tenant to generated model queries. Applications must establish the tenant context at their authenticated boundary. - Actor-bound audit revisions:
#[orm(auditable)]requires a validated user/service/systemAuditContext; the active tenant and optional correlation ID are recorded with recursively redacted bounded changes. Generated instance saves/deletes and their audit entry share a savepoint and fail together. Eligible v2 updates expose guarded revision restoration, which rejects stale, cross-tenant, redacted, malformed, legacy, create/delete, and oversized revisions and records a compensating audit entry. The host still derives authenticated principal/tenant authority, while bulk per-row history and durable export remain explicit. See Auditable Revisions. - Field privacy:
#[orm(encrypted)]transparently encrypts supportedStringfields with a versioned AES-256-GCM envelope. Randomized ciphertext cannot be filtered or sorted; use a separate keyed blind index where needed. - Native relational enums:
#[derive(Enum)]owns one closed label mapping for SQLx, Serde and ORM values.Blueprint::native_enumemits a named, drift-checked PostgreSQL type withstrict-postgres, inline MySQL/MariaDBENUM, or a SQLiteTEXT CHECKconstraint. - Scout hooks and providers:
#[orm(searchable)]calls a configuredSearchEngineafter generated writes/deletes.scout-httpsupplies bounded Meilisearch, Elasticsearch and Algolia adapters; the generated effect is process-local unless the application composes the transactional outbox. See Scout Search Providers. - Typed pgvector queries:
pgvectorre-exportsVectorwith SQLx support; vector/distance values in L2, cosine and inner-product helpers are bound, not interpolated. The strict PostgreSQL matrix creates the extension and runs a typed live lifecycle. See RAG Systems & Vector Search. - Bounded Qdrant vectors:
qdrantkeeps specialized dense-cosine collection/upsert/delete/query semantics separate from SQL Active Record, with resource/transport bounds, deterministic fallback, authenticated protocol fixtures and a pinned live lifecycle. - Native Redis structures:
redisadds an immutable namespace and bounded Hash, Set and Sorted Set operations in addition to.remember; remote endpoints require TLS and live evidence covers isolation and native commands. - Portable document recovery: MongoDB, SurrealDB and the deterministic store expose identifier-preserving inventory. An application-operated, AES-256-GCM snapshot binds application/collection scope, compares two bounded source observations, resumes only into an exact destination subset and verifies the final inventory. Writers, schema provisioning, key custody and durable backup storage remain explicit operator responsibilities. See Polyglot Persistence.
- Structured telemetry: generated/raw query and stream spans expose only static model/table/operation metadata, managed transactions record bounded outcomes, and Rullst-created pools emit checkout timing. Core’s opt-in OpenTelemetry layer can export the standard tracing signals; subscriber, sampling, collector and separately configured SQLx logs remain host policy.
- Comparative SQLite evidence: a lockfile-pinned Criterion harness gives Rullst, Diesel and SeaORM one typed connection, the same indexed schema, 100-row seed, SQLite policy and five logical operations. The CI history is scoped comparison evidence; it does not claim universal or negligible overhead, networked-database throughput or complete-application performance.
- Durable opt-in outbox:
Outbox::enqueuecommits a stream-scoped, idempotent event with relational domain state. Exact lease tokens, bounded retry and dead-letter are shared by SQLite, PostgreSQL, MySQL and MariaDB. Delivery is at least once, so the application dispatcher and consumer remain idempotent; generated observers are not silently converted into events. See the transactional outbox tutorial. - Database-first introspection:
cargo rullst generate:modelsreads SQLite, PostgreSQL, or MySQL metadata using bound schema/table parameters, normalizes table module identifiers, and rejects unsafe SQL identifiers, collisions, or columns requiring unsupported ORM remapping before writing files. - Additive migration generation:
make:migration:autocompares supported model definitions and emits a migration for review. - Cascading soft deletes: Opt-in relationship metadata can cascade through generated delete methods; transaction-aware variants use the supplied transaction.
- Partial updates:
.update_partial()binds only the selected supported fields. - Model policies:
#[orm(policy = "MyPolicy")]invokes the configured policy on generated create/update/delete/restore operations. - Strict lazy-loading prevention: the global toggle makes generated lazy relationship methods return a validation error instead of performing the query.
- Explicit Capability Boundaries: Unsupported replication paths fail closed instead of reporting simulated success.
🛠️ Quick Start
Installation
After the RC is published, install its exact train with:
cargo add rullst-orm@12.0.0-rc.1
cargo add tokio -F full
Zero-to-Hero Example
use rullst_orm::{Orm, FromRow};
// 1. Just add the Orm macro to your struct!
#[derive(Debug, Clone, FromRow, Orm)]
pub struct User {
pub id: i32, // ID = 0 means it hasn't been saved yet
pub name: String,
pub email: String,
#[orm(hidden)] // Won't be exposed in JSON responses
pub password: String,
}
#[tokio::main]
async fn main() -> Result<(), rullst_orm::Error> {
// 2. Initialize the connection pool (Supports SQLite, Postgres, MySQL)
Orm::init("sqlite::memory:").await?;
// 3. Create a new user
let mut user = User {
id: 0,
name: "Alice".to_string(),
email: "alice@example.com".to_string(),
password: "secret_password".to_string(),
};
user.save().await?; // Runs INSERT and hydrates the generated ID.
// 4. Fluent Queries
let active_users = User::query()
.where_like("email", "%@example.com")
.order_by_desc("id")
.limit(10)
.get()
.await?;
println!("Found users: {:?}", active_users);
Ok(())
}
Native database enums
Generated applications should select a strict primary feature. PostgreSQL
native enums specifically require strict-postgres, because SQLx’s dynamic
Any driver cannot decode custom PostgreSQL types:
rullst-orm = { version = "12.0.0-rc.1", features = ["strict-postgres"] }
Derive one label contract and use it in schema code:
#![allow(unused)]
fn main() {
use rullst_orm::schema::{Blueprint, Schema};
use rullst_orm::{Enum, Orm};
#[derive(Enum, Debug, Clone, Copy, PartialEq, Eq)]
#[rullst_enum(type_name = "account_status", rename_all = "snake_case")]
enum AccountStatus {
AwaitingReview,
Active,
}
async fn create_schema() -> Result<(), rullst_orm::Error> {
Orm::init("postgres://user:password@localhost/application").await?;
Schema::create("accounts", |table: &mut Blueprint| {
table.id();
table.native_enum::<AccountStatus>("status").not_null();
}).await?;
Ok(())
}
}
The derive accepts 1–64 unique labels of at most 63 bytes using ASCII letters,
digits, spaces, underscores or hyphens. An existing PostgreSQL type must have
the exact same ordered labels or schema creation fails. MySQL/MariaDB store the
labels in the table’s inline ENUM; SQLite enforces them through TEXT CHECK.
Adding, removing or reordering labels is an explicit reviewed migration. Drop
every dependent table before calling Schema::drop_native_enum::<T>() on
PostgreSQL; the method is a validated no-op on the other backends.
📚 Documentation
We recently launched a brand-new Interactive Documentation Hub!
👉 Explore the Full Documentation in the Rullst Book
🛡️ Security
Rullst ORM uses SQLx prepared-statement bindings for values accepted by its query builders. Structural identifiers are restricted to a bounded ASCII identifier grammar before interpolation. Raw SQL and application authorization remain the caller’s responsibility; these controls reduce injection risk but are not an absolute safety guarantee.
📄 License
This project is licensed under the MIT License.