Clean up connection pooling documentation.

This commit is contained in:
Sergio Benitez 2018-08-15 02:07:17 -07:00
parent 60b9f06407
commit fe9fad339e
14 changed files with 479 additions and 439 deletions

2
.gitignore vendored
View File

@ -11,7 +11,7 @@
target target
# Generated databases # Generated databases
db.sql db.sqlite
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # 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 # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock

View File

@ -19,7 +19,9 @@ database_attribute = []
[lib] [lib]
proc-macro = true proc-macro = true
[dependencies.derive_utils]
git = "https://github.com/SergioBenitez/derive-utils"
rev = "160da392"
[dependencies] [dependencies]
quote = "0.6" quote = "0.6"
proc-macro2 = { version = "0.4", features = ["nightly"] }
syn = { version = "0.14", features = ["full", "extra-traits"] }

View File

@ -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 syn::{DataStruct, Fields, Data, Type, LitStr, DeriveInput, Ident, Visibility};
use spanned::Spanned;
type Result<T> = ::std::result::Result<T, Diagnostic>;
#[derive(Debug)] #[derive(Debug)]
struct DatabaseInvocation { struct DatabaseInvocation {
@ -20,10 +18,10 @@ struct DatabaseInvocation {
const EXAMPLE: &str = "example: `struct MyDatabase(diesel::SqliteConnection);`"; const EXAMPLE: &str = "example: `struct MyDatabase(diesel::SqliteConnection);`";
const ONLY_ON_STRUCTS_MSG: &str = "`database` attribute can only be used on structs"; 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 \ const ONLY_UNNAMED_FIELDS: &str = "`database` attribute can only be applied to \
exactly one unnamed field"; structs with exactly one unnamed field";
const NO_GENERIC_STRUCTS: &str = "`database` attribute cannot be applied to a struct with a \ const NO_GENERIC_STRUCTS: &str = "`database` attribute cannot be applied to a struct \
generic type"; with a generic type";
fn parse_invocation(attr: TokenStream, input: TokenStream) -> Result<DatabaseInvocation> { fn parse_invocation(attr: TokenStream, input: TokenStream) -> Result<DatabaseInvocation> {
let attr_stream2 = ::proc_macro2::TokenStream::from(attr); let attr_stream2 = ::proc_macro2::TokenStream::from(attr);
@ -61,44 +59,61 @@ fn parse_invocation(attr: TokenStream, input: TokenStream) -> Result<DatabaseInv
pub fn database_attr(attr: TokenStream, input: TokenStream) -> Result<TokenStream> { pub fn database_attr(attr: TokenStream, input: TokenStream) -> Result<TokenStream> {
let invocation = parse_invocation(attr, input)?; let invocation = parse_invocation(attr, input)?;
// Store everything we're going to need to generate code.
let connection_type = &invocation.connection_type; 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_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 pool_type = Ident::new(&format!("{}Pool", request_guard_type), request_guard_type.span());
let fairing_name = format!("'{}' Database Pool", name);
let tokens = quote! { // A few useful paths.
#request_guard_vis struct #request_guard_type( let databases = quote!(::rocket_contrib::databases);
pub ::rocket_contrib::databases::r2d2::PooledConnection<<#connection_type as ::rocket_contrib::databases::Poolable>::Manager> 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 { impl #request_guard_type {
/// Returns a fairing that initializes the associated database
/// connection pool.
pub fn fairing() -> impl ::rocket::fairing::Fairing { pub fn fairing() -> impl ::rocket::fairing::Fairing {
use ::rocket_contrib::databases::Poolable; use #databases::Poolable;
::rocket::fairing::AdHoc::on_attach(|rocket| { ::rocket::fairing::AdHoc::on_attach(#fairing_name, |rocket| {
let pool = ::rocket_contrib::databases::database_config(#database_name, rocket.config()) let pool = #databases::database_config(#name, rocket.config())
.map(#connection_type::pool); .map(#connection_type::pool);
match pool { match pool {
Ok(Ok(p)) => Ok(rocket.manage(#pool_type(p))), Ok(Ok(p)) => Ok(rocket.manage(#pool_type(p))),
Err(config_error) => { 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) Err(rocket)
}, },
Ok(Err(pool_error)) => { 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) Err(rocket)
}, },
} }
}) })
} }
/// Retrieves a connection of type `Self` from the `rocket` instance. Returns `Some` as long as /// Retrieves a connection of type `Self` from the `rocket`
/// `Self::fairing()` has been attached and there is at least one connection in the pool. /// 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<Self> { pub fn get_one(rocket: &::rocket::Rocket) -> Option<Self> {
rocket.state::<#pool_type>() rocket.state::<#pool_type>()
.and_then(|pool| pool.0.get().ok()) .and_then(|pool| pool.0.get().ok())
@ -115,19 +130,18 @@ pub fn database_attr(attr: TokenStream, input: TokenStream) -> Result<TokenStrea
} }
} }
impl<'a, 'r> ::rocket::request::FromRequest<'a, 'r> for #request_guard_type { impl<'a, 'r> #request::FromRequest<'a, 'r> for #request_guard_type {
type Error = (); type Error = ();
fn from_request(request: &'a ::rocket::request::Request<'r>) -> ::rocket::request::Outcome<Self, Self::Error> { fn from_request(request: &'a #request::Request<'r>) -> #request::Outcome<Self, ()> {
use ::rocket::{Outcome, http::Status};
let pool = request.guard::<::rocket::State<#pool_type>>()?; let pool = request.guard::<::rocket::State<#pool_type>>()?;
match pool.0.get() { match pool.0.get() {
Ok(conn) => ::rocket::Outcome::Success(#request_guard_type(conn)), Ok(conn) => Outcome::Success(#request_guard_type(conn)),
Err(_) => ::rocket::Outcome::Failure((::rocket::http::Status::ServiceUnavailable, ())), Err(_) => Outcome::Failure((Status::ServiceUnavailable, ())),
} }
} }
} }
}; }.into())
Ok(tokens.into())
} }

