From fe9fad339e8687d8cdfafe994147e845306ab80f Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Wed, 15 Aug 2018 02:07:17 -0700 Subject: [PATCH] Clean up connection pooling documentation. --- .gitignore | 2 +- contrib/codegen/Cargo.toml | 6 +- contrib/codegen/src/database.rs | 72 +++-- contrib/codegen/src/lib.rs | 13 +- contrib/codegen/src/spanned.rs | 29 -- contrib/lib/Cargo.toml | 4 + contrib/lib/src/databases.rs | 549 ++++++++++++++++++-------------- contrib/lib/src/lib.rs | 2 +- core/lib/src/data/from_data.rs | 2 - core/lib/src/logger.rs | 8 +- examples/todo/Cargo.toml | 2 +- examples/todo/README.md | 4 +- examples/todo/bootstrap.sh | 2 +- site/guide/state.md | 223 ++++++------- 14 files changed, 479 insertions(+), 439 deletions(-) delete mode 100644 contrib/codegen/src/spanned.rs diff --git a/.gitignore b/.gitignore index 03fcdce2..6f8c64bb 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,7 @@ target # Generated databases -db.sql +db.sqlite # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock diff --git a/contrib/codegen/Cargo.toml b/contrib/codegen/Cargo.toml index 83579635..107f5d0c 100644 --- a/contrib/codegen/Cargo.toml +++ b/contrib/codegen/Cargo.toml @@ -19,7 +19,9 @@ database_attribute = [] [lib] proc-macro = true +[dependencies.derive_utils] +git = "https://github.com/SergioBenitez/derive-utils" +rev = "160da392" + [dependencies] quote = "0.6" -proc-macro2 = { version = "0.4", features = ["nightly"] } -syn = { version = "0.14", features = ["full", "extra-traits"] } diff --git a/contrib/codegen/src/database.rs b/contrib/codegen/src/database.rs index c6eff17c..fdfff6ee 100644 --- a/contrib/codegen/src/database.rs +++ b/contrib/codegen/src/database.rs @@ -1,8 +1,6 @@ -use proc_macro::{TokenStream, Diagnostic}; +use proc_macro::TokenStream; +use derive_utils::{Spanned, Result}; use syn::{DataStruct, Fields, Data, Type, LitStr, DeriveInput, Ident, Visibility}; -use spanned::Spanned; - -type Result = ::std::result::Result; #[derive(Debug)] struct DatabaseInvocation { @@ -20,10 +18,10 @@ struct DatabaseInvocation { const EXAMPLE: &str = "example: `struct MyDatabase(diesel::SqliteConnection);`"; const ONLY_ON_STRUCTS_MSG: &str = "`database` attribute can only be used on structs"; -const ONLY_UNNAMED_FIELDS: &str = "`database` attribute can only be applied to structs with \ - exactly one unnamed field"; -const NO_GENERIC_STRUCTS: &str = "`database` attribute cannot be applied to a struct with a \ - generic type"; +const ONLY_UNNAMED_FIELDS: &str = "`database` attribute can only be applied to \ + structs with exactly one unnamed field"; +const NO_GENERIC_STRUCTS: &str = "`database` attribute cannot be applied to a struct \ + with a generic type"; fn parse_invocation(attr: TokenStream, input: TokenStream) -> Result { let attr_stream2 = ::proc_macro2::TokenStream::from(attr); @@ -61,44 +59,61 @@ fn parse_invocation(attr: TokenStream, input: TokenStream) -> Result Result { let invocation = parse_invocation(attr, input)?; + // Store everything we're going to need to generate code. let connection_type = &invocation.connection_type; - let database_name = &invocation.db_name; + let name = &invocation.db_name; let request_guard_type = &invocation.type_name; - let request_guard_vis = &invocation.visibility; + let vis = &invocation.visibility; let pool_type = Ident::new(&format!("{}Pool", request_guard_type), request_guard_type.span()); + let fairing_name = format!("'{}' Database Pool", name); - let tokens = quote! { - #request_guard_vis struct #request_guard_type( - pub ::rocket_contrib::databases::r2d2::PooledConnection<<#connection_type as ::rocket_contrib::databases::Poolable>::Manager> + // A few useful paths. + let databases = quote!(::rocket_contrib::databases); + let r2d2 = quote!(#databases::r2d2); + let request = quote!(::rocket::request); + + Ok(quote! { + /// The request guard type. + #vis struct #request_guard_type( + pub #r2d2::PooledConnection<<#connection_type as #databases::Poolable>::Manager> ); - #request_guard_vis struct #pool_type( - ::rocket_contrib::databases::r2d2::Pool<<#connection_type as ::rocket_contrib::databases::Poolable>::Manager> + + /// The pool type. + #vis struct #pool_type( + #r2d2::Pool<<#connection_type as #databases::Poolable>::Manager> ); impl #request_guard_type { + /// Returns a fairing that initializes the associated database + /// connection pool. pub fn fairing() -> impl ::rocket::fairing::Fairing { - use ::rocket_contrib::databases::Poolable; + use #databases::Poolable; - ::rocket::fairing::AdHoc::on_attach(|rocket| { - let pool = ::rocket_contrib::databases::database_config(#database_name, rocket.config()) + ::rocket::fairing::AdHoc::on_attach(#fairing_name, |rocket| { + let pool = #databases::database_config(#name, rocket.config()) .map(#connection_type::pool); match pool { Ok(Ok(p)) => Ok(rocket.manage(#pool_type(p))), Err(config_error) => { - ::rocket::logger::log_err(&format!("Error while instantiating database: '{}': {}", #database_name, config_error)); + ::rocket::logger::log_err(false, + &format!("Database configuration failure: '{}'", #name)); + ::rocket::logger::log_err(true, &format!("{}", config_error)); Err(rocket) }, Ok(Err(pool_error)) => { - ::rocket::logger::log_err(&format!("Error initializing pool for '{}': {:?}", #database_name, pool_error)); + ::rocket::logger::log_err(false, + &format!("Failed to initialize pool for '{}'", #name)); + ::rocket::logger::log_err(true, &format!("{:?}", pool_error)); Err(rocket) }, } }) } - /// Retrieves a connection of type `Self` from the `rocket` instance. Returns `Some` as long as - /// `Self::fairing()` has been attached and there is at least one connection in the pool. + /// Retrieves a connection of type `Self` from the `rocket` + /// instance. Returns `Some` as long as `Self::fairing()` has been + /// attached and there is at least one connection in the pool. pub fn get_one(rocket: &::rocket::Rocket) -> Option { rocket.state::<#pool_type>() .and_then(|pool| pool.0.get().ok()) @@ -115,19 +130,18 @@ pub fn database_attr(attr: TokenStream, input: TokenStream) -> Result ::rocket::request::FromRequest<'a, 'r> for #request_guard_type { + impl<'a, 'r> #request::FromRequest<'a, 'r> for #request_guard_type { type Error = (); - fn from_request(request: &'a ::rocket::request::Request<'r>) -> ::rocket::request::Outcome { + fn from_request(request: &'a #request::Request<'r>) -> #request::Outcome { + use ::rocket::{Outcome, http::Status}; let pool = request.guard::<::rocket::State<#pool_type>>()?; match pool.0.get() { - Ok(conn) => ::rocket::Outcome::Success(#request_guard_type(conn)), - Err(_) => ::rocket::Outcome::Failure((::rocket::http::Status::ServiceUnavailable, ())), + Ok(conn) => Outcome::Success(#request_guard_type(conn)), + Err(_) => Outcome::Failure((Status::ServiceUnavailable, ())), } } } - }; - - Ok(tokens.into()) + }.into()) } diff --git a/contrib/codegen/src/lib.rs b/contrib/codegen/src/lib.rs index 5c84ba51..1ccf56c5 100644 --- a/contrib/codegen/src/lib.rs +++ b/contrib/codegen/src/lib.rs @@ -1,4 +1,5 @@ #![feature(proc_macro_span, proc_macro_diagnostic)] +#![feature(crate_visibility_modifier)] #![recursion_limit="256"] //! # Rocket Contrib - Code Generation @@ -23,22 +24,24 @@ //! DATABASE_NAME := (string literal) //! -extern crate syn; +extern crate derive_utils; extern crate proc_macro; -extern crate proc_macro2; + +#[allow(unused_imports)] #[macro_use] extern crate quote; -mod spanned; +#[allow(unused_imports)] +crate use derive_utils::{syn, proc_macro2}; #[cfg(feature = "database_attribute")] mod database; -#[allow(dead_code)] +#[allow(unused_imports)] use proc_macro::TokenStream; +/// The procedural macro for the `databases` annotation. #[cfg(feature = "database_attribute")] #[proc_macro_attribute] -/// The procedural macro for the `databases` annotation. pub fn database(attr: TokenStream, input: TokenStream) -> TokenStream { ::database::database_attr(attr, input).unwrap_or_else(|diag| { diag.emit(); diff --git a/contrib/codegen/src/spanned.rs b/contrib/codegen/src/spanned.rs deleted file mode 100644 index 1b8e7a1b..00000000 --- a/contrib/codegen/src/spanned.rs +++ /dev/null @@ -1,29 +0,0 @@ -use proc_macro::Span; - -use quote::ToTokens; - -pub trait Spanned { - fn span(&self) -> Span; -} - -// FIXME: Remove this once proc_macro's stabilize. -impl Spanned for T { - fn span(&self) -> Span { - let token_stream = self.into_token_stream(); - let mut iter = token_stream.into_iter(); - let mut span = match iter.next() { - Some(tt) => tt.span().unstable(), - None => { - return Span::call_site(); - } - }; - - for tt in iter { - if let Some(joined) = span.join(tt.span().unstable()) { - span = joined; - } - } - - span - } -} diff --git a/contrib/lib/Cargo.toml b/contrib/lib/Cargo.toml index 1ada1803..a7e746d2 100644 --- a/contrib/lib/Cargo.toml +++ b/contrib/lib/Cargo.toml @@ -17,8 +17,12 @@ msgpack = ["serde", "rmp-serde"] tera_templates = ["tera", "templates"] handlebars_templates = ["handlebars", "templates"] static_files = [] + +# Database pooling features. +# Iternal use only. database_pool_codegen = ["rocket_contrib_codegen", "rocket_contrib_codegen/database_attribute"] database_pool = ["r2d2", "database_pool_codegen"] +# External features. diesel_pg_pool = ["database_pool", "diesel/postgres", "diesel/r2d2"] diesel_sqlite_pool = ["database_pool", "diesel/sqlite", "diesel/r2d2"] diesel_mysql_pool = ["database_pool", "diesel/mysql", "diesel/r2d2"] diff --git a/contrib/lib/src/databases.rs b/contrib/lib/src/databases.rs index 55f24774..ab715a87 100644 --- a/contrib/lib/src/databases.rs +++ b/contrib/lib/src/databases.rs @@ -1,3 +1,5 @@ +//! Traits, utilities, and a macro for easy database connection pooling. +//! //! # Overview //! //! This module provides traits, utilities, and a procedural macro that allows @@ -5,8 +7,7 @@ //! connection pools. A _database connection pool_ is a data structure that //! maintains active database connections for later use in the application. //! This implementation of connection pooling support is based on -//! [`r2d2`](https://crates.io/crates/r2d2) and exposes connections through -//! [request guards](../../rocket/request/trait.FromRequest.html). Databases are +//! [`r2d2`] and exposes connections through [request guards]. Databases are //! individually configured through Rocket's regular configuration mechanisms: a //! `Rocket.toml` file, environment variables, or procedurally. //! @@ -14,32 +15,34 @@ //! in three simple steps: //! //! 1. Configure your databases in `Rocket.toml`. -//! (see [Configuration](#configuration)) +//! (see [Configuration](#configuration)) //! 2. Associate a request guard type and fairing with each database. -//! (see [Guard Types](#guard-types)) +//! (see [Guard Types](#guard-types)) //! 3. Use the request guard to retrieve a connection in a handler. -//! (see [Handlers](#handlers)) +//! (see [Handlers](#handlers)) //! -//! For a list of supported databases, see [Provided Databases](#provided). -//! This support can be easily extended by implementing the -//! [`Poolable`](trait.Poolable.html) trait. See [Extending](#extending) -//! for more. +//! For a list of supported databases, see [Provided Databases](#provided). This +//! support can be easily extended by implementing the [`Poolable`] trait. See +//! [Extending](#extending) for more. //! -//! The next section provides a complete but un-detailed example of these steps -//! in actions. The sections following provide more detail for each component. +//! [`r2d2`]: https://crates.io/crates/r2d2 +//! [request guards]: [rocket::FromRequest] //! //! ## Example //! -//! Before using this library, the `database_pool` feature in `rocket_contrib` -//! must be enabled: +//! Before using this library, the feature corresponding to your database type +//! in `rocket_contrib` must be enabled: //! //! ```toml //! [dependencies.rocket_contrib] //! version = "0.4.0-dev" //! default-features = false -//! features = ["database_pool", "diesel_sqlite_pool"] +//! features = ["diesel_sqlite_pool"] //! ``` //! +//! See [Provided](#provided) for a list of supported database and their +//! associated feature name. +//! //! In `Rocket.toml` or the equivalent via environment variables: //! //! ```toml @@ -49,29 +52,49 @@ //! //! In your application's source code, one-time: //! -//! ```rust,ignore -//! #![feature(use_extern_macros)] -//! extern crate rocket; -//! extern crate rocket_contrib; -//! +//! ```rust +//! # #![feature(use_extern_macros)] +//! # +//! # extern crate rocket; +//! # extern crate rocket_contrib; +//! # //! use rocket_contrib::databases::{database, diesel}; //! //! #[database("sqlite_logs")] //! struct LogsDbConn(diesel::SqliteConnection); //! //! fn main() { +//! # if false { //! rocket::ignite() //! .attach(LogsDbConn::fairing()) //! .launch(); +//! # } //! } //! ``` //! //! Whenever a connection to the database is needed: //! -//! ```rust,ignore +//! ```rust +//! # #![feature(plugin, decl_macro, use_extern_macros)] +//! # #![plugin(rocket_codegen)] +//! # +//! # extern crate rocket; +//! # extern crate rocket_contrib; +//! # +//! # use rocket_contrib::databases::{database, diesel}; +//! # +//! # #[database("sqlite_logs")] +//! # struct LogsDbConn(diesel::SqliteConnection); +//! # +//! # type Logs = (); +//! # type Result = ::std::result::Result; +//! # //! #[get("/logs/")] -//! fn get_logs(conn: LogsDbConn, id: LogId) -> Result { +//! fn get_logs(conn: LogsDbConn, id: usize) -> Result { +//! # /* //! Logs::by_id(&conn, id) +//! # */ +//! # Ok(()) //! } //! ``` //! @@ -79,38 +102,49 @@ //! //! ## Configuration //! -//! There are a few ways to configure your database connection. You can use the -//! `Rocket.toml` file, you can build it yourself procedurally via the -//! `rocket::custom()` method, or through environment variables. +//! Databases can be configured via various mechanisms: `Rocket.toml`, +//! procedurally via `rocket::custom()`, or via environment variables. //! -//! ### Configuring via `Rocket.toml` +//! ### `Rocket.toml` //! -//! The following examples are all valid ways of configuring your database via -//! the `Rocket.toml` file. -//! -//! The basic structure includes attaching a key to the `global.databases` table -//! and including the __required__ keys `url` and `pool_size`. Additional -//! options that can be added to the table vary by adapter and are referenced -//! below in the [Supported Databases](#provided) section. +//! To configure a database via `Rocket.toml`, add a table for each database +//! to the `databases` table where the key is a name of your choice. The table +//! should have a `url` key and, optionally, a `pool_size` key. This looks as +//! follows: //! //! ```toml +//! // Option 1: //! [global.databases] -//! my_database = { url = "database.sqlite", pool_size = 10 } +//! sqlite_db = { url = "db.sqlite" } //! -//! [[global.databases.other_database]] -//! url = "mysql://root:root@localhost/other_database -//! pool_size = 25 +//! // Option 2: +//! [global.databases.pg_db] +//! url = "mysql://root:root@localhost/pg_db" +//! +//! // With a `pool_size` key: +//! [global.databases] +//! sqlite_db = { url = "db.sqlite", pool_size = 20 } //! ``` //! -//! ### Configuring procedurally +//! The table _requires_ one key: //! -//! It's also possible to procedurally configure your database via the -//! `rocket::custom()` method. Below is an example of doing this: +//! * `url` - the URl to the database //! -//! ```rust,ignore +//! Additionally, all configurations accept the following _optional_ keys: +//! +//! * `pool_size` - the size of the pool, i.e., the number of connections to +//! pool (defaults to the configured number of workers) +//! +//! Additional options may be required or supported by other adapters. +//! +//! ### Procedurally +//! +//! Databases can also be configured procedurally database via +//! `rocket::custom()`. The example below does just this: +//! +//! ```rust //! extern crate rocket; //! -//! use std::io::Error; //! use std::collections::HashMap; //! use rocket::config::{Config, Environment, Value}; //! @@ -118,6 +152,8 @@ //! let mut database_config = HashMap::new(); //! let mut databases = HashMap::new(); //! +//! // This is the same as the following TOML: +//! // my_db = { url = "database.sqlite" } //! database_config.insert("url", Value::from("database.sqlite")); //! databases.insert("my_db", Value::from(database_config)); //! @@ -126,15 +162,18 @@ //! .finalize() //! .unwrap(); //! +//! # if false { //! rocket::custom(config).launch(); +//! # } //! } //! ``` //! -//! ### Configuring via Environment Variable +//! ### Environment Variables //! -//! The final way to configure your databases is via an environment variable. -//! Following the syntax laid out in the guide on [Environment Variables](https://rocket.rs/guide/configuration/#environment-variables), -//! you can configure your database this way. Below is an example +//! Lastly, databases can be configured via environment variables by specifying +//! the `databases` table as detailed in the [Environment Variables +//! configuration +//! guide](https://rocket.rs/guide/configuration/#environment-variables): //! //! ```bash //! ROCKET_DATABASES={my_db={url="db.sqlite"}} @@ -142,49 +181,61 @@ //! //! ## Guard Types //! -//! The included database support generates request guard types that can be used -//! with Rocket handlers. In order to associate a configured database with a -//! type, you need to use the `database` procedural macro: +//! Once a database has been configured, the `#[database]` attribute can be used +//! to tie a type in your application to a configured database. The database +//! attributes accepts a single string parameter that indicates the name of the +//! database. This corresponds to the database name set as the database's +//! configuration key. +//! +//! The attribute can only be applied to unit-like structs with one type. The +//! internal type of the structure must implement [`Poolable`]. //! //! ```rust //! # #![feature(use_extern_macros)] //! # extern crate rocket; //! # extern crate rocket_contrib; -//! # use rocket_contrib::databases::{database, diesel}; +//! use rocket_contrib::databases::{database, diesel}; //! //! #[database("my_db")] //! struct MyDatabase(diesel::SqliteConnection); //! ``` //! -//! From there, the macro will generate code to turn your defined type into a -//! valid request guard type. The interior type must have an implementation of -//! the [`Poolable` trait](trait.Poolable.html). The trait implements methods -//! on the interior type that are used by the generated code to spin up a -//! connection pool. The trait can be used to extend other connection types that -//! aren't supported in this library. See the section on [Extending](#extending) -//! for more information. +//! The macro generates a [`FromRequest`] implementation for the decorated type, +//! allowing the type to be used as a request guard. This implementation +//! retrieves a connection from the database pool or fails with a +//! `Status::ServiceUnavailable` if no connections are available. The macro also +//! generates an implementation of the [`Deref`](::std::ops::Deref) trait with +//! the internal `Poolable` type as the target. //! -//! The generated code will give your defined type two methods, `get_one` and -//! `fairing`, as well as implementations of the [`FromRequest`](../../rocket/request/trait.FromRequest.html) -//! and [`Deref`](../../std/ops/trait.Deref.html) traits. +//! [`FromRequest`]: /rocket/request/trait.FromRequest.html //! -//! The `fairing` method will allow you to attach your database type to the -//! application state via the method call. You __will need__ to call the -//! `fairing` method on your type in order to be able to retrieve connections -//! in your request guards. +//! The macro will also generate two inherent methods on the decorated type: //! -//! Below is an example: +//! * `fn fairing() -> impl Fairing` //! -//! ```rust,ignore +//! Returns a fairing that initializes the associated database connection +//! pool. +//! +//! * `fn get_one(&Rocket) -> Option` +//! +//! Retrieves a connection from the configured pool. Returns `Some` as long +//! as `Self::fairing()` has been attached and there is at least one +//! connection in the pool. +//! +//! The fairing returned from the generated `fairing()` method _must_ be +//! attached for the request guard implementation to succeed. Putting the pieces +//! together, a use of the `#[database]` attribute looks as follows: +//! +//! ```rust //! # #![feature(use_extern_macros)] -//! # //! # extern crate rocket; //! # extern crate rocket_contrib; //! # //! # use std::collections::HashMap; //! # use rocket::config::{Config, Environment, Value}; -//! # use rocket_contrib::databases::{database, diesel}; //! # +//! use rocket_contrib::databases::{database, diesel}; +//! //! #[database("my_db")] //! struct MyDatabase(diesel::SqliteConnection); //! @@ -201,88 +252,116 @@ //! # .finalize() //! # .unwrap(); //! # +//! # if false { //! rocket::custom(config) -//! .attach(MyDatabase::fairing()); // Required! +//! .attach(MyDatabase::fairing()) //! .launch(); +//! # } //! } //! ``` //! //! ## Handlers //! -//! For request handlers, you should use the database type you defined in your -//! code as a request guard. Because of the `FromRequest` implementation that's -//! generated at compile-time, you can use this type in such a way. For example: +//! Finally, simply use your type as a request guard in a handler to retrieve a +//! connection to a given database: +//! +//! ```rust +//! # #![feature(use_extern_macros)] +//! # #![feature(plugin, decl_macro)] +//! # #![plugin(rocket_codegen)] +//! # +//! # extern crate rocket; +//! # extern crate rocket_contrib; +//! # use rocket_contrib::databases::{database, diesel}; +//! #[database("my_db")] +//! struct MyDatabase(diesel::SqliteConnection); //! -//! ```rust,ignore -//! #[database("my_db") -//! struct MyDatabase(diesel::MysqlConnection); -//! ... //! #[get("/")] //! fn my_handler(conn: MyDatabase) { +//! # /* //! ... +//! # */ //! } +//! # fn main() { } //! ``` //! -//! Additionally, because of the `Deref` implementation, you can dereference -//! the database type in order to access the inner connection type. For example: +//! The generated `Deref` implementation allows easy access to the inner +//! connection type: +//! +//! ```rust +//! # #![feature(plugin, decl_macro, use_extern_macros)] +//! # #![plugin(rocket_codegen)] +//! # +//! # extern crate rocket; +//! # extern crate rocket_contrib; +//! # use rocket_contrib::databases::{database, diesel}; +//! # +//! # type Data = (); +//! # +//! #[database("my_db")] +//! struct MyDatabase(diesel::SqliteConnection); +//! +//! fn load_from_db(conn: &diesel::SqliteConnection) -> Data { +//! // Do something with connection, return some data. +//! # () +//! } //! -//! ```rust,ignore //! #[get("/")] -//! fn my_handler(conn: MyDatabase) { -//! ... -//! Thing::load(&conn); -//! ... +//! fn my_handler(conn: MyDatabase) -> Data { +//! load_from_db(&conn) //! } +//! # fn main() { } //! ``` //! -//! Under the hood, the dereferencing of your type is returning the interior -//! type of your connection: -//! -//! ```rust,ignore -//! &self.0 -//! ``` -//! -//! This section should be simple. It should cover: -//! -//! * The fact that `MyType` is not a request guard, and you can use it. -//! * The `Deref` impl and what it means for using `&my_conn`. -//! //! # Database Support //! -//! This library provides built-in support for many popular databases and their -//! corresponding drivers. It also makes extending this support simple. +//! Built-in support is provided for many popular databases and drivers. Support +//! can be easily extended by [`Poolable`] implementations. //! //! ## Provided //! -//! The list below includes all presently supported database adapters, their -//! corresponding [`Poolable`] type, and any special considerations for -//! configuration, if any. +//! The list below includes all presently supported database adapters and their +//! corresponding [`Poolable`] type. //! -//! | Database Kind | Driver | `Poolable` Type | Feature | Notes | -//! | -- ------------- | ----------------------- | ------------------------- | --------------------- | ----- | -//! | MySQL | [Diesel](https://diesel.rs) | [`diesel::MysqlConnection`](http://docs.diesel.rs/diesel/mysql/struct.MysqlConnection.html) | `diesel_mysql_pool` | None | -//! | MySQL | [`rust-mysql-simple`](https://github.com/blackbeam/rust-mysql-simple) | [`mysql::conn`](https://docs.rs/mysql/14.0.0/mysql/struct.Conn.html) | `mysql_pool` | None | -//! | Postgres | [Diesel](https://diesel.rs) | [`diesel::PgConnection`](http://docs.diesel.rs/diesel/pg/struct.PgConnection.html) | `diesel_postgres_pool` | None | -//! | Postgres | [Rust-Postgres](https://github.com/sfackler/rust-postgres) | [`postgres::Connection`](https://docs.rs/postgres/0.15.2/postgres/struct.Connection.html) | `postgres_pool` | None | -//! | Sqlite | [Diesel](https://diesel.rs) | [`diesel::SqliteConnection`](http://docs.diesel.rs/diesel/prelude/struct.SqliteConnection.html) | `diesel_sqlite_pool` | None | -//! | Sqlite | [`Rustqlite`](https://github.com/jgallagher/rusqlite) | [`rusqlite::Connection`](https://docs.rs/rusqlite/0.13.0/rusqlite/struct.Connection.html) | `sqlite_pool` | None | -//! | Neo4j | [`rusted_cypher`](https://github.com/livioribeiro/rusted-cypher) | [`rusted_cypher::GraphClient`](https://docs.rs/rusted_cypher/1.1.0/rusted_cypher/graph/struct.GraphClient.html) | `cypher_pool` | None | -//! | Redis | [`Redis-rs`](https://github.com/mitsuhiko/redis-rs) | [`redis::Connection`](https://docs.rs/redis/0.9.0/redis/struct.Connection.html) | `redis_pool` | None | +//! | Kind | Driver | [`Poolable`] Type | Feature | +//! |----------|-----------------------|--------------------------------|------------------------| +//! | MySQL | [Diesel] | [`diesel::MysqlConnection`] | `diesel_mysql_pool` | +//! | MySQL | [`rust-mysql-simple`] | [`mysql::conn`] | `mysql_pool` | +//! | Postgres | [Diesel] | [`diesel::PgConnection`] | `diesel_postgres_pool` | +//! | Postgres | [Rust-Postgres] | [`postgres::Connection`] | `postgres_pool` | +//! | Sqlite | [Diesel] | [`diesel::SqliteConnection`] | `diesel_sqlite_pool` | +//! | Sqlite | [`Rustqlite`] | [`rusqlite::Connection`] | `sqlite_pool` | +//! | Neo4j | [`rusted_cypher`] | [`rusted_cypher::GraphClient`] | `cypher_pool` | +//! | Redis | [`redis-rs`] | [`redis::Connection`] | `redis_pool` | +//! +//! [Diesel]: https://diesel.rs +//! [`redis::Connection`]: https://docs.rs/redis/0.9.0/redis/struct.Connection.html +//! [`rusted_cypher::GraphClient`]: https://docs.rs/rusted_cypher/1.1.0/rusted_cypher/graph/struct.GraphClient.html +//! [`rusqlite::Connection`]: https://docs.rs/rusqlite/0.13.0/rusqlite/struct.Connection.html +//! [`diesel::SqliteConnection`]: http://docs.diesel.rs/diesel/prelude/struct.SqliteConnection.html +//! [`postgres::Connection`]: https://docs.rs/postgres/0.15.2/postgres/struct.Connection.html +//! [`diesel::PgConnection`]: http://docs.diesel.rs/diesel/pg/struct.PgConnection.html +//! [`mysql::conn`]: https://docs.rs/mysql/14.0.0/mysql/struct.Conn.html +//! [`diesel::MysqlConnection`]: http://docs.diesel.rs/diesel/mysql/struct.MysqlConnection.html +//! [`redis-rs`]: https://github.com/mitsuhiko/redis-rs +//! [`rusted_cypher`]: https://github.com/livioribeiro/rusted-cypher +//! [`Rustqlite`]: https://github.com/jgallagher/rusqlite +//! [Rust-Postgres]: https://github.com/sfackler/rust-postgres +//! [`rust-mysql-simple`]: https://github.com/blackbeam/rust-mysql-simple +//! [`diesel::PgConnection`]: http://docs.diesel.rs/diesel/pg/struct.PgConnection.html //! -//! ### How to use the table //! The above table lists all the supported database adapters in this library. //! In order to use particular `Poolable` type that's included in this library, -//! you must first enable the feature listed in the 'Feature' column. The inner -//! type you should use for your database type should be what's listed in the -//! corresponding `Poolable` Type column. +//! you must first enable the feature listed in the "Feature" column. The +//! interior type of your decorated database type should match the type in the +//! "`Poolable` Type" column. //! //! ## Extending //! //! Extending Rocket's support to your own custom database adapter (or other -//! database-like struct that can be pooled by r2d2) is as easy as implementing -//! the `Poolable` trait for your own type. See the documentation for the -//! [`Poolable` trait](trait.Poolable.html) for more details on how to implement -//! it and extend your type for use with Rocket's database pooling feature. +//! database-like struct that can be pooled by `r2d2`) is as easy as +//! implementing the [`Poolable`] trait. See the documentation for [`Poolable`] +//! for more details on how to implement it. pub extern crate r2d2; @@ -292,6 +371,7 @@ use std::marker::{Send, Sized}; use rocket::config::{self, Value}; +#[doc(inline)] pub use rocket_contrib_codegen::database; use self::r2d2::ManageConnection; @@ -324,12 +404,12 @@ pub extern crate redis; #[cfg(feature = "redis_pool")] pub extern crate r2d2_redis; -/// A struct containing database configuration options from some configuration. +/// A structure representing a particular database configuration. /// /// For the following configuration: /// /// ```toml -/// [[global.databases.my_database]] +/// [global.databases.my_database] /// url = "postgres://root:root@localhost/my_database /// pool_size = 10 /// certs = "sample_cert.pem" @@ -337,16 +417,16 @@ pub extern crate r2d2_redis; /// ``` /// /// The following structure would be generated after calling -/// `database_config("my_database", &some_config)`: +/// [`database_config`]`("my_database", &config)`: /// -/// ```ignore +/// ```rust,ignore /// DatabaseConfig { /// url: "dummy_db.sqlite", /// pool_size: 10, /// extras: { /// "certs": String("certs.pem"), -/// "key": String("key.pem") -/// } +/// "key": String("key.pem"), +/// }, /// } /// ``` #[derive(Debug, Clone, PartialEq)] @@ -361,12 +441,10 @@ pub struct DatabaseConfig<'a> { pub extras: BTreeMap, } -/// A wrapper around `r2d2::Error`s or a custom database error type. This type -/// is mostly relevant to implementors of the [Poolable](trait.Poolable.html) -/// trait. +/// A wrapper around `r2d2::Error`s or a custom database error type. /// -/// Example usages of this type are in the `Poolable` implementations that ship -/// with `rocket_contrib`. +/// This type is only relevant to implementors of the [`Poolable`] trait. See +/// the [`Poolable`] documentation for more information on how to use this type. #[derive(Debug)] pub enum DbError { /// The custom error type to wrap alongside `r2d2::Error`. @@ -375,43 +453,42 @@ pub enum DbError { PoolError(r2d2::Error), } -/// The error type for fetching the DatabaseConfig +/// Error returned on invalid database configurations. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DatabaseConfigError { - /// Returned when the `[[global.databases]]` key is missing or empty from - /// the loaded configuration. + /// The `databases` configuration key is missing or is empty. MissingTable, - /// Returned when the database configuration key is missing from the active + /// The requested database configuration key is missing from the active /// configuration. MissingKey, - /// Returned when the configuration associated with the key isn't in the - /// expected [Table](../../rocket/config/type.Table.html) format. + /// The configuration associated with the key isn't a + /// [Table](/rocket/config/type.Table.html). MalformedConfiguration, - /// Returned when the `url` field is missing. + /// The required `url` key is missing. MissingUrl, - /// Returned when the `url` field is of the wrong type. + /// The value for `url` isn't a string. MalformedUrl, - /// Returned when the `pool_size` exceeds `u32::max_value()` or is negative. + /// The `pool_size` exceeds `u32::max_value()` or is negative. InvalidPoolSize(i64), } -/// This method retrieves the database configuration from the loaded -/// configuration and returns a [`DatabaseConfig`](struct.DatabaseConfig.html) -/// struct. +/// Retrieves the database configuration for the database named `name`. /// -/// # Example: +/// This function is primarily used by the code generated by the `#[database]` +/// attribute. /// -/// Given the following configuration: +/// # Example +/// +/// Consider the following configuration: /// /// ```toml -/// [[global.databases]] +/// [global.databases] /// my_db = { url = "db/db.sqlite", pool_size = 25 } /// my_other_db = { url = "mysql://root:root@localhost/database" } /// ``` /// -/// Calling the `database_config` method will return the -/// [`DatabaseConfig`](struct.DatabaseConfig.html) structure for any valid -/// configuration key. See the example code below. +/// The following example uses `database_config` to retrieve the configurations +/// for the `my_db` and `my_other_db` databases: /// /// ```rust /// # extern crate rocket; @@ -428,29 +505,28 @@ pub enum DatabaseConfigError { /// # my_db.insert("pool_size".to_string(), Value::from(25)); /// # /// # let mut my_other_db = BTreeMap::new(); -/// # my_other_db.insert("url".to_string(), Value::from("mysql://root:root@localhost/database")); +/// # my_other_db.insert("url".to_string(), +/// # Value::from("mysql://root:root@localhost/database")); /// # /// # databases.insert("my_db".to_string(), Value::from(my_db)); /// # databases.insert("my_other_db".to_string(), Value::from(my_other_db)); /// # -/// # let config = Config::build(Environment::Development).extra("databases", databases).expect("custom config okay"); +/// # let config = Config::build(Environment::Development) +/// # .extra("databases", databases) +/// # .expect("custom config okay"); /// # -/// # rocket::custom(config).attach(AdHoc::on_attach(|rocket| { -/// # // HACK: This is a dirty hack required to be able to make this work -/// # let thing = { -/// # let rocket_config = rocket.config(); -/// let config = database_config("my_db", rocket_config).expect("my_db config okay"); +/// # rocket::custom(config).attach(AdHoc::on_attach("Testing", |rocket| { +/// # { +/// let config = database_config("my_db", rocket.config()).unwrap(); /// assert_eq!(config.url, "db/db.sqlite"); /// assert_eq!(config.pool_size, 25); /// -/// let other_config = database_config("my_other_db", rocket_config).expect("my_other_db config okay"); +/// let other_config = database_config("my_other_db", rocket.config()).unwrap(); /// assert_eq!(other_config.url, "mysql://root:root@localhost/database"); /// -/// let error = database_config("invalid_db", rocket_config).unwrap_err(); +/// let error = database_config("invalid_db", rocket.config()).unwrap_err(); /// assert_eq!(error, DatabaseConfigError::MissingKey); -/// # -/// # 10 -/// # }; +/// # } /// # /// # Ok(rocket) /// # })); @@ -513,112 +589,108 @@ impl<'a> Display for DatabaseConfigError { } } -/// Trait implemented by database adapters to allow for r2d2 connection pools to -/// be easily created. +/// Trait implemented by `r2d2`-based database adapters. /// /// # Provided Implementations /// -/// Rocket Contrib implements `Poolable` on several common database adapters. -/// The provided implementations are listed here. +/// Implementations of `Poolable` are provided for the following types: /// -/// * **diesel::MysqlConnection** -/// -/// * **diesel::PgConnection** -/// -/// * **diesel::SqliteConnection** -/// -/// * **postgres::Connection** -/// -/// * **mysql::Conn** -/// -/// * **rusqlite::Connection** -/// -/// * **rusted_cypher::GraphClient** -/// -/// * **redis::Connection** +/// * `diesel::MysqlConnection` +/// * `diesel::PgConnection` +/// * `diesel::SqliteConnection` +/// * `postgres::Connection` +/// * `mysql::Conn` +/// * `rusqlite::Connection` +/// * `rusted_cypher::GraphClient` +/// * `redis::Connection` /// /// # Implementation Guide /// /// As a r2d2-compatible database (or other resource) adapter provider, /// implementing `Poolable` in your own library will enable Rocket users to -/// consume your adapter with its built-in connection pooling primitives. +/// consume your adapter with its built-in connection pooling support. /// /// ## Example /// -/// This example assumes a `FooConnectionManager` implementing the -/// `ManageConnection`trait required by r2d2. This connection manager abstracts -/// over a pool of `FooClient` connections. +/// Consider a library `foo` with the following types: /// -/// Given the following definition of the client and connection manager: +/// * `foo::ConnectionManager`, which implements [`r2d2::ManageConnection`] +/// * `foo::Connection`, the `Connection` associated type of +/// `foo::ConnectionManager` +/// * `foo::Error`, errors resulting from manager instantiation /// -/// ```rust,ignore -/// struct FooClient { ... }; +/// [`r2d2`]: https://crates.io/crates/r2d2 +/// [`r2d2::ManageConnection`]: http://docs.rs/r2d2/0.8/r2d2/trait.ManageConnection.html /// -/// impl FooClient { -/// pub fn new(...) -> Result { -/// ... -/// } -/// } +/// In order for Rocket to generate the required code to automatically provision +/// a r2d2 connection pool into application state, the `Poolable` trait needs to +/// be implemented for the connection type. The following example implements +/// `Poolable` for `foo::Connection`: /// -/// struct FooConnectionManager { ... }; +/// ```rust +/// use rocket_contrib::databases::{r2d2, DbError, DatabaseConfig, Poolable}; /// -/// impl FooConnectionManager { -/// pub fn new(...) -> Result { -/// ... -/// } -/// } -/// ``` -/// -/// In order to allow for Rocket Contrib to generate the required code to -/// automatically provision a r2d2 connection pool into application state, the -/// `Poolable` trait needs to be implemented for the connection type. -/// -/// Given the above definitions, the following would be a valid implementation -/// of the `Poolable` trait: -/// -/// ```rust,ignore -/// impl Poolable for FooClient { -/// type Manager = FooConnectionManager; +/// # mod foo { +/// # use rocket_contrib::databases::r2d2; +/// # use std::fmt; +/// # #[derive(Debug)] pub struct Error; +/// # impl ::std::error::Error for Error { } +/// # impl fmt::Display for Error { +/// # fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { Ok(()) } +/// # } +/// # +/// # pub struct Connection; +/// # pub struct ConnectionManager; +/// # +/// # type Result = ::std::result::Result; +/// # +/// # impl self::r2d2::ManageConnection for ConnectionManager { +/// # type Connection = Connection; +/// # type Error = Error; +/// # fn connect(&self) -> Result { panic!(()) } +/// # fn is_valid(&self, _: &mut Connection) -> Result<()> { panic!() } +/// # fn has_broken(&self, _: &mut Connection) -> bool { panic!() } +/// # } +/// # } +/// # +/// impl Poolable for foo::Connection { +/// type Manager = foo::ConnectionManager; /// type Error = DbError; /// /// fn pool(config: DatabaseConfig) -> Result, Self::Error> { -/// let manager = FooConnectionManager::new(config.url) +/// # let _ = config; /* +/// let manager = foo::ConnectionManager::new(config.url) /// .map_err(DbError::Custom)?; /// -/// r2d2::Pool::builder().max_size(config.pool_size).build(manager) +/// r2d2::Pool::builder() +/// .max_size(config.pool_size) +/// .build(manager) /// .map_err(DbError::PoolError) +/// # */ +/// # Err(DbError::Custom(foo::Error)) /// } /// } /// ``` /// -/// In the above example, the connection manager is failable and returns the the -/// `FooClient`'s error type. Since the error type can diverge from a simple -/// r2d2 pool error, the [`DbError`](enum.DbError.html) wrapper is used. This -/// error type is defined as part of the associated type in the `Poolable` trait -/// definition. +/// In this example, `ConnectionManager::new()` method returns a `foo::Error` on +/// failure. For convenience, the [`DbError`] enum is used to consolidate this +/// error type and the `r2d2::Error` type that can result from +/// `r2d2::Pool::builder()`. /// -/// Additionally, you'll notice that the `pool` method of the trait is used to -/// to create the connection manager and the pool. This method returns a -/// `Result` containing an r2d2 pool monomorphized to the `Manager` associated -/// type in the trait definition, or containing the `Error` associated type. -/// -/// In the event that the connection manager isn't failable (as is the case in -/// Diesel's r2d2 connection manager, for example), the associated error type +/// In the event that a connection manager isn't fallible (as is the case with +/// Diesel's r2d2 connection manager, for instance), the associated error type /// for the `Poolable` implementation can simply be `r2d2::Error` as this is the -/// only error that can be returned by the `pool` method. You can refer to the -/// included implementations of `Poolable` in the `rocket_contrib::databases` -/// module for concrete examples. -/// +/// only error that can be result. For more concrete example, consult Rocket's +/// existing implementations of [`Poolable`]. pub trait Poolable: Send + Sized + 'static { /// The associated connection manager for the given connection type. type Manager: ManageConnection; /// The associated error type in the event that constructing the connection - /// manager and/or the connection pool fails + /// manager and/or the connection pool fails. type Error; - /// Creates an r2d2 connection pool from the provided Manager associated - /// type and returns the pool or the error associated with the trait - /// implementation. + /// Creates an `r2d2` connection pool for `Manager::Connection`, returning + /// the pool on success. fn pool(config: DatabaseConfig) -> Result, Self::Error>; } @@ -721,7 +793,7 @@ impl Poolable for redis::Connection { mod tests { use std::collections::BTreeMap; use rocket::{Config, config::{Environment, Value}}; - use super::{DatabaseConfigError, database_config}; + use super::{DatabaseConfigError::*, database_config}; #[test] fn no_database_entry_in_config_returns_error() { @@ -730,7 +802,7 @@ mod tests { .unwrap(); let database_config_result = database_config("dummy_db", &config); - assert_eq!(Err(DatabaseConfigError::MissingTable), database_config_result); + assert_eq!(Err(MissingTable), database_config_result); } #[test] @@ -749,7 +821,7 @@ mod tests { let database_config_result = database_config("real_db", &config); - assert_eq!(Err(DatabaseConfigError::MissingKey), database_config_result); + assert_eq!(Err(MissingKey), database_config_result); } #[test] @@ -765,7 +837,7 @@ mod tests { let database_config_result = database_config("dummy_db", &config); - assert_eq!(Err(DatabaseConfigError::MalformedConfiguration), database_config_result); + assert_eq!(Err(MalformedConfiguration), database_config_result); } #[test] @@ -781,7 +853,7 @@ mod tests { let database_config_result = database_config("dummy_db", &config); - assert_eq!(Err(DatabaseConfigError::MissingUrl), database_config_result); + assert_eq!(Err(MissingUrl), database_config_result); } #[test] @@ -798,7 +870,7 @@ mod tests { let database_config_result = database_config("dummy_db", &config); - assert_eq!(Err(DatabaseConfigError::MalformedUrl), database_config_result); + assert_eq!(Err(MalformedUrl), database_config_result); } #[test] @@ -816,15 +888,16 @@ mod tests { let database_config_result = database_config("dummy_db", &config); - assert_eq!(Err(DatabaseConfigError::InvalidPoolSize(-1)), database_config_result); + assert_eq!(Err(InvalidPoolSize(-1)), database_config_result); } #[test] fn pool_size_beyond_u32_max_returns_error() { let mut database_extra = BTreeMap::new(); let mut connection_config = BTreeMap::new(); + let over_max = (u32::max_value()) as i64 + 1; connection_config.insert("url".to_string(), Value::from("dummy_db.sqlite")); - connection_config.insert("pool_size".to_string(), Value::from(4294967296)); + connection_config.insert("pool_size".to_string(), Value::from(over_max)); database_extra.insert("dummy_db", connection_config); let config = Config::build(Environment::Development) @@ -835,7 +908,7 @@ mod tests { let database_config_result = database_config("dummy_db", &config); // The size of `0` is an overflow wrap-around - assert_eq!(Err(DatabaseConfigError::InvalidPoolSize(0)), database_config_result); + assert_eq!(Err(InvalidPoolSize(over_max)), database_config_result); } #[test] diff --git a/contrib/lib/src/lib.rs b/contrib/lib/src/lib.rs index 841660e1..23ae2f0b 100644 --- a/contrib/lib/src/lib.rs +++ b/contrib/lib/src/lib.rs @@ -24,7 +24,7 @@ //! * [handlebars_templates](struct.Template.html) //! * [tera_templates](struct.Template.html) //! * [uuid](struct.Uuid.html) -//! * [database_pool](databases/index.html) +//! * [${database}_pool](databases/index.html) //! //! The recommend way to include features from this crate via Cargo in your //! project is by adding a `[dependencies.rocket_contrib]` section to your diff --git a/core/lib/src/data/from_data.rs b/core/lib/src/data/from_data.rs index 992ceb49..363c6bc7 100644 --- a/core/lib/src/data/from_data.rs +++ b/core/lib/src/data/from_data.rs @@ -154,8 +154,6 @@ impl<'a, S, E> IntoOutcome for Result { /// A `FromData` implementation allowing this looks like: /// /// ```rust -/// # #![allow(unused_attributes)] -/// # #![allow(unused_variables)] /// # #![feature(plugin, decl_macro)] /// # #![plugin(rocket_codegen)] /// # extern crate rocket; diff --git a/core/lib/src/logger.rs b/core/lib/src/logger.rs index 6abd4913..6fed0c2c 100644 --- a/core/lib/src/logger.rs +++ b/core/lib/src/logger.rs @@ -212,6 +212,10 @@ pub fn init(level: LoggingLevel) -> bool { // This method exists as a shim for the log macros that need to be called from // an end user's code. It was added as part of the work to support database // connection pools via procedural macros. -pub fn log_err(msg: &str) { - error!("{}", msg); +#[doc(hidden)] +pub fn log_err(indented: bool, msg: &str) { + match indented { + true => error_!("{}", msg), + false => error!("{}", msg), + } } diff --git a/examples/todo/Cargo.toml b/examples/todo/Cargo.toml index f2eac1af..01c9bb48 100644 --- a/examples/todo/Cargo.toml +++ b/examples/todo/Cargo.toml @@ -19,4 +19,4 @@ rand = "0.5" [dependencies.rocket_contrib] path = "../../contrib/lib" default_features = false -features = [ "tera_templates", "database_pool", "diesel_sqlite_pool" ] +features = ["tera_templates", "diesel_sqlite_pool"] diff --git a/examples/todo/README.md b/examples/todo/README.md index a4e26a0b..b23d175e 100644 --- a/examples/todo/README.md +++ b/examples/todo/README.md @@ -23,6 +23,6 @@ following commands: # install Diesel CLI tools cargo install diesel_cli --version '<= 1.2' --no-default-features --features=sqlite -# create db/db.sql -diesel migration run --database-url="db/db.sql" +# create db/db.sqlite +diesel migration run --database-url="db/db.sqlite" ``` diff --git a/examples/todo/bootstrap.sh b/examples/todo/bootstrap.sh index f9057f42..167a789e 100755 --- a/examples/todo/bootstrap.sh +++ b/examples/todo/bootstrap.sh @@ -12,6 +12,6 @@ pushd "${SCRIPT_PATH}" > /dev/null cargo install diesel_cli --version '<= 1.2' --no-default-features --features=sqlite > /dev/null fi - # create db/db.sql + # create db/db.sqlite diesel migration --database-url="${DATABASE_URL}" run > /dev/null popd > /dev/null diff --git a/site/guide/state.md b/site/guide/state.md index c3b87db2..b9dce5b8 100644 --- a/site/guide/state.md +++ b/site/guide/state.md @@ -147,141 +147,112 @@ to implement request timing. ## Databases -While Rocket doesn't have built-in support for databases yet, you can combine a -few external libraries to get native-feeling access to databases in a Rocket -application. Let's take a look at how we might integrate Rocket with two common -database libraries: [`diesel`], a type-safe ORM and query builder, and [`r2d2`], -a library for connection pooling. +Rocket includes built-in, ORM-agnostic support for databases. In particular, +Rocket provides a procedural macro that allows you to easily connect your Rocket +application to databases through connection pools. A _database connection pool_ +is a data structure that maintains active database connections for later use in +the application. This implementation of connection pooling support is based on +[`r2d2`] and exposes connections through request guards. Databases are +individually configured through Rocket's regular configuration mechanisms: a +`Rocket.toml` file, environment variables, or procedurally. -Our approach will be to have Rocket manage a pool of database connections using -managed state and then implement a request guard that retrieves one connection. -This will allow us to get access to the database in a handler by simply adding a -`DbConn` argument: +Connecting your Rocket application to a database using this library occurs in +three simple steps: -```rust -#[get("/users")] -fn handler(conn: DbConn) { ... } -``` + 1. Configure the databases in `Rocket.toml`. + 2. Associate a request guard type and fairing with each database. + 3. Use the request guard to retrieve a connection in a handler. -[`diesel`]: http://diesel.rs/ -[`r2d2`]: https://docs.rs/r2d2/ +Presently, Rocket provides built-in support for the following databases: -### Dependencies +| Kind | Driver | `Poolable` Type | Feature | +|----------|-----------------------|--------------------------------|------------------------| +| MySQL | [Diesel] | [`diesel::MysqlConnection`] | `diesel_mysql_pool` | +| MySQL | [`rust-mysql-simple`] | [`mysql::conn`] | `mysql_pool` | +| Postgres | [Diesel] | [`diesel::PgConnection`] | `diesel_postgres_pool` | +| Postgres | [Rust-Postgres] | [`postgres::Connection`] | `postgres_pool` | +| Sqlite | [Diesel] | [`diesel::SqliteConnection`] | `diesel_sqlite_pool` | +| Sqlite | [`Rustqlite`] | [`rusqlite::Connection`] | `sqlite_pool` | +| Neo4j | [`rusted_cypher`] | [`rusted_cypher::GraphClient`] | `cypher_pool` | +| Redis | [`redis-rs`] | [`redis::Connection`] | `redis_pool` | -To get started, we need to depend on the `diesel` and `r2d2` crates. For -detailed information on how to use Diesel, please see the [Diesel getting -started guide](http://diesel.rs/guides/getting-started/). For this example, we -use the following dependencies: - -``` -[dependencies] -rocket = "0.4.0-dev" -rocket_codegen = "0.4.0-dev" -diesel = { version = "<= 1.2", features = ["sqlite", "r2d2"] } -``` - -Your `diesel` dependency information may differ. The crates are imported as -well: - -```rust -extern crate rocket; -#[macro_use] extern crate diesel; -``` - -### Managed Pool - -The first step is to initialize a pool of database connections. The `init_pool` -function below uses `r2d2` to create a new pool of database connections. Diesel -advocates for using a `DATABASE_URL` environment variable to set the database -URL, and we use the same convention here. Excepting the long-winded types, the -code is fairly straightforward: the `DATABASE_URL` environment variable is -stored in the `DATABASE_URL` static, and an `r2d2::Pool` is created using the -default configuration parameters and a Diesel `SqliteConnection` -`ConnectionManager`. - -```rust -use diesel::sqlite::SqliteConnection; -use diesel::r2d2::{ConnectionManager, Pool, PooledConnection}; - -// An alias to the type for a pool of Diesel SQLite connections. -type SqlitePool = Pool>; - -// The URL to the database, set via the `DATABASE_URL` environment variable. -static DATABASE_URL: &'static str = env!("DATABASE_URL"); - -/// Initializes a database pool. -fn init_pool() -> SqlitePool { - let manager = ConnectionManager::::new(DATABASE_URL); - Pool::new(manager).expect("db pool") -} -``` - -We then use managed state to have Rocket manage the pool for us: - -```rust -fn main() { - rocket::ignite() - .manage(init_pool()) - .launch(); -} -``` - -### Connection Guard - -The second and final step is to implement a request guard that retrieves a -single connection from the managed connection pool. We create a new type, -`DbConn`, that wraps an `r2d2` pooled connection. We then implement -`FromRequest` for `DbConn` so that we can use it as a request guard. Finally, we -implement `Deref` with a target of `SqliteConnection` so that we can -transparently use an `&*DbConn` as an `&SqliteConnection`. - -```rust -use std::ops::Deref; -use rocket::http::Status; -use rocket::request::{self, FromRequest}; -use rocket::{Request, State, Outcome}; -use diesel::r2d2::{ConnectionManager, Pool, PooledConnection}; - -// Connection request guard type: a wrapper around an r2d2 pooled connection. -pub struct DbConn(pub PooledConnection>); - -/// Attempts to retrieve a single connection from the managed database pool. If -/// no pool is currently managed, fails with an `InternalServerError` status. If -/// no connections are available, fails with a `ServiceUnavailable` status. -impl<'a, 'r> FromRequest<'a, 'r> for DbConn { - type Error = (); - - fn from_request(request: &'a Request<'r>) -> request::Outcome { - let pool = request.guard::>()?; - match pool.get() { - Ok(conn) => Outcome::Success(DbConn(conn)), - Err(_) => Outcome::Failure((Status::ServiceUnavailable, ())) - } - } -} - -// For the convenience of using an &DbConn as an &SqliteConnection. -impl Deref for DbConn { - type Target = SqliteConnection; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} -``` +[`r2d2`]: https://crates.io/crates/r2d2 +[Diesel]: https://diesel.rs +[`redis::Connection`]: https://docs.rs/redis/0.9.0/redis/struct.Connection.html +[`rusted_cypher::GraphClient`]: https://docs.rs/rusted_cypher/1.1.0/rusted_cypher/graph/struct.GraphClient.html +[`rusqlite::Connection`]: https://docs.rs/rusqlite/0.13.0/rusqlite/struct.Connection.html +[`diesel::SqliteConnection`]: http://docs.diesel.rs/diesel/prelude/struct.SqliteConnection.html +[`postgres::Connection`]: https://docs.rs/postgres/0.15.2/postgres/struct.Connection.html +[`diesel::PgConnection`]: http://docs.diesel.rs/diesel/pg/struct.PgConnection.html +[`mysql::conn`]: https://docs.rs/mysql/14.0.0/mysql/struct.Conn.html +[`diesel::MysqlConnection`]: http://docs.diesel.rs/diesel/mysql/struct.MysqlConnection.html +[`redis-rs`]: https://github.com/mitsuhiko/redis-rs +[`rusted_cypher`]: https://github.com/livioribeiro/rusted-cypher +[`Rustqlite`]: https://github.com/jgallagher/rusqlite +[Rust-Postgres]: https://github.com/sfackler/rust-postgres +[`rust-mysql-simple`]: https://github.com/blackbeam/rust-mysql-simple +[`diesel::PgConnection`]: http://docs.diesel.rs/diesel/pg/struct.PgConnection.html ### Usage -With these two pieces in place, we can use `DbConn` as a request guard in any -handler or other request guard implementation, giving our application access to -a database. As a simple example, we might write a route that returns a JSON -array of some `Task` structures that are fetched from a database: +To connect your Rocket application to a given database, first identify the +"Kind" and "Driver" in the table that matches your environment. The feature +corresponding to your database type must be enabled. This is the feature +identified in the "Feature" column. For instance, for Diesel-based SQLite +databases, you'd write in `Cargo.toml`: + +```toml +[dependencies.rocket_contrib] +version = "0.4.0-dev" +default-features = false +features = ["diesel_sqlite_pool"] +``` + +Then, in `Rocket.toml` or the equivalent via environment variables, configure +the URL for the database in the `databases` table: + +```toml +[global.databases] +sqlite_logs = { url = "/path/to/database.sqlite" } +``` + +In your application's source code, create a unit-like struct with one internal +type. This type should be the type listed in the "`Poolable` Type" column. Then +decorate the type with the `#[database]` attribute, providing the name of the +database that you configured in the previous step as the only parameter. +Finally, attach the fairing returned by `YourType::fairing()`, which was +generated by the `#[database]` attribute: ```rust -#[get("/tasks")] -fn get_tasks(conn: DbConn) -> QueryResult>> { - all_tasks.order(tasks::id.desc()) - .load::(&*conn) - .map(|tasks| Json(tasks)) +use rocket_contrib::databases::{database, diesel}; + +#[database("sqlite_logs")] +struct LogsDbConn(diesel::SqliteConnection); + +fn main() { + rocket::ignite() + .attach(LogsDbConn::fairing()) + .launch(); } ``` + +That's it! Whenever a connection to the database is needed, use your type as a +request guard: + +```rust +impl Logs { + fn by_id(conn: &diesel::SqliteConnection, log_id: usize) -> Result { + logs.filter(id.eq(log_id)).load(conn) + } +} + +#[get("/logs/")] +fn get_logs(conn: LogsDbConn, id: usize) -> Result { + Logs::by_id(&conn, id) +} +``` + +For more on Rocket's built-in database support, see the +[`rocket_contrib::databases`] module documentation. + +[`rocket_contrib::databases`]: https://api.rocket.rs/rocket_contrib/databases/index.html