Skip to content

Commit 3379ccd

Browse files
committed
Add support of PostgreSQL 10 features of connection string with multiple hosts
1 parent 04668de commit 3379ccd

File tree

5 files changed

+76
-63
lines changed

5 files changed

+76
-63
lines changed

lib/connection-parameters.js

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@
88
*/
99

1010
var dns = require('dns')
11+
var async = require('async')
1112

1213
var defaults = require('./defaults')
1314

14-
var parse = require('pg-connection-string').parse // parses a connection string
15+
var parse = require('connection-string') // parses a connection string
16+
var pgparse = require('pg-connection-string').parse // parses unix domain a connection string
1517

1618
var val = function (key, config, envVar) {
1719
if (envVar === undefined) {
@@ -41,30 +43,88 @@ var useSsl = function () {
4143
}
4244

4345
var ConnectionParameters = function (config) {
44-
// if a string is passed, it is a raw connection string so we parse it into a config
45-
config = typeof config === 'string' ? parse(config) : config || {}
46+
config = config || {}
47+
48+
// if a string is passed, it is a raw connection string so we define it as
49+
// connectionString option and convert config to object
50+
if (typeof config === 'string') {
51+
config = { connectionString: config }
52+
}
4653

4754
// if the config has a connectionString defined, parse IT into the config we use
4855
// this will override other default values with what is stored in connectionString
4956
if (config.connectionString) {
50-
config = Object.assign({}, config, parse(config.connectionString))
57+
// a unix socket connection string starts from '/' or 'socket:'
58+
if (config.connectionString.indexOf('/') === 0 || config.connectionString.indexOf('socket:') === 0) {
59+
config = Object.assign({ isDomainSocket: true }, config, pgparse(config.connectionString))
60+
// connection uri
61+
} else {
62+
config = Object.assign({}, config, parse(config.connectionString))
63+
64+
// mimicrate connection-string object to postgrsql compatible
65+
// convert path to database name
66+
if (Array.isArray(config.path)) {
67+
config.database = config.path[0]
68+
}
69+
70+
// convert hosts list to host and port
71+
if (Array.isArray(config.hosts)) {
72+
config.host = config.hosts.map(function (host) {
73+
return host.name
74+
})
75+
// take first port in hosts list
76+
config.port = config.hosts[0].port
77+
}
78+
79+
// convert params to separeted options
80+
if (config.params && typeof config.params === 'object') {
81+
Object.keys(config.params).forEach(function (key) {
82+
var val = config.params[key]
83+
84+
switch (val) {
85+
case 'true':
86+
val = true
87+
break
88+
case 'false':
89+
val = false
90+
break
91+
default:
92+
var intVal = parseInt(val)
93+
if (!isNaN(intVal)) {
94+
val = intVal
95+
}
96+
}
97+
config[key] = val
98+
})
99+
}
100+
}
51101
}
52102

53103
this.user = val('user', config)
54104
this.database = val('database', config)
55105
this.port = parseInt(val('port', config), 10)
56-
this.host = val('host', config)
106+
this.host = Array.isArray(config.host) ? config.host : val('host', config)
57107
this.password = val('password', config)
58108
this.binary = val('binary', config)
59109
this.ssl = typeof config.ssl === 'undefined' ? useSsl() : config.ssl
60110
this.client_encoding = val('client_encoding', config)
61111
this.replication = val('replication', config)
62-
// a domain socket begins with '/'
63-
this.isDomainSocket = (!(this.host || '').indexOf('/'))
112+
113+
this.isDomainSocket = false
114+
if (config.isDomainSocket) {
115+
this.isDomainSocket = config.isDomainSocket
116+
}
117+
118+
// if host is string and its start from / use it as unix socket
119+
if (typeof this.host === 'string' && this.host.indexOf('/') === 0) {
120+
this.isDomainSocket = true
121+
}
64122

65123
this.application_name = val('application_name', config, 'PGAPPNAME')
66124
this.fallback_application_name = val('fallback_application_name', config, false)
67125
this.statement_timeout = val('statement_timeout', config, false)
126+
127+
this.target_session_attrs = val('target_session_attrs', config)
68128
}
69129

70130
// Convert arg to a string, surround in single quotes, and escape single quotes and backslashes
@@ -86,6 +146,7 @@ ConnectionParameters.prototype.getLibpqConnectionString = function (cb) {
86146
add(params, this, 'port')
87147
add(params, this, 'application_name')
88148
add(params, this, 'fallback_application_name')
149+
add(params, this, 'target_session_attrs')
89150

90151
var ssl = typeof this.ssl === 'object' ? this.ssl : {sslmode: this.ssl}
91152
add(params, ssl, 'sslmode')
@@ -101,17 +162,17 @@ ConnectionParameters.prototype.getLibpqConnectionString = function (cb) {
101162
params.push('replication=' + quoteParamValue(this.replication))
102163
}
103164
if (this.host) {
104-
params.push('host=' + quoteParamValue(this.host))
165+
params.push('host=' + quoteParamValue(Array.isArray(this.host) ? this.host.join(',') : this.host))
105166
}
106167
if (this.isDomainSocket) {
107168
return cb(null, params.join(' '))
108169
}
109170
if (this.client_encoding) {
110171
params.push('client_encoding=' + quoteParamValue(this.client_encoding))
111172
}
112-
dns.lookup(this.host, function (err, address) {
173+
async.map((Array.isArray(this.host) ? this.host : [this.host]), dns.lookup, function (err, addresses) {
113174
if (err) return cb(err, null)
114-
params.push('hostaddr=' + quoteParamValue(address))
175+
params.push('hostaddr=' + quoteParamValue(addresses.join(',')))
115176
return cb(null, params.join(' '))
116177
})
117178
}

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,18 @@
1919
"author": "Brian Carlson <brian.m.carlson@gmail.com>",
2020
"main": "./lib",
2121
"dependencies": {
22+
"async": "^2.6.1",
2223
"buffer-writer": "1.0.1",
24+
"connection-string": "^1.0.1",
2325
"packet-reader": "0.3.1",
24-
"pg-connection-string": "0.1.3",
26+
"pg-connection-string": "^2.0.0",
27+
"pg-native": "^3.0.0",
2528
"pg-pool": "~2.0.3",
2629
"pg-types": "~1.12.1",
2730
"pgpass": "1.x",
2831
"semver": "4.3.2"
2932
},
3033
"devDependencies": {
31-
"async": "0.9.0",
3234
"co": "4.6.0",
3335
"eslint": "4.2.0",
3436
"eslint-config-standard": "10.2.1",

test/integration/client/appname-tests.js

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ var suite = new helper.Suite()
77
var conInfo = helper.config
88

99
function getConInfo (override) {
10-
return Object.assign({}, conInfo, override )
10+
return Object.assign({}, conInfo, override)
1111
}
1212

1313
function getAppName (conf, cb) {
@@ -64,21 +64,6 @@ suite.test('application_name has precedence over fallback_application_name', fun
6464
})
6565
})
6666

67-
suite.test('application_name from connection string', function (done) {
68-
var appName = 'my app'
69-
var conParams = require(__dirname + '/../../../lib/connection-parameters')
70-
var conf
71-
if (process.argv[2]) {
72-
conf = new conParams(process.argv[2] + '?application_name=' + appName)
73-
} else {
74-
conf = 'postgres://?application_name=' + appName
75-
}
76-
getAppName(conf, function (res) {
77-
assert.strictEqual(res, appName)
78-
done()
79-
})
80-
})
81-
8267
// TODO: make the test work for native client too
8368
if (!helper.args.native) {
8469
suite.test('application_name is read from the env', function (done) {

test/unit/client/configuration-tests.js

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,6 @@ test('initializing from a config string', function () {
7777
assert.equal(client.database, 'databasename')
7878
})
7979

80-
test('uses the correct values from the config string with space in password', function () {
81-
var client = new Client('postgres://brian:pass word@host1:333/databasename')
82-
assert.equal(client.user, 'brian')
83-
assert.equal(client.password, 'pass word')
84-
assert.equal(client.host, 'host1')
85-
assert.equal(client.port, 333)
86-
assert.equal(client.database, 'databasename')
87-
})
88-
8980
test('when not including all values the defaults are used', function () {
9081
var client = new Client('postgres://host1')
9182
assert.equal(client.user, process.env['PGUSER'] || process.env.USER)

test/unit/connection-parameters/creation-tests.js

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,6 @@ test('ConnectionParameters initializing from config and config.connectionString'
8989
assert.equal(subject2.ssl, true)
9090
assert.equal(subject3.ssl, true)
9191
assert.equal(subject4.ssl, true)
92-
});
93-
94-
test('escape spaces if present', function () {
95-
var subject = new ConnectionParameters('postgres://localhost/post gres')
96-
assert.equal(subject.database, 'post gres')
9792
})
9893

9994
test('do not double escape spaces', function () {
@@ -232,27 +227,6 @@ test('libpq connection string building', function () {
232227

233228
test('password contains < and/or > characters', function () {
234229
return false
235-
var sourceConfig = {
236-
user: 'brian',
237-
password: 'hello<ther>e',
238-
port: 5432,
239-
host: 'localhost',
240-
database: 'postgres'
241-
}
242-
var connectionString = 'postgres://' + sourceConfig.user + ':' + sourceConfig.password + '@' + sourceConfig.host + ':' + sourceConfig.port + '/' + sourceConfig.database
243-
var subject = new ConnectionParameters(connectionString)
244-
assert.equal(subject.password, sourceConfig.password)
245-
})
246-
247-
test('username or password contains weird characters', function () {
248-
var defaults = require('../../../lib/defaults')
249-
defaults.ssl = true
250-
var strang = 'pg://my f%irst name:is&%awesome!@localhost:9000'
251-
var subject = new ConnectionParameters(strang)
252-
assert.equal(subject.user, 'my f%irst name')
253-
assert.equal(subject.password, 'is&%awesome!')
254-
assert.equal(subject.host, 'localhost')
255-
assert.equal(subject.ssl, true)
256230
})
257231

258232
test('url is properly encoded', function () {

0 commit comments

Comments
 (0)