Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Rullst ORM 🌟

A beautiful, type-safe, Active Record ORM for Rust.

Crates.io Downloads Docs.rs Build Status License: MIT

Important

This page documents the unreleased v12 source. Use a path dependency from this checkout until the planned 12.0.0-rc.1 is 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.

Security AuditStatusDescription
OpenSSF ScorecardOpenSSF ScorecardCurrent public supply-chain practice score; not a security certification
Release ProvenanceRelease provenanceProvenance attestations for release artifacts; no SLSA level is claimed here
CodecovFramework library coverageBlocking 90% target for the measured framework-library scope; the complete repository aggregate now also has its own 90% gate
Matrix DB TestsTestcontainersLive PostgreSQL, MySQL, MariaDB, MongoDB, SurrealDB and libSQL contracts, plus in-process DuckDB tests
OpenSSFOpenSSF Best PracticesOpen source security standards
Property testsProptestScheduled/manual bounded invariant evidence
Miri research matrixMiriManual bounded evidence; the selected pure-Rust privacy scope is strict, while native database FFI remains outside Miri
Kani research harnessesKaniManual, bounded formal evidence; not whole-ORM proof
CodeQL SASTCodeQL SASTAdvanced semantic code analysis
Cargo DenyCargo DenyBanning unmaintained/vulnerable crates
Cargo AuditAuto-AuditContinuous scanning for crate vulnerabilities
Cargo SemVercargo-semver-checksStrict SemVer API breakage checks
Cargo MacheteCargo MacheteDetecting unused and bloated dependencies
On-demand fuzzingFuzzingManual time-bounded targets; no continuous OSS-Fuzz claim
Mutation TestingMutantsMutation testing for test suite robustness
Continuous BenchmarksBenchmarks CIContinuous performance regression testing & live dashboard
Unsafe PolicyUnsafe PolicyAudits unsafe usage within the workflow’s declared scope
Panic PolicyPanic PolicyGraceful error handling across the framework

🚀 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/system AuditContext; 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 supported String fields 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_enum emits a named, drift-checked PostgreSQL type with strict-postgres, inline MySQL/MariaDB ENUM, or a SQLite TEXT CHECK constraint.
  • Scout hooks and providers: #[orm(searchable)] calls a configured SearchEngine after generated writes/deletes. scout-http supplies 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: pgvector re-exports Vector with 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: qdrant keeps 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: redis adds 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::enqueue commits 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:models reads 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:auto compares 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.