Skip to content

Commit 5a1a0bb

Browse files
committed
Add new postgres manager builder and support a custom validation query
Issue djc#73 was looking to make a "DISCARD ALL" query possible when a connection is returned to the pool. Because async drop isn't a thing, this isn't really possible at the moment. However, we can use the `is_valid` method on the `ManagedConnection` trait to discard all session state before yielding the connection. This PR makes it possible to change the query used within the `is_valid` method call in `bb8_postgres`. To make configuring the validation query a little easier, I added a new postgres connection manager builder helper type. Let me know if you're :+1: or :-1: on that. This also includes an example called buidler.rs that shows how someone could use `DISCARD ALL` as a validation query. It then prints a few things to show that session state was indeed cleared between checkouts. ``` $ cargo run --example builder ... The current connection PID: 86445 BB8 says, "beep boop" The current connection PID: 86445 After DISCARD ALL on checkout, BB8 says, "" ```
1 parent 5e751fd commit 5a1a0bb

File tree

2 files changed

+155
-3
lines changed

2 files changed

+155
-3
lines changed

postgres/examples/builder.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
use bb8::{Pool, PooledConnection, RunError};
2+
use bb8_postgres::{PostgresConnectionManager, PostgresConnectionManagerBuilder};
3+
4+
use std::error::Error;
5+
use std::str::FromStr;
6+
7+
// Demonstrate the postgres connection manager builder and its validation query.
8+
//
9+
// The simplest way to start the db is using Docker:
10+
// docker run --name gotham-middleware-postgres -e POSTGRES_PASSWORD=mysecretpassword -p 5432:5432 -d postgres
11+
#[tokio::main]
12+
async fn main() -> Result<(), Box<dyn Error>> {
13+
let config =
14+
tokio_postgres::config::Config::from_str("postgresql://postgres:docker@localhost:5432")
15+
.unwrap();
16+
17+
let pg_mgr = PostgresConnectionManagerBuilder::new()
18+
.tls(tokio_postgres::NoTls)
19+
.validation_query("DISCARD ALL".into())
20+
.config(config)
21+
.build()?;
22+
23+
let pool = match Pool::builder().build(pg_mgr).await {
24+
Ok(pool) => pool,
25+
Err(e) => panic!("builder error: {:?}", e),
26+
};
27+
28+
// The first checkout sets a session parameter.
29+
{
30+
let connection = pool.get().await?;
31+
println!(
32+
"The current connection PID: {}",
33+
get_pid(&connection).await?
34+
);
35+
set_session_parameter(&connection, "bb8.greeting", "beep boop").await?;
36+
let greeting = show_session_parameter(&connection, "bb8.greeting").await?;
37+
println!("BB8 says, {:?}", greeting);
38+
}
39+
40+
// The second checkout issues the DISCARD ALL validation query.
41+
{
42+
let connection = pool.get().await?;
43+
println!(
44+
"The current connection PID: {}",
45+
get_pid(&connection).await?
46+
);
47+
let greeting = show_session_parameter(&connection, "bb8.greeting").await?;
48+
println!("After DISCARD ALL on checkout, BB8 says, {:?}", greeting);
49+
}
50+
51+
Ok(())
52+
}
53+
54+
async fn get_pid(
55+
connection: &PooledConnection<'_, PostgresConnectionManager<tokio_postgres::NoTls>>,
56+
) -> Result<i32, RunError<tokio_postgres::Error>> {
57+
let row = connection.query_one("select pg_backend_pid()", &[]).await?;
58+
Ok(row.get::<usize, i32>(0))
59+
}
60+
61+
async fn set_session_parameter(
62+
connection: &PooledConnection<'_, PostgresConnectionManager<tokio_postgres::NoTls>>,
63+
key: &str,
64+
value: &str,
65+
) -> Result<(), RunError<tokio_postgres::Error>> {
66+
let query = format!("SET {} = '{}'", key, value);
67+
connection.query(query.as_str(), &[]).await?;
68+
Ok(())
69+
}
70+
71+
async fn show_session_parameter(
72+
connection: &PooledConnection<'_, PostgresConnectionManager<tokio_postgres::NoTls>>,
73+
key: &str,
74+
) -> Result<String, RunError<tokio_postgres::Error>> {
75+
let query = format!("SHOW {}", key);
76+
let row = connection.query_one(query.as_str(), &[]).await?;
77+
Ok(row.get::<usize, String>(0))
78+
}

