1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
//! # dhb-heroku-postgres
//! Given a DATABASE_URL, it should be dead simple to connect to a Heroku postgres database.
//! 
//! This crate makes it dead simple:
//!
//! You pass a DATABASE_URL to the postgres_client function and get a working client back, as in
//! ```rust,no_run
//! let mut client = get_client(&database_url);
//! ```
//! If you want a connection pool, you'll also want to pass in a maximum number of connections.
//! ```rust, no_run
//! let max_size = 20;
//! let mut pool = get_pool(&database_url, max_size);
//! ```
//!  
//! The reason I found the work to create this crate useful is that connecting to Heroku postgres has 2 quirks.
//! 1. On the one hand, it requires that we have a secure connection.
//! 2. On the other hand, it uses self-verified certificates.  So we have to enable ssl, but turn off verification.  

// postgres connection
use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode};
pub use postgres::Client;
use postgres_openssl::MakeTlsConnector;

pub use postgres;

// r2d2
pub use r2d2;
pub use r2d2_postgres;

fn get_connector() -> MakeTlsConnector {
    // Create Ssl postgres connector without verification as required to connect to Heroku.
    let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
    builder.set_verify(SslVerifyMode::NONE);
    MakeTlsConnector::new(builder.build())
}

/// Get a working client from a postgres url.
///
/// # Example:
/// ```rust,no_run
/// let database_url = "postgres://username:password@host:port/db_name";
/// let mut client = dhb_postgres_heroku::get_client(&database_url);
/// ```
/// # Panics
/// This will panic if it can't connect.  
/// That could be because your database_url is wrong, because your database is down, because your internet connection is failing, etc.
pub fn get_client(database_url: &str) -> Client {
    let connector = get_connector();

    // Create client with Heroku DATABASE_URL
    Client::connect(
        database_url,
        connector,
    ).unwrap()
}

/// 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.
/// 
/// # Example:
/// ```rust,no_run
/// let database_url = "postgres://username:password@host:port/db_name";
/// let mut client = dhb_postgres_heroku::get_client(&database_url);
/// dhb_postgres_heroku::smoke_test(&mut client);
/// ```
pub fn smoke_test(client: &mut Client) {
    // 1. Create table. 
    client.simple_query("
        CREATE TABLE IF NOT EXISTS person_nonconflicting (
            id      SERIAL PRIMARY KEY,
            name    TEXT NOT NULL,
            data    BYTEA
        )
    ").unwrap();

    // 2. Save a row.
    let name = "Ferris";
    let data = None::<&[u8]>;
    client.execute(
        "INSERT INTO person_nonconflicting (name, data) VALUES ($1, $2)",
        &[&name, &data],
    ).unwrap();

    // 3. Retrieve a row and verify by printing.
    for row in client.query("SELECT id, name, data FROM person_nonconflicting", &[]).unwrap() {
        let id: i32 = row.get(0);
        let name: &str = row.get(1);
        let data: Option<&[u8]> = row.get(2);

        println!("found person_nonconflicting: {} {} {:?}", id, name, data);
    }

    // 4. Clean up your mess by dropping the table.
    client.simple_query("DROP TABLE person_nonconflicting").unwrap();
} 

pub type HerokuPool = r2d2::Pool<r2d2_postgres::PostgresConnectionManager<MakeTlsConnector>>;

/// Get a heroku-capable r2d2 connection pool from a postgres url and a maximum size.
/// # Example:
/// ```rust,no_run
/// let database_url = "postgres://username:password@host:port/db_name";
/// let max_size = 4;
/// let mut pool = dhb_postgres_heroku::get_pool(&database_url, max_size);
/// ```
/// # Panics
/// This will panic if it can't connect.  
/// That could be because your database_url is wrong, because your database is down, because your internet connection is failing, etc.
pub fn get_pool(database_url: &str, max_size: u32) ->  HerokuPool {
    let connector = get_connector();
    let manager = r2d2_postgres::PostgresConnectionManager::new(
        database_url.parse().unwrap(),
        connector,
    );
    r2d2::Pool::builder()
        .max_size(max_size)
        .build(manager)
        .unwrap()
}