From 04668de22b0c898712d821060a72e1f004babcb1 Mon Sep 17 00:00:00 2001 From: "Denis Chistyakov (dench)" Date: Mon, 27 Aug 2018 21:50:09 +0500 Subject: [PATCH 1/2] 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 00e3ceed4..626233a43 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 3379ccd434ad50e1628b492fa04492f964d15d77 Mon Sep 17 00:00:00 2001 From: "Denis Chistyakov (dench)" Date: Wed, 29 Aug 2018 19:10:28 +0500 Subject: [PATCH 2/2] Add support of PostgreSQL 10 features of connection string with multiple hosts --- lib/connection-parameters.js | 81 ++++++++++++++++--- package.json | 6 +- test/integration/client/appname-tests.js | 17 +--- test/unit/client/configuration-tests.js | 9 --- .../connection-parameters/creation-tests.js | 26 ------ 5 files changed, 76 insertions(+), 63 deletions(-) diff --git a/lib/connection-parameters.js b/lib/connection-parameters.js index f31f28dd3..bec405499 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,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 @@ -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') @@ -101,7 +162,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(' ')) @@ -109,9 +170,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 d3569be96..51e65b97a 100644 --- a/package.json +++ b/package.json @@ -19,16 +19,18 @@ "author": "Brian Carlson ", "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", diff --git a/test/integration/client/appname-tests.js b/test/integration/client/appname-tests.js index e5883908d..c017c935b 100644 --- a/test/integration/client/appname-tests.js +++ b/test/integration/client/appname-tests.js @@ -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) { @@ -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: 'helloe', - 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 () {