postgres/src/lib.rs

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,76 @@ use tokio_postgres::config::Config;
99
use tokio_postgres::tls::{MakeTlsConnect, TlsConnect};
1010
use tokio_postgres::{Client, Error, Socket};
1111

12-
use std::fmt;
1312
use std::str::FromStr;
13+
use std::{error, fmt};
14+
15+
/// An error type for the postgres connection manager builder.
16+
#[derive(Debug, Clone, PartialEq, Eq)]
17+
pub struct BuilderError(String);
18+
19+
impl fmt::Display for BuilderError {
20+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
21+
write!(f, "Builder error: {}", &self)
22+
}
23+
}
24+
25+
impl error::Error for BuilderError {}
26+
27+
/// A builder for a postgres connection manager.
28+
#[derive(Debug)]
29+
pub struct PostgresConnectionManagerBuilder<Tls>
30+
where
31+
Tls: MakeTlsConnect<Socket>,
32+
{
33+
config: Option<Config>,
34+
validation_query: String,
35+
tls: Option<Tls>,
36+
}
37+
38+
impl<Tls> PostgresConnectionManagerBuilder<Tls>
39+
where
40+
Tls: MakeTlsConnect<Socket>,
41+
{
42+
/// Create a new builder.
43+
pub fn new() -> Self {
44+
Self {
45+
config: None,
46+
validation_query: "".into(),
47+
tls: None,
48+
}
49+
}
50+
51+
/// Set the postgres configuration.
52+
pub fn config(mut self, config: Config) -> Self {
53+
self.config = Some(config);
54+
self
55+
}
56+
57+
/// Set the validation query used by [`bb8::ManageConnection::is_valid`].
58+
pub fn validation_query(mut self, query: String) -> Self {
59+
self.validation_query = query;
60+
self
61+
}
62+
63+
/// Set the TLS configuraiton.
64+
pub fn tls(mut self, tls: Tls) -> Self {
65+
self.tls = Some(tls);
66+
self
67+
}
68+
69+
/// Build a postgres connection manager.
70+
pub fn build(self) -> Result<PostgresConnectionManager<Tls>, Box<dyn std::error::Error>> {
71+
Ok(PostgresConnectionManager {
72+
tls: self.tls.ok_or_else(|| {
73+
BuilderError("the tls config was set during the build process".into())
74+
})?,
75+
config: self.config.ok_or_else(|| {
76+
BuilderError("the config was set during the build process".into())
77+
})?,
78+
validation_query: self.validation_query,
79+
})
80+
}
81+
}
1482

1583
/// A `bb8::ManageConnection` for `tokio_postgres::Connection`s.
1684
#[derive(Clone)]
@@ -20,6 +88,7 @@ where
2088
{
2189
config: Config,
2290
tls: Tls,
91+
validation_query: String,
2392
}
2493

2594
impl<Tls> PostgresConnectionManager<Tls>
@@ -28,7 +97,11 @@ where
2897
{
2998
/// Create a new `PostgresConnectionManager` with the specified `config`.
3099
pub fn new(config: Config, tls: Tls) -> PostgresConnectionManager<Tls> {
31-
PostgresConnectionManager { config, tls }
100+
PostgresConnectionManager {
101+
config,
102+
tls,
103+
validation_query: "".into(),
104+
}
32105
}
33106

34107
/// Create a new `PostgresConnectionManager`, parsing the config from `params`.
@@ -68,7 +141,8 @@ where
68141
&self,
69142
conn: &mut bb8::PooledConnection<'_, Self>,
70143
) -> Result<(), Self::Error> {
71-
conn.simple_query("").await.map(|_| ())
144+
conn.simple_query(&self.validation_query).await?;
145+
Ok(())
72146
}
73147

74148
fn has_broken(&self, conn: &mut Self::Connection) -> bool {

0 commit comments

Comments
 (0)