Multiple Connections
Oxide can manage any number of database connections side-by-side. Declare
them once under [connections.*] in config/database.toml, then resolve
them with the DB facade — no passing handles through every function
signature.
Declare the connections
default = "${DB_CONNECTION:-mysql}"
[connections.mysql]driver = "mysql"host = "${DB_HOST:-127.0.0.1}"port = "${DB_PORT:-3306}"database = "${DB_DATABASE:-app}"username = "${DB_USERNAME:-root}"password = "${DB_PASSWORD:-}"
[connections.mysql2]driver = "mysql"host = "${DB2_HOST:-10.0.0.12}"port = "${DB2_PORT:-3306}"database = "${DB2_DATABASE:-reports}"username = "${DB2_USERNAME:-reporter}"password = "${DB2_PASSWORD:-}"
[connections.sqlite]driver = "sqlite"database = "storage/cache.sqlite"default picks which one the rest of the framework uses by default —
Model::get(), conn(), the migrator, oxide migrate, and so on.
Resolve a connection
use oxide::db::DB;
// Default connection (same as `oxide::db::conn()`).let default = DB::conn();
// Named connection — built on first access, cached thereafter.let reports = DB::connection("mysql2");
// Raw SQL against the named connectionreports .conn() .execute_unprepared("INSERT INTO events (kind) VALUES ('sync')") .await?;
// SeaORM entity query against the named connectionuse sea_orm::EntityTrait;let rows = user::Entity::find().all(reports.conn()).await?;DB::connection(name) returns a Database by value. The handle is
cheaply cloneable — the underlying sqlx pool is already Arc-shared —
so pass it around freely.
Laziness
Connections are built on first access, not at boot. That way a broken staging-only connection won’t kill local development. The sqlx pool itself is also lazy: no TCP handshake happens until the first query runs.
Consequence: a typo in the connection name won’t surface until something
actually calls DB::connection("tpyo"). The panic message names the
missing block so it’s easy to spot.
Use inside a Model query
The Model facade works against any connection via on(name):
use crate::app::models::Event;use oxide::db::Model;
// Default connection — unchangedlet all = Event::get().await?;let one = Event::find_id(1).await?;let first = Event::first().await?;
// Named connection — same three methods, scoped to mysql2let all = Event::on("mysql2").get().await?;let one = Event::on("mysql2").find_id(1).await?;let first = Event::on("mysql2").first().await?;on(name) returns a Scope<Entity> holding the resolved Database.
For queries beyond get/find_id/first, grab the connection and
drop into SeaORM’s full builder:
use oxide::db::Model;use sea_orm::{EntityTrait, QueryFilter, ColumnTrait};
let scope = Event::on("mysql2");let open = Event::find() .filter(event::Column::Status.eq("open")) .all(scope.conn()) .await?;Transactions
Transactions live on the connection, not the facade — same pattern:
use sea_orm::TransactionTrait;
let txn = DB::connection("mysql2").conn().begin().await?;// ... writes using `&txn` ...txn.commit().await?;Introspection
let names = DB::connection_names(); // Vec<String>let default = DB::default_name(); // &strlet backend = DB::connection("mysql2").backend(); // DbBackend::MySqlUseful for diagnostic commands or admin tooling.
What’s the default used for?
Unless a caller picks a specific connection, these all talk to the default:
oxide::db::conn()DB::conn()/DB::default_connection()Model::get(),Model::find_id(n),Model::first()- Every
oxide migrate/oxide migrate:freshinvocation - Every entity/command that doesn’t explicitly pass a different
db.conn()
Change database.default (usually via DB_CONNECTION) to redirect all
of the above at once.