Expand description

Traits, utilities, and a macro for easy database connection pooling.

Overview

This crate provides traits, utilities, and a procedural macro for configuring and accessing database connection pools in Rocket. A database connection pool is a data structure that maintains active database connections for later use in the application. This implementation is backed by r2d2 and exposes connections through request guards.

Databases are individually configured through Rocket’s regular configuration mechanisms. Connecting a Rocket application to a database using this library occurs in three simple steps:

  1. Configure your databases in Rocket.toml. (see Configuration)
  2. Associate a request guard type and fairing with each database. (see Guard Types)
  3. Use the request guard to retrieve a connection in a handler. (see Handlers)

For a list of supported databases, see Provided Databases. This support can be easily extended by implementing the Poolable trait. See Extending for more.

Example

Before using this library, the feature corresponding to your database type in rocket_sync_db_pools must be enabled:

[dependencies.rocket_sync_db_pools]
version = "=0.1.0-rc.3"
features = ["diesel_sqlite_pool"]

See Provided for a list of supported database and their associated feature name.

In whichever configuration source you choose, configure a databases dictionary with an internal dictionary for each database, here sqlite_logs in a TOML source:

[default.databases]
sqlite_logs = { url = "/path/to/database.sqlite" }

In your application’s source code, one-time:

use rocket_sync_db_pools::{database, diesel};

#[database("sqlite_logs")]
struct LogsDbConn(diesel::SqliteConnection);

#[launch]
fn rocket() -> _ {
    rocket::build().attach(LogsDbConn::fairing())
}

Whenever a connection to the database is needed:

#[get("/logs/<id>")]
async fn get_logs(conn: LogsDbConn, id: usize) -> Result<Logs> {
    conn.run(|c| Logs::by_id(c, id)).await
}

Usage

Configuration

Databases can be configured as any other values. Using the default configuration provider, either via Rocket.toml or environment variables. You can also use a custom provider.

Rocket.toml

To configure a database via Rocket.toml, add a table for each database to the databases table where the key is a name of your choice. The table should have a url key and, optionally, pool_size and timeout keys. This looks as follows:

# Option 1:
[global.databases]
sqlite_db = { url = "db.sqlite" }

# Option 2:
[global.databases.my_db]
url = "postgres://root:root@localhost/my_db"

# With `pool_size` and `timeout` keys:
[global.databases.sqlite_db]
url = "db.sqlite"
pool_size = 20
timeout = 5

The table requires one key:

  • url - the URl to the database

Additionally, all configurations accept the following optional keys:

  • pool_size - the size of the pool, i.e., the number of connections to pool (defaults to the configured number of workers * 4)
  • timeout - max number of seconds to wait for a connection to become available (defaults to 5)

Additional options may be required or supported by other adapters.

Procedurally

Databases can also be configured procedurally via rocket::custom(). The example below does just this:

use rocket::figment::{value::{Map, Value}, util::map};

#[launch]
fn rocket() -> _ {
    let db: Map<_, Value> = map! {
        "url" => "db.sqlite".into(),
        "pool_size" => 10.into(),
        "timeout" => 5.into(),
    };

    let figment = rocket::Config::figment()
        .merge(("databases", map!["my_db" => db]));

    rocket::custom(figment)
}

Environment Variables

Lastly, databases can be configured via environment variables by specifying the databases table as detailed in the Environment Variables configuration guide:

ROCKET_DATABASES='{my_db={url="db.sqlite"}}'

Multiple databases can be specified in the ROCKET_DATABASES environment variable as well by comma separating them:

ROCKET_DATABASES='{my_db={url="db.sqlite"},my_pg_db={url="postgres://root:root@localhost/my_pg_db"}}'

Guard Types

Once a database has been configured, the #[database] attribute can be used to tie a type in your application to a configured database. The database attribute accepts a single string parameter that indicates the name of the database. This corresponds to the database name set as the database’s configuration key.

See ExampleDb for everything that the macro generates. Specifically, it generates:

  • A FromRequest implementation for the decorated type.
  • A Sentinel implementation for the decorated type.
  • A fairing() method to initialize the database.
  • A run() method to execute blocking database operations in an async-safe manner.
  • A pool() method to retrieve the backing connection pool.

The attribute can only be applied to tuple structs with one field. The internal type of the structure must implement Poolable.

use rocket_sync_db_pools::diesel;

#[database("my_db")]
struct MyDatabase(diesel::SqliteConnection);

Other databases can be used by specifying their respective Poolable type:

use rocket_sync_db_pools::postgres;

#[database("my_pg_db")]
struct MyPgDatabase(postgres::Client);

The fairing returned from the generated fairing() method must be attached for the request guard implementation to succeed. Putting the pieces together, a use of the #[database] attribute looks as follows:

use rocket_sync_db_pools::diesel;

#[database("my_db")]
struct MyDatabase(diesel::SqliteConnection);

#[launch]
fn rocket() -> _ {
    rocket::custom(figment).attach(MyDatabase::fairing())
}

Handlers

Finally, use your type as a request guard in a handler to retrieve a connection wrapper for the database:

#[database("my_db")]
struct MyDatabase(diesel::SqliteConnection);

#[get("/")]
fn my_handler(conn: MyDatabase) {
    // ...
}

A connection can be retrieved and used with the run() method:

#[database("my_db")]
struct MyDatabase(diesel::SqliteConnection);

fn load_from_db(conn: &diesel::SqliteConnection) -> Data {
    // Do something with connection, return some data.
}

#[get("/")]
async fn my_handler(mut conn: MyDatabase) -> Data {
    conn.run(|c| load_from_db(c)).await
}

Database Support

Built-in support is provided for many popular databases and drivers. Support can be easily extended by Poolable implementations.

Provided

The list below includes all presently supported database adapters and their corresponding Poolable type.

KindDriverVersionPoolable TypeFeature
SqliteDiesel2diesel::SqliteConnectiondiesel_sqlite_pool
PostgresDiesel2diesel::PgConnectiondiesel_postgres_pool
MySQLDiesel2diesel::MysqlConnectiondiesel_mysql_pool
PostgresRust-Postgres0.19postgres::Clientpostgres_pool
SqliteRusqlite0.27rusqlite::Connectionsqlite_pool
Memcachememcache0.15memcache::Clientmemcache_pool

The above table lists all the supported database adapters in this library. In order to use particular Poolable type that’s included in this library, you must first enable the feature listed in the “Feature” column. The interior type of your decorated database type should match the type in the “Poolable Type” column.

Extending

Extending Rocket’s support to your own custom database adapter (or other database-like struct that can be pooled by r2d2) is as easy as implementing the Poolable trait. See the documentation for Poolable for more details on how to implement it.

Re-exports

Structs

  • A base Config for any Poolable type.

Enums

  • A wrapper around r2d2::Errors or a custom database error type.

Traits

  • Trait implemented by r2d2-based database adapters.

Type Aliases

Attribute Macros

  • Generates a request guard and fairing for retrieving a database connection.