Skip to content

Commit

Permalink
Introduce async database pools: 'rocket_db_pools'.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
SergioBenitez committed Jul 18, 2021
1 parent f7f068b commit 5b1a04d
Show file tree
Hide file tree
Showing 13 changed files with 956 additions and 862 deletions.
105 changes: 56 additions & 49 deletions contrib/db_pools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,58 +3,65 @@
[crates.io]: https://img.shields.io/crates/v/rocket_db_pools.svg
[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
[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]: https://github.com/SergioBenitez/Rocket/actions

This crate provides traits, utilities, and a procedural macro for configuring
and accessing database connection pools in Rocket.
Asynchronous database driver integration for Rocket. See the [crate docs] for
full usage details.

## Usage

First, enable the feature corresponding to your database type:

```toml
[dependencies.rocket_db_pools]
version = "0.1.0-dev"
features = ["sqlx_sqlite"]
```

A full list of supported databases and their associated feature names is
available in the [crate docs]. In whichever configuration source you choose,
configure a `databases` dictionary with a key for each database, here
`sqlite_logs` in a TOML source:

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

In your application's source code:

```rust
#[macro_use] extern crate rocket;
use rocket::serde::json::Json;

use rocket_db_pools::{Database, sqlx};

#[derive(Database)]
#[database("sqlite_logs")]
struct LogsDb(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]
fn rocket() -> _ {
rocket::build().attach(LogsDb::fairing())
}
```

See the [crate docs] for full details.
1. Add `rocket_db_pools` as a dependency with one or more [database driver
features] enabled:

```toml
[dependencies.rocket_db_pools]
version = "0.1.0-rc"
features = ["sqlx_sqlite"]
```

2. Choose a name for your database, here `sqlite_logs`. [Configure] _at least_ a
URL for the database:

```toml
[default.databases.sqlite_logs]
url = "/path/to/database.sqlite"
```

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
use rocket_db_pools::{Database, Connection};

#[derive(Database)]
#[database("sqlite_logs")]
struct Logs(sqlx::SqlitePool);

#[launch]
fn rocket() -> _ {
rocket::build().attach(Logs::init())
}
```

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
6 changes: 5 additions & 1 deletion contrib/db_pools/codegen/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
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>"]
description = "Procedural macros for rocket_db_pools."
repository = "https://github.com/SergioBenitez/Rocket/contrib/db_pools"
Expand All @@ -15,3 +15,7 @@ proc-macro = true
[dependencies]
devise = "0.3"
quote = "1"

[dev-dependencies]
rocket = { path = "../../../core/lib", default-features = false }
rocket_db_pools = { path = "../lib", features = ["deadpool_postgres"] }
108 changes: 71 additions & 37 deletions contrib/db_pools/codegen/src/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,68 +2,102 @@ use proc_macro::TokenStream;

use devise::{DeriveGenerator, FromMeta, MapperBuild, Support, ValidatorBuild};
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)]
struct DatabaseAttribute {
#[meta(naked)]
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 {
DeriveGenerator::build_for(input, quote!(impl rocket_db_pools::Database))
.support(Support::TupleStruct)
.validator(ValidatorBuild::new()
.struct_validate(|_, struct_| {
if struct_.fields.len() == 1 {
.struct_validate(|_, s| {
if s.fields.len() == 1 {
Ok(())
} 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,
.outer_mapper(MapperBuild::new()
.struct_map(|_, s| {
let decorated_type = &s.ident;
let pool_type = match &s.fields {
syn::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)
quote_spanned! { s.span() =>
impl From<#pool_type> for #decorated_type {
fn from(pool: #pool_type) -> Self {
Self(pool)
}
}
fn pool(&self) -> &Self::Pool { &self.0 }
})

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(MapperBuild::new()
.try_struct_map(|_, struct_| {
let decorated_type = &struct_.ident;
let pool_type = match &struct_.fields {
Fields::Unnamed(f) => &f.unnamed[0].ty,
.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! { struct_.span() =>
impl From<#pool_type> for #decorated_type {
fn from(pool: #pool_type) -> Self {
Self(pool)
}
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)
}
})
})
Expand Down
62 changes: 41 additions & 21 deletions contrib/db_pools/codegen/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,41 +1,61 @@
#![recursion_limit="256"]

#![warn(rust_2018_idioms)]

//! # `rocket_databases` - Code Generation
//! # `rocket_db_pool` - Code Generation
//!
//! This crate implements the code generation portion of the `rocket_databases`
//! crate.
//! Implements the code generation portion of the `rocket_db_pool` crate. This
//! is an implementation detail. This create should never be depended on
//! directly.

#[macro_use] extern crate quote;

mod database;

use proc_macro::TokenStream;

/// Defines a database type and implements [`Database`] on it.
/// Automatic derive for the [`Database`] trait.
///
/// ```rust
/// use rocket_db_pools::Database;
/// # type PoolType = rocket_db_pools::deadpool_postgres::Pool;
///
/// ```ignore
/// #[derive(Database)]
/// #[database("database_name")]
/// 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
/// on the struct. Custom implementations of `Database` should usually also
/// start with roughly this code:
/// * [`Database::NAME`] is set to the value in the `#[database("name")]`
/// attribute.
///
/// ```ignore
/// impl Database for Db {
/// const NAME: &'static str = "config_name";
/// type Pool = PoolType;
/// fn fairing() -> Fairing<Self> { Fairing::new(|p| Self(p)) }
/// fn pool(&self) -> &Self::Pool { &self.0 }
/// }
/// ```
/// This names the database, providing an anchor to configure the database via
/// `Rocket.toml` or any other configuration source. Specifically, the
/// configuration in `databases.name` is used to configure the driver.
///
/// * [`Database::Pool`] is set to the wrapped type: `PoolType` above. The type
/// 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))]
pub fn derive_database(input: TokenStream) -> TokenStream {
pub fn derive_database(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
crate::database::derive_database(input)
}
Loading

0 comments on commit 5b1a04d

Please sign in to comment.