Throw on reconnect attempt

Clients are not reusable.  This changes the client to raise errors whenever you try to reconnect a client that's already been used.  They're cheap to create: just instantiate a new one (or use the pool) 😉.

Closes #1352
This commit is contained in:
Brian Carlson 2017-07-14 14:57:48 -05:00 committed by Brian C
parent a83655a396
commit 27450d07e6
17 changed files with 219 additions and 181 deletions

View File

@ -34,6 +34,7 @@ var Client = function(config) {
this._types = new TypeOverrides(c.types);
this._ending = false;
this._connecting = false;
this._connected = false;
this._connectionError = false;
this.connection = c.connection || new Connection({
@ -54,6 +55,14 @@ util.inherits(Client, EventEmitter);
Client.prototype.connect = function(callback) {
var self = this;
var con = this.connection;
if (this._connecting || this._connected) {
const err = new Error('Client has already been connected. You cannot reuse a client.')
if (callback) {
callback(err)
return undefined
}
return Promise.reject(err)
}
this._connecting = true;
if(this.host && this.host.indexOf('/') === 0) {
@ -135,6 +144,7 @@ Client.prototype.connect = function(callback) {
//after the connection initially becomes ready for queries
con.once('readyForQuery', function() {
self._connecting = false;
self._connected = true;
self._attachListeners(con);
con.removeListener('error', connectingErrorHandler);
con.on('error', connectedErrorHandler)

View File

@ -21,7 +21,7 @@ const poolFactory = (Client) => {
for (var key in options) {
config[key] = options[key];
}
Pool.call(this, config);
return new Pool(config)
};
util.inherits(BoundPool, Pool);

View File

@ -33,6 +33,7 @@ var Client = module.exports = function(config) {
this._queryQueue = [];
this._connected = false;
this._connecting = false;
//keep these on the object for legacy reasons
//for the time being. TODO: deprecate all this jazz
@ -74,6 +75,13 @@ Client.prototype.connect = function(cb) {
})
}
if (this._connecting) {
process.nextTick(() => cb(new Error('Client has already been connected. You cannot reuse a client.')))
return result
}
this._connecting = true
this.connectionParameters.getLibpqConnectionString(function(err, conString) {
if(err) return onError(err);
self.native.connect(conString, function(err) {

View File

@ -21,7 +21,7 @@
"buffer-writer": "1.0.1",
"packet-reader": "0.3.1",
"pg-connection-string": "0.1.3",
"pg-pool": "1.*",
"pg-pool": "2.*",
"pg-types": "1.*",
"pgpass": "1.x",
"semver": "4.3.2"

View File

@ -8,7 +8,7 @@ suite.test("pool callback behavior", done => {
//test weird callback behavior with node-pool
const pool = new pg.Pool();
pool.connect(function(err) {
assert.isNull(err);
assert(!err);
arguments[1].emit("drain");
arguments[2]();
pool.end(done);
@ -52,7 +52,7 @@ suite.test("executing nested queries", function(done) {
const pool = new pg.Pool();
pool.connect(
assert.calls(function(err, client, release) {
assert.isNull(err);
assert(!err);
client.query(
"select now as now from NOW()",
assert.calls(function(err, result) {
@ -91,7 +91,7 @@ suite.test("query errors are handled and do not bubble if callback is provded",
const pool = new pg.Pool();
pool.connect(
assert.calls(function(err, client, release) {
assert.isNull(err);
assert(!err);
client.query(
"SELECT OISDJF FROM LEIWLISEJLSE",
assert.calls(function(err, result) {
@ -109,7 +109,7 @@ suite.test("callback is fired once and only once", function(done) {
const pool = new pg.Pool()
pool.connect(
assert.calls(function(err, client, release) {
assert.isNull(err);
assert(!err);
client.query("CREATE TEMP TABLE boom(name varchar(10))");
var callCount = 0;
client.query(
@ -136,14 +136,14 @@ suite.test("can provide callback and config object", function(done) {
const pool = new pg.Pool()
pool.connect(
assert.calls(function(err, client, release) {
assert.isNull(err);
assert(!err);
client.query(
{
name: "boom",
text: "select NOW()"
},
assert.calls(function(err, result) {
assert.isNull(err);
assert(!err);
assert.equal(result.rows[0].now.getYear(), new Date().getYear());
release();
pool.end(done)
@ -157,7 +157,7 @@ suite.test("can provide callback and config and parameters", function(done) {
const pool = new pg.Pool()
pool.connect(
assert.calls(function(err, client, release) {
assert.isNull(err);
assert(!err);
var config = {
text: "select $1::text as val"
};
@ -165,7 +165,7 @@ suite.test("can provide callback and config and parameters", function(done) {
config,
["hi"],
assert.calls(function(err, result) {
assert.isNull(err);
assert(!err);
assert.equal(result.rows.length, 1);
assert.equal(result.rows[0].val, "hi");
release()
@ -180,7 +180,7 @@ suite.test("null and undefined are both inserted as NULL", function(done) {
const pool = new pg.Pool()
pool.connect(
assert.calls(function(err, client, release) {
assert.isNull(err);
assert(!err);
client.query(
"CREATE TEMP TABLE my_nulls(a varchar(1), b varchar(1), c integer, d integer, e date, f date)"
);
@ -191,7 +191,7 @@ suite.test("null and undefined are both inserted as NULL", function(done) {
client.query(
"SELECT * FROM my_nulls",
assert.calls(function(err, result) {
assert.isNull(err);
assert(!err);
assert.equal(result.rows.length, 1);
assert.isNull(result.rows[0].a);
assert.isNull(result.rows[0].b);

View File

@ -5,8 +5,9 @@ var pg = helper.pg;
var suite = new helper.Suite()
const pool = new pg.Pool()
pool.connect(assert.calls(function(err, client, release) {
assert.isNull(err);
assert(!err);
suite.test('nulls', function(done) {
client.query('SELECT $1::text[] as array', [[null]], assert.success(function(result) {
@ -33,145 +34,144 @@ pool.connect(assert.calls(function(err, client, release) {
});
suite.test('cleanup', () => release())
pool.connect(assert.calls(function (err, client, release) {
assert(!err);
client.query("CREATE TEMP TABLE why(names text[], numbors integer[])");
client.query(new pg.Query('INSERT INTO why(names, numbors) VALUES(\'{"aaron", "brian","a b c" }\', \'{1, 2, 3}\')')).on('error', console.log);
suite.test('numbers', function (done) {
// client.connection.on('message', console.log)
client.query('SELECT numbors FROM why', assert.success(function (result) {
assert.lengthIs(result.rows[0].numbors, 3);
assert.equal(result.rows[0].numbors[0], 1);
assert.equal(result.rows[0].numbors[1], 2);
assert.equal(result.rows[0].numbors[2], 3);
done()
}))
})
suite.test('parses string arrays', function (done) {
client.query('SELECT names FROM why', assert.success(function (result) {
var names = result.rows[0].names;
assert.lengthIs(names, 3);
assert.equal(names[0], 'aaron');
assert.equal(names[1], 'brian');
assert.equal(names[2], "a b c");
done()
}))
})
suite.test('empty array', function (done) {
client.query("SELECT '{}'::text[] as names", assert.success(function (result) {
var names = result.rows[0].names;
assert.lengthIs(names, 0);
done()
}))
})
suite.test('element containing comma', function (done) {
client.query("SELECT '{\"joe,bob\",jim}'::text[] as names", assert.success(function (result) {
var names = result.rows[0].names;
assert.lengthIs(names, 2);
assert.equal(names[0], 'joe,bob');
assert.equal(names[1], 'jim');
done()
}))
})
suite.test('bracket in quotes', function (done) {
client.query("SELECT '{\"{\",\"}\"}'::text[] as names", assert.success(function (result) {
var names = result.rows[0].names;
assert.lengthIs(names, 2);
assert.equal(names[0], '{');
assert.equal(names[1], '}');
done()
}))
})
suite.test('null value', function (done) {
client.query("SELECT '{joe,null,bob,\"NULL\"}'::text[] as names", assert.success(function (result) {
var names = result.rows[0].names;
assert.lengthIs(names, 4);
assert.equal(names[0], 'joe');
assert.equal(names[1], null);
assert.equal(names[2], 'bob');
assert.equal(names[3], 'NULL');
done()
}))
})
suite.test('element containing quote char', function (done) {
client.query("SELECT ARRAY['joe''', 'jim', 'bob\"'] AS names", assert.success(function (result) {
var names = result.rows[0].names;
assert.lengthIs(names, 3);
assert.equal(names[0], 'joe\'');
assert.equal(names[1], 'jim');
assert.equal(names[2], 'bob"');
done()
}))
})
suite.test('nested array', function (done) {
client.query("SELECT '{{1,joe},{2,bob}}'::text[] as names", assert.success(function (result) {
var names = result.rows[0].names;
assert.lengthIs(names, 2);
assert.lengthIs(names[0], 2);
assert.equal(names[0][0], '1');
assert.equal(names[0][1], 'joe');
assert.lengthIs(names[1], 2);
assert.equal(names[1][0], '2');
assert.equal(names[1][1], 'bob');
done()
}))
})
suite.test('integer array', function (done) {
client.query("SELECT '{1,2,3}'::integer[] as names", assert.success(function (result) {
var names = result.rows[0].names;
assert.lengthIs(names, 3);
assert.equal(names[0], 1);
assert.equal(names[1], 2);
assert.equal(names[2], 3);
done()
}))
})
suite.test('integer nested array', function (done) {
client.query("SELECT '{{1,100},{2,100},{3,100}}'::integer[] as names", assert.success(function (result) {
var names = result.rows[0].names;
assert.lengthIs(names, 3);
assert.equal(names[0][0], 1);
assert.equal(names[0][1], 100);
assert.equal(names[1][0], 2);
assert.equal(names[1][1], 100);
assert.equal(names[2][0], 3);
assert.equal(names[2][1], 100);
done()
}))
})
suite.test('JS array parameter', function (done) {
client.query("SELECT $1::integer[] as names", [[[1, 100], [2, 100], [3, 100]]], assert.success(function (result) {
var names = result.rows[0].names;
assert.lengthIs(names, 3);
assert.equal(names[0][0], 1);
assert.equal(names[0][1], 100);
assert.equal(names[1][0], 2);
assert.equal(names[1][1], 100);
assert.equal(names[2][0], 3);
assert.equal(names[2][1], 100);
release();
pool.end(() => {
done()
})
}))
})
}))
}));
pool.connect(assert.calls(function (err, client, release) {
assert.isNull(err);
client.query("CREATE TEMP TABLE why(names text[], numbors integer[])");
client.query(new pg.Query('INSERT INTO why(names, numbors) VALUES(\'{"aaron", "brian","a b c" }\', \'{1, 2, 3}\')')).on('error', console.log);
suite.test('numbers', function (done) {
// client.connection.on('message', console.log)
client.query('SELECT numbors FROM why', assert.success(function (result) {
assert.lengthIs(result.rows[0].numbors, 3);
assert.equal(result.rows[0].numbors[0], 1);
assert.equal(result.rows[0].numbors[1], 2);
assert.equal(result.rows[0].numbors[2], 3);
done()
}))
})
suite.test('parses string arrays', function (done) {
client.query('SELECT names FROM why', assert.success(function (result) {
var names = result.rows[0].names;
assert.lengthIs(names, 3);
assert.equal(names[0], 'aaron');
assert.equal(names[1], 'brian');
assert.equal(names[2], "a b c");
done()
}))
})
suite.test('empty array', function (done) {
client.query("SELECT '{}'::text[] as names", assert.success(function (result) {
var names = result.rows[0].names;
assert.lengthIs(names, 0);
done()
}))
})
suite.test('element containing comma', function (done) {
client.query("SELECT '{\"joe,bob\",jim}'::text[] as names", assert.success(function (result) {
var names = result.rows[0].names;
assert.lengthIs(names, 2);
assert.equal(names[0], 'joe,bob');
assert.equal(names[1], 'jim');
done()
}))
})
suite.test('bracket in quotes', function (done) {
client.query("SELECT '{\"{\",\"}\"}'::text[] as names", assert.success(function (result) {
var names = result.rows[0].names;
assert.lengthIs(names, 2);
assert.equal(names[0], '{');
assert.equal(names[1], '}');
done()
}))
})
suite.test('null value', function (done) {
client.query("SELECT '{joe,null,bob,\"NULL\"}'::text[] as names", assert.success(function (result) {
var names = result.rows[0].names;
assert.lengthIs(names, 4);
assert.equal(names[0], 'joe');
assert.equal(names[1], null);
assert.equal(names[2], 'bob');
assert.equal(names[3], 'NULL');
done()
}))
})
suite.test('element containing quote char', function (done) {
client.query("SELECT ARRAY['joe''', 'jim', 'bob\"'] AS names", assert.success(function (result) {
var names = result.rows[0].names;
assert.lengthIs(names, 3);
assert.equal(names[0], 'joe\'');
assert.equal(names[1], 'jim');
assert.equal(names[2], 'bob"');
done()
}))
})
suite.test('nested array', function (done) {
client.query("SELECT '{{1,joe},{2,bob}}'::text[] as names", assert.success(function (result) {
var names = result.rows[0].names;
assert.lengthIs(names, 2);
assert.lengthIs(names[0], 2);
assert.equal(names[0][0], '1');
assert.equal(names[0][1], 'joe');
assert.lengthIs(names[1], 2);
assert.equal(names[1][0], '2');
assert.equal(names[1][1], 'bob');
done()
}))
})
suite.test('integer array', function (done) {
client.query("SELECT '{1,2,3}'::integer[] as names", assert.success(function (result) {
var names = result.rows[0].names;
assert.lengthIs(names, 3);
assert.equal(names[0], 1);
assert.equal(names[1], 2);
assert.equal(names[2], 3);
done()
}))
})
suite.test('integer nested array', function (done) {
client.query("SELECT '{{1,100},{2,100},{3,100}}'::integer[] as names", assert.success(function (result) {
var names = result.rows[0].names;
assert.lengthIs(names, 3);
assert.equal(names[0][0], 1);
assert.equal(names[0][1], 100);
assert.equal(names[1][0], 2);
assert.equal(names[1][1], 100);
assert.equal(names[2][0], 3);
assert.equal(names[2][1], 100);
done()
}))
})
suite.test('JS array parameter', function (done) {
client.query("SELECT $1::integer[] as names", [[[1, 100], [2, 100], [3, 100]]], assert.success(function (result) {
var names = result.rows[0].names;
assert.lengthIs(names, 3);
assert.equal(names[0][0], 1);
assert.equal(names[0][1], 100);
assert.equal(names[1][0], 2);
assert.equal(names[1][1], 100);
assert.equal(names[2][0], 3);
assert.equal(names[2][1], 100);
release();
pool.end(done)
}))
})
}))

View File

@ -13,7 +13,7 @@ suite.test("empty query message handling", function(done) {
suite.test('callback supported', function(done) {
const client = helper.client();
client.query("", function(err, result) {
assert.isNull(err);
assert(!err);
assert.empty(result.rows);
client.end(done)
})

View File

@ -17,6 +17,26 @@ var createErorrClient = function() {
const suite = new helper.Suite('error handling')
suite.test('re-using connections results in error callback', (done) => {
const client = new Client()
client.connect(() => {
client.connect(err => {
assert(err instanceof Error)
client.end(done)
})
})
})
suite.test('re-using connections results in promise rejection', (done) => {
const client = new Client()
client.connect().then(() => {
client.connect().catch(err => {
assert(err instanceof Error)
client.end().then(done)
})
})
})
suite.test('query receives error on client shutdown', function(done) {
var client = new Client();
client.connect(assert.success(function() {

View File

@ -5,21 +5,21 @@ var pg = helper.pg;
const pool = new pg.Pool()
new helper.Suite().test('should return insert metadata', function() {
pool.connect(assert.calls(function(err, client, done) {
assert.isNull(err);
assert(!err);
helper.versionGTE(client, '9.0.0', assert.success(function(hasRowCount) {
client.query("CREATE TEMP TABLE zugzug(name varchar(10))", assert.calls(function(err, result) {
assert.isNull(err);
assert(!err);
assert.equal(result.oid, null);
assert.equal(result.command, 'CREATE');
var q = client.query("INSERT INTO zugzug(name) VALUES('more work?')", assert.calls(function(err, result) {
assert.isNull(err);
assert(!err);
assert.equal(result.command, "INSERT");
assert.equal(result.rowCount, 1);
client.query('SELECT * FROM zugzug', assert.calls(function(err, result) {
assert.isNull(err);
assert(!err);
if(hasRowCount) assert.equal(result.rowCount, 1);
assert.equal(result.command, 'SELECT');
done();

View File

@ -11,11 +11,11 @@ const pool = new helper.pg.Pool()
const suite = new helper.Suite()
pool.connect(function (err, client, done) {
assert.isNull(err);
assert(!err);
suite.test('timestamp without time zone', function (cb) {
client.query("SELECT CAST($1 AS TIMESTAMP WITHOUT TIME ZONE) AS \"val\"", [date], function (err, result) {
assert.isNull(err);
assert(!err);
assert.equal(result.rows[0].val.getTime(), date.getTime());
cb()
})
@ -23,7 +23,7 @@ pool.connect(function (err, client, done) {
suite.test('timestamp with time zone', function (cb) {
client.query("SELECT CAST($1 AS TIMESTAMP WITH TIME ZONE) AS \"val\"", [date], function (err, result) {
assert.isNull(err);
assert(!err);
assert.equal(result.rows[0].val.getTime(), date.getTime());
done();

View File

@ -15,7 +15,7 @@ client.connect(assert.success(function () {
suite.test('name should not exist in the database', function (done) {
client.query(getZed, assert.calls(function (err, result) {
assert.isNull(err);
assert(!err);
assert.empty(result.rows);
done()
}))
@ -23,14 +23,14 @@ client.connect(assert.success(function () {
suite.test('can insert name', (done) => {
client.query("INSERT INTO person(name, age) VALUES($1, $2)", ['Zed', 270], assert.calls(function (err, result) {
assert.isNull(err)
assert(!err)
done()
}));
})
suite.test('name should exist in the database', function (done) {
client.query(getZed, assert.calls(function (err, result) {
assert.isNull(err);
assert(!err);
assert.equal(result.rows[0].name, 'Zed');
done()
}))
@ -42,7 +42,7 @@ client.connect(assert.success(function () {
suite.test('name should not exist in the database', function (done) {
client.query(getZed, assert.calls(function (err, result) {
assert.isNull(err);
assert(!err);
assert.empty(result.rows);
client.end(done)
}))

View File

@ -8,14 +8,14 @@ var testForTypeCoercion = function (type) {
const pool = new pg.Pool()
suite.test(`test type coercion ${type.name}`, (cb) => {
pool.connect(function (err, client, done) {
assert.isNull(err);
assert(!err);
client.query("create temp table test_type(col " + type.name + ")", assert.calls(function (err, result) {
assert.isNull(err);
assert(!err);
type.values.forEach(function (val) {
var insertQuery = client.query('insert into test_type(col) VALUES($1)', [val], assert.calls(function (err, result) {
assert.isNull(err);
assert(!err);
}));
var query = client.query(new pg.Query({
@ -152,11 +152,11 @@ suite.test('selecting nulls', cb => {
pool.connect(assert.calls(function (err, client, done) {
assert.ifError(err);
client.query('select null as res;', assert.calls(function (err, res) {
assert.isNull(err);
assert(!err);
assert.strictEqual(res.rows[0].res, null)
}))
client.query('select 7 <> $1 as res;', [null], function (err, res) {
assert.isNull(err);
assert(!err);
assert.strictEqual(res.rows[0].res, null);
done();
pool.end(cb)

View File

@ -6,7 +6,7 @@ new helper.Suite().test('idle timeout', function () {
const config = Object.assign({}, helper.config, { idleTimeoutMillis: 50 })
const pool = new helper.pg.Pool(config)
pool.connect(assert.calls(function (err, client, done) {
assert.isNull(err);
assert(!err);
client.query('SELECT NOW()');
done();
}));

View File

@ -13,7 +13,7 @@ helper.testPoolSize = function (max) {
for (var i = 0; i < max; i++) {
pool.connect(function (err, client, done) {
assert.isNull(err);
assert(!err);
client.query("SELECT * FROM NOW()")
client.query("select generate_series(0, 25)", function (err, result) {
assert.equal(result.rows.length, 26)

View File

@ -7,7 +7,7 @@ var suite = new helper.Suite()
suite.test('parsing array decimal results', function (done) {
const pool = new pg.Pool()
pool.connect(assert.calls(function (err, client, release) {
assert.isNull(err);
assert(!err);
client.query("CREATE TEMP TABLE why(names text[], numbors integer[], decimals double precision[])");
client.query(new pg.Query('INSERT INTO why(names, numbors, decimals) VALUES(\'{"aaron", "brian","a b c" }\', \'{1, 2, 3}\', \'{.1, 0.05, 3.654}\')')).on('error', console.log);
client.query('SELECT decimals FROM why', assert.success(function (result) {

View File

@ -8,11 +8,11 @@ suite.test('fires callback with results', function(done) {
var client = new Client(helper.config);
client.connect();
client.query('SELECT 1 as num', assert.calls(function(err, result) {
assert.isNull(err);
assert(!err);
assert.equal(result.rows[0].num, 1);
assert.strictEqual(result.rowCount, 1);
client.query('SELECT * FROM person WHERE name = $1', ['Brian'], assert.calls(function(err, result) {
assert.isNull(err);
assert(!err);
assert.equal(result.rows[0].name, 'Brian');
client.end(done);
}))

View File

@ -122,7 +122,7 @@ test('libpq connection string building', function() {
}
var subject = new ConnectionParameters(config);
subject.getLibpqConnectionString(assert.calls(function(err, constring) {
assert.isNull(err);
assert(!err);
var parts = constring.split(" ");
checkForPart(parts, "user='brian'");
checkForPart(parts, "password='xyz'");
@ -141,7 +141,7 @@ test('libpq connection string building', function() {
};
var subject = new ConnectionParameters(config);
subject.getLibpqConnectionString(assert.calls(function(err, constring) {
assert.isNull(err);
assert(!err);
var parts = constring.split(" ");
checkForPart(parts, "user='brian'");
checkForPart(parts, "hostaddr='127.0.0.1'");
@ -171,7 +171,7 @@ test('libpq connection string building', function() {
};
var subject = new ConnectionParameters(config);
subject.getLibpqConnectionString(assert.calls(function(err, constring) {
assert.isNull(err);
assert(!err);
var parts = constring.split(" ");
checkForPart(parts, "user='brian'");
checkForPart(parts, "host='/tmp/'");
@ -187,7 +187,7 @@ test('libpq connection string building', function() {
};
var subject = new ConnectionParameters(config);
subject.getLibpqConnectionString(assert.calls(function(err, constring) {
assert.isNull(err);
assert(!err);
var parts = constring.split(" ");
checkForPart(parts, "user='not\\\\brian'");
checkForPart(parts, "password='bad\\'chars'");
@ -200,7 +200,7 @@ test('libpq connection string building', function() {
}
var subject = new ConnectionParameters(config);
subject.getLibpqConnectionString(assert.calls(function(err, constring) {
assert.isNull(err);
assert(!err);
var parts = constring.split(" ");
checkForPart(parts, "client_encoding='utf-8'");
}));