View File

@ -1,4 +1,5 @@
#![feature(proc_macro_span, proc_macro_diagnostic)] #![feature(proc_macro_span, proc_macro_diagnostic)]
#![feature(crate_visibility_modifier)]
#![recursion_limit="256"] #![recursion_limit="256"]
//! # Rocket Contrib - Code Generation //! # Rocket Contrib - Code Generation
@ -23,22 +24,24 @@
//! DATABASE_NAME := (string literal) //! DATABASE_NAME := (string literal)
//! </pre> //! </pre>
extern crate syn; extern crate derive_utils;
extern crate proc_macro; extern crate proc_macro;
extern crate proc_macro2;
#[allow(unused_imports)]
#[macro_use] extern crate quote; #[macro_use] extern crate quote;
mod spanned; #[allow(unused_imports)]
crate use derive_utils::{syn, proc_macro2};
#[cfg(feature = "database_attribute")] #[cfg(feature = "database_attribute")]
mod database; mod database;
#[allow(dead_code)] #[allow(unused_imports)]
use proc_macro::TokenStream; use proc_macro::TokenStream;
/// The procedural macro for the `databases` annotation.
#[cfg(feature = "database_attribute")] #[cfg(feature = "database_attribute")]
#[proc_macro_attribute] #[proc_macro_attribute]
/// The procedural macro for the `databases` annotation.
pub fn database(attr: TokenStream, input: TokenStream) -> TokenStream { pub fn database(attr: TokenStream, input: TokenStream) -> TokenStream {
::database::database_attr(attr, input).unwrap_or_else(|diag| { ::database::database_attr(attr, input).unwrap_or_else(|diag| {
diag.emit(); diag.emit();

View File

@ -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<T: ToTokens> 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
}
}

View File

@ -17,8 +17,12 @@ msgpack = ["serde", "rmp-serde"]
tera_templates = ["tera", "templates"] tera_templates = ["tera", "templates"]
handlebars_templates = ["handlebars", "templates"] handlebars_templates = ["handlebars", "templates"]
static_files = [] static_files = []
# Database pooling features.
# Iternal use only.
database_pool_codegen = ["rocket_contrib_codegen", "rocket_contrib_codegen/database_attribute"] database_pool_codegen = ["rocket_contrib_codegen", "rocket_contrib_codegen/database_attribute"]
database_pool = ["r2d2", "database_pool_codegen"] database_pool = ["r2d2", "database_pool_codegen"]
# External features.
diesel_pg_pool = ["database_pool", "diesel/postgres", "diesel/r2d2"] diesel_pg_pool = ["database_pool", "diesel/postgres", "diesel/r2d2"]
diesel_sqlite_pool = ["database_pool", "diesel/sqlite", "diesel/r2d2"] diesel_sqlite_pool = ["database_pool", "diesel/sqlite", "diesel/r2d2"]
diesel_mysql_pool = ["database_pool", "diesel/mysql", "diesel/r2d2"] diesel_mysql_pool = ["database_pool", "diesel/mysql", "diesel/r2d2"]

View File

@ -1,3 +1,5 @@
//! Traits, utilities, and a macro for easy database connection pooling.
//!
//! # Overview //! # Overview
//! //!
//! This module provides traits, utilities, and a procedural macro that allows //! 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 //! connection pools. A _database connection pool_ is a data structure that
//! maintains active database connections for later use in the application. //! maintains active database connections for later use in the application.
//! This implementation of connection pooling support is based on //! This implementation of connection pooling support is based on
//! [`r2d2`](https://crates.io/crates/r2d2) and exposes connections through //! [`r2d2`] and exposes connections through [request guards]. Databases are
//! [request guards](../../rocket/request/trait.FromRequest.html). Databases are
//! individually configured through Rocket's regular configuration mechanisms: a //! individually configured through Rocket's regular configuration mechanisms: a
//! `Rocket.toml` file, environment variables, or procedurally. //! `Rocket.toml` file, environment variables, or procedurally.
//! //!
@ -20,26 +21,28 @@
//! 3. Use the request guard to retrieve a connection in a handler. //! 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). //! For a list of supported databases, see [Provided Databases](#provided). This
//! This support can be easily extended by implementing the //! support can be easily extended by implementing the [`Poolable`] trait. See
//! [`Poolable`](trait.Poolable.html) trait. See [Extending](#extending) //! [Extending](#extending) for more.
//! for more.
//! //!
//! The next section provides a complete but un-detailed example of these steps //! [`r2d2`]: https://crates.io/crates/r2d2
//! in actions. The sections following provide more detail for each component. //! [request guards]: [rocket::FromRequest]
//! //!
//! ## Example //! ## Example
//! //!
//! Before using this library, the `database_pool` feature in `rocket_contrib` //! Before using this library, the feature corresponding to your database type
//! must be enabled: //! in `rocket_contrib` must be enabled:
//! //!
//! ```toml //! ```toml
//! [dependencies.rocket_contrib] //! [dependencies.rocket_contrib]
//! version = "0.4.0-dev" //! version = "0.4.0-dev"
//! default-features = false //! 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: //! In `Rocket.toml` or the equivalent via environment variables:
//! //!
//! ```toml //! ```toml
@ -49,29 +52,49 @@
//! //!
//! In your application's source code, one-time: //! In your application's source code, one-time:
//! //!
//! ```rust,ignore //! ```rust
//! #![feature(use_extern_macros)] //! # #![feature(use_extern_macros)]
//! extern crate rocket; //! #
//! extern crate rocket_contrib; //! # extern crate rocket;
//! //! # extern crate rocket_contrib;
//! #
//! use rocket_contrib::databases::{database, diesel}; //! use rocket_contrib::databases::{database, diesel};
//! //!
//! #[database("sqlite_logs")] //! #[database("sqlite_logs")]
//! struct LogsDbConn(diesel::SqliteConnection); //! struct LogsDbConn(diesel::SqliteConnection);
//! //!
//! fn main() { //! fn main() {
//! # if false {
//! rocket::ignite() //! rocket::ignite()
//! .attach(LogsDbConn::fairing()) //! .attach(LogsDbConn::fairing())
//! .launch(); //! .launch();
//! # }
//! } //! }
//! ``` //! ```
//! //!
//! Whenever a connection to the database is needed: //! 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<T> = ::std::result::Result<T, ()>;
//! #
//! #[get("/logs/<id>")] //! #[get("/logs/<id>")]
//! fn get_logs(conn: LogsDbConn, id: LogId) -> Result<Logs> { //! fn get_logs(conn: LogsDbConn, id: usize) -> Result<Logs> {
//! # /*
//! Logs::by_id(&conn, id) //! Logs::by_id(&conn, id)
//! # */
//! # Ok(())
//! } //! }
//! ``` //! ```
//! //!
@ -79,38 +102,49 @@
//! //!
//! ## Configuration //! ## Configuration
//! //!
//! There are a few ways to configure your database connection. You can use the //! Databases can be configured via various mechanisms: `Rocket.toml`,
//! `Rocket.toml` file, you can build it yourself procedurally via the //! procedurally via `rocket::custom()`, or via environment variables.
//! `rocket::custom()` method, or through environment variables.
//! //!
//! ### Configuring via `Rocket.toml` //! ### `Rocket.toml`
//! //!
//! The following examples are all valid ways of configuring your database via //! To configure a database via `Rocket.toml`, add a table for each database
//! the `Rocket.toml` file. //! 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
//! The basic structure includes attaching a key to the `global.databases` table //! follows:
//! 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.
//! //!
//! ```toml //! ```toml
//! // Option 1:
//! [global.databases] //! [global.databases]
//! my_database = { url = "database.sqlite", pool_size = 10 } //! sqlite_db = { url = "db.sqlite" }
//! //!
//! [[global.databases.other_database]] //! // Option 2:
//! url = "mysql://root:root@localhost/other_database //! [global.databases.pg_db]
//! pool_size = 25 //! 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 //! * `url` - the URl to the database
//! `rocket::custom()` method. Below is an example of doing this:
//! //!
//! ```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; //! extern crate rocket;
//! //!
//! use std::io::Error;
//! use std::collections::HashMap; //! use std::collections::HashMap;
//! use rocket::config::{Config, Environment, Value}; //! use rocket::config::{Config, Environment, Value};
//! //!
@ -118,6 +152,8 @@
//! let mut database_config = HashMap::new(); //! let mut database_config = HashMap::new();
//! let mut databases = 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")); //! database_config.insert("url", Value::from("database.sqlite"));
//! databases.insert("my_db", Value::from(database_config)); //! databases.insert("my_db", Value::from(database_config));
//! //!
@ -126,15 +162,18 @@
//! .finalize() //! .finalize()
//! .unwrap(); //! .unwrap();
//! //!
//! # if false {
//! rocket::custom(config).launch(); //! rocket::custom(config).launch();
//! # }
//! } //! }
//! ``` //! ```
//! //!
//! ### Configuring via Environment Variable //! ### Environment Variables
//! //!
//! The final way to configure your databases is via an environment variable. //! Lastly, databases can be configured via environment variables by specifying
//! Following the syntax laid out in the guide on [Environment Variables](https://rocket.rs/guide/configuration/#environment-variables), //! the `databases` table as detailed in the [Environment Variables
//! you can configure your database this way. Below is an example //! configuration
//! guide](https://rocket.rs/guide/configuration/#environment-variables):
//! //!
//! ```bash //! ```bash
//! ROCKET_DATABASES={my_db={url="db.sqlite"}} //! ROCKET_DATABASES={my_db={url="db.sqlite"}}
@ -142,49 +181,61 @@
//! //!
//! ## Guard Types //! ## Guard Types
//! //!
//! The included database support generates request guard types that can be used //! Once a database has been configured, the `#[database]` attribute can be used
//! with Rocket handlers. In order to associate a configured database with a //! to tie a type in your application to a configured database. The database
//! type, you need to use the `database` procedural macro: //! 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 //! ```rust
//! # #![feature(use_extern_macros)] //! # #![feature(use_extern_macros)]
//! # extern crate rocket; //! # extern crate rocket;
//! # extern crate rocket_contrib; //! # extern crate rocket_contrib;
//! # use rocket_contrib::databases::{database, diesel}; //! use rocket_contrib::databases::{database, diesel};
//! //!
//! #[database("my_db")] //! #[database("my_db")]
//! struct MyDatabase(diesel::SqliteConnection); //! struct MyDatabase(diesel::SqliteConnection);
//! ``` //! ```
//! //!
//! From there, the macro will generate code to turn your defined type into a //! The macro generates a [`FromRequest`] implementation for the decorated type,
//! valid request guard type. The interior type must have an implementation of //! allowing the type to be used as a request guard. This implementation
//! the [`Poolable` trait](trait.Poolable.html). The trait implements methods //! retrieves a connection from the database pool or fails with a
//! on the interior type that are used by the generated code to spin up a //! `Status::ServiceUnavailable` if no connections are available. The macro also
//! connection pool. The trait can be used to extend other connection types that //! generates an implementation of the [`Deref`](::std::ops::Deref) trait with
//! aren't supported in this library. See the section on [Extending](#extending) //! the internal `Poolable` type as the target.
//! for more information.
//! //!
//! The generated code will give your defined type two methods, `get_one` and //! [`FromRequest`]: /rocket/request/trait.FromRequest.html
//! `fairing`, as well as implementations of the [`FromRequest`](../../rocket/request/trait.FromRequest.html)
//! and [`Deref`](../../std/ops/trait.Deref.html) traits.
//! //!
//! The `fairing` method will allow you to attach your database type to the //! The macro will also generate two inherent methods on the decorated type:
//! 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.
//! //!
//! 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<Self>`
//!
//! 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)] //! # #![feature(use_extern_macros)]
//! #
//! # extern crate rocket; //! # extern crate rocket;
//! # extern crate rocket_contrib; //! # extern crate rocket_contrib;
//! # //! #
//! # use std::collections::HashMap; //! # use std::collections::HashMap;
//! # use rocket::config::{Config, Environment, Value}; //! # use rocket::config::{Config, Environment, Value};
//! # use rocket_contrib::databases::{database, diesel};
//! # //! #
//! use rocket_contrib::databases::{database, diesel};
//!
//! #[database("my_db")] //! #[database("my_db")]
//! struct MyDatabase(diesel::SqliteConnection); //! struct MyDatabase(diesel::SqliteConnection);
//! //!
@ -201,88 +252,116 @@
//! # .finalize() //! # .finalize()
//! # .unwrap(); //! # .unwrap();
//! # //! #
//! # if false {
//! rocket::custom(config) //! rocket::custom(config)
//! .attach(MyDatabase::fairing()); // Required! //! .attach(MyDatabase::fairing())
//! .launch(); //! .launch();
//! # }
//! } //! }
//! ``` //! ```
//! //!
//! ## Handlers //! ## Handlers
//! //!
//! For request handlers, you should use the database type you defined in your //! Finally, simply use your type as a request guard in a handler to retrieve a
//! code as a request guard. Because of the `FromRequest` implementation that's //! connection to a given database:
//! generated at compile-time, you can use this type in such a way. For example: //!
//! ```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("/")] //! #[get("/")]
//! fn my_handler(conn: MyDatabase) { //! fn my_handler(conn: MyDatabase) {
//! # /*
//! ... //! ...
//! # */
//! } //! }
//! # fn main() { }
//! ``` //! ```
//! //!
//! Additionally, because of the `Deref` implementation, you can dereference //! The generated `Deref` implementation allows easy access to the inner
//! the database type in order to access the inner connection type. For example: //! 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("/")] //! #[get("/")]
//! fn my_handler(conn: MyDatabase) { //! fn my_handler(conn: MyDatabase) -> Data {
//! ... //! load_from_db(&conn)
//! Thing::load(&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 //! # Database Support
//! //!
//! This library provides built-in support for many popular databases and their //! Built-in support is provided for many popular databases and drivers. Support
//! corresponding drivers. It also makes extending this support simple. //! can be easily extended by [`Poolable`] implementations.
//! //!
//! ## Provided //! ## Provided
//! //!
//! The list below includes all presently supported database adapters, their //! The list below includes all presently supported database adapters and their
//! corresponding [`Poolable`] type, and any special considerations for //! corresponding [`Poolable`] type.
//! configuration, if any.
//! //!
//! | Database Kind | Driver | `Poolable` Type | Feature | Notes | //! | Kind | Driver | [`Poolable`] Type | Feature |
//! | -- ------------- | ----------------------- | ------------------------- | --------------------- | ----- | //! |----------|-----------------------|--------------------------------|------------------------|
//! | MySQL | [Diesel](https://diesel.rs) | [`diesel::MysqlConnection`](http://docs.diesel.rs/diesel/mysql/struct.MysqlConnection.html) | `diesel_mysql_pool` | None | //! | MySQL | [Diesel] | [`diesel::MysqlConnection`] | `diesel_mysql_pool` |
//! | 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 | //! | MySQL | [`rust-mysql-simple`] | [`mysql::conn`] | `mysql_pool` |
//! | Postgres | [Diesel](https://diesel.rs) | [`diesel::PgConnection`](http://docs.diesel.rs/diesel/pg/struct.PgConnection.html) | `diesel_postgres_pool` | None | //! | Postgres | [Diesel] | [`diesel::PgConnection`] | `diesel_postgres_pool` |
//! | 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 | //! | Postgres | [Rust-Postgres] | [`postgres::Connection`] | `postgres_pool` |
//! | Sqlite | [Diesel](https://diesel.rs) | [`diesel::SqliteConnection`](http://docs.diesel.rs/diesel/prelude/struct.SqliteConnection.html) | `diesel_sqlite_pool` | None | //! | Sqlite | [Diesel] | [`diesel::SqliteConnection`] | `diesel_sqlite_pool` |
//! | Sqlite | [`Rustqlite`](https://github.com/jgallagher/rusqlite) | [`rusqlite::Connection`](https://docs.rs/rusqlite/0.13.0/rusqlite/struct.Connection.html) | `sqlite_pool` | None | //! | Sqlite | [`Rustqlite`] | [`rusqlite::Connection`] | `sqlite_pool` |
//! | 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 | //! | Neo4j | [`rusted_cypher`] | [`rusted_cypher::GraphClient`] | `cypher_pool` |
//! | 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 | //! | 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. //! 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, //! 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 //! you must first enable the feature listed in the "Feature" column. The
//! type you should use for your database type should be what's listed in the //! interior type of your decorated database type should match the type in the
//! corresponding `Poolable` Type column. //! "`Poolable` Type" column.
//! //!
//! ## Extending //! ## Extending
//! //!
//! Extending Rocket's support to your own custom database adapter (or other //! 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 //! database-like struct that can be pooled by `r2d2`) is as easy as
//! the `Poolable` trait for your own type. See the documentation for the //! implementing the [`Poolable`] trait. See the documentation for [`Poolable`]
//! [`Poolable` trait](trait.Poolable.html) for more details on how to implement //! for more details on how to implement it.
//! it and extend your type for use with Rocket's database pooling feature.
pub extern crate r2d2; pub extern crate r2d2;
@ -292,6 +371,7 @@ use std::marker::{Send, Sized};
use rocket::config::{self, Value}; use rocket::config::{self, Value};
#[doc(inline)]
pub use rocket_contrib_codegen::database; pub use rocket_contrib_codegen::database;
use self::r2d2::ManageConnection; use self::r2d2::ManageConnection;
@ -324,12 +404,12 @@ pub extern crate redis;
#[cfg(feature = "redis_pool")] #[cfg(feature = "redis_pool")]
pub extern crate r2d2_redis; 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: /// For the following configuration:
/// ///
/// ```toml /// ```toml
/// [[global.databases.my_database]] /// [global.databases.my_database]
/// url = "postgres://root:root@localhost/my_database /// url = "postgres://root:root@localhost/my_database
/// pool_size = 10 /// pool_size = 10
/// certs = "sample_cert.pem" /// certs = "sample_cert.pem"
@ -337,16 +417,16 @@ pub extern crate r2d2_redis;
/// ``` /// ```
/// ///
/// The following structure would be generated after calling /// The following structure would be generated after calling
/// `database_config("my_database", &some_config)`: /// [`database_config`]`("my_database", &config)`:
/// ///
/// ```ignore /// ```rust,ignore
/// DatabaseConfig { /// DatabaseConfig {
/// url: "dummy_db.sqlite", /// url: "dummy_db.sqlite",
/// pool_size: 10, /// pool_size: 10,
/// extras: { /// extras: {
/// "certs": String("certs.pem"), /// "certs": String("certs.pem"),
/// "key": String("key.pem") /// "key": String("key.pem"),
/// } /// },
/// } /// }
/// ``` /// ```
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@ -361,12 +441,10 @@ pub struct DatabaseConfig<'a> {
pub extras: BTreeMap<String, Value>, pub extras: BTreeMap<String, Value>,
} }
/// A wrapper around `r2d2::Error`s or a custom database error type. This type /// A wrapper around `r2d2::Error`s or a custom database error type.
/// is mostly relevant to implementors of the [Poolable](trait.Poolable.html)
/// trait.
/// ///
/// Example usages of this type are in the `Poolable` implementations that ship /// This type is only relevant to implementors of the [`Poolable`] trait. See
/// with `rocket_contrib`. /// the [`Poolable`] documentation for more information on how to use this type.
#[derive(Debug)] #[derive(Debug)]
pub enum DbError<T> { pub enum DbError<T> {
/// The custom error type to wrap alongside `r2d2::Error`. /// The custom error type to wrap alongside `r2d2::Error`.
@ -375,43 +453,42 @@ pub enum DbError<T> {
PoolError(r2d2::Error), PoolError(r2d2::Error),
} }
/// The error type for fetching the DatabaseConfig /// Error returned on invalid database configurations.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DatabaseConfigError { pub enum DatabaseConfigError {
/// Returned when the `[[global.databases]]` key is missing or empty from /// The `databases` configuration key is missing or is empty.
/// the loaded configuration.
MissingTable, MissingTable,
/// Returned when the database configuration key is missing from the active /// The requested database configuration key is missing from the active
/// configuration. /// configuration.
MissingKey, MissingKey,
/// Returned when the configuration associated with the key isn't in the /// The configuration associated with the key isn't a
/// expected [Table](../../rocket/config/type.Table.html) format. /// [Table](/rocket/config/type.Table.html).
MalformedConfiguration, MalformedConfiguration,
/// Returned when the `url` field is missing. /// The required `url` key is missing.
MissingUrl, MissingUrl,
/// Returned when the `url` field is of the wrong type. /// The value for `url` isn't a string.
MalformedUrl, 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), InvalidPoolSize(i64),
} }
/// This method retrieves the database configuration from the loaded /// Retrieves the database configuration for the database named `name`.
/// configuration and returns a [`DatabaseConfig`](struct.DatabaseConfig.html)
/// struct.
/// ///
/// # Example: /// This function is primarily used by the code generated by the `#[database]`
/// attribute.
/// ///
/// Given the following configuration: /// # Example
///
/// Consider the following configuration:
/// ///
/// ```toml /// ```toml
/// [[global.databases]] /// [global.databases]
/// my_db = { url = "db/db.sqlite", pool_size = 25 } /// my_db = { url = "db/db.sqlite", pool_size = 25 }
/// my_other_db = { url = "mysql://root:root@localhost/database" } /// my_other_db = { url = "mysql://root:root@localhost/database" }
/// ``` /// ```
/// ///
/// Calling the `database_config` method will return the /// The following example uses `database_config` to retrieve the configurations
/// [`DatabaseConfig`](struct.DatabaseConfig.html) structure for any valid /// for the `my_db` and `my_other_db` databases:
/// configuration key. See the example code below.
/// ///
/// ```rust /// ```rust
/// # extern crate rocket; /// # extern crate rocket;
@ -428,29 +505,28 @@ pub enum DatabaseConfigError {
/// # my_db.insert("pool_size".to_string(), Value::from(25)); /// # my_db.insert("pool_size".to_string(), Value::from(25));
/// # /// #
/// # let mut my_other_db = BTreeMap::new(); /// # 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_db".to_string(), Value::from(my_db));
/// # databases.insert("my_other_db".to_string(), Value::from(my_other_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| { /// # rocket::custom(config).attach(AdHoc::on_attach("Testing", |rocket| {
/// # // HACK: This is a dirty hack required to be able to make this work /// # {
/// # let thing = { /// let config = database_config("my_db", rocket.config()).unwrap();
/// # let rocket_config = rocket.config();
/// let config = database_config("my_db", rocket_config).expect("my_db config okay");
/// assert_eq!(config.url, "db/db.sqlite"); /// assert_eq!(config.url, "db/db.sqlite");
/// assert_eq!(config.pool_size, 25); /// 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"); /// 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); /// assert_eq!(error, DatabaseConfigError::MissingKey);
/// # /// # }
/// # 10
/// # };
/// # /// #
/// # Ok(rocket) /// # Ok(rocket)
/// # })); /// # }));
@ -513,112 +589,108 @@ impl<'a> Display for DatabaseConfigError {
} }
} }
/// Trait implemented by database adapters to allow for r2d2 connection pools to /// Trait implemented by `r2d2`-based database adapters.
/// be easily created.
/// ///
/// # Provided Implementations /// # Provided Implementations
/// ///
/// Rocket Contrib implements `Poolable` on several common database adapters. /// Implementations of `Poolable` are provided for the following types:
/// The provided implementations are listed here.
/// ///
/// * **diesel::MysqlConnection** /// * `diesel::MysqlConnection`
/// /// * `diesel::PgConnection`
/// * **diesel::PgConnection** /// * `diesel::SqliteConnection`
/// /// * `postgres::Connection`
/// * **diesel::SqliteConnection** /// * `mysql::Conn`
/// /// * `rusqlite::Connection`
/// * **postgres::Connection** /// * `rusted_cypher::GraphClient`
/// /// * `redis::Connection`
/// * **mysql::Conn**
///
/// * **rusqlite::Connection**
///
/// * **rusted_cypher::GraphClient**
///
/// * **redis::Connection**
/// ///
/// # Implementation Guide /// # Implementation Guide
/// ///
/// As a r2d2-compatible database (or other resource) adapter provider, /// As a r2d2-compatible database (or other resource) adapter provider,
/// implementing `Poolable` in your own library will enable Rocket users to /// 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 /// ## Example
/// ///
/// This example assumes a `FooConnectionManager` implementing the /// Consider a library `foo` with the following types:
/// `ManageConnection`trait required by r2d2. This connection manager abstracts
/// over a pool of `FooClient` connections.
/// ///
/// 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 /// [`r2d2`]: https://crates.io/crates/r2d2
/// struct FooClient { ... }; /// [`r2d2::ManageConnection`]: http://docs.rs/r2d2/0.8/r2d2/trait.ManageConnection.html
/// ///
/// impl FooClient { /// In order for Rocket to generate the required code to automatically provision
/// pub fn new(...) -> Result<Self, foo::Error> { /// 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 { /// # mod foo {
/// pub fn new(...) -> Result<Self, foo::Error> { /// # 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(()) }
/// 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. /// # pub struct Connection;
/// /// # pub struct ConnectionManager;
/// Given the above definitions, the following would be a valid implementation /// #
/// of the `Poolable` trait: /// # type Result<T> = ::std::result::Result<T, Error>;
/// /// #
/// ```rust,ignore /// # impl self::r2d2::ManageConnection for ConnectionManager {
/// impl Poolable for FooClient { /// # type Connection = Connection;
/// type Manager = FooConnectionManager; /// # type Error = Error;
/// # fn connect(&self) -> Result<Connection> { panic!(()) }
/// # fn is_valid(&self, _: &mut Connection) -> Result<()> { panic!() }
/// # fn has_broken(&self, _: &mut Connection) -> bool { panic!() }
/// # }
/// # }
/// #
/// impl Poolable for foo::Connection {
/// type Manager = foo::ConnectionManager;
/// type Error = DbError<foo::Error>; /// type Error = DbError<foo::Error>;
/// ///
/// fn pool(config: DatabaseConfig) -> Result<r2d2::Pool<Self::Manager>, Self::Error> { /// fn pool(config: DatabaseConfig) -> Result<r2d2::Pool<Self::Manager>, Self::Error> {
/// let manager = FooConnectionManager::new(config.url) /// # let _ = config; /*
/// let manager = foo::ConnectionManager::new(config.url)
/// .map_err(DbError::Custom)?; /// .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) /// .map_err(DbError::PoolError)
/// # */
/// # Err(DbError::Custom(foo::Error))
/// } /// }
/// } /// }
/// ``` /// ```
/// ///
/// In the above example, the connection manager is failable and returns the the /// In this example, `ConnectionManager::new()` method returns a `foo::Error` on
/// `FooClient`'s error type. Since the error type can diverge from a simple /// failure. For convenience, the [`DbError`] enum is used to consolidate this
/// r2d2 pool error, the [`DbError`](enum.DbError.html) wrapper is used. This /// error type and the `r2d2::Error` type that can result from
/// error type is defined as part of the associated type in the `Poolable` trait /// `r2d2::Pool::builder()`.
/// definition.
/// ///
/// Additionally, you'll notice that the `pool` method of the trait is used to /// In the event that a connection manager isn't fallible (as is the case with
/// to create the connection manager and the pool. This method returns a /// Diesel's r2d2 connection manager, for instance), the associated error type
/// `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
/// for the `Poolable` implementation can simply be `r2d2::Error` as this is the /// 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 /// only error that can be result. For more concrete example, consult Rocket's
/// included implementations of `Poolable` in the `rocket_contrib::databases` /// existing implementations of [`Poolable`].
/// module for concrete examples.
///
pub trait Poolable: Send + Sized + 'static { pub trait Poolable: Send + Sized + 'static {
/// The associated connection manager for the given connection type. /// The associated connection manager for the given connection type.
type Manager: ManageConnection<Connection=Self>; type Manager: ManageConnection<Connection=Self>;
/// The associated error type in the event that constructing the connection /// 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; type Error;
/// Creates an r2d2 connection pool from the provided Manager associated /// Creates an `r2d2` connection pool for `Manager::Connection`, returning
/// type and returns the pool or the error associated with the trait /// the pool on success.
/// implementation.
fn pool(config: DatabaseConfig) -> Result<r2d2::Pool<Self::Manager>, Self::Error>; fn pool(config: DatabaseConfig) -> Result<r2d2::Pool<Self::Manager>, Self::Error>;
} }
@ -721,7 +793,7 @@ impl Poolable for redis::Connection {
mod tests { mod tests {
use std::collections::BTreeMap; use std::collections::BTreeMap;
use rocket::{Config, config::{Environment, Value}}; use rocket::{Config, config::{Environment, Value}};
use super::{DatabaseConfigError, database_config}; use super::{DatabaseConfigError::*, database_config};
#[test] #[test]
fn no_database_entry_in_config_returns_error() { fn no_database_entry_in_config_returns_error() {
@ -730,7 +802,7 @@ mod tests {
.unwrap(); .unwrap();
let database_config_result = database_config("dummy_db", &config); 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] #[test]
@ -749,7 +821,7 @@ mod tests {
let database_config_result = database_config("real_db", &config); 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] #[test]
@ -765,7 +837,7 @@ mod tests {
let database_config_result = database_config("dummy_db", &config); 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] #[test]
@ -781,7 +853,7 @@ mod tests {
let database_config_result = database_config("dummy_db", &config); 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] #[test]
@ -798,7 +870,7 @@ mod tests {
let database_config_result = database_config("dummy_db", &config); 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] #[test]
@ -816,15 +888,16 @@ mod tests {
let database_config_result = database_config("dummy_db", &config); 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] #[test]
fn pool_size_beyond_u32_max_returns_error() { fn pool_size_beyond_u32_max_returns_error() {
let mut database_extra = BTreeMap::new(); let mut database_extra = BTreeMap::new();
let mut connection_config = 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("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); database_extra.insert("dummy_db", connection_config);
let config = Config::build(Environment::Development) let config = Config::build(Environment::Development)
@ -835,7 +908,7 @@ mod tests {
let database_config_result = database_config("dummy_db", &config); let database_config_result = database_config("dummy_db", &config);
// The size of `0` is an overflow wrap-around // 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] #[test]

View File

@ -24,7 +24,7 @@
//! * [handlebars_templates](struct.Template.html) //! * [handlebars_templates](struct.Template.html)
//! * [tera_templates](struct.Template.html) //! * [tera_templates](struct.Template.html)
//! * [uuid](struct.Uuid.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 //! The recommend way to include features from this crate via Cargo in your
//! project is by adding a `[dependencies.rocket_contrib]` section to your //! project is by adding a `[dependencies.rocket_contrib]` section to your

View File

@ -154,8 +154,6 @@ impl<'a, S, E> IntoOutcome<S, (Status, E), Data> for Result<S, E> {
/// A `FromData` implementation allowing this looks like: /// A `FromData` implementation allowing this looks like:
/// ///
/// ```rust /// ```rust
/// # #![allow(unused_attributes)]
/// # #![allow(unused_variables)]
/// # #![feature(plugin, decl_macro)] /// # #![feature(plugin, decl_macro)]
/// # #![plugin(rocket_codegen)] /// # #![plugin(rocket_codegen)]
/// # extern crate rocket; /// # extern crate rocket;

View File

@ -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 // 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 // an end user's code. It was added as part of the work to support database
// connection pools via procedural macros. // connection pools via procedural macros.
pub fn log_err(msg: &str) { #[doc(hidden)]
error!("{}", msg); pub fn log_err(indented: bool, msg: &str) {
match indented {
true => error_!("{}", msg),
false => error!("{}", msg),
}
} }

View File

@ -19,4 +19,4 @@ rand = "0.5"
[dependencies.rocket_contrib] [dependencies.rocket_contrib]
path = "../../contrib/lib" path = "../../contrib/lib"
default_features = false default_features = false
features = [ "tera_templates", "database_pool", "diesel_sqlite_pool" ] features = ["tera_templates", "diesel_sqlite_pool"]

View File

@ -23,6 +23,6 @@ following commands:
# install Diesel CLI tools # install Diesel CLI tools
cargo install diesel_cli --version '<= 1.2' --no-default-features --features=sqlite cargo install diesel_cli --version '<= 1.2' --no-default-features --features=sqlite
# create db/db.sql # create db/db.sqlite
diesel migration run --database-url="db/db.sql" diesel migration run --database-url="db/db.sqlite"
``` ```

View File

@ -12,6 +12,6 @@ pushd "${SCRIPT_PATH}" > /dev/null
cargo install diesel_cli --version '<= 1.2' --no-default-features --features=sqlite > /dev/null cargo install diesel_cli --version '<= 1.2' --no-default-features --features=sqlite > /dev/null
fi fi
# create db/db.sql # create db/db.sqlite
diesel migration --database-url="${DATABASE_URL}" run > /dev/null diesel migration --database-url="${DATABASE_URL}" run > /dev/null
popd > /dev/null popd > /dev/null

View File

@ -147,141 +147,112 @@ to implement request timing.
## Databases ## Databases
While Rocket doesn't have built-in support for databases yet, you can combine a Rocket includes built-in, ORM-agnostic support for databases. In particular,
few external libraries to get native-feeling access to databases in a Rocket Rocket provides a procedural macro that allows you to easily connect your Rocket
application. Let's take a look at how we might integrate Rocket with two common application to databases through connection pools. A _database connection pool_
database libraries: [`diesel`], a type-safe ORM and query builder, and [`r2d2`], is a data structure that maintains active database connections for later use in
a library for connection pooling. 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 Connecting your Rocket application to a database using this library occurs in
managed state and then implement a request guard that retrieves one connection. three simple steps:
This will allow us to get access to the database in a handler by simply adding a
`DbConn` argument: 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.
Presently, Rocket provides built-in support for the following databases:
| 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` |
[`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
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 ```rust
#[get("/users")] use rocket_contrib::databases::{database, diesel};
fn handler(conn: DbConn) { ... }
```
[`diesel`]: http://diesel.rs/ #[database("sqlite_logs")]
[`r2d2`]: https://docs.rs/r2d2/ struct LogsDbConn(diesel::SqliteConnection);
### Dependencies
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<ConnectionManager<SqliteConnection>>;
// 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::<SqliteConnection>::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() { fn main() {
rocket::ignite() rocket::ignite()
.manage(init_pool()) .attach(LogsDbConn::fairing())
.launch(); .launch();
} }
``` ```
### Connection Guard That's it! Whenever a connection to the database is needed, use your type as a
request 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 ```rust
use std::ops::Deref; impl Logs {
use rocket::http::Status; fn by_id(conn: &diesel::SqliteConnection, log_id: usize) -> Result<Logs> {
use rocket::request::{self, FromRequest}; logs.filter(id.eq(log_id)).load(conn)
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<ConnectionManager<SqliteConnection>>);
/// 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<Self, Self::Error> {
let pool = request.guard::<State<SqlitePool>>()?;
match pool.get() {
Ok(conn) => Outcome::Success(DbConn(conn)),
Err(_) => Outcome::Failure((Status::ServiceUnavailable, ()))
}
} }
} }
// For the convenience of using an &DbConn as an &SqliteConnection. #[get("/logs/<id>")]
impl Deref for DbConn { fn get_logs(conn: LogsDbConn, id: usize) -> Result<Logs> {
type Target = SqliteConnection; Logs::by_id(&conn, id)
fn deref(&self) -> &Self::Target {
&self.0
}
} }
``` ```
### Usage For more on Rocket's built-in database support, see the
[`rocket_contrib::databases`] module documentation.
With these two pieces in place, we can use `DbConn` as a request guard in any [`rocket_contrib::databases`]: https://api.rocket.rs/rocket_contrib/databases/index.html
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:
```rust
#[get("/tasks")]
fn get_tasks(conn: DbConn) -> QueryResult<Json<Vec<Task>>> {
all_tasks.order(tasks::id.desc())
.load::<Task>(&*conn)
.map(|tasks| Json(tasks))
}
```