From cdd03e77f195061fe43b7c65c537d782af0fd073 Mon Sep 17 00:00:00 2001
From: Sebastian Webber <sebastian@swebber.me>
Date: Thu, 20 Jul 2023 19:16:11 -0300
Subject: [PATCH 1/2] add auth_query_database config

This commit adds the new config auth_query_database to allow use a
different database to get the hashes from the database instance.

I updated the example folder with functional code to validate the new
feature and updated the config docs.

Also updated ruby code to use the new env var LOG_LEVEL.

Signed-off-by: Sebastian Webber <sebastian@swebber.me>
---
 .gitignore                              |   1 +
 CONFIG.md                               |  42 ++++++++
 docker-compose.yml                      |   4 +
 examples/docker/01-setup.sql            |   2 +
 examples/docker/02-add-users-and-db.sql |   3 +
 examples/docker/03-query-auth.sql       |  17 ++++
 examples/docker/pgcat.toml              | 125 +++++-------------------
 pgcat.toml                              |   4 +
 src/auth_passthrough.rs                 |  13 ++-
 src/config.rs                           |  30 ++++++
 src/pool.rs                             |   3 +
 src/query_router.rs                     |   2 +
 src/server.rs                           |   5 +-
 tests/ruby/helpers/pgcat_process.rb     |   2 +-
 14 files changed, 145 insertions(+), 108 deletions(-)
 create mode 100644 examples/docker/01-setup.sql
 create mode 100644 examples/docker/02-add-users-and-db.sql
 create mode 100644 examples/docker/03-query-auth.sql

diff --git a/.gitignore b/.gitignore
index 0b436164..492aafd1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,4 @@ lcov.info
 dev/.bash_history
 dev/cache
 !dev/cache/.keepme
+.bundle
diff --git a/CONFIG.md b/CONFIG.md
index 3118a49a..d81c1e3e 100644
--- a/CONFIG.md
+++ b/CONFIG.md
@@ -297,6 +297,38 @@ Query to be sent to servers to obtain the hash used for md5 authentication. The
 established using the database configured in the pool. This parameter is inherited by every pool
 and can be redefined in pool configuration.
 
+#### Configuration example
+
+> This example uses the `postgres` database.
+
+add on `pgcat.toml`:
+
+```toml
+auth_query = "SELECT * FROM pgcat.user_lookup('$1');"
+auth_query_user = "connection_pooler"
+auth_query_password = "user-look-up-pass"
+auth_query_database = "postgres"
+```
+
+setup in the `postgres` database:
+
+```sql
+CREATE ROLE connection_pooler PASSWORD 'user-look-up-pass' LOGIN;
+CREATE SCHEMA IF NOT EXISTS pgcat;
+
+CREATE OR REPLACE FUNCTION pgcat.user_lookup(i_username text)
+    RETURNS table ("user" text, hash text) AS $$
+SELECT usename as user, passwd as hash FROM pg_catalog.pg_shadow
+WHERE usename = i_username;
+$$ LANGUAGE sql SECURITY DEFINER;
+
+GRANT CONNECT ON DATABASE postgres TO  connection_pooler;
+GRANT USAGE ON SCHEMA pgcat TO connection_pooler;
+
+REVOKE ALL ON FUNCTION pgcat.user_lookup(text) FROM public, connection_pooler;
+GRANT EXECUTE ON FUNCTION pgcat.user_lookup(text) TO connection_pooler;
+```
+
 ### auth_query_user
 ```
 path: pools.<pool_name>.auth_query_user
@@ -319,6 +351,16 @@ Password to be used for connecting to servers to obtain the hash used for md5 au
 specified in `auth_query_user`. The connection will be established using the database configured in the pool.
 This parameter is inherited by every pool and can be redefined in pool configuration.
 
+### auth_query_database
+```
+path: pools.<pool_name>.auth_query_database
+default: <UNSET>
+example: "postgres"
+```
+
+Database to be used for connecting to servers to obtain the hash used for md5 authentication by sending the query
+specified in `auth_query_query`. This parameter is inherited by every pool and can be redefined in pool configuration.
+
 ### automatic_sharding_key
 ```
 path: pools.<pool_name>.automatic_sharding_key
diff --git a/docker-compose.yml b/docker-compose.yml
index 96d1f395..81b094c6 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -5,6 +5,10 @@ services:
     environment:
       POSTGRES_PASSWORD: postgres
       POSTGRES_HOST_AUTH_METHOD: md5
