mirror of https://github.com/rwf2/Rocket.git
Rearrange contrib database support modules.
This commit is contained in:
parent
4b09e77ccb
commit
2366bff05f
|
@ -1,877 +0,0 @@
|
||||||
//! Traits, utilities, and a macro for easy database connection pooling.
|
|
||||||
//!
|
|
||||||
//! # Overview
|
|
||||||
//!
|
|
||||||
//! This module provides traits, utilities, and a procedural macro that allows
|
|
||||||
//! you to easily connect your Rocket application to databases through
|
|
||||||
//! connection pools. A _database connection pool_ is a data structure that
|
|
||||||
//! maintains active database connections for later use in the application.
|
|
||||||
//! This implementation of connection pooling support is based on
|
|
||||||
//! [`r2d2`] and exposes connections through [request guards]. Databases are
|
|
||||||
//! individually configured through Rocket's regular configuration mechanisms: a
|
|
||||||
//! `Rocket.toml` file, environment variables, or procedurally.
|
|
||||||
//!
|
|
||||||
//! Connecting your Rocket application to a database using this library occurs
|
|
||||||
//! in three simple steps:
|
|
||||||
//!
|
|
||||||
//! 1. Configure your databases in `Rocket.toml`.
|
|
||||||
//! (see [Configuration](#configuration))
|
|
||||||
//! 2. Associate a request guard type and fairing with each database.
|
|
||||||
//! (see [Guard Types](#guard-types))
|
|
||||||
//! 3. Use the request guard to retrieve a connection in a handler.
|
|
||||||
//! (see [Handlers](#handlers))
|
|
||||||
//!
|
|
||||||
//! For a list of supported databases, see [Provided Databases](#provided). This
|
|
||||||
//! support can be easily extended by implementing the [`Poolable`] trait. See
|
|
||||||
//! [Extending](#extending) for more.
|
|
||||||
//!
|
|
||||||
//! ## Example
|
|
||||||
//!
|
|
||||||
//! Before using this library, the feature corresponding to your database type
|
|
||||||
//! in `rocket_contrib` must be enabled:
|
|
||||||
//!
|
|
||||||
//! ```toml
|
|
||||||
//! [dependencies.rocket_contrib]
|
|
||||||
//! version = "0.5.0-dev"
|
|
||||||
//! default-features = false
|
|
||||||
//! features = ["diesel_sqlite_pool"]
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! See [Provided](#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:
|
|
||||||
//!
|
|
||||||
//! ```toml
|
|
||||||
//! [global.databases]
|
|
||||||
//! sqlite_logs = { url = "/path/to/database.sqlite" }
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! In your application's source code, one-time:
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! #[macro_use] extern crate rocket;
|
|
||||||
//! #[macro_use] extern crate rocket_contrib;
|
|
||||||
//!
|
|
||||||
//! # #[cfg(feature = "diesel_sqlite_pool")]
|
|
||||||
//! # mod test {
|
|
||||||
//! use rocket_contrib::databases::diesel;
|
|
||||||
//!
|
|
||||||
//! #[database("sqlite_logs")]
|
|
||||||
//! struct LogsDbConn(diesel::SqliteConnection);
|
|
||||||
//!
|
|
||||||
//! #[launch]
|
|
||||||
//! fn rocket() -> rocket::Rocket {
|
|
||||||
//! rocket::ignite().attach(LogsDbConn::fairing())
|
|
||||||
//! }
|
|
||||||
//! # } fn main() {}
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! Whenever a connection to the database is needed:
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! # #[macro_use] extern crate rocket;
|
|
||||||
//! # #[macro_use] extern crate rocket_contrib;
|
|
||||||
//! #
|
|
||||||
//! # #[cfg(feature = "diesel_sqlite_pool")]
|
|
||||||
//! # mod test {
|
|
||||||
//! # use rocket_contrib::databases::diesel;
|
|
||||||
//! #
|
|
||||||
//! # #[database("sqlite_logs")]
|
|
||||||
//! # struct LogsDbConn(diesel::SqliteConnection);
|
|
||||||
//! #
|
|
||||||
//! # type Logs = ();
|
|
||||||
//! # type Result<T> = std::result::Result<T, ()>;
|
|
||||||
//! #
|
|
||||||
//! #[get("/logs/<id>")]
|
|
||||||
//! async fn get_logs(conn: LogsDbConn, id: usize) -> Result<Logs> {
|
|
||||||
//! # /*
|
|
||||||
//! conn.run(|c| Logs::by_id(c, id)).await
|
|
||||||
//! # */
|
|
||||||
//! # Ok(())
|
|
||||||
//! }
|
|
||||||
//! # } fn main() {}
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! # 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, a `pool_size` key. This looks as
|
|
||||||
//! follows:
|
|
||||||
//!
|
|
||||||
//! ```toml
|
|
||||||
//! # Option 1:
|
|
||||||
//! [global.databases]
|
|
||||||
//! sqlite_db = { url = "db.sqlite" }
|
|
||||||
//!
|
|
||||||
//! # Option 2:
|
|
||||||
//! [global.databases.my_db]
|
|
||||||
//! url = "mysql://root:root@localhost/my_db"
|
|
||||||
//!
|
|
||||||
//! # With a `pool_size` key:
|
|
||||||
//! [global.databases]
|
|
||||||
//! sqlite_db = { url = "db.sqlite", pool_size = 20 }
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! 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 * 2)
|
|
||||||
//!
|
|
||||||
//! 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:
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! # #[cfg(feature = "diesel_sqlite_pool")] {
|
|
||||||
//! use rocket::figment::{value::{Map, Value}, util::map};
|
|
||||||
//!
|
|
||||||
//! #[rocket::launch]
|
|
||||||
//! fn rocket() -> _ {
|
|
||||||
//! let db: Map<_, Value> = map! {
|
|
||||||
//! "url" => "db.sqlite".into(),
|
|
||||||
//! "pool_size" => 10.into()
|
|
||||||
//! };
|
|
||||||
//!
|
|
||||||
//! let figment = rocket::Config::figment()
|
|
||||||
//! .merge(("databases", map!["my_db" => db]));
|
|
||||||
//!
|
|
||||||
//! rocket::custom(figment)
|
|
||||||
//! }
|
|
||||||
//! # rocket();
|
|
||||||
//! # }
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! ### Environment Variables
|
|
||||||
//!
|
|
||||||
//! Lastly, databases can be configured via environment variables by specifying
|
|
||||||
//! the `databases` table as detailed in the [Environment Variables
|
|
||||||
//! configuration
|
|
||||||
//! guide](https://rocket.rs/master/guide/configuration/#environment-variables):
|
|
||||||
//!
|
|
||||||
//! ```bash
|
|
||||||
//! ROCKET_DATABASES='{my_db={url="db.sqlite"}}'
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! Multiple databases can be specified in the `ROCKET_DATABASES` environment variable
|
|
||||||
//! as well by comma separating them:
|
|
||||||
//!
|
|
||||||
//! ```bash
|
|
||||||
//! 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
|
|
||||||
//! attributes 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.
|
|
||||||
//!
|
|
||||||
//! The macro generates a [`FromRequest`] implementation for the decorated type,
|
|
||||||
//! allowing the type to be used as a request guard. This implementation
|
|
||||||
//! retrieves a connection from the database pool or fails with a
|
|
||||||
//! `Status::ServiceUnavailable` if connecting to the database times out.
|
|
||||||
//!
|
|
||||||
//! The macro will also generate two inherent methods on the decorated type:
|
|
||||||
//!
|
|
||||||
//! * `fn fairing() -> impl Fairing`
|
|
||||||
//!
|
|
||||||
//! Returns a fairing that initializes the associated database connection
|
|
||||||
//! pool.
|
|
||||||
//!
|
|
||||||
//! * `async fn get_one(&Rocket) -> Option<Self>`
|
|
||||||
//!
|
|
||||||
//! Retrieves a connection wrapper from the configured pool. Returns `Some`
|
|
||||||
//! as long as `Self::fairing()` has been attached.
|
|
||||||
//!
|
|
||||||
//! The attribute can only be applied to unit-like structs with one type. The
|
|
||||||
//! internal type of the structure must implement [`Poolable`].
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! # extern crate rocket;
|
|
||||||
//! # #[macro_use] extern crate rocket_contrib;
|
|
||||||
//! # #[cfg(feature = "diesel_sqlite_pool")]
|
|
||||||
//! # mod test {
|
|
||||||
//! use rocket_contrib::databases::diesel;
|
|
||||||
//!
|
|
||||||
//! #[database("my_db")]
|
|
||||||
//! struct MyDatabase(diesel::SqliteConnection);
|
|
||||||
//! # }
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! Other databases can be used by specifying their respective [`Poolable`]
|
|
||||||
//! type:
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! # extern crate rocket;
|
|
||||||
//! # #[macro_use] extern crate rocket_contrib;
|
|
||||||
//! # #[cfg(feature = "postgres_pool")]
|
|
||||||
//! # mod test {
|
|
||||||
//! use rocket_contrib::databases::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:
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! # #[macro_use] extern crate rocket;
|
|
||||||
//! # #[macro_use] extern crate rocket_contrib;
|
|
||||||
//! #
|
|
||||||
//! # #[cfg(feature = "diesel_sqlite_pool")] {
|
|
||||||
//! # use rocket::figment::{value::{Map, Value}, util::map};
|
|
||||||
//! use rocket_contrib::databases::diesel;
|
|
||||||
//!
|
|
||||||
//! #[database("my_db")]
|
|
||||||
//! struct MyDatabase(diesel::SqliteConnection);
|
|
||||||
//!
|
|
||||||
//! #[launch]
|
|
||||||
//! fn rocket() -> _ {
|
|
||||||
//! # let db: Map<_, Value> = map![
|
|
||||||
//! # "url" => "db.sqlite".into(), "pool_size" => 10.into()
|
|
||||||
//! # ];
|
|
||||||
//! # let figment = rocket::Config::figment().merge(("databases", map!["my_db" => db]));
|
|
||||||
//! 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:
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! # #[macro_use] extern crate rocket;
|
|
||||||
//! # #[macro_use] extern crate rocket_contrib;
|
|
||||||
//! #
|
|
||||||
//! # #[cfg(feature = "diesel_sqlite_pool")]
|
|
||||||
//! # mod test {
|
|
||||||
//! # use rocket_contrib::databases::diesel;
|
|
||||||
//! #[database("my_db")]
|
|
||||||
//! struct MyDatabase(diesel::SqliteConnection);
|
|
||||||
//!
|
|
||||||
//! #[get("/")]
|
|
||||||
//! fn my_handler(conn: MyDatabase) {
|
|
||||||
//! // ...
|
|
||||||
//! }
|
|
||||||
//! # }
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! A connection can be retrieved and used with the `run()` method:
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! # #[macro_use] extern crate rocket;
|
|
||||||
//! # #[macro_use] extern crate rocket_contrib;
|
|
||||||
//! #
|
|
||||||
//! # #[cfg(feature = "diesel_sqlite_pool")]
|
|
||||||
//! # mod test {
|
|
||||||
//! # use rocket_contrib::databases::diesel;
|
|
||||||
//! # type Data = ();
|
|
||||||
//! #[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.
|
|
||||||
//!
|
|
||||||
// Note: Keep this table in sync with site/guite/6-state.md
|
|
||||||
//! | Kind | Driver | Version | `Poolable` Type | Feature |
|
|
||||||
//! |----------|-----------------------|-----------|--------------------------------|------------------------|
|
|
||||||
//! | MySQL | [Diesel] | `1` | [`diesel::MysqlConnection`] | `diesel_mysql_pool` |
|
|
||||||
//! | MySQL | [`rust-mysql-simple`] | `18` | [`mysql::Conn`] | `mysql_pool` |
|
|
||||||
//! | Postgres | [Diesel] | `1` | [`diesel::PgConnection`] | `diesel_postgres_pool` |
|
|
||||||
//! | Postgres | [Rust-Postgres] | `0.19` | [`postgres::Client`] | `postgres_pool` |
|
|
||||||
//! | Sqlite | [Diesel] | `1` | [`diesel::SqliteConnection`] | `diesel_sqlite_pool` |
|
|
||||||
//! | Sqlite | [`Rusqlite`] | `0.23` | [`rusqlite::Connection`] | `sqlite_pool` |
|
|
||||||
//! | Memcache | [`memcache`] | `0.15` | [`memcache::Client`] | `memcache_pool` |
|
|
||||||
//!
|
|
||||||
//! [Diesel]: https://diesel.rs
|
|
||||||
//! [`rusqlite::Connection`]: https://docs.rs/rusqlite/0.23.0/rusqlite/struct.Connection.html
|
|
||||||
//! [`diesel::SqliteConnection`]: http://docs.diesel.rs/diesel/prelude/struct.SqliteConnection.html
|
|
||||||
//! [`postgres::Client`]: https://docs.rs/postgres/0.19/postgres/struct.Client.html
|
|
||||||
//! [`diesel::PgConnection`]: http://docs.diesel.rs/diesel/pg/struct.PgConnection.html
|
|
||||||
//! [`mysql::Conn`]: https://docs.rs/mysql/18/mysql/struct.Conn.html
|
|
||||||
//! [`diesel::MysqlConnection`]: http://docs.diesel.rs/diesel/mysql/struct.MysqlConnection.html
|
|
||||||
//! [`Rusqlite`]: https://github.com/jgallagher/rusqlite
|
|
||||||
//! [Rust-Postgres]: https://github.com/sfackler/rust-postgres
|
|
||||||
//! [`rust-mysql-simple`]: https://github.com/blackbeam/rust-mysql-simple
|
|
||||||
//! [`diesel::PgConnection`]: http://docs.diesel.rs/diesel/pg/struct.PgConnection.html
|
|
||||||
//! [`memcache`]: https://github.com/aisk/rust-memcache
|
|
||||||
//! [`memcache::Client`]: https://docs.rs/memcache/0.15/memcache/struct.Client.html
|
|
||||||
//!
|
|
||||||
//! 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.
|
|
||||||
//!
|
|
||||||
//! [`FromRequest`]: rocket::request::FromRequest
|
|
||||||
//! [request guards]: rocket::request::FromRequest
|
|
||||||
//! [`Poolable`]: crate::databases::Poolable
|
|
||||||
|
|
||||||
pub extern crate r2d2;
|
|
||||||
|
|
||||||
#[cfg(any(
|
|
||||||
feature = "diesel_sqlite_pool",
|
|
||||||
feature = "diesel_postgres_pool",
|
|
||||||
feature = "diesel_mysql_pool"
|
|
||||||
))]
|
|
||||||
pub extern crate diesel;
|
|
||||||
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use rocket::fairing::{AdHoc, Fairing};
|
|
||||||
use rocket::request::{Request, Outcome, FromRequest};
|
|
||||||
use rocket::outcome::IntoOutcome;
|
|
||||||
use rocket::http::Status;
|
|
||||||
|
|
||||||
use rocket::tokio::sync::{OwnedSemaphorePermit, Semaphore, Mutex};
|
|
||||||
use rocket::tokio::time::timeout;
|
|
||||||
|
|
||||||
use self::r2d2::ManageConnection;
|
|
||||||
|
|
||||||
#[doc(hidden)] pub use rocket_contrib_codegen::*;
|
|
||||||
|
|
||||||
#[cfg(feature = "postgres_pool")] pub extern crate postgres;
|
|
||||||
#[cfg(feature = "postgres_pool")] pub extern crate r2d2_postgres;
|
|
||||||
|
|
||||||
#[cfg(feature = "mysql_pool")] pub extern crate mysql;
|
|
||||||
#[cfg(feature = "mysql_pool")] pub extern crate r2d2_mysql;
|
|
||||||
|
|
||||||
#[cfg(feature = "sqlite_pool")] pub extern crate rusqlite;
|
|
||||||
#[cfg(feature = "sqlite_pool")] pub extern crate r2d2_sqlite;
|
|
||||||
|
|
||||||
#[cfg(feature = "memcache_pool")] pub extern crate memcache;
|
|
||||||
#[cfg(feature = "memcache_pool")] pub extern crate r2d2_memcache;
|
|
||||||
|
|
||||||
/// A base `Config` for any `Poolable` type.
|
|
||||||
///
|
|
||||||
/// For the following configuration:
|
|
||||||
///
|
|
||||||
/// ```toml
|
|
||||||
/// [global.databases.my_database]
|
|
||||||
/// url = "postgres://root:root@localhost/my_database"
|
|
||||||
/// pool_size = 10
|
|
||||||
/// timeout = 5
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ...`Config::from("my_database", rocket)` would return the following struct:
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use rocket_contrib::databases::Config;
|
|
||||||
/// Config {
|
|
||||||
/// url: "postgres://root:root@localhost/my_database".into(),
|
|
||||||
/// pool_size: 10,
|
|
||||||
/// timeout: 5
|
|
||||||
/// };
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// If you want to implement your own custom database adapter (or other
|
|
||||||
/// database-like struct that can be pooled by `r2d2`) and need some more
|
|
||||||
/// configurations options, you may need to define a custom `Config` struct.
|
|
||||||
/// Note, however, that the configuration values in `Config` are required.
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
|
||||||
pub struct Config {
|
|
||||||
/// Connection URL specified in the Rocket configuration.
|
|
||||||
pub url: String,
|
|
||||||
/// Initial pool size. Defaults to the number of Rocket workers * 2.
|
|
||||||
pub pool_size: u32,
|
|
||||||
/// How long to wait, in seconds, for a new connection before timing out.
|
|
||||||
/// Defaults to `5`.
|
|
||||||
// FIXME: Use `time`.
|
|
||||||
pub timeout: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
use rocket::figment::{self, Figment, providers::Serialized};
|
|
||||||
|
|
||||||
impl Config {
|
|
||||||
/// Retrieves the database configuration for the database named `name`.
|
|
||||||
///
|
|
||||||
/// This function is primarily used by the generated code from the
|
|
||||||
/// `#[database]` attribute.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # #[cfg(feature = "diesel_sqlite_pool")] {
|
|
||||||
/// # use rocket::figment::{Figment, providers::{Format, Toml}};
|
|
||||||
/// // Assume that these are the contents of `Rocket.toml`:
|
|
||||||
/// # let toml = Toml::string(r#"
|
|
||||||
/// [global.databases]
|
|
||||||
/// my_db = { url = "db/db.sqlite", pool_size = 25 }
|
|
||||||
/// my_other_db = { url = "mysql://root:root@localhost/database" }
|
|
||||||
/// # "#).nested();
|
|
||||||
///
|
|
||||||
/// use rocket_contrib::databases::Config;
|
|
||||||
///
|
|
||||||
/// fn pool(rocket: &rocket::Rocket) {
|
|
||||||
/// let config = Config::from("my_db", rocket).unwrap();
|
|
||||||
/// assert_eq!(config.url, "db/db.sqlite");
|
|
||||||
/// assert_eq!(config.pool_size, 25);
|
|
||||||
///
|
|
||||||
/// let config = Config::from("my_other_db", rocket).unwrap();
|
|
||||||
/// assert_eq!(config.url, "mysql://root:root@localhost/database");
|
|
||||||
/// assert_eq!(config.pool_size, (rocket.config().workers * 2) as u32);
|
|
||||||
///
|
|
||||||
/// let config = Config::from("unknown_db", rocket);
|
|
||||||
/// assert!(config.is_err())
|
|
||||||
/// }
|
|
||||||
/// #
|
|
||||||
/// # let config = Figment::from(rocket::Config::default()).merge(toml);
|
|
||||||
/// # let rocket = rocket::custom(config);
|
|
||||||
/// # pool(&rocket);
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
pub fn from(db_name: &str, rocket: &rocket::Rocket) -> Result<Config, figment::Error> {
|
|
||||||
let db_key = format!("databases.{}", db_name);
|
|
||||||
let key = |name: &str| format!("{}.{}", db_key, name);
|
|
||||||
Figment::from(rocket.figment())
|
|
||||||
.merge(Serialized::default(&key("pool_size"), rocket.config().workers * 2))
|
|
||||||
.merge(Serialized::default(&key("timeout"), 5))
|
|
||||||
.extract_inner::<Self>(&db_key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A wrapper around `r2d2::Error`s or a custom database error type.
|
|
||||||
///
|
|
||||||
/// This type is only relevant to implementors of the [`Poolable`] trait. See
|
|
||||||
/// the [`Poolable`] documentation for more information on how to use this type.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Error<T> {
|
|
||||||
/// A custom error of type `T`.
|
|
||||||
Custom(T),
|
|
||||||
/// An error occurred while initializing an `r2d2` pool.
|
|
||||||
Pool(r2d2::Error),
|
|
||||||
/// An error occurred while extracting a `figment` configuration.
|
|
||||||
Config(figment::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> From<figment::Error> for Error<T> {
|
|
||||||
fn from(error: figment::Error) -> Self {
|
|
||||||
Error::Config(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> From<r2d2::Error> for Error<T> {
|
|
||||||
fn from(error: r2d2::Error) -> Self {
|
|
||||||
Error::Pool(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Trait implemented by `r2d2`-based database adapters.
|
|
||||||
///
|
|
||||||
/// # Provided Implementations
|
|
||||||
///
|
|
||||||
/// Implementations of `Poolable` are provided for the following types:
|
|
||||||
///
|
|
||||||
/// * `diesel::MysqlConnection`
|
|
||||||
/// * `diesel::PgConnection`
|
|
||||||
/// * `diesel::SqliteConnection`
|
|
||||||
/// * `postgres::Connection`
|
|
||||||
/// * `mysql::Conn`
|
|
||||||
/// * `rusqlite::Connection`
|
|
||||||
///
|
|
||||||
/// # Implementation Guide
|
|
||||||
///
|
|
||||||
/// As an r2d2-compatible database (or other resource) adapter provider,
|
|
||||||
/// implementing `Poolable` in your own library will enable Rocket users to
|
|
||||||
/// consume your adapter with its built-in connection pooling support.
|
|
||||||
///
|
|
||||||
/// ## Example
|
|
||||||
///
|
|
||||||
/// Consider a library `foo` with the following types:
|
|
||||||
///
|
|
||||||
/// * `foo::ConnectionManager`, which implements [`r2d2::ManageConnection`]
|
|
||||||
/// * `foo::Connection`, the `Connection` associated type of
|
|
||||||
/// `foo::ConnectionManager`
|
|
||||||
/// * `foo::Error`, errors resulting from manager instantiation
|
|
||||||
///
|
|
||||||
/// In order for Rocket to generate the required code to automatically provision
|
|
||||||
/// a r2d2 connection pool into application state, the `Poolable` trait needs to
|
|
||||||
/// be implemented for the connection type. The following example implements
|
|
||||||
/// `Poolable` for `foo::Connection`:
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # mod foo {
|
|
||||||
/// # use std::fmt;
|
|
||||||
/// # use rocket_contrib::databases::r2d2;
|
|
||||||
/// # #[derive(Debug)] pub struct Error;
|
|
||||||
/// # impl std::error::Error for Error { }
|
|
||||||
/// # impl fmt::Display for Error {
|
|
||||||
/// # fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { Ok(()) }
|
|
||||||
/// # }
|
|
||||||
/// #
|
|
||||||
/// # pub struct Connection;
|
|
||||||
/// # pub struct ConnectionManager;
|
|
||||||
/// #
|
|
||||||
/// # type Result<T> = std::result::Result<T, Error>;
|
|
||||||
/// #
|
|
||||||
/// # impl ConnectionManager {
|
|
||||||
/// # pub fn new(url: &str) -> Result<Self> { Err(Error) }
|
|
||||||
/// # }
|
|
||||||
/// #
|
|
||||||
/// # impl self::r2d2::ManageConnection for ConnectionManager {
|
|
||||||
/// # type Connection = Connection;
|
|
||||||
/// # type Error = Error;
|
|
||||||
/// # fn connect(&self) -> Result<Connection> { panic!(()) }
|
|
||||||
/// # fn is_valid(&self, _: &mut Connection) -> Result<()> { panic!() }
|
|
||||||
/// # fn has_broken(&self, _: &mut Connection) -> bool { panic!() }
|
|
||||||
/// # }
|
|
||||||
/// # }
|
|
||||||
/// use rocket_contrib::databases::{r2d2, Error, Config, Poolable, PoolResult};
|
|
||||||
///
|
|
||||||
/// impl Poolable for foo::Connection {
|
|
||||||
/// type Manager = foo::ConnectionManager;
|
|
||||||
/// type Error = foo::Error;
|
|
||||||
///
|
|
||||||
/// fn pool(db_name: &str, rocket: &rocket::Rocket) -> PoolResult<Self> {
|
|
||||||
/// let config = Config::from(db_name, rocket)?;
|
|
||||||
/// let manager = foo::ConnectionManager::new(&config.url).map_err(Error::Custom)?;
|
|
||||||
/// Ok(r2d2::Pool::builder().max_size(config.pool_size).build(manager)?)
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// In this example, `ConnectionManager::new()` method returns a `foo::Error` on
|
|
||||||
/// failure. The [`Error`] enum consolidates this type, the `r2d2::Error` type
|
|
||||||
/// that can result from `r2d2::Pool::builder()`, and the
|
|
||||||
/// [`figment::Error`](rocket::figment::Error) type from
|
|
||||||
/// `database::Config::from()`.
|
|
||||||
///
|
|
||||||
/// In the event that a connection manager isn't fallible (as is the case with
|
|
||||||
/// Diesel's r2d2 connection manager, for instance), the associated error type
|
|
||||||
/// for the `Poolable` implementation should be `std::convert::Infallible`.
|
|
||||||
///
|
|
||||||
/// For more concrete example, consult Rocket's existing implementations of
|
|
||||||
/// [`Poolable`].
|
|
||||||
pub trait Poolable: Send + Sized + 'static {
|
|
||||||
/// The associated connection manager for the given connection type.
|
|
||||||
type Manager: ManageConnection<Connection=Self>;
|
|
||||||
|
|
||||||
/// The associated error type in the event that constructing the connection
|
|
||||||
/// manager and/or the connection pool fails.
|
|
||||||
type Error: std::fmt::Debug;
|
|
||||||
|
|
||||||
/// Creates an `r2d2` connection pool for `Manager::Connection`, returning
|
|
||||||
/// the pool on success.
|
|
||||||
fn pool(db_name: &str, rocket: &rocket::Rocket) -> PoolResult<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A type alias for the return type of [`Poolable::pool()`].
|
|
||||||
#[allow(type_alias_bounds)]
|
|
||||||
pub type PoolResult<P: Poolable> = Result<r2d2::Pool<P::Manager>, Error<P::Error>>;
|
|
||||||
|
|
||||||
#[cfg(feature = "diesel_sqlite_pool")]
|
|
||||||
impl Poolable for diesel::SqliteConnection {
|
|
||||||
type Manager = diesel::r2d2::ConnectionManager<diesel::SqliteConnection>;
|
|
||||||
type Error = std::convert::Infallible;
|
|
||||||
|
|
||||||
fn pool(db_name: &str, rocket: &rocket::Rocket) -> PoolResult<Self> {
|
|
||||||
let config = Config::from(db_name, rocket)?;
|
|
||||||
let manager = diesel::r2d2::ConnectionManager::new(&config.url);
|
|
||||||
Ok(r2d2::Pool::builder().max_size(config.pool_size).build(manager)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "diesel_postgres_pool")]
|
|
||||||
impl Poolable for diesel::PgConnection {
|
|
||||||
type Manager = diesel::r2d2::ConnectionManager<diesel::PgConnection>;
|
|
||||||
type Error = std::convert::Infallible;
|
|
||||||
|
|
||||||
fn pool(db_name: &str, rocket: &rocket::Rocket) -> PoolResult<Self> {
|
|
||||||
let config = Config::from(db_name, rocket)?;
|
|
||||||
let manager = diesel::r2d2::ConnectionManager::new(&config.url);
|
|
||||||
Ok(r2d2::Pool::builder().max_size(config.pool_size).build(manager)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "diesel_mysql_pool")]
|
|
||||||
impl Poolable for diesel::MysqlConnection {
|
|
||||||
type Manager = diesel::r2d2::ConnectionManager<diesel::MysqlConnection>;
|
|
||||||
type Error = std::convert::Infallible;
|
|
||||||
|
|
||||||
fn pool(db_name: &str, rocket: &rocket::Rocket) -> PoolResult<Self> {
|
|
||||||
let config = Config::from(db_name, rocket)?;
|
|
||||||
let manager = diesel::r2d2::ConnectionManager::new(&config.url);
|
|
||||||
Ok(r2d2::Pool::builder().max_size(config.pool_size).build(manager)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Add a feature to enable TLS in `postgres`; parse a suitable `config`.
|
|
||||||
#[cfg(feature = "postgres_pool")]
|
|
||||||
impl Poolable for postgres::Client {
|
|
||||||
type Manager = r2d2_postgres::PostgresConnectionManager<postgres::tls::NoTls>;
|
|
||||||
type Error = postgres::Error;
|
|
||||||
|
|
||||||
fn pool(db_name: &str, rocket: &rocket::Rocket) -> PoolResult<Self> {
|
|
||||||
let config = Config::from(db_name, rocket)?;
|
|
||||||
let url = config.url.parse().map_err(Error::Custom)?;
|
|
||||||
let manager = r2d2_postgres::PostgresConnectionManager::new(url, postgres::tls::NoTls);
|
|
||||||
Ok(r2d2::Pool::builder().max_size(config.pool_size).build(manager)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "mysql_pool")]
|
|
||||||
impl Poolable for mysql::Conn {
|
|
||||||
type Manager = r2d2_mysql::MysqlConnectionManager;
|
|
||||||
type Error = std::convert::Infallible;
|
|
||||||
|
|
||||||
fn pool(db_name: &str, rocket: &rocket::Rocket) -> PoolResult<Self> {
|
|
||||||
let config = Config::from(db_name, rocket)?;
|
|
||||||
let opts = mysql::OptsBuilder::from_opts(&config.url);
|
|
||||||
let manager = r2d2_mysql::MysqlConnectionManager::new(opts);
|
|
||||||
Ok(r2d2::Pool::builder().max_size(config.pool_size).build(manager)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "sqlite_pool")]
|
|
||||||
impl Poolable for rusqlite::Connection {
|
|
||||||
type Manager = r2d2_sqlite::SqliteConnectionManager;
|
|
||||||
type Error = std::convert::Infallible;
|
|
||||||
|
|
||||||
fn pool(db_name: &str, rocket: &rocket::Rocket) -> PoolResult<Self> {
|
|
||||||
let config = Config::from(db_name, rocket)?;
|
|
||||||
let manager = r2d2_sqlite::SqliteConnectionManager::file(&*config.url);
|
|
||||||
Ok(r2d2::Pool::builder().max_size(config.pool_size).build(manager)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "memcache_pool")]
|
|
||||||
impl Poolable for memcache::Client {
|
|
||||||
type Manager = r2d2_memcache::MemcacheConnectionManager;
|
|
||||||
// Unused, but we might want it in the future without a breaking change.
|
|
||||||
type Error = memcache::MemcacheError;
|
|
||||||
|
|
||||||
fn pool(db_name: &str, rocket: &rocket::Rocket) -> PoolResult<Self> {
|
|
||||||
let config = Config::from(db_name, rocket)?;
|
|
||||||
let manager = r2d2_memcache::MemcacheConnectionManager::new(&*config.url);
|
|
||||||
Ok(r2d2::Pool::builder().max_size(config.pool_size).build(manager)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Unstable internal details of generated code for the #[database] attribute.
|
|
||||||
///
|
|
||||||
/// This type is implemented here instead of in generated code to ensure all
|
|
||||||
/// types are properly checked.
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub struct ConnectionPool<K, C: Poolable> {
|
|
||||||
config: Config,
|
|
||||||
// This is an 'Option' so that we can drop the pool in a 'spawn_blocking'.
|
|
||||||
pool: Option<r2d2::Pool<C::Manager>>,
|
|
||||||
semaphore: Arc<Semaphore>,
|
|
||||||
_marker: PhantomData<fn() -> K>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K, C: Poolable> Clone for ConnectionPool<K, C> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
ConnectionPool {
|
|
||||||
config: self.config.clone(),
|
|
||||||
pool: self.pool.clone(),
|
|
||||||
semaphore: self.semaphore.clone(),
|
|
||||||
_marker: PhantomData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Unstable internal details of generated code for the #[database] attribute.
|
|
||||||
///
|
|
||||||
/// This type is implemented here instead of in generated code to ensure all
|
|
||||||
/// types are properly checked.
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub struct Connection<K, C: Poolable> {
|
|
||||||
connection: Arc<Mutex<Option<r2d2::PooledConnection<C::Manager>>>>,
|
|
||||||
permit: Option<OwnedSemaphorePermit>,
|
|
||||||
_marker: PhantomData<fn() -> K>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// A wrapper around spawn_blocking that propagates panics to the calling code.
|
|
||||||
async fn run_blocking<F, R>(job: F) -> R
|
|
||||||
where F: FnOnce() -> R + Send + 'static, R: Send + 'static,
|
|
||||||
{
|
|
||||||
match tokio::task::spawn_blocking(job).await {
|
|
||||||
Ok(ret) => ret,
|
|
||||||
Err(e) => match e.try_into_panic() {
|
|
||||||
Ok(panic) => std::panic::resume_unwind(panic),
|
|
||||||
Err(_) => unreachable!("spawn_blocking tasks are never cancelled"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! dberr {
|
|
||||||
($msg:literal, $db_name:expr, $efmt:literal, $error:expr, $rocket:expr) => ({
|
|
||||||
rocket::error!(concat!("database ", $msg, " error for pool named `{}`"), $db_name);
|
|
||||||
error_!($efmt, $error);
|
|
||||||
return Err($rocket);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: 'static, C: Poolable> ConnectionPool<K, C> {
|
|
||||||
pub fn fairing(fairing_name: &'static str, db: &'static str) -> impl Fairing {
|
|
||||||
AdHoc::on_attach(fairing_name, move |rocket| async move {
|
|
||||||
let config = match Config::from(db, &rocket) {
|
|
||||||
Ok(config) => config,
|
|
||||||
Err(e) => dberr!("config", db, "{}", e, rocket),
|
|
||||||
};
|
|
||||||
|
|
||||||
let pool_size = config.pool_size;
|
|
||||||
match C::pool(db, &rocket) {
|
|
||||||
Ok(pool) => Ok(rocket.manage(ConnectionPool::<K, C> {
|
|
||||||
config,
|
|
||||||
pool: Some(pool),
|
|
||||||
semaphore: Arc::new(Semaphore::new(pool_size as usize)),
|
|
||||||
_marker: PhantomData,
|
|
||||||
})),
|
|
||||||
Err(Error::Config(e)) => dberr!("config", db, "{}", e, rocket),
|
|
||||||
Err(Error::Pool(e)) => dberr!("pool init", db, "{}", e, rocket),
|
|
||||||
Err(Error::Custom(e)) => dberr!("pool manager", db, "{:?}", e, rocket),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get(&self) -> Result<Connection<K, C>, ()> {
|
|
||||||
let duration = std::time::Duration::from_secs(self.config.timeout as u64);
|
|
||||||
let permit = match timeout(duration, self.semaphore.clone().acquire_owned()).await {
|
|
||||||
Ok(p) => p.expect("internal invariant broken: semaphore should not be closed"),
|
|
||||||
Err(_) => {
|
|
||||||
error_!("database connection retrieval timed out");
|
|
||||||
return Err(());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let pool = self.pool.as_ref().cloned()
|
|
||||||
.expect("internal invariant broken: self.pool is Some");
|
|
||||||
|
|
||||||
match run_blocking(move || pool.get_timeout(duration)).await {
|
|
||||||
Ok(c) => Ok(Connection {
|
|
||||||
connection: Arc::new(Mutex::new(Some(c))),
|
|
||||||
permit: Some(permit),
|
|
||||||
_marker: PhantomData,
|
|
||||||
}),
|
|
||||||
Err(e) => {
|
|
||||||
error_!("failed to get a database connection: {}", e);
|
|
||||||
Err(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub async fn get_one(rocket: &rocket::Rocket) -> Option<Connection<K, C>> {
|
|
||||||
match rocket.state::<Self>() {
|
|
||||||
Some(pool) => pool.get().await.ok(),
|
|
||||||
None => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub async fn get_pool(rocket: &rocket::Rocket) -> Option<Self> {
|
|
||||||
rocket.state::<Self>().map(|pool| pool.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: 'static, C: Poolable> Connection<K, C> {
|
|
||||||
#[inline]
|
|
||||||
pub async fn run<F, R>(&self, f: F) -> R
|
|
||||||
where F: FnOnce(&mut C) -> R + Send + 'static,
|
|
||||||
R: Send + 'static,
|
|
||||||
{
|
|
||||||
let mut connection = self.connection.clone().lock_owned().await;
|
|
||||||
run_blocking(move || {
|
|
||||||
let conn = connection.as_mut()
|
|
||||||
.expect("internal invariant broken: self.connection is Some");
|
|
||||||
f(conn)
|
|
||||||
}).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K, C: Poolable> Drop for Connection<K, C> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
let connection = self.connection.clone();
|
|
||||||
let permit = self.permit.take();
|
|
||||||
tokio::spawn(async move {
|
|
||||||
let mut connection = connection.lock_owned().await;
|
|
||||||
tokio::task::spawn_blocking(move || {
|
|
||||||
if let Some(conn) = connection.take() {
|
|
||||||
drop(conn);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Explicitly dropping the permit here so that it's only
|
|
||||||
// released after the connection is.
|
|
||||||
drop(permit);
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K, C: Poolable> Drop for ConnectionPool<K, C> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
let pool = self.pool.take();
|
|
||||||
tokio::task::spawn_blocking(move || drop(pool));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rocket::async_trait]
|
|
||||||
impl<'a, 'r, K: 'static, C: Poolable> FromRequest<'a, 'r> for Connection<K, C> {
|
|
||||||
type Error = ();
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
async fn from_request(request: &'a Request<'r>) -> Outcome<Self, ()> {
|
|
||||||
match request.managed_state::<ConnectionPool<K, C>>() {
|
|
||||||
Some(c) => c.get().await.into_outcome(Status::ServiceUnavailable),
|
|
||||||
None => {
|
|
||||||
error_!("Missing database fairing for `{}`", std::any::type_name::<K>());
|
|
||||||
Outcome::Failure((Status::InternalServerError, ()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
/// A base `Config` for any `Poolable` type.
|
||||||
|
///
|
||||||
|
/// For the following configuration:
|
||||||
|
///
|
||||||
|
/// ```toml
|
||||||
|
/// [global.databases.my_database]
|
||||||
|
/// url = "postgres://root:root@localhost/my_database"
|
||||||
|
/// pool_size = 10
|
||||||
|
/// timeout = 5
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ...`Config::from("my_database", rocket)` would return the following struct:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use rocket_contrib::databases::Config;
|
||||||
|
/// Config {
|
||||||
|
/// url: "postgres://root:root@localhost/my_database".into(),
|
||||||
|
/// pool_size: 10,
|
||||||
|
/// timeout: 5
|
||||||
|
/// };
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// If you want to implement your own custom database adapter (or other
|
||||||
|
/// database-like struct that can be pooled by `r2d2`) and need some more
|
||||||
|
/// configurations options, you may need to define a custom `Config` struct.
|
||||||
|
/// Note, however, that the configuration values in `Config` are required.
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||||
|
pub struct Config {
|
||||||
|
/// Connection URL specified in the Rocket configuration.
|
||||||
|
pub url: String,
|
||||||
|
/// Initial pool size. Defaults to the number of Rocket workers * 2.
|
||||||
|
pub pool_size: u32,
|
||||||
|
/// How long to wait, in seconds, for a new connection before timing out.
|
||||||
|
/// Defaults to `5`.
|
||||||
|
// FIXME: Use `time`.
|
||||||
|
pub timeout: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use rocket::figment::{self, Figment, providers::Serialized};
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
/// Retrieves the database configuration for the database named `name`.
|
||||||
|
///
|
||||||
|
/// This function is primarily used by the generated code from the
|
||||||
|
/// `#[database]` attribute.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # #[cfg(feature = "diesel_sqlite_pool")] {
|
||||||
|
/// # use rocket::figment::{Figment, providers::{Format, Toml}};
|
||||||
|
/// // Assume that these are the contents of `Rocket.toml`:
|
||||||
|
/// # let toml = Toml::string(r#"
|
||||||
|
/// [global.databases]
|
||||||
|
/// my_db = { url = "db/db.sqlite", pool_size = 25 }
|
||||||
|
/// my_other_db = { url = "mysql://root:root@localhost/database" }
|
||||||
|
/// # "#).nested();
|
||||||
|
///
|
||||||
|
/// use rocket_contrib::databases::Config;
|
||||||
|
///
|
||||||
|
/// fn pool(rocket: &rocket::Rocket) {
|
||||||
|
/// let config = Config::from("my_db", rocket).unwrap();
|
||||||
|
/// assert_eq!(config.url, "db/db.sqlite");
|
||||||
|
/// assert_eq!(config.pool_size, 25);
|
||||||
|
///
|
||||||
|
/// let config = Config::from("my_other_db", rocket).unwrap();
|
||||||
|
/// assert_eq!(config.url, "mysql://root:root@localhost/database");
|
||||||
|
/// assert_eq!(config.pool_size, (rocket.config().workers * 2) as u32);
|
||||||
|
///
|
||||||
|
/// let config = Config::from("unknown_db", rocket);
|
||||||
|
/// assert!(config.is_err())
|
||||||
|
/// }
|
||||||
|
/// #
|
||||||
|
/// # let config = Figment::from(rocket::Config::default()).merge(toml);
|
||||||
|
/// # let rocket = rocket::custom(config);
|
||||||
|
/// # pool(&rocket);
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub fn from(db_name: &str, rocket: &rocket::Rocket) -> Result<Config, figment::Error> {
|
||||||
|
let db_key = format!("databases.{}", db_name);
|
||||||
|
let key = |name: &str| format!("{}.{}", db_key, name);
|
||||||
|
Figment::from(rocket.figment())
|
||||||
|
.merge(Serialized::default(&key("pool_size"), rocket.config().workers * 2))
|
||||||
|
.merge(Serialized::default(&key("timeout"), 5))
|
||||||
|
.extract_inner::<Self>(&db_key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,188 @@
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use rocket::fairing::{AdHoc, Fairing};
|
||||||
|
use rocket::request::{Request, Outcome, FromRequest};
|
||||||
|
use rocket::outcome::IntoOutcome;
|
||||||
|
use rocket::http::Status;
|
||||||
|
|
||||||
|
use rocket::tokio::sync::{OwnedSemaphorePermit, Semaphore, Mutex};
|
||||||
|
use rocket::tokio::time::timeout;
|
||||||
|
|
||||||
|
use crate::databases::{Config, Poolable, Error};
|
||||||
|
|
||||||
|
/// Unstable internal details of generated code for the #[database] attribute.
|
||||||
|
///
|
||||||
|
/// This type is implemented here instead of in generated code to ensure all
|
||||||
|
/// types are properly checked.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub struct ConnectionPool<K, C: Poolable> {
|
||||||
|
config: Config,
|
||||||
|
// This is an 'Option' so that we can drop the pool in a 'spawn_blocking'.
|
||||||
|
pool: Option<r2d2::Pool<C::Manager>>,
|
||||||
|
semaphore: Arc<Semaphore>,
|
||||||
|
_marker: PhantomData<fn() -> K>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K, C: Poolable> Clone for ConnectionPool<K, C> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
ConnectionPool {
|
||||||
|
config: self.config.clone(),
|
||||||
|
pool: self.pool.clone(),
|
||||||
|
semaphore: self.semaphore.clone(),
|
||||||
|
_marker: PhantomData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unstable internal details of generated code for the #[database] attribute.
|
||||||
|
///
|
||||||
|
/// This type is implemented here instead of in generated code to ensure all
|
||||||
|
/// types are properly checked.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub struct Connection<K, C: Poolable> {
|
||||||
|
connection: Arc<Mutex<Option<r2d2::PooledConnection<C::Manager>>>>,
|
||||||
|
permit: Option<OwnedSemaphorePermit>,
|
||||||
|
_marker: PhantomData<fn() -> K>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// A wrapper around spawn_blocking that propagates panics to the calling code.
|
||||||
|
async fn run_blocking<F, R>(job: F) -> R
|
||||||
|
where F: FnOnce() -> R + Send + 'static, R: Send + 'static,
|
||||||
|
{
|
||||||
|
match tokio::task::spawn_blocking(job).await {
|
||||||
|
Ok(ret) => ret,
|
||||||
|
Err(e) => match e.try_into_panic() {
|
||||||
|
Ok(panic) => std::panic::resume_unwind(panic),
|
||||||
|
Err(_) => unreachable!("spawn_blocking tasks are never cancelled"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! dberr {
|
||||||
|
($msg:literal, $db_name:expr, $efmt:literal, $error:expr, $rocket:expr) => ({
|
||||||
|
rocket::error!(concat!("database ", $msg, " error for pool named `{}`"), $db_name);
|
||||||
|
error_!($efmt, $error);
|
||||||
|
return Err($rocket);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K: 'static, C: Poolable> ConnectionPool<K, C> {
|
||||||
|
pub fn fairing(fairing_name: &'static str, db: &'static str) -> impl Fairing {
|
||||||
|
AdHoc::on_attach(fairing_name, move |rocket| async move {
|
||||||
|
let config = match Config::from(db, &rocket) {
|
||||||
|
Ok(config) => config,
|
||||||
|
Err(e) => dberr!("config", db, "{}", e, rocket),
|
||||||
|
};
|
||||||
|
|
||||||
|
let pool_size = config.pool_size;
|
||||||
|
match C::pool(db, &rocket) {
|
||||||
|
Ok(pool) => Ok(rocket.manage(ConnectionPool::<K, C> {
|
||||||
|
config,
|
||||||
|
pool: Some(pool),
|
||||||
|
semaphore: Arc::new(Semaphore::new(pool_size as usize)),
|
||||||
|
_marker: PhantomData,
|
||||||
|
})),
|
||||||
|
Err(Error::Config(e)) => dberr!("config", db, "{}", e, rocket),
|
||||||
|
Err(Error::Pool(e)) => dberr!("pool init", db, "{}", e, rocket),
|
||||||
|
Err(Error::Custom(e)) => dberr!("pool manager", db, "{:?}", e, rocket),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get(&self) -> Result<Connection<K, C>, ()> {
|
||||||
|
let duration = std::time::Duration::from_secs(self.config.timeout as u64);
|
||||||
|
let permit = match timeout(duration, self.semaphore.clone().acquire_owned()).await {
|
||||||
|
Ok(p) => p.expect("internal invariant broken: semaphore should not be closed"),
|
||||||
|
Err(_) => {
|
||||||
|
error_!("database connection retrieval timed out");
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let pool = self.pool.as_ref().cloned()
|
||||||
|
.expect("internal invariant broken: self.pool is Some");
|
||||||
|
|
||||||
|
match run_blocking(move || pool.get_timeout(duration)).await {
|
||||||
|
Ok(c) => Ok(Connection {
|
||||||
|
connection: Arc::new(Mutex::new(Some(c))),
|
||||||
|
permit: Some(permit),
|
||||||
|
_marker: PhantomData,
|
||||||
|
}),
|
||||||
|
Err(e) => {
|
||||||
|
error_!("failed to get a database connection: {}", e);
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub async fn get_one(rocket: &rocket::Rocket) -> Option<Connection<K, C>> {
|
||||||
|
match rocket.state::<Self>() {
|
||||||
|
Some(pool) => pool.get().await.ok(),
|
||||||
|
None => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub async fn get_pool(rocket: &rocket::Rocket) -> Option<Self> {
|
||||||
|
rocket.state::<Self>().map(|pool| pool.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K: 'static, C: Poolable> Connection<K, C> {
|
||||||
|
#[inline]
|
||||||
|
pub async fn run<F, R>(&self, f: F) -> R
|
||||||
|
where F: FnOnce(&mut C) -> R + Send + 'static,
|
||||||
|
R: Send + 'static,
|
||||||
|
{
|
||||||
|
let mut connection = self.connection.clone().lock_owned().await;
|
||||||
|
run_blocking(move || {
|
||||||
|
let conn = connection.as_mut()
|
||||||
|
.expect("internal invariant broken: self.connection is Some");
|
||||||
|
f(conn)
|
||||||
|
}).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K, C: Poolable> Drop for Connection<K, C> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let connection = self.connection.clone();
|
||||||
|
let permit = self.permit.take();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let mut connection = connection.lock_owned().await;
|
||||||
|
tokio::task::spawn_blocking(move || {
|
||||||
|
if let Some(conn) = connection.take() {
|
||||||
|
drop(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explicitly dropping the permit here so that it's only
|
||||||
|
// released after the connection is.
|
||||||
|
drop(permit);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K, C: Poolable> Drop for ConnectionPool<K, C> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let pool = self.pool.take();
|
||||||
|
tokio::task::spawn_blocking(move || drop(pool));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rocket::async_trait]
|
||||||
|
impl<'a, 'r, K: 'static, C: Poolable> FromRequest<'a, 'r> for Connection<K, C> {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
async fn from_request(request: &'a Request<'r>) -> Outcome<Self, ()> {
|
||||||
|
match request.managed_state::<ConnectionPool<K, C>>() {
|
||||||
|
Some(c) => c.get().await.into_outcome(Status::ServiceUnavailable),
|
||||||
|
None => {
|
||||||
|
error_!("Missing database fairing for `{}`", std::any::type_name::<K>());
|
||||||
|
Outcome::Failure((Status::InternalServerError, ()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
use rocket::figment;
|
||||||
|
|
||||||
|
/// A wrapper around `r2d2::Error`s or a custom database error type.
|
||||||
|
///
|
||||||
|
/// This type is only relevant to implementors of the [`Poolable`] trait. See
|
||||||
|
/// the [`Poolable`] documentation for more information on how to use this type.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error<T> {
|
||||||
|
/// A custom error of type `T`.
|
||||||
|
Custom(T),
|
||||||
|
/// An error occurred while initializing an `r2d2` pool.
|
||||||
|
Pool(r2d2::Error),
|
||||||
|
/// An error occurred while extracting a `figment` configuration.
|
||||||
|
Config(figment::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<figment::Error> for Error<T> {
|
||||||
|
fn from(error: figment::Error) -> Self {
|
||||||
|
Error::Config(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<r2d2::Error> for Error<T> {
|
||||||
|
fn from(error: r2d2::Error) -> Self {
|
||||||
|
Error::Pool(error)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,395 @@
|
||||||
|
//! Traits, utilities, and a macro for easy database connection pooling.
|
||||||
|
//!
|
||||||
|
//! # Overview
|
||||||
|
//!
|
||||||
|
//! This module provides traits, utilities, and a procedural macro that allows
|
||||||
|
//! you to easily connect your Rocket application to databases through
|
||||||
|
//! connection pools. A _database connection pool_ is a data structure that
|
||||||
|
//! maintains active database connections for later use in the application.
|
||||||
|
//! This implementation of connection pooling support is based on
|
||||||
|
//! [`r2d2`] and exposes connections through [request guards]. Databases are
|
||||||
|
//! individually configured through Rocket's regular configuration mechanisms: a
|
||||||
|
//! `Rocket.toml` file, environment variables, or procedurally.
|
||||||
|
//!
|
||||||
|
//! Connecting your Rocket application to a database using this library occurs
|
||||||
|
//! in three simple steps:
|
||||||
|
//!
|
||||||
|
//! 1. Configure your databases in `Rocket.toml`.
|
||||||
|
//! (see [Configuration](#configuration))
|
||||||
|
//! 2. Associate a request guard type and fairing with each database.
|
||||||
|
//! (see [Guard Types](#guard-types))
|
||||||
|
//! 3. Use the request guard to retrieve a connection in a handler.
|
||||||
|
//! (see [Handlers](#handlers))
|
||||||
|
//!
|
||||||
|
//! For a list of supported databases, see [Provided Databases](#provided). This
|
||||||
|
//! support can be easily extended by implementing the [`Poolable`] trait. See
|
||||||
|
//! [Extending](#extending) for more.
|
||||||
|
//!
|
||||||
|
//! ## Example
|
||||||
|
//!
|
||||||
|
//! Before using this library, the feature corresponding to your database type
|
||||||
|
//! in `rocket_contrib` must be enabled:
|
||||||
|
//!
|
||||||
|
//! ```toml
|
||||||
|
//! [dependencies.rocket_contrib]
|
||||||
|
//! version = "0.5.0-dev"
|
||||||
|
//! default-features = false
|
||||||
|
//! features = ["diesel_sqlite_pool"]
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! See [Provided](#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:
|
||||||
|
//!
|
||||||
|
//! ```toml
|
||||||
|
//! [global.databases]
|
||||||
|
//! sqlite_logs = { url = "/path/to/database.sqlite" }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! In your application's source code, one-time:
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! #[macro_use] extern crate rocket;
|
||||||
|
//! #[macro_use] extern crate rocket_contrib;
|
||||||
|
//!
|
||||||
|
//! # #[cfg(feature = "diesel_sqlite_pool")]
|
||||||
|
//! # mod test {
|
||||||
|
//! use rocket_contrib::databases::diesel;
|
||||||
|
//!
|
||||||
|
//! #[database("sqlite_logs")]
|
||||||
|
//! struct LogsDbConn(diesel::SqliteConnection);
|
||||||
|
//!
|
||||||
|
//! #[launch]
|
||||||
|
//! fn rocket() -> rocket::Rocket {
|
||||||
|
//! rocket::ignite().attach(LogsDbConn::fairing())
|
||||||
|
//! }
|
||||||
|
//! # } fn main() {}
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Whenever a connection to the database is needed:
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! # #[macro_use] extern crate rocket;
|
||||||
|
//! # #[macro_use] extern crate rocket_contrib;
|
||||||
|
//! #
|
||||||
|
//! # #[cfg(feature = "diesel_sqlite_pool")]
|
||||||
|
//! # mod test {
|
||||||
|
//! # use rocket_contrib::databases::diesel;
|
||||||
|
//! #
|
||||||
|
//! # #[database("sqlite_logs")]
|
||||||
|
//! # struct LogsDbConn(diesel::SqliteConnection);
|
||||||
|
//! #
|
||||||
|
//! # type Logs = ();
|
||||||
|
//! # type Result<T> = std::result::Result<T, ()>;
|
||||||
|
//! #
|
||||||
|
//! #[get("/logs/<id>")]
|
||||||
|
//! async fn get_logs(conn: LogsDbConn, id: usize) -> Result<Logs> {
|
||||||
|
//! # /*
|
||||||
|
//! conn.run(|c| Logs::by_id(c, id)).await
|
||||||
|
//! # */
|
||||||
|
//! # Ok(())
|
||||||
|
//! }
|
||||||
|
//! # } fn main() {}
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! # 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, a `pool_size` key. This looks as
|
||||||
|
//! follows:
|
||||||
|
//!
|
||||||
|
//! ```toml
|
||||||
|
//! # Option 1:
|
||||||
|
//! [global.databases]
|
||||||
|
//! sqlite_db = { url = "db.sqlite" }
|
||||||
|
//!
|
||||||
|
//! # Option 2:
|
||||||
|
//! [global.databases.my_db]
|
||||||
|
//! url = "mysql://root:root@localhost/my_db"
|
||||||
|
//!
|
||||||
|
//! # With a `pool_size` key:
|
||||||
|
//! [global.databases]
|
||||||
|
//! sqlite_db = { url = "db.sqlite", pool_size = 20 }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! 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 * 2)
|
||||||
|
//!
|
||||||
|
//! 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:
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! # #[cfg(feature = "diesel_sqlite_pool")] {
|
||||||
|
//! use rocket::figment::{value::{Map, Value}, util::map};
|
||||||
|
//!
|
||||||
|
//! #[rocket::launch]
|
||||||
|
//! fn rocket() -> _ {
|
||||||
|
//! let db: Map<_, Value> = map! {
|
||||||
|
//! "url" => "db.sqlite".into(),
|
||||||
|
//! "pool_size" => 10.into()
|
||||||
|
//! };
|
||||||
|
//!
|
||||||
|
//! let figment = rocket::Config::figment()
|
||||||
|
//! .merge(("databases", map!["my_db" => db]));
|
||||||
|
//!
|
||||||
|
//! rocket::custom(figment)
|
||||||
|
//! }
|
||||||
|
//! # rocket();
|
||||||
|
//! # }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Environment Variables
|
||||||
|
//!
|
||||||
|
//! Lastly, databases can be configured via environment variables by specifying
|
||||||
|
//! the `databases` table as detailed in the [Environment Variables
|
||||||
|
//! configuration
|
||||||
|
//! guide](https://rocket.rs/master/guide/configuration/#environment-variables):
|
||||||
|
//!
|
||||||
|
//! ```bash
|
||||||
|
//! ROCKET_DATABASES='{my_db={url="db.sqlite"}}'
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Multiple databases can be specified in the `ROCKET_DATABASES` environment variable
|
||||||
|
//! as well by comma separating them:
|
||||||
|
//!
|
||||||
|
//! ```bash
|
||||||
|
//! 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
|
||||||
|
//! attributes 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.
|
||||||
|
//!
|
||||||
|
//! The macro generates a [`FromRequest`] implementation for the decorated type,
|
||||||
|
//! allowing the type to be used as a request guard. This implementation
|
||||||
|
//! retrieves a connection from the database pool or fails with a
|
||||||
|
//! `Status::ServiceUnavailable` if connecting to the database times out.
|
||||||
|
//!
|
||||||
|
//! The macro will also generate two inherent methods on the decorated type:
|
||||||
|
//!
|
||||||
|
//! * `fn fairing() -> impl Fairing`
|
||||||
|
//!
|
||||||
|
//! Returns a fairing that initializes the associated database connection
|
||||||
|
//! pool.
|
||||||
|
//!
|
||||||
|
//! * `async fn get_one(&Rocket) -> Option<Self>`
|
||||||
|
//!
|
||||||
|
//! Retrieves a connection wrapper from the configured pool. Returns `Some`
|
||||||
|
//! as long as `Self::fairing()` has been attached.
|
||||||
|
//!
|
||||||
|
//! The attribute can only be applied to unit-like structs with one type. The
|
||||||
|
//! internal type of the structure must implement [`Poolable`].
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! # extern crate rocket;
|
||||||
|
//! # #[macro_use] extern crate rocket_contrib;
|
||||||
|
//! # #[cfg(feature = "diesel_sqlite_pool")]
|
||||||
|
//! # mod test {
|
||||||
|
//! use rocket_contrib::databases::diesel;
|
||||||
|
//!
|
||||||
|
//! #[database("my_db")]
|
||||||
|
//! struct MyDatabase(diesel::SqliteConnection);
|
||||||
|
//! # }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Other databases can be used by specifying their respective [`Poolable`]
|
||||||
|
//! type:
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! # extern crate rocket;
|
||||||
|
//! # #[macro_use] extern crate rocket_contrib;
|
||||||
|
//! # #[cfg(feature = "postgres_pool")]
|
||||||
|
//! # mod test {
|
||||||
|
//! use rocket_contrib::databases::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:
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! # #[macro_use] extern crate rocket;
|
||||||
|
//! # #[macro_use] extern crate rocket_contrib;
|
||||||
|
//! #
|
||||||
|
//! # #[cfg(feature = "diesel_sqlite_pool")] {
|
||||||
|
//! # use rocket::figment::{value::{Map, Value}, util::map};
|
||||||
|
//! use rocket_contrib::databases::diesel;
|
||||||
|
//!
|
||||||
|
//! #[database("my_db")]
|
||||||
|
//! struct MyDatabase(diesel::SqliteConnection);
|
||||||
|
//!
|
||||||
|
//! #[launch]
|
||||||
|
//! fn rocket() -> _ {
|
||||||
|
//! # let db: Map<_, Value> = map![
|
||||||
|
//! # "url" => "db.sqlite".into(), "pool_size" => 10.into()
|
||||||
|
//! # ];
|
||||||
|
//! # let figment = rocket::Config::figment().merge(("databases", map!["my_db" => db]));
|
||||||
|
//! 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:
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! # #[macro_use] extern crate rocket;
|
||||||
|
//! # #[macro_use] extern crate rocket_contrib;
|
||||||
|
//! #
|
||||||
|
//! # #[cfg(feature = "diesel_sqlite_pool")]
|
||||||
|
//! # mod test {
|
||||||
|
//! # use rocket_contrib::databases::diesel;
|
||||||
|
//! #[database("my_db")]
|
||||||
|
//! struct MyDatabase(diesel::SqliteConnection);
|
||||||
|
//!
|
||||||
|
//! #[get("/")]
|
||||||
|
//! fn my_handler(conn: MyDatabase) {
|
||||||
|
//! // ...
|
||||||
|
//! }
|
||||||
|
//! # }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! A connection can be retrieved and used with the `run()` method:
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! # #[macro_use] extern crate rocket;
|
||||||
|
//! # #[macro_use] extern crate rocket_contrib;
|
||||||
|
//! #
|
||||||
|
//! # #[cfg(feature = "diesel_sqlite_pool")]
|
||||||
|
//! # mod test {
|
||||||
|
//! # use rocket_contrib::databases::diesel;
|
||||||
|
//! # type Data = ();
|
||||||
|
//! #[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.
|
||||||
|
//!
|
||||||
|
// Note: Keep this table in sync with site/guite/6-state.md
|
||||||
|
//! | Kind | Driver | Version | `Poolable` Type | Feature |
|
||||||
|
//! |----------|-----------------------|-----------|--------------------------------|------------------------|
|
||||||
|
//! | MySQL | [Diesel] | `1` | [`diesel::MysqlConnection`] | `diesel_mysql_pool` |
|
||||||
|
//! | MySQL | [`rust-mysql-simple`] | `18` | [`mysql::Conn`] | `mysql_pool` |
|
||||||
|
//! | Postgres | [Diesel] | `1` | [`diesel::PgConnection`] | `diesel_postgres_pool` |
|
||||||
|
//! | Postgres | [Rust-Postgres] | `0.19` | [`postgres::Client`] | `postgres_pool` |
|
||||||
|
//! | Sqlite | [Diesel] | `1` | [`diesel::SqliteConnection`] | `diesel_sqlite_pool` |
|
||||||
|
//! | Sqlite | [`Rusqlite`] | `0.23` | [`rusqlite::Connection`] | `sqlite_pool` |
|
||||||
|
//! | Memcache | [`memcache`] | `0.15` | [`memcache::Client`] | `memcache_pool` |
|
||||||
|
//!
|
||||||
|
//! [Diesel]: https://diesel.rs
|
||||||
|
//! [`rusqlite::Connection`]: https://docs.rs/rusqlite/0.23.0/rusqlite/struct.Connection.html
|
||||||
|
//! [`diesel::SqliteConnection`]: http://docs.diesel.rs/diesel/prelude/struct.SqliteConnection.html
|
||||||
|
//! [`postgres::Client`]: https://docs.rs/postgres/0.19/postgres/struct.Client.html
|
||||||
|
//! [`diesel::PgConnection`]: http://docs.diesel.rs/diesel/pg/struct.PgConnection.html
|
||||||
|
//! [`mysql::Conn`]: https://docs.rs/mysql/18/mysql/struct.Conn.html
|
||||||
|
//! [`diesel::MysqlConnection`]: http://docs.diesel.rs/diesel/mysql/struct.MysqlConnection.html
|
||||||
|
//! [`Rusqlite`]: https://github.com/jgallagher/rusqlite
|
||||||
|
//! [Rust-Postgres]: https://github.com/sfackler/rust-postgres
|
||||||
|
//! [`rust-mysql-simple`]: https://github.com/blackbeam/rust-mysql-simple
|
||||||
|
//! [`diesel::PgConnection`]: http://docs.diesel.rs/diesel/pg/struct.PgConnection.html
|
||||||
|
//! [`memcache`]: https://github.com/aisk/rust-memcache
|
||||||
|
//! [`memcache::Client`]: https://docs.rs/memcache/0.15/memcache/struct.Client.html
|
||||||
|
//!
|
||||||
|
//! 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.
|
||||||
|
//!
|
||||||
|
//! [`FromRequest`]: rocket::request::FromRequest
|
||||||
|
//! [request guards]: rocket::request::FromRequest
|
||||||
|
//! [`Poolable`]: crate::databases::Poolable
|
||||||
|
|
||||||
|
pub extern crate r2d2;
|
||||||
|
|
||||||
|
#[cfg(any(
|
||||||
|
feature = "diesel_sqlite_pool",
|
||||||
|
feature = "diesel_postgres_pool",
|
||||||
|
feature = "diesel_mysql_pool"
|
||||||
|
))]
|
||||||
|
pub extern crate diesel;
|
||||||
|
|
||||||
|
#[cfg(feature = "postgres_pool")] pub extern crate postgres;
|
||||||
|
#[cfg(feature = "postgres_pool")] pub extern crate r2d2_postgres;
|
||||||
|
|
||||||
|
#[cfg(feature = "mysql_pool")] pub extern crate mysql;
|
||||||
|
#[cfg(feature = "mysql_pool")] pub extern crate r2d2_mysql;
|
||||||
|
|
||||||
|
#[cfg(feature = "sqlite_pool")] pub extern crate rusqlite;
|
||||||
|
#[cfg(feature = "sqlite_pool")] pub extern crate r2d2_sqlite;
|
||||||
|
|
||||||
|
#[cfg(feature = "memcache_pool")] pub extern crate memcache;
|
||||||
|
#[cfg(feature = "memcache_pool")] pub extern crate r2d2_memcache;
|
||||||
|
|
||||||
|
mod poolable;
|
||||||
|
mod config;
|
||||||
|
mod error;
|
||||||
|
mod connection;
|
||||||
|
|
||||||
|
pub use self::poolable::{Poolable, PoolResult};
|
||||||
|
pub use self::config::Config;
|
||||||
|
pub use self::error::Error;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub use rocket_contrib_codegen::*;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub use self::connection::*;
|
|
@ -0,0 +1,195 @@
|
||||||
|
use r2d2::ManageConnection;
|
||||||
|
|
||||||
|
use crate::databases::{Config, Error};
|
||||||
|
|
||||||
|
/// Trait implemented by `r2d2`-based database adapters.
|
||||||
|
///
|
||||||
|
/// # Provided Implementations
|
||||||
|
///
|
||||||
|
/// Implementations of `Poolable` are provided for the following types:
|
||||||
|
///
|
||||||
|
/// * `diesel::MysqlConnection`
|
||||||
|
/// * `diesel::PgConnection`
|
||||||
|
/// * `diesel::SqliteConnection`
|
||||||
|
/// * `postgres::Connection`
|
||||||
|
/// * `mysql::Conn`
|
||||||
|
/// * `rusqlite::Connection`
|
||||||
|
///
|
||||||
|
/// # Implementation Guide
|
||||||
|
///
|
||||||
|
/// As an r2d2-compatible database (or other resource) adapter provider,
|
||||||
|
/// implementing `Poolable` in your own library will enable Rocket users to
|
||||||
|
/// consume your adapter with its built-in connection pooling support.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
|
/// Consider a library `foo` with the following types:
|
||||||
|
///
|
||||||
|
/// * `foo::ConnectionManager`, which implements [`r2d2::ManageConnection`]
|
||||||
|
/// * `foo::Connection`, the `Connection` associated type of
|
||||||
|
/// `foo::ConnectionManager`
|
||||||
|
/// * `foo::Error`, errors resulting from manager instantiation
|
||||||
|
///
|
||||||
|
/// In order for Rocket to generate the required code to automatically provision
|
||||||
|
/// a r2d2 connection pool into application state, the `Poolable` trait needs to
|
||||||
|
/// be implemented for the connection type. The following example implements
|
||||||
|
/// `Poolable` for `foo::Connection`:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # mod foo {
|
||||||
|
/// # use std::fmt;
|
||||||
|
/// # use rocket_contrib::databases::r2d2;
|
||||||
|
/// # #[derive(Debug)] pub struct Error;
|
||||||
|
/// # impl std::error::Error for Error { }
|
||||||
|
/// # impl fmt::Display for Error {
|
||||||
|
/// # fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { Ok(()) }
|
||||||
|
/// # }
|
||||||
|
/// #
|
||||||
|
/// # pub struct Connection;
|
||||||
|
/// # pub struct ConnectionManager;
|
||||||
|
/// #
|
||||||
|
/// # type Result<T> = std::result::Result<T, Error>;
|
||||||
|
/// #
|
||||||
|
/// # impl ConnectionManager {
|
||||||
|
/// # pub fn new(url: &str) -> Result<Self> { Err(Error) }
|
||||||
|
/// # }
|
||||||
|
/// #
|
||||||
|
/// # impl self::r2d2::ManageConnection for ConnectionManager {
|
||||||
|
/// # type Connection = Connection;
|
||||||
|
/// # type Error = Error;
|
||||||
|
/// # fn connect(&self) -> Result<Connection> { panic!(()) }
|
||||||
|
/// # fn is_valid(&self, _: &mut Connection) -> Result<()> { panic!() }
|
||||||
|
/// # fn has_broken(&self, _: &mut Connection) -> bool { panic!() }
|
||||||
|
/// # }
|
||||||
|
/// # }
|
||||||
|
/// use rocket_contrib::databases::{r2d2, Error, Config, Poolable, PoolResult};
|
||||||
|
///
|
||||||
|
/// impl Poolable for foo::Connection {
|
||||||
|
/// type Manager = foo::ConnectionManager;
|
||||||
|
/// type Error = foo::Error;
|
||||||
|
///
|
||||||
|
/// fn pool(db_name: &str, rocket: &rocket::Rocket) -> PoolResult<Self> {
|
||||||
|
/// let config = Config::from(db_name, rocket)?;
|
||||||
|
/// let manager = foo::ConnectionManager::new(&config.url).map_err(Error::Custom)?;
|
||||||
|
/// Ok(r2d2::Pool::builder().max_size(config.pool_size).build(manager)?)
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// In this example, `ConnectionManager::new()` method returns a `foo::Error` on
|
||||||
|
/// failure. The [`Error`] enum consolidates this type, the `r2d2::Error` type
|
||||||
|
/// that can result from `r2d2::Pool::builder()`, and the
|
||||||
|
/// [`figment::Error`](rocket::figment::Error) type from
|
||||||
|
/// `database::Config::from()`.
|
||||||
|
///
|
||||||
|
/// In the event that a connection manager isn't fallible (as is the case with
|
||||||
|
/// Diesel's r2d2 connection manager, for instance), the associated error type
|
||||||
|
/// for the `Poolable` implementation should be `std::convert::Infallible`.
|
||||||
|
///
|
||||||
|
/// For more concrete example, consult Rocket's existing implementations of
|
||||||
|
/// [`Poolable`].
|
||||||
|
pub trait Poolable: Send + Sized + 'static {
|
||||||
|
/// The associated connection manager for the given connection type.
|
||||||
|
type Manager: ManageConnection<Connection=Self>;
|
||||||
|
|
||||||
|
/// The associated error type in the event that constructing the connection
|
||||||
|
/// manager and/or the connection pool fails.
|
||||||
|
type Error: std::fmt::Debug;
|
||||||
|
|
||||||
|
/// Creates an `r2d2` connection pool for `Manager::Connection`, returning
|
||||||
|
/// the pool on success.
|
||||||
|
fn pool(db_name: &str, rocket: &rocket::Rocket) -> PoolResult<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A type alias for the return type of [`Poolable::pool()`].
|
||||||
|
#[allow(type_alias_bounds)]
|
||||||
|
pub type PoolResult<P: Poolable> = Result<r2d2::Pool<P::Manager>, Error<P::Error>>;
|
||||||
|
|
||||||
|
#[cfg(feature = "diesel_sqlite_pool")]
|
||||||
|
impl Poolable for diesel::SqliteConnection {
|
||||||
|
type Manager = diesel::r2d2::ConnectionManager<diesel::SqliteConnection>;
|
||||||
|
type Error = std::convert::Infallible;
|
||||||
|
|
||||||
|
fn pool(db_name: &str, rocket: &rocket::Rocket) -> PoolResult<Self> {
|
||||||
|
let config = Config::from(db_name, rocket)?;
|
||||||
|
let manager = diesel::r2d2::ConnectionManager::new(&config.url);
|
||||||
|
Ok(r2d2::Pool::builder().max_size(config.pool_size).build(manager)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "diesel_postgres_pool")]
|
||||||
|
impl Poolable for diesel::PgConnection {
|
||||||
|
type Manager = diesel::r2d2::ConnectionManager<diesel::PgConnection>;
|
||||||
|
type Error = std::convert::Infallible;
|
||||||
|
|
||||||
|
fn pool(db_name: &str, rocket: &rocket::Rocket) -> PoolResult<Self> {
|
||||||
|
let config = Config::from(db_name, rocket)?;
|
||||||
|
let manager = diesel::r2d2::ConnectionManager::new(&config.url);
|
||||||
|
Ok(r2d2::Pool::builder().max_size(config.pool_size).build(manager)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "diesel_mysql_pool")]
|
||||||
|
impl Poolable for diesel::MysqlConnection {
|
||||||
|
type Manager = diesel::r2d2::ConnectionManager<diesel::MysqlConnection>;
|
||||||
|
type Error = std::convert::Infallible;
|
||||||
|
|
||||||
|
fn pool(db_name: &str, rocket: &rocket::Rocket) -> PoolResult<Self> {
|
||||||
|
let config = Config::from(db_name, rocket)?;
|
||||||
|
let manager = diesel::r2d2::ConnectionManager::new(&config.url);
|
||||||
|
Ok(r2d2::Pool::builder().max_size(config.pool_size).build(manager)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Add a feature to enable TLS in `postgres`; parse a suitable `config`.
|
||||||
|
#[cfg(feature = "postgres_pool")]
|
||||||
|
impl Poolable for postgres::Client {
|
||||||
|
type Manager = r2d2_postgres::PostgresConnectionManager<postgres::tls::NoTls>;
|
||||||
|
type Error = postgres::Error;
|
||||||
|
|
||||||
|
fn pool(db_name: &str, rocket: &rocket::Rocket) -> PoolResult<Self> {
|
||||||
|
let config = Config::from(db_name, rocket)?;
|
||||||
|
let url = config.url.parse().map_err(Error::Custom)?;
|
||||||
|
let manager = r2d2_postgres::PostgresConnectionManager::new(url, postgres::tls::NoTls);
|
||||||
|
Ok(r2d2::Pool::builder().max_size(config.pool_size).build(manager)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "mysql_pool")]
|
||||||
|
impl Poolable for mysql::Conn {
|
||||||
|
type Manager = r2d2_mysql::MysqlConnectionManager;
|
||||||
|
type Error = std::convert::Infallible;
|
||||||
|
|
||||||
|
fn pool(db_name: &str, rocket: &rocket::Rocket) -> PoolResult<Self> {
|
||||||
|
let config = Config::from(db_name, rocket)?;
|
||||||
|
let opts = mysql::OptsBuilder::from_opts(&config.url);
|
||||||
|
let manager = r2d2_mysql::MysqlConnectionManager::new(opts);
|
||||||
|
Ok(r2d2::Pool::builder().max_size(config.pool_size).build(manager)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "sqlite_pool")]
|
||||||
|
impl Poolable for rusqlite::Connection {
|
||||||
|
type Manager = r2d2_sqlite::SqliteConnectionManager;
|
||||||
|
type Error = std::convert::Infallible;
|
||||||
|
|
||||||
|
fn pool(db_name: &str, rocket: &rocket::Rocket) -> PoolResult<Self> {
|
||||||
|
let config = Config::from(db_name, rocket)?;
|
||||||
|
let manager = r2d2_sqlite::SqliteConnectionManager::file(&*config.url);
|
||||||
|
Ok(r2d2::Pool::builder().max_size(config.pool_size).build(manager)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "memcache_pool")]
|
||||||
|
impl Poolable for memcache::Client {
|
||||||
|
type Manager = r2d2_memcache::MemcacheConnectionManager;
|
||||||
|
// Unused, but we might want it in the future without a breaking change.
|
||||||
|
type Error = memcache::MemcacheError;
|
||||||
|
|
||||||
|
fn pool(db_name: &str, rocket: &rocket::Rocket) -> PoolResult<Self> {
|
||||||
|
let config = Config::from(db_name, rocket)?;
|
||||||
|
let manager = r2d2_memcache::MemcacheConnectionManager::new(&*config.url);
|
||||||
|
Ok(r2d2::Pool::builder().max_size(config.pool_size).build(manager)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue