Rustango docs
← Cookbook

Chapter 16 — Every feature on every backend

"Tri-dialect everywhere": every framework surface runs on PostgreSQL, MySQL 8+, and SQLite out of the box. Concretely:

16.220 — Multi-tenant runserver on any backend

What: Cli::tenancy().run().await and server::Builder boot the operator console + tenant admin + host-based dispatch on PG, MySQL, or SQLite.

Recipe:

// Same code on every backend — only DATABASE_URL changes.
#[rustango::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    rustango::manage::Cli::new()
        .tenancy()
        .api(my_app::urls::router())
        .run().await
}

On PG: DATABASE_URL=postgres://…. On MySQL: DATABASE_URL=mysql://…. On SQLite: DATABASE_URL=sqlite:./var/registry.db?mode=rwc.

server::Builder<DB> is generic over the registry backend. Builder::from_env() is the PG-default constructor; Builder::<DB>::from_pool(pool, url, apex) is the explicit-backend constructor for non-PG.

16.221 — Storage modes — pick the right one

ModeBackendsUse when
database (default)PG, MySQL, SQLiteEnterprise B2B; compliance; geographic sharding; small-to-medium N
schemaPostgres onlyHigh-N SaaS on PG (500+ tenants); shared connection pool matters

On MySQL/SQLite, Org.storage_mode = "schema" returns a clear runtime validation error pointing the user at database (semantics equivalent on those backends — one DB / file per tenant).

16.222 — Jobs queue, per-backend pickup

PgJobQueue (name kept for back-compat) now runs on PG / MySQL 8+ / SQLite. PG + MySQL 8+ use FOR UPDATE SKIP LOCKED for atomic multi-worker pickup; SQLite uses a transaction-bounded UPDATE … WHERE id = (SELECT id … LIMIT 1) RETURNING … (SQLite serializes writers globally so the pickup is implicitly mutually- exclusive).

let pool = rustango::sql::Pool::connect("sqlite:./var/jobs.db?mode=rwc").await?;
rustango::jobs::pg::PgJobQueue::ensure_table_pool(&pool).await?;
let queue = std::sync::Arc::new(
    rustango::jobs::pg::PgJobQueue::with_workers_pool(pool, 1)
);
queue.register::<SendWelcomeEmail>().await;
queue.start().await;
queue.dispatch(&SendWelcomeEmail { user_id: 42 }).await?;

Cargo.toml: rustango = { features = ["sqlite", "jobs-postgres"] }. The feature name is preserved for back-compat — the queue itself is no longer PG-only.

16.223 — manage inspectdb on any backend

PG/MySQL use information_schema; SQLite uses PRAGMA table_info

  • sqlite_master. Emits per-dialect type-mapped #[derive(Model)] source.
# Postgres
cargo run -- inspectdb --schema public

# MySQL — `--schema` is the database name (DATABASE() default)
cargo run -- inspectdb

# SQLite — `--schema` is ignored
cargo run -- inspectdb --table users

16.224 — Media on any backend

The media Cargo feature no longer requires postgres. Every MediaManager method dispatches per-dialect; PG-specific SQL idioms (ANY($1), NOW() - INTERVAL, DELETE … USING, ON CONFLICT DO UPDATE, INSERT … RETURNING) translated to portable equivalents.

# Tri-dialect media
rustango = { version = "0.38", default-features = false, features = ["sqlite", "media", "storage"] }
let pool = rustango::sql::Pool::connect("sqlite:./var/app.db?mode=rwc").await?;
rustango::media::ensure_all_tables_pool(&pool).await?;
let manager = MediaManager::new_pool(pool, registry);
let m = manager.save_bytes(opts).await?;
let m = manager.get(m.id.get().copied().unwrap()).await?.unwrap();

16.225 — Permissions facade, fixtures, auth

The top-level rustango::permissions::*_for_model_pool<T> typed helpers, tenancy::auth::authenticate_user_pool, and fixtures:: load_all_pool / Fixture::load_into_pool all run on any backend.

Tests covering this chapter

  • PG live tests — every existing suite still green (1386 lib tests on PG; 22 PG media live; 4 PG jobs live; 3 PG inspectdb live).
  • SQLite live tests:
    • media_sqlite_live — save → get → delete → purge round-trip; collection CRUD; tag lifecycle (ON CONFLICT, IGNORE, subquery DELETE, popular_tags aggregate).
    • jobs_sqlite_live — dispatch persists + drains; pending_count sweep.
    • inspectdb_sqlite_liveAuto<i64> PK + max_length + FK + Option<String> nullable; --table filtering.