+    ports:
+      - "15432:5432"
+    volumes:
+      - "${PWD}/examples/docker:/docker-entrypoint-initdb.d"
   pgcat:
     build: .
     command:
diff --git a/examples/docker/01-setup.sql b/examples/docker/01-setup.sql
new file mode 100644
index 00000000..9a930f09
--- /dev/null
+++ b/examples/docker/01-setup.sql
@@ -0,0 +1,2 @@
+ALTER SYSTEM SET password_encryption to 'md5';
+SELECT pg_reload_conf();
diff --git a/examples/docker/02-add-users-and-db.sql b/examples/docker/02-add-users-and-db.sql
new file mode 100644
index 00000000..6d1bdc81
--- /dev/null
+++ b/examples/docker/02-add-users-and-db.sql
@@ -0,0 +1,3 @@
+CREATE ROLE myappadmin PASSWORD 'myappadmin' LOGIN;
+CREATE DATABASE myapp owner myappadmin;
+GRANT ALL PRIVILEGES ON DATABASE myapp TO myappadmin;
diff --git a/examples/docker/03-query-auth.sql b/examples/docker/03-query-auth.sql
new file mode 100644
index 00000000..861c87ff
--- /dev/null
+++ b/examples/docker/03-query-auth.sql
@@ -0,0 +1,17 @@
+CREATE ROLE connection_pooler PASSWORD 'user-look-up-pass' LOGIN;
+CREATE SCHEMA IF NOT EXISTS pgcat;
+
+CREATE OR REPLACE FUNCTION pgcat.user_lookup(i_username text)
+    RETURNS table ("user" text, hash text) AS $$
+SELECT usename as user, passwd as hash FROM pg_catalog.pg_shadow
+WHERE usename = i_username;
+$$ LANGUAGE sql SECURITY DEFINER;
+
+-- usage:
+-- SELECT * FROM pgcat.user_lookup('$1');
+
+GRANT CONNECT ON DATABASE postgres TO  connection_pooler;
+GRANT USAGE ON SCHEMA pgcat TO connection_pooler;
+
+REVOKE ALL ON FUNCTION pgcat.user_lookup(text) FROM public, connection_pooler;
+GRANT EXECUTE ON FUNCTION pgcat.user_lookup(text) TO connection_pooler;
diff --git a/examples/docker/pgcat.toml b/examples/docker/pgcat.toml
index 5fd929de..03a96ec1 100644
--- a/examples/docker/pgcat.toml
+++ b/examples/docker/pgcat.toml
@@ -1,123 +1,42 @@
-#
-# PgCat config example.
-#
-
-#
-# General pooler settings
+## pgcat.toml
 [general]
-# What IP to run on, 0.0.0.0 means accessible from everywhere.
 host = "0.0.0.0"
-
-# Port to run on, same as PgBouncer used in this example.
 port = 6432
-
-# Whether to enable prometheus exporter or not.
 enable_prometheus_exporter = true
-
-# Port at which prometheus exporter listens on.
 prometheus_exporter_port = 9930
 
-# How long to wait before aborting a server connection (ms).
-connect_timeout = 5000
-
-# How much time to give `SELECT 1` health check query to return with a result (ms).
-healthcheck_timeout = 1000
-
-# How long to keep connection available for immediate re-use, without running a healthcheck query on it
-healthcheck_delay = 30000
-
-# How much time to give clients during shutdown before forcibly killing client connections (ms).
-shutdown_timeout = 60000
-
-# For how long to ban a server if it fails a health check (seconds).
-ban_time = 60 # seconds
-
-# If we should log client connections
-log_client_connections = false
+log_client_connections = true
+log_client_disconnections = true
 
-# If we should log client disconnections
-log_client_disconnections = false
+server_tls = false
+verify_server_certificate = false
+#tls_certificate = "/etc/postgresql/certificate/server.crt"
+#tls_private_key = "/etc/postgresql/certificate/server.key"
 
-# TLS
-# tls_certificate = "server.cert"
-# tls_private_key = "server.key"
-
-# Credentials to access the virtual administrative database (pgbouncer or pgcat)
-# Connecting to that database allows running commands like `SHOW POOLS`, `SHOW DATABASES`, etc..
 admin_username = "postgres"
 admin_password = "postgres"
 
-# pool
-# configs are structured as pool.<pool_name>
-# the pool_name is what clients use as database name when connecting
-# For the example below a client can connect using "postgres://sharding_user:sharding_user@pgcat_host:pgcat_port/sharded"
-[pools.postgres]
-# Pool mode (see PgBouncer docs for more).
-# session: one server connection per connected client
-# transaction: one server connection per client transaction
-pool_mode = "transaction"
+auth_query = "SELECT * FROM pgcat.user_lookup('$1');"
+auth_query_user = "connection_pooler"
+auth_query_password = "user-look-up-pass"
+auth_query_database = "postgres"
 
