Skip to main content

Crate rainier_orm

Crate rainier_orm 

Source
Expand description

§rainier_orm

A dynamic, multi-dialect DBAL/ORM. You define a plain struct, derive Entity on it, and run generic CRUD across SQLite / Cloudflare D1 / MySQL / Postgres — no per-entity repositories written by hand.

Three pieces compose it:

  1. #[derive(Entity)] turns a struct into table + column metadata, a generated row decoder (Entity::from_row), and value projections. Column SQL types are deferred to each field type’s SqlType impl and decoding to its FromColumn impl, so adding a supported type is one trait impl — not macro surgery.
  2. SQL generation via sea-query (re-exported), which already renders MySQL / Postgres / SQLite. Dialect translation is therefore free: the same statement is built for whatever the Executor reports.
  3. A pluggable Executor trait — one impl (sea-orm) covers MySQL, Postgres, and SQLite; the Cloudflare path (D1 over HTTP, D1 native binding in a Worker) implements the same trait. The core is wasm-safe so the Worker can use it; native executors are feature-gated.

Constraints (unique columns, indexes, foreign keys) are derived too; see schema. Relationships are not generated as navigation — a foreign key is a flat id field you traverse with repo::find_by, because the related rows may live in a different backend (a cross-backend JOIN is impossible).

§The executors live elsewhere

This crate defines the Executor port and implements none of it. Every driver — sea-orm for native MySQL / Postgres / SQLite, Cloudflare D1 over HTTP, libSQL / Turso — lives in rainier-drivers, alongside Redis, Memcached and the AWS clients.

That is not filing for its own sake. It is what keeps this crate wasm-safe with no feature dance: there are no optional dependencies here to accidentally enable, so the core compiles for wasm32 unconditionally and a Worker takes the same rainier-orm a server does.

Prefer a native driver wherever the runtime allows it: sea-orm is faster, supports real transactions, and pools locally. The HTTP executors exist for one reason — a wasm32 runtime has no sockets, so a fetch-based transport is the only way to reach a database there.

ⓘ
use rainier_drivers::sql::SeaOrmExecutor;   // native: preferred
use rainier_drivers::sql::D1Executor;       // wasm: HTTP transport
ⓘ
use rainier_orm::{Entity, repo};

#[derive(Entity)]
#[orm(table = "users")]
struct User {
    #[orm(pk, auto_increment)]
    id: u64,
    #[orm(unique)]
    email: String,
    timezone: Option<String>,
}

let id = repo::insert(db, &User { id: 0, email: "a@b.c".into(), timezone: None }).await?;
let u: Option<User> = repo::find_by_pk(db, id).await?;
let by_email: Option<User> = repo::find_one_by(db, "email", "a@b.c").await?;

Beyond CRUD: a fluent query builder (repo::query — predicates, joins, first_or_create, partial update / atomic increment), repo::upsert, a keyset Cursor, derived constraints + DDL (schema), and column types past the primitives: Json<T> (serde-as-text), Base64Bytes (uniform binary), and string-backed enums via impl_string_column!.

Re-exports§

pub use active::Tracked;
pub use blueprint::Action;
pub use blueprint::Blueprint;
pub use blueprint::Column;
pub use blueprint::ColumnKind;
pub use blueprint::IndexDef;
pub use blueprint::TableChanges;
pub use shard::directory_db_name;
pub use shard::shard_db_name;
pub use shard::HashLocator;
pub use shard::MapCatalog;
pub use shard::ShardCatalog;
pub use shard::ShardId;
pub use shard::ShardLocator;
pub use shard::ShardedExecutor;
pub use shard::SlotLocator;
pub use pool::PoolConfig;
pub use query::Query;
pub use sharding::IdAllocator;
pub use sharding::ShardingSettings;
pub use string_column::StringColumn;
pub use sea_query;

Modules§

