<div align="center">
<h1>Rullst ORM ๐</h1>
<p><strong>A beautiful, type-safe, Active Record ORM for Rust.</strong></p>
<p>
<a href="https://crates.io/crates/rullst-orm"><img src="https://img.shields.io/crates/v/rullst-orm?style=flat-square&color=orange" alt="Crates.io" /></a>
<a href="https://crates.io/crates/rullst-orm"><img src="https://img.shields.io/crates/d/rullst-orm?style=flat-square&color=orange" alt="Downloads" /></a>
<a href="https://docs.rs/rullst-orm/12.1.1"><img src="https://img.shields.io/docsrs/rullst-orm?style=flat-square&color=blue" alt="Docs.rs" /></a>
<a href="https://github.com/Rullst/Rullst/actions"><img src="https://img.shields.io/github/actions/workflow/status/Rullst/Rullst/ci.yml?branch=v12&style=flat-square&label=Build" alt="Build Status" /></a>
<img src="https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square" alt="License: MIT" />
</p>
</div>
๐ **[Visit the Official Website & Documentation Hub](https://github.com/Rullst/Rullst/blob/v12.1.1/docs/src/SUMMARY.md)** ๐
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.
<div align="center">
<h3>๐ก๏ธ Security Engineering</h3>
<p>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.</p>
| **OpenSSF Scorecard** | <a href="https://scorecard.dev/viewer/?uri=github.com/Rullst/Rullst"><img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fapi.scorecard.dev%2Fprojects%2Fgithub.com%2FRullst%2FRullst&query=%24.score&label=OpenSSF%20Scorecard&style=flat-square" alt="OpenSSF Scorecard" /></a> | Current public supply-chain practice score; not a security certification |
| **Release Provenance** | <a href="https://github.com/Rullst/Rullst/actions/workflows/release.yml"><img src="https://img.shields.io/github/actions/workflow/status/Rullst/Rullst/release.yml?style=flat-square&label=" alt="Release provenance" /></a> | Provenance attestations for release artifacts; no SLSA level is claimed here |
| **Codecov** | <a href="https://codecov.io/gh/Rullst/Rullst"><img src="https://codecov.io/github/Rullst/Rullst/branch/v12/graph/badge.svg?component=framework_libraries" alt="Framework library coverage" /></a> | Blocking 90% target for the measured framework-library scope; repository aggregate remains separately visible |
| **Matrix DB Tests** | <a href="https://github.com/Rullst/Rullst/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/Rullst/Rullst/ci.yml?branch=v12&style=flat-square&label=" alt="Testcontainers" /></a> | Live PostgreSQL, MySQL, MariaDB, MongoDB, SurrealDB and libSQL contracts, plus in-process DuckDB tests |
| **OpenSSF** | <a href="https://www.bestpractices.dev/projects/13359"><img src="https://img.shields.io/cii/level/13359?style=flat-square&label=" alt="OpenSSF Best Practices" /></a> | Open source security standards |
| **Property tests** | <a href="https://github.com/Rullst/Rullst/actions/workflows/proptest.yml"><img src="https://img.shields.io/github/actions/workflow/status/Rullst/Rullst/proptest.yml?branch=v12&style=flat-square&label=Proptest" alt="Proptest" /></a> | Scheduled/manual bounded invariant evidence |
| **Miri research matrix** | <a href="https://github.com/Rullst/Rullst/actions/workflows/miri.yml"><img src="https://img.shields.io/github/actions/workflow/status/Rullst/Rullst/miri.yml?branch=v12&style=flat-square&label=Miri" alt="Miri" /></a> | Manual bounded evidence; the selected compatible scopes are strict, while native database FFI remains outside Miri |
| **Kani research harnesses** | <a href="https://github.com/Rullst/Rullst/actions/workflows/kani.yml"><img src="https://img.shields.io/github/actions/workflow/status/Rullst/Rullst/kani.yml?branch=v12&style=flat-square&label=Kani" alt="Kani" /></a> | Manual, bounded formal evidence; not whole-ORM proof |
| **CodeQL SAST** | <a href="https://github.com/Rullst/Rullst/actions/workflows/codeql.yml"><img src="https://img.shields.io/github/actions/workflow/status/Rullst/Rullst/codeql.yml?branch=v12&style=flat-square&label=" alt="CodeQL SAST" /></a> | Advanced semantic code analysis |
| **Cargo Deny** | <a href="https://github.com/Rullst/Rullst/actions/workflows/cargo-deny.yml"><img src="https://img.shields.io/github/actions/workflow/status/Rullst/Rullst/cargo-deny.yml?branch=v12&style=flat-square&label=" alt="Cargo Deny" /></a> | Banning unmaintained/vulnerable crates |
| **Cargo Audit** | <a href="https://github.com/Rullst/Rullst/actions/workflows/audit.yml"><img src="https://img.shields.io/github/actions/workflow/status/Rullst/Rullst/audit.yml?branch=v12&style=flat-square&label=" alt="Auto-Audit" /></a> | Continuous scanning for crate vulnerabilities |
| **Cargo SemVer** | <a href="https://github.com/Rullst/Rullst/actions/workflows/semver.yml"><img src="https://img.shields.io/github/actions/workflow/status/Rullst/Rullst/semver.yml?branch=v12&style=flat-square&label=" alt="cargo-semver-checks" /></a> | Strict SemVer API breakage checks |
| **Cargo Machete** | <a href="https://github.com/Rullst/Rullst/actions/workflows/machete.yml"><img src="https://img.shields.io/github/actions/workflow/status/Rullst/Rullst/machete.yml?branch=v12&style=flat-square&label=" alt="Cargo Machete" /></a> | Detecting unused and bloated dependencies |
| **On-demand fuzzing** | <a href="https://github.com/Rullst/Rullst/actions/workflows/fuzzing.yml"><img src="https://img.shields.io/github/actions/workflow/status/Rullst/Rullst/fuzzing.yml?branch=v12&style=flat-square&label=Fuzzing" alt="Fuzzing" /></a> | Manual time-bounded targets; no continuous OSS-Fuzz claim |
| **Mutation Testing** | <a href="https://github.com/Rullst/Rullst/actions/workflows/mutants.yml"><img src="https://img.shields.io/github/actions/workflow/status/Rullst/Rullst/mutants.yml?branch=v12&style=flat-square&label=" alt="Mutants" /></a> | Mutation testing for test suite robustness |
| **Continuous Benchmarks** | <a href="https://github.com/Rullst/Rullst/actions/workflows/bench.yml"><img src="https://img.shields.io/github/actions/workflow/status/Rullst/Rullst/bench.yml?branch=v12&style=flat-square&label=" alt="Benchmarks CI" /></a> | Continuous performance regression testing & live dashboard |
| **Unsafe Policy** | <a href="https://github.com/Rullst/Rullst/actions/workflows/unsafe-policy.yml"><img src="https://img.shields.io/github/actions/workflow/status/Rullst/Rullst/unsafe-policy.yml?branch=v12&style=flat-square&label=" alt="Unsafe Policy" /></a> | Audits unsafe usage within the workflow's declared scope |
| **Panic Policy** | <a href="https://github.com/Rullst/Rullst/actions/workflows/zero-panics.yml"><img src="https://img.shields.io/github/actions/workflow/status/Rullst/Rullst/zero-panics.yml?branch=v12&style=flat-square&label=" alt="Panic Policy" /></a> | Graceful error handling across the framework |
</div>
## ๐ Why Rullst ORM?
In traditional Rust database handling, you have to write raw SQL queries, manage connection pools manually, and bind variables repetitively. Rullst ORM abstracts the heavy lifting behind a single `#[derive(Orm)]` macro, generating hundreds of safe, chainable query methods at compile time.
**Key Features:**
- **Generated CRUD**: Derive insert, update, delete, and find helpers from model metadata.
- **Typed Fluent Query Builder**: Generated primitive filters such as
`.where_id(i32)` and `.where_name(impl Into<String>)` reject mismatched value
types at compile time; string-column methods validate identifiers at runtime,
while explicitly named raw methods remain caller-owned SQL escape hatches.
- **Eager Loading**: Batch supported `has_many`, `belongs_to`, `morph_many`,
`morph_one`, and typed `morph_to` targets. Inverse polymorphic fields use an
explicit target per relation and a persisted `<morph_name>_id` plus
`<morph_name>_type` discriminator.
- **Fail-Closed Tenant Scopes**: Models declaring `tenant_column` require
`with_tenant`, inject the tenant predicate into generated queries, protect
instance mutations, and reserve explicit `unscoped()` for reviewed global
paths. Authentication and permission to use that escape hatch remain the
application's responsibility.
- **Actor-Bound Audit Revisions**: Opt-in auditable models require a validated
user/service/system `AuditContext`, derive tenant metadata from `with_tenant`,
and persist recursively redacted bounded changes in the model transaction.
Eligible v2 update revisions can be restored with stale-state, tenant, model,
identity, and sensitive-field guards; the compensating update records its
actor, reason, and source revision. Bulk per-row history and durable external
export remain explicit application/outbox work. See the
[audit revision guide](https://github.com/Rullst/Rullst/blob/v12.1.1/docs/src/tutorials/50-auditable-revisions.md).
- **Bounded Post-Commit Effects**: `after_commit` and the generated observer
`committed` callback run only after `Orm::transaction` or a direct generated
save/delete commits. Rollback discards them, and post-commit failures use a
distinct error that says the database is already durable. These callbacks
remain process-local; caller-owned raw SQLx transactions cannot expose their
eventual commit decision to generated hooks.
- **Durable Transactional Outbox**: opt-in `Outbox` events commit atomically
with domain state and use stream-scoped idempotency keys, bounded leases,
exact claim tokens, retries and dead-letter state. PostgreSQL, MySQL,
MariaDB and SQLite share the contract. Delivery is at least once, so the
external consumer must also be idempotent.
- **Data Governance & Privacy Helpers**: At-rest encryption, recursive audit masking, and data-erasure primitives; legal compliance remains application-specific.
- **Scout Search Providers**: `scout-http` adds bounded Meilisearch,
Elasticsearch and Algolia update/delete/search adapters with deterministic
offline fallbacks. Generated projections run after commit; guaranteed crash
recovery requires explicit outbox composition. Meilisearch has a live pinned
container contract, while Elastic/Algolia have protocol fixtures rather than
hosted-provider certification.
- **Typed pgvector Search**: `pgvector` re-exports SQLx-compatible `Vector` and
the L2/cosine/inner-product helpers bind vector and distance values. A
digest-pinned PostgreSQL + pgvector lifecycle covers typed insert/read and
parameterized nearest-neighbor filtering/ordering; RAG orchestration and
production index tuning remain application concerns.
- **Bounded Qdrant Search**: `qdrant` exposes validated dense-cosine collection,
single-point upsert/delete and bounded nearest-neighbor query operations with
deterministic fallback, authenticated protocol fixtures and a digest-pinned
live lifecycle. Named/sparse vectors, arbitrary filters and ANN tuning remain
explicit provider/application work.
- **Native Redis Structures**: `redis` adds a separately namespaced Hash, Set
and Sorted Set datastore with bounded inputs/reads, TLS-required remote
endpoints, redacted ACL credentials and a deterministic fallback. A pinned
lifecycle covers increment, membership, ranking, exact deletion and namespace
isolation; Lists, Streams and cluster/failover remain outside the contract.
- **Structured ORM Telemetry**: Generated/raw queries and streams emit
`rullst.orm.query` spans without SQL, bindings or model values; managed
transactions record bounded commit/rollback outcomes and every Rullst-owned
pool reports checkout timing. The application's tracing/OpenTelemetry
subscriber controls export, sampling and retention.
- **Reproducible Comparative Benchmark**: A lockfile-pinned Criterion harness
gives Rullst, Diesel and SeaORM one typed SQLite connection, an equivalent
indexed schema/seed/policy and the same five operations. It records evidence
for that exact runner; the initial smoke did not support the historical
โnegligible overhead versus Dieselโ claim.
- **Database-First Introspection**: The official framework CLI (`cargo rullst generate:models`) connects to legacy databases and generates your `#[derive(Orm)]` Rust structs automatically.
- **Declarative Migration Preview**: `make:migration:auto` offers a bounded
SQLite AST/schema diff; review generated SQL before applying it.
- **Atomic Cascading Soft Deletes**: Mark generated has-one/has-many
relationships for cascade; implicit deletes open a transaction when needed,
while explicit or task-scoped transactions are reused without nesting.
- **Typed Partial Updates**: `.update_partial()` changes only explicitly
selected columns and preserves model policy/tenant checks.
- **Native Relational Enums**: `#[derive(Enum)]` owns one closed label mapping
for SQLx, Serde and ORM values. `Blueprint::native_enum` emits a named,
drift-checked PostgreSQL type with `strict-postgres`, inline MySQL/MariaDB
`ENUM`, or a SQLite `TEXT CHECK` constraint.
- **Stable Keyset Chunking**: `.chunk_by_id()` traverses ascending generated
`i32` IDs without offset drift when already processed rows are deleted, and
propagates callback errors. `.chunk()` remains available for offset-based
compatibility.
- **Transaction-Aware Redis Query Cache**: `.remember(seconds)` uses a
versioned SHA-256 key bound to the application namespace, active tenant,
generated SQL and typed bindings. Generated reads bypass cache inside every
ORM transaction so Redis cannot replace the transaction's database view.
Generated model saves/deletes invalidate keys for the active tenant and table
only after commit, using a bounded non-blocking scan; cluster/failover
evidence remains outside the current contract.
- **Model Policies (Authorization)**: `#[orm(policy = "MyPolicy")]` checks generated
instance mutations. Policy-protected models reject `delete_all()` because
bulk SQL cannot invoke per-row authorization; load the intended rows and call
their instance `delete()` methods inside `Orm::transaction` instead.
- **Development lazy-loading diagnostics**: An opt-in development policy can fail loudly when a guarded lazy load would hide an N+1 query.
- **Explicit Capability Boundaries**: Unsupported replication paths fail closed instead of reporting simulated success.
- **Optional Polyglot Persistence**: Feature-gated MongoDB document CRUD,
DuckDB OLAP queries, Turso/libSQL edge SQL, and SurrealDB document/read-only
graph operations live behind explicit capability APIs instead of pretending
to be one universal Active Record interface. See the
[Polyglot guide](https://github.com/Rullst/Rullst/blob/v12.1.1/docs/src/polyglot-persistence.md).
- **Authenticated Portable Document Recovery**: MongoDB, SurrealDB and the
deterministic store expose stable identifier-preserving inventory. A bounded
AES-256-GCM snapshot performs two equal source scans, binds application and
collection scope, resumes only into an empty or exactly matching subset and
verifies the final inventory. It is an application-operated recovery
primitive, not an online cross-store transaction or managed backup service.
- **Bounded Turso Primary Profile**: `#[derive(Orm)]` with
`#[orm(backend = "turso")]` exposes typed CRUD/query methods through
`TursoOrm`, reversible checked migrations, and a persistent offline or remote
libSQL store. The CLI currently guarantees this primary path for the
blank/API starter; SQLx-specific relations, hooks, schema auto-diff and the
other blueprints are not claimed as parity.
---
### Query and transaction boundaries
`select`, `pluck_string`, and `pluck_i32` accept validated column identifiers,
including `table.column`. Expressions and aliases belong in explicit raw SQL
APIs. Qualified names retain skipped/encrypted-field restrictions; supported
encrypted string projections use the original field name when decrypting.
`where_in` and `or_where_in` with an empty vector emit a false predicate, so an
empty selected-ID list cannot become an unrestricted read or delete. An empty
`where_not_in` remains true. Unfiltered `delete_all` on models without policies
is still deliberately a bulk operation. Model-wide, tenant, and soft-delete
scopes constrain every user `OR` branch, and keyset traversal applies its cursor
to the entire original filter. Nested generated subqueries propagate validation
errors, including a missing tenant context, into their containing query.
Generated `Model::search()` uses those same model-wide and tenant scopes for
both its SQL fallback and external Scout result IDs. Missing tenant context
fails before contacting Scout, and an empty provider result remains an empty
match even when a database contains an explicitly inserted ID of zero. Search
index access controls still belong to the application/operator.
Prefer `Orm::transaction` with ordinary model/query methods when combining
eager relationships or `after_fetch` hooks with transactional reads. Fetches
release the managed transaction lock before invoking hooks and loading related
records, and scalar projections use that same transaction. Caller-owned raw
SQLx transactions still support plain `get_with_tx` reads, but eager-loading or
`after_fetch` configurations fail with an actionable error because their
secondary queries require the managed executor context.
A transaction-backed stream retains exclusive access to the transaction until
it is consumed or dropped. Consume/drop it before starting another operation
on that transaction. Transactional streams reject `after_fetch` hooks to avoid
reentrant queries while retaining that access; use `get()` inside
`Orm::transaction` for those models. Streaming does not eagerly load relations.
Full `save`/`delete` policies and their lifecycle callbacks run while their
executor is borrowed.
Ordinary ORM calls made recursively from those callbacks on the same task fail
with a typed `Validation` error instead of waiting on their own transaction.
Perform database authorization and any required row locking explicitly within
the managed transaction before invoking the mutation; the policy can then
validate trusted context and in-memory model values. Defer only post-commit
effects to `after_commit`/`committed`, which run outside that callback scope.
Failure in an `after_save` hook still rolls back the generated mutation.
Independently spawned tasks and separately retained raw connections do not
inherit this executor contract and must not be used to bypass atomicity.
Raw SQL, unscoped `QueryBuilder::new()` construction, direct editing of builder
SQL fields, and custom `SubqueryBuilder`
implementations remain trusted application code. Custom subqueries should
expose their validation failure through `validation_error()`; the trait's
compatibility default does not validate arbitrary custom SQL. These controls
do not authenticate users or decide who may invoke `unscoped()`.
## ๐ ๏ธ Quick Start
### Installation
Add the library to your `Cargo.toml`:
Install the exact stable train with:
```bash
cargo add rullst-orm@12.1.0
cargo add tokio -F full
```
### Zero-to-Hero Example
```rust
use rullst_orm::{Orm, FromRow};
// 1. Just add the Orm macro to your struct!
#[derive(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)] // Excluded from the ORM's generated to_json() projection
pub password: String,
}
#[tokio::main]
async fn main() -> Result<(), rullst_orm::Error> {
// 2. Initialize the connection pool (SQLite, Postgres, MySQL/MariaDB)
Orm::init("sqlite::memory:").await?;
// 3. Create a new user through the generated Active Record API
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 {} user(s)", active_users.len());
Ok(())
}
```
### Selecting an isolated SQLx driver
From 12.1, a standalone ORM dependency can omit unrelated SQLx drivers:
```toml
rullst-orm = { version = "12.1.0", default-features = false, features = ["strict-postgres"] }
```
`strict-mysql` and `strict-sqlite` select their respective backends. The default
`drivers-all` feature preserves the existing three-driver convenience profile.
If an application previously disabled defaults without choosing a backend,
enable `drivers-all` explicitly or select a strict backend when upgrading.
Cargo features combine across dependencies. A dependency enabling ORM defaults,
Studio, a SQLite queue or Turso's offline transport can restore other drivers;
the example above proves a standalone ORM boundary, not a complete facade or
Studio application. Check the final normal/build graph with `cargo tree`.
Derived enums also emit codecs only for the ORM's selected drivers; the
application does not need matching Cargo feature names. The CI consumer compiles
generated CRUD, transactions and enum encoding/decoding and rejects unrelated
SQLx driver packages separately from the workspace test matrix.
### Native database enums
Select a strict primary feature in generated applications. PostgreSQL native
enums specifically require `strict-postgres`, because SQLx's dynamic `Any`
driver cannot decode custom PostgreSQL types:
```toml
rullst-orm = { version = "12.1.0", features = ["strict-postgres"] }
```
Then derive one label contract and use it in schema code:
```rust
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?;
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.
### Optional Redis query cache
Enable the `redis` feature and give each application sharing a Redis database a
stable namespace:
```rust
use rullst_orm::Orm;
Orm::init_redis_with_namespace(
"redis://127.0.0.1:6379",
"academy-production",
).await?;
let users = User::query().where_like("email", "%@example.com")
.remember(30)
.get()
.await?;
```
An explicitly remembered query outside a transaction requires Redis
initialization. Connection/command failures and corrupt cache entries fall back
to the database, while missing configuration fails closed. Explicit and
task-scoped transactions always bypass the cache. Generated model saves and
deletes invalidate that table's generated cache keys after commit. Raw SQL,
bulk builders and writes outside generated model methods cannot be inferred, so
keep a defensive TTL and do not cache authorization or other reads whose
freshness requires a stronger distributed consistency contract.
Register process-local effects against the managed commit boundary:
```rust
use rullst_orm::{Error, Orm, after_commit};
`OutboxMigration` through the application's normal migration runner in
production. Generated observers are not silently persisted, and an ACK lost
after the external effect can cause redelivery; see the
[transactional outbox tutorial](https://github.com/Rullst/Rullst/blob/v12.1.1/docs/src/tutorials/38-transactional-outbox.md).
---
## ๐ Documentation
We recently launched a brand-new **Interactive Documentation Hub**!
๐ **[Explore the Full Documentation in the Rullst Book](https://github.com/Rullst/Rullst/blob/v12.1.1/docs/src/SUMMARY.md)**
---
## ๐ก๏ธ 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](https://github.com/Rullst/Rullst/blob/v12.1.1/LICENSE).