-# If the client doesn't specify, route traffic to
-# this role by default.
-#
-# any: round-robin between primary and replicas,
-# replica: round-robin between replicas only without touching the primary,
-# primary: all queries go to the primary unless otherwise specified.
+## session mode
+[pools.myapp]
+pool_mode = "session"
 default_role = "any"
 
-# Query parser. If enabled, we'll attempt to parse
-# every incoming query to determine if it's a read or a write.
-# If it's a read query, we'll direct it to a replica. Otherwise, if it's a write,
-# we'll direct it to the primary.
-query_parser_enabled = true
-
-# If the query parser is enabled and this setting is enabled, the primary will be part of the pool of databases used for
-# load balancing of read queries. Otherwise, the primary will only be used for write
-# queries. The primary can always be explicitly selected with our custom protocol.
-primary_reads_enabled = true
+# query_parser_enabled = true
 
-# So what if you wanted to implement a different hashing function,
-# or you've already built one and you want this pooler to use it?
-#
-# Current options:
-#
-# pg_bigint_hash: PARTITION BY HASH (Postgres hashing function)
-# sha1: A hashing function based on SHA1
-#
 sharding_function = "pg_bigint_hash"
 
-# Credentials for users that may connect to this cluster
-[pools.postgres.users.0]
-username = "postgres"
-password = "postgres"
-# Maximum number of server connections that can be established for this user
-# The maximum number of connection from a single Pgcat process to any database in the cluster
-# is the sum of pool_size across all users.
-pool_size = 9
-
-# Maximum query duration. Dangerous, but protects against DBs that died in a non-obvious way.
-statement_timeout = 0
-
-# Shard 0
-[pools.postgres.shards.0]
-# [ host, port, role ]
-servers = [
-    [ "postgres", 5432, "primary" ],
-    [ "postgres", 5432, "replica" ]
-]
-# Database name (e.g. "postgres")
-database = "postgres"
-
-[pools.postgres.shards.1]
-servers = [
-    [ "postgres", 5432, "primary" ],
-    [ "postgres", 5432, "replica" ],
-]
-database = "postgres"
+[pools.myapp.users.0]
+username = "myappadmin"
+pool_size = 23
 
-[pools.postgres.shards.2]
+[pools.myapp.shards.0]
 servers = [
-    [ "postgres", 5432, "primary" ],
-    [ "postgres", 5432, "replica" ],
+#  [ "localhost", 15432, "primary" ], # to use without docker
+    [ "postgres", 5432, "primary" ],  # to use with docker
 ]
-database = "postgres"
+database = "myapp"
diff --git a/pgcat.toml b/pgcat.toml
index 3e8801b6..c2560a73 100644
--- a/pgcat.toml
+++ b/pgcat.toml
@@ -195,6 +195,10 @@ sharding_function = "pg_bigint_hash"
 # This parameter is inherited by every pool and can be redefined in pool configuration.
 # auth_query_password = "sharding_user"
 
+# Database to be used for connecting to servers to obtain the hash used for md5 authentication by sending the query
+# specified in `auth_query_query`. This parameter is inherited by every pool and can be redefined in pool configuration.
+# auth_query_database = "shared_db"
+
 # Automatically parse this from queries and route queries to the right shard!
 # automatic_sharding_key = "data.id"
 
diff --git a/src/auth_passthrough.rs b/src/auth_passthrough.rs
index fc0f6dc6..10f54824 100644
--- a/src/auth_passthrough.rs
+++ b/src/auth_passthrough.rs
@@ -8,15 +8,17 @@ pub struct AuthPassthrough {
     password: String,
     query: String,
     user: String,
+    database: Option<String>,
 }
 
 impl AuthPassthrough {
     /// Initializes an AuthPassthrough.
-    pub fn new(query: &str, user: &str, password: &str) -> Self {
+    pub fn new(query: &str, user: &str, password: &str, database: Option<String>) -> Self {
         AuthPassthrough {
             password: password.to_string(),
             query: query.to_string(),
             user: user.to_string(),
+            database: database,
         }
     }
 
@@ -28,6 +30,7 @@ impl AuthPassthrough {
                 pool_config.auth_query.as_ref().unwrap(),
                 pool_config.auth_query_user.as_ref().unwrap(),
                 pool_config.auth_query_password.as_ref().unwrap(),
+                pool_config.auth_query_database.clone(),
             ));
         }
 
