package/package.json000644 0000001641 3560116604 011550 0ustar00000000 000000 { "name": "pg-pool", "version": "2.0.6", "description": "Connection pool for node-postgres", "main": "index.js", "directories": { "test": "test" }, "scripts": { "test": " node_modules/.bin/mocha && node_modules/.bin/standard" }, "repository": { "type": "git", "url": "git://github.com/brianc/node-pg-pool.git" }, "keywords": [ "pg", "postgres", "pool", "database" ], "author": "Brian M. Carlson", "license": "MIT", "bugs": { "url": "https://github.com/brianc/node-pg-pool/issues" }, "homepage": "https://github.com/brianc/node-pg-pool#readme", "devDependencies": { "bluebird": "3.4.1", "co": "4.6.0", "expect.js": "0.3.1", "lodash": "^4.17.11", "mocha": "^5.2.0", "pg": "*", "pg-cursor": "^1.3.0", "standard": "7.1.2", "standard-format": "^2.2.4" }, "dependencies": {}, "peerDependencies": { "pg": ">5.0" } } package/.travis.yml000644 0000000447 3560116604 011376 0ustar00000000 000000 language: node_js matrix: include: - node_js: "4" addons: postgresql: "9.1" dist: precise - node_js: "6" addons: postgresql: "9.4" - node_js: "8" addons: postgresql: "9.6" - node_js: "10" addons: postgresql: "9.6" package/index.js000644 0000021411 3560116604 010724 0ustar00000000 000000 'use strict' const EventEmitter = require('events').EventEmitter const NOOP = function () { } const removeWhere = (list, predicate) => { const i = list.findIndex(predicate) return i === -1 ? undefined : list.splice(i, 1)[0] } class IdleItem { constructor (client, timeoutId) { this.client = client this.timeoutId = timeoutId } } class PendingItem { constructor (callback) { this.callback = callback } } function throwOnRelease () { throw new Error('Release called on client which has already been released to the pool.') } function release (client, err) { client.release = throwOnRelease if (err || this.ending) { this._remove(client) this._pulseQueue() return } // idle timeout let tid if (this.options.idleTimeoutMillis) { tid = setTimeout(() => { this.log('remove idle client') this._remove(client) }, this.options.idleTimeoutMillis) } this._idle.push(new IdleItem(client, tid)) this._pulseQueue() } function promisify (Promise, callback) { if (callback) { return { callback: callback, result: undefined } } let rej let res const cb = function (err, client) { err ? rej(err) : res(client) } const result = new Promise(function (resolve, reject) { res = resolve rej = reject }) return { callback: cb, result: result } } function makeIdleListener (pool, client) { return function idleListener (err) { err.client = client client.removeListener('error', idleListener) client.on('error', () => { pool.log('additional client error after disconnection due to error', err) }) pool._remove(client) // TODO - document that once the pool emits an error // the client has already been closed & purged and is unusable pool.emit('error', err, client) } } class Pool extends EventEmitter { constructor (options, Client) { super() this.options = Object.assign({}, options) this.options.max = this.options.max || this.options.poolSize || 10 this.log = this.options.log || function () { } this.Client = this.options.Client || Client || require('pg').Client this.Promise = this.options.Promise || global.Promise if (typeof this.options.idleTimeoutMillis === 'undefined') { this.options.idleTimeoutMillis = 10000 } this._clients = [] this._idle = [] this._pendingQueue = [] this._endCallback = undefined this.ending = false this.ended = false } _isFull () { return this._clients.length >= this.options.max } _pulseQueue () { this.log('pulse queue') if (this.ended) { this.log('pulse queue ended') return } if (this.ending) { this.log('pulse queue on ending') if (this._idle.length) { this._idle.slice().map(item => { this._remove(item.client) }) } if (!this._clients.length) { this.ended = true this._endCallback() } return } // if we don't have any waiting, do nothing if (!this._pendingQueue.length) { this.log('no queued requests') return } // if we don't have any idle clients and we have no more room do nothing if (!this._idle.length && this._isFull()) { return } const waiter = this._pendingQueue.shift() if (this._idle.length) { const idleItem = this._idle.pop() clearTimeout(idleItem.timeoutId) const client = idleItem.client client.release = release.bind(this, client) this.emit('acquire', client) return waiter.callback(undefined, client, client.release) } if (!this._isFull()) { return this.newClient(waiter) } throw new Error('unexpected condition') } _remove (client) { const removed = removeWhere( this._idle, item => item.client === client ) if (removed !== undefined) { clearTimeout(removed.timeoutId) } this._clients = this._clients.filter(c => c !== client) client.end() this.emit('remove', client) } connect (cb) { if (this.ending) { const err = new Error('Cannot use a pool after calling end on the pool') return cb ? cb(err) : this.Promise.reject(err) } const response = promisify(this.Promise, cb) const result = response.result // if we don't have to connect a new client, don't do so if (this._clients.length >= this.options.max || this._idle.length) { // if we have idle clients schedule a pulse immediately if (this._idle.length) { process.nextTick(() => this._pulseQueue()) } if (!this.options.connectionTimeoutMillis) { this._pendingQueue.push(new PendingItem(response.callback)) return result } const queueCallback = (err, res, done) => { clearTimeout(tid) response.callback(err, res, done) } const pendingItem = new PendingItem(queueCallback) // set connection timeout on checking out an existing client const tid = setTimeout(() => { // remove the callback from pending waiters because // we're going to call it with a timeout error removeWhere(this._pendingQueue, (i) => i.callback === queueCallback) pendingItem.timedOut = true response.callback(new Error('timeout exceeded when trying to connect')) }, this.options.connectionTimeoutMillis) this._pendingQueue.push(pendingItem) return result } this.newClient(new PendingItem(response.callback)) return result } newClient (pendingItem) { const client = new this.Client(this.options) this._clients.push(client) const idleListener = makeIdleListener(this, client) this.log('checking client timeout') // connection timeout logic let tid let timeoutHit = false if (this.options.connectionTimeoutMillis) { tid = setTimeout(() => { this.log('ending client due to timeout') timeoutHit = true // force kill the node driver, and let libpq do its teardown client.connection ? client.connection.stream.destroy() : client.end() }, this.options.connectionTimeoutMillis) } this.log('connecting new client') client.connect((err) => { if (tid) { clearTimeout(tid) } client.on('error', idleListener) if (err) { this.log('client failed to connect', err) // remove the dead client from our list of clients this._clients = this._clients.filter(c => c !== client) if (timeoutHit) { err.message = 'Connection terminated due to connection timeout' } // this client won’t be released, so move on immediately this._pulseQueue() if (!pendingItem.timedOut) { pendingItem.callback(err, undefined, NOOP) } } else { this.log('new client connected') client.release = release.bind(this, client) this.emit('connect', client) this.emit('acquire', client) if (!pendingItem.timedOut) { if (this.options.verify) { this.options.verify(client, pendingItem.callback) } else { pendingItem.callback(undefined, client, client.release) } } else { if (this.options.verify) { this.options.verify(client, client.release) } else { client.release() } } } }) } query (text, values, cb) { // guard clause against passing a function as the first parameter if (typeof text === 'function') { const response = promisify(this.Promise, text) setImmediate(function () { return response.callback(new Error('Passing a function as the first parameter to pool.query is not supported')) }) return response.result } // allow plain text query without values if (typeof values === 'function') { cb = values values = undefined } const response = promisify(this.Promise, cb) cb = response.callback this.connect((err, client) => { if (err) { return cb(err) } this.log('dispatching query') client.query(text, values, (err, res) => { this.log('query dispatched') client.release(err) if (err) { return cb(err) } else { return cb(undefined, res) } }) }) return response.result } end (cb) { this.log('ending') if (this.ending) { const err = new Error('Called end on pool more than once') return cb ? cb(err) : this.Promise.reject(err) } this.ending = true const promised = promisify(this.Promise, cb) this._endCallback = promised.callback this._pulseQueue() return promised.result } get waitingCount () { return this._pendingQueue.length } get idleCount () { return this._idle.length } get totalCount () { return this._clients.length } } module.exports = Pool package/LICENSE000644 0000002061 3560116604 010264 0ustar00000000 000000 MIT License Copyright (c) 2017 Brian M. Carlson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package/Makefile000644 0000000364 3560116604 010723 0ustar00000000 000000 .PHONY: jshint test publish-patch test test: npm test patch: test npm version patch -m "Bump version" git push origin master --tags npm publish minor: test npm version minor -m "Bump version" git push origin master --tags npm publish package/README.md000644 0000026313 3560116604 010544 0ustar00000000 000000 # pg-pool [![Build Status](https://travis-ci.org/brianc/node-pg-pool.svg?branch=master)](https://travis-ci.org/brianc/node-pg-pool) A connection pool for node-postgres ## install ```sh npm i pg-pool pg ``` ## use ### create to use pg-pool you must first create an instance of a pool ```js var Pool = require('pg-pool') // by default the pool uses the same // configuration as whatever `pg` version you have installed var pool = new Pool() // you can pass properties to the pool // these properties are passed unchanged to both the node-postgres Client constructor // and the node-pool (https://github.com/coopernurse/node-pool) constructor // allowing you to fully configure the behavior of both var pool2 = new Pool({ database: 'postgres', user: 'brianc', password: 'secret!', port: 5432, ssl: true, max: 20, // set pool max size to 20 min: 4, // set min pool size to 4 idleTimeoutMillis: 1000, // close idle clients after 1 second connectionTimeoutMillis: 1000, // return an error after 1 second if connection could not be established }) //you can supply a custom client constructor //if you want to use the native postgres client var NativeClient = require('pg').native.Client var nativePool = new Pool({ Client: NativeClient }) //you can even pool pg-native clients directly var PgNativeClient = require('pg-native') var pgNativePool = new Pool({ Client: PgNativeClient }) ``` ##### Note: The Pool constructor does not support passing a Database URL as the parameter. To use pg-pool on heroku, for example, you need to parse the URL into a config object. Here is an example of how to parse a Database URL. ```js const Pool = require('pg-pool'); const url = require('url') const params = url.parse(process.env.DATABASE_URL); const auth = params.auth.split(':'); const config = { user: auth[0], password: auth[1], host: params.hostname, port: params.port, database: params.pathname.split('/')[1], ssl: true }; const pool = new Pool(config); /* Transforms, 'progres://DBuser:secret@DBHost:#####/myDB', into config = { user: 'DBuser', password: 'secret', host: 'DBHost', port: '#####', database: 'myDB', ssl: true } */ ``` ### acquire clients with a promise pg-pool supports a fully promise-based api for acquiring clients ```js var pool = new Pool() pool.connect().then(client => { client.query('select $1::text as name', ['pg-pool']).then(res => { client.release() console.log('hello from', res.rows[0].name) }) .catch(e => { client.release() console.error('query error', e.message, e.stack) }) }) ``` ### plays nice with async/await this ends up looking much nicer if you're using [co](https://github.com/tj/co) or async/await: ```js // with async/await (async () => { var pool = new Pool() var client = await pool.connect() try { var result = await client.query('select $1::text as name', ['brianc']) console.log('hello from', result.rows[0]) } finally { client.release() } })().catch(e => console.error(e.message, e.stack)) // with co co(function * () { var client = yield pool.connect() try { var result = yield client.query('select $1::text as name', ['brianc']) console.log('hello from', result.rows[0]) } finally { client.release() } }).catch(e => console.error(e.message, e.stack)) ``` ### your new favorite helper method because its so common to just run a query and return the client to the pool afterward pg-pool has this built-in: ```js var pool = new Pool() var time = await pool.query('SELECT NOW()') var name = await pool.query('select $1::text as name', ['brianc']) console.log(name.rows[0].name, 'says hello at', time.rows[0].name) ``` you can also use a callback here if you'd like: ```js var pool = new Pool() pool.query('SELECT $1::text as name', ['brianc'], function (err, res) { console.log(res.rows[0].name) // brianc }) ``` __pro tip:__ unless you need to run a transaction (which requires a single client for multiple queries) or you have some other edge case like [streaming rows](https://github.com/brianc/node-pg-query-stream) or using a [cursor](https://github.com/brianc/node-pg-cursor) you should almost always just use `pool.query`. Its easy, it does the right thing :tm:, and wont ever forget to return clients back to the pool after the query is done. ### drop-in backwards compatible pg-pool still and will always support the traditional callback api for acquiring a client. This is the exact API node-postgres has shipped with for years: ```js var pool = new Pool() pool.connect((err, client, done) => { if (err) return done(err) client.query('SELECT $1::text as name', ['pg-pool'], (err, res) => { done() if (err) { return console.error('query error', e.message, e.stack) } console.log('hello from', res.rows[0].name) }) }) ``` ### shut it down When you are finished with the pool if all the clients are idle the pool will close them after `config.idleTimeoutMillis` and your app will shutdown gracefully. If you don't want to wait for the timeout you can end the pool as follows: ```js var pool = new Pool() var client = await pool.connect() console.log(await client.query('select now()')) client.release() await pool.end() ``` ### a note on instances The pool should be a __long-lived object__ in your application. Generally you'll want to instantiate one pool when your app starts up and use the same instance of the pool throughout the lifetime of your application. If you are frequently creating a new pool within your code you likely don't have your pool initialization code in the correct place. Example: ```js // assume this is a file in your program at ./your-app/lib/db.js // correct usage: create the pool and let it live // 'globally' here, controlling access to it through exported methods var pool = new pg.Pool() // this is the right way to export the query method module.exports.query = (text, values) => { console.log('query:', text, values) return pool.query(text, values) } // this would be the WRONG way to export the connect method module.exports.connect = () => { // notice how we would be creating a pool instance here // every time we called 'connect' to get a new client? // that's a bad thing & results in creating an unbounded // number of pools & therefore connections var aPool = new pg.Pool() return aPool.connect() } ``` ### events Every instance of a `Pool` is an event emitter. These instances emit the following events: #### error Emitted whenever an idle client in the pool encounters an error. This is common when your PostgreSQL server shuts down, reboots, or a network partition otherwise causes it to become unavailable while your pool has connected clients. Example: ```js const Pool = require('pg-pool') const pool = new Pool() // attach an error handler to the pool for when a connected, idle client // receives an error by being disconnected, etc pool.on('error', function(error, client) { // handle this in the same way you would treat process.on('uncaughtException') // it is supplied the error as well as the idle client which received the error }) ``` #### connect Fired whenever the pool creates a __new__ `pg.Client` instance and successfully connects it to the backend. Example: ```js const Pool = require('pg-pool') const pool = new Pool() var count = 0 pool.on('connect', client => { client.count = count++ }) pool .connect() .then(client => { return client .query('SELECT $1::int AS "clientCount"', [client.count]) .then(res => console.log(res.rows[0].clientCount)) // outputs 0 .then(() => client) }) .then(client => client.release()) ``` #### acquire Fired whenever the a client is acquired from the pool Example: This allows you to count the number of clients which have ever been acquired from the pool. ```js var Pool = require('pg-pool') var pool = new Pool() var acquireCount = 0 pool.on('acquire', function (client) { acquireCount++ }) var connectCount = 0 pool.on('connect', function () { connectCount++ }) for (var i = 0; i < 200; i++) { pool.query('SELECT NOW()') } setTimeout(function () { console.log('connect count:', connectCount) // output: connect count: 10 console.log('acquire count:', acquireCount) // output: acquire count: 200 }, 100) ``` ### environment variables pg-pool & node-postgres support some of the same environment variables as `psql` supports. The most common are: ``` PGDATABASE=my_db PGUSER=username PGPASSWORD="my awesome password" PGPORT=5432 PGSSLMODE=require ``` Usually I will export these into my local environment via a `.env` file with environment settings or export them in `~/.bash_profile` or something similar. This way I get configurability which works with both the postgres suite of tools (`psql`, `pg_dump`, `pg_restore`) and node, I can vary the environment variables locally and in production, and it supports the concept of a [12-factor app](http://12factor.net/) out of the box. ## bring your own promise In versions of node `<=0.12.x` there is no native promise implementation available globally. You can polyfill the promise globally like this: ```js // first run `npm install promise-polyfill --save if (typeof Promise == 'undefined') { global.Promise = require('promise-polyfill') } ``` You can use any other promise implementation you'd like. The pool also allows you to configure the promise implementation on a per-pool level: ```js var bluebirdPool = new Pool({ Promise: require('bluebird') }) ``` __please note:__ in node `<=0.12.x` the pool will throw if you do not provide a promise constructor in one of the two ways mentioned above. In node `>=4.0.0` the pool will use the native promise implementation by default; however, the two methods above still allow you to "bring your own." ## tests To run tests clone the repo, `npm i` in the working dir, and then run `npm test` ## contributions I love contributions. Please make sure they have tests, and submit a PR. If you're not sure if the issue is worth it or will be accepted it never hurts to open an issue to begin the conversation. If you're interested in keeping up with node-postgres releated stuff, you can follow me on twitter at [@briancarlson](https://twitter.com/briancarlson) - I generally announce any noteworthy updates there. ## license The MIT License (MIT) Copyright (c) 2016 Brian M. Carlson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package/test/bring-your-own-promise.js000644 0000002115 3560116604 015146 0ustar00000000 000000 'use strict' const co = require('co') const expect = require('expect.js') const describe = require('mocha').describe const it = require('mocha').it const BluebirdPromise = require('bluebird') const Pool = require('../') const checkType = promise => { expect(promise).to.be.a(BluebirdPromise) return promise.catch(e => undefined) } describe('Bring your own promise', function () { it('uses supplied promise for operations', co.wrap(function * () { const pool = new Pool({ Promise: BluebirdPromise }) const client1 = yield checkType(pool.connect()) client1.release() yield checkType(pool.query('SELECT NOW()')) const client2 = yield checkType(pool.connect()) // TODO - make sure pg supports BYOP as well client2.release() yield checkType(pool.end()) })) it('uses promises in errors', co.wrap(function * () { const pool = new Pool({ Promise: BluebirdPromise, port: 48484 }) yield checkType(pool.connect()) yield checkType(pool.end()) yield checkType(pool.connect()) yield checkType(pool.query()) yield checkType(pool.end()) })) }) package/test/connection-strings.js000644 0000001505 3560116604 014424 0ustar00000000 000000 const expect = require('expect.js') const describe = require('mocha').describe const it = require('mocha').it const Pool = require('../') describe('Connection strings', function () { it('pool delegates connectionString property to client', function (done) { const connectionString = 'postgres://foo:bar@baz:1234/xur' const pool = new Pool({ // use a fake client so we can check we're passed the connectionString Client: function (args) { expect(args.connectionString).to.equal(connectionString) return { connect: function (cb) { cb(new Error('testing')) }, on: function () { } } }, connectionString: connectionString }) pool.connect(function (err, client) { expect(err).to.not.be(undefined) done() }) }) }) package/test/connection-timeout.js000644 0000013712 3560116604 014424 0ustar00000000 000000 'use strict' const net = require('net') const co = require('co') const expect = require('expect.js') const describe = require('mocha').describe const it = require('mocha').it const before = require('mocha').before const after = require('mocha').after const Pool = require('../') describe('connection timeout', () => { const connectionFailure = new Error('Temporary connection failure') before((done) => { this.server = net.createServer((socket) => { socket.on('data', () => { // discard any buffered data or the server wont terminate }) }) this.server.listen(() => { this.port = this.server.address().port done() }) }) after((done) => { this.server.close(done) }) it('should callback with an error if timeout is passed', (done) => { const pool = new Pool({ connectionTimeoutMillis: 10, port: this.port }) pool.connect((err, client, release) => { expect(err).to.be.an(Error) expect(err.message).to.contain('timeout') expect(client).to.equal(undefined) expect(pool.idleCount).to.equal(0) done() }) }) it('should reject promise with an error if timeout is passed', (done) => { const pool = new Pool({ connectionTimeoutMillis: 10, port: this.port }) pool.connect().catch(err => { expect(err).to.be.an(Error) expect(err.message).to.contain('timeout') expect(pool.idleCount).to.equal(0) done() }) }) it('should handle multiple timeouts', co.wrap(function * () { const errors = [] const pool = new Pool({ connectionTimeoutMillis: 1, port: this.port }) for (var i = 0; i < 15; i++) { try { yield pool.connect() } catch (e) { errors.push(e) } } expect(errors).to.have.length(15) }.bind(this))) it('should timeout on checkout of used connection', (done) => { const pool = new Pool({ connectionTimeoutMillis: 100, max: 1 }) pool.connect((err, client, release) => { expect(err).to.be(undefined) expect(client).to.not.be(undefined) pool.connect((err, client) => { expect(err).to.be.an(Error) expect(client).to.be(undefined) release() pool.end(done) }) }) }) it('should not break further pending checkouts on a timeout', (done) => { const pool = new Pool({ connectionTimeoutMillis: 200, max: 1 }) pool.connect((err, client, releaseOuter) => { expect(err).to.be(undefined) pool.connect((err, client) => { expect(err).to.be.an(Error) expect(client).to.be(undefined) releaseOuter() }) setTimeout(() => { pool.connect((err, client, releaseInner) => { expect(err).to.be(undefined) expect(client).to.not.be(undefined) releaseInner() pool.end(done) }) }, 100) }) }) it('should timeout on query if all clients are busy', (done) => { const pool = new Pool({ connectionTimeoutMillis: 100, max: 1 }) pool.connect((err, client, release) => { expect(err).to.be(undefined) expect(client).to.not.be(undefined) pool.query('select now()', (err, result) => { expect(err).to.be.an(Error) expect(result).to.be(undefined) release() pool.end(done) }) }) }) it('should recover from timeout errors', (done) => { const pool = new Pool({ connectionTimeoutMillis: 100, max: 1 }) pool.connect((err, client, release) => { expect(err).to.be(undefined) expect(client).to.not.be(undefined) pool.query('select now()', (err, result) => { expect(err).to.be.an(Error) expect(result).to.be(undefined) release() pool.query('select $1::text as name', ['brianc'], (err, res) => { expect(err).to.be(undefined) expect(res.rows).to.have.length(1) pool.end(done) }) }) }) }) it('continues processing after a connection failure', (done) => { const Client = require('pg').Client const orgConnect = Client.prototype.connect let called = false Client.prototype.connect = function (cb) { // Simulate a failure on first call if (!called) { called = true return setTimeout(() => { cb(connectionFailure) }, 100) } // And pass-through the second call orgConnect.call(this, cb) } const pool = new Pool({ Client: Client, connectionTimeoutMillis: 1000, max: 1 }) pool.connect((err, client, release) => { expect(err).to.be(connectionFailure) pool.query('select $1::text as name', ['brianc'], (err, res) => { expect(err).to.be(undefined) expect(res.rows).to.have.length(1) pool.end(done) }) }) }) it('releases newly connected clients if the queued already timed out', (done) => { const Client = require('pg').Client const orgConnect = Client.prototype.connect let connection = 0 Client.prototype.connect = function (cb) { // Simulate a failure on first call if (connection === 0) { connection++ return setTimeout(() => { cb(connectionFailure) }, 300) } // And second connect taking > connection timeout if (connection === 1) { connection++ return setTimeout(() => { orgConnect.call(this, cb) }, 1000) } orgConnect.call(this, cb) } const pool = new Pool({ Client: Client, connectionTimeoutMillis: 1000, max: 1 }) // Direct connect pool.connect((err, client, release) => { expect(err).to.be(connectionFailure) }) // Queued let called = 0 pool.connect((err, client, release) => { // Verify the callback is only called once expect(called++).to.be(0) expect(err).to.be.an(Error) pool.query('select $1::text as name', ['brianc'], (err, res) => { expect(err).to.be(undefined) expect(res.rows).to.have.length(1) pool.end(done) }) }) }) }) package/test/ending.js000644 0000001537 3560116604 012047 0ustar00000000 000000 'use strict' const co = require('co') const expect = require('expect.js') const describe = require('mocha').describe const it = require('mocha').it const Pool = require('../') describe('pool ending', () => { it('ends without being used', (done) => { const pool = new Pool() pool.end(done) }) it('ends with a promise', () => { return new Pool().end() }) it('ends with clients', co.wrap(function * () { const pool = new Pool() const res = yield pool.query('SELECT $1::text as name', ['brianc']) expect(res.rows[0].name).to.equal('brianc') return pool.end() })) it('allows client to finish', co.wrap(function * () { const pool = new Pool() const query = pool.query('SELECT $1::text as name', ['brianc']) yield pool.end() const res = yield query expect(res.rows[0].name).to.equal('brianc') })) }) package/test/error-handling.js000644 0000011644 3560116604 013516 0ustar00000000 000000 'use strict' const net = require('net') const co = require('co') const expect = require('expect.js') const describe = require('mocha').describe const it = require('mocha').it const Pool = require('../') describe('pool error handling', function () { it('Should complete these queries without dying', function (done) { const pool = new Pool() let errors = 0 let shouldGet = 0 function runErrorQuery () { shouldGet++ return new Promise(function (resolve, reject) { pool.query("SELECT 'asd'+1 ").then(function (res) { reject(res) // this should always error }).catch(function (err) { errors++ resolve(err) }) }) } const ps = [] for (let i = 0; i < 5; i++) { ps.push(runErrorQuery()) } Promise.all(ps).then(function () { expect(shouldGet).to.eql(errors) pool.end(done) }) }) describe('calling release more than once', () => { it('should throw each time', co.wrap(function * () { const pool = new Pool() const client = yield pool.connect() client.release() expect(() => client.release()).to.throwError() expect(() => client.release()).to.throwError() return yield pool.end() })) }) describe('calling connect after end', () => { it('should return an error', function * () { const pool = new Pool() const res = yield pool.query('SELECT $1::text as name', ['hi']) expect(res.rows[0].name).to.equal('hi') const wait = pool.end() pool.query('select now()') yield wait expect(() => pool.query('select now()')).to.reject() }) }) describe('using an ended pool', () => { it('rejects all additional promises', (done) => { const pool = new Pool() const promises = [] pool.end() .then(() => { const squash = promise => promise.catch(e => 'okay!') promises.push(squash(pool.connect())) promises.push(squash(pool.query('SELECT NOW()'))) promises.push(squash(pool.end())) Promise.all(promises).then(res => { expect(res).to.eql(['okay!', 'okay!', 'okay!']) done() }) }) }) it('returns an error on all additional callbacks', (done) => { const pool = new Pool() pool.end(() => { pool.query('SELECT *', (err) => { expect(err).to.be.an(Error) pool.connect((err) => { expect(err).to.be.an(Error) pool.end((err) => { expect(err).to.be.an(Error) done() }) }) }) }) }) }) describe('error from idle client', () => { it('removes client from pool', co.wrap(function * () { const pool = new Pool() const client = yield pool.connect() expect(pool.totalCount).to.equal(1) expect(pool.waitingCount).to.equal(0) expect(pool.idleCount).to.equal(0) client.release() yield new Promise((resolve, reject) => { process.nextTick(() => { pool.once('error', (err) => { expect(err.message).to.equal('expected') expect(pool.idleCount).to.equal(0) expect(pool.totalCount).to.equal(0) pool.end().then(resolve, reject) }) client.emit('error', new Error('expected')) }) }) })) }) describe('passing a function to pool.query', () => { it('calls back with error', (done) => { const pool = new Pool() console.log('passing fn to query') pool.query((err) => { expect(err).to.be.an(Error) pool.end(done) }) }) }) describe('pool with lots of errors', () => { it('continues to work and provide new clients', co.wrap(function * () { const pool = new Pool({ max: 1 }) const errors = [] for (var i = 0; i < 20; i++) { try { yield pool.query('invalid sql') } catch (err) { errors.push(err) } } expect(errors).to.have.length(20) expect(pool.idleCount).to.equal(0) expect(pool.query).to.be.a(Function) const res = yield pool.query('SELECT $1::text as name', ['brianc']) expect(res.rows).to.have.length(1) expect(res.rows[0].name).to.equal('brianc') return pool.end() })) }) it('should continue with queued items after a connection failure', (done) => { const closeServer = net.createServer((socket) => { socket.destroy() }).unref() closeServer.listen(() => { const pool = new Pool({ max: 1, port: closeServer.address().port }) pool.connect((err) => { expect(err).to.be.an(Error) if (err.errno) { expect(err.errno).to.be('ECONNRESET') } }) pool.connect((err) => { expect(err).to.be.an(Error) if (err.errno) { expect(err.errno).to.be('ECONNRESET') } closeServer.close(() => { pool.end(done) }) }) }) }) }) package/test/events.js000644 0000004355 3560116604 012110 0ustar00000000 000000 'use strict' const expect = require('expect.js') const EventEmitter = require('events').EventEmitter const describe = require('mocha').describe const it = require('mocha').it const Pool = require('../') describe('events', function () { it('emits connect before callback', function (done) { const pool = new Pool() let emittedClient = false pool.on('connect', function (client) { emittedClient = client }) pool.connect(function (err, client, release) { if (err) return done(err) release() pool.end() expect(client).to.be(emittedClient) done() }) }) it('emits "connect" only with a successful connection', function () { const pool = new Pool({ // This client will always fail to connect Client: mockClient({ connect: function (cb) { process.nextTick(() => { cb(new Error('bad news')) }) } }) }) pool.on('connect', function () { throw new Error('should never get here') }) return pool.connect().catch(e => expect(e.message).to.equal('bad news')) }) it('emits acquire every time a client is acquired', function (done) { const pool = new Pool() let acquireCount = 0 pool.on('acquire', function (client) { expect(client).to.be.ok() acquireCount++ }) for (let i = 0; i < 10; i++) { pool.connect(function (err, client, release) { if (err) return done(err) release() }) pool.query('SELECT now()') } setTimeout(function () { expect(acquireCount).to.be(20) pool.end(done) }, 100) }) it('emits error and client if an idle client in the pool hits an error', function (done) { const pool = new Pool() pool.connect(function (err, client) { expect(err).to.equal(undefined) client.release() setImmediate(function () { client.emit('error', new Error('problem')) }) pool.once('error', function (err, errClient) { expect(err.message).to.equal('problem') expect(errClient).to.equal(client) done() }) }) }) }) function mockClient (methods) { return function () { const client = new EventEmitter() Object.assign(client, methods) return client } } package/test/idle-timeout.js000644 0000004420 3560116604 013176 0ustar00000000 000000 'use strict' const co = require('co') const expect = require('expect.js') const describe = require('mocha').describe const it = require('mocha').it const Pool = require('../') const wait = time => new Promise((resolve) => setTimeout(resolve, time)) describe('idle timeout', () => { it('should timeout and remove the client', (done) => { const pool = new Pool({ idleTimeoutMillis: 10 }) pool.query('SELECT NOW()') pool.on('remove', () => { expect(pool.idleCount).to.equal(0) expect(pool.totalCount).to.equal(0) done() }) }) it('times out and removes clients when others are also removed', co.wrap(function * () { const pool = new Pool({ idleTimeoutMillis: 10 }) const clientA = yield pool.connect() const clientB = yield pool.connect() clientA.release() clientB.release(new Error()) const removal = new Promise((resolve) => { pool.on('remove', () => { expect(pool.idleCount).to.equal(0) expect(pool.totalCount).to.equal(0) resolve() }) }) const timeout = wait(100).then(() => Promise.reject(new Error('Idle timeout failed to occur'))) try { yield Promise.race([removal, timeout]) } finally { pool.end() } })) it('can remove idle clients and recreate them', co.wrap(function * () { const pool = new Pool({ idleTimeoutMillis: 1 }) const results = [] for (var i = 0; i < 20; i++) { let query = pool.query('SELECT NOW()') expect(pool.idleCount).to.equal(0) expect(pool.totalCount).to.equal(1) results.push(yield query) yield wait(2) expect(pool.idleCount).to.equal(0) expect(pool.totalCount).to.equal(0) } expect(results).to.have.length(20) })) it('does not time out clients which are used', co.wrap(function * () { const pool = new Pool({ idleTimeoutMillis: 1 }) const results = [] for (var i = 0; i < 20; i++) { let client = yield pool.connect() expect(pool.totalCount).to.equal(1) expect(pool.idleCount).to.equal(0) yield wait(10) results.push(yield client.query('SELECT NOW()')) client.release() expect(pool.idleCount).to.equal(1) expect(pool.totalCount).to.equal(1) } expect(results).to.have.length(20) return pool.end() })) }) package/test/index.js000644 0000015437 3560116604 011716 0ustar00000000 000000 'use strict' const expect = require('expect.js') const _ = require('lodash') const describe = require('mocha').describe const it = require('mocha').it const Pool = require('../') describe('pool', function () { describe('with callbacks', function () { it('works totally unconfigured', function (done) { const pool = new Pool() pool.connect(function (err, client, release) { if (err) return done(err) client.query('SELECT NOW()', function (err, res) { release() if (err) return done(err) expect(res.rows).to.have.length(1) pool.end(done) }) }) }) it('passes props to clients', function (done) { const pool = new Pool({ binary: true }) pool.connect(function (err, client, release) { release() if (err) return done(err) expect(client.binary).to.eql(true) pool.end(done) }) }) it('can run a query with a callback without parameters', function (done) { const pool = new Pool() pool.query('SELECT 1 as num', function (err, res) { expect(res.rows[0]).to.eql({ num: 1 }) pool.end(function () { done(err) }) }) }) it('can run a query with a callback', function (done) { const pool = new Pool() pool.query('SELECT $1::text as name', ['brianc'], function (err, res) { expect(res.rows[0]).to.eql({ name: 'brianc' }) pool.end(function () { done(err) }) }) }) it('passes connection errors to callback', function (done) { const pool = new Pool({ port: 53922 }) pool.query('SELECT $1::text as name', ['brianc'], function (err, res) { expect(res).to.be(undefined) expect(err).to.be.an(Error) // a connection error should not polute the pool with a dead client expect(pool.totalCount).to.equal(0) pool.end(function (err) { done(err) }) }) }) it('does not pass client to error callback', function (done) { const pool = new Pool({ port: 58242 }) pool.connect(function (err, client, release) { expect(err).to.be.an(Error) expect(client).to.be(undefined) expect(release).to.be.a(Function) pool.end(done) }) }) it('removes client if it errors in background', function (done) { const pool = new Pool() pool.connect(function (err, client, release) { release() if (err) return done(err) client.testString = 'foo' setTimeout(function () { client.emit('error', new Error('on purpose')) }, 10) }) pool.on('error', function (err) { expect(err.message).to.be('on purpose') expect(err.client).to.not.be(undefined) expect(err.client.testString).to.be('foo') err.client.connection.stream.on('end', function () { pool.end(done) }) }) }) it('should not change given options', function (done) { const options = { max: 10 } const pool = new Pool(options) pool.connect(function (err, client, release) { release() if (err) return done(err) expect(options).to.eql({ max: 10 }) pool.end(done) }) }) it('does not create promises when connecting', function (done) { const pool = new Pool() const returnValue = pool.connect(function (err, client, release) { release() if (err) return done(err) pool.end(done) }) expect(returnValue).to.be(undefined) }) it('does not create promises when querying', function (done) { const pool = new Pool() const returnValue = pool.query('SELECT 1 as num', function (err) { pool.end(function () { done(err) }) }) expect(returnValue).to.be(undefined) }) it('does not create promises when ending', function (done) { const pool = new Pool() const returnValue = pool.end(done) expect(returnValue).to.be(undefined) }) it('never calls callback syncronously', function (done) { const pool = new Pool() pool.connect((err, client) => { if (err) throw err client.release() setImmediate(() => { let called = false pool.connect((err, client) => { if (err) throw err called = true client.release() setImmediate(() => { pool.end(done) }) }) expect(called).to.equal(false) }) }) }) }) describe('with promises', function () { it('connects, queries, and disconnects', function () { const pool = new Pool() return pool.connect().then(function (client) { return client.query('select $1::text as name', ['hi']).then(function (res) { expect(res.rows).to.eql([{ name: 'hi' }]) client.release() return pool.end() }) }) }) it('executes a query directly', () => { const pool = new Pool() return pool .query('SELECT $1::text as name', ['hi']) .then(res => { expect(res.rows).to.have.length(1) expect(res.rows[0].name).to.equal('hi') return pool.end() }) }) it('properly pools clients', function () { const pool = new Pool({ poolSize: 9 }) const promises = _.times(30, function () { return pool.connect().then(function (client) { return client.query('select $1::text as name', ['hi']).then(function (res) { client.release() return res }) }) }) return Promise.all(promises).then(function (res) { expect(res).to.have.length(30) expect(pool.totalCount).to.be(9) return pool.end() }) }) it('supports just running queries', function () { const pool = new Pool({ poolSize: 9 }) const text = 'select $1::text as name' const values = ['hi'] const query = { text: text, values: values } const promises = _.times(30, () => pool.query(query)) return Promise.all(promises).then(function (queries) { expect(queries).to.have.length(30) return pool.end() }) }) it('recovers from query errors', function () { const pool = new Pool() const errors = [] const promises = _.times(30, () => { return pool.query('SELECT asldkfjasldkf') .catch(function (e) { errors.push(e) }) }) return Promise.all(promises).then(() => { expect(errors).to.have.length(30) expect(pool.totalCount).to.equal(0) expect(pool.idleCount).to.equal(0) return pool.query('SELECT $1::text as name', ['hi']).then(function (res) { expect(res.rows).to.eql([{ name: 'hi' }]) return pool.end() }) }) }) }) }) package/test/logging.js000644 0000000777 3560116604 012236 0ustar00000000 000000 const expect = require('expect.js') const describe = require('mocha').describe const it = require('mocha').it const Pool = require('../') describe('logging', function () { it('logs to supplied log function if given', function () { const messages = [] const log = function (msg) { messages.push(msg) } const pool = new Pool({ log: log }) return pool.query('SELECT NOW()').then(function () { expect(messages.length).to.be.greaterThan(0) return pool.end() }) }) }) package/test/mocha.opts000644 0000000057 3560116604 012237 0ustar00000000 000000 --require test/setup.js --bail --timeout 10000 package/test/setup.js000644 0000000276 3560116604 011742 0ustar00000000 000000 const crash = reason => { process.on(reason, err => { console.error(reason, err.stack) process.exit(-1) }) } crash('unhandledRejection') crash('uncaughtError') crash('warning') package/test/sizing.js000644 0000003351 3560116604 012102 0ustar00000000 000000 const expect = require('expect.js') const co = require('co') const _ = require('lodash') const describe = require('mocha').describe const it = require('mocha').it const Pool = require('../') describe('pool size of 1', () => { it('can create a single client and use it once', co.wrap(function * () { const pool = new Pool({ max: 1 }) expect(pool.waitingCount).to.equal(0) const client = yield pool.connect() const res = yield client.query('SELECT $1::text as name', ['hi']) expect(res.rows[0].name).to.equal('hi') client.release() pool.end() })) it('can create a single client and use it multiple times', co.wrap(function * () { const pool = new Pool({ max: 1 }) expect(pool.waitingCount).to.equal(0) const client = yield pool.connect() const wait = pool.connect() expect(pool.waitingCount).to.equal(1) client.release() const client2 = yield wait expect(client).to.equal(client2) client2.release() return yield pool.end() })) it('can only send 1 query at a time', co.wrap(function * () { const pool = new Pool({ max: 1 }) // the query text column name changed in PostgreSQL 9.2 const versionResult = yield pool.query('SHOW server_version_num') const version = parseInt(versionResult.rows[0].server_version_num, 10) const queryColumn = version < 90200 ? 'current_query' : 'query' const queryText = 'SELECT COUNT(*) as counts FROM pg_stat_activity WHERE ' + queryColumn + ' = $1' const queries = _.times(20, () => pool.query(queryText, [queryText])) const results = yield Promise.all(queries) const counts = results.map(res => parseInt(res.rows[0].counts, 10)) expect(counts).to.eql(_.times(20, i => 1)) return yield pool.end() })) }) package/test/submittable.js000644 0000001024 3560116604 013105 0ustar00000000 000000 'use strict' const Cursor = require('pg-cursor') const expect = require('expect.js') const describe = require('mocha').describe const it = require('mocha').it const Pool = require('../') describe('submittle', () => { it('is returned from the query method', false, (done) => { const pool = new Pool() const cursor = pool.query(new Cursor('SELECT * from generate_series(0, 1000)')) cursor.read((err, rows) => { expect(err).to.be(undefined) expect(!!rows).to.be.ok() cursor.close(done) }) }) }) package/test/timeout.js000644 0000000000 3560116604 012251 0ustar00000000 000000 package/test/verify.js000644 0000001020 3560116604 012072 0ustar00000000 000000 'use strict' const expect = require('expect.js') const describe = require('mocha').describe const it = require('mocha').it const Pool = require('../') describe('verify', () => { it('verifies a client with a callback', false, (done) => { const pool = new Pool({ verify: (client, cb) => { client.release() cb(new Error('nope')) } }) pool.connect((err, client) => { expect(err).to.be.an(Error) expect(err.message).to.be('nope') pool.end() done() }) }) })