dhb_postgres_heroku/
lib.rs

1//! # dhb-heroku-postgres
2//! Given a DATABASE_URL, it should be dead simple to connect to a Heroku postgres database.
3//! 
4//! This crate makes it dead simple:
5//!
6//! You pass a DATABASE_URL to the postgres_client function and get a working client back, as in
7//! ```rust,no_run
8//! let mut client = get_client(&database_url);
9//! ```
10//! If you want a connection pool, you'll also want to pass in a maximum number of connections.
11//! ```rust, no_run
12//! let max_size = 20;
13//! let mut pool = get_pool(&database_url, max_size);
14//! ```
15//!  
16//! The reason I found the work to create this crate useful is that connecting to Heroku postgres has 2 quirks.
17//! 1. On the one hand, it requires that we have a secure connection.
18//! 2. On the other hand, it uses self-verified certificates.  So we have to enable ssl, but turn off verification.  
19
20// postgres connection
21use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode};
22pub use postgres::Client;
23use postgres_openssl::MakeTlsConnector;
24
25pub use postgres;
26
27// r2d2
28pub use r2d2;
29pub use r2d2_postgres;
30
31fn get_connector() -> MakeTlsConnector {
32    // Create Ssl postgres connector without verification as required to connect to Heroku.
33    let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
34    builder.set_verify(SslVerifyMode::NONE);
35    MakeTlsConnector::new(builder.build())
36}
37
38/// Get a working client from a postgres url.
39///
40/// # Example:
41/// ```rust,no_run
42/// let database_url = "postgres://username:password@host:port/db_name";
43/// let mut client = dhb_postgres_heroku::get_client(&database_url);
44/// ```
45/// # Panics
46/// This will panic if it can't connect.  
47/// That could be because your database_url is wrong, because your database is down, because your internet connection is failing, etc.
48pub fn get_client(database_url: &str) -> Client {
49    let connector = get_connector();
50
51    // Create client with Heroku DATABASE_URL
52    Client::connect(
53        database_url,
54        connector,
55    ).unwrap()
56}
57
58/// Try out a client by running through a set of postgres commands to create a table, insert a row, read the row, and drop the table.
59/// 
60/// # Example:
61/// ```rust,no_run
62/// let database_url = "postgres://username:password@host:port/db_name";
63/// let mut client = dhb_postgres_heroku::get_client(&database_url);
64/// dhb_postgres_heroku::smoke_test(&mut client);
65/// ```
66pub fn smoke_test(client: &mut Client) {
67    // 1. Create table. 
68    client.simple_query("
69        CREATE TABLE IF NOT EXISTS person_nonconflicting (
70            id      SERIAL PRIMARY KEY,
71            name    TEXT NOT NULL,
72            data    BYTEA
73        )
74    ").unwrap();
75
76    // 2. Save a row.
77    let name = "Ferris";
78    let data = None::<&[u8]>;
79    client.execute(
80        "INSERT INTO person_nonconflicting (name, data) VALUES ($1, $2)",
81        &[&name, &data],
82    ).unwrap();
83
84    // 3. Retrieve a row and verify by printing.
85    for row in client.query("SELECT id, name, data FROM person_nonconflicting", &[]).unwrap() {
86        let id: i32 = row.get(0);
87        let name: &str = row.get(1);
88        let data: Option<&[u8]> = row.get(2);
89
90        println!("found person_nonconflicting: {} {} {:?}", id, name, data);
91    }
92
93    // 4. Clean up your mess by dropping the table.
94    client.simple_query("DROP TABLE person_nonconflicting").unwrap();
95} 
96
97pub type HerokuPool = r2d2::Pool<r2d2_postgres::PostgresConnectionManager<MakeTlsConnector>>;
98
99/// Get a heroku-capable r2d2 connection pool from a postgres url and a maximum size.
100/// # Example:
101/// ```rust,no_run
102/// let database_url = "postgres://username:password@host:port/db_name";
103/// let max_size = 4;
104/// let mut pool = dhb_postgres_heroku::get_pool(&database_url, max_size);
105/// ```
106/// # Panics
107/// This will panic if it can't connect.  
108/// That could be because your database_url is wrong, because your database is down, because your internet connection is failing, etc.
109pub fn get_pool(database_url: &str, max_size: u32) ->  HerokuPool {
110    let connector = get_connector();
111    let manager = r2d2_postgres::PostgresConnectionManager::new(
112        database_url.parse().unwrap(),
113        connector,
114    );
115    r2d2::Pool::builder()
116        .max_size(max_size)
117        .build(manager)
118        .unwrap()
119}
120
121
122/// This type is defined so that you can pass around a connection after obtaining it from a connection pool.
123pub type HerokuPooledConnection = r2d2::PooledConnection<r2d2_postgres::PostgresConnectionManager<postgres_openssl::MakeTlsConnector>>;
124
125/// This function demonstrates how to get a HerokuPooledConnection from a HerokuPool.
126pub fn get_connection(pool: HerokuPool) -> HerokuPooledConnection {
127    pool.get().unwrap()
128}