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}