active
ActiveRecord-style change tracking: load a row, mutate it in place, then save the diff.
blueprint
The schema builder — a table described once, rendered per dialect.
ddl
A declarative, dialect-portable schema-change language — the single way to express an incremental migration step.
migrate
Schema migrations — an ordered list of steps applied idempotently, tracked in a _orm_migrations table, against one executor or many.
pool
Connection-pool configuration — and an honest account of where pooling does and doesn’t apply.
query
A fluent query builder over an Entity — where / where_not / like / in / null checks, ordering, paging, single-backend joins, and the terminals all / first / count / delete / first_or_create.
repo
Generic CRUD over any Entity, plus a keyset Cursor for walking large tables without holding them in memory.
schema
Generic DDL: turn an Entity’s metadata into CREATE TABLE + CREATE INDEX statements, rendered for any Dialect.
shard
The sharding executor — turns N physical databases into one logical Executor that routes each operation to the right shard.
sharding
Sharding policy on top of the routing primitives in crate::shard.
string_column
Support for custom column types stored as text — the common case being a Rust enum persisted as a string ("pending", "verified", …).

Macros§

impl_string_column
Wire a StringColumn type into the ORM’s column traits. See the module docs.

Structs§

Base64Bytes
Bytes persisted as base64 in a TEXT column, uniformly across dialects. Wire it into the ORM with the impl_string_column! machinery via its StringColumn impl below.
ColumnSpec
One column’s metadata, produced by #[derive(Entity)] as a const.
ExecOutcome
The result of a write statement.
ForeignKeySpec
A foreign-key constraint. The local columns reference foreign_table(foreign_columns). Generated from #[orm(references = "table(col)")] on a field.
IndexSpec
A secondary index over one or more columns, produced as a separate CREATE [UNIQUE] INDEX. Single-column indexes come from #[orm(index)] on a field; composite ones from #[orm(index = "a, b")] / #[orm(unique = "a, b")] on the struct.
Json
A serde value persisted as JSON text. Deref/From make it ergonomic to treat as the inner T.
ShardCodec
Packs a physical shard number into the high bits of a u64 id, so any id (primary key or a shard-encoded foreign key) self-describes its shard.

Enums§

ColumnType
The SQL column type, dialect-independent. Rendered per-dialect in crate::schema.
Dialect
The SQL dialect a backend speaks. Cloudflare D1 maps to Sqlite (it is SQLite over HTTP); the executor handles the transport difference.
RefAction
The referential action for a foreign key’s ON DELETE / ON UPDATE.
ShardRoute
Where a single operation should run.

Traits§

Entity
A struct that maps to one table. Implemented by #[derive(Entity)]; hand impls are possible but the derive is the intended path.
EntityTrait
A struct that maps to one table. Implemented by #[derive(Entity)]; hand impls are possible but the derive is the intended path.
Executor
A database backend. The single integration point: implement this and the generic CRUD layer works against your store unchanged.
FromColumn
Decodes one column of a Row into a Rust value. The generated decoder calls T::from_column(row, "col")? per field. The bare T impls treat a NULL as a decode error; the blanket Option<T> impl below turns it into None.
Row
A single result row, addressed by column name. Every getter returns Option so a NULL is distinguishable from a missing/zero value; the non-Option FromColumn impls turn a None into a decode error.
SqlType
Maps a Rust field type to its column type. The derive reads <FieldType as SqlType>::{COLUMN_TYPE, NULLABLE}, so Option<T> becomes a nullable T automatically and new types need only this impl.
ToColumn
Lowers a field value to a sea_query::Value for binding.

Functions§

stable_hash
Stable 64-bit hash (FNV-1a) — deterministic across processes/builds, unlike std’s randomized hasher, so a string shard key always routes the same way.

Type Aliases§

Error
The crate error type. anyhow keeps the surface small and wasm-safe; a typed error is a later tightening.
Result

Derive Macros§

Entity
Factory
Derive a test factory for an entity.