@@ -64,7 +67,7 @@ impl AuthPassthrough {
     /// ```
     /// use pgcat::auth_passthrough::AuthPassthrough;
     /// use pgcat::config::Address;
-    /// let auth_passthrough = AuthPassthrough::new("SELECT * FROM public.user_lookup('$1');", "postgres", "postgres");
+    /// let auth_passthrough = AuthPassthrough::new("SELECT * FROM public.user_lookup('$1');", "postgres", "postgres", None);
     /// auth_passthrough.fetch_hash(&Address::default());
     /// ```
     ///
@@ -87,7 +90,11 @@ impl AuthPassthrough {
 
         let auth_query = self.query.replace("$1", user);
 
-        match Server::exec_simple_query(address, &auth_user, &auth_query).await {
+        let mut query_address = address.clone();
+        if self.database.is_some() {
+            query_address.database = self.database.clone().unwrap();
+        }
+        match Server::exec_simple_query(&query_address, &auth_user, &auth_query).await {
             Ok(password_data) => {
                 if password_data.len() == 2 && password_data.first().unwrap() == user {
                     if let Some(stripped_hash) = password_data
diff --git a/src/config.rs b/src/config.rs
index 9228b9bb..ed53d61d 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -322,6 +322,7 @@ pub struct General {
     pub auth_query: Option<String>,
     pub auth_query_user: Option<String>,
     pub auth_query_password: Option<String>,
+    pub auth_query_database: Option<String>,
 
     #[serde(default)]
     pub prepared_statements: bool,
@@ -448,6 +449,7 @@ impl Default for General {
             auth_query: None,
             auth_query_user: None,
             auth_query_password: None,
+            auth_query_database: None,
             server_lifetime: Self::default_server_lifetime(),
             server_round_robin: Self::default_server_round_robin(),
             validate_config: true,
@@ -538,6 +540,7 @@ pub struct Pool {
     pub auth_query: Option<String>,
     pub auth_query_user: Option<String>,
     pub auth_query_password: Option<String>,
+    pub auth_query_database: Option<String>,
 
     #[serde(default = "Pool::default_cleanup_server_connections")]
     pub cleanup_server_connections: bool,
@@ -674,6 +677,7 @@ impl Default for Pool {
             auth_query: None,
             auth_query_user: None,
             auth_query_password: None,
+            auth_query_database: None,
             server_lifetime: None,
             plugins: None,
             cleanup_server_connections: true,
@@ -876,6 +880,10 @@ impl Config {
             if pool.auth_query_password.is_none() {
                 pool.auth_query_password = self.general.auth_query_password.clone();
             }
+
+            if pool.auth_query_database.is_none() {
+                pool.auth_query_database = self.general.auth_query_database.clone();
+            }
         }
     }
 }
@@ -1011,6 +1019,28 @@ impl Config {
             self.general.server_lifetime
         );
         info!("Sever round robin: {}", self.general.server_round_robin);
+
+        if self.general.auth_query.is_some() {
+            info!(
+                "Auth query configured: {}",
+                self.general.auth_query.clone().unwrap()
+            );
+        }
+
+        if self.general.auth_query_user.is_some() {
+            info!(
+                "Auth query user: {}",
+                self.general.auth_query_user.clone().unwrap()
+            );
+        }
+
+        if self.general.auth_query_database.is_some() {
+            info!(
+                "Auth query database: {}",
+                self.general.auth_query_database.clone().unwrap()
+            );
+        }
+
         match self.general.tls_certificate.clone() {
             Some(tls_certificate) => {
                 info!("TLS certificate: {}", tls_certificate);
diff --git a/src/pool.rs b/src/pool.rs
index b9293521..b761d9bb 100644
--- a/src/pool.rs
+++ b/src/pool.rs
@@ -142,6 +142,7 @@ pub struct PoolSettings {
     pub auth_query: Option<String>,
     pub auth_query_user: Option<String>,
     pub auth_query_password: Option<String>,
+    pub auth_query_database: Option<String>,
 
     /// Plugins
     pub plugins: Option<Plugins>,
@@ -169,6 +170,7 @@ impl Default for PoolSettings {
             auth_query: None,
             auth_query_user: None,
             auth_query_password: None,
+            auth_query_database: None,
             plugins: None,
         }
     }
@@ -474,6 +476,7 @@ impl ConnectionPool {
                         auth_query: pool_config.auth_query.clone(),
                         auth_query_user: pool_config.auth_query_user.clone(),
                         auth_query_password: pool_config.auth_query_password.clone(),
+                        auth_query_database: pool_config.auth_query_database.clone(),
                         plugins: match pool_config.plugins {
                             Some(ref plugins) => Some(plugins.clone()),
                             None => config.plugins.clone(),
diff --git a/src/query_router.rs b/src/query_router.rs
index 126b8138..36a94506 100644
--- a/src/query_router.rs
+++ b/src/query_router.rs
@@ -1176,6 +1176,7 @@ mod test {
             auth_query: None,
             auth_query_password: None,
             auth_query_user: None,
+            auth_query_database: None,
             db: "test".to_string(),
             plugins: None,
         };
@@ -1251,6 +1252,7 @@ mod test {
             auth_query: None,
             auth_query_password: None,
             auth_query_user: None,
+            auth_query_database: None,
             db: "test".to_string(),
             plugins: None,
         };
diff --git a/src/server.rs b/src/server.rs
index fa68b678..bc877120 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -1194,7 +1194,10 @@ impl Server {
     ) -> Result<Vec<String>, Error> {
         let client_server_map: ClientServerMap = Arc::new(Mutex::new(HashMap::new()));
 
-        debug!("Connecting to server to obtain auth hashes.");
+        debug!(
+            "Connecting to server to obtain auth hashes from db '{}'.",
+            &address.database
+        );
         let mut server = Server::startup(
             address,
             user,
diff --git a/tests/ruby/helpers/pgcat_process.rb b/tests/ruby/helpers/pgcat_process.rb
index e1dbea8b..b2bffc95 100644
--- a/tests/ruby/helpers/pgcat_process.rb
+++ b/tests/ruby/helpers/pgcat_process.rb
@@ -18,7 +18,7 @@ def self.finalize(pid, log_filename, config_filename)
   end
 
   def initialize(log_level)
-    @env = {"RUST_LOG" => log_level}
+    @env = {"LOG_LEVEL" => log_level}
     @port = rand(20000..32760)
     @log_level = log_level
     @log_filename = "/tmp/pgcat_log_#{SecureRandom.urlsafe_base64}.log"

From e41ed754a87a053d736f2696ce5a6083aaaaf9ca Mon Sep 17 00:00:00 2001
From: Sebastian Webber <sebastian@swebber.me>
Date: Mon, 7 Aug 2023 15:17:26 -0300
Subject: [PATCH 2/2] add integration test

Signed-off-by: Sebastian Webber <sebastian@swebber.me>
---
 tests/ruby/auth_query_spec.rb | 173 +++++++++++++++++++++-------------
 1 file changed, 105 insertions(+), 68 deletions(-)

diff --git a/tests/ruby/auth_query_spec.rb b/tests/ruby/auth_query_spec.rb
index 1ac62164..8e2746d3 100644
--- a/tests/ruby/auth_query_spec.rb
+++ b/tests/ruby/auth_query_spec.rb
@@ -3,11 +3,14 @@
 require_relative 'spec_helper'
 require_relative 'helpers/auth_query_helper'
 
-describe "Auth Query" do
-  let(:configured_instances) {[5432, 10432]}
+describe 'Auth Query' do
+  let(:configured_instances) { [5432, 10_432] }
   let(:config_user) { { 'username' => 'sharding_user', 'password' => 'sharding_user' } }
   let(:pg_user) { { 'username' => 'sharding_user', 'password' => 'sharding_user' } }
-  let(:processes) { Helpers::AuthQuery.single_shard_auth_query(pool_name: "sharded_db", pg_user: pg_user, config_user: config_user, extra_conf: config, wait_until_ready: wait_until_ready ) }
+  let(:processes) do
+    Helpers::AuthQuery.single_shard_auth_query(pool_name: 'sharded_db', pg_user: pg_user, config_user: config_user,
+                                               extra_conf: config, wait_until_ready: wait_until_ready)
+  end
   let(:config) { {} }
   let(:wait_until_ready) { true }
 
@@ -19,21 +22,25 @@
     @failing_process = false
   end
 
-  context "when auth_query is not configured" do
+  context 'when auth_query is not configured' do
     context 'and cleartext passwords are set' do
-      it "uses local passwords" do
-        conn = PG.connect(processes.pgcat.connection_string("sharded_db", config_user['username'], config_user['password']))
+      it 'uses local passwords' do
+        conn = PG.connect(processes.pgcat.connection_string('sharded_db', config_user['username'],
+                                                            config_user['password']))
 
-        expect(conn.async_exec("SELECT 1 + 2")).not_to be_nil
+        expect(conn.async_exec('SELECT 1 + 2')).not_to be_nil
       end
     end
 
     context 'and cleartext passwords are not set' do
       let(:config_user) { { 'username' => 'sharding_user' } }
 
-      it "does not start because it is not possible to authenticate" do
+      it 'does not start because it is not possible to authenticate' do
         @failing_process = true
-        expect { processes.pgcat }.to raise_error(StandardError, /You have to specify a user password for every pool if auth_query is not specified/)
+        expect do
+          processes.pgcat
+        end.to raise_error(StandardError,
+                           /You have to specify a user password for every pool if auth_query is not specified/)
       end
     end
   end
@@ -41,12 +48,11 @@
   context 'when auth_query is configured' do
     context 'with global configuration' do
       around(:example) do |example|
-
         # Set up auth query
         Helpers::AuthQuery.set_up_auth_query_for_user(
           user: 'md5_auth_user',
           password: 'secret'
-        );
+        )
 
         example.run
 
@@ -54,15 +60,36 @@
         Helpers::AuthQuery.tear_down_auth_query_for_user(
           user: 'md5_auth_user',
           password: 'secret'
-        );
+        )
+      end
+
+      context 'with different lookup database' do
+        let(:config) do
+          { 'general' => {
+            'auth_query' => "SELECT * FROM public.user_lookup2('$1');",
+            'auth_query_user' => 'md5_auth_use2r',
+            'auth_query_password' => 'secret',
+            'auth_query_database' => 'lookup_db' ## doesn't exist yet
+          } }
+        end
+
+        it 'it uses obtained passwords' do ## should fail
+          connection_string = processes.pgcat.connection_string('sharded_db', pg_user['username'])
+          conn = PG.connect(connection_string)
+
+          expect(conn.exec('SELECT 1 + 2')).not_to be_nil
+        end
       end
 
       context 'with correct global parameters' do
-        let(:config) { { 'general' => { 'auth_query' => "SELECT * FROM public.user_lookup('$1');", 'auth_query_user' => 'md5_auth_user', 'auth_query_password' => 'secret' } } }
+        let(:config) do
+          { 'general' => { 'auth_query' => "SELECT * FROM public.user_lookup('$1');", 'auth_query_user' => 'md5_auth_user',
+                           'auth_query_password' => 'secret' } }
+        end
         context 'and with cleartext passwords set' do
           it 'it uses local passwords' do
-            conn = PG.connect(processes.pgcat.connection_string("sharded_db", pg_user['username'], pg_user['password']))
-            expect(conn.exec("SELECT 1 + 2")).not_to be_nil
+            conn = PG.connect(processes.pgcat.connection_string('sharded_db', pg_user['username'], pg_user['password']))
+            expect(conn.exec('SELECT 1 + 2')).not_to be_nil
           end
         end
 
@@ -70,68 +97,75 @@
           let(:config_user) { { 'username' => 'sharding_user', 'password' => 'sharding_user' } }
 
           it 'it uses obtained passwords' do
-            connection_string = processes.pgcat.connection_string("sharded_db", pg_user['username'], pg_user['password'])
+            connection_string = processes.pgcat.connection_string('sharded_db', pg_user['username'],
+                                                                  pg_user['password'])
             conn = PG.connect(connection_string)
-            expect(conn.async_exec("SELECT 1 + 2")).not_to be_nil
+            expect(conn.async_exec('SELECT 1 + 2')).not_to be_nil
           end
 
           it 'allows passwords to be changed without closing existing connections' do
-            pgconn = PG.connect(processes.pgcat.connection_string("sharded_db", pg_user['username']))
-            expect(pgconn.exec("SELECT 1 + 2")).not_to be_nil
+            pgconn = PG.connect(processes.pgcat.connection_string('sharded_db', pg_user['username']))
+            expect(pgconn.exec('SELECT 1 + 2')).not_to be_nil
             Helpers::AuthQuery.exec_in_instances(query: "ALTER USER #{pg_user['username']} WITH ENCRYPTED PASSWORD 'secret2';")
-            expect(pgconn.exec("SELECT 1 + 4")).not_to be_nil
+            expect(pgconn.exec('SELECT 1 + 4')).not_to be_nil
             Helpers::AuthQuery.exec_in_instances(query: "ALTER USER #{pg_user['username']} WITH ENCRYPTED PASSWORD '#{pg_user['password']}';")
           end
 
           it 'allows passwords to be changed and that new password is needed when reconnecting' do
-            pgconn = PG.connect(processes.pgcat.connection_string("sharded_db", pg_user['username']))
-            expect(pgconn.exec("SELECT 1 + 2")).not_to be_nil
+            pgconn = PG.connect(processes.pgcat.connection_string('sharded_db', pg_user['username']))
+            expect(pgconn.exec('SELECT 1 + 2')).not_to be_nil
             Helpers::AuthQuery.exec_in_instances(query: "ALTER USER #{pg_user['username']} WITH ENCRYPTED PASSWORD 'secret2';")
-            newconn = PG.connect(processes.pgcat.connection_string("sharded_db", pg_user['username'], 'secret2'))
-            expect(newconn.exec("SELECT 1 + 2")).not_to be_nil
+            newconn = PG.connect(processes.pgcat.connection_string('sharded_db', pg_user['username'], 'secret2'))
+            expect(newconn.exec('SELECT 1 + 2')).not_to be_nil
             Helpers::AuthQuery.exec_in_instances(query: "ALTER USER #{pg_user['username']} WITH ENCRYPTED PASSWORD '#{pg_user['password']}';")
           end
         end
       end
 
       context 'with wrong parameters' do
-        let(:config) { { 'general' => { 'auth_query' => 'SELECT 1', 'auth_query_user' => 'wrong_user', 'auth_query_password' => 'wrong' } } }
+        let(:config) do
+          { 'general' => { 'auth_query' => 'SELECT 1', 'auth_query_user' => 'wrong_user',
+                           'auth_query_password' => 'wrong' } }
+        end
 
         context 'and with clear text passwords set' do
-          it "it uses local passwords" do
-            conn = PG.connect(processes.pgcat.connection_string("sharded_db", pg_user['username'], pg_user['password']))
+          it 'it uses local passwords' do
+            conn = PG.connect(processes.pgcat.connection_string('sharded_db', pg_user['username'], pg_user['password']))
 
-            expect(conn.async_exec("SELECT 1 + 2")).not_to be_nil
+            expect(conn.async_exec('SELECT 1 + 2')).not_to be_nil
           end
         end
 
         context 'and with cleartext passwords not set' do
           let(:config_user) { { 'username' => 'sharding_user' } }
-          it "it fails to start as it cannot authenticate against servers" do
+          it 'it fails to start as it cannot authenticate against servers' do
             @failing_process = true
-            expect { PG.connect(processes.pgcat.connection_string("sharded_db", pg_user['username'], pg_user['password'])) }.to raise_error(StandardError, /Error trying to obtain password from auth_query/ )
+            expect do
+              PG.connect(processes.pgcat.connection_string('sharded_db', pg_user['username'],
+                                                           pg_user['password']))
+            end.to raise_error(StandardError, /Error trying to obtain password from auth_query/)
           end
 
           context 'and we fix the issue and reload' do
             let(:wait_until_ready) { false }
 
             it 'fails in the beginning but starts working after reloading config' do
-              connection_string = processes.pgcat.connection_string("sharded_db", pg_user['username'], pg_user['password'])
-              while !(processes.pgcat.logs =~ /Waiting for clients/) do
-                sleep 0.5
-              end
+              connection_string = processes.pgcat.connection_string('sharded_db', pg_user['username'],
+                                                                    pg_user['password'])
+              sleep 0.5 until processes.pgcat.logs =~ /Waiting for clients/
 
-              expect { PG.connect(connection_string)}.to raise_error(PG::ConnectionBad)
+              expect { PG.connect(connection_string) }.to raise_error(PG::ConnectionBad)
               expect(processes.pgcat.logs).to match(/Error trying to obtain password from auth_query/)
 
               current_config = processes.pgcat.current_config
-              config = { 'general' => { 'auth_query' => "SELECT * FROM public.user_lookup('$1');", 'auth_query_user' => 'md5_auth_user', 'auth_query_password' => 'secret' } }
+              config = { 'general' => { 'auth_query' => "SELECT * FROM public.user_lookup('$1');",
+                                        'auth_query_user' => 'md5_auth_user', 'auth_query_password' => 'secret' } }
               processes.pgcat.update_config(current_config.deep_merge(config))
               processes.pgcat.reload_config
 
               conn = nil
-              expect { conn = PG.connect(connection_string)}.not_to raise_error
-              expect(conn.async_exec("SELECT 1 + 2")).not_to be_nil
+              expect { conn = PG.connect(connection_string) }.not_to raise_error
+              expect(conn.async_exec('SELECT 1 + 2')).not_to be_nil
             end
           end
         end
@@ -140,18 +174,17 @@
 
     context 'with per pool configuration' do
       around(:example) do |example|
-
         # Set up auth query
         Helpers::AuthQuery.set_up_auth_query_for_user(
           user: 'md5_auth_user',
           password: 'secret'
-        );
+        )
 
         Helpers::AuthQuery.set_up_auth_query_for_user(
           user: 'md5_auth_user1',
           password: 'secret',
           database: 'shard1'
-        );
+        )
 
         example.run
 
@@ -159,40 +192,43 @@
         Helpers::AuthQuery.tear_down_auth_query_for_user(
           user: 'md5_auth_user',
           password: 'secret'
-        );
+        )
 
         Helpers::AuthQuery.tear_down_auth_query_for_user(
           user: 'md5_auth_user1',
           password: 'secret',
           database: 'shard1'
-        );
+        )
       end
 
       context 'with correct parameters' do
-        let(:processes) { Helpers::AuthQuery.two_pools_auth_query(pool_names: ["sharded_db0", "sharded_db1"], pg_user: pg_user, config_user: config_user, extra_conf: config ) }
-        let(:config) {
-          { 'pools' =>
-            {
-              'sharded_db0' => {
-                'auth_query' => "SELECT * FROM public.user_lookup('$1');",
-                'auth_query_user' => 'md5_auth_user',
-                'auth_query_password' => 'secret'
-              },
-              'sharded_db1' => {
-                'auth_query' => "SELECT * FROM public.user_lookup('$1');",
-                'auth_query_user' => 'md5_auth_user1',
-                'auth_query_password' => 'secret'
-              },
+        let(:processes) do
+          Helpers::AuthQuery.two_pools_auth_query(pool_names: %w[sharded_db0 sharded_db1], pg_user: pg_user,
+                                                  config_user: config_user, extra_conf: config)
+        end
+        let(:config) do
+          { 'pools' => {
+            'sharded_db0' => {
+              'auth_query' => "SELECT * FROM public.user_lookup('$1');",
+              'auth_query_user' => 'md5_auth_user',
+              'auth_query_password' => 'secret'
+            },
+            'sharded_db1' => {
+              'auth_query' => "SELECT * FROM public.user_lookup('$1');",
+              'auth_query_user' => 'md5_auth_user1',
+              'auth_query_password' => 'secret'
             }
-          }
-        } 
+          } }
+        end
 
         context 'and with cleartext passwords set' do
           it 'it uses local passwords' do
-            conn = PG.connect(processes.pgcat.connection_string("sharded_db0", pg_user['username'], pg_user['password']))
-            expect(conn.exec("SELECT 1 + 2")).not_to be_nil
-            conn = PG.connect(processes.pgcat.connection_string("sharded_db1", pg_user['username'], pg_user['password']))
-            expect(conn.exec("SELECT 1 + 2")).not_to be_nil
+            conn = PG.connect(processes.pgcat.connection_string('sharded_db0', pg_user['username'],
+                                                                pg_user['password']))
+            expect(conn.exec('SELECT 1 + 2')).not_to be_nil
+            conn = PG.connect(processes.pgcat.connection_string('sharded_db1', pg_user['username'],
+                                                                pg_user['password']))
+            expect(conn.exec('SELECT 1 + 2')).not_to be_nil
           end
         end
 
@@ -200,15 +236,16 @@
           let(:config_user) { { 'username' => 'sharding_user' } }
 
           it 'it uses obtained passwords' do
-            connection_string = processes.pgcat.connection_string("sharded_db0", pg_user['username'], pg_user['password'])
+            connection_string = processes.pgcat.connection_string('sharded_db0', pg_user['username'],
+                                                                  pg_user['password'])
             conn = PG.connect(connection_string)
-            expect(conn.async_exec("SELECT 1 + 2")).not_to be_nil
-            connection_string = processes.pgcat.connection_string("sharded_db1", pg_user['username'], pg_user['password'])
+            expect(conn.async_exec('SELECT 1 + 2')).not_to be_nil
+            connection_string = processes.pgcat.connection_string('sharded_db1', pg_user['username'],
+                                                                  pg_user['password'])
             conn = PG.connect(connection_string)
-            expect(conn.async_exec("SELECT 1 + 2")).not_to be_nil
+            expect(conn.async_exec('SELECT 1 + 2')).not_to be_nil
           end
         end
-
       end
     end
   end