mirror of https://github.com/rwf2/Rocket.git
Introduce async database pools: 'rocket_db_pools'.
This is the async analog of 'rocket_sync_db_pools', rewritten to be cleaner, leaner, easier to maintain and extend, and better documented. Resolves #1117. Resolves #1187.
This commit is contained in:
parent
f7f068be11
commit
5b1a04deab
|
@ -3,58 +3,65 @@
|
||||||
[crates.io]: https://img.shields.io/crates/v/rocket_db_pools.svg
|
[crates.io]: https://img.shields.io/crates/v/rocket_db_pools.svg
|
||||||
[crate]: https://crates.io/crates/rocket_db_pools
|
[crate]: https://crates.io/crates/rocket_db_pools
|
||||||
[docs.svg]: https://img.shields.io/badge/web-master-red.svg?style=flat&label=docs&colorB=d33847
|
[docs.svg]: https://img.shields.io/badge/web-master-red.svg?style=flat&label=docs&colorB=d33847
|
||||||
[crate docs]: https://api.rocket.rs/master/rocket_db_pools
|
[crate docs]: https://api.rocket.rs/v0.5-rc/rocket_db_pools
|
||||||
[ci.svg]: https://github.com/SergioBenitez/Rocket/workflows/CI/badge.svg
|
[ci.svg]: https://github.com/SergioBenitez/Rocket/workflows/CI/badge.svg
|
||||||
[ci]: https://github.com/SergioBenitez/Rocket/actions
|
[ci]: https://github.com/SergioBenitez/Rocket/actions
|
||||||
|
|
||||||
This crate provides traits, utilities, and a procedural macro for configuring
|
Asynchronous database driver integration for Rocket. See the [crate docs] for
|
||||||
and accessing database connection pools in Rocket.
|
full usage details.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
First, enable the feature corresponding to your database type:
|
1. Add `rocket_db_pools` as a dependency with one or more [database driver
|
||||||
|
features] enabled:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies.rocket_db_pools]
|
[dependencies.rocket_db_pools]
|
||||||
version = "0.1.0-dev"
|
version = "0.1.0-rc"
|
||||||
features = ["sqlx_sqlite"]
|
features = ["sqlx_sqlite"]
|
||||||
```
|
```
|
||||||
|
|
||||||
A full list of supported databases and their associated feature names is
|
2. Choose a name for your database, here `sqlite_logs`. [Configure] _at least_ a
|
||||||
available in the [crate docs]. In whichever configuration source you choose,
|
URL for the database:
|
||||||
configure a `databases` dictionary with a key for each database, here
|
|
||||||
`sqlite_logs` in a TOML source:
|
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[default.databases]
|
[default.databases.sqlite_logs]
|
||||||
sqlite_logs = { url = "/path/to/database.sqlite" }
|
url = "/path/to/database.sqlite"
|
||||||
```
|
```
|
||||||
|
|
||||||
In your application's source code:
|
3. [Derive `Database`] for a unit type (`Logs` here) which
|
||||||
|
wraps the selected driver's [`Pool`] type and is decorated with
|
||||||
|
`#[database("name")]`. Attach `Type::init()` to your application's `Rocket`
|
||||||
|
to initialize the database pool:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
#[macro_use] extern crate rocket;
|
use rocket_db_pools::{Database, Connection};
|
||||||
use rocket::serde::json::Json;
|
|
||||||
|
|
||||||
use rocket_db_pools::{Database, sqlx};
|
|
||||||
|
|
||||||
#[derive(Database)]
|
#[derive(Database)]
|
||||||
#[database("sqlite_logs")]
|
#[database("sqlite_logs")]
|
||||||
struct LogsDb(sqlx::SqlitePool);
|
struct Logs(sqlx::SqlitePool);
|
||||||
|
|
||||||
type LogsDbConn = <LogsDb as Database>::Connection;
|
|
||||||
|
|
||||||
#[get("/logs/<id>")]
|
|
||||||
async fn get_logs(mut db: LogsDbConn, id: usize) -> Result<Json<Vec<String>>> {
|
|
||||||
let logs = sqlx::query!("SELECT text FROM logs;").execute(&mut *db).await?;
|
|
||||||
|
|
||||||
Ok(Json(logs))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[launch]
|
#[launch]
|
||||||
fn rocket() -> _ {
|
fn rocket() -> _ {
|
||||||
rocket::build().attach(LogsDb::fairing())
|
rocket::build().attach(Logs::init())
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
See the [crate docs] for full details.
|
4. Use [`Connection<Type>`] as a request guard to retrieve an
|
||||||
|
active database connection:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[get("/<id>")]
|
||||||
|
async fn read(mut db: Connection<Logs>, id: i64) -> Result<Log> {
|
||||||
|
sqlx::query!("SELECT content FROM logs WHERE id = ?", id)
|
||||||
|
.fetch_one(&mut *db)
|
||||||
|
.map_ok(|r| Log(r.content))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
[database driver features]: https://api.rocket.rs/v0.5-rc/rocket_db_pools/index.html#supported-drivers
|
||||||
|
[`Pool`]: https://api.rocket.rs/v0.5-rc/rocket_db_pools/index.html#supported-drivers
|
||||||
|
[Configure]: https://api.rocket.rs/v0.5-rc/rocket_db_pools/index.html#configuration
|
||||||
|
[Derive `Database`]: https://api.rocket.rs/v0.5-rc/rocket_db_pools/derive.Database.html
|
||||||
|
[`Connection<Type>`]: https://api.rocket.rs/v0.5-rc/rocket_db_pools/struct.Connection.html
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "rocket_db_pools_codegen"
|
name = "rocket_db_pools_codegen"
|
||||||
version = "0.1.0-dev"
|
version = "0.1.0-rc"
|
||||||
authors = ["Sergio Benitez <sb@sergio.bz>", "Jeb Rosen <jeb@jebrosen.com>"]
|
authors = ["Sergio Benitez <sb@sergio.bz>", "Jeb Rosen <jeb@jebrosen.com>"]
|
||||||
description = "Procedural macros for rocket_db_pools."
|
description = "Procedural macros for rocket_db_pools."
|
||||||
repository = "https://github.com/SergioBenitez/Rocket/contrib/db_pools"
|
repository = "https://github.com/SergioBenitez/Rocket/contrib/db_pools"
|
||||||
|
@ -15,3 +15,7 @@ proc-macro = true
|
||||||
[dependencies]
|
[dependencies]
|
||||||
devise = "0.3"
|
devise = "0.3"
|
||||||
quote = "1"
|
quote = "1"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
rocket = { path = "../../../core/lib", default-features = false }
|
||||||
|
rocket_db_pools = { path = "../lib", features = ["deadpool_postgres"] }
|
||||||
|
|
|
@ -2,7 +2,10 @@ use proc_macro::TokenStream;
|
||||||
|
|
||||||
use devise::{DeriveGenerator, FromMeta, MapperBuild, Support, ValidatorBuild};
|
use devise::{DeriveGenerator, FromMeta, MapperBuild, Support, ValidatorBuild};
|
||||||
use devise::proc_macro2_diagnostics::SpanDiagnosticExt;
|
use devise::proc_macro2_diagnostics::SpanDiagnosticExt;
|
||||||
use devise::syn::{Fields, spanned::Spanned};
|
use devise::syn::{self, spanned::Spanned};
|
||||||
|
|
||||||
|
const ONE_DATABASE_ATTR: &str = "missing `#[database(\"name\")]` attribute";
|
||||||
|
const ONE_UNNAMED_FIELD: &str = "struct must have exactly one unnamed field";
|
||||||
|
|
||||||
#[derive(Debug, FromMeta)]
|
#[derive(Debug, FromMeta)]
|
||||||
struct DatabaseAttribute {
|
struct DatabaseAttribute {
|
||||||
|
@ -10,61 +13,92 @@ struct DatabaseAttribute {
|
||||||
name: String,
|
name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
const ONE_DATABASE_ATTR: &str = "`Database` derive requires exactly one \
|
|
||||||
`#[database(\"\")] attribute`";
|
|
||||||
const ONE_UNNAMED_FIELD: &str = "`Database` derive can only be applied to \
|
|
||||||
structs with exactly one unnamed field";
|
|
||||||
|
|
||||||
pub fn derive_database(input: TokenStream) -> TokenStream {
|
pub fn derive_database(input: TokenStream) -> TokenStream {
|
||||||
DeriveGenerator::build_for(input, quote!(impl rocket_db_pools::Database))
|
DeriveGenerator::build_for(input, quote!(impl rocket_db_pools::Database))
|
||||||
.support(Support::TupleStruct)
|
.support(Support::TupleStruct)
|
||||||
.validator(ValidatorBuild::new()
|
.validator(ValidatorBuild::new()
|
||||||
.struct_validate(|_, struct_| {
|
.struct_validate(|_, s| {
|
||||||
if struct_.fields.len() == 1 {
|
if s.fields.len() == 1 {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
return Err(struct_.fields.span().error(ONE_UNNAMED_FIELD))
|
Err(s.fields.span().error(ONE_UNNAMED_FIELD))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.inner_mapper(MapperBuild::new()
|
|
||||||
.try_struct_map(|_, struct_| {
|
|
||||||
let krate = quote_spanned!(struct_.span() => ::rocket_db_pools);
|
|
||||||
let db_name = match DatabaseAttribute::one_from_attrs("database", &struct_.attrs)? {
|
|
||||||
Some(attr) => attr.name,
|
|
||||||
None => return Err(struct_.span().error(ONE_DATABASE_ATTR)),
|
|
||||||
};
|
|
||||||
let fairing_name = format!("'{}' Database Pool", db_name);
|
|
||||||
|
|
||||||
let pool_type = match &struct_.fields {
|
|
||||||
Fields::Unnamed(f) => &f.unnamed[0].ty,
|
|
||||||
_ => unreachable!("Support::TupleStruct"),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(quote_spanned! { struct_.span() =>
|
|
||||||
const NAME: &'static str = #db_name;
|
|
||||||
type Pool = #pool_type;
|
|
||||||
fn fairing() -> #krate::Fairing<Self> {
|
|
||||||
#krate::Fairing::new(#fairing_name)
|
|
||||||
}
|
|
||||||
fn pool(&self) -> &Self::Pool { &self.0 }
|
|
||||||
})
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.outer_mapper(MapperBuild::new()
|
.outer_mapper(MapperBuild::new()
|
||||||
.try_struct_map(|_, struct_| {
|
.struct_map(|_, s| {
|
||||||
let decorated_type = &struct_.ident;
|
let decorated_type = &s.ident;
|
||||||
let pool_type = match &struct_.fields {
|
let pool_type = match &s.fields {
|
||||||
Fields::Unnamed(f) => &f.unnamed[0].ty,
|
syn::Fields::Unnamed(f) => &f.unnamed[0].ty,
|
||||||
_ => unreachable!("Support::TupleStruct"),
|
_ => unreachable!("Support::TupleStruct"),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(quote_spanned! { struct_.span() =>
|
quote_spanned! { s.span() =>
|
||||||
impl From<#pool_type> for #decorated_type {
|
impl From<#pool_type> for #decorated_type {
|
||||||
fn from(pool: #pool_type) -> Self {
|
fn from(pool: #pool_type) -> Self {
|
||||||
Self(pool)
|
Self(pool)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for #decorated_type {
|
||||||
|
type Target = #pool_type;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::DerefMut for #decorated_type {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rocket::async_trait]
|
||||||
|
impl<'r> rocket::request::FromRequest<'r> for &'r #decorated_type {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
async fn from_request(
|
||||||
|
req: &'r rocket::request::Request<'_>
|
||||||
|
) -> rocket::request::Outcome<Self, Self::Error> {
|
||||||
|
match #decorated_type::fetch(req.rocket()) {
|
||||||
|
Some(db) => rocket::outcome::Outcome::Success(db),
|
||||||
|
None => rocket::outcome::Outcome::Failure((
|
||||||
|
rocket::http::Status::InternalServerError, ()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl rocket::Sentinel for &#decorated_type {
|
||||||
|
fn abort(rocket: &rocket::Rocket<rocket::Ignite>) -> bool {
|
||||||
|
#decorated_type::fetch(rocket).is_none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.outer_mapper(quote!(#[rocket::async_trait]))
|
||||||
|
.inner_mapper(MapperBuild::new()
|
||||||
|
.try_struct_map(|_, s| {
|
||||||
|
let db_name = DatabaseAttribute::one_from_attrs("database", &s.attrs)?
|
||||||
|
.map(|attr| attr.name)
|
||||||
|
.ok_or_else(|| s.span().error(ONE_DATABASE_ATTR))?;
|
||||||
|
|
||||||
|
let fairing_name = format!("'{}' Database Pool", db_name);
|
||||||
|
|
||||||
|
let pool_type = match &s.fields {
|
||||||
|
syn::Fields::Unnamed(f) => &f.unnamed[0].ty,
|
||||||
|
_ => unreachable!("Support::TupleStruct"),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(quote_spanned! { s.span() =>
|
||||||
|
type Pool = #pool_type;
|
||||||
|
|
||||||
|
const NAME: &'static str = #db_name;
|
||||||
|
|
||||||
|
fn init() -> rocket_db_pools::Initializer<Self> {
|
||||||
|
rocket_db_pools::Initializer::with_name(#fairing_name)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,41 +1,61 @@
|
||||||
#![recursion_limit="256"]
|
#![recursion_limit="256"]
|
||||||
|
|
||||||
#![warn(rust_2018_idioms)]
|
#![warn(rust_2018_idioms)]
|
||||||
|
|
||||||
//! # `rocket_databases` - Code Generation
|
//! # `rocket_db_pool` - Code Generation
|
||||||
//!
|
//!
|
||||||
//! This crate implements the code generation portion of the `rocket_databases`
|
//! Implements the code generation portion of the `rocket_db_pool` crate. This
|
||||||
//! crate.
|
//! is an implementation detail. This create should never be depended on
|
||||||
|
//! directly.
|
||||||
|
|
||||||
#[macro_use] extern crate quote;
|
#[macro_use] extern crate quote;
|
||||||
|
|
||||||
mod database;
|
mod database;
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
/// Automatic derive for the [`Database`] trait.
|
||||||
|
///
|
||||||
/// Defines a database type and implements [`Database`] on it.
|
/// ```rust
|
||||||
|
/// use rocket_db_pools::Database;
|
||||||
|
/// # type PoolType = rocket_db_pools::deadpool_postgres::Pool;
|
||||||
///
|
///
|
||||||
/// ```ignore
|
|
||||||
/// #[derive(Database)]
|
/// #[derive(Database)]
|
||||||
/// #[database("database_name")]
|
/// #[database("database_name")]
|
||||||
/// struct Db(PoolType);
|
/// struct Db(PoolType);
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// `PoolType` must implement [`Pool`].
|
/// The derive generates an implementation of [`Database`] as follows:
|
||||||
///
|
///
|
||||||
/// This macro generates the following code, implementing the [`Database`] trait
|
/// * [`Database::NAME`] is set to the value in the `#[database("name")]`
|
||||||
/// on the struct. Custom implementations of `Database` should usually also
|
/// attribute.
|
||||||
/// start with roughly this code:
|
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// This names the database, providing an anchor to configure the database via
|
||||||
/// impl Database for Db {
|
/// `Rocket.toml` or any other configuration source. Specifically, the
|
||||||
/// const NAME: &'static str = "config_name";
|
/// configuration in `databases.name` is used to configure the driver.
|
||||||
/// type Pool = PoolType;
|
///
|
||||||
/// fn fairing() -> Fairing<Self> { Fairing::new(|p| Self(p)) }
|
/// * [`Database::Pool`] is set to the wrapped type: `PoolType` above. The type
|
||||||
/// fn pool(&self) -> &Self::Pool { &self.0 }
|
/// must implement [`Pool`].
|
||||||
/// }
|
///
|
||||||
/// ```
|
/// To meet the required [`Database`] supertrait bounds, this derive also
|
||||||
|
/// generates implementations for:
|
||||||
|
///
|
||||||
|
/// * `From<Db::Pool>`
|
||||||
|
///
|
||||||
|
/// * `Deref<Target = Db::Pool>`
|
||||||
|
///
|
||||||
|
/// * `DerefMut<Target = Db::Pool>`
|
||||||
|
///
|
||||||
|
/// * `FromRequest<'_> for &Db`
|
||||||
|
///
|
||||||
|
/// * `Sentinel for &Db`
|
||||||
|
///
|
||||||
|
/// The `Deref` impls enable accessing the database pool directly from
|
||||||
|
/// references `&Db` or `&mut Db`. To force a dereference to the underlying
|
||||||
|
/// type, use `&db.0` or `&**db` or their `&mut` variants.
|
||||||
|
///
|
||||||
|
/// [`Database`]: ../rocket_db_pools/trait.Database.html
|
||||||
|
/// [`Database::NAME`]: ../rocket_db_pools/trait.Database.html#associatedconstant.NAME
|
||||||
|
/// [`Database::Pool`]: ../rocket_db_pools/trait.Database.html#associatedtype.Pool
|
||||||
|
/// [`Pool`]: ../rocket_db_pools/trait.Pool.html
|
||||||
#[proc_macro_derive(Database, attributes(database))]
|
#[proc_macro_derive(Database, attributes(database))]
|
||||||
pub fn derive_database(input: TokenStream) -> TokenStream {
|
pub fn derive_database(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
crate::database::derive_database(input)
|
crate::database::derive_database(input)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "rocket_db_pools"
|
name = "rocket_db_pools"
|
||||||
version = "0.1.0-dev"
|
version = "0.1.0-rc"
|
||||||
authors = ["Sergio Benitez <sb@sergio.bz>", "Jeb Rosen <jeb@jebrosen.com>"]
|
authors = ["Sergio Benitez <sb@sergio.bz>", "Jeb Rosen <jeb@jebrosen.com>"]
|
||||||
description = "Rocket async database pooling support"
|
description = "Rocket async database pooling support"
|
||||||
repository = "https://github.com/SergioBenitez/Rocket/contrib/db_pools"
|
repository = "https://github.com/SergioBenitez/Rocket/contrib/db_pools"
|
||||||
|
@ -9,32 +9,64 @@ keywords = ["rocket", "framework", "database", "pools"]
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
deadpool_postgres = ["deadpool-postgres"]
|
# deadpool features
|
||||||
deadpool_redis = ["deadpool-redis"]
|
deadpool_postgres = ["deadpool-postgres", "deadpool"]
|
||||||
|
deadpool_redis = ["deadpool-redis", "deadpool"]
|
||||||
|
# sqlx features
|
||||||
sqlx_mysql = ["sqlx", "sqlx/mysql"]
|
sqlx_mysql = ["sqlx", "sqlx/mysql"]
|
||||||
sqlx_postgres = ["sqlx", "sqlx/postgres"]
|
sqlx_postgres = ["sqlx", "sqlx/postgres"]
|
||||||
sqlx_sqlite = ["sqlx", "sqlx/sqlite"]
|
sqlx_sqlite = ["sqlx", "sqlx/sqlite"]
|
||||||
|
sqlx_mssql = ["sqlx", "sqlx/mssql"]
|
||||||
[dependencies]
|
sqlx_macros = ["sqlx/macros"]
|
||||||
rocket_db_pools_codegen = { path = "../codegen" }
|
# implicit features: mongodb
|
||||||
|
|
||||||
# integration-specific
|
|
||||||
deadpool-postgres = { version = "0.9", default-features = false, optional = true }
|
|
||||||
deadpool-redis = { version = "0.8", default-features = false, optional = true }
|
|
||||||
mongodb = { version = "1", default-features = false, features = ["tokio-runtime"], optional = true }
|
|
||||||
mysql_async = { version = "0.27", default-features = false, optional = true }
|
|
||||||
redis = { version = "0.20", default-features = false, features = ["aio", "tokio-comp"] }
|
|
||||||
sqlx = { version = "0.5", default-features = false, features = ["runtime-tokio-rustls"], optional = true }
|
|
||||||
|
|
||||||
[dependencies.rocket]
|
[dependencies.rocket]
|
||||||
path = "../../../core/lib"
|
path = "../../../core/lib"
|
||||||
|
version = "0.5.0-rc.1"
|
||||||
default-features = false
|
default-features = false
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[dependencies.rocket_db_pools_codegen]
|
||||||
all-features = true
|
path = "../codegen"
|
||||||
|
version = "0.1.0-rc"
|
||||||
|
|
||||||
|
[dependencies.deadpool]
|
||||||
|
version = "0.8"
|
||||||
|
default-features = false
|
||||||
|
features = ["rt_tokio_1", "managed"]
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.deadpool-postgres]
|
||||||
|
version = "0.9"
|
||||||
|
default-features = false
|
||||||
|
features = ["rt_tokio_1"]
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.deadpool-redis]
|
||||||
|
version = "0.8.1"
|
||||||
|
default-features = false
|
||||||
|
features = ["rt_tokio_1"]
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.mongodb]
|
||||||
|
version = "1"
|
||||||
|
default-features = false
|
||||||
|
features = ["tokio-runtime"]
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.sqlx]
|
||||||
|
version = "0.5"
|
||||||
|
default-features = false
|
||||||
|
features = ["runtime-tokio-rustls"]
|
||||||
|
optional = true
|
||||||
|
|
||||||
[dev-dependencies.rocket]
|
[dev-dependencies.rocket]
|
||||||
path = "../../../core/lib"
|
path = "../../../core/lib"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["json"]
|
features = ["json"]
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
version_check = "0.9"
|
||||||
|
|
|
@ -1,64 +1,83 @@
|
||||||
use rocket::figment::{self, Figment, providers::Serialized};
|
|
||||||
use rocket::serde::{Deserialize, Serialize};
|
use rocket::serde::{Deserialize, Serialize};
|
||||||
use rocket::{Build, Rocket};
|
|
||||||
|
|
||||||
/// A base `Config` for any `Pool` type.
|
/// Base configuration for all database drivers.
|
||||||
///
|
///
|
||||||
/// For the following configuration:
|
/// A dictionary matching this structure is extracted from the active
|
||||||
|
/// [`Figment`](crate::figment::Figment), scoped to `databases.name`, where
|
||||||
|
/// `name` is the name of the database, by the
|
||||||
|
/// [`Initializer`](crate::Initializer) fairing on ignition and used to
|
||||||
|
/// configure the relevant database and database pool.
|
||||||
|
///
|
||||||
|
/// With the default provider, these parameters are typically configured in a
|
||||||
|
/// `Rocket.toml` file:
|
||||||
///
|
///
|
||||||
/// ```toml
|
/// ```toml
|
||||||
/// [global.databases.my_database]
|
/// [default.databases.db_name]
|
||||||
/// url = "postgres://root:root@localhost/my_database"
|
/// url = "/path/to/db.sqlite"
|
||||||
/// pool_size = 10
|
///
|
||||||
|
/// # only `url` is required. `Initializer` provides defaults for the rest.
|
||||||
|
/// min_connections = 64
|
||||||
|
/// max_connections = 1024
|
||||||
|
/// connect_timeout = 5
|
||||||
|
/// idle_timeout = 120
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ...the following struct would be passed to [`Pool::initialize()`]:
|
/// Alternatively, a custom provider can be used. For example, a custom `Figment`
|
||||||
|
/// with a global `databases.name` configuration:
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use rocket_db_pools::Config;
|
/// # use rocket::launch;
|
||||||
/// Config {
|
/// #[launch]
|
||||||
/// url: "postgres://root:root@localhost/my_database".into(),
|
/// fn rocket() -> _ {
|
||||||
/// pool_size: 10,
|
/// let figment = rocket::Config::figment()
|
||||||
/// timeout: 5,
|
/// .merge(("databases.name", rocket_db_pools::Config {
|
||||||
/// };
|
/// url: "db:specific@config&url".into(),
|
||||||
|
/// min_connections: None,
|
||||||
|
/// max_connections: 1024,
|
||||||
|
/// connect_timeout: 3,
|
||||||
|
/// idle_timeout: None,
|
||||||
|
/// }));
|
||||||
|
///
|
||||||
|
/// rocket::custom(figment)
|
||||||
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// If you want to implement your own custom database adapter and need some more
|
/// For general information on configuration in Rocket, see [`rocket::config`].
|
||||||
/// configuration options, you may need to define a custom `Config` struct.
|
/// For higher-level details on configuring a database, see the [crate-level
|
||||||
///
|
/// docs](crate#configuration).
|
||||||
/// [`Pool::initialize()`]: crate::Pool::initialize
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||||
#[serde(crate = "rocket::serde")]
|
#[serde(crate = "rocket::serde")]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
/// Connection URL specified in the Rocket configuration.
|
/// Database-specific connection and configuration URL.
|
||||||
|
///
|
||||||
|
/// The format of the URL is database specific; consult your database's
|
||||||
|
/// documentation.
|
||||||
pub url: String,
|
pub url: String,
|
||||||
/// Initial pool size. Defaults to the number of Rocket workers * 4.
|
/// Minimum number of connections to maintain in the pool.
|
||||||
pub pool_size: u32,
|
///
|
||||||
/// How long to wait, in seconds, for a new connection before timing out.
|
/// **Note:** `deadpool` drivers do not support and thus ignore this value.
|
||||||
/// Defaults to `5`.
|
///
|
||||||
// FIXME: Use `time`.
|
/// _Default:_ `None`.
|
||||||
pub timeout: u8,
|
pub min_connections: Option<u32>,
|
||||||
}
|
/// Maximum number of connections to maintain in the pool.
|
||||||
|
///
|
||||||
impl Config {
|
/// _Default:_ `workers * 4`.
|
||||||
pub fn from(db_name: &str, rocket: &Rocket<Build>) -> Result<Self, figment::Error> {
|
pub max_connections: usize,
|
||||||
Self::figment(db_name, rocket).extract::<Self>()
|
/// Number of seconds to wait for a connection before timing out.
|
||||||
}
|
///
|
||||||
|
/// If the timeout elapses before a connection can be made or retrieved from
|
||||||
pub fn figment(db_name: &str, rocket: &Rocket<Build>) -> Figment {
|
/// a pool, an error is returned.
|
||||||
let db_key = format!("databases.{}", db_name);
|
///
|
||||||
let default_pool_size = rocket.figment()
|
/// _Default:_ `5`.
|
||||||
.extract_inner::<u32>(rocket::Config::WORKERS)
|
pub connect_timeout: u64,
|
||||||
.map(|workers| workers * 4)
|
/// Maximum number of seconds to keep a connection alive for.
|
||||||
.ok();
|
///
|
||||||
|
/// After a connection is established, it is maintained in a pool for
|
||||||
let figment = Figment::from(rocket.figment())
|
/// efficient connection retrieval. When an `idle_timeout` is set, that
|
||||||
.focus(&db_key)
|
/// connection will be closed after the timeout elapses. If an
|
||||||
.join(Serialized::default("timeout", 5));
|
/// `idle_timeout` is not specified, the behavior is driver specific but
|
||||||
|
/// typically defaults to keeping a connection active indefinitely.
|
||||||
match default_pool_size {
|
///
|
||||||
Some(pool_size) => figment.join(Serialized::default("pool_size", pool_size)),
|
/// _Default:_ `None`.
|
||||||
None => figment
|
pub idle_timeout: Option<u64>,
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,119 +1,279 @@
|
||||||
use rocket::fairing::{Info, Kind};
|
use std::marker::PhantomData;
|
||||||
use rocket::futures::future::BoxFuture;
|
use std::ops::{Deref, DerefMut};
|
||||||
use rocket::http::Status;
|
|
||||||
|
use rocket::{error, info_, Build, Ignite, Phase, Rocket, Sentinel};
|
||||||
|
use rocket::fairing::{self, Fairing, Info, Kind};
|
||||||
use rocket::request::{FromRequest, Outcome, Request};
|
use rocket::request::{FromRequest, Outcome, Request};
|
||||||
|
use rocket::http::Status;
|
||||||
|
|
||||||
use rocket::yansi::Paint;
|
use rocket::yansi::Paint;
|
||||||
use rocket::{Build, Ignite, Rocket, Sentinel};
|
use rocket::figment::providers::Serialized;
|
||||||
|
|
||||||
use crate::{Error, Pool};
|
use crate::Pool;
|
||||||
|
|
||||||
/// Trait implemented to define a database connection pool.
|
/// Derivable trait which ties a database [`Pool`] with a configuration name.
|
||||||
pub trait Database: Sized + Send + Sync + 'static {
|
///
|
||||||
/// The name of this connection pool in the configuration.
|
/// This trait should rarely, if ever, be implemented manually. Instead, it
|
||||||
const NAME: &'static str;
|
/// should be derived:
|
||||||
|
///
|
||||||
/// The underlying connection type returned by this pool.
|
/// ```rust
|
||||||
/// Must implement [`Pool`].
|
/// # #[cfg(feature = "deadpool_redis")] mod _inner {
|
||||||
|
/// # use rocket::launch;
|
||||||
|
/// use rocket_db_pools::{deadpool_redis, Database};
|
||||||
|
///
|
||||||
|
/// #[derive(Database)]
|
||||||
|
/// #[database("memdb")]
|
||||||
|
/// struct Db(deadpool_redis::Pool);
|
||||||
|
///
|
||||||
|
/// #[launch]
|
||||||
|
/// fn rocket() -> _ {
|
||||||
|
/// rocket::build().attach(Db::init())
|
||||||
|
/// }
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// See the [`Database` derive](derive@crate::Database) for details.
|
||||||
|
pub trait Database: From<Self::Pool> + DerefMut<Target = Self::Pool> + Send + Sync + 'static {
|
||||||
|
/// The [`Pool`] type of connections to this database.
|
||||||
|
///
|
||||||
|
/// When `Database` is derived, this takes the value of the `Inner` type in
|
||||||
|
/// `struct Db(Inner)`.
|
||||||
type Pool: Pool;
|
type Pool: Pool;
|
||||||
|
|
||||||
/// Returns a fairing that attaches this connection pool to the server.
|
/// The configuration name for this database.
|
||||||
fn fairing() -> Fairing<Self>;
|
///
|
||||||
|
/// When `Database` is derived, this takes the value `"name"` in the
|
||||||
|
/// `#[database("name")]` attribute.
|
||||||
|
const NAME: &'static str;
|
||||||
|
|
||||||
/// Direct shared access to the underlying database pool
|
/// Returns a fairing that initializes the database and its connection pool.
|
||||||
fn pool(&self) -> &Self::Pool;
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # #[cfg(feature = "deadpool_postgres")] mod _inner {
|
||||||
|
/// # use rocket::launch;
|
||||||
|
/// use rocket_db_pools::{deadpool_postgres, Database};
|
||||||
|
///
|
||||||
|
/// #[derive(Database)]
|
||||||
|
/// #[database("pg_db")]
|
||||||
|
/// struct Db(deadpool_postgres::Pool);
|
||||||
|
///
|
||||||
|
/// #[launch]
|
||||||
|
/// fn rocket() -> _ {
|
||||||
|
/// rocket::build().attach(Db::init())
|
||||||
|
/// }
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
fn init() -> Initializer<Self> {
|
||||||
|
Initializer::new()
|
||||||
|
}
|
||||||
|
|
||||||
/// get().await returns a connection from the pool (or an error)
|
/// Returns a reference to the initialized database in `rocket`. The
|
||||||
fn get(&self) -> BoxFuture<'_, Result<Connection<Self>, <Self::Pool as Pool>::GetError>> {
|
/// initializer fairing returned by `init()` must have already executed for
|
||||||
Box::pin(async move { self.pool().get().await.map(Connection)} )
|
/// `Option` to be `Some`. This is guaranteed to be the case if the fairing
|
||||||
|
/// is attached and either:
|
||||||
|
///
|
||||||
|
/// * Rocket is in the [`Orbit`](rocket::Orbit) phase. That is, the
|
||||||
|
/// application is running. This is always the case in request guards
|
||||||
|
/// and liftoff fairings,
|
||||||
|
/// * _or_ Rocket is in the [`Build`](rocket::Build) or
|
||||||
|
/// [`Ignite`](rocket::Ignite) phase and the `Initializer` fairing has
|
||||||
|
/// already been run. This is the case in all fairing callbacks
|
||||||
|
/// corresponding to fairings attached _after_ the `Initializer`
|
||||||
|
/// fairing.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// Run database migrations in an ignite fairing. It is imperative that the
|
||||||
|
/// migration fairing be registered _after_ the `init()` fairing.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # #[cfg(feature = "sqlx_sqlite")] mod _inner {
|
||||||
|
/// # use rocket::launch;
|
||||||
|
/// use rocket::{Rocket, Build};
|
||||||
|
/// use rocket::fairing::{self, AdHoc};
|
||||||
|
///
|
||||||
|
/// use rocket_db_pools::{sqlx, Database};
|
||||||
|
///
|
||||||
|
/// #[derive(Database)]
|
||||||
|
/// #[database("sqlite_db")]
|
||||||
|
/// struct Db(sqlx::SqlitePool);
|
||||||
|
///
|
||||||
|
/// async fn run_migrations(rocket: Rocket<Build>) -> fairing::Result {
|
||||||
|
/// if let Some(db) = Db::fetch(&rocket) {
|
||||||
|
/// // run migrations using `db`. get the inner type with &db.0.
|
||||||
|
/// Ok(rocket)
|
||||||
|
/// } else {
|
||||||
|
/// Err(rocket)
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[launch]
|
||||||
|
/// fn rocket() -> _ {
|
||||||
|
/// rocket::build()
|
||||||
|
/// .attach(Db::init())
|
||||||
|
/// .attach(AdHoc::try_on_ignite("DB Migrations", run_migrations))
|
||||||
|
/// }
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
fn fetch<P: Phase>(rocket: &Rocket<P>) -> Option<&Self> {
|
||||||
|
if let Some(db) = rocket.state() {
|
||||||
|
return Some(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
let dbtype = std::any::type_name::<Self>();
|
||||||
|
let fairing = Paint::default(format!("{}::init()", dbtype)).bold();
|
||||||
|
error!("Attempted to fetch unattached database `{}`.", Paint::default(dbtype).bold());
|
||||||
|
info_!("`{}` fairing must be attached prior to using this database.", fairing);
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A connection. The underlying connection type is determined by `D`, which
|
/// A [`Fairing`] which initializes a [`Database`] and its connection pool.
|
||||||
/// must implement [`Database`].
|
///
|
||||||
|
/// A value of this type can be created for any type `D` that implements
|
||||||
|
/// [`Database`] via the [`Database::init()`] method on the type. Normally, a
|
||||||
|
/// value of this type _never_ needs to be constructed directly. This
|
||||||
|
/// documentation exists purely as a reference.
|
||||||
|
///
|
||||||
|
/// This fairing initializes a database pool. Specifically, it:
|
||||||
|
///
|
||||||
|
/// 1. Reads the configuration at `database.db_name`, where `db_name` is
|
||||||
|
/// [`Database::NAME`].
|
||||||
|
///
|
||||||
|
/// 2. Sets [`Config`](crate::Config) defaults on the configuration figment.
|
||||||
|
///
|
||||||
|
/// 3. Calls [`Pool::init()`].
|
||||||
|
///
|
||||||
|
/// 4. Stores the database instance in managed storage, retrievable via
|
||||||
|
/// [`Database::fetch()`].
|
||||||
|
///
|
||||||
|
/// The name of the fairing itself is `Initializer<D>`, with `D` replaced with
|
||||||
|
/// the type name `D` unless a name is explicitly provided via
|
||||||
|
/// [`Self::with_name()`].
|
||||||
|
pub struct Initializer<D: Database>(Option<&'static str>, PhantomData<fn() -> D>);
|
||||||
|
|
||||||
|
/// A request guard which retrieves a single connection to a [`Database`].
|
||||||
|
///
|
||||||
|
/// For a database type of `Db`, a request guard of `Connection<Db>` retrieves a
|
||||||
|
/// single connection to `Db`.
|
||||||
|
///
|
||||||
|
/// The request guard succeeds if the database was initialized by the
|
||||||
|
/// [`Initializer`] fairing and a connection is available within
|
||||||
|
/// [`connect_timeout`](crate::Config::connect_timeout) seconds.
|
||||||
|
/// * If the `Initializer` fairing was _not_ attached, the guard _fails_ with
|
||||||
|
/// status `InternalServerError`. A [`Sentinel`] guards this condition, and so
|
||||||
|
/// this type of failure is unlikely to occur. A `None` error is returned.
|
||||||
|
/// * If a connection is not available within `connect_timeout` seconds or
|
||||||
|
/// another error occurs, the gaurd _fails_ with status `ServiceUnavailable`
|
||||||
|
/// and the error is returned in `Some`.
|
||||||
|
///
|
||||||
|
/// ## Deref
|
||||||
|
///
|
||||||
|
/// A type of `Connection<Db>` dereferences, mutably and immutably, to the
|
||||||
|
/// native database connection type. The [driver table](crate#supported-drivers)
|
||||||
|
/// lists the concrete native `Deref` types.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # #[cfg(feature = "sqlx_sqlite")] mod _inner {
|
||||||
|
/// # use rocket::get;
|
||||||
|
/// # type Pool = rocket_db_pools::sqlx::SqlitePool;
|
||||||
|
/// use rocket_db_pools::{Database, Connection};
|
||||||
|
///
|
||||||
|
/// #[derive(Database)]
|
||||||
|
/// #[database("db")]
|
||||||
|
/// struct Db(Pool);
|
||||||
|
///
|
||||||
|
/// #[get("/")]
|
||||||
|
/// async fn db_op(db: Connection<Db>) {
|
||||||
|
/// // use `&*db` to get an immutable borrow to the native connection type
|
||||||
|
/// // use `&mut *db` to get a mutable borrow to the native connection type
|
||||||
|
/// }
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
pub struct Connection<D: Database>(<D::Pool as Pool>::Connection);
|
pub struct Connection<D: Database>(<D::Pool as Pool>::Connection);
|
||||||
|
|
||||||
impl<D: Database> std::ops::Deref for Connection<D> {
|
impl<D: Database> Initializer<D> {
|
||||||
type Target = <D::Pool as Pool>::Connection;
|
/// Returns a database initializer fairing for `D`.
|
||||||
fn deref(&self) -> &Self::Target {
|
///
|
||||||
&self.0
|
/// This method should never need to be called manually. See the [crate
|
||||||
|
/// docs](crate) for usage information.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(None, std::marker::PhantomData)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a database initializer fairing for `D` with name `name`.
|
||||||
|
///
|
||||||
|
/// This method should never need to be called manually. See the [crate
|
||||||
|
/// docs](crate) for usage information.
|
||||||
|
pub fn with_name(name: &'static str) -> Self {
|
||||||
|
Self(Some(name), std::marker::PhantomData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D: Database> std::ops::DerefMut for Connection<D> {
|
#[rocket::async_trait]
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
impl<D: Database> Fairing for Initializer<D> {
|
||||||
&mut self.0
|
fn info(&self) -> Info {
|
||||||
|
Info {
|
||||||
|
name: self.0.unwrap_or(std::any::type_name::<Self>()),
|
||||||
|
kind: Kind::Ignite,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn on_ignite(&self, rocket: Rocket<Build>) -> fairing::Result {
|
||||||
|
let workers: usize = rocket.figment()
|
||||||
|
.extract_inner(rocket::Config::WORKERS)
|
||||||
|
.unwrap_or_else(|_| rocket::Config::default().workers);
|
||||||
|
|
||||||
|
let figment = rocket.figment()
|
||||||
|
.focus(&format!("databases.{}", D::NAME))
|
||||||
|
.merge(Serialized::default("max_connections", workers * 4))
|
||||||
|
.merge(Serialized::default("connect_timeout", 5));
|
||||||
|
|
||||||
|
match <D::Pool>::init(&figment).await {
|
||||||
|
Ok(pool) => Ok(rocket.manage(D::from(pool))),
|
||||||
|
Err(e) => {
|
||||||
|
error!("failed to initialize database: {}", e);
|
||||||
|
Err(rocket)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rocket::async_trait]
|
#[rocket::async_trait]
|
||||||
impl<'r, D: Database> FromRequest<'r> for Connection<D> {
|
impl<'r, D: Database> FromRequest<'r> for Connection<D> {
|
||||||
type Error = Error<<D::Pool as Pool>::GetError>;
|
type Error = Option<<D::Pool as Pool>::Error>;
|
||||||
|
|
||||||
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||||
let db: &D = match req.rocket().state() {
|
match D::fetch(req.rocket()) {
|
||||||
Some(p) => p,
|
Some(db) => match db.get().await {
|
||||||
_ => {
|
|
||||||
let dbtype = Paint::default(std::any::type_name::<D>()).bold();
|
|
||||||
let fairing = Paint::default(format!("{}::fairing()", dbtype)).wrap().bold();
|
|
||||||
error!("requesting `{}` DB connection without attaching `{}`.", dbtype, fairing);
|
|
||||||
info_!("Attach `{}` to use database connection pooling.", fairing);
|
|
||||||
return Outcome::Failure((Status::InternalServerError, Error::UnattachedFairing));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match db.pool().get().await {
|
|
||||||
Ok(conn) => Outcome::Success(Connection(conn)),
|
Ok(conn) => Outcome::Success(Connection(conn)),
|
||||||
Err(e) => Outcome::Failure((Status::ServiceUnavailable, Error::Db(e))),
|
Err(e) => Outcome::Failure((Status::ServiceUnavailable, Some(e))),
|
||||||
|
},
|
||||||
|
None => Outcome::Failure((Status::InternalServerError, None)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D: Database> Sentinel for Connection<D> {
|
impl<D: Database> Sentinel for Connection<D> {
|
||||||
fn abort(rocket: &Rocket<Ignite>) -> bool {
|
fn abort(rocket: &Rocket<Ignite>) -> bool {
|
||||||
if rocket.state::<D>().is_none() {
|
D::fetch(rocket).is_none()
|
||||||
let dbtype = Paint::default(std::any::type_name::<D>()).bold();
|
|
||||||
let fairing = Paint::default(format!("{}::fairing()", dbtype)).wrap().bold();
|
|
||||||
error!("requesting `{}` DB connection without attaching `{}`.", dbtype, fairing);
|
|
||||||
info_!("Attach `{}` to use database connection pooling.", fairing);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The database fairing for pool types created with the `pool!` macro.
|
impl<D: Database> Deref for Connection<D> {
|
||||||
pub struct Fairing<D: Database>(&'static str, std::marker::PhantomData<fn(D::Pool)>);
|
type Target = <D::Pool as Pool>::Connection;
|
||||||
|
|
||||||
impl<D: Database + From<D::Pool>> Fairing<D> {
|
fn deref(&self) -> &Self::Target {
|
||||||
/// Create a new database fairing with the given constructor. This
|
&self.0
|
||||||
/// constructor will be called to create an instance of `D` after the pool
|
|
||||||
/// is initialized and before it is placed into managed state.
|
|
||||||
pub fn new(fairing_name: &'static str) -> Self {
|
|
||||||
Self(fairing_name, std::marker::PhantomData)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rocket::async_trait]
|
impl<D: Database> DerefMut for Connection<D> {
|
||||||
impl<D: Database + From<D::Pool>> rocket::fairing::Fairing for Fairing<D> {
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
fn info(&self) -> Info {
|
&mut self.0
|
||||||
Info {
|
|
||||||
name: self.0,
|
|
||||||
kind: Kind::Ignite,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn on_ignite(&self, rocket: Rocket<Build>) -> Result<Rocket<Build>, Rocket<Build>> {
|
|
||||||
let pool = match <D::Pool>::initialize(D::NAME, &rocket).await {
|
|
||||||
Ok(p) => p,
|
|
||||||
Err(e) => {
|
|
||||||
error!("error initializing database connection pool: {}", e);
|
|
||||||
return Err(rocket);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let db: D = pool.into();
|
|
||||||
|
|
||||||
Ok(rocket.manage(db))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +1,35 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use rocket::figment;
|
/// A general error type for use by [`Pool`](crate::Pool#implementing)
|
||||||
|
/// implementors and returned by the [`Connection`](crate::Connection) request
|
||||||
/// A general error type designed for the `Poolable` trait.
|
/// guard.
|
||||||
///
|
|
||||||
/// [`Pool::initialize`] can return an error for any of several reasons:
|
|
||||||
///
|
|
||||||
/// * Missing or incorrect configuration, including some syntax errors
|
|
||||||
/// * An error connecting to the database.
|
|
||||||
///
|
|
||||||
/// [`Pool::initialize`]: crate::Pool::initialize
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error<E> {
|
pub enum Error<A, B = A> {
|
||||||
/// A database-specific error occurred
|
/// An error that occured during database/pool initialization.
|
||||||
Db(E),
|
Init(A),
|
||||||
|
|
||||||
/// An error occurred in the configuration
|
/// An error that ocurred while retrieving a connection from the pool.
|
||||||
Figment(figment::Error),
|
Get(B),
|
||||||
|
|
||||||
/// Required fairing was not attached
|
/// A [`Figment`](crate::figment::Figment) configuration error.
|
||||||
UnattachedFairing,
|
Config(crate::figment::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: fmt::Display> fmt::Display for Error<E> {
|
impl<A: fmt::Display, B: fmt::Display> fmt::Display for Error<A, B> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Error::Db(e) => e.fmt(f),
|
Error::Init(e) => write!(f, "failed to initialize database: {}", e),
|
||||||
Error::Figment(e) => write!(f, "bad configuration: {}", e),
|
Error::Get(e) => write!(f, "failed to get db connection: {}", e),
|
||||||
Error::UnattachedFairing => write!(f, "required database fairing was not attached"),
|
Error::Config(e) => write!(f, "bad configuration: {}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: fmt::Debug + fmt::Display> std::error::Error for Error<E> {}
|
impl<A, B> std::error::Error for Error<A, B>
|
||||||
|
where A: fmt::Debug + fmt::Display, B: fmt::Debug + fmt::Display {}
|
||||||
|
|
||||||
impl<E> From<figment::Error> for Error<E> {
|
impl<A, B> From<crate::figment::Error> for Error<A, B> {
|
||||||
fn from(e: figment::Error) -> Self {
|
fn from(e: crate::figment::Error) -> Self {
|
||||||
Self::Figment(e)
|
Self::Config(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,382 +1,238 @@
|
||||||
//! Traits, utilities, and a macro for easy database connection pooling.
|
//! Asynchronous database driver connection pooling integration for Rocket.
|
||||||
//!
|
//!
|
||||||
//! # Overview
|
//! # Quickstart
|
||||||
//!
|
//!
|
||||||
//! This crate provides traits, utilities, and a procedural macro for
|
//! 1. Add `rocket_db_pools` as a dependency with one or more [database driver
|
||||||
//! configuring and accessing database connection pools in Rocket. A _database
|
//! features](#supported-drivers) enabled:
|
||||||
//! connection pool_ is a data structure that maintains active database
|
|
||||||
//! connections for later use in the application.
|
|
||||||
//!
|
|
||||||
//! 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](#configuration))
|
|
||||||
//! 2. Associate a Database 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 [`Pool`] trait. See
|
|
||||||
//! [Extending](#extending) for more.
|
|
||||||
//!
|
|
||||||
//! ## Example
|
|
||||||
//!
|
|
||||||
//! Before using this library, the feature corresponding to your database type
|
|
||||||
//! in `rocket_db_pools` must be enabled:
|
|
||||||
//!
|
//!
|
||||||
//! ```toml
|
//! ```toml
|
||||||
//! [dependencies.rocket_db_pools]
|
//! [dependencies.rocket_db_pools]
|
||||||
//! version = "0.1.0-dev"
|
//! version = "0.1.0-rc"
|
||||||
//! features = ["sqlx_sqlite"]
|
//! features = ["sqlx_sqlite"]
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! See [Provided](#provided) for a list of supported database and their
|
//! 2. Choose a name for your database, here `sqlite_logs`.
|
||||||
//! associated feature name.
|
//! [Configure](#configuration) _at least_ a URL for the database:
|
||||||
//!
|
|
||||||
//! 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
|
//! ```toml
|
||||||
//! [default.databases]
|
//! [default.databases.sqlite_logs]
|
||||||
//! sqlite_logs = { url = "/path/to/database.sqlite" }
|
//! url = "/path/to/database.sqlite"
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! In your application's source code, one-time:
|
//! 3. [Derive](derive@Database) [`Database`] for a unit type (`Logs` here)
|
||||||
|
//! which wraps the selected driver's [`Pool`] type (see [the driver
|
||||||
|
//! table](#supported-drivers)) and is decorated with `#[database("name")]`.
|
||||||
|
//! Attach `Type::init()` to your application's `Rocket` to initialize the
|
||||||
|
//! database pool:
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! # #[macro_use] extern crate rocket;
|
//! # #[cfg(feature = "sqlx_sqlite")] mod _inner {
|
||||||
//! # #[cfg(feature = "sqlx_sqlite")]
|
//! # use rocket::launch;
|
||||||
//! # mod test {
|
//! use rocket_db_pools::{sqlx, Database};
|
||||||
//! use rocket_db_pools::{Database, Connection, sqlx};
|
|
||||||
//!
|
//!
|
||||||
//! #[derive(Database)]
|
//! #[derive(Database)]
|
||||||
//! #[database("sqlite_logs")]
|
//! #[database("sqlite_logs")]
|
||||||
//! struct LogsDb(sqlx::SqlitePool);
|
//! struct Logs(sqlx::SqlitePool);
|
||||||
//!
|
|
||||||
//! type LogsDbConn = Connection<LogsDb>;
|
|
||||||
//!
|
//!
|
||||||
//! #[launch]
|
//! #[launch]
|
||||||
//! fn rocket() -> _ {
|
//! fn rocket() -> _ {
|
||||||
//! rocket::build().attach(LogsDb::fairing())
|
//! rocket::build().attach(Logs::init())
|
||||||
//! }
|
//! }
|
||||||
//! # } fn main() {}
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! These steps can be repeated as many times as necessary to configure
|
//! 4. Use [`Connection<Type>`](Connection) as a request guard to retrieve an
|
||||||
//! multiple databases.
|
//! active database connection, which dereferences to the native type in the
|
||||||
//!
|
//! [`Connection` deref](#supported-drivers) column.
|
||||||
//! Whenever a connection to the database is needed:
|
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! # #[macro_use] extern crate rocket;
|
//! # #[cfg(feature = "sqlx_sqlite")] mod _inner {
|
||||||
//! # #[macro_use] extern crate rocket_db_pools;
|
//! # use rocket::{get, response::Responder};
|
||||||
//! #
|
//! # use rocket_db_pools::{sqlx, Database};
|
||||||
//! # #[cfg(feature = "sqlx_sqlite")]
|
|
||||||
//! # mod test {
|
|
||||||
//! # use rocket::serde::json::Json;
|
|
||||||
//! # use rocket_db_pools::{Database, Connection, sqlx};
|
|
||||||
//! #
|
|
||||||
//! # #[derive(Database)]
|
//! # #[derive(Database)]
|
||||||
//! # #[database("sqlite_logs")]
|
//! # #[database("sqlite_logs")]
|
||||||
//! # struct LogsDb(sqlx::SqlitePool);
|
//! # struct Logs(sqlx::SqlitePool);
|
||||||
//! # type LogsDbConn = Connection<LogsDb>;
|
|
||||||
//! #
|
//! #
|
||||||
//! # type Result<T> = std::result::Result<T, ()>;
|
//! # #[derive(Responder)]
|
||||||
|
//! # struct Log(String);
|
||||||
//! #
|
//! #
|
||||||
//! #[get("/logs/<id>")]
|
//! use rocket_db_pools::Connection;
|
||||||
//! async fn get_logs(conn: LogsDbConn, id: usize) -> Result<Json<Vec<String>>> {
|
//! use rocket_db_pools::sqlx::Row;
|
||||||
//! # /*
|
//!
|
||||||
//! let logs = sqlx::query!().await?;
|
//! #[get("/<id>")]
|
||||||
//! Ok(Json(logs))
|
//! async fn read(mut db: Connection<Logs>, id: i64) -> Option<Log> {
|
||||||
//! # */
|
//! sqlx::query("SELECT content FROM logs WHERE id = ?").bind(id)
|
||||||
//! # Ok(Json(vec![]))
|
//! .fetch_one(&mut *db).await
|
||||||
|
//! .and_then(|r| Ok(Log(r.try_get(0)?)))
|
||||||
|
//! .ok()
|
||||||
//! }
|
//! }
|
||||||
//! # } fn main() {}
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! # Usage
|
//! Alternatively, use a reference to the database type as a request guard to
|
||||||
|
//! retrieve the entire pool, but note that unlike retrieving a `Connection`,
|
||||||
|
//! doing so does _not_ guarantee that a connection is available:
|
||||||
//!
|
//!
|
||||||
//! ## Configuration
|
//! ```rust
|
||||||
|
//! # #[cfg(feature = "sqlx_sqlite")] mod _inner {
|
||||||
|
//! # use rocket::{get, response::Responder};
|
||||||
|
//! # use rocket_db_pools::{sqlx, Database};
|
||||||
|
//! # #[derive(Database)]
|
||||||
|
//! # #[database("sqlite_logs")]
|
||||||
|
//! # struct Logs(sqlx::SqlitePool);
|
||||||
|
//! #
|
||||||
|
//! # #[derive(Responder)]
|
||||||
|
//! # struct Log(String);
|
||||||
|
//! #
|
||||||
|
//! use rocket_db_pools::sqlx::Row;
|
||||||
//!
|
//!
|
||||||
//! Databases can be configured as any other values. Using the default
|
//! #[get("/<id>")]
|
||||||
//! configuration provider, either via `Rocket.toml` or environment variables.
|
//! async fn read(db: &Logs, id: i64) -> Option<Log> {
|
||||||
//! You can also use a custom provider.
|
//! sqlx::query("SELECT content FROM logs WHERE id = ?").bind(id)
|
||||||
|
//! .fetch_one(&db.0).await
|
||||||
|
//! .and_then(|r| Ok(Log(r.try_get(0)?)))
|
||||||
|
//! .ok()
|
||||||
|
//! }
|
||||||
|
//! # }
|
||||||
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! ### `Rocket.toml`
|
//! # Supported Drivers
|
||||||
//!
|
//!
|
||||||
//! To configure a database via `Rocket.toml`, add a table for each database
|
//! At present, this crate supports _three_ drivers: [`deadpool`], [`sqlx`],
|
||||||
//! to the `databases` table where the key is a name of your choice. The table
|
//! and [`mongodb`]. Each driver may support multiple databases.
|
||||||
//! should have a `url` key and, optionally, a `pool_size` key. This looks as
|
//!
|
||||||
//! follows:
|
//! ## `deadpool` (v0.8)
|
||||||
|
//!
|
||||||
|
//! | Database | Feature | [`Pool`] Type | [`Connection`] Deref |
|
||||||
|
//! |----------|---------------------|-----------------------------|---------------------------------------|
|
||||||
|
//! | Postgres | `deadpool_postgres` | [`deadpool_postgres::Pool`] | [`deadpool_postgres::ClientWrapper`] |
|
||||||
|
//! | Redis | `deadpool_redis` | [`deadpool_redis::Pool`] | [`deadpool_redis::ConnectionWrapper`] |
|
||||||
|
//!
|
||||||
|
//! ## `sqlx` (v0.5)
|
||||||
|
//!
|
||||||
|
//! | Database | Feature | [`Pool`] Type | [`Connection`] Deref |
|
||||||
|
//! |----------|-----------------|----------------------|------------------------------------|
|
||||||
|
//! | Postgres | `sqlx_postgres` | [`sqlx::PgPool`] | [`sqlx::PoolConnection<Postgres>`] |
|
||||||
|
//! | MySQL | `sqlx_mysql` | [`sqlx::MySqlPool`] | [`sqlx::PoolConnection<MySql>`] |
|
||||||
|
//! | SQLite | `sqlx_sqlite` | [`sqlx::SqlitePool`] | [`sqlx::PoolConnection<Sqlite>`] |
|
||||||
|
//! | MSSQL | `sqlx_mssql` | [`sqlx::MssqlPool`] | [`sqlx::PoolConnection<Mssql>`] |
|
||||||
|
//!
|
||||||
|
//! [`sqlx::PgPool`]: https://docs.rs/sqlx/0.5/sqlx/type.PgPool.html
|
||||||
|
//! [`sqlx::MySqlPool`]: https://docs.rs/sqlx/0.5/sqlx/type.MySqlPool.html
|
||||||
|
//! [`sqlx::SqlitePool`]: https://docs.rs/sqlx/0.5/sqlx/type.SqlitePool.html
|
||||||
|
//! [`sqlx::MssqlPool`]: https://docs.rs/sqlx/0.5/sqlx/type.MssqlPool.html
|
||||||
|
//! [`sqlx::PoolConnection<Postgres>`]: https://docs.rs/sqlx/0.5/sqlx/pool/struct.PoolConnection.html
|
||||||
|
//! [`sqlx::PoolConnection<MySql>`]: https://docs.rs/sqlx/0.5/sqlx/pool/struct.PoolConnection.html
|
||||||
|
//! [`sqlx::PoolConnection<Sqlite>`]: https://docs.rs/sqlx/0.5/sqlx/pool/struct.PoolConnection.html
|
||||||
|
//! [`sqlx::PoolConnection<Mssql>`]: https://docs.rs/sqlx/0.5/sqlx/pool/struct.PoolConnection.html
|
||||||
|
//!
|
||||||
|
//! ## `mongodb` (v1)
|
||||||
|
//!
|
||||||
|
//! | Database | Feature | [`Pool`] Type and [`Connection`] Deref |
|
||||||
|
//! |----------|-----------|----------------------------------------|
|
||||||
|
//! | MongoDB | `mongodb` | [`mongodb::Client`] |
|
||||||
|
//!
|
||||||
|
//! ## Enabling Additional Driver Features
|
||||||
|
//!
|
||||||
|
//! Only the minimal features for each driver crate are enabled by
|
||||||
|
//! `rocket_db_pools`. To use additional driver functionality exposed via its
|
||||||
|
//! crate's features, you'll need to depend on the crate directly with those
|
||||||
|
//! features enabled in `Cargo.toml`:
|
||||||
//!
|
//!
|
||||||
//! ```toml
|
//! ```toml
|
||||||
//! # Option 1:
|
//! [dependencies.sqlx]
|
||||||
//! [global.databases]
|
//! version = "0.5"
|
||||||
//! sqlite_db = { url = "db.sqlite" }
|
//! default-features = false
|
||||||
|
//! features = ["macros", "offline", "migrate"]
|
||||||
//!
|
//!
|
||||||
//! # Option 2:
|
//! [dependencies.rocket_db_pools]
|
||||||
//! [global.databases.my_db]
|
//! version = "0.1.0-rc"
|
||||||
//! url = "postgres://root:root@localhost/my_db"
|
//! features = ["sqlx_sqlite"]
|
||||||
//!
|
|
||||||
//! # With a `pool_size` key:
|
|
||||||
//! [global.databases]
|
|
||||||
//! sqlite_db = { url = "db.sqlite", pool_size = 20 }
|
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! Most databases use the default [`Config`] type, for which one key is required:
|
//! # Configuration
|
||||||
//!
|
//!
|
||||||
//! * `url` - the URl to the database
|
//! Configuration for a database named `db_name` is deserialized from a
|
||||||
|
//! `databases.db_name` configuration parameter into a [`Config`] structure via
|
||||||
|
//! Rocket's [configuration facilities](rocket::config). By default,
|
||||||
|
//! configuration can be provided in `Rocket.toml`:
|
||||||
//!
|
//!
|
||||||
//! And one optional key is accepted:
|
//! ```toml
|
||||||
|
//! [default.databases.db_name]
|
||||||
|
//! url = "db.sqlite"
|
||||||
//!
|
//!
|
||||||
//! * `pool_size` - the size of the pool, i.e., the number of connections to
|
//! # only `url` is required. the rest have defaults and are thus optional
|
||||||
//! pool (defaults to the configured number of workers * 4)
|
//! min_connections = 64
|
||||||
//! TODO: currently ignored by most `Pool` implementations.
|
//! max_connections = 1024
|
||||||
//!
|
//! connect_timeout = 5
|
||||||
//! Different options may be required or supported by other adapters, according
|
//! idle_timeout = 120
|
||||||
//! to the type specified by [`Pool::Config`].
|
|
||||||
//!
|
|
||||||
//! ### Procedurally
|
|
||||||
//!
|
|
||||||
//! Databases can also be configured procedurally via `rocket::custom()`.
|
|
||||||
//! The example below does just this:
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! # #[cfg(feature = "sqlx_sqlite")] {
|
|
||||||
//! # use rocket::launch;
|
|
||||||
//! use rocket::figment::{value::{Map, Value}, util::map};
|
|
||||||
//!
|
|
||||||
//! #[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
|
//! Or via environment variables:
|
||||||
//!
|
//!
|
||||||
//! Lastly, databases can be configured via environment variables by specifying
|
//! ```sh
|
||||||
//! the `databases` table as detailed in the [Environment Variables
|
//! ROCKET_DATABASES='{db_name={url="db.sqlite",idle_timeout=120}}'
|
||||||
//! 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
|
//! See [`Config`] for details on configuration parameters.
|
||||||
//! as well by comma separating them:
|
|
||||||
//!
|
//!
|
||||||
//! ```bash
|
//! **Note:** `deadpool` drivers do not support and thus ignore the
|
||||||
//! ROCKET_DATABASES='{my_db={url="db.sqlite"},my_pg_db={url="postgres://root:root@localhost/my_pg_db"}}'
|
//! `min_connections` value.
|
||||||
//! ```
|
|
||||||
//!
|
//!
|
||||||
//! ## Database Types
|
//! ## Driver Defaults
|
||||||
//!
|
//!
|
||||||
//! Once a database has been configured, the `#[derive(Database)]` macro can be
|
//! Some drivers provide configuration defaults different from the underyling
|
||||||
//! used to tie a type in your application to a configured database. The derive
|
//! database's defaults. A best-effort attempt is made to document those
|
||||||
//! accepts a single attribute, `#[database("name")]` that indicates the
|
//! differences below:
|
||||||
//! name of the database. This corresponds to the database name set as the
|
|
||||||
//! database's configuration key.
|
|
||||||
//!
|
//!
|
||||||
//! The [`Database`] trait provides a method, `fairing()`, which places an
|
//! * `sqlx_sqlite`
|
||||||
//! instance of the decorated type in managed state; thus, the database pool can
|
|
||||||
//! be accessed with a `&State<DbType>` request guard.
|
|
||||||
//!
|
//!
|
||||||
//! The [`Connection`] type also implements [`FromRequest`], allowing it to be
|
//! - foreign keys : `enabled`
|
||||||
//! used as a request guard. This implementation retrieves a connection from the
|
//! - journal mode : `WAL`
|
||||||
//! database pool or fails with a `Status::ServiceUnavailable` if connecting to
|
//! - create-missing : `enabled`
|
||||||
//! the database fails or times out.
|
//! - synchronous : `full` (even when `WAL`)
|
||||||
|
//! - busy timeout : `connection_timeout`
|
||||||
//!
|
//!
|
||||||
//! The derive can only be applied to unit-like structs with one type. The
|
//! * `sqlx_postgres`
|
||||||
//! internal type of the structure must implement [`Pool`].
|
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! - sslmode : `prefer`
|
||||||
//! # #[macro_use] extern crate rocket_db_pools;
|
//! - statement-cache-capacity : `100`
|
||||||
//! # #[cfg(feature = "sqlx_sqlite")]
|
//! - user : result of `whoami`
|
||||||
//! # mod test {
|
|
||||||
//! use rocket_db_pools::{Database, sqlx};
|
|
||||||
//!
|
//!
|
||||||
//! #[derive(Database)]
|
//! * `sqlx_mysql`
|
||||||
//! #[database("my_db")]
|
|
||||||
//! struct MyDatabase(sqlx::SqlitePool);
|
|
||||||
//! # }
|
|
||||||
//! ```
|
|
||||||
//!
|
//!
|
||||||
//! Other databases can be used by specifying their respective [`Pool`] type:
|
//! - sslmode : `PREFERRED`
|
||||||
|
//! - statement-cache-capacity : `100`
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! # Extending
|
||||||
//! # #[macro_use] extern crate rocket_db_pools;
|
|
||||||
//! # #[cfg(feature = "deadpool_postgres")]
|
|
||||||
//! # mod test {
|
|
||||||
//! use rocket_db_pools::{Database, deadpool_postgres};
|
|
||||||
//!
|
//!
|
||||||
//! #[derive(Database)]
|
//! Any database driver can implement support for this libary by implementing
|
||||||
//! #[database("my_pg_db")]
|
//! the [`Pool`] trait.
|
||||||
//! struct MyPgDatabase(deadpool_postgres::Pool);
|
|
||||||
//! # }
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! The fairing returned from the `fairing()` method _must_ be attached for the
|
|
||||||
//! request guards to succeed. Putting the pieces together, a use of
|
|
||||||
//! `#[derive(Database)]` looks as follows:
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! # #[macro_use] extern crate rocket;
|
|
||||||
//! # #[macro_use] extern crate rocket_db_pools;
|
|
||||||
//! #
|
|
||||||
//! # #[cfg(feature = "sqlx_sqlite")] {
|
|
||||||
//! # use rocket::figment::{value::{Map, Value}, util::map};
|
|
||||||
//! use rocket_db_pools::{Database, sqlx};
|
|
||||||
//!
|
|
||||||
//! #[derive(Database)]
|
|
||||||
//! #[database("my_db")]
|
|
||||||
//! struct MyDatabase(sqlx::SqlitePool);
|
|
||||||
//!
|
|
||||||
//! #[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, access your type via `State` in a handler to access
|
|
||||||
//! the database connection pool:
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! # #[macro_use] extern crate rocket;
|
|
||||||
//! # #[macro_use] extern crate rocket_db_pools;
|
|
||||||
//! #
|
|
||||||
//! # #[cfg(feature = "sqlx_sqlite")]
|
|
||||||
//! # mod test {
|
|
||||||
//! # use rocket_db_pools::{Database, Connection, sqlx};
|
|
||||||
//! use rocket::State;
|
|
||||||
//!
|
|
||||||
//! #[derive(Database)]
|
|
||||||
//! #[database("my_db")]
|
|
||||||
//! struct MyDatabase(sqlx::SqlitePool);
|
|
||||||
//!
|
|
||||||
//! #[get("/")]
|
|
||||||
//! fn my_handler(conn: &State<MyDatabase>) {
|
|
||||||
//! // ...
|
|
||||||
//! }
|
|
||||||
//! # }
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! Alternatively, access a single connection directly via the `Connection`
|
|
||||||
//! request guard:
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! # #[macro_use] extern crate rocket;
|
|
||||||
//! # #[macro_use] extern crate rocket_db_pools;
|
|
||||||
//! #
|
|
||||||
//! # #[cfg(feature = "sqlx_sqlite")]
|
|
||||||
//! # mod test {
|
|
||||||
//! # use rocket_db_pools::{Database, Connection, sqlx};
|
|
||||||
//! # type Data = ();
|
|
||||||
//! #[derive(Database)]
|
|
||||||
//! #[database("my_db")]
|
|
||||||
//! struct MyDatabase(sqlx::SqlitePool);
|
|
||||||
//!
|
|
||||||
//! type MyConnection = Connection<MyDatabase>;
|
|
||||||
//!
|
|
||||||
//! async fn load_from_db(conn: &mut sqlx::SqliteConnection) -> Data {
|
|
||||||
//! // Do something with connection, return some data.
|
|
||||||
//! # ()
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! #[get("/")]
|
|
||||||
//! async fn my_handler(mut conn: MyConnection) -> Data {
|
|
||||||
//! load_from_db(&mut conn).await
|
|
||||||
//! }
|
|
||||||
//! # }
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! # Database Support
|
|
||||||
//!
|
|
||||||
//! Built-in support is provided for many popular databases and drivers. Support
|
|
||||||
//! can be easily extended by [`Pool`] implementations.
|
|
||||||
//!
|
|
||||||
//! ## Provided
|
|
||||||
//!
|
|
||||||
//! The list below includes all presently supported database adapters and their
|
|
||||||
//! corresponding [`Pool`] type.
|
|
||||||
//!
|
|
||||||
// Note: Keep this table in sync with site/guite/6-state.md
|
|
||||||
//! | Kind | Driver | Version | `Pool` Type | Feature |
|
|
||||||
//! |----------|-----------------------|-----------|--------------------------------|------------------------|
|
|
||||||
//! | MySQL | [sqlx] | `0.5` | [`sqlx::MySqlPool`] | `sqlx_mysql` |
|
|
||||||
//! | Postgres | [sqlx] | `0.5` | [`sqlx::PgPool`] | `sqlx_postgres` |
|
|
||||||
//! | Sqlite | [sqlx] | `0.5` | [`sqlx::SqlitePool`] | `sqlx_sqlite` |
|
|
||||||
//! | Mongodb | [mongodb] | `2.0.0-beta` | [`mongodb::Client`] | `mongodb` |
|
|
||||||
//! | MySQL | [mysql_async] | `0.27` | [`mysql_async::Pool`] | `mysql_async` |
|
|
||||||
//! | Postgres | [deadpool-postgres] | `0.8` | [`deadpool_postgres::Pool`] | `deadpool_postgres` |
|
|
||||||
//! | Redis | [deadpool-redis] | `0.8` | [`deadpool_redis::Pool`] | `deadpool_redis` |
|
|
||||||
//!
|
|
||||||
//! [sqlx]: https://docs.rs/sqlx/0.5/sqlx/
|
|
||||||
//! [deadpool-postgres]: https://docs.rs/deadpool-postgres/0.8/deadpool_postgres/
|
|
||||||
//! [deadpool-redis]: https://docs.rs/deadpool-redis/0.8/deadpool_redis/
|
|
||||||
//! [mongodb]: https://docs.rs/mongodb/2.0.0-beta/mongodb/index.html
|
|
||||||
//! [mysql_async]: https://docs.rs/mysql_async/0.27/mysql_async/
|
|
||||||
//!
|
|
||||||
//! The above table lists all the supported database adapters in this library.
|
|
||||||
//! In order to use particular `Pool` 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
|
|
||||||
//! "`Pool` Type" column.
|
|
||||||
//!
|
|
||||||
//! ## Extending
|
|
||||||
//!
|
|
||||||
//! Extending Rocket's support to your own custom database adapter is as easy as
|
|
||||||
//! implementing the [`Pool`] trait. See the documentation for [`Pool`]
|
|
||||||
//! for more details on how to implement it.
|
|
||||||
//!
|
|
||||||
//! [`FromRequest`]: rocket::request::FromRequest
|
|
||||||
//! [request guards]: rocket::request::FromRequest
|
|
||||||
//! [`Database`]: crate::Database
|
|
||||||
//! [`Pool`]: crate::Pool
|
|
||||||
|
|
||||||
#![doc(html_root_url = "https://api.rocket.rs/master/rocket_db_pools")]
|
#![doc(html_root_url = "https://api.rocket.rs/master/rocket_db_pools")]
|
||||||
#![doc(html_favicon_url = "https://rocket.rs/images/favicon.ico")]
|
#![doc(html_favicon_url = "https://rocket.rs/images/favicon.ico")]
|
||||||
#![doc(html_logo_url = "https://rocket.rs/images/logo-boxed.png")]
|
#![doc(html_logo_url = "https://rocket.rs/images/logo-boxed.png")]
|
||||||
|
|
||||||
#[doc(hidden)]
|
#![deny(missing_docs)]
|
||||||
#[macro_use]
|
|
||||||
pub extern crate rocket;
|
|
||||||
|
|
||||||
|
/// Re-export of the `figment` crate.
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use rocket::figment;
|
||||||
|
|
||||||
|
pub use rocket;
|
||||||
#[cfg(feature = "deadpool_postgres")] pub use deadpool_postgres;
|
#[cfg(feature = "deadpool_postgres")] pub use deadpool_postgres;
|
||||||
#[cfg(feature = "deadpool_redis")] pub use deadpool_redis;
|
#[cfg(feature = "deadpool_redis")] pub use deadpool_redis;
|
||||||
#[cfg(feature = "mysql_async")] pub use mysql_async;
|
|
||||||
#[cfg(feature = "mongodb")] pub use mongodb;
|
#[cfg(feature = "mongodb")] pub use mongodb;
|
||||||
#[cfg(feature = "sqlx")] pub use sqlx;
|
#[cfg(feature = "sqlx")] pub use sqlx;
|
||||||
|
|
||||||
mod config;
|
|
||||||
mod database;
|
mod database;
|
||||||
mod error;
|
mod error;
|
||||||
mod pool;
|
mod pool;
|
||||||
|
mod config;
|
||||||
|
|
||||||
pub use self::config::Config;
|
pub use self::database::{Connection, Database, Initializer};
|
||||||
pub use self::database::{Connection, Database, Fairing};
|
|
||||||
pub use self::error::Error;
|
pub use self::error::Error;
|
||||||
pub use self::pool::Pool;
|
pub use self::pool::Pool;
|
||||||
|
pub use self::config::Config;
|
||||||
|
|
||||||
pub use rocket_db_pools_codegen::*;
|
pub use rocket_db_pools_codegen::*;
|
||||||
|
|
|
@ -1,61 +1,115 @@
|
||||||
use rocket::async_trait;
|
use rocket::figment::Figment;
|
||||||
use rocket::{Build, Rocket};
|
|
||||||
|
|
||||||
use crate::{Config, Error};
|
#[allow(unused_imports)]
|
||||||
|
use {std::time::Duration, crate::{Error, Config}};
|
||||||
|
|
||||||
/// This trait is implemented on connection pool types that can be used with the
|
/// Generic [`Database`](crate::Database) driver connection pool trait.
|
||||||
/// [`Database`] derive macro.
|
|
||||||
///
|
///
|
||||||
/// `Pool` determines how the connection pool is initialized from configuration,
|
/// This trait provides a generic interface to various database pooling
|
||||||
/// such as a connection string and optional pool size, along with the returned
|
/// implementations in the Rust ecosystem. It can be implemented by anyone, but
|
||||||
/// `Connection` type.
|
/// this crate provides implementations for common drivers.
|
||||||
///
|
///
|
||||||
/// Implementations of this trait should use `async_trait`.
|
/// **Implementations of this trait outside of this crate should be rare. You
|
||||||
|
/// _do not_ need to implement this trait or understand its specifics to use
|
||||||
|
/// this crate.**
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Async Trait
|
||||||
///
|
///
|
||||||
|
/// [`Pool`] is an _async_ trait. Implementations of `Pool` must be decorated
|
||||||
|
/// with an attribute of `#[async_trait]`:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # #[macro_use] extern crate rocket;
|
||||||
|
/// use rocket::figment::Figment;
|
||||||
|
/// use rocket_db_pools::Pool;
|
||||||
|
///
|
||||||
|
/// # struct MyPool;
|
||||||
|
/// # type Connection = ();
|
||||||
|
/// # type Error = std::convert::Infallible;
|
||||||
|
/// #[rocket::async_trait]
|
||||||
|
/// impl Pool for MyPool {
|
||||||
|
/// type Connection = Connection;
|
||||||
|
///
|
||||||
|
/// type Error = Error;
|
||||||
|
///
|
||||||
|
/// async fn init(figment: &Figment) -> Result<Self, Self::Error> {
|
||||||
|
/// todo!("initialize and return an instance of the pool");
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// async fn get(&self) -> Result<Self::Connection, Self::Error> {
|
||||||
|
/// todo!("fetch one connection from the pool");
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
/// use rocket::{Build, Rocket};
|
|
||||||
///
|
///
|
||||||
/// #[derive(Debug)]
|
/// ## Implementing
|
||||||
/// struct Error { /* ... */ }
|
///
|
||||||
/// # impl std::fmt::Display for Error {
|
/// Implementations of `Pool` typically trace the following outline:
|
||||||
/// # fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
///
|
||||||
/// # unimplemented!("example")
|
/// 1. The `Error` associated type is set to [`Error`].
|
||||||
|
///
|
||||||
|
/// 2. A [`Config`] is [extracted](Figment::extract()) from the `figment`
|
||||||
|
/// passed to init.
|
||||||
|
///
|
||||||
|
/// 3. The pool is initialized and returned in `init()`, wrapping
|
||||||
|
/// initialization errors in [`Error::Init`].
|
||||||
|
///
|
||||||
|
/// 4. A connection is retrieved in `get()`, wrapping errors in
|
||||||
|
/// [`Error::Get`].
|
||||||
|
///
|
||||||
|
/// Concretely, this looks like:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use rocket::figment::Figment;
|
||||||
|
/// use rocket_db_pools::{Pool, Config, Error};
|
||||||
|
/// #
|
||||||
|
/// # type InitError = std::convert::Infallible;
|
||||||
|
/// # type GetError = std::convert::Infallible;
|
||||||
|
/// # type Connection = ();
|
||||||
|
/// #
|
||||||
|
/// # struct MyPool(Config);
|
||||||
|
/// # impl MyPool {
|
||||||
|
/// # fn new(c: Config) -> Result<Self, InitError> {
|
||||||
|
/// # Ok(Self(c))
|
||||||
|
/// # }
|
||||||
|
/// #
|
||||||
|
/// # fn acquire(&self) -> Result<Connection, GetError> {
|
||||||
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// # }
|
/// # }
|
||||||
/// # impl std::error::Error for Error { }
|
|
||||||
///
|
|
||||||
/// struct Pool { /* ... */ }
|
|
||||||
/// struct Connection { /* .. */ }
|
|
||||||
///
|
///
|
||||||
/// #[rocket::async_trait]
|
/// #[rocket::async_trait]
|
||||||
/// impl rocket_db_pools::Pool for Pool {
|
/// impl Pool for MyPool {
|
||||||
/// type Connection = Connection;
|
/// type Connection = Connection;
|
||||||
/// type InitError = Error;
|
|
||||||
/// type GetError = Error;
|
|
||||||
///
|
///
|
||||||
/// async fn initialize(db_name: &str, rocket: &Rocket<Build>)
|
/// type Error = Error<InitError, GetError>;
|
||||||
/// -> Result<Self, rocket_db_pools::Error<Self::InitError>>
|
///
|
||||||
/// {
|
/// async fn init(figment: &Figment) -> Result<Self, Self::Error> {
|
||||||
/// unimplemented!("example")
|
/// // Extract the config from `figment`.
|
||||||
|
/// let config: Config = figment.extract()?;
|
||||||
|
///
|
||||||
|
/// // Read config values, initialize `MyPool`. Map errors of type
|
||||||
|
/// // `InitError` to `Error<InitError, _>` with `Error::Init`.
|
||||||
|
/// let pool = MyPool::new(config).map_err(Error::Init)?;
|
||||||
|
///
|
||||||
|
/// // Return the fully intialized pool.
|
||||||
|
/// Ok(pool)
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// async fn get(&self) -> Result<Connection, Self::GetError> {
|
/// async fn get(&self) -> Result<Self::Connection, Self::Error> {
|
||||||
/// unimplemented!("example")
|
/// // Get one connection from the pool, here via an `acquire()` method.
|
||||||
|
/// // Map errors of type `GetError` to `Error<_, GetError>`.
|
||||||
|
/// self.acquire().map_err(Error::Get)
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[async_trait]
|
#[rocket::async_trait]
|
||||||
pub trait Pool: Sized + Send + Sync + 'static {
|
pub trait Pool: Sized + Send + Sync + 'static {
|
||||||
/// The type returned by get().
|
/// The connection type managed by this pool, returned by [`Self::get()`].
|
||||||
type Connection;
|
type Connection;
|
||||||
|
|
||||||
/// The error type returned by `initialize`.
|
/// The error type returned by [`Self::init()`] and [`Self::get()`].
|
||||||
type InitError: std::error::Error;
|
type Error: std::error::Error;
|
||||||
|
|
||||||
/// The error type returned by `get`.
|
|
||||||
type GetError: std::error::Error;
|
|
||||||
|
|
||||||
/// Constructs a pool from a [Value](rocket::figment::value::Value).
|
/// Constructs a pool from a [Value](rocket::figment::value::Value).
|
||||||
///
|
///
|
||||||
|
@ -69,219 +123,136 @@ pub trait Pool: Sized + Send + Sync + 'static {
|
||||||
/// This method returns an error if the configuration is not compatible, or
|
/// This method returns an error if the configuration is not compatible, or
|
||||||
/// if creating a pool failed due to an unavailable database server,
|
/// if creating a pool failed due to an unavailable database server,
|
||||||
/// insufficient resources, or another database-specific error.
|
/// insufficient resources, or another database-specific error.
|
||||||
async fn initialize(db_name: &str, rocket: &Rocket<Build>)
|
async fn init(figment: &Figment) -> Result<Self, Self::Error>;
|
||||||
-> Result<Self, Error<Self::InitError>>;
|
|
||||||
|
|
||||||
/// Asynchronously gets a connection from the factory or pool.
|
/// Asynchronously retrieves a connection from the factory or pool.
|
||||||
///
|
///
|
||||||
/// ## Errors
|
/// ## Errors
|
||||||
///
|
///
|
||||||
/// This method returns an error if a connection could not be retrieved,
|
/// This method returns an error if a connection could not be retrieved,
|
||||||
/// such as a preconfigured timeout elapsing or when the database server is
|
/// such as a preconfigured timeout elapsing or when the database server is
|
||||||
/// unavailable.
|
/// unavailable.
|
||||||
async fn get(&self) -> Result<Self::Connection, Self::GetError>;
|
async fn get(&self) -> Result<Self::Connection, Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "deadpool")]
|
||||||
|
mod deadpool_postgres {
|
||||||
|
use deadpool::managed::{Manager, Pool, PoolConfig, PoolError, Object};
|
||||||
|
use super::{Duration, Error, Config, Figment};
|
||||||
|
|
||||||
|
pub trait DeadManager: Manager + Sized + Send + Sync + 'static {
|
||||||
|
fn new(config: &Config) -> Result<Self, Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "deadpool_postgres")]
|
#[cfg(feature = "deadpool_postgres")]
|
||||||
#[async_trait]
|
impl DeadManager for deadpool_postgres::Manager {
|
||||||
impl Pool for deadpool_postgres::Pool {
|
fn new(config: &Config) -> Result<Self, Self::Error> {
|
||||||
type Connection = deadpool_postgres::Client;
|
Ok(Self::new(config.url.parse()?, deadpool_postgres::tokio_postgres::NoTls))
|
||||||
type InitError = deadpool_postgres::tokio_postgres::Error;
|
|
||||||
type GetError = deadpool_postgres::PoolError;
|
|
||||||
|
|
||||||
async fn initialize(db_name: &str, rocket: &Rocket<Build>)
|
|
||||||
-> std::result::Result<Self, Error<Self::InitError>>
|
|
||||||
{
|
|
||||||
let config = Config::from(db_name, rocket)?;
|
|
||||||
let manager = deadpool_postgres::Manager::new(
|
|
||||||
config.url.parse().map_err(Error::Db)?,
|
|
||||||
// TODO: add TLS support in config
|
|
||||||
deadpool_postgres::tokio_postgres::NoTls,
|
|
||||||
);
|
|
||||||
let mut pool_config = deadpool_postgres::PoolConfig::new(config.pool_size as usize);
|
|
||||||
pool_config.timeouts.wait = Some(std::time::Duration::from_secs(config.timeout.into()));
|
|
||||||
|
|
||||||
Ok(deadpool_postgres::Pool::from_config(manager, pool_config))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get(&self) -> Result<Self::Connection, Self::GetError> {
|
|
||||||
self.get().await
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "deadpool_redis")]
|
#[cfg(feature = "deadpool_redis")]
|
||||||
#[async_trait]
|
impl DeadManager for deadpool_redis::Manager {
|
||||||
impl Pool for deadpool_redis::Pool {
|
fn new(config: &Config) -> Result<Self, Self::Error> {
|
||||||
type Connection = deadpool_redis::ConnectionWrapper;
|
Self::new(config.url.as_str())
|
||||||
type InitError = deadpool_redis::redis::RedisError;
|
}
|
||||||
type GetError = deadpool_redis::PoolError;
|
|
||||||
|
|
||||||
async fn initialize(db_name: &str, rocket: &Rocket<Build>)
|
|
||||||
-> std::result::Result<Self, Error<Self::InitError>>
|
|
||||||
{
|
|
||||||
let config = Config::from(db_name, rocket)?;
|
|
||||||
let manager = deadpool_redis::Manager::new(config.url).map_err(Error::Db)?;
|
|
||||||
|
|
||||||
let mut pool_config = deadpool_redis::PoolConfig::new(config.pool_size as usize);
|
|
||||||
pool_config.timeouts.wait = Some(std::time::Duration::from_secs(config.timeout.into()));
|
|
||||||
|
|
||||||
Ok(deadpool_redis::Pool::from_config(manager, pool_config))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get(&self) -> Result<Self::Connection, Self::GetError> {
|
#[rocket::async_trait]
|
||||||
self.get().await
|
impl<M: DeadManager, C: From<Object<M>>> crate::Pool for Pool<M, C>
|
||||||
|
where M::Type: Send, C: Send + Sync + 'static, M::Error: std::error::Error
|
||||||
|
{
|
||||||
|
type Error = Error<M::Error, PoolError<M::Error>>;
|
||||||
|
|
||||||
|
type Connection = C;
|
||||||
|
|
||||||
|
async fn init(figment: &Figment) -> Result<Self, Self::Error> {
|
||||||
|
let config: Config = figment.extract()?;
|
||||||
|
let manager = M::new(&config).map_err(Error::Init)?;
|
||||||
|
|
||||||
|
let mut pool = PoolConfig::new(config.max_connections);
|
||||||
|
pool.timeouts.create = Some(Duration::from_secs(config.connect_timeout));
|
||||||
|
pool.timeouts.wait = Some(Duration::from_secs(config.connect_timeout));
|
||||||
|
pool.timeouts.recycle = config.idle_timeout.map(Duration::from_secs);
|
||||||
|
pool.runtime = deadpool::Runtime::Tokio1;
|
||||||
|
Ok(Pool::from_config(manager, pool))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get(&self) -> Result<Self::Connection, Self::Error> {
|
||||||
|
self.get().await.map_err(Error::Get)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "sqlx")]
|
||||||
|
mod sqlx {
|
||||||
|
use sqlx::ConnectOptions;
|
||||||
|
use super::{Duration, Error, Config, Figment};
|
||||||
|
|
||||||
|
type Options<D> = <<D as sqlx::Database>::Connection as sqlx::Connection>::Options;
|
||||||
|
|
||||||
|
// Provide specialized configuration for particular databases.
|
||||||
|
fn specialize(__options: &mut dyn std::any::Any, __config: &Config) {
|
||||||
|
#[cfg(feature = "sqlx_sqlite")]
|
||||||
|
if let Some(o) = __options.downcast_mut::<sqlx::sqlite::SqliteConnectOptions>() {
|
||||||
|
*o = std::mem::take(o)
|
||||||
|
.busy_timeout(Duration::from_secs(__config.connect_timeout))
|
||||||
|
.create_if_missing(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rocket::async_trait]
|
||||||
|
impl<D: sqlx::Database> crate::Pool for sqlx::Pool<D> {
|
||||||
|
type Error = Error<sqlx::Error>;
|
||||||
|
|
||||||
|
type Connection = sqlx::pool::PoolConnection<D>;
|
||||||
|
|
||||||
|
async fn init(figment: &Figment) -> Result<Self, Self::Error> {
|
||||||
|
let config = figment.extract::<Config>()?;
|
||||||
|
let mut opts = config.url.parse::<Options<D>>().map_err(Error::Init)?;
|
||||||
|
opts.disable_statement_logging();
|
||||||
|
specialize(&mut opts, &config);
|
||||||
|
|
||||||
|
sqlx::pool::PoolOptions::new()
|
||||||
|
.max_connections(config.max_connections as u32)
|
||||||
|
.connect_timeout(Duration::from_secs(config.connect_timeout))
|
||||||
|
.idle_timeout(config.idle_timeout.map(Duration::from_secs))
|
||||||
|
.min_connections(config.min_connections.unwrap_or_default())
|
||||||
|
.connect_with(opts)
|
||||||
|
.await
|
||||||
|
.map_err(Error::Init)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get(&self) -> Result<Self::Connection, Self::Error> {
|
||||||
|
self.acquire().await.map_err(Error::Get)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "mongodb")]
|
#[cfg(feature = "mongodb")]
|
||||||
#[async_trait]
|
mod mongodb {
|
||||||
impl Pool for mongodb::Client {
|
use mongodb::{Client, options::ClientOptions};
|
||||||
type Connection = mongodb::Client;
|
use super::{Duration, Error, Config, Figment};
|
||||||
type InitError = mongodb::error::Error;
|
|
||||||
type GetError = std::convert::Infallible;
|
|
||||||
|
|
||||||
async fn initialize(db_name: &str, rocket: &Rocket<Build>)
|
#[rocket::async_trait]
|
||||||
-> std::result::Result<Self, Error<Self::InitError>>
|
impl crate::Pool for Client {
|
||||||
{
|
type Error = Error<mongodb::error::Error, std::convert::Infallible>;
|
||||||
let config = Config::from(db_name, rocket)?;
|
|
||||||
let mut options = mongodb::options::ClientOptions::parse(&config.url)
|
|
||||||
.await
|
|
||||||
.map_err(Error::Db)?;
|
|
||||||
options.max_pool_size = Some(config.pool_size);
|
|
||||||
options.wait_queue_timeout = Some(std::time::Duration::from_secs(config.timeout.into()));
|
|
||||||
|
|
||||||
mongodb::Client::with_options(options).map_err(Error::Db)
|
type Connection = Client;
|
||||||
|
|
||||||
|
async fn init(figment: &Figment) -> Result<Self, Self::Error> {
|
||||||
|
let config = figment.extract::<Config>()?;
|
||||||
|
let mut opts = ClientOptions::parse(&config.url).await.map_err(Error::Init)?;
|
||||||
|
opts.min_pool_size = config.min_connections;
|
||||||
|
opts.max_pool_size = Some(config.max_connections as u32);
|
||||||
|
opts.max_idle_time = config.idle_timeout.map(Duration::from_secs);
|
||||||
|
opts.wait_queue_timeout = Some(Duration::from_secs(config.connect_timeout));
|
||||||
|
opts.connect_timeout = Some(Duration::from_secs(config.connect_timeout));
|
||||||
|
Client::with_options(opts).map_err(Error::Init)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get(&self) -> Result<Self::Connection, Self::GetError> {
|
async fn get(&self) -> Result<Self::Connection, Self::Error> {
|
||||||
Ok(self.clone())
|
Ok(self.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "mysql_async")]
|
|
||||||
#[async_trait]
|
|
||||||
impl Pool for mysql_async::Pool {
|
|
||||||
type Connection = mysql_async::Conn;
|
|
||||||
type InitError = mysql_async::Error;
|
|
||||||
type GetError = mysql_async::Error;
|
|
||||||
|
|
||||||
async fn initialize(db_name: &str, rocket: &Rocket<Build>)
|
|
||||||
-> std::result::Result<Self, Error<Self::InitError>>
|
|
||||||
{
|
|
||||||
use rocket::figment::{self, error::{Actual, Kind}};
|
|
||||||
|
|
||||||
let config = Config::from(db_name, rocket)?;
|
|
||||||
let original_opts = mysql_async::Opts::from_url(&config.url)
|
|
||||||
.map_err(|_| figment::Error::from(Kind::InvalidValue(
|
|
||||||
Actual::Str(config.url.to_string()),
|
|
||||||
"mysql connection string".to_string()
|
|
||||||
)))?;
|
|
||||||
|
|
||||||
let new_pool_opts = original_opts.pool_opts()
|
|
||||||
.clone()
|
|
||||||
.with_constraints(
|
|
||||||
mysql_async::PoolConstraints::new(0, config.pool_size as usize)
|
|
||||||
.expect("usize can't be < 0")
|
|
||||||
);
|
|
||||||
|
|
||||||
// TODO: timeout
|
|
||||||
|
|
||||||
let opts = mysql_async::OptsBuilder::from_opts(original_opts)
|
|
||||||
.pool_opts(new_pool_opts);
|
|
||||||
|
|
||||||
Ok(mysql_async::Pool::new(opts))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get(&self) -> std::result::Result<Self::Connection, Self::GetError> {
|
|
||||||
self.get_conn().await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "sqlx_mysql")]
|
|
||||||
#[async_trait]
|
|
||||||
impl Pool for sqlx::MySqlPool {
|
|
||||||
type Connection = sqlx::pool::PoolConnection<sqlx::MySql>;
|
|
||||||
type InitError = sqlx::Error;
|
|
||||||
type GetError = sqlx::Error;
|
|
||||||
|
|
||||||
async fn initialize(db_name: &str, rocket: &Rocket<Build>)
|
|
||||||
-> std::result::Result<Self, Error<Self::InitError>>
|
|
||||||
{
|
|
||||||
use sqlx::ConnectOptions;
|
|
||||||
|
|
||||||
let config = Config::from(db_name, rocket)?;
|
|
||||||
let mut opts = config.url.parse::<sqlx::mysql::MySqlConnectOptions>()
|
|
||||||
.map_err(Error::Db)?;
|
|
||||||
opts.disable_statement_logging();
|
|
||||||
sqlx::pool::PoolOptions::new()
|
|
||||||
.max_connections(config.pool_size)
|
|
||||||
.connect_timeout(std::time::Duration::from_secs(config.timeout.into()))
|
|
||||||
.connect_with(opts)
|
|
||||||
.await
|
|
||||||
.map_err(Error::Db)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get(&self) -> std::result::Result<Self::Connection, Self::GetError> {
|
|
||||||
self.acquire().await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "sqlx_postgres")]
|
|
||||||
#[async_trait]
|
|
||||||
impl Pool for sqlx::PgPool {
|
|
||||||
type Connection = sqlx::pool::PoolConnection<sqlx::Postgres>;
|
|
||||||
type InitError = sqlx::Error;
|
|
||||||
type GetError = sqlx::Error;
|
|
||||||
|
|
||||||
async fn initialize(db_name: &str, rocket: &Rocket<Build>)
|
|
||||||
-> std::result::Result<Self, Error<Self::InitError>>
|
|
||||||
{
|
|
||||||
use sqlx::ConnectOptions;
|
|
||||||
|
|
||||||
let config = Config::from(db_name, rocket)?;
|
|
||||||
let mut opts = config.url.parse::<sqlx::postgres::PgConnectOptions>()
|
|
||||||
.map_err(Error::Db)?;
|
|
||||||
opts.disable_statement_logging();
|
|
||||||
sqlx::pool::PoolOptions::new()
|
|
||||||
.max_connections(config.pool_size)
|
|
||||||
.connect_timeout(std::time::Duration::from_secs(config.timeout.into()))
|
|
||||||
.connect_with(opts)
|
|
||||||
.await
|
|
||||||
.map_err(Error::Db)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get(&self) -> std::result::Result<Self::Connection, Self::GetError> {
|
|
||||||
self.acquire().await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "sqlx_sqlite")]
|
|
||||||
#[async_trait]
|
|
||||||
impl Pool for sqlx::SqlitePool {
|
|
||||||
type Connection = sqlx::pool::PoolConnection<sqlx::Sqlite>;
|
|
||||||
type InitError = sqlx::Error;
|
|
||||||
type GetError = sqlx::Error;
|
|
||||||
|
|
||||||
async fn initialize(db_name: &str, rocket: &Rocket<Build>)
|
|
||||||
-> std::result::Result<Self, Error<Self::InitError>>
|
|
||||||
{
|
|
||||||
use sqlx::ConnectOptions;
|
|
||||||
|
|
||||||
let config = Config::from(db_name, rocket)?;
|
|
||||||
let mut opts = config.url.parse::<sqlx::sqlite::SqliteConnectOptions>()
|
|
||||||
.map_err(Error::Db)?
|
|
||||||
.create_if_missing(true);
|
|
||||||
opts.disable_statement_logging();
|
|
||||||
|
|
||||||
dbg!(sqlx::pool::PoolOptions::new()
|
|
||||||
.max_connections(config.pool_size)
|
|
||||||
.connect_timeout(std::time::Duration::from_secs(config.timeout.into())))
|
|
||||||
.connect_with(opts)
|
|
||||||
.await
|
|
||||||
.map_err(Error::Db)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get(&self) -> std::result::Result<Self::Connection, Self::GetError> {
|
|
||||||
self.acquire().await
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,17 +3,14 @@ use rocket::fairing::{self, AdHoc};
|
||||||
use rocket::response::status::Created;
|
use rocket::response::status::Created;
|
||||||
use rocket::serde::{Serialize, Deserialize, json::Json};
|
use rocket::serde::{Serialize, Deserialize, json::Json};
|
||||||
|
|
||||||
use rocket_db_pools::{sqlx, Database};
|
use rocket_db_pools::{sqlx, Database, Connection};
|
||||||
|
|
||||||
use futures::stream::TryStreamExt;
|
use futures::{stream::TryStreamExt, future::TryFutureExt};
|
||||||
use futures::future::TryFutureExt;
|
|
||||||
|
|
||||||
#[derive(Database)]
|
#[derive(Database)]
|
||||||
#[database("sqlx")]
|
#[database("sqlx")]
|
||||||
struct Db(sqlx::SqlitePool);
|
struct Db(sqlx::SqlitePool);
|
||||||
|
|
||||||
type Connection = rocket_db_pools::Connection<Db>;
|
|
||||||
|
|
||||||
type Result<T, E = rocket::response::Debug<sqlx::Error>> = std::result::Result<T, E>;
|
type Result<T, E = rocket::response::Debug<sqlx::Error>> = std::result::Result<T, E>;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
@ -26,7 +23,7 @@ struct Post {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/", data = "<post>")]
|
#[post("/", data = "<post>")]
|
||||||
async fn create(mut db: Connection, post: Json<Post>) -> Result<Created<Json<Post>>> {
|
async fn create(mut db: Connection<Db>, post: Json<Post>) -> Result<Created<Json<Post>>> {
|
||||||
// There is no support for `RETURNING`.
|
// There is no support for `RETURNING`.
|
||||||
sqlx::query!("INSERT INTO posts (title, text) VALUES (?, ?)", post.title, post.text)
|
sqlx::query!("INSERT INTO posts (title, text) VALUES (?, ?)", post.title, post.text)
|
||||||
.execute(&mut *db)
|
.execute(&mut *db)
|
||||||
|
@ -36,7 +33,7 @@ async fn create(mut db: Connection, post: Json<Post>) -> Result<Created<Json<Pos
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
async fn list(mut db: Connection) -> Result<Json<Vec<i64>>> {
|
async fn list(mut db: Connection<Db>) -> Result<Json<Vec<i64>>> {
|
||||||
let ids = sqlx::query!("SELECT id FROM posts")
|
let ids = sqlx::query!("SELECT id FROM posts")
|
||||||
.fetch(&mut *db)
|
.fetch(&mut *db)
|
||||||
.map_ok(|record| record.id)
|
.map_ok(|record| record.id)
|
||||||
|
@ -47,7 +44,7 @@ async fn list(mut db: Connection) -> Result<Json<Vec<i64>>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<id>")]
|
#[get("/<id>")]
|
||||||
async fn read(mut db: Connection, id: i64) -> Option<Json<Post>> {
|
async fn read(mut db: Connection<Db>, id: i64) -> Option<Json<Post>> {
|
||||||
sqlx::query!("SELECT id, title, text FROM posts WHERE id = ?", id)
|
sqlx::query!("SELECT id, title, text FROM posts WHERE id = ?", id)
|
||||||
.fetch_one(&mut *db)
|
.fetch_one(&mut *db)
|
||||||
.map_ok(|r| Json(Post { id: Some(r.id), title: r.title, text: r.text }))
|
.map_ok(|r| Json(Post { id: Some(r.id), title: r.title, text: r.text }))
|
||||||
|
@ -56,7 +53,7 @@ async fn read(mut db: Connection, id: i64) -> Option<Json<Post>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[delete("/<id>")]
|
#[delete("/<id>")]
|
||||||
async fn delete(mut db: Connection, id: i64) -> Result<Option<()>> {
|
async fn delete(mut db: Connection<Db>, id: i64) -> Result<Option<()>> {
|
||||||
let result = sqlx::query!("DELETE FROM posts WHERE id = ?", id)
|
let result = sqlx::query!("DELETE FROM posts WHERE id = ?", id)
|
||||||
.execute(&mut *db)
|
.execute(&mut *db)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -65,20 +62,20 @@ async fn delete(mut db: Connection, id: i64) -> Result<Option<()>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[delete("/")]
|
#[delete("/")]
|
||||||
async fn destroy(mut db: Connection) -> Result<()> {
|
async fn destroy(mut db: Connection<Db>) -> Result<()> {
|
||||||
sqlx::query!("DELETE FROM posts").execute(&mut *db).await?;
|
sqlx::query!("DELETE FROM posts").execute(&mut *db).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn init_db(rocket: Rocket<Build>) -> fairing::Result {
|
async fn run_migrations(rocket: Rocket<Build>) -> fairing::Result {
|
||||||
match rocket.state::<Db>() {
|
match Db::fetch(&rocket) {
|
||||||
Some(db) => {
|
Some(db) => match sqlx::migrate!("db/sqlx/migrations").run(&**db).await {
|
||||||
if let Err(e) = sqlx::migrate!("db/sqlx/migrations").run(db.pool()).await {
|
Ok(_) => Ok(rocket),
|
||||||
|
Err(e) => {
|
||||||
error!("Failed to initialize SQLx database: {}", e);
|
error!("Failed to initialize SQLx database: {}", e);
|
||||||
return Err(rocket);
|
Err(rocket)
|
||||||
}
|
}
|
||||||
Ok(rocket)
|
|
||||||
}
|
}
|
||||||
None => Err(rocket),
|
None => Err(rocket),
|
||||||
}
|
}
|
||||||
|
@ -86,9 +83,8 @@ async fn init_db(rocket: Rocket<Build>) -> fairing::Result {
|
||||||
|
|
||||||
pub fn stage() -> AdHoc {
|
pub fn stage() -> AdHoc {
|
||||||
AdHoc::on_ignite("SQLx Stage", |rocket| async {
|
AdHoc::on_ignite("SQLx Stage", |rocket| async {
|
||||||
rocket
|
rocket.attach(Db::init())
|
||||||
.attach(Db::fairing())
|
.attach(AdHoc::try_on_ignite("SQLx Migrations", run_migrations))
|
||||||
.attach(AdHoc::try_on_ignite("SQLx Database", init_db))
|
|
||||||
.mount("/sqlx", routes![list, create, read, delete, destroy])
|
.mount("/sqlx", routes![list, create, read, delete, destroy])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,8 @@ echo ":::: Generating the docs..."
|
||||||
pushd "${PROJECT_ROOT}" > /dev/null 2>&1
|
pushd "${PROJECT_ROOT}" > /dev/null 2>&1
|
||||||
# Set the crate version and fill in missing doc URLs with docs.rs links.
|
# Set the crate version and fill in missing doc URLs with docs.rs links.
|
||||||
RUSTDOCFLAGS="-Zunstable-options --crate-version ${DOC_VERSION}" \
|
RUSTDOCFLAGS="-Zunstable-options --crate-version ${DOC_VERSION}" \
|
||||||
cargo doc -p rocket -p rocket_sync_db_pools -p rocket_dyn_templates \
|
cargo doc -p rocket \
|
||||||
|
-p rocket_sync_db_pools -p rocket_dyn_templates -p rocket_db_pools \
|
||||||
-Zrustdoc-map --no-deps --all-features
|
-Zrustdoc-map --no-deps --all-features
|
||||||
popd > /dev/null 2>&1
|
popd > /dev/null 2>&1
|
||||||
|
|
||||||
|
|
|
@ -74,13 +74,13 @@ function indir() {
|
||||||
|
|
||||||
function test_contrib() {
|
function test_contrib() {
|
||||||
DB_POOLS_FEATURES=(
|
DB_POOLS_FEATURES=(
|
||||||
deadpool-postgres
|
deadpool_postgres
|
||||||
deadpool-redis
|
deadpool_redis
|
||||||
mongodb
|
|
||||||
mysql_async
|
|
||||||
sqlx_mysql
|
sqlx_mysql
|
||||||
sqlx_postgres
|
sqlx_postgres
|
||||||
sqlx_sqlite
|
sqlx_sqlite
|
||||||
|
sqlx_mssql
|
||||||
|
mongodb
|
||||||
)
|
)
|
||||||
|
|
||||||
SYNC_DB_POOLS_FEATURES=(
|
SYNC_DB_POOLS_FEATURES=(
|
||||||
|
|
Loading…
Reference in New Issue