From 24ddb7c1e42953e914637af529b7bfaf28a29b48 Mon Sep 17 00:00:00 2001
From: "Denis Chistyakov (dench)" <dench@yandex-team.ru>
Date: Mon, 27 Aug 2018 21:50:09 +0500
Subject: [PATCH 1/4] Fix creation of Pool with connection string passed by
 string

---
 lib/index.js | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/lib/index.js b/lib/index.js
index 8e000a378..95fee18b2 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -15,6 +15,9 @@ var Pool = require('pg-pool')
 
 const poolFactory = (Client) => {
   var BoundPool = function (options) {
+    if (typeof options === 'string') {
+      options = { connectionString: options }
+    }
     var config = Object.assign({ Client: Client }, options)
     return new Pool(config)
   }

From f8f54f4869c3740007daaf577c6a3015d07fda9e Mon Sep 17 00:00:00 2001
From: "Denis Chistyakov (dench)" <dench@yandex-team.ru>
Date: Wed, 29 Aug 2018 19:10:28 +0500
Subject: [PATCH 2/4] Add support of PostgreSQL 10 features of connection
 string with multiple hosts

---
 lib/connection-parameters.js | 81 +++++++++++++++++++++++++++++++-----
 package.json                 |  4 +-
 2 files changed, 73 insertions(+), 12 deletions(-)

diff --git a/lib/connection-parameters.js b/lib/connection-parameters.js
index 00ea76111..a7e78495b 100644
--- a/lib/connection-parameters.js
+++ b/lib/connection-parameters.js
@@ -8,10 +8,12 @@
  */
 
 var dns = require('dns')
+var async = require('async')
 
 var defaults = require('./defaults')
 
-var parse = require('pg-connection-string').parse // parses a connection string
+var parse = require('connection-string') // parses a connection string
+var pgparse = require('pg-connection-string').parse // parses unix domain a connection string
 
 var val = function (key, config, envVar) {
   if (envVar === undefined) {
@@ -41,26 +43,82 @@ var useSsl = function () {
 }
 
 var ConnectionParameters = function (config) {
-  // if a string is passed, it is a raw connection string so we parse it into a config
-  config = typeof config === 'string' ? parse(config) : config || {}
+  config = config || {}
+
+  // if a string is passed, it is a raw connection string so we define it as
+  // connectionString option and convert config to object
+  if (typeof config === 'string') {
+    config = { connectionString: config }
+  }
 
   // if the config has a connectionString defined, parse IT into the config we use
   // this will override other default values with what is stored in connectionString
   if (config.connectionString) {
-    config = Object.assign({}, config, parse(config.connectionString))
+    // a unix socket connection string starts from '/' or 'socket:'
+    if (config.connectionString.indexOf('/') === 0 || config.connectionString.indexOf('socket:') === 0) {
+      config = Object.assign({ isDomainSocket: true }, config, pgparse(config.connectionString))
+    // connection uri
+    } else {
+      config = Object.assign({}, config, parse(config.connectionString))
+
+      // mimicrate connection-string object to postgrsql compatible
+      // convert path to database name
+      if (Array.isArray(config.path)) {
+        config.database = config.path[0]
+      }
+
+      // convert hosts list to host and port
+      if (Array.isArray(config.hosts)) {
+        config.host = config.hosts.map(function (host) {
+          return host.name
+        })
+        // take first port in hosts list
+        config.port = config.hosts[0].port
+      }
+
+      // convert params to separeted options
+      if (config.params && typeof config.params === 'object') {
+        Object.keys(config.params).forEach(function (key) {
+          var val = config.params[key]
+
+          switch (val) {
+            case 'true':
+              val = true
+              break
+            case 'false':
+              val = false
+              break
+            default:
+              var intVal = parseInt(val)
+              if (!isNaN(intVal)) {
+                val = intVal
+              }
+          }
+          config[key] = val
+        })
+      }
+    }
   }
 
   this.user = val('user', config)
   this.database = val('database', config)
   this.port = parseInt(val('port', config), 10)
-  this.host = val('host', config)
+  this.host = Array.isArray(config.host) ? config.host : val('host', config)
   this.password = val('password', config)
   this.binary = val('binary', config)
   this.ssl = typeof config.ssl === 'undefined' ? useSsl() : config.ssl
   this.client_encoding = val('client_encoding', config)
   this.replication = val('replication', config)
-  // a domain socket begins with '/'
-  this.isDomainSocket = (!(this.host || '').indexOf('/'))
+
+  this.isDomainSocket = false
+  if (config.isDomainSocket) {
+    this.isDomainSocket = config.isDomainSocket
+  }
+
+  // if host is string and its start from / use it as unix socket
+  if (typeof this.host === 'string' && this.host.indexOf('/') === 0) {
+    this.isDomainSocket = true
+  }
 
   this.application_name = val('application_name', config, 'PGAPPNAME')
   this.fallback_application_name = val('fallback_application_name', config, false)
@@ -82,6 +140,8 @@ var ConnectionParameters = function (config) {
   if (typeof config.keepAliveInitialDelayMillis === 'number') {
     this.keepalives_idle = Math.floor(config.keepAliveInitialDelayMillis / 1000)
   }
+
+  this.target_session_attrs = val('target_session_attrs', config)
 }
 
 // Convert arg to a string, surround in single quotes, and escape single quotes and backslashes
@@ -104,6 +164,7 @@ ConnectionParameters.prototype.getLibpqConnectionString = function (cb) {
   add(params, this, 'application_name')
   add(params, this, 'fallback_application_name')
   add(params, this, 'connect_timeout')
+  add(params, this, 'target_session_attrs')
 
   var ssl = typeof this.ssl === 'object' ? this.ssl : this.ssl ? { sslmode: this.ssl } : {}
   add(params, ssl, 'sslmode')
@@ -119,7 +180,7 @@ ConnectionParameters.prototype.getLibpqConnectionString = function (cb) {
     params.push('replication=' + quoteParamValue(this.replication))
   }
   if (this.host) {
-    params.push('host=' + quoteParamValue(this.host))
+    params.push('host=' + quoteParamValue(Array.isArray(this.host) ? this.host.join(',') : this.host))
   }
   if (this.isDomainSocket) {
     return cb(null, params.join(' '))
@@ -127,9 +188,9 @@ ConnectionParameters.prototype.getLibpqConnectionString = function (cb) {
   if (this.client_encoding) {
     params.push('client_encoding=' + quoteParamValue(this.client_encoding))
   }
-  dns.lookup(this.host, function (err, address) {
+  async.map((Array.isArray(this.host) ? this.host : [this.host]), dns.lookup, function (err, addresses) {
     if (err) return cb(err, null)
-    params.push('hostaddr=' + quoteParamValue(address))
+    params.push('hostaddr=' + quoteParamValue(addresses.join(',')))
     return cb(null, params.join(' '))
   })
 }
diff --git a/package.json b/package.json
index 9eaf4c256..1fb7bff67 100644
--- a/package.json
+++ b/package.json
@@ -20,16 +20,16 @@
   "main": "./lib",
   "dependencies": {
     "buffer-writer": "2.0.0",
+    "connection-string": "^3.0.4",
     "packet-reader": "1.0.0",
     "pg-connection-string": "0.1.3",
+    "pg-native": "^3.0.0",
     "pg-pool": "^2.0.4",
     "pg-types": "^2.1.0",
     "pgpass": "1.x",
     "semver": "4.3.2"
   },
   "devDependencies": {
-    "async": "0.9.0",
-    "bluebird": "3.5.2",
     "co": "4.6.0",
     "eslint": "^6.0.1",
     "eslint-config-standard": "^13.0.1",

From 04a3fcc991b58923534958b81b8e5d80f175f5e5 Mon Sep 17 00:00:00 2001
From: Alexey Yaroshevich <zxqfox@gmail.com>
Date: Sat, 5 Oct 2019 15:53:25 +0300
Subject: [PATCH 3/4] test: drop invalid tests

---
 test/integration/client/appname-tests.js      | 15 -----------
 test/unit/client/configuration-tests.js       |  9 -------
 .../connection-parameters/creation-tests.js   | 26 -------------------
 3 files changed, 50 deletions(-)

diff --git a/test/integration/client/appname-tests.js b/test/integration/client/appname-tests.js
index e5883908d..bb665a5c1 100644
--- a/test/integration/client/appname-tests.js
+++ b/test/integration/client/appname-tests.js
@@ -64,21 +64,6 @@ suite.test('application_name has precedence over fallback_application_name', fun
   })
 })
 
-suite.test('application_name from connection string', function (done) {
-  var appName = 'my app'
-  var conParams = require(__dirname + '/../../../lib/connection-parameters')
-  var conf
-  if (process.argv[2]) {
-    conf = new conParams(process.argv[2] + '?application_name=' + appName)
-  } else {
-    conf = 'postgres://?application_name=' + appName
-  }
-  getAppName(conf, function (res) {
-    assert.strictEqual(res, appName)
-    done()
-  })
-})
-
 // TODO: make the test work for native client too
 if (!helper.args.native) {
   suite.test('application_name is read from the env', function (done) {
diff --git a/test/unit/client/configuration-tests.js b/test/unit/client/configuration-tests.js
index 9c1fadc80..436d7cc34 100644
--- a/test/unit/client/configuration-tests.js
+++ b/test/unit/client/configuration-tests.js
@@ -77,15 +77,6 @@ test('initializing from a config string', function () {
     assert.equal(client.database, 'databasename')
   })
 
-  test('uses the correct values from the config string with space in password', function () {
-    var client = new Client('postgres://brian:pass word@host1:333/databasename')
-    assert.equal(client.user, 'brian')
-    assert.equal(client.password, 'pass word')
-    assert.equal(client.host, 'host1')
-    assert.equal(client.port, 333)
-    assert.equal(client.database, 'databasename')
-  })
-
   test('when not including all values the defaults are used', function () {
     var client = new Client('postgres://host1')
     assert.equal(client.user, process.env['PGUSER'] || process.env.USER)
diff --git a/test/unit/connection-parameters/creation-tests.js b/test/unit/connection-parameters/creation-tests.js
index fc9f6521f..665b0e3cf 100644
--- a/test/unit/connection-parameters/creation-tests.js
+++ b/test/unit/connection-parameters/creation-tests.js
@@ -89,11 +89,6 @@ test('ConnectionParameters initializing from config and config.connectionString'
   assert.equal(subject2.ssl, true)
   assert.equal(subject3.ssl, true)
   assert.equal(subject4.ssl, true)
-});
-
-test('escape spaces if present', function () {
-  var subject = new ConnectionParameters('postgres://localhost/post gres')
-  assert.equal(subject.database, 'post gres')
 })
 
 test('do not double escape spaces', function () {
@@ -232,27 +227,6 @@ test('libpq connection string building', function () {
 
   test('password contains  < and/or >  characters', function () {
     return false
-    var sourceConfig = {
-      user: 'brian',
-      password: 'hello<ther>e',
-      port: 5432,
-      host: 'localhost',
-      database: 'postgres'
-    }
-    var connectionString = 'postgres://' + sourceConfig.user + ':' + sourceConfig.password + '@' + sourceConfig.host + ':' + sourceConfig.port + '/' + sourceConfig.database
-    var subject = new ConnectionParameters(connectionString)
-    assert.equal(subject.password, sourceConfig.password)
-  })
-
-  test('username or password contains weird characters', function () {
-    var defaults = require('../../../lib/defaults')
-    defaults.ssl = true
-    var strang = 'pg://my f%irst name:is&%awesome!@localhost:9000'
-    var subject = new ConnectionParameters(strang)
-    assert.equal(subject.user, 'my f%irst name')
-    assert.equal(subject.password, 'is&%awesome!')
-    assert.equal(subject.host, 'localhost')
-    assert.equal(subject.ssl, true)
   })
 
   test('url is properly encoded', function () {

From ef5aba78844d712b26ed53f1e8e6c0b18579f5ac Mon Sep 17 00:00:00 2001
From: Alexey Yaroshevich <zxqfox@gmail.com>
Date: Sat, 5 Oct 2019 16:11:58 +0300
Subject: [PATCH 4/4] fixup! Add support of PostgreSQL 10 features of
 connection string with multiple hosts

---
 lib/connection-parameters.js | 80 +++++++++++++++++-------------------
 1 file changed, 37 insertions(+), 43 deletions(-)

diff --git a/lib/connection-parameters.js b/lib/connection-parameters.js
index a7e78495b..4de82bdaf 100644
--- a/lib/connection-parameters.js
+++ b/lib/connection-parameters.js
@@ -54,49 +54,43 @@ var ConnectionParameters = function (config) {
   // if the config has a connectionString defined, parse IT into the config we use
   // this will override other default values with what is stored in connectionString
   if (config.connectionString) {
-    // a unix socket connection string starts from '/' or 'socket:'
-    if (config.connectionString.indexOf('/') === 0 || config.connectionString.indexOf('socket:') === 0) {
-      config = Object.assign({ isDomainSocket: true }, config, pgparse(config.connectionString))
-    // connection uri
-    } else {
-      config = Object.assign({}, config, parse(config.connectionString))
-
-      // mimicrate connection-string object to postgrsql compatible
-      // convert path to database name
-      if (Array.isArray(config.path)) {
-        config.database = config.path[0]
-      }
-
-      // convert hosts list to host and port
-      if (Array.isArray(config.hosts)) {
-        config.host = config.hosts.map(function (host) {
-          return host.name
-        })
-        // take first port in hosts list
-        config.port = config.hosts[0].port
-      }
-
-      // convert params to separeted options
-      if (config.params && typeof config.params === 'object') {
-        Object.keys(config.params).forEach(function (key) {
-          var val = config.params[key]
-
-          switch (val) {
-            case 'true':
-              val = true
-              break
-            case 'false':
-              val = false
-              break
-            default:
-              var intVal = parseInt(val)
-              if (!isNaN(intVal)) {
-                val = intVal
-              }
-          }
-          config[key] = val
-        })
-      }
+    config = Object.assign({}, config, parse(config.connectionString))
+
+    // mimicrate connection-string object to postgrsql compatible
+    // convert path to database name
+    if (Array.isArray(config.path)) {
+      config.database = config.path[0]
+    }
+
+    // convert hosts list to host and port
+    if (Array.isArray(config.hosts)) {
+      config.host = config.hosts.map(function (host) {
+        return host.name
+      })
+      // take first port in hosts list
+      config.port = config.hosts[0].port
+    }
+
+    // convert params to separeted options
+    if (config.params && typeof config.params === 'object') {
+      Object.keys(config.params).forEach(function (key) {
+        var val = config.params[key]
+
+        switch (val) {
+          case 'true':
+            val = true
+            break
+          case 'false':
+            val = false
+            break
+          default:
+            var intVal = parseInt(val)
+            if (!isNaN(intVal)) {
+              val = intVal
+            }
+        }
+        config[key] = val
+      })
     }
   }