Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple hosts support fix #1470 #1711

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 71 additions & 10 deletions lib/connection-parameters.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -41,30 +43,88 @@ 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)
this.statement_timeout = val('statement_timeout', config, false)

this.target_session_attrs = val('target_session_attrs', config)
}

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

var ssl = typeof this.ssl === 'object' ? this.ssl : {sslmode: this.ssl}
add(params, ssl, 'sslmode')
Expand All @@ -101,17 +162,17 @@ 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(' '))
}
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(' '))
})
}
Expand Down
3 changes: 3 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,18 @@
"author": "Brian Carlson <[email protected]>",
"main": "./lib",
"dependencies": {
"async": "^2.6.1",
"buffer-writer": "1.0.1",
"connection-string": "^1.0.1",
"packet-reader": "0.3.1",
"pg-connection-string": "0.1.3",
"pg-connection-string": "^2.0.0",
"pg-native": "^3.0.0",
"pg-pool": "~2.0.3",
"pg-types": "~1.12.1",
"pgpass": "1.x",
"semver": "4.3.2"
},
"devDependencies": {
"async": "0.9.0",
"co": "4.6.0",
"eslint": "4.2.0",
"eslint-config-standard": "10.2.1",
Expand Down
17 changes: 1 addition & 16 deletions test/integration/client/appname-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ var suite = new helper.Suite()
var conInfo = helper.config

function getConInfo (override) {
return Object.assign({}, conInfo, override )
return Object.assign({}, conInfo, override)
}

function getAppName (conf, cb) {
Expand Down Expand Up @@ -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) {
Expand Down
9 changes: 0 additions & 9 deletions test/unit/client/configuration-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
26 changes: 0 additions & 26 deletions test/unit/connection-parameters/creation-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand Down Expand Up @@ -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 () {
Expand Down