Lint pg & turn off semicolons

This commit is contained in:
Brian M. Carlson 2020-04-10 10:47:57 -05:00
parent 6adbcabf50
commit c13cf81ee8
155 changed files with 4698 additions and 4049 deletions

View File

@ -9,9 +9,7 @@
],
"ignorePatterns": [
"node_modules",
"packages/pg",
"packages/pg-protocol/dist/**/*",
"packages/pg-pool"
"packages/pg-protocol/dist/**/*"
],
"parserOptions": {
"ecmaVersion": 2017,

View File

@ -1,5 +1,5 @@
{
"semi": true,
"semi": false,
"printWidth": 120,
"trailingComma": "es5",
"singleQuote": true

View File

@ -1,53 +1,53 @@
'use strict';
const Result = require('pg/lib/result.js');
const prepare = require('pg/lib/utils.js').prepareValue;
const EventEmitter = require('events').EventEmitter;
const util = require('util');
'use strict'
const Result = require('pg/lib/result.js')
const prepare = require('pg/lib/utils.js').prepareValue
const EventEmitter = require('events').EventEmitter
const util = require('util')
let nextUniqueID = 1; // concept borrowed from org.postgresql.core.v3.QueryExecutorImpl
let nextUniqueID = 1 // concept borrowed from org.postgresql.core.v3.QueryExecutorImpl
function Cursor(text, values, config) {
EventEmitter.call(this);
EventEmitter.call(this)
this._conf = config || {};
this.text = text;
this.values = values ? values.map(prepare) : null;
this.connection = null;
this._queue = [];
this.state = 'initialized';
this._result = new Result(this._conf.rowMode, this._conf.types);
this._cb = null;
this._rows = null;
this._portal = null;
this._ifNoData = this._ifNoData.bind(this);
this._rowDescription = this._rowDescription.bind(this);
this._conf = config || {}
this.text = text
this.values = values ? values.map(prepare) : null
this.connection = null
this._queue = []
this.state = 'initialized'
this._result = new Result(this._conf.rowMode, this._conf.types)
this._cb = null
this._rows = null
this._portal = null
this._ifNoData = this._ifNoData.bind(this)
this._rowDescription = this._rowDescription.bind(this)
}
util.inherits(Cursor, EventEmitter);
util.inherits(Cursor, EventEmitter)
Cursor.prototype._ifNoData = function () {
this.state = 'idle';
this._shiftQueue();
};
this.state = 'idle'
this._shiftQueue()
}
Cursor.prototype._rowDescription = function () {
if (this.connection) {
this.connection.removeListener('noData', this._ifNoData);
this.connection.removeListener('noData', this._ifNoData)
}
};
}
Cursor.prototype.submit = function (connection) {
this.connection = connection;
this._portal = 'C_' + nextUniqueID++;
this.connection = connection
this._portal = 'C_' + nextUniqueID++
const con = connection;
const con = connection
con.parse(
{
text: this.text,
},
true
);
)
con.bind(
{
@ -55,7 +55,7 @@ Cursor.prototype.submit = function (connection) {
values: this.values,
},
true
);
)
con.describe(
{
@ -63,156 +63,156 @@ Cursor.prototype.submit = function (connection) {
name: this._portal, // AWS Redshift requires a portal name
},
true
);
)
con.flush();
con.flush()
if (this._conf.types) {
this._result._getTypeParser = this._conf.types.getTypeParser;
this._result._getTypeParser = this._conf.types.getTypeParser
}
con.once('noData', this._ifNoData);
con.once('rowDescription', this._rowDescription);
};
con.once('noData', this._ifNoData)
con.once('rowDescription', this._rowDescription)
}
Cursor.prototype._shiftQueue = function () {
if (this._queue.length) {
this._getRows.apply(this, this._queue.shift());
this._getRows.apply(this, this._queue.shift())
}
};
}
Cursor.prototype._closePortal = function () {
// because we opened a named portal to stream results
// we need to close the same named portal. Leaving a named portal
// open can lock tables for modification if inside a transaction.
// see https://github.com/brianc/node-pg-cursor/issues/56
this.connection.close({ type: 'P', name: this._portal });
this.connection.sync();
};
this.connection.close({ type: 'P', name: this._portal })
this.connection.sync()
}
Cursor.prototype.handleRowDescription = function (msg) {
this._result.addFields(msg.fields);
this.state = 'idle';
this._shiftQueue();
};
this._result.addFields(msg.fields)
this.state = 'idle'
this._shiftQueue()
}
Cursor.prototype.handleDataRow = function (msg) {
const row = this._result.parseRow(msg.fields);
this.emit('row', row, this._result);
this._rows.push(row);
};
const row = this._result.parseRow(msg.fields)
this.emit('row', row, this._result)
this._rows.push(row)
}
Cursor.prototype._sendRows = function () {
this.state = 'idle';
this.state = 'idle'
setImmediate(() => {
const cb = this._cb;
const cb = this._cb
// remove callback before calling it
// because likely a new one will be added
// within the call to this callback
this._cb = null;
this._cb = null
if (cb) {
this._result.rows = this._rows;
cb(null, this._rows, this._result);
this._result.rows = this._rows
cb(null, this._rows, this._result)
}
this._rows = [];
});
};
this._rows = []
})
}
Cursor.prototype.handleCommandComplete = function (msg) {
this._result.addCommandComplete(msg);
this._closePortal();
};
this._result.addCommandComplete(msg)
this._closePortal()
}
Cursor.prototype.handlePortalSuspended = function () {
this._sendRows();
};
this._sendRows()
}
Cursor.prototype.handleReadyForQuery = function () {
this._sendRows();
this.state = 'done';
this.emit('end', this._result);
};
this._sendRows()
this.state = 'done'
this.emit('end', this._result)
}
Cursor.prototype.handleEmptyQuery = function () {
this.connection.sync();
};
this.connection.sync()
}
Cursor.prototype.handleError = function (msg) {
this.connection.removeListener('noData', this._ifNoData);
this.connection.removeListener('rowDescription', this._rowDescription);
this.state = 'error';
this._error = msg;
this.connection.removeListener('noData', this._ifNoData)
this.connection.removeListener('rowDescription', this._rowDescription)
this.state = 'error'
this._error = msg
// satisfy any waiting callback
if (this._cb) {
this._cb(msg);
this._cb(msg)
}
// dispatch error to all waiting callbacks
for (let i = 0; i < this._queue.length; i++) {
this._queue.pop()[1](msg);
this._queue.pop()[1](msg)
}
if (this.listenerCount('error') > 0) {
// only dispatch error events if we have a listener
this.emit('error', msg);
this.emit('error', msg)
}
// call sync to keep this connection from hanging
this.connection.sync();
};
this.connection.sync()
}
Cursor.prototype._getRows = function (rows, cb) {
this.state = 'busy';
this._cb = cb;
this._rows = [];
this.state = 'busy'
this._cb = cb
this._rows = []
const msg = {
portal: this._portal,
rows: rows,
};
this.connection.execute(msg, true);
this.connection.flush();
};
}
this.connection.execute(msg, true)
this.connection.flush()
}
// users really shouldn't be calling 'end' here and terminating a connection to postgres
// via the low level connection.end api
Cursor.prototype.end = util.deprecate(function (cb) {
if (this.state !== 'initialized') {
this.connection.sync();
this.connection.sync()
}
this.connection.once('end', cb);
this.connection.end();
}, 'Cursor.end is deprecated. Call end on the client itself to end a connection to the database.');
this.connection.once('end', cb)
this.connection.end()
}, 'Cursor.end is deprecated. Call end on the client itself to end a connection to the database.')
Cursor.prototype.close = function (cb) {
if (!this.connection || this.state === 'done') {
if (cb) {
return setImmediate(cb);
return setImmediate(cb)
} else {
return;
return
}
}
this._closePortal();
this.state = 'done';
this._closePortal()
this.state = 'done'
if (cb) {
this.connection.once('readyForQuery', function () {
cb();
});
cb()
})
}
};
}
Cursor.prototype.read = function (rows, cb) {
if (this.state === 'idle') {
return this._getRows(rows, cb);
return this._getRows(rows, cb)
}
if (this.state === 'busy' || this.state === 'initialized') {
return this._queue.push([rows, cb]);
return this._queue.push([rows, cb])
}
if (this.state === 'error') {
return setImmediate(() => cb(this._error));
return setImmediate(() => cb(this._error))
}
if (this.state === 'done') {
return setImmediate(() => cb(null, []));
return setImmediate(() => cb(null, []))
} else {
throw new Error('Unknown state: ' + this.state);
throw new Error('Unknown state: ' + this.state)
}
};
}
module.exports = Cursor;
module.exports = Cursor

View File

@ -1,54 +1,54 @@
const assert = require('assert');
const Cursor = require('../');
const pg = require('pg');
const assert = require('assert')
const Cursor = require('../')
const pg = require('pg')
const text = 'SELECT generate_series as num FROM generate_series(0, 50)';
const text = 'SELECT generate_series as num FROM generate_series(0, 50)'
describe('close', function () {
beforeEach(function (done) {
const client = (this.client = new pg.Client());
client.connect(done);
});
const client = (this.client = new pg.Client())
client.connect(done)
})
this.afterEach(function (done) {
this.client.end(done);
});
this.client.end(done)
})
it('can close a finished cursor without a callback', function (done) {
const cursor = new Cursor(text);
this.client.query(cursor);
this.client.query('SELECT NOW()', done);
const cursor = new Cursor(text)
this.client.query(cursor)
this.client.query('SELECT NOW()', done)
cursor.read(100, function (err) {
assert.ifError(err);
cursor.close();
});
});
assert.ifError(err)
cursor.close()
})
})
it('closes cursor early', function (done) {
const cursor = new Cursor(text);
this.client.query(cursor);
this.client.query('SELECT NOW()', done);
const cursor = new Cursor(text)
this.client.query(cursor)
this.client.query('SELECT NOW()', done)
cursor.read(25, function (err) {
assert.ifError(err);
cursor.close();
});
});
assert.ifError(err)
cursor.close()
})
})
it('works with callback style', function (done) {
const cursor = new Cursor(text);
const client = this.client;
client.query(cursor);
const cursor = new Cursor(text)
const client = this.client
client.query(cursor)
cursor.read(25, function (err, rows) {
assert.ifError(err);
assert.strictEqual(rows.length, 25);
assert.ifError(err)
assert.strictEqual(rows.length, 25)
cursor.close(function (err) {
assert.ifError(err);
client.query('SELECT NOW()', done);
});
});
});
assert.ifError(err)
client.query('SELECT NOW()', done)
})
})
})
it('is a no-op to "close" the cursor before submitting it', function (done) {
const cursor = new Cursor(text);
cursor.close(done);
});
});
const cursor = new Cursor(text)
cursor.close(done)
})
})

View File

@ -1,86 +1,86 @@
'use strict';
const assert = require('assert');
const Cursor = require('../');
const pg = require('pg');
'use strict'
const assert = require('assert')
const Cursor = require('../')
const pg = require('pg')
const text = 'SELECT generate_series as num FROM generate_series(0, 4)';
const text = 'SELECT generate_series as num FROM generate_series(0, 4)'
describe('error handling', function () {
it('can continue after error', function (done) {
const client = new pg.Client();
client.connect();
const cursor = client.query(new Cursor('asdfdffsdf'));
const client = new pg.Client()
client.connect()
const cursor = client.query(new Cursor('asdfdffsdf'))
cursor.read(1, function (err) {
assert(err);
assert(err)
client.query('SELECT NOW()', function (err) {
assert.ifError(err);
client.end();
done();
});
});
});
});
assert.ifError(err)
client.end()
done()
})
})
})
})
describe('read callback does not fire sync', () => {
it('does not fire error callback sync', (done) => {
const client = new pg.Client();
client.connect();
const cursor = client.query(new Cursor('asdfdffsdf'));
let after = false;
const client = new pg.Client()
client.connect()
const cursor = client.query(new Cursor('asdfdffsdf'))
let after = false
cursor.read(1, function (err) {
assert(err, 'error should be returned');
assert.strictEqual(after, true, 'should not call read sync');
after = false;
assert(err, 'error should be returned')
assert.strictEqual(after, true, 'should not call read sync')
after = false
cursor.read(1, function (err) {
assert(err, 'error should be returned');
assert.strictEqual(after, true, 'should not call read sync');
client.end();
done();
});
after = true;
});
after = true;
});
assert(err, 'error should be returned')
assert.strictEqual(after, true, 'should not call read sync')
client.end()
done()
})
after = true
})
after = true
})
it('does not fire result sync after finished', (done) => {
const client = new pg.Client();
client.connect();
const cursor = client.query(new Cursor('SELECT NOW()'));
let after = false;
const client = new pg.Client()
client.connect()
const cursor = client.query(new Cursor('SELECT NOW()'))
let after = false
cursor.read(1, function (err) {
assert(!err);
assert.strictEqual(after, true, 'should not call read sync');
assert(!err)
assert.strictEqual(after, true, 'should not call read sync')
cursor.read(1, function (err) {
assert(!err);
after = false;
assert(!err)
after = false
cursor.read(1, function (err) {
assert(!err);
assert.strictEqual(after, true, 'should not call read sync');
client.end();
done();
});
after = true;
});
});
after = true;
});
});
assert(!err)
assert.strictEqual(after, true, 'should not call read sync')
client.end()
done()
})
after = true
})
})
after = true
})
})
describe('proper cleanup', function () {
it('can issue multiple cursors on one client', function (done) {
const client = new pg.Client();
client.connect();
const cursor1 = client.query(new Cursor(text));
const client = new pg.Client()
client.connect()
const cursor1 = client.query(new Cursor(text))
cursor1.read(8, function (err, rows) {
assert.ifError(err);
assert.strictEqual(rows.length, 5);
const cursor2 = client.query(new Cursor(text));
assert.ifError(err)
assert.strictEqual(rows.length, 5)
const cursor2 = client.query(new Cursor(text))
cursor2.read(8, function (err, rows) {
assert.ifError(err);
assert.strictEqual(rows.length, 5);
client.end();
done();
});
});
});
});
assert.ifError(err)
assert.strictEqual(rows.length, 5)
client.end()
done()
})
})
})
})

View File

@ -1,181 +1,181 @@
const assert = require('assert');
const Cursor = require('../');
const pg = require('pg');
const assert = require('assert')
const Cursor = require('../')
const pg = require('pg')
const text = 'SELECT generate_series as num FROM generate_series(0, 5)';
const text = 'SELECT generate_series as num FROM generate_series(0, 5)'
describe('cursor', function () {
beforeEach(function (done) {
const client = (this.client = new pg.Client());
client.connect(done);
const client = (this.client = new pg.Client())
client.connect(done)
this.pgCursor = function (text, values) {
return client.query(new Cursor(text, values || []));
};
});
return client.query(new Cursor(text, values || []))
}
})
afterEach(function () {
this.client.end();
});
this.client.end()
})
it('fetch 6 when asking for 10', function (done) {
const cursor = this.pgCursor(text);
const cursor = this.pgCursor(text)
cursor.read(10, function (err, res) {
assert.ifError(err);
assert.strictEqual(res.length, 6);
done();
});
});
assert.ifError(err)
assert.strictEqual(res.length, 6)
done()
})
})
it('end before reading to end', function (done) {
const cursor = this.pgCursor(text);
const cursor = this.pgCursor(text)
cursor.read(3, function (err, res) {
assert.ifError(err);
assert.strictEqual(res.length, 3);
done();
});
});
assert.ifError(err)
assert.strictEqual(res.length, 3)
done()
})
})
it('callback with error', function (done) {
const cursor = this.pgCursor('select asdfasdf');
const cursor = this.pgCursor('select asdfasdf')
cursor.read(1, function (err) {
assert(err);
done();
});
});
assert(err)
done()
})
})
it('read a partial chunk of data', function (done) {
const cursor = this.pgCursor(text);
const cursor = this.pgCursor(text)
cursor.read(2, function (err, res) {
assert.ifError(err);
assert.strictEqual(res.length, 2);
assert.ifError(err)
assert.strictEqual(res.length, 2)
cursor.read(3, function (err, res) {
assert(!err);
assert.strictEqual(res.length, 3);
assert(!err)
assert.strictEqual(res.length, 3)
cursor.read(1, function (err, res) {
assert(!err);
assert.strictEqual(res.length, 1);
assert(!err)
assert.strictEqual(res.length, 1)
cursor.read(1, function (err, res) {
assert(!err);
assert.ifError(err);
assert.strictEqual(res.length, 0);
done();
});
});
});
});
});
assert(!err)
assert.ifError(err)
assert.strictEqual(res.length, 0)
done()
})
})
})
})
})
it('read return length 0 past the end', function (done) {
const cursor = this.pgCursor(text);
const cursor = this.pgCursor(text)
cursor.read(2, function (err) {
assert(!err);
assert(!err)
cursor.read(100, function (err, res) {
assert(!err);
assert.strictEqual(res.length, 4);
assert(!err)
assert.strictEqual(res.length, 4)
cursor.read(100, function (err, res) {
assert(!err);
assert.strictEqual(res.length, 0);
done();
});
});
});
});
assert(!err)
assert.strictEqual(res.length, 0)
done()
})
})
})
})
it('read huge result', function (done) {
this.timeout(10000);
const text = 'SELECT generate_series as num FROM generate_series(0, 100000)';
const values = [];
const cursor = this.pgCursor(text, values);
let count = 0;
this.timeout(10000)
const text = 'SELECT generate_series as num FROM generate_series(0, 100000)'
const values = []
const cursor = this.pgCursor(text, values)
let count = 0
const read = function () {
cursor.read(100, function (err, rows) {
if (err) return done(err);
if (err) return done(err)
if (!rows.length) {
assert.strictEqual(count, 100001);
return done();
assert.strictEqual(count, 100001)
return done()
}
count += rows.length;
count += rows.length
if (count % 10000 === 0) {
// console.log(count)
}
setImmediate(read);
});
};
read();
});
setImmediate(read)
})
}
read()
})
it('normalizes parameter values', function (done) {
const text = 'SELECT $1::json me';
const values = [{ name: 'brian' }];
const cursor = this.pgCursor(text, values);
const text = 'SELECT $1::json me'
const values = [{ name: 'brian' }]
const cursor = this.pgCursor(text, values)
cursor.read(1, function (err, rows) {
if (err) return done(err);
assert.strictEqual(rows[0].me.name, 'brian');
if (err) return done(err)
assert.strictEqual(rows[0].me.name, 'brian')
cursor.read(1, function (err, rows) {
assert(!err);
assert.strictEqual(rows.length, 0);
done();
});
});
});
assert(!err)
assert.strictEqual(rows.length, 0)
done()
})
})
})
it('returns result along with rows', function (done) {
const cursor = this.pgCursor(text);
const cursor = this.pgCursor(text)
cursor.read(1, function (err, rows, result) {
assert.ifError(err);
assert.strictEqual(rows.length, 1);
assert.strictEqual(rows, result.rows);
assert.ifError(err)
assert.strictEqual(rows.length, 1)
assert.strictEqual(rows, result.rows)
assert.deepStrictEqual(
result.fields.map((f) => f.name),
['num']
);
done();
});
});
)
done()
})
})
it('emits row events', function (done) {
const cursor = this.pgCursor(text);
cursor.read(10);
cursor.on('row', (row, result) => result.addRow(row));
const cursor = this.pgCursor(text)
cursor.read(10)
cursor.on('row', (row, result) => result.addRow(row))
cursor.on('end', (result) => {
assert.strictEqual(result.rows.length, 6);
done();
});
});
assert.strictEqual(result.rows.length, 6)
done()
})
})
it('emits row events when cursor is closed manually', function (done) {
const cursor = this.pgCursor(text);
cursor.on('row', (row, result) => result.addRow(row));
const cursor = this.pgCursor(text)
cursor.on('row', (row, result) => result.addRow(row))
cursor.on('end', (result) => {
assert.strictEqual(result.rows.length, 3);
done();
});
assert.strictEqual(result.rows.length, 3)
done()
})
cursor.read(3, () => cursor.close());
});
cursor.read(3, () => cursor.close())
})
it('emits error events', function (done) {
const cursor = this.pgCursor('select asdfasdf');
const cursor = this.pgCursor('select asdfasdf')
cursor.on('error', function (err) {
assert(err);
done();
});
});
assert(err)
done()
})
})
it('returns rowCount on insert', function (done) {
const pgCursor = this.pgCursor;
const pgCursor = this.pgCursor
this.client
.query('CREATE TEMPORARY TABLE pg_cursor_test (foo VARCHAR(1), bar VARCHAR(1))')
.then(function () {
const cursor = pgCursor('insert into pg_cursor_test values($1, $2)', ['a', 'b']);
const cursor = pgCursor('insert into pg_cursor_test values($1, $2)', ['a', 'b'])
cursor.read(1, function (err, rows, result) {
assert.ifError(err);
assert.strictEqual(rows.length, 0);
assert.strictEqual(result.rowCount, 1);
done();
});
assert.ifError(err)
assert.strictEqual(rows.length, 0)
assert.strictEqual(result.rowCount, 1)
done()
})
})
.catch(done);
});
});
.catch(done)
})
})

View File

@ -1,34 +1,34 @@
const assert = require('assert');
const pg = require('pg');
const Cursor = require('../');
const assert = require('assert')
const pg = require('pg')
const Cursor = require('../')
describe('queries with no data', function () {
beforeEach(function (done) {
const client = (this.client = new pg.Client());
client.connect(done);
});
const client = (this.client = new pg.Client())
client.connect(done)
})
afterEach(function () {
this.client.end();
});
this.client.end()
})
it('handles queries that return no data', function (done) {
const cursor = new Cursor('CREATE TEMPORARY TABLE whatwhat (thing int)');
this.client.query(cursor);
const cursor = new Cursor('CREATE TEMPORARY TABLE whatwhat (thing int)')
this.client.query(cursor)
cursor.read(100, function (err, rows) {
assert.ifError(err);
assert.strictEqual(rows.length, 0);
done();
});
});
assert.ifError(err)
assert.strictEqual(rows.length, 0)
done()
})
})
it('handles empty query', function (done) {
let cursor = new Cursor('-- this is a comment');
cursor = this.client.query(cursor);
let cursor = new Cursor('-- this is a comment')
cursor = this.client.query(cursor)
cursor.read(100, function (err, rows) {
assert.ifError(err);
assert.strictEqual(rows.length, 0);
done();
});
});
});
assert.ifError(err)
assert.strictEqual(rows.length, 0)
done()
})
})
})

View File

@ -1,107 +1,107 @@
'use strict';
const assert = require('assert');
const Cursor = require('../');
const pg = require('pg');
'use strict'
const assert = require('assert')
const Cursor = require('../')
const pg = require('pg')
const text = 'SELECT generate_series as num FROM generate_series(0, 50)';
const text = 'SELECT generate_series as num FROM generate_series(0, 50)'
function poolQueryPromise(pool, readRowCount) {
return new Promise((resolve, reject) => {
pool.connect((err, client, done) => {
if (err) {
done(err);
return reject(err);
done(err)
return reject(err)
}
const cursor = client.query(new Cursor(text));
const cursor = client.query(new Cursor(text))
cursor.read(readRowCount, (err) => {
if (err) {
done(err);
return reject(err);
done(err)
return reject(err)
}
cursor.close((err) => {
if (err) {
done(err);
return reject(err);
done(err)
return reject(err)
}
done();
resolve();
});
});
});
});
done()
resolve()
})
})
})
})
}
describe('pool', function () {
beforeEach(function () {
this.pool = new pg.Pool({ max: 1 });
});
this.pool = new pg.Pool({ max: 1 })
})
afterEach(function () {
this.pool.end();
});
this.pool.end()
})
it('closes cursor early, single pool query', function (done) {
poolQueryPromise(this.pool, 25)
.then(() => done())
.catch((err) => {
assert.ifError(err);
done();
});
});
assert.ifError(err)
done()
})
})
it('closes cursor early, saturated pool', function (done) {
const promises = [];
const promises = []
for (let i = 0; i < 10; i++) {
promises.push(poolQueryPromise(this.pool, 25));
promises.push(poolQueryPromise(this.pool, 25))
}
Promise.all(promises)
.then(() => done())
.catch((err) => {
assert.ifError(err);
done();
});
});
assert.ifError(err)
done()
})
})
it('closes exhausted cursor, single pool query', function (done) {
poolQueryPromise(this.pool, 100)
.then(() => done())
.catch((err) => {
assert.ifError(err);
done();
});
});
assert.ifError(err)
done()
})
})
it('closes exhausted cursor, saturated pool', function (done) {
const promises = [];
const promises = []
for (let i = 0; i < 10; i++) {
promises.push(poolQueryPromise(this.pool, 100));
promises.push(poolQueryPromise(this.pool, 100))
}
Promise.all(promises)
.then(() => done())
.catch((err) => {
assert.ifError(err);
done();
});
});
assert.ifError(err)
done()
})
})
it('can close multiple times on a pool', async function () {
const pool = new pg.Pool({ max: 1 });
const pool = new pg.Pool({ max: 1 })
const run = async () => {
const cursor = new Cursor(text);
const client = await pool.connect();
client.query(cursor);
const cursor = new Cursor(text)
const client = await pool.connect()
client.query(cursor)
await new Promise((resolve) => {
cursor.read(25, function (err) {
assert.ifError(err);
assert.ifError(err)
cursor.close(function (err) {
assert.ifError(err);
client.release();
resolve();
});
});
});
};
await Promise.all([run(), run(), run()]);
await pool.end();
});
});
assert.ifError(err)
client.release()
resolve()
})
})
})
}
await Promise.all([run(), run(), run()])
await pool.end()
})
})

View File

@ -1,35 +1,35 @@
'use strict';
const assert = require('assert');
const Cursor = require('../');
const pg = require('pg');
'use strict'
const assert = require('assert')
const Cursor = require('../')
const pg = require('pg')
describe('query config passed to result', () => {
it('passes rowMode to result', (done) => {
const client = new pg.Client();
client.connect();
const text = 'SELECT generate_series as num FROM generate_series(0, 5)';
const cursor = client.query(new Cursor(text, null, { rowMode: 'array' }));
const client = new pg.Client()
client.connect()
const text = 'SELECT generate_series as num FROM generate_series(0, 5)'
const cursor = client.query(new Cursor(text, null, { rowMode: 'array' }))
cursor.read(10, (err, rows) => {
assert(!err);
assert.deepStrictEqual(rows, [[0], [1], [2], [3], [4], [5]]);
client.end();
done();
});
});
assert(!err)
assert.deepStrictEqual(rows, [[0], [1], [2], [3], [4], [5]])
client.end()
done()
})
})
it('passes types to result', (done) => {
const client = new pg.Client();
client.connect();
const text = 'SELECT generate_series as num FROM generate_series(0, 2)';
const client = new pg.Client()
client.connect()
const text = 'SELECT generate_series as num FROM generate_series(0, 2)'
const types = {
getTypeParser: () => () => 'foo',
};
const cursor = client.query(new Cursor(text, null, { types }));
}
const cursor = client.query(new Cursor(text, null, { types }))
cursor.read(10, (err, rows) => {
assert(!err);
assert.deepStrictEqual(rows, [{ num: 'foo' }, { num: 'foo' }, { num: 'foo' }]);
client.end();
done();
});
});
});
assert(!err)
assert.deepStrictEqual(rows, [{ num: 'foo' }, { num: 'foo' }, { num: 'foo' }])
client.end()
done()
})
})
})

View File

@ -1,43 +1,43 @@
const assert = require('assert');
const Cursor = require('../');
const pg = require('pg');
const assert = require('assert')
const Cursor = require('../')
const pg = require('pg')
describe('transactions', () => {
it('can execute multiple statements in a transaction', async () => {
const client = new pg.Client();
await client.connect();
await client.query('begin');
await client.query('CREATE TEMP TABLE foobar(id SERIAL PRIMARY KEY)');
const cursor = client.query(new Cursor('SELECT * FROM foobar'));
const client = new pg.Client()
await client.connect()
await client.query('begin')
await client.query('CREATE TEMP TABLE foobar(id SERIAL PRIMARY KEY)')
const cursor = client.query(new Cursor('SELECT * FROM foobar'))
const rows = await new Promise((resolve, reject) => {
cursor.read(10, (err, rows) => (err ? reject(err) : resolve(rows)));
});
assert.strictEqual(rows.length, 0);
await client.query('ALTER TABLE foobar ADD COLUMN name TEXT');
await client.end();
});
cursor.read(10, (err, rows) => (err ? reject(err) : resolve(rows)))
})
assert.strictEqual(rows.length, 0)
await client.query('ALTER TABLE foobar ADD COLUMN name TEXT')
await client.end()
})
it('can execute multiple statements in a transaction if ending cursor early', async () => {
const client = new pg.Client();
await client.connect();
await client.query('begin');
await client.query('CREATE TEMP TABLE foobar(id SERIAL PRIMARY KEY)');
const cursor = client.query(new Cursor('SELECT * FROM foobar'));
await new Promise((resolve) => cursor.close(resolve));
await client.query('ALTER TABLE foobar ADD COLUMN name TEXT');
await client.end();
});
const client = new pg.Client()
await client.connect()
await client.query('begin')
await client.query('CREATE TEMP TABLE foobar(id SERIAL PRIMARY KEY)')
const cursor = client.query(new Cursor('SELECT * FROM foobar'))
await new Promise((resolve) => cursor.close(resolve))
await client.query('ALTER TABLE foobar ADD COLUMN name TEXT')
await client.end()
})
it('can execute multiple statements in a transaction if no data', async () => {
const client = new pg.Client();
await client.connect();
await client.query('begin');
const client = new pg.Client()
await client.connect()
await client.query('begin')
// create a cursor that has no data response
const createText = 'CREATE TEMP TABLE foobar(id SERIAL PRIMARY KEY)';
const cursor = client.query(new Cursor(createText));
const err = await new Promise((resolve) => cursor.read(100, resolve));
assert.ifError(err);
await client.query('ALTER TABLE foobar ADD COLUMN name TEXT');
await client.end();
});
});
const createText = 'CREATE TEMP TABLE foobar(id SERIAL PRIMARY KEY)'
const cursor = client.query(new Cursor(createText))
const err = await new Promise((resolve) => cursor.read(100, resolve))
assert.ifError(err)
await client.query('ALTER TABLE foobar ADD COLUMN name TEXT')
await client.end()
})
})

View File

@ -1,18 +1,16 @@
'use strict'
const EventEmitter = require('events').EventEmitter
const NOOP = function () { }
const NOOP = function () {}
const removeWhere = (list, predicate) => {
const i = list.findIndex(predicate)
return i === -1
? undefined
: list.splice(i, 1)[0]
return i === -1 ? undefined : list.splice(i, 1)[0]
}
class IdleItem {
constructor (client, idleListener, timeoutId) {
constructor(client, idleListener, timeoutId) {
this.client = client
this.idleListener = idleListener
this.timeoutId = timeoutId
@ -20,16 +18,16 @@ class IdleItem {
}
class PendingItem {
constructor (callback) {
constructor(callback) {
this.callback = callback
}
}
function throwOnDoubleRelease () {
function throwOnDoubleRelease() {
throw new Error('Release called on client which has already been released to the pool.')
}
function promisify (Promise, callback) {
function promisify(Promise, callback) {
if (callback) {
return { callback: callback, result: undefined }
}
@ -45,8 +43,8 @@ function promisify (Promise, callback) {
return { callback: cb, result: result }
}
function makeIdleListener (pool, client) {
return function idleListener (err) {
function makeIdleListener(pool, client) {
return function idleListener(err) {
err.client = client
client.removeListener('error', idleListener)
@ -61,7 +59,7 @@ function makeIdleListener (pool, client) {
}
class Pool extends EventEmitter {
constructor (options, Client) {
constructor(options, Client) {
super()
this.options = Object.assign({}, options)
@ -72,13 +70,13 @@ class Pool extends EventEmitter {
configurable: true,
enumerable: false,
writable: true,
value: options.password
value: options.password,
})
}
this.options.max = this.options.max || this.options.poolSize || 10
this.options.maxUses = this.options.maxUses || Infinity
this.log = this.options.log || function () { }
this.log = this.options.log || function () {}
this.Client = this.options.Client || Client || require('pg').Client
this.Promise = this.options.Promise || global.Promise
@ -94,11 +92,11 @@ class Pool extends EventEmitter {
this.ended = false
}
_isFull () {
_isFull() {
return this._clients.length >= this.options.max
}
_pulseQueue () {
_pulseQueue() {
this.log('pulse queue')
if (this.ended) {
this.log('pulse queue ended')
@ -107,7 +105,7 @@ class Pool extends EventEmitter {
if (this.ending) {
this.log('pulse queue on ending')
if (this._idle.length) {
this._idle.slice().map(item => {
this._idle.slice().map((item) => {
this._remove(item.client)
})
}
@ -141,22 +139,19 @@ class Pool extends EventEmitter {
throw new Error('unexpected condition')
}
_remove (client) {
const removed = removeWhere(
this._idle,
item => item.client === client
)
_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)
this._clients = this._clients.filter((c) => c !== client)
client.end()
this.emit('remove', client)
}
connect (cb) {
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)
@ -202,7 +197,7 @@ class Pool extends EventEmitter {
return result
}
newClient (pendingItem) {
newClient(pendingItem) {
const client = new this.Client(this.options)
this._clients.push(client)
const idleListener = makeIdleListener(this, client)
@ -230,7 +225,7 @@ class Pool extends EventEmitter {
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)
this._clients = this._clients.filter((c) => c !== client)
if (timeoutHit) {
err.message = 'Connection terminated due to connection timeout'
}
@ -250,7 +245,7 @@ class Pool extends EventEmitter {
}
// acquire a client for a pending work item
_acquireClient (client, pendingItem, idleListener, isNew) {
_acquireClient(client, pendingItem, idleListener, isNew) {
if (isNew) {
this.emit('connect', client)
}
@ -294,7 +289,7 @@ class Pool extends EventEmitter {
// release a client back to the poll, include an error
// to remove it from the pool
_release (client, idleListener, err) {
_release(client, idleListener, err) {
client.on('error', idleListener)
client._poolUseCount = (client._poolUseCount || 0) + 1
@ -322,7 +317,7 @@ class Pool extends EventEmitter {
this._pulseQueue()
}
query (text, values, cb) {
query(text, values, cb) {
// guard clause against passing a function as the first parameter
if (typeof text === 'function') {
const response = promisify(this.Promise, text)
@ -375,7 +370,7 @@ class Pool extends EventEmitter {
return response.result
}
end (cb) {
end(cb) {
this.log('ending')
if (this.ending) {
const err = new Error('Called end on pool more than once')
@ -388,15 +383,15 @@ class Pool extends EventEmitter {
return promised.result
}
get waitingCount () {
get waitingCount() {
return this._pendingQueue.length
}
get idleCount () {
get idleCount() {
return this._idle.length
}
get totalCount () {
get totalCount() {
return this._clients.length
}
}

View File

@ -8,29 +8,35 @@ const BluebirdPromise = require('bluebird')
const Pool = require('../')
const checkType = promise => {
const checkType = (promise) => {
expect(promise).to.be.a(BluebirdPromise)
return promise.catch(e => undefined)
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 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())
}))
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())
})
)
})

View File

@ -15,10 +15,10 @@ describe('Connection strings', function () {
connect: function (cb) {
cb(new Error('testing'))
},
on: function () { }
on: function () {},
}
},
connectionString: connectionString
connectionString: connectionString,
})
pool.connect(function (err, client) {
@ -27,4 +27,3 @@ describe('Connection strings', function () {
})
})
})

View File

@ -43,7 +43,7 @@ describe('connection timeout', () => {
it('should reject promise with an error if timeout is passed', (done) => {
const pool = new Pool({ connectionTimeoutMillis: 10, port: this.port, host: 'localhost' })
pool.connect().catch(err => {
pool.connect().catch((err) => {
expect(err).to.be.an(Error)
expect(err.message).to.contain('timeout')
expect(pool.idleCount).to.equal(0)
@ -51,18 +51,23 @@ describe('connection timeout', () => {
})
})
it('should handle multiple timeouts', co.wrap(function* () {
const errors = []
const pool = new Pool({ connectionTimeoutMillis: 1, port: this.port, host: 'localhost' })
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 handle multiple timeouts',
co.wrap(
function* () {
const errors = []
const pool = new Pool({ connectionTimeoutMillis: 1, port: this.port, host: 'localhost' })
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 })
@ -153,7 +158,7 @@ describe('connection timeout', () => {
const pool = new Pool({
Client: Client,
connectionTimeoutMillis: 1000,
max: 1
max: 1,
})
pool.connect((err, client, release) => {
@ -199,7 +204,7 @@ describe('connection timeout', () => {
const pool = new Pool({
Client: Client,
connectionTimeoutMillis: 1000,
max: 1
max: 1,
})
// Direct connect

View File

@ -17,18 +17,24 @@ describe('pool ending', () => {
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(
'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')
}))
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')
})
)
})

View File

@ -16,12 +16,15 @@ describe('pool error handling', function () {
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)
})
pool
.query("SELECT 'asd'+1 ")
.then(function (res) {
reject(res) // this should always error
})
.catch(function (err) {
errors++
resolve(err)
})
})
}
const ps = []
@ -35,14 +38,17 @@ describe('pool error handling', function () {
})
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()
}))
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()
})
)
it('should throw each time with callbacks', function (done) {
const pool = new Pool()
@ -75,17 +81,16 @@ describe('pool error handling', function () {
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()
})
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) => {
@ -106,68 +111,74 @@ describe('pool error handling', function () {
})
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(() => {
let poolError
pool.once('error', (err) => {
poolError = err
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(() => {
let poolError
pool.once('error', (err) => {
poolError = err
})
let clientError
client.once('error', (err) => {
clientError = err
})
client.emit('error', new Error('expected'))
expect(clientError.message).to.equal('expected')
expect(poolError.message).to.equal('expected')
expect(pool.idleCount).to.equal(0)
expect(pool.totalCount).to.equal(0)
pool.end().then(resolve, reject)
})
let clientError
client.once('error', (err) => {
clientError = err
})
client.emit('error', new Error('expected'))
expect(clientError.message).to.equal('expected')
expect(poolError.message).to.equal('expected')
expect(pool.idleCount).to.equal(0)
expect(pool.totalCount).to.equal(0)
pool.end().then(resolve, reject)
})
})
}))
)
})
describe('error from in-use client', () => {
it('keeps the client in the 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)
it(
'keeps the client in the 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)
yield new Promise((resolve, reject) => {
process.nextTick(() => {
let poolError
pool.once('error', (err) => {
poolError = err
yield new Promise((resolve, reject) => {
process.nextTick(() => {
let poolError
pool.once('error', (err) => {
poolError = err
})
let clientError
client.once('error', (err) => {
clientError = err
})
client.emit('error', new Error('expected'))
expect(clientError.message).to.equal('expected')
expect(poolError).not.to.be.ok()
expect(pool.idleCount).to.equal(0)
expect(pool.totalCount).to.equal(1)
client.release()
pool.end().then(resolve, reject)
})
let clientError
client.once('error', (err) => {
clientError = err
})
client.emit('error', new Error('expected'))
expect(clientError.message).to.equal('expected')
expect(poolError).not.to.be.ok()
expect(pool.idleCount).to.equal(0)
expect(pool.totalCount).to.equal(1)
client.release()
pool.end().then(resolve, reject)
})
})
}))
)
})
describe('passing a function to pool.query', () => {
@ -182,30 +193,35 @@ describe('pool error handling', function () {
})
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)
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()
}))
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()
const closeServer = net
.createServer((socket) => {
socket.destroy()
})
.unref()
closeServer.listen(() => {
const pool = new Pool({ max: 1, port: closeServer.address().port, host: 'localhost' })

View File

@ -31,13 +31,13 @@ describe('events', function () {
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'))
return pool.connect().catch((e) => expect(e.message).to.equal('bad news'))
})
it('emits acquire every time a client is acquired', function (done) {
@ -77,7 +77,7 @@ describe('events', function () {
})
})
function mockClient (methods) {
function mockClient(methods) {
return function () {
const client = new EventEmitter()
Object.assign(client, methods)

View File

@ -7,7 +7,7 @@ const it = require('mocha').it
const Pool = require('../')
const wait = time => new Promise((resolve) => setTimeout(resolve, time))
const wait = (time) => new Promise((resolve) => setTimeout(resolve, time))
describe('idle timeout', () => {
it('should timeout and remove the client', (done) => {
@ -20,60 +20,68 @@ describe('idle timeout', () => {
})
})
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())
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', () => {
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)
resolve()
})
}
expect(results).to.have.length(20)
})
)
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()
}))
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()
})
)
})

View File

@ -167,13 +167,11 @@ describe('pool', function () {
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()
})
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 () {
@ -210,10 +208,9 @@ describe('pool', function () {
const errors = []
const promises = _.times(30, () => {
return pool.query('SELECT asldkfjasldkf')
.catch(function (e) {
errors.push(e)
})
return pool.query('SELECT asldkfjasldkf').catch(function (e) {
errors.push(e)
})
})
return Promise.all(promises).then(() => {
expect(errors).to.have.length(30)

View File

@ -8,78 +8,91 @@ const it = require('mocha').it
const Pool = require('../')
describe('maxUses', () => {
it('can create a single client and use it once', co.wrap(function * () {
const pool = new Pool({ maxUses: 2 })
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 once',
co.wrap(function* () {
const pool = new Pool({ maxUses: 2 })
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('getting a connection a second time returns the same connection and releasing it also closes it', co.wrap(function * () {
const pool = new Pool({ maxUses: 2 })
expect(pool.waitingCount).to.equal(0)
const client = yield pool.connect()
client.release()
const client2 = yield pool.connect()
expect(client).to.equal(client2)
expect(client2._ending).to.equal(false)
client2.release()
expect(client2._ending).to.equal(true)
return yield pool.end()
}))
it(
'getting a connection a second time returns the same connection and releasing it also closes it',
co.wrap(function* () {
const pool = new Pool({ maxUses: 2 })
expect(pool.waitingCount).to.equal(0)
const client = yield pool.connect()
client.release()
const client2 = yield pool.connect()
expect(client).to.equal(client2)
expect(client2._ending).to.equal(false)
client2.release()
expect(client2._ending).to.equal(true)
return yield pool.end()
})
)
it('getting a connection a third time returns a new connection', co.wrap(function * () {
const pool = new Pool({ maxUses: 2 })
expect(pool.waitingCount).to.equal(0)
const client = yield pool.connect()
client.release()
const client2 = yield pool.connect()
expect(client).to.equal(client2)
client2.release()
const client3 = yield pool.connect()
expect(client3).not.to.equal(client2)
client3.release()
return yield pool.end()
}))
it(
'getting a connection a third time returns a new connection',
co.wrap(function* () {
const pool = new Pool({ maxUses: 2 })
expect(pool.waitingCount).to.equal(0)
const client = yield pool.connect()
client.release()
const client2 = yield pool.connect()
expect(client).to.equal(client2)
client2.release()
const client3 = yield pool.connect()
expect(client3).not.to.equal(client2)
client3.release()
return yield pool.end()
})
)
it('getting a connection from a pending request gets a fresh client when the released candidate is expended', co.wrap(function * () {
const pool = new Pool({ max: 1, maxUses: 2 })
expect(pool.waitingCount).to.equal(0)
const client1 = yield pool.connect()
pool.connect()
.then(client2 => {
it(
'getting a connection from a pending request gets a fresh client when the released candidate is expended',
co.wrap(function* () {
const pool = new Pool({ max: 1, maxUses: 2 })
expect(pool.waitingCount).to.equal(0)
const client1 = yield pool.connect()
pool.connect().then((client2) => {
expect(client2).to.equal(client1)
expect(pool.waitingCount).to.equal(1)
// Releasing the client this time should also expend it since maxUses is 2, causing client3 to be a fresh client
client2.release()
})
const client3Promise = pool.connect()
.then(client3 => {
const client3Promise = pool.connect().then((client3) => {
// client3 should be a fresh client since client2's release caused the first client to be expended
expect(pool.waitingCount).to.equal(0)
expect(client3).not.to.equal(client1)
return client3.release()
})
// There should be two pending requests since we have 3 connect requests but a max size of 1
expect(pool.waitingCount).to.equal(2)
// Releasing the client should not yet expend it since maxUses is 2
client1.release()
yield client3Promise
return yield pool.end()
}))
// There should be two pending requests since we have 3 connect requests but a max size of 1
expect(pool.waitingCount).to.equal(2)
// Releasing the client should not yet expend it since maxUses is 2
client1.release()
yield client3Promise
return yield pool.end()
})
)
it('logs when removing an expended client', co.wrap(function * () {
const messages = []
const log = function (msg) {
messages.push(msg)
}
const pool = new Pool({ maxUses: 1, log })
const client = yield pool.connect()
client.release()
expect(messages).to.contain('remove expended client')
return yield pool.end()
}))
it(
'logs when removing an expended client',
co.wrap(function* () {
const messages = []
const log = function (msg) {
messages.push(msg)
}
const pool = new Pool({ maxUses: 1, log })
const client = yield pool.connect()
client.release()
expect(messages).to.contain('remove expended client')
return yield pool.end()
})
)
})

View File

@ -1,5 +1,5 @@
const crash = reason => {
process.on(reason, err => {
const crash = (reason) => {
process.on(reason, (err) => {
console.error(reason, err.stack)
process.exit(-1)
})

View File

@ -8,43 +8,51 @@ 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 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 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 })
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'
// 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()
}))
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()
})
)
})

View File

@ -12,7 +12,7 @@ describe('verify', () => {
verify: (client, cb) => {
client.release()
cb(new Error('nope'))
}
},
})
pool.connect((err, client) => {

View File

@ -1,28 +1,28 @@
// file for microbenchmarking
import { Writer } from './buffer-writer';
import { serialize } from './index';
import { BufferReader } from './buffer-reader';
import { Writer } from './buffer-writer'
import { serialize } from './index'
import { BufferReader } from './buffer-reader'
const LOOPS = 1000;
let count = 0;
let start = Date.now();
const writer = new Writer();
const LOOPS = 1000
let count = 0
let start = Date.now()
const writer = new Writer()
const reader = new BufferReader();
const buffer = Buffer.from([33, 33, 33, 33, 33, 33, 33, 0]);
const reader = new BufferReader()
const buffer = Buffer.from([33, 33, 33, 33, 33, 33, 33, 0])
const run = () => {
if (count > LOOPS) {
console.log(Date.now() - start);
return;
console.log(Date.now() - start)
return
}
count++;
count++
for (let i = 0; i < LOOPS; i++) {
reader.setBuffer(0, buffer);
reader.cstring();
reader.setBuffer(0, buffer)
reader.cstring()
}
setImmediate(run);
};
setImmediate(run)
}
run();
run()

View File

@ -1,53 +1,53 @@
const emptyBuffer = Buffer.allocUnsafe(0);
const emptyBuffer = Buffer.allocUnsafe(0)
export class BufferReader {
private buffer: Buffer = emptyBuffer;
private buffer: Buffer = emptyBuffer
// TODO(bmc): support non-utf8 encoding?
private encoding: string = 'utf-8';
private encoding: string = 'utf-8'
constructor(private offset: number = 0) {}
public setBuffer(offset: number, buffer: Buffer): void {
this.offset = offset;
this.buffer = buffer;
this.offset = offset
this.buffer = buffer
}
public int16(): number {
const result = this.buffer.readInt16BE(this.offset);
this.offset += 2;
return result;
const result = this.buffer.readInt16BE(this.offset)
this.offset += 2
return result
}
public byte(): number {
const result = this.buffer[this.offset];
this.offset++;
return result;
const result = this.buffer[this.offset]
this.offset++
return result
}
public int32(): number {
const result = this.buffer.readInt32BE(this.offset);
this.offset += 4;
return result;
const result = this.buffer.readInt32BE(this.offset)
this.offset += 4
return result
}
public string(length: number): string {
const result = this.buffer.toString(this.encoding, this.offset, this.offset + length);
this.offset += length;
return result;
const result = this.buffer.toString(this.encoding, this.offset, this.offset + length)
this.offset += length
return result
}
public cstring(): string {
const start = this.offset;
let end = start;
const start = this.offset
let end = start
while (this.buffer[end++] !== 0) {}
this.offset = end;
return this.buffer.toString(this.encoding, start, end - 1);
this.offset = end
return this.buffer.toString(this.encoding, start, end - 1)
}
public bytes(length: number): Buffer {
const result = this.buffer.slice(this.offset, this.offset + length);
this.offset += length;
return result;
const result = this.buffer.slice(this.offset, this.offset + length)
this.offset += length
return result
}
}

View File

@ -1,85 +1,85 @@
//binary data writer tuned for encoding binary specific to the postgres binary protocol
export class Writer {
private buffer: Buffer;
private offset: number = 5;
private headerPosition: number = 0;
private buffer: Buffer
private offset: number = 5
private headerPosition: number = 0
constructor(private size = 256) {
this.buffer = Buffer.alloc(size);
this.buffer = Buffer.alloc(size)
}
private ensure(size: number): void {
var remaining = this.buffer.length - this.offset;
var remaining = this.buffer.length - this.offset
if (remaining < size) {
var oldBuffer = this.buffer;
var oldBuffer = this.buffer
// exponential growth factor of around ~ 1.5
// https://stackoverflow.com/questions/2269063/buffer-growth-strategy
var newSize = oldBuffer.length + (oldBuffer.length >> 1) + size;
this.buffer = Buffer.alloc(newSize);
oldBuffer.copy(this.buffer);
var newSize = oldBuffer.length + (oldBuffer.length >> 1) + size
this.buffer = Buffer.alloc(newSize)
oldBuffer.copy(this.buffer)
}
}
public addInt32(num: number): Writer {
this.ensure(4);
this.buffer[this.offset++] = (num >>> 24) & 0xff;
this.buffer[this.offset++] = (num >>> 16) & 0xff;
this.buffer[this.offset++] = (num >>> 8) & 0xff;
this.buffer[this.offset++] = (num >>> 0) & 0xff;
return this;
this.ensure(4)
this.buffer[this.offset++] = (num >>> 24) & 0xff
this.buffer[this.offset++] = (num >>> 16) & 0xff
this.buffer[this.offset++] = (num >>> 8) & 0xff
this.buffer[this.offset++] = (num >>> 0) & 0xff
return this
}
public addInt16(num: number): Writer {
this.ensure(2);
this.buffer[this.offset++] = (num >>> 8) & 0xff;
this.buffer[this.offset++] = (num >>> 0) & 0xff;
return this;
this.ensure(2)
this.buffer[this.offset++] = (num >>> 8) & 0xff
this.buffer[this.offset++] = (num >>> 0) & 0xff
return this
}
public addCString(string: string): Writer {
if (!string) {
this.ensure(1);
this.ensure(1)
} else {
var len = Buffer.byteLength(string);
this.ensure(len + 1); // +1 for null terminator
this.buffer.write(string, this.offset, 'utf-8');
this.offset += len;
var len = Buffer.byteLength(string)
this.ensure(len + 1) // +1 for null terminator
this.buffer.write(string, this.offset, 'utf-8')
this.offset += len
}
this.buffer[this.offset++] = 0; // null terminator
return this;
this.buffer[this.offset++] = 0 // null terminator
return this
}
public addString(string: string = ''): Writer {
var len = Buffer.byteLength(string);
this.ensure(len);
this.buffer.write(string, this.offset);
this.offset += len;
return this;
var len = Buffer.byteLength(string)
this.ensure(len)
this.buffer.write(string, this.offset)
this.offset += len
return this
}
public add(otherBuffer: Buffer): Writer {
this.ensure(otherBuffer.length);
otherBuffer.copy(this.buffer, this.offset);
this.offset += otherBuffer.length;
return this;
this.ensure(otherBuffer.length)
otherBuffer.copy(this.buffer, this.offset)
this.offset += otherBuffer.length
return this
}
private join(code?: number): Buffer {
if (code) {
this.buffer[this.headerPosition] = code;
this.buffer[this.headerPosition] = code
//length is everything in this packet minus the code
const length = this.offset - (this.headerPosition + 1);
this.buffer.writeInt32BE(length, this.headerPosition + 1);
const length = this.offset - (this.headerPosition + 1)
this.buffer.writeInt32BE(length, this.headerPosition + 1)
}
return this.buffer.slice(code ? 0 : 5, this.offset);
return this.buffer.slice(code ? 0 : 5, this.offset)
}
public flush(code?: number): Buffer {
var result = this.join(code);
this.offset = 5;
this.headerPosition = 0;
this.buffer = Buffer.allocUnsafe(this.size);
return result;
var result = this.join(code)
this.offset = 5
this.headerPosition = 0
this.buffer = Buffer.allocUnsafe(this.size)
return result
}
}

View File

@ -1,18 +1,18 @@
import buffers from './testing/test-buffers';
import BufferList from './testing/buffer-list';
import { parse } from '.';
import assert from 'assert';
import { PassThrough } from 'stream';
import { BackendMessage } from './messages';
import buffers from './testing/test-buffers'
import BufferList from './testing/buffer-list'
import { parse } from '.'
import assert from 'assert'
import { PassThrough } from 'stream'
import { BackendMessage } from './messages'
var authOkBuffer = buffers.authenticationOk();
var paramStatusBuffer = buffers.parameterStatus('client_encoding', 'UTF8');
var readyForQueryBuffer = buffers.readyForQuery();
var backendKeyDataBuffer = buffers.backendKeyData(1, 2);
var commandCompleteBuffer = buffers.commandComplete('SELECT 3');
var parseCompleteBuffer = buffers.parseComplete();
var bindCompleteBuffer = buffers.bindComplete();
var portalSuspendedBuffer = buffers.portalSuspended();
var authOkBuffer = buffers.authenticationOk()
var paramStatusBuffer = buffers.parameterStatus('client_encoding', 'UTF8')
var readyForQueryBuffer = buffers.readyForQuery()
var backendKeyDataBuffer = buffers.backendKeyData(1, 2)
var commandCompleteBuffer = buffers.commandComplete('SELECT 3')
var parseCompleteBuffer = buffers.parseComplete()
var bindCompleteBuffer = buffers.bindComplete()
var portalSuspendedBuffer = buffers.portalSuspended()
var addRow = function (bufferList: BufferList, name: string, offset: number) {
return bufferList
@ -22,8 +22,8 @@ var addRow = function (bufferList: BufferList, name: string, offset: number) {
.addInt32(offset++) // objectId of field's data type
.addInt16(offset++) // datatype size
.addInt32(offset++) // type modifier
.addInt16(0); // format code, 0 => text
};
.addInt16(0) // format code, 0 => text
}
var row1 = {
name: 'id',
@ -33,9 +33,9 @@ var row1 = {
dataTypeSize: 4,
typeModifier: 5,
formatCode: 0,
};
var oneRowDescBuff = buffers.rowDescription([row1]);
row1.name = 'bang';
}
var oneRowDescBuff = buffers.rowDescription([row1])
row1.name = 'bang'
var twoRowBuf = buffers.rowDescription([
row1,
@ -48,59 +48,59 @@ var twoRowBuf = buffers.rowDescription([
typeModifier: 14,
formatCode: 0,
},
]);
])
var emptyRowFieldBuf = new BufferList().addInt16(0).join(true, 'D');
var emptyRowFieldBuf = new BufferList().addInt16(0).join(true, 'D')
var emptyRowFieldBuf = buffers.dataRow([]);
var emptyRowFieldBuf = buffers.dataRow([])
var oneFieldBuf = new BufferList()
.addInt16(1) // number of fields
.addInt32(5) // length of bytes of fields
.addCString('test')
.join(true, 'D');
.join(true, 'D')
var oneFieldBuf = buffers.dataRow(['test']);
var oneFieldBuf = buffers.dataRow(['test'])
var expectedAuthenticationOkayMessage = {
name: 'authenticationOk',
length: 8,
};
}
var expectedParameterStatusMessage = {
name: 'parameterStatus',
parameterName: 'client_encoding',
parameterValue: 'UTF8',
length: 25,
};
}
var expectedBackendKeyDataMessage = {
name: 'backendKeyData',
processID: 1,
secretKey: 2,
};
}
var expectedReadyForQueryMessage = {
name: 'readyForQuery',
length: 5,
status: 'I',
};
}
var expectedCommandCompleteMessage = {
name: 'commandComplete',
length: 13,
text: 'SELECT 3',
};
}
var emptyRowDescriptionBuffer = new BufferList()
.addInt16(0) // number of fields
.join(true, 'T');
.join(true, 'T')
var expectedEmptyRowDescriptionMessage = {
name: 'rowDescription',
length: 6,
fieldCount: 0,
fields: [],
};
}
var expectedOneRowMessage = {
name: 'rowDescription',
length: 27,
@ -116,7 +116,7 @@ var expectedOneRowMessage = {
format: 'text',
},
],
};
}
var expectedTwoRowMessage = {
name: 'rowDescription',
@ -142,125 +142,125 @@ var expectedTwoRowMessage = {
format: 'text',
},
],
};
}
var testForMessage = function (buffer: Buffer, expectedMessage: any) {
it('recieves and parses ' + expectedMessage.name, async () => {
const messages = await parseBuffers([buffer]);
const [lastMessage] = messages;
const messages = await parseBuffers([buffer])
const [lastMessage] = messages
for (const key in expectedMessage) {
assert.deepEqual((lastMessage as any)[key], expectedMessage[key]);
assert.deepEqual((lastMessage as any)[key], expectedMessage[key])
}
});
};
})
}
var plainPasswordBuffer = buffers.authenticationCleartextPassword();
var md5PasswordBuffer = buffers.authenticationMD5Password();
var SASLBuffer = buffers.authenticationSASL();
var SASLContinueBuffer = buffers.authenticationSASLContinue();
var SASLFinalBuffer = buffers.authenticationSASLFinal();
var plainPasswordBuffer = buffers.authenticationCleartextPassword()
var md5PasswordBuffer = buffers.authenticationMD5Password()
var SASLBuffer = buffers.authenticationSASL()
var SASLContinueBuffer = buffers.authenticationSASLContinue()
var SASLFinalBuffer = buffers.authenticationSASLFinal()
var expectedPlainPasswordMessage = {
name: 'authenticationCleartextPassword',
};
}
var expectedMD5PasswordMessage = {
name: 'authenticationMD5Password',
salt: Buffer.from([1, 2, 3, 4]),
};
}
var expectedSASLMessage = {
name: 'authenticationSASL',
mechanisms: ['SCRAM-SHA-256'],
};
}
var expectedSASLContinueMessage = {
name: 'authenticationSASLContinue',
data: 'data',
};
}
var expectedSASLFinalMessage = {
name: 'authenticationSASLFinal',
data: 'data',
};
}
var notificationResponseBuffer = buffers.notification(4, 'hi', 'boom');
var notificationResponseBuffer = buffers.notification(4, 'hi', 'boom')
var expectedNotificationResponseMessage = {
name: 'notification',
processId: 4,
channel: 'hi',
payload: 'boom',
};
}
const parseBuffers = async (buffers: Buffer[]): Promise<BackendMessage[]> => {
const stream = new PassThrough();
const stream = new PassThrough()
for (const buffer of buffers) {
stream.write(buffer);
stream.write(buffer)
}
stream.end();
const msgs: BackendMessage[] = [];
await parse(stream, (msg) => msgs.push(msg));
return msgs;
};
stream.end()
const msgs: BackendMessage[] = []
await parse(stream, (msg) => msgs.push(msg))
return msgs
}
describe('PgPacketStream', function () {
testForMessage(authOkBuffer, expectedAuthenticationOkayMessage);
testForMessage(plainPasswordBuffer, expectedPlainPasswordMessage);
testForMessage(md5PasswordBuffer, expectedMD5PasswordMessage);
testForMessage(SASLBuffer, expectedSASLMessage);
testForMessage(SASLContinueBuffer, expectedSASLContinueMessage);
testForMessage(SASLFinalBuffer, expectedSASLFinalMessage);
testForMessage(authOkBuffer, expectedAuthenticationOkayMessage)
testForMessage(plainPasswordBuffer, expectedPlainPasswordMessage)
testForMessage(md5PasswordBuffer, expectedMD5PasswordMessage)
testForMessage(SASLBuffer, expectedSASLMessage)
testForMessage(SASLContinueBuffer, expectedSASLContinueMessage)
testForMessage(SASLFinalBuffer, expectedSASLFinalMessage)
testForMessage(paramStatusBuffer, expectedParameterStatusMessage);
testForMessage(backendKeyDataBuffer, expectedBackendKeyDataMessage);
testForMessage(readyForQueryBuffer, expectedReadyForQueryMessage);
testForMessage(commandCompleteBuffer, expectedCommandCompleteMessage);
testForMessage(notificationResponseBuffer, expectedNotificationResponseMessage);
testForMessage(paramStatusBuffer, expectedParameterStatusMessage)
testForMessage(backendKeyDataBuffer, expectedBackendKeyDataMessage)
testForMessage(readyForQueryBuffer, expectedReadyForQueryMessage)
testForMessage(commandCompleteBuffer, expectedCommandCompleteMessage)
testForMessage(notificationResponseBuffer, expectedNotificationResponseMessage)
testForMessage(buffers.emptyQuery(), {
name: 'emptyQuery',
length: 4,
});
})
testForMessage(Buffer.from([0x6e, 0, 0, 0, 4]), {
name: 'noData',
});
})
describe('rowDescription messages', function () {
testForMessage(emptyRowDescriptionBuffer, expectedEmptyRowDescriptionMessage);
testForMessage(oneRowDescBuff, expectedOneRowMessage);
testForMessage(twoRowBuf, expectedTwoRowMessage);
});
testForMessage(emptyRowDescriptionBuffer, expectedEmptyRowDescriptionMessage)
testForMessage(oneRowDescBuff, expectedOneRowMessage)
testForMessage(twoRowBuf, expectedTwoRowMessage)
})
describe('parsing rows', function () {
describe('parsing empty row', function () {
testForMessage(emptyRowFieldBuf, {
name: 'dataRow',
fieldCount: 0,
});
});
})
})
describe('parsing data row with fields', function () {
testForMessage(oneFieldBuf, {
name: 'dataRow',
fieldCount: 1,
fields: ['test'],
});
});
});
})
})
})
describe('notice message', function () {
// this uses the same logic as error message
var buff = buffers.notice([{ type: 'C', value: 'code' }]);
var buff = buffers.notice([{ type: 'C', value: 'code' }])
testForMessage(buff, {
name: 'notice',
code: 'code',
});
});
})
})
testForMessage(buffers.error([]), {
name: 'error',
});
})
describe('with all the fields', function () {
var buffer = buffers.error([
@ -316,7 +316,7 @@ describe('PgPacketStream', function () {
type: 'Z', // ignored
value: 'alsdkf',
},
]);
])
testForMessage(buffer, {
name: 'error',
@ -332,37 +332,37 @@ describe('PgPacketStream', function () {
file: 'file',
line: 'line',
routine: 'routine',
});
});
})
})
testForMessage(parseCompleteBuffer, {
name: 'parseComplete',
});
})
testForMessage(bindCompleteBuffer, {
name: 'bindComplete',
});
})
testForMessage(bindCompleteBuffer, {
name: 'bindComplete',
});
})
testForMessage(buffers.closeComplete(), {
name: 'closeComplete',
});
})
describe('parses portal suspended message', function () {
testForMessage(portalSuspendedBuffer, {
name: 'portalSuspended',
});
});
})
})
describe('parses replication start message', function () {
testForMessage(Buffer.from([0x57, 0x00, 0x00, 0x00, 0x04]), {
name: 'replicationStart',
length: 4,
});
});
})
})
describe('copy', () => {
testForMessage(buffers.copyIn(0), {
@ -370,140 +370,140 @@ describe('PgPacketStream', function () {
length: 7,
binary: false,
columnTypes: [],
});
})
testForMessage(buffers.copyIn(2), {
name: 'copyInResponse',
length: 11,
binary: false,
columnTypes: [0, 1],
});
})
testForMessage(buffers.copyOut(0), {
name: 'copyOutResponse',
length: 7,
binary: false,
columnTypes: [],
});
})
testForMessage(buffers.copyOut(3), {
name: 'copyOutResponse',
length: 13,
binary: false,
columnTypes: [0, 1, 2],
});
})
testForMessage(buffers.copyDone(), {
name: 'copyDone',
length: 4,
});
})
testForMessage(buffers.copyData(Buffer.from([5, 6, 7])), {
name: 'copyData',
length: 7,
chunk: Buffer.from([5, 6, 7]),
});
});
})
})
// since the data message on a stream can randomly divide the incomming
// tcp packets anywhere, we need to make sure we can parse every single
// split on a tcp message
describe('split buffer, single message parsing', function () {
var fullBuffer = buffers.dataRow([null, 'bang', 'zug zug', null, '!']);
var fullBuffer = buffers.dataRow([null, 'bang', 'zug zug', null, '!'])
it('parses when full buffer comes in', async function () {
const messages = await parseBuffers([fullBuffer]);
const message = messages[0] as any;
assert.equal(message.fields.length, 5);
assert.equal(message.fields[0], null);
assert.equal(message.fields[1], 'bang');
assert.equal(message.fields[2], 'zug zug');
assert.equal(message.fields[3], null);
assert.equal(message.fields[4], '!');
});
const messages = await parseBuffers([fullBuffer])
const message = messages[0] as any
assert.equal(message.fields.length, 5)
assert.equal(message.fields[0], null)
assert.equal(message.fields[1], 'bang')
assert.equal(message.fields[2], 'zug zug')
assert.equal(message.fields[3], null)
assert.equal(message.fields[4], '!')
})
var testMessageRecievedAfterSpiltAt = async function (split: number) {
var firstBuffer = Buffer.alloc(fullBuffer.length - split);
var secondBuffer = Buffer.alloc(fullBuffer.length - firstBuffer.length);
fullBuffer.copy(firstBuffer, 0, 0);
fullBuffer.copy(secondBuffer, 0, firstBuffer.length);
const messages = await parseBuffers([fullBuffer]);
const message = messages[0] as any;
assert.equal(message.fields.length, 5);
assert.equal(message.fields[0], null);
assert.equal(message.fields[1], 'bang');
assert.equal(message.fields[2], 'zug zug');
assert.equal(message.fields[3], null);
assert.equal(message.fields[4], '!');
};
var firstBuffer = Buffer.alloc(fullBuffer.length - split)
var secondBuffer = Buffer.alloc(fullBuffer.length - firstBuffer.length)
fullBuffer.copy(firstBuffer, 0, 0)
fullBuffer.copy(secondBuffer, 0, firstBuffer.length)
const messages = await parseBuffers([fullBuffer])
const message = messages[0] as any
assert.equal(message.fields.length, 5)
assert.equal(message.fields[0], null)
assert.equal(message.fields[1], 'bang')
assert.equal(message.fields[2], 'zug zug')
assert.equal(message.fields[3], null)
assert.equal(message.fields[4], '!')
}
it('parses when split in the middle', function () {
testMessageRecievedAfterSpiltAt(6);
});
testMessageRecievedAfterSpiltAt(6)
})
it('parses when split at end', function () {
testMessageRecievedAfterSpiltAt(2);
});
testMessageRecievedAfterSpiltAt(2)
})
it('parses when split at beginning', function () {
testMessageRecievedAfterSpiltAt(fullBuffer.length - 2);
testMessageRecievedAfterSpiltAt(fullBuffer.length - 1);
testMessageRecievedAfterSpiltAt(fullBuffer.length - 5);
});
});
testMessageRecievedAfterSpiltAt(fullBuffer.length - 2)
testMessageRecievedAfterSpiltAt(fullBuffer.length - 1)
testMessageRecievedAfterSpiltAt(fullBuffer.length - 5)
})
})
describe('split buffer, multiple message parsing', function () {
var dataRowBuffer = buffers.dataRow(['!']);
var readyForQueryBuffer = buffers.readyForQuery();
var fullBuffer = Buffer.alloc(dataRowBuffer.length + readyForQueryBuffer.length);
dataRowBuffer.copy(fullBuffer, 0, 0);
readyForQueryBuffer.copy(fullBuffer, dataRowBuffer.length, 0);
var dataRowBuffer = buffers.dataRow(['!'])
var readyForQueryBuffer = buffers.readyForQuery()
var fullBuffer = Buffer.alloc(dataRowBuffer.length + readyForQueryBuffer.length)
dataRowBuffer.copy(fullBuffer, 0, 0)
readyForQueryBuffer.copy(fullBuffer, dataRowBuffer.length, 0)
var verifyMessages = function (messages: any[]) {
assert.strictEqual(messages.length, 2);
assert.strictEqual(messages.length, 2)
assert.deepEqual(messages[0], {
name: 'dataRow',
fieldCount: 1,
length: 11,
fields: ['!'],
});
assert.equal(messages[0].fields[0], '!');
})
assert.equal(messages[0].fields[0], '!')
assert.deepEqual(messages[1], {
name: 'readyForQuery',
length: 5,
status: 'I',
});
};
})
}
// sanity check
it('recieves both messages when packet is not split', async function () {
const messages = await parseBuffers([fullBuffer]);
verifyMessages(messages);
});
const messages = await parseBuffers([fullBuffer])
verifyMessages(messages)
})
var splitAndVerifyTwoMessages = async function (split: number) {
var firstBuffer = Buffer.alloc(fullBuffer.length - split);
var secondBuffer = Buffer.alloc(fullBuffer.length - firstBuffer.length);
fullBuffer.copy(firstBuffer, 0, 0);
fullBuffer.copy(secondBuffer, 0, firstBuffer.length);
const messages = await parseBuffers([firstBuffer, secondBuffer]);
verifyMessages(messages);
};
var firstBuffer = Buffer.alloc(fullBuffer.length - split)
var secondBuffer = Buffer.alloc(fullBuffer.length - firstBuffer.length)
fullBuffer.copy(firstBuffer, 0, 0)
fullBuffer.copy(secondBuffer, 0, firstBuffer.length)
const messages = await parseBuffers([firstBuffer, secondBuffer])
verifyMessages(messages)
}
describe('recieves both messages when packet is split', function () {
it('in the middle', function () {
return splitAndVerifyTwoMessages(11);
});
return splitAndVerifyTwoMessages(11)
})
it('at the front', function () {
return Promise.all([
splitAndVerifyTwoMessages(fullBuffer.length - 1),
splitAndVerifyTwoMessages(fullBuffer.length - 4),
splitAndVerifyTwoMessages(fullBuffer.length - 6),
]);
});
])
})
it('at the end', function () {
return Promise.all([splitAndVerifyTwoMessages(8), splitAndVerifyTwoMessages(1)]);
});
});
});
});
return Promise.all([splitAndVerifyTwoMessages(8), splitAndVerifyTwoMessages(1)])
})
})
})
})

View File

@ -1,11 +1,11 @@
import { BackendMessage } from './messages';
import { serialize } from './serializer';
import { Parser, MessageCallback } from './parser';
import { BackendMessage } from './messages'
import { serialize } from './serializer'
import { Parser, MessageCallback } from './parser'
export function parse(stream: NodeJS.ReadableStream, callback: MessageCallback): Promise<void> {
const parser = new Parser();
stream.on('data', (buffer: Buffer) => parser.parse(buffer, callback));
return new Promise((resolve) => stream.on('end', () => resolve()));
const parser = new Parser()
stream.on('data', (buffer: Buffer) => parser.parse(buffer, callback))
return new Promise((resolve) => stream.on('end', () => resolve()))
}
export { serialize };
export { serialize }

View File

@ -1,4 +1,4 @@
export type Mode = 'text' | 'binary';
export type Mode = 'text' | 'binary'
export const enum MessageName {
parseComplete = 'parseComplete',
@ -30,106 +30,106 @@ export const enum MessageName {
}
export interface BackendMessage {
name: MessageName;
length: number;
name: MessageName
length: number
}
export const parseComplete: BackendMessage = {
name: MessageName.parseComplete,
length: 5,
};
}
export const bindComplete: BackendMessage = {
name: MessageName.bindComplete,
length: 5,
};
}
export const closeComplete: BackendMessage = {
name: MessageName.closeComplete,
length: 5,
};
}
export const noData: BackendMessage = {
name: MessageName.noData,
length: 5,
};
}
export const portalSuspended: BackendMessage = {
name: MessageName.portalSuspended,
length: 5,
};
}
export const replicationStart: BackendMessage = {
name: MessageName.replicationStart,
length: 4,
};
}
export const emptyQuery: BackendMessage = {
name: MessageName.emptyQuery,
length: 4,
};
}
export const copyDone: BackendMessage = {
name: MessageName.copyDone,
length: 4,
};
}
interface NoticeOrError {
message: string | undefined;
severity: string | undefined;
code: string | undefined;
detail: string | undefined;
hint: string | undefined;
position: string | undefined;
internalPosition: string | undefined;
internalQuery: string | undefined;
where: string | undefined;
schema: string | undefined;
table: string | undefined;
column: string | undefined;
dataType: string | undefined;
constraint: string | undefined;
file: string | undefined;
line: string | undefined;
routine: string | undefined;
message: string | undefined
severity: string | undefined
code: string | undefined
detail: string | undefined
hint: string | undefined
position: string | undefined
internalPosition: string | undefined
internalQuery: string | undefined
where: string | undefined
schema: string | undefined
table: string | undefined
column: string | undefined
dataType: string | undefined
constraint: string | undefined
file: string | undefined
line: string | undefined
routine: string | undefined
}
export class DatabaseError extends Error implements NoticeOrError {
public severity: string | undefined;
public code: string | undefined;
public detail: string | undefined;
public hint: string | undefined;
public position: string | undefined;
public internalPosition: string | undefined;
public internalQuery: string | undefined;
public where: string | undefined;
public schema: string | undefined;
public table: string | undefined;
public column: string | undefined;
public dataType: string | undefined;
public constraint: string | undefined;
public file: string | undefined;
public line: string | undefined;
public routine: string | undefined;
public severity: string | undefined
public code: string | undefined
public detail: string | undefined
public hint: string | undefined
public position: string | undefined
public internalPosition: string | undefined
public internalQuery: string | undefined
public where: string | undefined
public schema: string | undefined
public table: string | undefined
public column: string | undefined
public dataType: string | undefined
public constraint: string | undefined
public file: string | undefined
public line: string | undefined
public routine: string | undefined
constructor(message: string, public readonly length: number, public readonly name: MessageName) {
super(message);
super(message)
}
}
export class CopyDataMessage {
public readonly name = MessageName.copyData;
public readonly name = MessageName.copyData
constructor(public readonly length: number, public readonly chunk: Buffer) {}
}
export class CopyResponse {
public readonly columnTypes: number[];
public readonly columnTypes: number[]
constructor(
public readonly length: number,
public readonly name: MessageName,
public readonly binary: boolean,
columnCount: number
) {
this.columnTypes = new Array(columnCount);
this.columnTypes = new Array(columnCount)
}
}
@ -146,15 +146,15 @@ export class Field {
}
export class RowDescriptionMessage {
public readonly name: MessageName = MessageName.rowDescription;
public readonly fields: Field[];
public readonly name: MessageName = MessageName.rowDescription
public readonly fields: Field[]
constructor(public readonly length: number, public readonly fieldCount: number) {
this.fields = new Array(this.fieldCount);
this.fields = new Array(this.fieldCount)
}
}
export class ParameterStatusMessage {
public readonly name: MessageName = MessageName.parameterStatus;
public readonly name: MessageName = MessageName.parameterStatus
constructor(
public readonly length: number,
public readonly parameterName: string,
@ -163,17 +163,17 @@ export class ParameterStatusMessage {
}
export class AuthenticationMD5Password implements BackendMessage {
public readonly name: MessageName = MessageName.authenticationMD5Password;
public readonly name: MessageName = MessageName.authenticationMD5Password
constructor(public readonly length: number, public readonly salt: Buffer) {}
}
export class BackendKeyDataMessage {
public readonly name: MessageName = MessageName.backendKeyData;
public readonly name: MessageName = MessageName.backendKeyData
constructor(public readonly length: number, public readonly processID: number, public readonly secretKey: number) {}
}
export class NotificationResponseMessage {
public readonly name: MessageName = MessageName.notification;
public readonly name: MessageName = MessageName.notification
constructor(
public readonly length: number,
public readonly processId: number,
@ -183,40 +183,40 @@ export class NotificationResponseMessage {
}
export class ReadyForQueryMessage {
public readonly name: MessageName = MessageName.readyForQuery;
public readonly name: MessageName = MessageName.readyForQuery
constructor(public readonly length: number, public readonly status: string) {}
}
export class CommandCompleteMessage {
public readonly name: MessageName = MessageName.commandComplete;
public readonly name: MessageName = MessageName.commandComplete
constructor(public readonly length: number, public readonly text: string) {}
}
export class DataRowMessage {
public readonly fieldCount: number;
public readonly name: MessageName = MessageName.dataRow;
public readonly fieldCount: number
public readonly name: MessageName = MessageName.dataRow
constructor(public length: number, public fields: any[]) {
this.fieldCount = fields.length;
this.fieldCount = fields.length
}
}
export class NoticeMessage implements BackendMessage, NoticeOrError {
constructor(public readonly length: number, public readonly message: string | undefined) {}
public readonly name = MessageName.notice;
public severity: string | undefined;
public code: string | undefined;
public detail: string | undefined;
public hint: string | undefined;
public position: string | undefined;
public internalPosition: string | undefined;
public internalQuery: string | undefined;
public where: string | undefined;
public schema: string | undefined;
public table: string | undefined;
public column: string | undefined;
public dataType: string | undefined;
public constraint: string | undefined;
public file: string | undefined;
public line: string | undefined;
public routine: string | undefined;
public readonly name = MessageName.notice
public severity: string | undefined
public code: string | undefined
public detail: string | undefined
public hint: string | undefined
public position: string | undefined
public internalPosition: string | undefined
public internalQuery: string | undefined
public where: string | undefined
public schema: string | undefined
public table: string | undefined
public column: string | undefined
public dataType: string | undefined
public constraint: string | undefined
public file: string | undefined
public line: string | undefined
public routine: string | undefined
}

View File

@ -1,13 +1,13 @@
import assert from 'assert';
import { serialize } from './serializer';
import BufferList from './testing/buffer-list';
import assert from 'assert'
import { serialize } from './serializer'
import BufferList from './testing/buffer-list'
describe('serializer', () => {
it('builds startup message', function () {
const actual = serialize.startup({
user: 'brian',
database: 'bang',
});
})
assert.deepEqual(
actual,
new BufferList()
@ -21,59 +21,59 @@ describe('serializer', () => {
.addCString("'utf-8'")
.addCString('')
.join(true)
);
});
)
})
it('builds password message', function () {
const actual = serialize.password('!');
assert.deepEqual(actual, new BufferList().addCString('!').join(true, 'p'));
});
const actual = serialize.password('!')
assert.deepEqual(actual, new BufferList().addCString('!').join(true, 'p'))
})
it('builds request ssl message', function () {
const actual = serialize.requestSsl();
const expected = new BufferList().addInt32(80877103).join(true);
assert.deepEqual(actual, expected);
});
const actual = serialize.requestSsl()
const expected = new BufferList().addInt32(80877103).join(true)
assert.deepEqual(actual, expected)
})
it('builds SASLInitialResponseMessage message', function () {
const actual = serialize.sendSASLInitialResponseMessage('mech', 'data');
assert.deepEqual(actual, new BufferList().addCString('mech').addInt32(4).addString('data').join(true, 'p'));
});
const actual = serialize.sendSASLInitialResponseMessage('mech', 'data')
assert.deepEqual(actual, new BufferList().addCString('mech').addInt32(4).addString('data').join(true, 'p'))
})
it('builds SCRAMClientFinalMessage message', function () {
const actual = serialize.sendSCRAMClientFinalMessage('data');
assert.deepEqual(actual, new BufferList().addString('data').join(true, 'p'));
});
const actual = serialize.sendSCRAMClientFinalMessage('data')
assert.deepEqual(actual, new BufferList().addString('data').join(true, 'p'))
})
it('builds query message', function () {
var txt = 'select * from boom';
const actual = serialize.query(txt);
assert.deepEqual(actual, new BufferList().addCString(txt).join(true, 'Q'));
});
var txt = 'select * from boom'
const actual = serialize.query(txt)
assert.deepEqual(actual, new BufferList().addCString(txt).join(true, 'Q'))
})
describe('parse message', () => {
it('builds parse message', function () {
const actual = serialize.parse({ text: '!' });
var expected = new BufferList().addCString('').addCString('!').addInt16(0).join(true, 'P');
assert.deepEqual(actual, expected);
});
const actual = serialize.parse({ text: '!' })
var expected = new BufferList().addCString('').addCString('!').addInt16(0).join(true, 'P')
assert.deepEqual(actual, expected)
})
it('builds parse message with named query', function () {
const actual = serialize.parse({
name: 'boom',
text: 'select * from boom',
types: [],
});
var expected = new BufferList().addCString('boom').addCString('select * from boom').addInt16(0).join(true, 'P');
assert.deepEqual(actual, expected);
});
})
var expected = new BufferList().addCString('boom').addCString('select * from boom').addInt16(0).join(true, 'P')
assert.deepEqual(actual, expected)
})
it('with multiple parameters', function () {
const actual = serialize.parse({
name: 'force',
text: 'select * from bang where name = $1',
types: [1, 2, 3, 4],
});
})
var expected = new BufferList()
.addCString('force')
.addCString('select * from bang where name = $1')
@ -82,14 +82,14 @@ describe('serializer', () => {
.addInt32(2)
.addInt32(3)
.addInt32(4)
.join(true, 'P');
assert.deepEqual(actual, expected);
});
});
.join(true, 'P')
assert.deepEqual(actual, expected)
})
})
describe('bind messages', function () {
it('with no values', function () {
const actual = serialize.bind();
const actual = serialize.bind()
var expectedBuffer = new BufferList()
.addCString('')
@ -97,16 +97,16 @@ describe('serializer', () => {
.addInt16(0)
.addInt16(0)
.addInt16(0)
.join(true, 'B');
assert.deepEqual(actual, expectedBuffer);
});
.join(true, 'B')
assert.deepEqual(actual, expectedBuffer)
})
it('with named statement, portal, and values', function () {
const actual = serialize.bind({
portal: 'bang',
statement: 'woo',
values: ['1', 'hi', null, 'zing'],
});
})
var expectedBuffer = new BufferList()
.addCString('bang') // portal name
.addCString('woo') // statement name
@ -120,17 +120,17 @@ describe('serializer', () => {
.addInt32(4)
.add(Buffer.from('zing'))
.addInt16(0)
.join(true, 'B');
assert.deepEqual(actual, expectedBuffer);
});
});
.join(true, 'B')
assert.deepEqual(actual, expectedBuffer)
})
})
it('with named statement, portal, and buffer value', function () {
const actual = serialize.bind({
portal: 'bang',
statement: 'woo',
values: ['1', 'hi', null, Buffer.from('zing', 'utf8')],
});
})
var expectedBuffer = new BufferList()
.addCString('bang') // portal name
.addCString('woo') // statement name
@ -148,96 +148,96 @@ describe('serializer', () => {
.addInt32(4)
.add(Buffer.from('zing', 'utf-8'))
.addInt16(0)
.join(true, 'B');
assert.deepEqual(actual, expectedBuffer);
});
.join(true, 'B')
assert.deepEqual(actual, expectedBuffer)
})
describe('builds execute message', function () {
it('for unamed portal with no row limit', function () {
const actual = serialize.execute();
var expectedBuffer = new BufferList().addCString('').addInt32(0).join(true, 'E');
assert.deepEqual(actual, expectedBuffer);
});
const actual = serialize.execute()
var expectedBuffer = new BufferList().addCString('').addInt32(0).join(true, 'E')
assert.deepEqual(actual, expectedBuffer)
})
it('for named portal with row limit', function () {
const actual = serialize.execute({
portal: 'my favorite portal',
rows: 100,
});
var expectedBuffer = new BufferList().addCString('my favorite portal').addInt32(100).join(true, 'E');
assert.deepEqual(actual, expectedBuffer);
});
});
})
var expectedBuffer = new BufferList().addCString('my favorite portal').addInt32(100).join(true, 'E')
assert.deepEqual(actual, expectedBuffer)
})
})
it('builds flush command', function () {
const actual = serialize.flush();
var expected = new BufferList().join(true, 'H');
assert.deepEqual(actual, expected);
});
const actual = serialize.flush()
var expected = new BufferList().join(true, 'H')
assert.deepEqual(actual, expected)
})
it('builds sync command', function () {
const actual = serialize.sync();
var expected = new BufferList().join(true, 'S');
assert.deepEqual(actual, expected);
});
const actual = serialize.sync()
var expected = new BufferList().join(true, 'S')
assert.deepEqual(actual, expected)
})
it('builds end command', function () {
const actual = serialize.end();
var expected = Buffer.from([0x58, 0, 0, 0, 4]);
assert.deepEqual(actual, expected);
});
const actual = serialize.end()
var expected = Buffer.from([0x58, 0, 0, 0, 4])
assert.deepEqual(actual, expected)
})
describe('builds describe command', function () {
it('describe statement', function () {
const actual = serialize.describe({ type: 'S', name: 'bang' });
var expected = new BufferList().addChar('S').addCString('bang').join(true, 'D');
assert.deepEqual(actual, expected);
});
const actual = serialize.describe({ type: 'S', name: 'bang' })
var expected = new BufferList().addChar('S').addCString('bang').join(true, 'D')
assert.deepEqual(actual, expected)
})
it('describe unnamed portal', function () {
const actual = serialize.describe({ type: 'P' });
var expected = new BufferList().addChar('P').addCString('').join(true, 'D');
assert.deepEqual(actual, expected);
});
});
const actual = serialize.describe({ type: 'P' })
var expected = new BufferList().addChar('P').addCString('').join(true, 'D')
assert.deepEqual(actual, expected)
})
})
describe('builds close command', function () {
it('describe statement', function () {
const actual = serialize.close({ type: 'S', name: 'bang' });
var expected = new BufferList().addChar('S').addCString('bang').join(true, 'C');
assert.deepEqual(actual, expected);
});
const actual = serialize.close({ type: 'S', name: 'bang' })
var expected = new BufferList().addChar('S').addCString('bang').join(true, 'C')
assert.deepEqual(actual, expected)
})
it('describe unnamed portal', function () {
const actual = serialize.close({ type: 'P' });
var expected = new BufferList().addChar('P').addCString('').join(true, 'C');
assert.deepEqual(actual, expected);
});
});
const actual = serialize.close({ type: 'P' })
var expected = new BufferList().addChar('P').addCString('').join(true, 'C')
assert.deepEqual(actual, expected)
})
})
describe('copy messages', function () {
it('builds copyFromChunk', () => {
const actual = serialize.copyData(Buffer.from([1, 2, 3]));
const expected = new BufferList().add(Buffer.from([1, 2, 3])).join(true, 'd');
assert.deepEqual(actual, expected);
});
const actual = serialize.copyData(Buffer.from([1, 2, 3]))
const expected = new BufferList().add(Buffer.from([1, 2, 3])).join(true, 'd')
assert.deepEqual(actual, expected)
})
it('builds copy fail', () => {
const actual = serialize.copyFail('err!');
const expected = new BufferList().addCString('err!').join(true, 'f');
assert.deepEqual(actual, expected);
});
const actual = serialize.copyFail('err!')
const expected = new BufferList().addCString('err!').join(true, 'f')
assert.deepEqual(actual, expected)
})
it('builds copy done', () => {
const actual = serialize.copyDone();
const expected = new BufferList().join(true, 'c');
assert.deepEqual(actual, expected);
});
});
const actual = serialize.copyDone()
const expected = new BufferList().join(true, 'c')
assert.deepEqual(actual, expected)
})
})
it('builds cancel message', () => {
const actual = serialize.cancel(3, 4);
const expected = new BufferList().addInt16(1234).addInt16(5678).addInt32(3).addInt32(4).join(true);
assert.deepEqual(actual, expected);
});
});
const actual = serialize.cancel(3, 4)
const expected = new BufferList().addInt16(1234).addInt16(5678).addInt32(3).addInt32(4).join(true)
assert.deepEqual(actual, expected)
})
})

View File

@ -1,4 +1,4 @@
import { TransformOptions } from 'stream';
import { TransformOptions } from 'stream'
import {
Mode,
bindComplete,
@ -24,28 +24,28 @@ import {
MessageName,
AuthenticationMD5Password,
NoticeMessage,
} from './messages';
import { BufferReader } from './buffer-reader';
import assert from 'assert';
} from './messages'
import { BufferReader } from './buffer-reader'
import assert from 'assert'
// every message is prefixed with a single bye
const CODE_LENGTH = 1;
const CODE_LENGTH = 1
// every message has an int32 length which includes itself but does
// NOT include the code in the length
const LEN_LENGTH = 4;
const LEN_LENGTH = 4
const HEADER_LENGTH = CODE_LENGTH + LEN_LENGTH;
const HEADER_LENGTH = CODE_LENGTH + LEN_LENGTH
export type Packet = {
code: number;
packet: Buffer;
};
code: number
packet: Buffer
}
const emptyBuffer = Buffer.allocUnsafe(0);
const emptyBuffer = Buffer.allocUnsafe(0)
type StreamOptions = TransformOptions & {
mode: Mode;
};
mode: Mode
}
const enum MessageCodes {
DataRow = 0x44, // D
@ -71,275 +71,275 @@ const enum MessageCodes {
CopyData = 0x64, // d
}
export type MessageCallback = (msg: BackendMessage) => void;
export type MessageCallback = (msg: BackendMessage) => void
export class Parser {
private remainingBuffer: Buffer = emptyBuffer;
private reader = new BufferReader();
private mode: Mode;
private remainingBuffer: Buffer = emptyBuffer
private reader = new BufferReader()
private mode: Mode
constructor(opts?: StreamOptions) {
if (opts?.mode === 'binary') {
throw new Error('Binary mode not supported yet');
throw new Error('Binary mode not supported yet')
}
this.mode = opts?.mode || 'text';
this.mode = opts?.mode || 'text'
}
public parse(buffer: Buffer, callback: MessageCallback) {
let combinedBuffer = buffer;
let combinedBuffer = buffer
if (this.remainingBuffer.byteLength) {
combinedBuffer = Buffer.allocUnsafe(this.remainingBuffer.byteLength + buffer.byteLength);
this.remainingBuffer.copy(combinedBuffer);
buffer.copy(combinedBuffer, this.remainingBuffer.byteLength);
combinedBuffer = Buffer.allocUnsafe(this.remainingBuffer.byteLength + buffer.byteLength)
this.remainingBuffer.copy(combinedBuffer)
buffer.copy(combinedBuffer, this.remainingBuffer.byteLength)
}
let offset = 0;
let offset = 0
while (offset + HEADER_LENGTH <= combinedBuffer.byteLength) {
// code is 1 byte long - it identifies the message type
const code = combinedBuffer[offset];
const code = combinedBuffer[offset]
// length is 1 Uint32BE - it is the length of the message EXCLUDING the code
const length = combinedBuffer.readUInt32BE(offset + CODE_LENGTH);
const length = combinedBuffer.readUInt32BE(offset + CODE_LENGTH)
const fullMessageLength = CODE_LENGTH + length;
const fullMessageLength = CODE_LENGTH + length
if (fullMessageLength + offset <= combinedBuffer.byteLength) {
const message = this.handlePacket(offset + HEADER_LENGTH, code, length, combinedBuffer);
callback(message);
offset += fullMessageLength;
const message = this.handlePacket(offset + HEADER_LENGTH, code, length, combinedBuffer)
callback(message)
offset += fullMessageLength
} else {
break;
break
}
}
if (offset === combinedBuffer.byteLength) {
this.remainingBuffer = emptyBuffer;
this.remainingBuffer = emptyBuffer
} else {
this.remainingBuffer = combinedBuffer.slice(offset);
this.remainingBuffer = combinedBuffer.slice(offset)
}
}
private handlePacket(offset: number, code: number, length: number, bytes: Buffer): BackendMessage {
switch (code) {
case MessageCodes.BindComplete:
return bindComplete;
return bindComplete
case MessageCodes.ParseComplete:
return parseComplete;
return parseComplete
case MessageCodes.CloseComplete:
return closeComplete;
return closeComplete
case MessageCodes.NoData:
return noData;
return noData
case MessageCodes.PortalSuspended:
return portalSuspended;
return portalSuspended
case MessageCodes.CopyDone:
return copyDone;
return copyDone
case MessageCodes.ReplicationStart:
return replicationStart;
return replicationStart
case MessageCodes.EmptyQuery:
return emptyQuery;
return emptyQuery
case MessageCodes.DataRow:
return this.parseDataRowMessage(offset, length, bytes);
return this.parseDataRowMessage(offset, length, bytes)
case MessageCodes.CommandComplete:
return this.parseCommandCompleteMessage(offset, length, bytes);
return this.parseCommandCompleteMessage(offset, length, bytes)
case MessageCodes.ReadyForQuery:
return this.parseReadyForQueryMessage(offset, length, bytes);
return this.parseReadyForQueryMessage(offset, length, bytes)
case MessageCodes.NotificationResponse:
return this.parseNotificationMessage(offset, length, bytes);
return this.parseNotificationMessage(offset, length, bytes)
case MessageCodes.AuthenticationResponse:
return this.parseAuthenticationResponse(offset, length, bytes);
return this.parseAuthenticationResponse(offset, length, bytes)
case MessageCodes.ParameterStatus:
return this.parseParameterStatusMessage(offset, length, bytes);
return this.parseParameterStatusMessage(offset, length, bytes)
case MessageCodes.BackendKeyData:
return this.parseBackendKeyData(offset, length, bytes);
return this.parseBackendKeyData(offset, length, bytes)
case MessageCodes.ErrorMessage:
return this.parseErrorMessage(offset, length, bytes, MessageName.error);
return this.parseErrorMessage(offset, length, bytes, MessageName.error)
case MessageCodes.NoticeMessage:
return this.parseErrorMessage(offset, length, bytes, MessageName.notice);
return this.parseErrorMessage(offset, length, bytes, MessageName.notice)
case MessageCodes.RowDescriptionMessage:
return this.parseRowDescriptionMessage(offset, length, bytes);
return this.parseRowDescriptionMessage(offset, length, bytes)
case MessageCodes.CopyIn:
return this.parseCopyInMessage(offset, length, bytes);
return this.parseCopyInMessage(offset, length, bytes)
case MessageCodes.CopyOut:
return this.parseCopyOutMessage(offset, length, bytes);
return this.parseCopyOutMessage(offset, length, bytes)
case MessageCodes.CopyData:
return this.parseCopyData(offset, length, bytes);
return this.parseCopyData(offset, length, bytes)
default:
assert.fail(`unknown message code: ${code.toString(16)}`);
assert.fail(`unknown message code: ${code.toString(16)}`)
}
}
private parseReadyForQueryMessage(offset: number, length: number, bytes: Buffer) {
this.reader.setBuffer(offset, bytes);
const status = this.reader.string(1);
return new ReadyForQueryMessage(length, status);
this.reader.setBuffer(offset, bytes)
const status = this.reader.string(1)
return new ReadyForQueryMessage(length, status)
}
private parseCommandCompleteMessage(offset: number, length: number, bytes: Buffer) {
this.reader.setBuffer(offset, bytes);
const text = this.reader.cstring();
return new CommandCompleteMessage(length, text);
this.reader.setBuffer(offset, bytes)
const text = this.reader.cstring()
return new CommandCompleteMessage(length, text)
}
private parseCopyData(offset: number, length: number, bytes: Buffer) {
const chunk = bytes.slice(offset, offset + (length - 4));
return new CopyDataMessage(length, chunk);
const chunk = bytes.slice(offset, offset + (length - 4))
return new CopyDataMessage(length, chunk)
}
private parseCopyInMessage(offset: number, length: number, bytes: Buffer) {
return this.parseCopyMessage(offset, length, bytes, MessageName.copyInResponse);
return this.parseCopyMessage(offset, length, bytes, MessageName.copyInResponse)
}
private parseCopyOutMessage(offset: number, length: number, bytes: Buffer) {
return this.parseCopyMessage(offset, length, bytes, MessageName.copyOutResponse);
return this.parseCopyMessage(offset, length, bytes, MessageName.copyOutResponse)
}
private parseCopyMessage(offset: number, length: number, bytes: Buffer, messageName: MessageName) {
this.reader.setBuffer(offset, bytes);
const isBinary = this.reader.byte() !== 0;
const columnCount = this.reader.int16();
const message = new CopyResponse(length, messageName, isBinary, columnCount);
this.reader.setBuffer(offset, bytes)
const isBinary = this.reader.byte() !== 0
const columnCount = this.reader.int16()
const message = new CopyResponse(length, messageName, isBinary, columnCount)
for (let i = 0; i < columnCount; i++) {
message.columnTypes[i] = this.reader.int16();
message.columnTypes[i] = this.reader.int16()
}
return message;
return message
}
private parseNotificationMessage(offset: number, length: number, bytes: Buffer) {
this.reader.setBuffer(offset, bytes);
const processId = this.reader.int32();
const channel = this.reader.cstring();
const payload = this.reader.cstring();
return new NotificationResponseMessage(length, processId, channel, payload);
this.reader.setBuffer(offset, bytes)
const processId = this.reader.int32()
const channel = this.reader.cstring()
const payload = this.reader.cstring()
return new NotificationResponseMessage(length, processId, channel, payload)
}
private parseRowDescriptionMessage(offset: number, length: number, bytes: Buffer) {
this.reader.setBuffer(offset, bytes);
const fieldCount = this.reader.int16();
const message = new RowDescriptionMessage(length, fieldCount);
this.reader.setBuffer(offset, bytes)
const fieldCount = this.reader.int16()
const message = new RowDescriptionMessage(length, fieldCount)
for (let i = 0; i < fieldCount; i++) {
message.fields[i] = this.parseField();
message.fields[i] = this.parseField()
}
return message;
return message
}
private parseField(): Field {
const name = this.reader.cstring();
const tableID = this.reader.int32();
const columnID = this.reader.int16();
const dataTypeID = this.reader.int32();
const dataTypeSize = this.reader.int16();
const dataTypeModifier = this.reader.int32();
const mode = this.reader.int16() === 0 ? 'text' : 'binary';
return new Field(name, tableID, columnID, dataTypeID, dataTypeSize, dataTypeModifier, mode);
const name = this.reader.cstring()
const tableID = this.reader.int32()
const columnID = this.reader.int16()
const dataTypeID = this.reader.int32()
const dataTypeSize = this.reader.int16()
const dataTypeModifier = this.reader.int32()
const mode = this.reader.int16() === 0 ? 'text' : 'binary'
return new Field(name, tableID, columnID, dataTypeID, dataTypeSize, dataTypeModifier, mode)
}
private parseDataRowMessage(offset: number, length: number, bytes: Buffer) {
this.reader.setBuffer(offset, bytes);
const fieldCount = this.reader.int16();
const fields: any[] = new Array(fieldCount);
this.reader.setBuffer(offset, bytes)
const fieldCount = this.reader.int16()
const fields: any[] = new Array(fieldCount)
for (let i = 0; i < fieldCount; i++) {
const len = this.reader.int32();
const len = this.reader.int32()
// a -1 for length means the value of the field is null
fields[i] = len === -1 ? null : this.reader.string(len);
fields[i] = len === -1 ? null : this.reader.string(len)
}
return new DataRowMessage(length, fields);
return new DataRowMessage(length, fields)
}
private parseParameterStatusMessage(offset: number, length: number, bytes: Buffer) {
this.reader.setBuffer(offset, bytes);
const name = this.reader.cstring();
const value = this.reader.cstring();
return new ParameterStatusMessage(length, name, value);
this.reader.setBuffer(offset, bytes)
const name = this.reader.cstring()
const value = this.reader.cstring()
return new ParameterStatusMessage(length, name, value)
}
private parseBackendKeyData(offset: number, length: number, bytes: Buffer) {
this.reader.setBuffer(offset, bytes);
const processID = this.reader.int32();
const secretKey = this.reader.int32();
return new BackendKeyDataMessage(length, processID, secretKey);
this.reader.setBuffer(offset, bytes)
const processID = this.reader.int32()
const secretKey = this.reader.int32()
return new BackendKeyDataMessage(length, processID, secretKey)
}
public parseAuthenticationResponse(offset: number, length: number, bytes: Buffer) {
this.reader.setBuffer(offset, bytes);
const code = this.reader.int32();
this.reader.setBuffer(offset, bytes)
const code = this.reader.int32()
// TODO(bmc): maybe better types here
const message: BackendMessage & any = {
name: MessageName.authenticationOk,
length,
};
}
switch (code) {
case 0: // AuthenticationOk
break;
break
case 3: // AuthenticationCleartextPassword
if (message.length === 8) {
message.name = MessageName.authenticationCleartextPassword;
message.name = MessageName.authenticationCleartextPassword
}
break;
break
case 5: // AuthenticationMD5Password
if (message.length === 12) {
message.name = MessageName.authenticationMD5Password;
const salt = this.reader.bytes(4);
return new AuthenticationMD5Password(length, salt);
message.name = MessageName.authenticationMD5Password
const salt = this.reader.bytes(4)
return new AuthenticationMD5Password(length, salt)
}
break;
break
case 10: // AuthenticationSASL
message.name = MessageName.authenticationSASL;
message.mechanisms = [];
let mechanism: string;
message.name = MessageName.authenticationSASL
message.mechanisms = []
let mechanism: string
do {
mechanism = this.reader.cstring();
mechanism = this.reader.cstring()
if (mechanism) {
message.mechanisms.push(mechanism);
message.mechanisms.push(mechanism)
}
} while (mechanism);
break;
} while (mechanism)
break
case 11: // AuthenticationSASLContinue
message.name = MessageName.authenticationSASLContinue;
message.data = this.reader.string(length - 4);
break;
message.name = MessageName.authenticationSASLContinue
message.data = this.reader.string(length - 4)
break
case 12: // AuthenticationSASLFinal
message.name = MessageName.authenticationSASLFinal;
message.data = this.reader.string(length - 4);
break;
message.name = MessageName.authenticationSASLFinal
message.data = this.reader.string(length - 4)
break
default:
throw new Error('Unknown authenticationOk message type ' + code);
throw new Error('Unknown authenticationOk message type ' + code)
}
return message;
return message
}
private parseErrorMessage(offset: number, length: number, bytes: Buffer, name: MessageName) {
this.reader.setBuffer(offset, bytes);
const fields: Record<string, string> = {};
let fieldType = this.reader.string(1);
this.reader.setBuffer(offset, bytes)
const fields: Record<string, string> = {}
let fieldType = this.reader.string(1)
while (fieldType !== '\0') {
fields[fieldType] = this.reader.cstring();
fieldType = this.reader.string(1);
fields[fieldType] = this.reader.cstring()
fieldType = this.reader.string(1)
}
const messageValue = fields.M;
const messageValue = fields.M
const message =
name === MessageName.notice
? new NoticeMessage(length, messageValue)
: new DatabaseError(messageValue, length, name);
: new DatabaseError(messageValue, length, name)
message.severity = fields.S;
message.code = fields.C;
message.detail = fields.D;
message.hint = fields.H;
message.position = fields.P;
message.internalPosition = fields.p;
message.internalQuery = fields.q;
message.where = fields.W;
message.schema = fields.s;
message.table = fields.t;
message.column = fields.c;
message.dataType = fields.d;
message.constraint = fields.n;
message.file = fields.F;
message.line = fields.L;
message.routine = fields.R;
return message;
message.severity = fields.S
message.code = fields.C
message.detail = fields.D
message.hint = fields.H
message.position = fields.P
message.internalPosition = fields.p
message.internalQuery = fields.q
message.where = fields.W
message.schema = fields.s
message.table = fields.t
message.column = fields.c
message.dataType = fields.d
message.constraint = fields.n
message.file = fields.F
message.line = fields.L
message.routine = fields.R
return message
}
}

View File

@ -1,4 +1,4 @@
import { Writer } from './buffer-writer';
import { Writer } from './buffer-writer'
const enum code {
startup = 0x70,
@ -16,58 +16,58 @@ const enum code {
copyFail = 0x66,
}
const writer = new Writer();
const writer = new Writer()
const startup = (opts: Record<string, string>): Buffer => {
// protocol version
writer.addInt16(3).addInt16(0);
writer.addInt16(3).addInt16(0)
for (const key of Object.keys(opts)) {
writer.addCString(key).addCString(opts[key]);
writer.addCString(key).addCString(opts[key])
}
writer.addCString('client_encoding').addCString("'utf-8'");
writer.addCString('client_encoding').addCString("'utf-8'")
var bodyBuffer = writer.addCString('').flush();
var bodyBuffer = writer.addCString('').flush()
// this message is sent without a code
var length = bodyBuffer.length + 4;
var length = bodyBuffer.length + 4
return new Writer().addInt32(length).add(bodyBuffer).flush();
};
return new Writer().addInt32(length).add(bodyBuffer).flush()
}
const requestSsl = (): Buffer => {
const response = Buffer.allocUnsafe(8);
response.writeInt32BE(8, 0);
response.writeInt32BE(80877103, 4);
return response;
};
const response = Buffer.allocUnsafe(8)
response.writeInt32BE(8, 0)
response.writeInt32BE(80877103, 4)
return response
}
const password = (password: string): Buffer => {
return writer.addCString(password).flush(code.startup);
};
return writer.addCString(password).flush(code.startup)
}
const sendSASLInitialResponseMessage = function (mechanism: string, initialResponse: string): Buffer {
// 0x70 = 'p'
writer.addCString(mechanism).addInt32(Buffer.byteLength(initialResponse)).addString(initialResponse);
writer.addCString(mechanism).addInt32(Buffer.byteLength(initialResponse)).addString(initialResponse)
return writer.flush(code.startup);
};
return writer.flush(code.startup)
}
const sendSCRAMClientFinalMessage = function (additionalData: string): Buffer {
return writer.addString(additionalData).flush(code.startup);
};
return writer.addString(additionalData).flush(code.startup)
}
const query = (text: string): Buffer => {
return writer.addCString(text).flush(code.query);
};
return writer.addCString(text).flush(code.query)
}
type ParseOpts = {
name?: string;
types?: number[];
text: string;
};
name?: string
types?: number[]
text: string
}
const emptyArray: any[] = [];
const emptyArray: any[] = []
const parse = (query: ParseOpts): Buffer => {
// expect something like this:
@ -76,169 +76,169 @@ const parse = (query: ParseOpts): Buffer => {
// types: ['int8', 'bool'] }
// normalize missing query names to allow for null
const name = query.name || '';
const name = query.name || ''
if (name.length > 63) {
/* eslint-disable no-console */
console.error('Warning! Postgres only supports 63 characters for query names.');
console.error('You supplied %s (%s)', name, name.length);
console.error('This can cause conflicts and silent errors executing queries');
console.error('Warning! Postgres only supports 63 characters for query names.')
console.error('You supplied %s (%s)', name, name.length)
console.error('This can cause conflicts and silent errors executing queries')
/* eslint-enable no-console */
}
const types = query.types || emptyArray;
const types = query.types || emptyArray
var len = types.length;
var len = types.length
var buffer = writer
.addCString(name) // name of query
.addCString(query.text) // actual query text
.addInt16(len);
.addInt16(len)
for (var i = 0; i < len; i++) {
buffer.addInt32(types[i]);
buffer.addInt32(types[i])
}
return writer.flush(code.parse);
};
return writer.flush(code.parse)
}
type BindOpts = {
portal?: string;
binary?: boolean;
statement?: string;
values?: any[];
};
portal?: string
binary?: boolean
statement?: string
values?: any[]
}
const bind = (config: BindOpts = {}): Buffer => {
// normalize config
const portal = config.portal || '';
const statement = config.statement || '';
const binary = config.binary || false;
var values = config.values || emptyArray;
var len = values.length;
const portal = config.portal || ''
const statement = config.statement || ''
const binary = config.binary || false
var values = config.values || emptyArray
var len = values.length
var useBinary = false;
var useBinary = false
// TODO(bmc): all the loops in here aren't nice, we can do better
for (var j = 0; j < len; j++) {
useBinary = useBinary || values[j] instanceof Buffer;
useBinary = useBinary || values[j] instanceof Buffer
}
var buffer = writer.addCString(portal).addCString(statement);
var buffer = writer.addCString(portal).addCString(statement)
if (!useBinary) {
buffer.addInt16(0);
buffer.addInt16(0)
} else {
buffer.addInt16(len);
buffer.addInt16(len)
for (j = 0; j < len; j++) {
buffer.addInt16(values[j] instanceof Buffer ? 1 : 0);
buffer.addInt16(values[j] instanceof Buffer ? 1 : 0)
}
}
buffer.addInt16(len);
buffer.addInt16(len)
for (var i = 0; i < len; i++) {
var val = values[i];
var val = values[i]
if (val === null || typeof val === 'undefined') {
buffer.addInt32(-1);
buffer.addInt32(-1)
} else if (val instanceof Buffer) {
buffer.addInt32(val.length);
buffer.add(val);
buffer.addInt32(val.length)
buffer.add(val)
} else {
buffer.addInt32(Buffer.byteLength(val));
buffer.addString(val);
buffer.addInt32(Buffer.byteLength(val))
buffer.addString(val)
}
}
if (binary) {
buffer.addInt16(1); // format codes to use binary
buffer.addInt16(1);
buffer.addInt16(1) // format codes to use binary
buffer.addInt16(1)
} else {
buffer.addInt16(0); // format codes to use text
buffer.addInt16(0) // format codes to use text
}
return writer.flush(code.bind);
};
return writer.flush(code.bind)
}
type ExecOpts = {
portal?: string;
rows?: number;
};
portal?: string
rows?: number
}
const emptyExecute = Buffer.from([code.execute, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00]);
const emptyExecute = Buffer.from([code.execute, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00])
const execute = (config?: ExecOpts): Buffer => {
// this is the happy path for most queries
if (!config || (!config.portal && !config.rows)) {
return emptyExecute;
return emptyExecute
}
const portal = config.portal || '';
const rows = config.rows || 0;
const portal = config.portal || ''
const rows = config.rows || 0
const portalLength = Buffer.byteLength(portal);
const len = 4 + portalLength + 1 + 4;
const portalLength = Buffer.byteLength(portal)
const len = 4 + portalLength + 1 + 4
// one extra bit for code
const buff = Buffer.allocUnsafe(1 + len);
buff[0] = code.execute;
buff.writeInt32BE(len, 1);
buff.write(portal, 5, 'utf-8');
buff[portalLength + 5] = 0; // null terminate portal cString
buff.writeUInt32BE(rows, buff.length - 4);
return buff;
};
const buff = Buffer.allocUnsafe(1 + len)
buff[0] = code.execute
buff.writeInt32BE(len, 1)
buff.write(portal, 5, 'utf-8')
buff[portalLength + 5] = 0 // null terminate portal cString
buff.writeUInt32BE(rows, buff.length - 4)
return buff
}
const cancel = (processID: number, secretKey: number): Buffer => {
const buffer = Buffer.allocUnsafe(16);
buffer.writeInt32BE(16, 0);
buffer.writeInt16BE(1234, 4);
buffer.writeInt16BE(5678, 6);
buffer.writeInt32BE(processID, 8);
buffer.writeInt32BE(secretKey, 12);
return buffer;
};
const buffer = Buffer.allocUnsafe(16)
buffer.writeInt32BE(16, 0)
buffer.writeInt16BE(1234, 4)
buffer.writeInt16BE(5678, 6)
buffer.writeInt32BE(processID, 8)
buffer.writeInt32BE(secretKey, 12)
return buffer
}
type PortalOpts = {
type: 'S' | 'P';
name?: string;
};
type: 'S' | 'P'
name?: string
}
const cstringMessage = (code: code, string: string): Buffer => {
const stringLen = Buffer.byteLength(string);
const len = 4 + stringLen + 1;
const stringLen = Buffer.byteLength(string)
const len = 4 + stringLen + 1
// one extra bit for code
const buffer = Buffer.allocUnsafe(1 + len);
buffer[0] = code;
buffer.writeInt32BE(len, 1);
buffer.write(string, 5, 'utf-8');
buffer[len] = 0; // null terminate cString
return buffer;
};
const buffer = Buffer.allocUnsafe(1 + len)
buffer[0] = code
buffer.writeInt32BE(len, 1)
buffer.write(string, 5, 'utf-8')
buffer[len] = 0 // null terminate cString
return buffer
}
const emptyDescribePortal = writer.addCString('P').flush(code.describe);
const emptyDescribeStatement = writer.addCString('S').flush(code.describe);
const emptyDescribePortal = writer.addCString('P').flush(code.describe)
const emptyDescribeStatement = writer.addCString('S').flush(code.describe)
const describe = (msg: PortalOpts): Buffer => {
return msg.name
? cstringMessage(code.describe, `${msg.type}${msg.name || ''}`)
: msg.type === 'P'
? emptyDescribePortal
: emptyDescribeStatement;
};
: emptyDescribeStatement
}
const close = (msg: PortalOpts): Buffer => {
const text = `${msg.type}${msg.name || ''}`;
return cstringMessage(code.close, text);
};
const text = `${msg.type}${msg.name || ''}`
return cstringMessage(code.close, text)
}
const copyData = (chunk: Buffer): Buffer => {
return writer.add(chunk).flush(code.copyFromChunk);
};
return writer.add(chunk).flush(code.copyFromChunk)
}
const copyFail = (message: string): Buffer => {
return cstringMessage(code.copyFail, message);
};
return cstringMessage(code.copyFail, message)
}
const codeOnlyBuffer = (code: code): Buffer => Buffer.from([code, 0x00, 0x00, 0x00, 0x04]);
const codeOnlyBuffer = (code: code): Buffer => Buffer.from([code, 0x00, 0x00, 0x00, 0x04])
const flushBuffer = codeOnlyBuffer(code.flush);
const syncBuffer = codeOnlyBuffer(code.sync);
const endBuffer = codeOnlyBuffer(code.end);
const copyDoneBuffer = codeOnlyBuffer(code.copyDone);
const flushBuffer = codeOnlyBuffer(code.flush)
const syncBuffer = codeOnlyBuffer(code.sync)
const endBuffer = codeOnlyBuffer(code.end)
const copyDoneBuffer = codeOnlyBuffer(code.copyDone)
const serialize = {
startup,
@ -259,6 +259,6 @@ const serialize = {
copyDone: () => copyDoneBuffer,
copyFail,
cancel,
};
}
export { serialize };
export { serialize }

View File

@ -2,74 +2,74 @@ export default class BufferList {
constructor(public buffers: Buffer[] = []) {}
public add(buffer: Buffer, front?: boolean) {
this.buffers[front ? 'unshift' : 'push'](buffer);
return this;
this.buffers[front ? 'unshift' : 'push'](buffer)
return this
}
public addInt16(val: number, front?: boolean) {
return this.add(Buffer.from([val >>> 8, val >>> 0]), front);
return this.add(Buffer.from([val >>> 8, val >>> 0]), front)
}
public getByteLength(initial?: number) {
return this.buffers.reduce(function (previous, current) {
return previous + current.length;
}, initial || 0);
return previous + current.length
}, initial || 0)
}
public addInt32(val: number, first?: boolean) {
return this.add(
Buffer.from([(val >>> 24) & 0xff, (val >>> 16) & 0xff, (val >>> 8) & 0xff, (val >>> 0) & 0xff]),
first
);
)
}
public addCString(val: string, front?: boolean) {
var len = Buffer.byteLength(val);
var buffer = Buffer.alloc(len + 1);
buffer.write(val);
buffer[len] = 0;
return this.add(buffer, front);
var len = Buffer.byteLength(val)
var buffer = Buffer.alloc(len + 1)
buffer.write(val)
buffer[len] = 0
return this.add(buffer, front)
}
public addString(val: string, front?: boolean) {
var len = Buffer.byteLength(val);
var buffer = Buffer.alloc(len);
buffer.write(val);
return this.add(buffer, front);
var len = Buffer.byteLength(val)
var buffer = Buffer.alloc(len)
buffer.write(val)
return this.add(buffer, front)
}
public addChar(char: string, first?: boolean) {
return this.add(Buffer.from(char, 'utf8'), first);
return this.add(Buffer.from(char, 'utf8'), first)
}
public addByte(byte: number) {
return this.add(Buffer.from([byte]));
return this.add(Buffer.from([byte]))
}
public join(appendLength?: boolean, char?: string): Buffer {
var length = this.getByteLength();
var length = this.getByteLength()
if (appendLength) {
this.addInt32(length + 4, true);
return this.join(false, char);
this.addInt32(length + 4, true)
return this.join(false, char)
}
if (char) {
this.addChar(char, true);
length++;
this.addChar(char, true)
length++
}
var result = Buffer.alloc(length);
var index = 0;
var result = Buffer.alloc(length)
var index = 0
this.buffers.forEach(function (buffer) {
buffer.copy(result, index, 0);
index += buffer.length;
});
return result;
buffer.copy(result, index, 0)
index += buffer.length
})
return result
}
public static concat(): Buffer {
var total = new BufferList();
var total = new BufferList()
for (var i = 0; i < arguments.length; i++) {
total.add(arguments[i]);
total.add(arguments[i])
}
return total.join();
return total.join()
}
}

View File

@ -1,54 +1,54 @@
// http://developer.postgresql.org/pgdocs/postgres/protocol-message-formats.html
import BufferList from './buffer-list';
import BufferList from './buffer-list'
const buffers = {
readyForQuery: function () {
return new BufferList().add(Buffer.from('I')).join(true, 'Z');
return new BufferList().add(Buffer.from('I')).join(true, 'Z')
},
authenticationOk: function () {
return new BufferList().addInt32(0).join(true, 'R');
return new BufferList().addInt32(0).join(true, 'R')
},
authenticationCleartextPassword: function () {
return new BufferList().addInt32(3).join(true, 'R');
return new BufferList().addInt32(3).join(true, 'R')
},
authenticationMD5Password: function () {
return new BufferList()
.addInt32(5)
.add(Buffer.from([1, 2, 3, 4]))
.join(true, 'R');
.join(true, 'R')
},
authenticationSASL: function () {
return new BufferList().addInt32(10).addCString('SCRAM-SHA-256').addCString('').join(true, 'R');
return new BufferList().addInt32(10).addCString('SCRAM-SHA-256').addCString('').join(true, 'R')
},
authenticationSASLContinue: function () {
return new BufferList().addInt32(11).addString('data').join(true, 'R');
return new BufferList().addInt32(11).addString('data').join(true, 'R')
},
authenticationSASLFinal: function () {
return new BufferList().addInt32(12).addString('data').join(true, 'R');
return new BufferList().addInt32(12).addString('data').join(true, 'R')
},
parameterStatus: function (name: string, value: string) {
return new BufferList().addCString(name).addCString(value).join(true, 'S');
return new BufferList().addCString(name).addCString(value).join(true, 'S')
},
backendKeyData: function (processID: number, secretKey: number) {
return new BufferList().addInt32(processID).addInt32(secretKey).join(true, 'K');
return new BufferList().addInt32(processID).addInt32(secretKey).join(true, 'K')
},
commandComplete: function (string: string) {
return new BufferList().addCString(string).join(true, 'C');
return new BufferList().addCString(string).join(true, 'C')
},
rowDescription: function (fields: any[]) {
fields = fields || [];
var buf = new BufferList();
buf.addInt16(fields.length);
fields = fields || []
var buf = new BufferList()
buf.addInt16(fields.length)
fields.forEach(function (field) {
buf
.addCString(field.name)
@ -57,67 +57,67 @@ const buffers = {
.addInt32(field.dataTypeID || 0)
.addInt16(field.dataTypeSize || 0)
.addInt32(field.typeModifier || 0)
.addInt16(field.formatCode || 0);
});
return buf.join(true, 'T');
.addInt16(field.formatCode || 0)
})
return buf.join(true, 'T')
},
dataRow: function (columns: any[]) {
columns = columns || [];
var buf = new BufferList();
buf.addInt16(columns.length);
columns = columns || []
var buf = new BufferList()
buf.addInt16(columns.length)
columns.forEach(function (col) {
if (col == null) {
buf.addInt32(-1);
buf.addInt32(-1)
} else {
var strBuf = Buffer.from(col, 'utf8');
buf.addInt32(strBuf.length);
buf.add(strBuf);
var strBuf = Buffer.from(col, 'utf8')
buf.addInt32(strBuf.length)
buf.add(strBuf)
}
});
return buf.join(true, 'D');
})
return buf.join(true, 'D')
},
error: function (fields: any) {
return buffers.errorOrNotice(fields).join(true, 'E');
return buffers.errorOrNotice(fields).join(true, 'E')
},
notice: function (fields: any) {
return buffers.errorOrNotice(fields).join(true, 'N');
return buffers.errorOrNotice(fields).join(true, 'N')
},
errorOrNotice: function (fields: any) {
fields = fields || [];
var buf = new BufferList();
fields = fields || []
var buf = new BufferList()
fields.forEach(function (field: any) {
buf.addChar(field.type);
buf.addCString(field.value);
});
return buf.add(Buffer.from([0])); // terminator
buf.addChar(field.type)
buf.addCString(field.value)
})
return buf.add(Buffer.from([0])) // terminator
},
parseComplete: function () {
return new BufferList().join(true, '1');
return new BufferList().join(true, '1')
},
bindComplete: function () {
return new BufferList().join(true, '2');
return new BufferList().join(true, '2')
},
notification: function (id: number, channel: string, payload: string) {
return new BufferList().addInt32(id).addCString(channel).addCString(payload).join(true, 'A');
return new BufferList().addInt32(id).addCString(channel).addCString(payload).join(true, 'A')
},
emptyQuery: function () {
return new BufferList().join(true, 'I');
return new BufferList().join(true, 'I')
},
portalSuspended: function () {
return new BufferList().join(true, 's');
return new BufferList().join(true, 's')
},
closeComplete: function () {
return new BufferList().join(true, '3');
return new BufferList().join(true, '3')
},
copyIn: function (cols: number) {
@ -125,11 +125,11 @@ const buffers = {
// text mode
.addByte(0)
// column count
.addInt16(cols);
.addInt16(cols)
for (let i = 0; i < cols; i++) {
list.addInt16(i);
list.addInt16(i)
}
return list.join(true, 'G');
return list.join(true, 'G')
},
copyOut: function (cols: number) {
@ -137,20 +137,20 @@ const buffers = {
// text mode
.addByte(0)
// column count
.addInt16(cols);
.addInt16(cols)
for (let i = 0; i < cols; i++) {
list.addInt16(i);
list.addInt16(i)
}
return list.join(true, 'H');
return list.join(true, 'H')
},
copyData: function (bytes: Buffer) {
return new BufferList().add(bytes).join(true, 'd');
return new BufferList().add(bytes).join(true, 'd')
},
copyDone: function () {
return new BufferList().join(true, 'c');
return new BufferList().join(true, 'c')
},
};
}
export default buffers;
export default buffers

View File

@ -1 +1 @@
declare module 'chunky';
declare module 'chunky'

View File

@ -1,31 +1,31 @@
const { Readable } = require('stream');
const Cursor = require('pg-cursor');
const { Readable } = require('stream')
const Cursor = require('pg-cursor')
class PgQueryStream extends Readable {
constructor(text, values, config = {}) {
const { batchSize, highWaterMark = 100 } = config;
const { batchSize, highWaterMark = 100 } = config
// https://nodejs.org/api/stream.html#stream_new_stream_readable_options
super({ objectMode: true, emitClose: true, autoDestroy: true, highWaterMark: batchSize || highWaterMark });
this.cursor = new Cursor(text, values, config);
super({ objectMode: true, emitClose: true, autoDestroy: true, highWaterMark: batchSize || highWaterMark })
this.cursor = new Cursor(text, values, config)
// delegate Submittable callbacks to cursor
this.handleRowDescription = this.cursor.handleRowDescription.bind(this.cursor);
this.handleDataRow = this.cursor.handleDataRow.bind(this.cursor);
this.handlePortalSuspended = this.cursor.handlePortalSuspended.bind(this.cursor);
this.handleCommandComplete = this.cursor.handleCommandComplete.bind(this.cursor);
this.handleReadyForQuery = this.cursor.handleReadyForQuery.bind(this.cursor);
this.handleError = this.cursor.handleError.bind(this.cursor);
this.handleEmptyQuery = this.cursor.handleEmptyQuery.bind(this.cursor);
this.handleRowDescription = this.cursor.handleRowDescription.bind(this.cursor)
this.handleDataRow = this.cursor.handleDataRow.bind(this.cursor)
this.handlePortalSuspended = this.cursor.handlePortalSuspended.bind(this.cursor)
this.handleCommandComplete = this.cursor.handleCommandComplete.bind(this.cursor)
this.handleReadyForQuery = this.cursor.handleReadyForQuery.bind(this.cursor)
this.handleError = this.cursor.handleError.bind(this.cursor)
this.handleEmptyQuery = this.cursor.handleEmptyQuery.bind(this.cursor)
}
submit(connection) {
this.cursor.submit(connection);
this.cursor.submit(connection)
}
_destroy(_err, cb) {
this.cursor.close((err) => {
cb(err || _err);
});
cb(err || _err)
})
}
// https://nodejs.org/api/stream.html#stream_readable_read_size_1
@ -33,13 +33,13 @@ class PgQueryStream extends Readable {
this.cursor.read(size, (err, rows, result) => {
if (err) {
// https://nodejs.org/api/stream.html#stream_errors_while_reading
this.destroy(err);
this.destroy(err)
} else {
for (const row of rows) this.push(row);
if (rows.length < size) this.push(null);
for (const row of rows) this.push(row)
if (rows.length < size) this.push(null)
}
});
})
}
}
module.exports = PgQueryStream;
module.exports = PgQueryStream

View File

@ -1,4 +1,4 @@
// only newer versions of node support async iterator
if (!process.version.startsWith('v8')) {
require('./async-iterator.es6');
require('./async-iterator.es6')
}

View File

@ -1,91 +1,91 @@
var assert = require('assert');
var concat = require('concat-stream');
var assert = require('assert')
var concat = require('concat-stream')
var QueryStream = require('../');
var helper = require('./helper');
var QueryStream = require('../')
var helper = require('./helper')
if (process.version.startsWith('v8.')) {
console.error('warning! node less than 10lts stream closing semantics may not behave properly');
console.error('warning! node less than 10lts stream closing semantics may not behave properly')
} else {
helper('close', function (client) {
it('emits close', function (done) {
var stream = new QueryStream('SELECT * FROM generate_series(0, $1) num', [3], { batchSize: 2, highWaterMark: 2 });
var query = client.query(stream);
query.pipe(concat(function () {}));
query.on('close', done);
});
});
var stream = new QueryStream('SELECT * FROM generate_series(0, $1) num', [3], { batchSize: 2, highWaterMark: 2 })
var query = client.query(stream)
query.pipe(concat(function () {}))
query.on('close', done)
})
})
helper('early close', function (client) {
it('can be closed early', function (done) {
var stream = new QueryStream('SELECT * FROM generate_series(0, $1) num', [20000], {
batchSize: 2,
highWaterMark: 2,
});
var query = client.query(stream);
var readCount = 0;
})
var query = client.query(stream)
var readCount = 0
query.on('readable', function () {
readCount++;
query.read();
});
readCount++
query.read()
})
query.once('readable', function () {
query.destroy();
});
query.destroy()
})
query.on('close', function () {
assert(readCount < 10, 'should not have read more than 10 rows');
done();
});
});
assert(readCount < 10, 'should not have read more than 10 rows')
done()
})
})
it('can destroy stream while reading', function (done) {
var stream = new QueryStream('SELECT * FROM generate_series(0, 100), pg_sleep(1)');
client.query(stream);
stream.on('data', () => done(new Error('stream should not have returned rows')));
var stream = new QueryStream('SELECT * FROM generate_series(0, 100), pg_sleep(1)')
client.query(stream)
stream.on('data', () => done(new Error('stream should not have returned rows')))
setTimeout(() => {
stream.destroy();
stream.on('close', done);
}, 100);
});
stream.destroy()
stream.on('close', done)
}, 100)
})
it('emits an error when calling destroy with an error', function (done) {
var stream = new QueryStream('SELECT * FROM generate_series(0, 100), pg_sleep(1)');
client.query(stream);
stream.on('data', () => done(new Error('stream should not have returned rows')));
var stream = new QueryStream('SELECT * FROM generate_series(0, 100), pg_sleep(1)')
client.query(stream)
stream.on('data', () => done(new Error('stream should not have returned rows')))
setTimeout(() => {
stream.destroy(new Error('intentional error'));
stream.destroy(new Error('intentional error'))
stream.on('error', (err) => {
// make sure there's an error
assert(err);
assert.strictEqual(err.message, 'intentional error');
done();
});
}, 100);
});
assert(err)
assert.strictEqual(err.message, 'intentional error')
done()
})
}, 100)
})
it('can destroy stream while reading an error', function (done) {
var stream = new QueryStream('SELECT * from pg_sleep(1), basdfasdf;');
client.query(stream);
stream.on('data', () => done(new Error('stream should not have returned rows')));
var stream = new QueryStream('SELECT * from pg_sleep(1), basdfasdf;')
client.query(stream)
stream.on('data', () => done(new Error('stream should not have returned rows')))
stream.once('error', () => {
stream.destroy();
stream.destroy()
// wait a bit to let any other errors shake through
setTimeout(done, 100);
});
});
setTimeout(done, 100)
})
})
it('does not crash when destroying the stream immediately after calling read', function (done) {
var stream = new QueryStream('SELECT * from generate_series(0, 100), pg_sleep(1);');
client.query(stream);
stream.on('data', () => done(new Error('stream should not have returned rows')));
stream.destroy();
stream.on('close', done);
});
var stream = new QueryStream('SELECT * from generate_series(0, 100), pg_sleep(1);')
client.query(stream)
stream.on('data', () => done(new Error('stream should not have returned rows')))
stream.destroy()
stream.on('close', done)
})
it('does not crash when destroying the stream before its submitted', function (done) {
var stream = new QueryStream('SELECT * from generate_series(0, 100), pg_sleep(1);');
stream.on('data', () => done(new Error('stream should not have returned rows')));
stream.destroy();
stream.on('close', done);
});
});
var stream = new QueryStream('SELECT * from generate_series(0, 100), pg_sleep(1);')
stream.on('data', () => done(new Error('stream should not have returned rows')))
stream.destroy()
stream.on('close', done)
})
})
}

View File

@ -1,28 +1,28 @@
var assert = require('assert');
var concat = require('concat-stream');
var through = require('through');
var helper = require('./helper');
var assert = require('assert')
var concat = require('concat-stream')
var through = require('through')
var helper = require('./helper')
var QueryStream = require('../');
var QueryStream = require('../')
helper('concat', function (client) {
it('concats correctly', function (done) {
var stream = new QueryStream('SELECT * FROM generate_series(0, 200) num', []);
var query = client.query(stream);
var stream = new QueryStream('SELECT * FROM generate_series(0, 200) num', [])
var query = client.query(stream)
query
.pipe(
through(function (row) {
this.push(row.num);
this.push(row.num)
})
)
.pipe(
concat(function (result) {
var total = result.reduce(function (prev, cur) {
return prev + cur;
});
assert.equal(total, 20100);
return prev + cur
})
assert.equal(total, 20100)
})
);
stream.on('end', done);
});
});
)
stream.on('end', done)
})
})

View File

@ -1,26 +1,26 @@
var assert = require('assert');
var QueryStream = require('../');
var assert = require('assert')
var QueryStream = require('../')
describe('stream config options', () => {
// this is mostly for backwards compatability.
it('sets readable.highWaterMark based on batch size', () => {
var stream = new QueryStream('SELECT NOW()', [], {
batchSize: 88,
});
assert.equal(stream._readableState.highWaterMark, 88);
});
})
assert.equal(stream._readableState.highWaterMark, 88)
})
it('sets readable.highWaterMark based on highWaterMark config', () => {
var stream = new QueryStream('SELECT NOW()', [], {
highWaterMark: 88,
});
})
assert.equal(stream._readableState.highWaterMark, 88);
});
assert.equal(stream._readableState.highWaterMark, 88)
})
it('defaults to 100 for highWaterMark', () => {
var stream = new QueryStream('SELECT NOW()', []);
var stream = new QueryStream('SELECT NOW()', [])
assert.equal(stream._readableState.highWaterMark, 100);
});
});
assert.equal(stream._readableState.highWaterMark, 100)
})
})

View File

@ -1,22 +1,22 @@
const assert = require('assert');
const helper = require('./helper');
const QueryStream = require('../');
const assert = require('assert')
const helper = require('./helper')
const QueryStream = require('../')
helper('empty-query', function (client) {
it('handles empty query', function (done) {
const stream = new QueryStream('-- this is a comment', []);
const query = client.query(stream);
const stream = new QueryStream('-- this is a comment', [])
const query = client.query(stream)
query
.on('end', function () {
// nothing should happen for empty query
done();
done()
})
.on('data', function () {
// noop to kick off reading
});
});
})
})
it('continues to function after stream', function (done) {
client.query('SELECT NOW()', done);
});
});
client.query('SELECT NOW()', done)
})
})

View File

@ -1,24 +1,24 @@
var assert = require('assert');
var helper = require('./helper');
var assert = require('assert')
var helper = require('./helper')
var QueryStream = require('../');
var QueryStream = require('../')
helper('error', function (client) {
it('receives error on stream', function (done) {
var stream = new QueryStream('SELECT * FROM asdf num', []);
var query = client.query(stream);
var stream = new QueryStream('SELECT * FROM asdf num', [])
var query = client.query(stream)
query
.on('error', function (err) {
assert(err);
assert.equal(err.code, '42P01');
done();
assert(err)
assert.equal(err.code, '42P01')
done()
})
.on('data', function () {
// noop to kick of reading
});
});
})
})
it('continues to function after stream', function (done) {
client.query('SELECT NOW()', done);
});
});
client.query('SELECT NOW()', done)
})
})

View File

@ -1,35 +1,35 @@
var assert = require('assert');
var helper = require('./helper');
var QueryStream = require('../');
var assert = require('assert')
var helper = require('./helper')
var QueryStream = require('../')
helper('fast reader', function (client) {
it('works', function (done) {
var stream = new QueryStream('SELECT * FROM generate_series(0, 200) num', []);
var query = client.query(stream);
var result = [];
var stream = new QueryStream('SELECT * FROM generate_series(0, 200) num', [])
var query = client.query(stream)
var result = []
stream.on('readable', function () {
var res = stream.read();
var res = stream.read()
while (res) {
if (result.length !== 201) {
assert(res, 'should not return null on evented reader');
assert(res, 'should not return null on evented reader')
} else {
// a readable stream will emit a null datum when it finishes being readable
// https://nodejs.org/api/stream.html#stream_event_readable
assert.equal(res, null);
assert.equal(res, null)
}
if (res) {
result.push(res.num);
result.push(res.num)
}
res = stream.read();
res = stream.read()
}
});
})
stream.on('end', function () {
var total = result.reduce(function (prev, cur) {
return prev + cur;
});
assert.equal(total, 20100);
done();
});
assert.strictEqual(query.read(2), null);
});
});
return prev + cur
})
assert.equal(total, 20100)
done()
})
assert.strictEqual(query.read(2), null)
})
})

View File

@ -1,17 +1,17 @@
var pg = require('pg');
var pg = require('pg')
module.exports = function (name, cb) {
describe(name, function () {
var client = new pg.Client();
var client = new pg.Client()
before(function (done) {
client.connect(done);
});
client.connect(done)
})
cb(client);
cb(client)
after(function (done) {
client.end();
client.on('end', done);
});
});
};
client.end()
client.on('end', done)
})
})
}

View File

@ -1,17 +1,17 @@
var assert = require('assert');
var concat = require('concat-stream');
var assert = require('assert')
var concat = require('concat-stream')
var QueryStream = require('../');
var QueryStream = require('../')
require('./helper')('instant', function (client) {
it('instant', function (done) {
var query = new QueryStream('SELECT pg_sleep(1)', []);
var stream = client.query(query);
var query = new QueryStream('SELECT pg_sleep(1)', [])
var stream = client.query(query)
stream.pipe(
concat(function (res) {
assert.equal(res.length, 1);
done();
assert.equal(res.length, 1)
done()
})
);
});
});
)
})
})

View File

@ -1,32 +1,32 @@
var pg = require('pg');
var QueryStream = require('../');
var pg = require('pg')
var QueryStream = require('../')
describe('end semantics race condition', function () {
before(function (done) {
var client = new pg.Client();
client.connect();
client.on('drain', client.end.bind(client));
client.on('end', done);
client.query('create table IF NOT EXISTS p(id serial primary key)');
client.query('create table IF NOT EXISTS c(id int primary key references p)');
});
var client = new pg.Client()
client.connect()
client.on('drain', client.end.bind(client))
client.on('end', done)
client.query('create table IF NOT EXISTS p(id serial primary key)')
client.query('create table IF NOT EXISTS c(id int primary key references p)')
})
it('works', function (done) {
var client1 = new pg.Client();
client1.connect();
var client2 = new pg.Client();
client2.connect();
var client1 = new pg.Client()
client1.connect()
var client2 = new pg.Client()
client2.connect()
var qr = new QueryStream('INSERT INTO p DEFAULT VALUES RETURNING id');
client1.query(qr);
var id = null;
var qr = new QueryStream('INSERT INTO p DEFAULT VALUES RETURNING id')
client1.query(qr)
var id = null
qr.on('data', function (row) {
id = row.id;
});
id = row.id
})
qr.on('end', function () {
client2.query('INSERT INTO c(id) VALUES ($1)', [id], function (err, rows) {
client1.end();
client2.end();
done(err);
});
});
});
});
client1.end()
client2.end()
done(err)
})
})
})
})

View File

@ -1,38 +1,38 @@
var assert = require('assert');
var helper = require('./helper');
var QueryStream = require('../');
var assert = require('assert')
var helper = require('./helper')
var QueryStream = require('../')
helper('passing options', function (client) {
it('passes row mode array', function (done) {
var stream = new QueryStream('SELECT * FROM generate_series(0, 10) num', [], { rowMode: 'array' });
var query = client.query(stream);
var result = [];
var stream = new QueryStream('SELECT * FROM generate_series(0, 10) num', [], { rowMode: 'array' })
var query = client.query(stream)
var result = []
query.on('data', (datum) => {
result.push(datum);
});
result.push(datum)
})
query.on('end', () => {
const expected = new Array(11).fill(0).map((_, i) => [i]);
assert.deepEqual(result, expected);
done();
});
});
const expected = new Array(11).fill(0).map((_, i) => [i])
assert.deepEqual(result, expected)
done()
})
})
it('passes custom types', function (done) {
const types = {
getTypeParser: () => (string) => string,
};
var stream = new QueryStream('SELECT * FROM generate_series(0, 10) num', [], { types });
var query = client.query(stream);
var result = [];
}
var stream = new QueryStream('SELECT * FROM generate_series(0, 10) num', [], { types })
var query = client.query(stream)
var result = []
query.on('data', (datum) => {
result.push(datum);
});
result.push(datum)
})
query.on('end', () => {
const expected = new Array(11).fill(0).map((_, i) => ({
num: i.toString(),
}));
assert.deepEqual(result, expected);
done();
});
});
});
}))
assert.deepEqual(result, expected)
done()
})
})
})

View File

@ -1,23 +1,23 @@
var concat = require('concat-stream');
var tester = require('stream-tester');
var JSONStream = require('JSONStream');
var concat = require('concat-stream')
var tester = require('stream-tester')
var JSONStream = require('JSONStream')
var QueryStream = require('../');
var QueryStream = require('../')
require('./helper')('pauses', function (client) {
it('pauses', function (done) {
this.timeout(5000);
var stream = new QueryStream('SELECT * FROM generate_series(0, $1) num', [200], { batchSize: 2, highWaterMark: 2 });
var query = client.query(stream);
var pauser = tester.createPauseStream(0.1, 100);
this.timeout(5000)
var stream = new QueryStream('SELECT * FROM generate_series(0, $1) num', [200], { batchSize: 2, highWaterMark: 2 })
var query = client.query(stream)
var pauser = tester.createPauseStream(0.1, 100)
query
.pipe(JSONStream.stringify())
.pipe(pauser)
.pipe(
concat(function (json) {
JSON.parse(json);
done();
JSON.parse(json)
done()
})
);
});
});
)
})
})

View File

@ -1,31 +1,31 @@
var helper = require('./helper');
var QueryStream = require('../');
var concat = require('concat-stream');
var helper = require('./helper')
var QueryStream = require('../')
var concat = require('concat-stream')
var Transform = require('stream').Transform;
var Transform = require('stream').Transform
var mapper = new Transform({ objectMode: true });
var mapper = new Transform({ objectMode: true })
mapper._transform = function (obj, enc, cb) {
this.push(obj);
setTimeout(cb, 5);
};
this.push(obj)
setTimeout(cb, 5)
}
helper('slow reader', function (client) {
it('works', function (done) {
this.timeout(50000);
this.timeout(50000)
var stream = new QueryStream('SELECT * FROM generate_series(0, 201) num', [], {
highWaterMark: 100,
batchSize: 50,
});
})
stream.on('end', function () {
// console.log('stream end')
});
client.query(stream);
})
client.query(stream)
stream.pipe(mapper).pipe(
concat(function (res) {
done();
done()
})
);
});
});
)
})
})

View File

@ -1,25 +1,25 @@
var QueryStream = require('../');
var spec = require('stream-spec');
var assert = require('assert');
var QueryStream = require('../')
var spec = require('stream-spec')
var assert = require('assert')
require('./helper')('stream tester timestamp', function (client) {
it('should not warn about max listeners', function (done) {
var sql = "SELECT * FROM generate_series('1983-12-30 00:00'::timestamp, '2013-12-30 00:00', '1 years')";
var stream = new QueryStream(sql, []);
var ended = false;
var query = client.query(stream);
var sql = "SELECT * FROM generate_series('1983-12-30 00:00'::timestamp, '2013-12-30 00:00', '1 years')"
var stream = new QueryStream(sql, [])
var ended = false
var query = client.query(stream)
query.on('end', function () {
ended = true;
});
spec(query).readable().pausable({ strict: true }).validateOnExit();
ended = true
})
spec(query).readable().pausable({ strict: true }).validateOnExit()
var checkListeners = function () {
assert(stream.listeners('end').length < 10);
assert(stream.listeners('end').length < 10)
if (!ended) {
setImmediate(checkListeners);
setImmediate(checkListeners)
} else {
done();
done()
}
};
checkListeners();
});
});
}
checkListeners()
})
})

View File

@ -1,12 +1,12 @@
var spec = require('stream-spec');
var spec = require('stream-spec')
var QueryStream = require('../');
var QueryStream = require('../')
require('./helper')('stream tester', function (client) {
it('passes stream spec', function (done) {
var stream = new QueryStream('SELECT * FROM generate_series(0, 200) num', []);
var query = client.query(stream);
spec(query).readable().pausable({ strict: true }).validateOnExit();
stream.on('end', done);
});
});
var stream = new QueryStream('SELECT * FROM generate_series(0, 200) num', [])
var query = client.query(stream)
spec(query).readable().pausable({ strict: true }).validateOnExit()
stream.on('end', done)
})
})

View File

@ -1,69 +1,69 @@
const pg = require("./lib");
const pg = require('./lib')
const pool = new pg.Pool()
const params = {
text:
"select typname, typnamespace, typowner, typlen, typbyval, typcategory, typispreferred, typisdefined, typdelim, typrelid, typelem, typarray from pg_type where typtypmod = $1 and typisdefined = $2",
values: [-1, true]
};
'select typname, typnamespace, typowner, typlen, typbyval, typcategory, typispreferred, typisdefined, typdelim, typrelid, typelem, typarray from pg_type where typtypmod = $1 and typisdefined = $2',
values: [-1, true],
}
const insert = {
text: 'INSERT INTO foobar(name, age) VALUES ($1, $2)',
values: ['brian', 100]
values: ['brian', 100],
}
const seq = {
text: 'SELECT * FROM generate_series(1, 1000)'
text: 'SELECT * FROM generate_series(1, 1000)',
}
const exec = async (client, q) => {
const result = await client.query({
text: q.text,
values: q.values,
rowMode: "array"
});
};
rowMode: 'array',
})
}
const bench = async (client, q, time) => {
let start = Date.now();
let count = 0;
let start = Date.now()
let count = 0
while (true) {
await exec(client, q);
count++;
await exec(client, q)
count++
if (Date.now() - start > time) {
return count;
return count
}
}
};
}
const run = async () => {
const client = new pg.Client();
await client.connect();
const client = new pg.Client()
await client.connect()
await client.query('CREATE TEMP TABLE foobar(name TEXT, age NUMERIC)')
await bench(client, params, 1000);
console.log("warmup done");
const seconds = 5;
await bench(client, params, 1000)
console.log('warmup done')
const seconds = 5
let queries = await bench(client, params, seconds * 1000);
let queries = await bench(client, params, seconds * 1000)
console.log('')
console.log("little queries:", queries);
console.log("qps", queries / seconds);
console.log("on my laptop best so far seen 733 qps")
console.log('little queries:', queries)
console.log('qps', queries / seconds)
console.log('on my laptop best so far seen 733 qps')
console.log('')
queries = await bench(client, seq, seconds * 1000);
console.log("sequence queries:", queries);
console.log("qps", queries / seconds);
console.log("on my laptop best so far seen 1309 qps")
queries = await bench(client, seq, seconds * 1000)
console.log('sequence queries:', queries)
console.log('qps', queries / seconds)
console.log('on my laptop best so far seen 1309 qps')
console.log('')
queries = await bench(client, insert, seconds * 1000);
console.log("insert queries:", queries);
console.log("qps", queries / seconds);
console.log("on my laptop best so far seen 5799 qps")
queries = await bench(client, insert, seconds * 1000)
console.log('insert queries:', queries)
console.log('qps', queries / seconds)
console.log('on my laptop best so far seen 5799 qps')
console.log()
await client.end();
await client.end();
};
await client.end()
await client.end()
}
run().catch(e => console.error(e) || process.exit(-1));
run().catch((e) => console.error(e) || process.exit(-1))

View File

@ -37,7 +37,7 @@ var Client = function (config) {
configurable: true,
enumerable: false,
writable: true,
value: this.connectionParameters.password
value: this.connectionParameters.password,
})
this.replication = this.connectionParameters.replication
@ -52,13 +52,15 @@ var Client = function (config) {
this._connectionError = false
this._queryable = true
this.connection = c.connection || new Connection({
stream: c.stream,
ssl: this.connectionParameters.ssl,
keepAlive: c.keepAlive || false,
keepAliveInitialDelayMillis: c.keepAliveInitialDelayMillis || 0,
encoding: this.connectionParameters.client_encoding || 'utf8'
})
this.connection =
c.connection ||
new Connection({
stream: c.stream,
ssl: this.connectionParameters.ssl,
keepAlive: c.keepAlive || false,
keepAliveInitialDelayMillis: c.keepAliveInitialDelayMillis || 0,
encoding: this.connectionParameters.client_encoding || 'utf8',
})
this.queryQueue = []
this.binary = c.binary || defaults.binary
this.processID = null
@ -127,9 +129,10 @@ Client.prototype._connect = function (callback) {
function checkPgPass(cb) {
return function (msg) {
if (typeof self.password === 'function') {
self._Promise.resolve()
self._Promise
.resolve()
.then(() => self.password())
.then(pass => {
.then((pass) => {
if (pass !== undefined) {
if (typeof pass !== 'string') {
con.emit('error', new TypeError('Password must be a string'))
@ -140,7 +143,8 @@ Client.prototype._connect = function (callback) {
self.connectionParameters.password = self.password = null
}
cb(msg)
}).catch(err => {
})
.catch((err) => {
con.emit('error', err)
})
} else if (self.password !== null) {
@ -157,22 +161,31 @@ Client.prototype._connect = function (callback) {
}
// password request handling
con.on('authenticationCleartextPassword', checkPgPass(function () {
con.password(self.password)
}))
con.on(
'authenticationCleartextPassword',
checkPgPass(function () {
con.password(self.password)
})
)
// password request handling
con.on('authenticationMD5Password', checkPgPass(function (msg) {
con.password(utils.postgresMd5PasswordHash(self.user, self.password, msg.salt))
}))
con.on(
'authenticationMD5Password',
checkPgPass(function (msg) {
con.password(utils.postgresMd5PasswordHash(self.user, self.password, msg.salt))
})
)
// password request handling (SASL)
var saslSession
con.on('authenticationSASL', checkPgPass(function (msg) {
saslSession = sasl.startSession(msg.mechanisms)
con.on(
'authenticationSASL',
checkPgPass(function (msg) {
saslSession = sasl.startSession(msg.mechanisms)
con.sendSASLInitialResponseMessage(saslSession.mechanism, saslSession.response)
}))
con.sendSASLInitialResponseMessage(saslSession.mechanism, saslSession.response)
})
)
// password request handling (SASL)
con.on('authenticationSASLContinue', function (msg) {
@ -259,9 +272,7 @@ Client.prototype._connect = function (callback) {
})
con.once('end', () => {
const error = this._ending
? new Error('Connection terminated')
: new Error('Connection terminated unexpectedly')
const error = this._ending ? new Error('Connection terminated') : new Error('Connection terminated unexpectedly')
clearTimeout(connectionTimeoutHandle)
this._errorAllQueries(error)
@ -367,7 +378,7 @@ Client.prototype.getStartupConf = function () {
var data = {
user: params.user,
database: params.database
database: params.database,
}
var appName = params.application_name || params.fallback_application_name
@ -422,11 +433,11 @@ Client.prototype.escapeIdentifier = function (str) {
// Ported from PostgreSQL 9.2.4 source code in src/interfaces/libpq/fe-exec.c
Client.prototype.escapeLiteral = function (str) {
var hasBackslash = false
var escaped = '\''
var escaped = "'"
for (var i = 0; i < str.length; i++) {
var c = str[i]
if (c === '\'') {
if (c === "'") {
escaped += c + c
} else if (c === '\\') {
escaped += c + c
@ -436,7 +447,7 @@ Client.prototype.escapeLiteral = function (str) {
}
}
escaped += '\''
escaped += "'"
if (hasBackslash === true) {
escaped = ' E' + escaped
@ -488,7 +499,7 @@ Client.prototype.query = function (config, values, callback) {
query = new Query(config, values, callback)
if (!query.callback) {
result = new this._Promise((resolve, reject) => {
query.callback = (err, res) => err ? reject(err) : resolve(res)
query.callback = (err, res) => (err ? reject(err) : resolve(res))
})
}
}
@ -507,7 +518,7 @@ Client.prototype.query = function (config, values, callback) {
// we already returned an error,
// just do nothing if query completes
query.callback = () => { }
query.callback = () => {}
// Remove from queue
var index = this.queryQueue.indexOf(query)

View File

@ -80,14 +80,18 @@ Connection.prototype.connect = function (port, host) {
case 'N': // Server does not support SSL connections
self.stream.end()
return self.emit('error', new Error('The server does not support SSL connections'))
default: // Any other response byte, including 'E' (ErrorResponse) indicating a server error
default:
// Any other response byte, including 'E' (ErrorResponse) indicating a server error
self.stream.end()
return self.emit('error', new Error('There was an error establishing an SSL connection'))
}
var tls = require('tls')
const options = Object.assign({
socket: self.stream
}, self.ssl)
const options = Object.assign(
{
socket: self.stream,
},
self.ssl
)
if (net.isIP(host) === 0) {
options.servername = host
}

View File

@ -22,9 +22,7 @@ var val = function (key, config, envVar) {
envVar = process.env[envVar]
}
return config[key] ||
envVar ||
defaults[key]
return config[key] || envVar || defaults[key]
}
var useSsl = function () {
@ -66,7 +64,7 @@ var ConnectionParameters = function (config) {
configurable: true,
enumerable: false,
writable: true,
value: val('password', config)
value: val('password', config),
})
this.binary = val('binary', config)
@ -74,7 +72,7 @@ var ConnectionParameters = function (config) {
this.client_encoding = val('client_encoding', config)
this.replication = val('replication', config)
// a domain socket begins with '/'
this.isDomainSocket = (!(this.host || '').indexOf('/'))
this.isDomainSocket = !(this.host || '').indexOf('/')
this.application_name = val('application_name', config, 'PGAPPNAME')
this.fallback_application_name = val('fallback_application_name', config, false)

View File

@ -35,7 +35,7 @@ var Connection = function (config) {
this._emitMessage = false
this._reader = new Reader({
headerSize: 1,
lengthPadding: -4
lengthPadding: -4,
})
var self = this
this.on('newListener', function (eventName) {
@ -88,14 +88,18 @@ Connection.prototype.connect = function (port, host) {
case 'N': // Server does not support SSL connections
self.stream.end()
return self.emit('error', new Error('The server does not support SSL connections'))
default: // Any other response byte, including 'E' (ErrorResponse) indicating a server error
default:
// Any other response byte, including 'E' (ErrorResponse) indicating a server error
self.stream.end()
return self.emit('error', new Error('There was an error establishing an SSL connection'))
}
var tls = require('tls')
const options = Object.assign({
socket: self.stream
}, self.ssl)
const options = Object.assign(
{
socket: self.stream,
},
self.ssl
)
if (net.isIP(host) === 0) {
options.servername = host
}
@ -127,23 +131,16 @@ Connection.prototype.attachListeners = function (stream) {
}
Connection.prototype.requestSsl = function () {
var bodyBuffer = this.writer
.addInt16(0x04D2)
.addInt16(0x162F).flush()
var bodyBuffer = this.writer.addInt16(0x04d2).addInt16(0x162f).flush()
var length = bodyBuffer.length + 4
var buffer = new Writer()
.addInt32(length)
.add(bodyBuffer)
.join()
var buffer = new Writer().addInt32(length).add(bodyBuffer).join()
this.stream.write(buffer)
}
Connection.prototype.startup = function (config) {
var writer = this.writer
.addInt16(3)
.addInt16(0)
var writer = this.writer.addInt16(3).addInt16(0)
Object.keys(config).forEach(function (key) {
var val = config[key]
@ -157,27 +154,16 @@ Connection.prototype.startup = function (config) {
var length = bodyBuffer.length + 4
var buffer = new Writer()
.addInt32(length)
.add(bodyBuffer)
.join()
var buffer = new Writer().addInt32(length).add(bodyBuffer).join()
this.stream.write(buffer)
}
Connection.prototype.cancel = function (processID, secretKey) {
var bodyBuffer = this.writer
.addInt16(1234)
.addInt16(5678)
.addInt32(processID)
.addInt32(secretKey)
.flush()
var bodyBuffer = this.writer.addInt16(1234).addInt16(5678).addInt32(processID).addInt32(secretKey).flush()
var length = bodyBuffer.length + 4
var buffer = new Writer()
.addInt32(length)
.add(bodyBuffer)
.join()
var buffer = new Writer().addInt32(length).add(bodyBuffer).join()
this.stream.write(buffer)
}
@ -188,18 +174,14 @@ Connection.prototype.password = function (password) {
Connection.prototype.sendSASLInitialResponseMessage = function (mechanism, initialResponse) {
// 0x70 = 'p'
this.writer
.addCString(mechanism)
.addInt32(Buffer.byteLength(initialResponse))
.addString(initialResponse)
this.writer.addCString(mechanism).addInt32(Buffer.byteLength(initialResponse)).addString(initialResponse)
this._send(0x70)
}
Connection.prototype.sendSCRAMClientFinalMessage = function (additionalData) {
// 0x70 = 'p'
this.writer
.addString(additionalData)
this.writer.addString(additionalData)
this._send(0x70)
}
@ -263,13 +245,17 @@ Connection.prototype.bind = function (config, more) {
var values = config.values || []
var len = values.length
var useBinary = false
for (var j = 0; j < len; j++) { useBinary |= values[j] instanceof Buffer }
var buffer = this.writer
.addCString(config.portal)
.addCString(config.statement)
if (!useBinary) { buffer.addInt16(0) } else {
for (var j = 0; j < len; j++) {
useBinary |= values[j] instanceof Buffer
}
var buffer = this.writer.addCString(config.portal).addCString(config.statement)
if (!useBinary) {
buffer.addInt16(0)
} else {
buffer.addInt16(len)
for (j = 0; j < len; j++) { buffer.addInt16(values[j] instanceof Buffer) }
for (j = 0; j < len; j++) {
buffer.addInt16(values[j] instanceof Buffer)
}
}
buffer.addInt16(len)
for (var i = 0; i < len; i++) {
@ -301,9 +287,7 @@ Connection.prototype.execute = function (config, more) {
config = config || {}
config.portal = config.portal || ''
config.rows = config.rows || ''
this.writer
.addCString(config.portal)
.addInt32(config.rows)
this.writer.addCString(config.portal).addInt32(config.rows)
// 0x45 = 'E'
this._send(0x45, more)

View File

@ -70,7 +70,7 @@ module.exports = {
keepalives: 1,
keepalives_idle: 0
keepalives_idle: 0,
}
var pgTypes = require('pg-types')

View File

@ -14,7 +14,7 @@ var Pool = require('pg-pool')
const poolFactory = (Client) => {
return class BoundPool extends Pool {
constructor (options) {
constructor(options) {
super(options, Client)
}
}
@ -54,10 +54,10 @@ if (typeof process.env.NODE_PG_FORCE_NATIVE !== 'undefined') {
// overwrite module.exports.native so that getter is never called again
Object.defineProperty(module.exports, 'native', {
value: native
value: native,
})
return native
}
},
})
}

View File

@ -22,7 +22,7 @@ assert(semver.gte(Native.version, pkg.minNativeVersion), msg)
var NativeQuery = require('./query')
var Client = module.exports = function (config) {
var Client = (module.exports = function (config) {
EventEmitter.call(this)
config = config || {}
@ -30,7 +30,7 @@ var Client = module.exports = function (config) {
this._types = new TypeOverrides(config.types)
this.native = new Native({
types: this._types
types: this._types,
})
this._queryQueue = []
@ -41,7 +41,7 @@ var Client = module.exports = function (config) {
// keep these on the object for legacy reasons
// for the time being. TODO: deprecate all this jazz
var cp = this.connectionParameters = new ConnectionParameters(config)
var cp = (this.connectionParameters = new ConnectionParameters(config))
this.user = cp.user
// "hiding" the password so it doesn't show up in stack traces
@ -50,7 +50,7 @@ var Client = module.exports = function (config) {
configurable: true,
enumerable: false,
writable: true,
value: cp.password
value: cp.password,
})
this.database = cp.database
this.host = cp.host
@ -58,7 +58,7 @@ var Client = module.exports = function (config) {
// a hash to hold named queries
this.namedQueries = {}
}
})
Client.Query = NativeQuery
@ -115,7 +115,7 @@ Client.prototype._connect = function (cb) {
self.native.on('notification', function (msg) {
self.emit('notification', {
channel: msg.relname,
payload: msg.extra
payload: msg.extra,
})
})
@ -180,7 +180,7 @@ Client.prototype.query = function (config, values, callback) {
resolveOut = resolve
rejectOut = reject
})
query.callback = (err, res) => err ? rejectOut(err) : resolveOut(res)
query.callback = (err, res) => (err ? rejectOut(err) : resolveOut(res))
}
}
@ -248,7 +248,7 @@ Client.prototype.end = function (cb) {
var result
if (!cb) {
result = new this._Promise(function (resolve, reject) {
cb = (err) => err ? reject(err) : resolve()
cb = (err) => (err ? reject(err) : resolve())
})
}
this.native.end(function () {

View File

@ -11,7 +11,7 @@ var EventEmitter = require('events').EventEmitter
var util = require('util')
var utils = require('../utils')
var NativeQuery = module.exports = function (config, values, callback) {
var NativeQuery = (module.exports = function (config, values, callback) {
EventEmitter.call(this)
config = utils.normalizeQueryConfig(config, values, callback)
this.text = config.text
@ -27,27 +27,30 @@ var NativeQuery = module.exports = function (config, values, callback) {
// this has almost no meaning because libpq
// reads all rows into memory befor returning any
this._emitRowEvents = false
this.on('newListener', function (event) {
if (event === 'row') this._emitRowEvents = true
}.bind(this))
}
this.on(
'newListener',
function (event) {
if (event === 'row') this._emitRowEvents = true
}.bind(this)
)
})
util.inherits(NativeQuery, EventEmitter)
var errorFieldMap = {
/* eslint-disable quote-props */
'sqlState': 'code',
'statementPosition': 'position',
'messagePrimary': 'message',
'context': 'where',
'schemaName': 'schema',
'tableName': 'table',
'columnName': 'column',
'dataTypeName': 'dataType',
'constraintName': 'constraint',
'sourceFile': 'file',
'sourceLine': 'line',
'sourceFunction': 'routine'
sqlState: 'code',
statementPosition: 'position',
messagePrimary: 'message',
context: 'where',
schemaName: 'schema',
tableName: 'table',
columnName: 'column',
dataTypeName: 'dataType',
constraintName: 'constraint',
sourceFile: 'file',
sourceLine: 'line',
sourceFunction: 'routine',
}
NativeQuery.prototype.handleError = function (err) {
@ -77,10 +80,12 @@ NativeQuery.prototype.catch = function (callback) {
NativeQuery.prototype._getPromise = function () {
if (this._promise) return this._promise
this._promise = new Promise(function (resolve, reject) {
this._once('end', resolve)
this._once('error', reject)
}.bind(this))
this._promise = new Promise(
function (resolve, reject) {
this._once('end', resolve)
this._once('error', reject)
}.bind(this)
)
return this._promise
}
@ -105,7 +110,7 @@ NativeQuery.prototype.submit = function (client) {
if (self._emitRowEvents) {
if (results.length > 1) {
rows.forEach((rowOfRows, i) => {
rowOfRows.forEach(row => {
rowOfRows.forEach((row) => {
self.emit('row', row, results[i])
})
})

View File

@ -42,14 +42,22 @@ class Query extends EventEmitter {
requiresPreparation() {
// named queries must always be prepared
if (this.name) { return true }
if (this.name) {
return true
}
// always prepare if there are max number of rows expected per
// portal execution
if (this.rows) { return true }
if (this.rows) {
return true
}
// don't prepare empty text queries
if (!this.text) { return false }
if (!this.text) {
return false
}
// prepare if there are values
if (!this.values) { return false }
if (!this.values) {
return false
}
return this.values.length > 0
}
@ -168,10 +176,13 @@ class Query extends EventEmitter {
}
_getRows(connection, rows) {
connection.execute({
portal: this.portal,
rows: rows
}, true)
connection.execute(
{
portal: this.portal,
rows: rows,
},
true
)
connection.flush()
}
@ -181,11 +192,14 @@ class Query extends EventEmitter {
this.isPreparedStatement = true
// TODO refactor this poor encapsulation
if (!this.hasBeenParsed(connection)) {
connection.parse({
text: this.text,
name: this.name,
types: this.types
}, true)
connection.parse(
{
text: this.text,
name: this.name,
types: this.types,
},
true
)
}
if (this.values) {
@ -198,17 +212,23 @@ class Query extends EventEmitter {
}
// http://developer.postgresql.org/pgdocs/postgres/protocol-flow.html#PROTOCOL-FLOW-EXT-QUERY
connection.bind({
portal: this.portal,
statement: this.name,
values: this.values,
binary: this.binary
}, true)
connection.bind(
{
portal: this.portal,
statement: this.name,
values: this.values,
binary: this.binary,
},
true
)
connection.describe({
type: 'P',
name: this.portal || ''
}, true)
connection.describe(
{
type: 'P',
name: this.portal || '',
},
true
)
this._getRows(connection, this.rows)
}

View File

@ -1,7 +1,7 @@
'use strict'
const crypto = require('crypto')
function startSession (mechanisms) {
function startSession(mechanisms) {
if (mechanisms.indexOf('SCRAM-SHA-256') === -1) {
throw new Error('SASL: Only mechanism SCRAM-SHA-256 is currently supported')
}
@ -12,11 +12,11 @@ function startSession (mechanisms) {
mechanism: 'SCRAM-SHA-256',
clientNonce,
response: 'n,,n=*,r=' + clientNonce,
message: 'SASLInitialResponse'
message: 'SASLInitialResponse',
}
}
function continueSession (session, password, serverData) {
function continueSession(session, password, serverData) {
if (session.message !== 'SASLInitialResponse') {
throw new Error('SASL: Last message was not SASLInitialResponse')
}
@ -53,42 +53,46 @@ function continueSession (session, password, serverData) {
session.response = clientFinalMessageWithoutProof + ',p=' + clientProof
}
function finalizeSession (session, serverData) {
function finalizeSession(session, serverData) {
if (session.message !== 'SASLResponse') {
throw new Error('SASL: Last message was not SASLResponse')
}
var serverSignature
String(serverData).split(',').forEach(function (part) {
switch (part[0]) {
case 'v':
serverSignature = part.substr(2)
break
}
})
String(serverData)
.split(',')
.forEach(function (part) {
switch (part[0]) {
case 'v':
serverSignature = part.substr(2)
break
}
})
if (serverSignature !== session.serverSignature) {
throw new Error('SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature does not match')
}
}
function extractVariablesFromFirstServerMessage (data) {
function extractVariablesFromFirstServerMessage(data) {
var nonce, salt, iteration
String(data).split(',').forEach(function (part) {
switch (part[0]) {
case 'r':
nonce = part.substr(2)
break
case 's':
salt = part.substr(2)
break
case 'i':
iteration = parseInt(part.substr(2), 10)
break
}
})
String(data)
.split(',')
.forEach(function (part) {
switch (part[0]) {
case 'r':
nonce = part.substr(2)
break
case 's':
salt = part.substr(2)
break
case 'i':
iteration = parseInt(part.substr(2), 10)
break
}
})
if (!nonce) {
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: nonce missing')
@ -105,11 +109,11 @@ function extractVariablesFromFirstServerMessage (data) {
return {
nonce,
salt,
iteration
iteration,
}
}
function xorBuffers (a, b) {
function xorBuffers(a, b) {
if (!Buffer.isBuffer(a)) a = Buffer.from(a)
if (!Buffer.isBuffer(b)) b = Buffer.from(b)
var res = []
@ -125,11 +129,11 @@ function xorBuffers (a, b) {
return Buffer.from(res)
}
function createHMAC (key, msg) {
function createHMAC(key, msg) {
return crypto.createHmac('sha256', key).update(msg).digest()
}
function Hi (password, saltBytes, iterations) {
function Hi(password, saltBytes, iterations) {
var ui1 = createHMAC(password, Buffer.concat([saltBytes, Buffer.from([0, 0, 0, 1])]))
var ui = ui1
for (var i = 0; i < iterations - 1; i++) {
@ -143,5 +147,5 @@ function Hi (password, saltBytes, iterations) {
module.exports = {
startSession,
continueSession,
finalizeSession
finalizeSession,
}

View File

@ -9,7 +9,7 @@
var types = require('pg-types')
function TypeOverrides (userTypes) {
function TypeOverrides(userTypes) {
this._types = userTypes || types
this.text = {}
this.binary = {}
@ -17,9 +17,12 @@ function TypeOverrides (userTypes) {
TypeOverrides.prototype.getOverrides = function (format) {
switch (format) {
case 'text': return this.text
case 'binary': return this.binary
default: return {}
case 'text':
return this.text
case 'binary':
return this.binary
default:
return {}
}
}

View File

@ -11,10 +11,8 @@ const crypto = require('crypto')
const defaults = require('./defaults')
function escapeElement (elementRepresentation) {
var escaped = elementRepresentation
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
function escapeElement(elementRepresentation) {
var escaped = elementRepresentation.replace(/\\/g, '\\\\').replace(/"/g, '\\"')
return '"' + escaped + '"'
}
@ -22,7 +20,7 @@ function escapeElement (elementRepresentation) {
// convert a JS array to a postgres array literal
// uses comma separator so won't work for types like box that use
// a different array separator.
function arrayString (val) {
function arrayString(val) {
var result = '{'
for (var i = 0; i < val.length; i++) {
if (i > 0) {
@ -76,7 +74,7 @@ var prepareValue = function (val, seen) {
return val.toString()
}
function prepareObject (val, seen) {
function prepareObject(val, seen) {
if (val && typeof val.toPostgres === 'function') {
seen = seen || []
if (seen.indexOf(val) !== -1) {
@ -89,48 +87,66 @@ function prepareObject (val, seen) {
return JSON.stringify(val)
}
function pad (number, digits) {
function pad(number, digits) {
number = '' + number
while (number.length < digits) { number = '0' + number }
while (number.length < digits) {
number = '0' + number
}
return number
}
function dateToString (date) {
function dateToString(date) {
var offset = -date.getTimezoneOffset()
var year = date.getFullYear()
var isBCYear = year < 1
if (isBCYear) year = Math.abs(year) + 1 // negative years are 1 off their BC representation
var ret = pad(year, 4) + '-' +
pad(date.getMonth() + 1, 2) + '-' +
pad(date.getDate(), 2) + 'T' +
pad(date.getHours(), 2) + ':' +
pad(date.getMinutes(), 2) + ':' +
pad(date.getSeconds(), 2) + '.' +
var ret =
pad(year, 4) +
'-' +
pad(date.getMonth() + 1, 2) +
'-' +
pad(date.getDate(), 2) +
'T' +
pad(date.getHours(), 2) +
':' +
pad(date.getMinutes(), 2) +
':' +
pad(date.getSeconds(), 2) +
'.' +
pad(date.getMilliseconds(), 3)
if (offset < 0) {
ret += '-'
offset *= -1
} else { ret += '+' }
} else {
ret += '+'
}
ret += pad(Math.floor(offset / 60), 2) + ':' + pad(offset % 60, 2)
if (isBCYear) ret += ' BC'
return ret
}
function dateToStringUTC (date) {
function dateToStringUTC(date) {
var year = date.getUTCFullYear()
var isBCYear = year < 1
if (isBCYear) year = Math.abs(year) + 1 // negative years are 1 off their BC representation
var ret = pad(year, 4) + '-' +
pad(date.getUTCMonth() + 1, 2) + '-' +
pad(date.getUTCDate(), 2) + 'T' +
pad(date.getUTCHours(), 2) + ':' +
pad(date.getUTCMinutes(), 2) + ':' +
pad(date.getUTCSeconds(), 2) + '.' +
var ret =
pad(year, 4) +
'-' +
pad(date.getUTCMonth() + 1, 2) +
'-' +
pad(date.getUTCDate(), 2) +
'T' +
pad(date.getUTCHours(), 2) +
':' +
pad(date.getUTCMinutes(), 2) +
':' +
pad(date.getUTCSeconds(), 2) +
'.' +
pad(date.getUTCMilliseconds(), 3)
ret += '+00:00'
@ -138,9 +154,9 @@ function dateToStringUTC (date) {
return ret
}
function normalizeQueryConfig (config, values, callback) {
function normalizeQueryConfig(config, values, callback) {
// can take in strings or config objects
config = (typeof (config) === 'string') ? { text: config } : config
config = typeof config === 'string' ? { text: config } : config
if (values) {
if (typeof values === 'function') {
config.callback = values
@ -166,12 +182,12 @@ const postgresMd5PasswordHash = function (user, password, salt) {
}
module.exports = {
prepareValue: function prepareValueWrapper (value) {
prepareValue: function prepareValueWrapper(value) {
// this ensures that extra arguments do not get passed into prepareValue
// by accident, eg: from calling values.map(utils.prepareValue)
return prepareValue(value)
},
normalizeQueryConfig,
postgresMd5PasswordHash,
md5
md5,
}

View File

@ -3,32 +3,32 @@ var args = require(__dirname + '/../test/cli')
var pg = require(__dirname + '/../lib')
var people = [
{name: 'Aaron', age: 10},
{name: 'Brian', age: 20},
{name: 'Chris', age: 30},
{name: 'David', age: 40},
{name: 'Elvis', age: 50},
{name: 'Frank', age: 60},
{name: 'Grace', age: 70},
{name: 'Haley', age: 80},
{name: 'Irma', age: 90},
{name: 'Jenny', age: 100},
{name: 'Kevin', age: 110},
{name: 'Larry', age: 120},
{name: 'Michelle', age: 130},
{name: 'Nancy', age: 140},
{name: 'Olivia', age: 150},
{name: 'Peter', age: 160},
{name: 'Quinn', age: 170},
{name: 'Ronda', age: 180},
{name: 'Shelley', age: 190},
{name: 'Tobias', age: 200},
{name: 'Uma', age: 210},
{name: 'Veena', age: 220},
{name: 'Wanda', age: 230},
{name: 'Xavier', age: 240},
{name: 'Yoyo', age: 250},
{name: 'Zanzabar', age: 260}
{ name: 'Aaron', age: 10 },
{ name: 'Brian', age: 20 },
{ name: 'Chris', age: 30 },
{ name: 'David', age: 40 },
{ name: 'Elvis', age: 50 },
{ name: 'Frank', age: 60 },
{ name: 'Grace', age: 70 },
{ name: 'Haley', age: 80 },
{ name: 'Irma', age: 90 },
{ name: 'Jenny', age: 100 },
{ name: 'Kevin', age: 110 },
{ name: 'Larry', age: 120 },
{ name: 'Michelle', age: 130 },
{ name: 'Nancy', age: 140 },
{ name: 'Olivia', age: 150 },
{ name: 'Peter', age: 160 },
{ name: 'Quinn', age: 170 },
{ name: 'Ronda', age: 180 },
{ name: 'Shelley', age: 190 },
{ name: 'Tobias', age: 200 },
{ name: 'Uma', age: 210 },
{ name: 'Veena', age: 220 },
{ name: 'Wanda', age: 230 },
{ name: 'Xavier', age: 240 },
{ name: 'Yoyo', age: 250 },
{ name: 'Zanzabar', age: 260 },
]
var con = new pg.Client({
@ -36,7 +36,7 @@ var con = new pg.Client({
port: args.port,
user: args.user,
password: args.password,
database: args.database
database: args.database,
})
con.connect((err) => {
@ -45,8 +45,7 @@ con.connect((err) => {
}
con.query(
'DROP TABLE IF EXISTS person;'
+ ' CREATE TABLE person (id serial, name varchar(10), age integer)',
'DROP TABLE IF EXISTS person;' + ' CREATE TABLE person (id serial, name varchar(10), age integer)',
(err) => {
if (err) {
throw err
@ -56,10 +55,8 @@ con.connect((err) => {
console.log('Filling it with people')
con.query(
'INSERT INTO person (name, age) VALUES'
+ people
.map((person) => ` ('${person.name}', ${person.age})`)
.join(','),
'INSERT INTO person (name, age) VALUES' +
people.map((person) => ` ('${person.name}', ${person.age})`).join(','),
(err, result) => {
if (err) {
throw err
@ -67,6 +64,8 @@ con.connect((err) => {
console.log(`Inserted ${result.rowCount} people`)
con.end()
})
})
}
)
}
)
})

View File

@ -2,22 +2,17 @@
var pg = require(__dirname + '/../lib')
var args = require(__dirname + '/../test/cli')
var queries = [
'select CURRENT_TIMESTAMP',
"select interval '1 day' + interval '1 hour'",
"select TIMESTAMP 'today'"]
var queries = ['select CURRENT_TIMESTAMP', "select interval '1 day' + interval '1 hour'", "select TIMESTAMP 'today'"]
queries.forEach(function (query) {
var client = new pg.Client({
user: args.user,
database: args.database,
password: args.password
password: args.password,
})
client.connect()
client
.query(query)
.on('row', function (row) {
console.log(row)
client.end()
})
client.query(query).on('row', function (row) {
console.log(row)
client.end()
})
})

View File

@ -1,7 +1,10 @@
'use strict'
var helper = require(__dirname + '/../test/integration/test-helper')
var pg = helper.pg
pg.connect(helper.config, assert.success(function (client) {
var query = client.query('select oid, typname from pg_type where typtype = \'b\' order by oid')
query.on('row', console.log)
}))
pg.connect(
helper.config,
assert.success(function (client) {
var query = client.query("select oid, typname from pg_type where typtype = 'b' order by oid")
query.on('row', console.log)
})
)

View File

@ -10,7 +10,7 @@ p.add = function (buffer, front) {
}
p.addInt16 = function (val, front) {
return this.add(Buffer.from([(val >>> 8), (val >>> 0)]), front)
return this.add(Buffer.from([val >>> 8, val >>> 0]), front)
}
p.getByteLength = function (initial) {
@ -20,12 +20,10 @@ p.getByteLength = function (initial) {
}
p.addInt32 = function (val, first) {
return this.add(Buffer.from([
(val >>> 24 & 0xFF),
(val >>> 16 & 0xFF),
(val >>> 8 & 0xFF),
(val >>> 0 & 0xFF)
]), first)
return this.add(
Buffer.from([(val >>> 24) & 0xff, (val >>> 16) & 0xff, (val >>> 8) & 0xff, (val >>> 0) & 0xff]),
first
)
}
p.addCString = function (val, front) {

View File

@ -9,13 +9,15 @@ suite.test('null and undefined are both inserted as NULL', function (done) {
pool.connect(
assert.calls(function (err, client, release) {
assert(!err)
client.query(
'CREATE TEMP TABLE my_nulls(a varchar(1), b varchar(1), c integer, d integer, e date, f date)'
)
client.query(
'INSERT INTO my_nulls(a,b,c,d,e,f) VALUES ($1,$2,$3,$4,$5,$6)',
[null, undefined, null, undefined, null, undefined]
)
client.query('CREATE TEMP TABLE my_nulls(a varchar(1), b varchar(1), c integer, d integer, e date, f date)')
client.query('INSERT INTO my_nulls(a,b,c,d,e,f) VALUES ($1,$2,$3,$4,$5,$6)', [
null,
undefined,
null,
undefined,
null,
undefined,
])
client.query(
'SELECT * FROM my_nulls',
assert.calls(function (err, result) {
@ -36,7 +38,7 @@ suite.test('null and undefined are both inserted as NULL', function (done) {
)
})
suite.test('pool callback behavior', done => {
suite.test('pool callback behavior', (done) => {
// test weird callback behavior with node-pool
const pool = new pg.Pool()
pool.connect(function (err) {
@ -50,51 +52,63 @@ suite.test('pool callback behavior', done => {
suite.test('query timeout', (cb) => {
const pool = new pg.Pool({ query_timeout: 1000 })
pool.connect().then((client) => {
client.query('SELECT pg_sleep(2)', assert.calls(function (err, result) {
assert(err)
assert(err.message === 'Query read timeout')
client.release()
pool.end(cb)
}))
client.query(
'SELECT pg_sleep(2)',
assert.calls(function (err, result) {
assert(err)
assert(err.message === 'Query read timeout')
client.release()
pool.end(cb)
})
)
})
})
suite.test('query recover from timeout', (cb) => {
const pool = new pg.Pool({ query_timeout: 1000 })
pool.connect().then((client) => {
client.query('SELECT pg_sleep(20)', assert.calls(function (err, result) {
assert(err)
assert(err.message === 'Query read timeout')
client.release(err)
pool.connect().then((client) => {
client.query('SELECT 1', assert.calls(function (err, result) {
assert(!err)
client.release(err)
pool.end(cb)
}))
client.query(
'SELECT pg_sleep(20)',
assert.calls(function (err, result) {
assert(err)
assert(err.message === 'Query read timeout')
client.release(err)
pool.connect().then((client) => {
client.query(
'SELECT 1',
assert.calls(function (err, result) {
assert(!err)
client.release(err)
pool.end(cb)
})
)
})
})
}))
)
})
})
suite.test('query no timeout', (cb) => {
const pool = new pg.Pool({ query_timeout: 10000 })
pool.connect().then((client) => {
client.query('SELECT pg_sleep(1)', assert.calls(function (err, result) {
assert(!err)
client.release()
pool.end(cb)
}))
client.query(
'SELECT pg_sleep(1)',
assert.calls(function (err, result) {
assert(!err)
client.release()
pool.end(cb)
})
)
})
})
suite.test('callback API', done => {
suite.test('callback API', (done) => {
const client = new helper.Client()
client.query('CREATE TEMP TABLE peep(name text)')
client.query('INSERT INTO peep(name) VALUES ($1)', ['brianc'])
const config = {
text: 'INSERT INTO peep(name) VALUES ($1)',
values: ['brian']
values: ['brian'],
}
client.query(config)
client.query('INSERT INTO peep(name) VALUES ($1)', ['aaron'])
@ -104,18 +118,18 @@ suite.test('callback API', done => {
assert.equal(res.rowCount, 3)
assert.deepEqual(res.rows, [
{
name: 'aaron'
name: 'aaron',
},
{
name: 'brian'
name: 'brian',
},
{
name: 'brianc'
}
name: 'brianc',
},
])
done()
})
client.connect(err => {
client.connect((err) => {
assert(!err)
client.once('drain', () => client.end())
})
@ -175,8 +189,7 @@ suite.test('query errors are handled and do not bubble if callback is provided',
)
})
)
}
)
})
suite.test('callback is fired once and only once', function (done) {
const pool = new pg.Pool()
@ -189,14 +202,10 @@ suite.test('callback is fired once and only once', function (done) {
[
"INSERT INTO boom(name) VALUES('hai')",
"INSERT INTO boom(name) VALUES('boom')",
"INSERT INTO boom(name) VALUES('zoom')"
"INSERT INTO boom(name) VALUES('zoom')",
].join(';'),
function (err, callback) {
assert.equal(
callCount++,
0,
'Call count should be 0. More means this callback fired more than once.'
)
assert.equal(callCount++, 0, 'Call count should be 0. More means this callback fired more than once.')
release()
pool.end(done)
}
@ -213,7 +222,7 @@ suite.test('can provide callback and config object', function (done) {
client.query(
{
name: 'boom',
text: 'select NOW()'
text: 'select NOW()',
},
assert.calls(function (err, result) {
assert(!err)
@ -232,7 +241,7 @@ suite.test('can provide callback and config and parameters', function (done) {
assert.calls(function (err, client, release) {
assert(!err)
var config = {
text: 'select $1::text as val'
text: 'select $1::text as val',
}
client.query(
config,

View File

@ -6,24 +6,29 @@ var suite = new helper.Suite()
var conInfo = helper.config
function getConInfo (override) {
return Object.assign({}, conInfo, override )
function getConInfo(override) {
return Object.assign({}, conInfo, override)
}
function getAppName (conf, cb) {
function getAppName(conf, cb) {
var client = new Client(conf)
client.connect(assert.success(function () {
client.query('SHOW application_name', assert.success(function (res) {
var appName = res.rows[0].application_name
cb(appName)
client.end()
}))
}))
client.connect(
assert.success(function () {
client.query(
'SHOW application_name',
assert.success(function (res) {
var appName = res.rows[0].application_name
cb(appName)
client.end()
})
)
})
)
}
suite.test('No default appliation_name ', function (done) {
var conf = getConInfo()
getAppName({ }, function (res) {
getAppName({}, function (res) {
assert.strictEqual(res, '')
done()
})
@ -32,7 +37,7 @@ suite.test('No default appliation_name ', function (done) {
suite.test('fallback_application_name is used', function (done) {
var fbAppName = 'this is my app'
var conf = getConInfo({
'fallback_application_name': fbAppName
fallback_application_name: fbAppName,
})
getAppName(conf, function (res) {
assert.strictEqual(res, fbAppName)
@ -43,7 +48,7 @@ suite.test('fallback_application_name is used', function (done) {
suite.test('application_name is used', function (done) {
var appName = 'some wired !@#$% application_name'
var conf = getConInfo({
'application_name': appName
application_name: appName,
})
getAppName(conf, function (res) {
assert.strictEqual(res, appName)
@ -55,8 +60,8 @@ suite.test('application_name has precedence over fallback_application_name', fun
var appName = 'some wired !@#$% application_name'
var fbAppName = 'some other strange $$test$$ appname'
var conf = getConInfo({
'application_name': appName,
'fallback_application_name': fbAppName
application_name: appName,
fallback_application_name: fbAppName,
})
getAppName(conf, function (res) {
assert.strictEqual(res, appName)
@ -82,8 +87,8 @@ suite.test('application_name from connection string', function (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) {
var appName = process.env.PGAPPNAME = 'testest'
getAppName({ }, function (res) {
var appName = (process.env.PGAPPNAME = 'testest')
getAppName({}, function (res) {
delete process.env.PGAPPNAME
assert.strictEqual(res, appName)
done()

View File

@ -6,172 +6,226 @@ var suite = new helper.Suite()
const pool = new pg.Pool()
pool.connect(assert.calls(function (err, client, release) {
assert(!err)
suite.test('nulls', function (done) {
client.query('SELECT $1::text[] as array', [[null]], assert.success(function (result) {
var array = result.rows[0].array
assert.lengthIs(array, 1)
assert.isNull(array[0])
done()
}))
})
suite.test('elements containing JSON-escaped characters', function (done) {
var param = '\\"\\"'
for (var i = 1; i <= 0x1f; i++) {
param += String.fromCharCode(i)
}
client.query('SELECT $1::text[] as array', [[param]], assert.success(function (result) {
var array = result.rows[0].array
assert.lengthIs(array, 1)
assert.equal(array[0], param)
done()
}))
})
suite.test('cleanup', () => release())
pool.connect(assert.calls(function (err, client, 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(() => {
suite.test('nulls', function (done) {
client.query(
'SELECT $1::text[] as array',
[[null]],
assert.success(function (result) {
var array = result.rows[0].array
assert.lengthIs(array, 1)
assert.isNull(array[0])
done()
})
}))
)
})
}))
}))
suite.test('elements containing JSON-escaped characters', function (done) {
var param = '\\"\\"'
for (var i = 1; i <= 0x1f; i++) {
param += String.fromCharCode(i)
}
client.query(
'SELECT $1::text[] as array',
[[param]],
assert.success(function (result) {
var array = result.rows[0].array
assert.lengthIs(array, 1)
assert.equal(array[0], param)
done()
})
)
})
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()
})
})
)
})
})
)
})
)

File diff suppressed because one or more lines are too long

View File

@ -25,7 +25,7 @@ suite.test('default values are used in new clients', function () {
ssl: false,
application_name: undefined,
fallback_application_name: undefined,
parseInputDatesAsUTC: false
parseInputDatesAsUTC: false,
})
var client = new pg.Client()
@ -33,7 +33,7 @@ suite.test('default values are used in new clients', function () {
user: process.env.USER,
database: process.env.USER,
password: null,
port: 5432
port: 5432,
})
})
@ -50,7 +50,7 @@ suite.test('modified values are passed to created clients', function () {
password: 'zap',
database: 'pow',
port: 1234,
host: 'blam'
host: 'blam',
})
})

View File

@ -10,26 +10,26 @@ const options = {
port: 54321,
connectionTimeoutMillis: 2000,
user: 'not',
database: 'existing'
database: 'existing',
}
const serverWithConnectionTimeout = (timeout, callback) => {
const sockets = new Set()
const server = net.createServer(socket => {
const server = net.createServer((socket) => {
sockets.add(socket)
socket.once('end', () => sockets.delete(socket))
socket.on('data', data => {
socket.on('data', (data) => {
// deny request for SSL
if (data.length === 8) {
socket.write(Buffer.from('N', 'utf8'))
// consider all authentication requests as good
// consider all authentication requests as good
} else if (!data[0]) {
socket.write(buffers.authenticationOk())
// send ReadyForQuery `timeout` ms after authentication
setTimeout(() => socket.write(buffers.readyForQuery()), timeout).unref()
// respond with our canned response
// respond with our canned response
} else {
socket.write(buffers.readyForQuery())
}
@ -37,7 +37,7 @@ const serverWithConnectionTimeout = (timeout, callback) => {
})
let closing = false
const closeServer = done => {
const closeServer = (done) => {
if (closing) return
closing = true
@ -50,32 +50,34 @@ const serverWithConnectionTimeout = (timeout, callback) => {
server.listen(options.port, options.host, () => callback(closeServer))
}
suite.test('successful connection', done => {
serverWithConnectionTimeout(0, closeServer => {
suite.test('successful connection', (done) => {
serverWithConnectionTimeout(0, (closeServer) => {
const timeoutId = setTimeout(() => {
throw new Error('Client should have connected successfully but it did not.')
}, 3000)
const client = new helper.Client(options)
client.connect()
client
.connect()
.then(() => client.end())
.then(() => closeServer(done))
.catch(err => closeServer(() => done(err)))
.catch((err) => closeServer(() => done(err)))
.then(() => clearTimeout(timeoutId))
})
})
suite.test('expired connection timeout', done => {
serverWithConnectionTimeout(options.connectionTimeoutMillis * 2, closeServer => {
suite.test('expired connection timeout', (done) => {
serverWithConnectionTimeout(options.connectionTimeoutMillis * 2, (closeServer) => {
const timeoutId = setTimeout(() => {
throw new Error('Client should have emitted an error but it did not.')
}, 3000)
const client = new helper.Client(options)
client.connect()
client
.connect()
.then(() => client.end())
.then(() => closeServer(() => done(new Error('Connection timeout should have expired but it did not.'))))
.catch(err => {
.catch((err) => {
assert(err instanceof Error)
assert(/timeout expired\s*/.test(err.message))
closeServer(done)

View File

@ -4,19 +4,21 @@ const Client = helper.pg.Client
const suite = new helper.Suite()
const customTypes = {
getTypeParser: () => () => 'okay!'
getTypeParser: () => () => 'okay!',
}
suite.test('custom type parser in client config', (done) => {
const client = new Client({ types: customTypes })
client.connect()
.then(() => {
client.query('SELECT NOW() as val', assert.success(function (res) {
client.connect().then(() => {
client.query(
'SELECT NOW() as val',
assert.success(function (res) {
assert.equal(res.rows[0].val, 'okay!')
client.end().then(done)
}))
})
})
)
})
})
// Custom type-parsers per query are not supported in native
@ -24,15 +26,17 @@ if (!helper.args.native) {
suite.test('custom type parser in query', (done) => {
const client = new Client()
client.connect()
.then(() => {
client.query({
client.connect().then(() => {
client.query(
{
text: 'SELECT NOW() as val',
types: customTypes
}, assert.success(function (res) {
types: customTypes,
},
assert.success(function (res) {
assert.equal(res.rows[0].val, 'okay!')
client.end().then(done)
}))
})
})
)
})
})
}

View File

@ -7,7 +7,7 @@ suite.test('empty query message handling', function (done) {
assert.emits(client, 'drain', function () {
client.end(done)
})
client.query({text: ''})
client.query({ text: '' })
})
suite.test('callback supported', function (done) {

View File

@ -33,7 +33,7 @@ suite.test('sending non-array argument as values causes an error callback', (don
suite.test('re-using connections results in error callback', (done) => {
const client = new Client()
client.connect(() => {
client.connect(err => {
client.connect((err) => {
assert(err instanceof Error)
client.end(done)
})
@ -43,7 +43,7 @@ suite.test('re-using connections results in error callback', (done) => {
suite.test('re-using connections results in promise rejection', (done) => {
const client = new Client()
client.connect().then(() => {
client.connect().catch(err => {
client.connect().catch((err) => {
assert(err instanceof Error)
client.end().then(done)
})
@ -53,33 +53,43 @@ suite.test('re-using connections results in promise rejection', (done) => {
suite.test('using a client after closing it results in error', (done) => {
const client = new Client()
client.connect(() => {
client.end(assert.calls(() => {
client.query('SELECT 1', assert.calls((err) => {
assert.equal(err.message, 'Client was closed and is not queryable')
done()
}))
}))
client.end(
assert.calls(() => {
client.query(
'SELECT 1',
assert.calls((err) => {
assert.equal(err.message, 'Client was closed and is not queryable')
done()
})
)
})
)
})
})
suite.test('query receives error on client shutdown', function (done) {
var client = new Client()
client.connect(assert.success(function () {
const config = {
text: 'select pg_sleep(5)',
name: 'foobar'
}
let queryError
client.query(new pg.Query(config), assert.calls(function (err, res) {
assert(err instanceof Error)
queryError = err
}))
setTimeout(() => client.end(), 50)
client.once('end', () => {
assert(queryError instanceof Error)
done()
client.connect(
assert.success(function () {
const config = {
text: 'select pg_sleep(5)',
name: 'foobar',
}
let queryError
client.query(
new pg.Query(config),
assert.calls(function (err, res) {
assert(err instanceof Error)
queryError = err
})
)
setTimeout(() => client.end(), 50)
client.once('end', () => {
assert(queryError instanceof Error)
done()
})
})
}))
)
})
var ensureFuture = function (testClient, done) {
@ -95,11 +105,13 @@ suite.test('when query is parsing', (done) => {
var q = client.query({ text: 'CREATE TEMP TABLE boom(age integer); INSERT INTO boom (age) VALUES (28);' })
// this query wont parse since there isn't a table named bang
var query = client.query(new pg.Query({
text: 'select * from bang where name = $1',
values: ['0']
}))
// this query wont parse since there isn't a table named bang
var query = client.query(
new pg.Query({
text: 'select * from bang where name = $1',
values: ['0'],
})
)
assert.emits(query, 'error', function (err) {
ensureFuture(client, done)
@ -111,10 +123,12 @@ suite.test('when a query is binding', function (done) {
var q = client.query({ text: 'CREATE TEMP TABLE boom(age integer); INSERT INTO boom (age) VALUES (28);' })
var query = client.query(new pg.Query({
text: 'select * from boom where age = $1',
values: ['asldkfjasdf']
}))
var query = client.query(
new pg.Query({
text: 'select * from boom where age = $1',
values: ['asldkfjasdf'],
})
)
assert.emits(query, 'error', function (err) {
assert.equal(err.severity, 'ERROR')
@ -124,12 +138,14 @@ suite.test('when a query is binding', function (done) {
suite.test('non-query error with callback', function (done) {
var client = new Client({
user: 'asldkfjsadlfkj'
user: 'asldkfjsadlfkj',
})
client.connect(assert.calls(function (error, client) {
assert(error instanceof Error)
done()
}))
client.connect(
assert.calls(function (error, client) {
assert(error instanceof Error)
done()
})
)
})
suite.test('non-error calls supplied callback', function (done) {
@ -138,18 +154,20 @@ suite.test('non-error calls supplied callback', function (done) {
password: helper.args.password,
host: helper.args.host,
port: helper.args.port,
database: helper.args.database
database: helper.args.database,
})
client.connect(assert.calls(function (err) {
assert.ifError(err)
client.end(done)
}))
client.connect(
assert.calls(function (err) {
assert.ifError(err)
client.end(done)
})
)
})
suite.test('when connecting to an invalid host with callback', function (done) {
var client = new Client({
user: 'very invalid username'
user: 'very invalid username',
})
client.on('error', () => {
assert.fail('unexpected error event when connecting')
@ -162,7 +180,7 @@ suite.test('when connecting to an invalid host with callback', function (done) {
suite.test('when connecting to invalid host with promise', function (done) {
var client = new Client({
user: 'very invalid username'
user: 'very invalid username',
})
client.on('error', () => {
assert.fail('unexpected error event when connecting')
@ -172,13 +190,12 @@ suite.test('when connecting to invalid host with promise', function (done) {
suite.test('non-query error', function (done) {
var client = new Client({
user: 'asldkfjsadlfkj'
user: 'asldkfjsadlfkj',
})
client.connect().catch((e) => {
assert(e instanceof Error)
done()
})
client.connect()
.catch(e => {
assert(e instanceof Error)
done()
})
})
suite.test('within a simple query', (done) => {
@ -199,7 +216,7 @@ suite.test('connected, idle client error', (done) => {
throw new Error('Should not receive error callback after connection')
}
setImmediate(() => {
(client.connection || client.native).emit('error', new Error('expected'))
;(client.connection || client.native).emit('error', new Error('expected'))
})
})
client.on('error', (err) => {
@ -211,9 +228,9 @@ suite.test('connected, idle client error', (done) => {
suite.test('cannot pass non-string values to query as text', (done) => {
const client = new Client()
client.connect()
client.query({ text: { } }, (err) => {
client.query({ text: {} }, (err) => {
assert(err)
client.query({ }, (err) => {
client.query({}, (err) => {
client.on('drain', () => {
client.end(done)
})

View File

@ -2,21 +2,26 @@
var helper = require('./test-helper')
const pool = new helper.pg.Pool()
pool.connect(assert.success(function (client, done) {
var types = require('pg-types')
// 1231 = numericOID
types.setTypeParser(1700, function () {
return 'yes'
pool.connect(
assert.success(function (client, done) {
var types = require('pg-types')
// 1231 = numericOID
types.setTypeParser(1700, function () {
return 'yes'
})
types.setTypeParser(1700, 'binary', function () {
return 'yes'
})
var bignum = '294733346389144765940638005275322203805'
client.query('CREATE TEMP TABLE bignumz(id numeric)')
client.query('INSERT INTO bignumz(id) VALUES ($1)', [bignum])
client.query(
'SELECT * FROM bignumz',
assert.success(function (result) {
assert.equal(result.rows[0].id, 'yes')
done()
pool.end()
})
)
})
types.setTypeParser(1700, 'binary', function () {
return 'yes'
})
var bignum = '294733346389144765940638005275322203805'
client.query('CREATE TEMP TABLE bignumz(id numeric)')
client.query('INSERT INTO bignumz(id) VALUES ($1)', [bignum])
client.query('SELECT * FROM bignumz', assert.success(function (result) {
assert.equal(result.rows[0].id, 'yes')
done()
pool.end()
}))
}))
)

View File

@ -6,38 +6,54 @@ var suite = new helper.Suite()
var conInfo = helper.config
function getConInfo (override) {
return Object.assign({}, conInfo, override )
function getConInfo(override) {
return Object.assign({}, conInfo, override)
}
function testClientVersion(cb) {
var client = new Client({})
client.connect(assert.success(function () {
helper.versionGTE(client, 100000, assert.success(function(isGreater) {
return client.end(assert.success(function () {
if (!isGreater) {
console.log('skip idle_in_transaction_session_timeout at client-level is only available in v10 and above');
return;
}
cb();
}))
}))
}))
client.connect(
assert.success(function () {
helper.versionGTE(
client,
100000,
assert.success(function (isGreater) {
return client.end(
assert.success(function () {
if (!isGreater) {
console.log(
'skip idle_in_transaction_session_timeout at client-level is only available in v10 and above'
)
return
}
cb()
})
)
})
)
})
)
}
function getIdleTransactionSessionTimeout (conf, cb) {
function getIdleTransactionSessionTimeout(conf, cb) {
var client = new Client(conf)
client.connect(assert.success(function () {
client.query('SHOW idle_in_transaction_session_timeout', assert.success(function (res) {
var timeout = res.rows[0].idle_in_transaction_session_timeout
cb(timeout)
client.end()
}))
}))
client.connect(
assert.success(function () {
client.query(
'SHOW idle_in_transaction_session_timeout',
assert.success(function (res) {
var timeout = res.rows[0].idle_in_transaction_session_timeout
cb(timeout)
client.end()
})
)
})
)
}
if (!helper.args.native) { // idle_in_transaction_session_timeout is not supported with the native client
testClientVersion(function(){
if (!helper.args.native) {
// idle_in_transaction_session_timeout is not supported with the native client
testClientVersion(function () {
suite.test('No default idle_in_transaction_session_timeout ', function (done) {
getConInfo()
getIdleTransactionSessionTimeout({}, function (res) {
@ -48,7 +64,7 @@ if (!helper.args.native) { // idle_in_transaction_session_timeout is not support
suite.test('idle_in_transaction_session_timeout integer is used', function (done) {
var conf = getConInfo({
'idle_in_transaction_session_timeout': 3000
idle_in_transaction_session_timeout: 3000,
})
getIdleTransactionSessionTimeout(conf, function (res) {
assert.strictEqual(res, '3s')
@ -58,7 +74,7 @@ if (!helper.args.native) { // idle_in_transaction_session_timeout is not support
suite.test('idle_in_transaction_session_timeout float is used', function (done) {
var conf = getConInfo({
'idle_in_transaction_session_timeout': 3000.7
idle_in_transaction_session_timeout: 3000.7,
})
getIdleTransactionSessionTimeout(conf, function (res) {
assert.strictEqual(res, '3s')
@ -68,7 +84,7 @@ if (!helper.args.native) { // idle_in_transaction_session_timeout is not support
suite.test('idle_in_transaction_session_timeout string is used', function (done) {
var conf = getConInfo({
'idle_in_transaction_session_timeout': '3000'
idle_in_transaction_session_timeout: '3000',
})
getIdleTransactionSessionTimeout(conf, function (res) {
assert.strictEqual(res, '3s')

View File

@ -3,26 +3,35 @@ var helper = require('./test-helper')
var assert = require('assert')
const pool = new helper.pg.Pool()
pool.connect(assert.success(function (client, done) {
helper.versionGTE(client, 90200, assert.success(function (jsonSupported) {
if (!jsonSupported) {
console.log('skip json test on older versions of postgres')
done()
return pool.end()
}
client.query('CREATE TEMP TABLE stuff(id SERIAL PRIMARY KEY, data JSON)')
var value = { name: 'Brian', age: 250, alive: true, now: new Date() }
client.query('INSERT INTO stuff (data) VALUES ($1)', [value])
client.query('SELECT * FROM stuff', assert.success(function (result) {
assert.equal(result.rows.length, 1)
assert.equal(typeof result.rows[0].data, 'object')
var row = result.rows[0].data
assert.strictEqual(row.name, value.name)
assert.strictEqual(row.age, value.age)
assert.strictEqual(row.alive, value.alive)
assert.equal(JSON.stringify(row.now), JSON.stringify(value.now))
done()
pool.end()
}))
}))
}))
pool.connect(
assert.success(function (client, done) {
helper.versionGTE(
client,
90200,
assert.success(function (jsonSupported) {
if (!jsonSupported) {
console.log('skip json test on older versions of postgres')
done()
return pool.end()
}
client.query('CREATE TEMP TABLE stuff(id SERIAL PRIMARY KEY, data JSON)')
var value = { name: 'Brian', age: 250, alive: true, now: new Date() }
client.query('INSERT INTO stuff (data) VALUES ($1)', [value])
client.query(
'SELECT * FROM stuff',
assert.success(function (result) {
assert.equal(result.rows.length, 1)
assert.equal(typeof result.rows[0].data, 'object')
var row = result.rows[0].data
assert.strictEqual(row.name, value.name)
assert.strictEqual(row.age, value.age)
assert.strictEqual(row.alive, value.alive)
assert.equal(JSON.stringify(row.now), JSON.stringify(value.now))
done()
pool.end()
})
)
})
)
})
)

View File

@ -6,64 +6,73 @@ const helper = require('./test-helper')
const suite = new helper.Suite('multiple result sets')
suite.test('two select results work', co.wrap(function * () {
const client = new helper.Client()
yield client.connect()
suite.test(
'two select results work',
co.wrap(function* () {
const client = new helper.Client()
yield client.connect()
const results = yield client.query(`SELECT 'foo'::text as name; SELECT 'bar'::text as baz`)
assert(Array.isArray(results))
const results = yield client.query(`SELECT 'foo'::text as name; SELECT 'bar'::text as baz`)
assert(Array.isArray(results))
assert.equal(results[0].fields[0].name, 'name')
assert.deepEqual(results[0].rows, [{ name: 'foo' }])
assert.equal(results[0].fields[0].name, 'name')
assert.deepEqual(results[0].rows, [{ name: 'foo' }])
assert.equal(results[1].fields[0].name, 'baz')
assert.deepEqual(results[1].rows, [{ baz: 'bar' }])
assert.equal(results[1].fields[0].name, 'baz')
assert.deepEqual(results[1].rows, [{ baz: 'bar' }])
return client.end()
}))
return client.end()
})
)
suite.test('multiple selects work', co.wrap(function * () {
const client = new helper.Client()
yield client.connect()
suite.test(
'multiple selects work',
co.wrap(function* () {
const client = new helper.Client()
yield client.connect()
const text = `
const text = `
SELECT * FROM generate_series(2, 4) as foo;
SELECT * FROM generate_series(8, 10) as bar;
SELECT * FROM generate_series(20, 22) as baz;
`
const results = yield client.query(text)
assert(Array.isArray(results))
const results = yield client.query(text)
assert(Array.isArray(results))
assert.equal(results[0].fields[0].name, 'foo')
assert.deepEqual(results[0].rows, [{ foo: 2 }, { foo: 3 }, { foo: 4 }])
assert.equal(results[0].fields[0].name, 'foo')
assert.deepEqual(results[0].rows, [{ foo: 2 }, { foo: 3 }, { foo: 4 }])
assert.equal(results[1].fields[0].name, 'bar')
assert.deepEqual(results[1].rows, [{ bar: 8 }, { bar: 9 }, { bar: 10 }])
assert.equal(results[1].fields[0].name, 'bar')
assert.deepEqual(results[1].rows, [{ bar: 8 }, { bar: 9 }, { bar: 10 }])
assert.equal(results[2].fields[0].name, 'baz')
assert.deepEqual(results[2].rows, [{ baz: 20 }, { baz: 21 }, { baz: 22 }])
assert.equal(results[2].fields[0].name, 'baz')
assert.deepEqual(results[2].rows, [{ baz: 20 }, { baz: 21 }, { baz: 22 }])
assert.equal(results.length, 3)
assert.equal(results.length, 3)
return client.end()
}))
return client.end()
})
)
suite.test('mixed queries and statements', co.wrap(function * () {
const client = new helper.Client()
yield client.connect()
suite.test(
'mixed queries and statements',
co.wrap(function* () {
const client = new helper.Client()
yield client.connect()
const text = `
const text = `
CREATE TEMP TABLE weather(type text);
INSERT INTO weather(type) VALUES ('rain');
SELECT * FROM weather;
`
const results = yield client.query(text)
assert(Array.isArray(results))
assert.equal(results[0].command, 'CREATE')
assert.equal(results[1].command, 'INSERT')
assert.equal(results[2].command, 'SELECT')
const results = yield client.query(text)
assert(Array.isArray(results))
assert.equal(results[0].command, 'CREATE')
assert.equal(results[1].command, 'INSERT')
assert.equal(results[2].command, 'SELECT')
return client.end()
}))
return client.end()
})
)

View File

@ -16,29 +16,34 @@ Server.prototype.start = function (cb) {
// it responds with our specified response immediatley after receiving every buffer
// this is sufficient into convincing the client its connectet to a valid backend
// if we respond with a readyForQuery message
this.server = net.createServer(function (socket) {
this.socket = socket
if (this.response) {
this.socket.on('data', function (data) {
// deny request for SSL
if (data.length == 8) {
this.socket.write(Buffer.from('N', 'utf8'))
// consider all authentication requests as good
} else if (!data[0]) {
this.socket.write(buffers.authenticationOk())
// respond with our canned response
} else {
this.socket.write(this.response)
}
}.bind(this))
}
}.bind(this))
this.server = net.createServer(
function (socket) {
this.socket = socket
if (this.response) {
this.socket.on(
'data',
function (data) {
// deny request for SSL
if (data.length == 8) {
this.socket.write(Buffer.from('N', 'utf8'))
// consider all authentication requests as good
} else if (!data[0]) {
this.socket.write(buffers.authenticationOk())
// respond with our canned response
} else {
this.socket.write(this.response)
}
}.bind(this)
)
}
}.bind(this)
)
var port = 54321
var options = {
host: 'localhost',
port: port
port: port,
}
this.server.listen(options.port, options.host, function () {
cb(options)
@ -58,12 +63,11 @@ var testServer = function (server, cb) {
server.start(function (options) {
// connect a client to it
var client = new helper.Client(options)
client.connect()
.catch((err) => {
assert(err instanceof Error)
clearTimeout(timeoutId)
server.close(cb)
})
client.connect().catch((err) => {
assert(err instanceof Error)
clearTimeout(timeoutId)
server.close(cb)
})
server.server.on('connection', () => {
// after 50 milliseconds, drop the client

View File

@ -7,33 +7,39 @@ suite.test('noData message handling', function () {
var q = client.query({
name: 'boom',
text: 'create temp table boom(id serial, size integer)'
text: 'create temp table boom(id serial, size integer)',
})
client.query({
name: 'insert',
text: 'insert into boom(size) values($1)',
values: [100]
}, function (err, result) {
if (err) {
console.log(err)
throw err
client.query(
{
name: 'insert',
text: 'insert into boom(size) values($1)',
values: [100],
},
function (err, result) {
if (err) {
console.log(err)
throw err
}
}
})
)
client.query({
name: 'insert',
values: [101]
values: [101],
})
var query = client.query({
name: 'fetch',
text: 'select size from boom where size < $1',
values: [101]
}, (err, res) => {
var row = res.rows[0]
assert.strictEqual(row.size, 100)
})
var query = client.query(
{
name: 'fetch',
text: 'select size from boom where size < $1',
values: [101],
},
(err, res) => {
var row = res.rows[0]
assert.strictEqual(row.size, 100)
}
)
client.on('drain', client.end.bind(client))
})

View File

@ -15,11 +15,13 @@ suite.test('can access results when no rows are returned', function (done) {
pool.connect(
assert.success(function (client, release) {
const q = new pg.Query('select $1::text as val limit 0', ['hi'])
var query = client.query(q, assert.success(function (result) {
checkResult(result)
release()
pool.end(done)
})
var query = client.query(
q,
assert.success(function (result) {
checkResult(result)
release()
pool.end(done)
})
)
assert.emits(query, 'end', checkResult)

View File

@ -5,31 +5,40 @@ const suite = new helper.Suite()
suite.test('emits notify message', function (done) {
const client = helper.client()
client.query('LISTEN boom', assert.calls(function () {
const otherClient = helper.client()
let bothEmitted = -1
otherClient.query('LISTEN boom', assert.calls(function () {
assert.emits(client, 'notification', function (msg) {
// make sure PQfreemem doesn't invalidate string pointers
setTimeout(function () {
assert.equal(msg.channel, 'boom')
assert.ok(msg.payload == 'omg!' /* 9.x */ || msg.payload == '' /* 8.x */, 'expected blank payload or correct payload but got ' + msg.message)
client.end(++bothEmitted ? done : undefined)
}, 100)
})
assert.emits(otherClient, 'notification', function (msg) {
assert.equal(msg.channel, 'boom')
otherClient.end(++bothEmitted ? done : undefined)
})
client.query(
'LISTEN boom',
assert.calls(function () {
const otherClient = helper.client()
let bothEmitted = -1
otherClient.query(
'LISTEN boom',
assert.calls(function () {
assert.emits(client, 'notification', function (msg) {
// make sure PQfreemem doesn't invalidate string pointers
setTimeout(function () {
assert.equal(msg.channel, 'boom')
assert.ok(
msg.payload == 'omg!' /* 9.x */ || msg.payload == '' /* 8.x */,
'expected blank payload or correct payload but got ' + msg.message
)
client.end(++bothEmitted ? done : undefined)
}, 100)
})
assert.emits(otherClient, 'notification', function (msg) {
assert.equal(msg.channel, 'boom')
otherClient.end(++bothEmitted ? done : undefined)
})
client.query("NOTIFY boom, 'omg!'", function (err, q) {
if (err) {
// notify not supported with payload on 8.x
client.query('NOTIFY boom')
}
})
}))
}))
client.query("NOTIFY boom, 'omg!'", function (err, q) {
if (err) {
// notify not supported with payload on 8.x
client.query('NOTIFY boom')
}
})
})
)
})
)
})
// this test fails on travis due to their config

View File

@ -7,23 +7,31 @@ const suite = new helper.Suite()
const pool = new pg.Pool(helper.config)
suite.test('ability to turn on and off parser', function () {
if (helper.args.binary) return false
pool.connect(assert.success(function (client, done) {
pg.defaults.parseInt8 = true
client.query('CREATE TEMP TABLE asdf(id SERIAL PRIMARY KEY)')
client.query('SELECT COUNT(*) as "count", \'{1,2,3}\'::bigint[] as array FROM asdf', assert.success(function (res) {
assert.strictEqual(0, res.rows[0].count)
assert.strictEqual(1, res.rows[0].array[0])
assert.strictEqual(2, res.rows[0].array[1])
assert.strictEqual(3, res.rows[0].array[2])
pg.defaults.parseInt8 = false
client.query('SELECT COUNT(*) as "count", \'{1,2,3}\'::bigint[] as array FROM asdf', assert.success(function (res) {
done()
assert.strictEqual('0', res.rows[0].count)
assert.strictEqual('1', res.rows[0].array[0])
assert.strictEqual('2', res.rows[0].array[1])
assert.strictEqual('3', res.rows[0].array[2])
pool.end()
}))
}))
}))
pool.connect(
assert.success(function (client, done) {
pg.defaults.parseInt8 = true
client.query('CREATE TEMP TABLE asdf(id SERIAL PRIMARY KEY)')
client.query(
'SELECT COUNT(*) as "count", \'{1,2,3}\'::bigint[] as array FROM asdf',
assert.success(function (res) {
assert.strictEqual(0, res.rows[0].count)
assert.strictEqual(1, res.rows[0].array[0])
assert.strictEqual(2, res.rows[0].array[1])
assert.strictEqual(3, res.rows[0].array[2])
pg.defaults.parseInt8 = false
client.query(
'SELECT COUNT(*) as "count", \'{1,2,3}\'::bigint[] as array FROM asdf',
assert.success(function (res) {
done()
assert.strictEqual('0', res.rows[0].count)
assert.strictEqual('1', res.rows[0].array[0])
assert.strictEqual('2', res.rows[0].array[1])
assert.strictEqual('3', res.rows[0].array[2])
pool.end()
})
)
})
)
})
)
})

View File

@ -12,11 +12,13 @@ var suite = new helper.Suite()
var parseCount = 0
suite.test('first named prepared statement', function (done) {
var query = client.query(new Query({
text: 'select name from person where age <= $1 and name LIKE $2',
values: [20, 'Bri%'],
name: queryName
}))
var query = client.query(
new Query({
text: 'select name from person where age <= $1 and name LIKE $2',
values: [20, 'Bri%'],
name: queryName,
})
)
assert.emits(query, 'row', function (row) {
assert.equal(row.name, 'Brian')
@ -26,11 +28,13 @@ var suite = new helper.Suite()
})
suite.test('second named prepared statement with same name & text', function (done) {
var cachedQuery = client.query(new Query({
text: 'select name from person where age <= $1 and name LIKE $2',
name: queryName,
values: [10, 'A%']
}))
var cachedQuery = client.query(
new Query({
text: 'select name from person where age <= $1 and name LIKE $2',
name: queryName,
values: [10, 'A%'],
})
)
assert.emits(cachedQuery, 'row', function (row) {
assert.equal(row.name, 'Aaron')
@ -40,10 +44,12 @@ var suite = new helper.Suite()
})
suite.test('with same name, but without query text', function (done) {
var q = client.query(new Query({
name: queryName,
values: [30, '%n%']
}))
var q = client.query(
new Query({
name: queryName,
values: [30, '%n%'],
})
)
assert.emits(q, 'row', function (row) {
assert.equal(row.name, 'Aaron')
@ -58,17 +64,22 @@ var suite = new helper.Suite()
})
suite.test('with same name, but with different text', function (done) {
client.query(new Query({
text: 'select name from person where age >= $1 and name LIKE $2',
name: queryName,
values: [30, '%n%']
}), assert.calls(err => {
assert.equal(err.message, `Prepared statements must be unique - '${queryName}' was used for a different statement`)
done()
}))
client.query(
new Query({
text: 'select name from person where age >= $1 and name LIKE $2',
name: queryName,
values: [30, '%n%'],
}),
assert.calls((err) => {
assert.equal(
err.message,
`Prepared statements must be unique - '${queryName}' was used for a different statement`
)
done()
})
)
})
})()
;(function () {
var statementName = 'differ'
var statement1 = 'select count(*)::int4 as count from person'
@ -78,22 +89,27 @@ var suite = new helper.Suite()
var client2 = helper.client()
suite.test('client 1 execution', function (done) {
var query = client1.query({
name: statementName,
text: statement1
}, (err, res) => {
assert(!err)
assert.equal(res.rows[0].count, 26)
done()
})
var query = client1.query(
{
name: statementName,
text: statement1,
},
(err, res) => {
assert(!err)
assert.equal(res.rows[0].count, 26)
done()
}
)
})
suite.test('client 2 execution', function (done) {
var query = client2.query(new Query({
name: statementName,
text: statement2,
values: [11]
}))
var query = client2.query(
new Query({
name: statementName,
text: statement2,
values: [11],
})
)
assert.emits(query, 'row', function (row) {
assert.equal(row.count, 1)
@ -108,7 +124,6 @@ var suite = new helper.Suite()
return client1.end().then(() => client2.end())
})
})()
;(function () {
var client = helper.client()
client.query('CREATE TEMP TABLE zoom(name varchar(100));')
@ -131,21 +146,31 @@ var suite = new helper.Suite()
}
suite.test('with small row count', function (done) {
var query = client.query(new Query({
name: 'get names',
text: 'SELECT name FROM zoom ORDER BY name COLLATE "C"',
rows: 1
}, done))
var query = client.query(
new Query(
{
name: 'get names',
text: 'SELECT name FROM zoom ORDER BY name COLLATE "C"',
rows: 1,
},
done
)
)
checkForResults(query)
})
suite.test('with large row count', function (done) {
var query = client.query(new Query({
name: 'get names',
text: 'SELECT name FROM zoom ORDER BY name COLLATE "C"',
rows: 1000
}, done))
var query = client.query(
new Query(
{
name: 'get names',
text: 'SELECT name FROM zoom ORDER BY name COLLATE "C"',
rows: 1000,
},
done
)
)
checkForResults(query)
})

View File

@ -7,43 +7,37 @@ const suite = new helper.Suite()
suite.test('valid connection completes promise', () => {
const client = new pg.Client()
return client.connect()
.then(() => {
return client.end()
.then(() => { })
})
return client.connect().then(() => {
return client.end().then(() => {})
})
})
suite.test('valid connection completes promise', () => {
const client = new pg.Client()
return client.connect()
.then(() => {
return client.end()
.then(() => { })
})
return client.connect().then(() => {
return client.end().then(() => {})
})
})
suite.test('invalid connection rejects promise', (done) => {
const client = new pg.Client({ host: 'alksdjflaskdfj' })
return client.connect()
.catch(e => {
assert(e instanceof Error)
done()
})
return client.connect().catch((e) => {
assert(e instanceof Error)
done()
})
})
suite.test('connected client does not reject promise after connection', (done) => {
const client = new pg.Client()
return client.connect()
.then(() => {
setTimeout(() => {
client.on('error', (e) => {
assert(e instanceof Error)
client.end()
done()
})
// manually kill the connection
client.emit('error', new Error('something bad happened...but not really'))
}, 50)
})
return client.connect().then(() => {
setTimeout(() => {
client.on('error', (e) => {
assert(e instanceof Error)
client.end()
done()
})
// manually kill the connection
client.emit('error', new Error('something bad happened...but not really'))
}, 50)
})
})

View File

@ -13,22 +13,21 @@ const suite = new helper.Suite()
suite.test('promise API', (cb) => {
const pool = new pg.Pool()
pool.connect().then((client) => {
client.query('SELECT $1::text as name', ['foo'])
client
.query('SELECT $1::text as name', ['foo'])
.then(function (result) {
assert.equal(result.rows[0].name, 'foo')
return client
})
.then(function (client) {
client.query('ALKJSDF')
.catch(function (e) {
assert(e instanceof Error)
client.query('SELECT 1 as num')
.then(function (result) {
assert.equal(result.rows[0].num, 1)
client.release()
pool.end(cb)
})
client.query('ALKJSDF').catch(function (e) {
assert(e instanceof Error)
client.query('SELECT 1 as num').then(function (result) {
assert.equal(result.rows[0].num, 1)
client.release()
pool.end(cb)
})
})
})
})
})
@ -52,4 +51,4 @@ suite.test('promise API with configurable promise type', (cb) => {
throw error
})
})
});
})

View File

@ -4,12 +4,17 @@ var pg = helper.pg
new helper.Suite().test('support for complex column names', function () {
const pool = new pg.Pool()
pool.connect(assert.success(function (client, done) {
client.query("CREATE TEMP TABLE t ( \"complex''column\" TEXT )")
client.query('SELECT * FROM t', assert.success(function (res) {
done()
assert.strictEqual(res.fields[0].name, "complex''column")
pool.end()
}))
}))
pool.connect(
assert.success(function (client, done) {
client.query('CREATE TEMP TABLE t ( "complex\'\'column" TEXT )')
client.query(
'SELECT * FROM t',
assert.success(function (res) {
done()
assert.strictEqual(res.fields[0].name, "complex''column")
pool.end()
})
)
})
)
})

View File

@ -7,57 +7,79 @@ var suite = new helper.Suite()
suite.test('client end during query execution of prepared statement', function (done) {
var client = new Client()
client.connect(assert.success(function () {
var sleepQuery = 'select pg_sleep($1)'
client.connect(
assert.success(function () {
var sleepQuery = 'select pg_sleep($1)'
var queryConfig = {
name: 'sleep query',
text: sleepQuery,
values: [5]
}
var queryConfig = {
name: 'sleep query',
text: sleepQuery,
values: [5],
}
var queryInstance = new Query(queryConfig, assert.calls(function (err, result) {
assert.equal(err.message, 'Connection terminated')
done()
}))
var queryInstance = new Query(
queryConfig,
assert.calls(function (err, result) {
assert.equal(err.message, 'Connection terminated')
done()
})
)
var query1 = client.query(queryInstance)
var query1 = client.query(queryInstance)
query1.on('error', function (err) {
assert.fail('Prepared statement should not emit error')
query1.on('error', function (err) {
assert.fail('Prepared statement should not emit error')
})
query1.on('row', function (row) {
assert.fail('Prepared statement should not emit row')
})
query1.on('end', function (err) {
assert.fail('Prepared statement when executed should not return before being killed')
})
client.end()
})
query1.on('row', function (row) {
assert.fail('Prepared statement should not emit row')
})
query1.on('end', function (err) {
assert.fail('Prepared statement when executed should not return before being killed')
})
client.end()
}))
)
})
function killIdleQuery (targetQuery, cb) {
function killIdleQuery(targetQuery, cb) {
var client2 = new Client(helper.args)
var pidColName = 'procpid'
var queryColName = 'current_query'
client2.connect(assert.success(function () {
helper.versionGTE(client2, 90200, assert.success(function (isGreater) {
if (isGreater) {
pidColName = 'pid'
queryColName = 'query'
}
var killIdleQuery = 'SELECT ' + pidColName + ', (SELECT pg_terminate_backend(' + pidColName + ')) AS killed FROM pg_stat_activity WHERE ' + queryColName + ' = $1'
client2.query(killIdleQuery, [targetQuery], assert.calls(function (err, res) {
assert.ifError(err)
assert.equal(res.rows.length, 1)
client2.end(cb)
assert.emits(client2, 'end')
}))
}))
}))
client2.connect(
assert.success(function () {
helper.versionGTE(
client2,
90200,
assert.success(function (isGreater) {
if (isGreater) {
pidColName = 'pid'
queryColName = 'query'
}
var killIdleQuery =
'SELECT ' +
pidColName +
', (SELECT pg_terminate_backend(' +
pidColName +
')) AS killed FROM pg_stat_activity WHERE ' +
queryColName +
' = $1'
client2.query(
killIdleQuery,
[targetQuery],
assert.calls(function (err, res) {
assert.ifError(err)
assert.equal(res.rows.length, 1)
client2.end(cb)
assert.emits(client2, 'end')
})
)
})
)
})
)
}
suite.test('query killed during query execution of prepared statement', function (done) {
@ -65,34 +87,39 @@ suite.test('query killed during query execution of prepared statement', function
return done()
}
var client = new Client(helper.args)
client.connect(assert.success(function () {
var sleepQuery = 'select pg_sleep($1)'
client.connect(
assert.success(function () {
var sleepQuery = 'select pg_sleep($1)'
const queryConfig = {
name: 'sleep query',
text: sleepQuery,
values: [5]
}
const queryConfig = {
name: 'sleep query',
text: sleepQuery,
values: [5],
}
// client should emit an error because it is unexpectedly disconnected
assert.emits(client, 'error')
// client should emit an error because it is unexpectedly disconnected
assert.emits(client, 'error')
var query1 = client.query(new Query(queryConfig), assert.calls(function (err, result) {
assert.equal(err.message, 'terminating connection due to administrator command')
}))
var query1 = client.query(
new Query(queryConfig),
assert.calls(function (err, result) {
assert.equal(err.message, 'terminating connection due to administrator command')
})
)
query1.on('error', function (err) {
assert.fail('Prepared statement should not emit error')
query1.on('error', function (err) {
assert.fail('Prepared statement should not emit error')
})
query1.on('row', function (row) {
assert.fail('Prepared statement should not emit row')
})
query1.on('end', function (err) {
assert.fail('Prepared statement when executed should not return before being killed')
})
killIdleQuery(sleepQuery, done)
})
query1.on('row', function (row) {
assert.fail('Prepared statement should not emit row')
})
query1.on('end', function (err) {
assert.fail('Prepared statement when executed should not return before being killed')
})
killIdleQuery(sleepQuery, done)
}))
)
})

View File

@ -1,88 +1,115 @@
"use strict";
var helper = require('./test-helper');
var util = require('util');
var Query = helper.pg.Query;
'use strict'
var helper = require('./test-helper')
var util = require('util')
var Query = helper.pg.Query
test('error during query execution', function() {
var client = new Client(helper.args);
client.connect(assert.success(function() {
var queryText = 'select pg_sleep(10)'
var sleepQuery = new Query(queryText);
var pidColName = 'procpid'
var queryColName = 'current_query';
helper.versionGTE(client, 90200, assert.success(function(isGreater) {
if(isGreater) {
pidColName = 'pid';
queryColName = 'query';
}
var query1 = client.query(sleepQuery, assert.calls(function(err, result) {
assert(err);
client.end();
}));
//ensure query1 does not emit an 'end' event
//because it was killed and received an error
//https://github.com/brianc/node-postgres/issues/547
query1.on('end', function() {
assert.fail('Query with an error should not emit "end" event')
})
setTimeout(function() {
var client2 = new Client(helper.args);
client2.connect(assert.success(function() {
var killIdleQuery = `SELECT ${pidColName}, (SELECT pg_cancel_backend(${pidColName})) AS killed FROM pg_stat_activity WHERE ${queryColName} LIKE $1`;
client2.query(killIdleQuery, [queryText], assert.calls(function(err, res) {
assert.ifError(err);
assert(res.rows.length > 0);
client2.end();
assert.emits(client2, 'end');
}));
}));
}, 300)
}));
}));
});
test('error during query execution', function () {
var client = new Client(helper.args)
client.connect(
assert.success(function () {
var queryText = 'select pg_sleep(10)'
var sleepQuery = new Query(queryText)
var pidColName = 'procpid'
var queryColName = 'current_query'
helper.versionGTE(
client,
90200,
assert.success(function (isGreater) {
if (isGreater) {
pidColName = 'pid'
queryColName = 'query'
}
var query1 = client.query(
sleepQuery,
assert.calls(function (err, result) {
assert(err)
client.end()
})
)
//ensure query1 does not emit an 'end' event
//because it was killed and received an error
//https://github.com/brianc/node-postgres/issues/547
query1.on('end', function () {
assert.fail('Query with an error should not emit "end" event')
})
setTimeout(function () {
var client2 = new Client(helper.args)
client2.connect(
assert.success(function () {
var killIdleQuery = `SELECT ${pidColName}, (SELECT pg_cancel_backend(${pidColName})) AS killed FROM pg_stat_activity WHERE ${queryColName} LIKE $1`
client2.query(
killIdleQuery,
[queryText],
assert.calls(function (err, res) {
assert.ifError(err)
assert(res.rows.length > 0)
client2.end()
assert.emits(client2, 'end')
})
)
})
)
}, 300)
})
)
})
)
})
if (helper.config.native) {
return
}
test('9.3 column error fields', function() {
var client = new Client(helper.args);
client.connect(assert.success(function() {
helper.versionGTE(client, 90300, assert.success(function(isGreater) {
if(!isGreater) {
return client.end();
}
test('9.3 column error fields', function () {
var client = new Client(helper.args)
client.connect(
assert.success(function () {
helper.versionGTE(
client,
90300,
assert.success(function (isGreater) {
if (!isGreater) {
return client.end()
}
client.query('CREATE TEMP TABLE column_err_test(a int NOT NULL)');
client.query('INSERT INTO column_err_test(a) VALUES (NULL)', function (err) {
assert.equal(err.severity, 'ERROR');
assert.equal(err.code, '23502');
assert.equal(err.table, 'column_err_test');
assert.equal(err.column, 'a');
return client.end();
});
}));
}));
});
client.query('CREATE TEMP TABLE column_err_test(a int NOT NULL)')
client.query('INSERT INTO column_err_test(a) VALUES (NULL)', function (err) {
assert.equal(err.severity, 'ERROR')
assert.equal(err.code, '23502')
assert.equal(err.table, 'column_err_test')
assert.equal(err.column, 'a')
return client.end()
})
})
)
})
)
})
test('9.3 constraint error fields', function() {
var client = new Client(helper.args);
client.connect(assert.success(function() {
helper.versionGTE(client, 90300, assert.success(function(isGreater) {
if(!isGreater) {
console.log('skip 9.3 error field on older versions of postgres');
return client.end();
}
test('9.3 constraint error fields', function () {
var client = new Client(helper.args)
client.connect(
assert.success(function () {
helper.versionGTE(
client,
90300,
assert.success(function (isGreater) {
if (!isGreater) {
console.log('skip 9.3 error field on older versions of postgres')
return client.end()
}
client.query('CREATE TEMP TABLE constraint_err_test(a int PRIMARY KEY)');
client.query('INSERT INTO constraint_err_test(a) VALUES (1)');
client.query('INSERT INTO constraint_err_test(a) VALUES (1)', function (err) {
assert.equal(err.severity, 'ERROR');
assert.equal(err.code, '23505');
assert.equal(err.table, 'constraint_err_test');
assert.equal(err.constraint, 'constraint_err_test_pkey');
return client.end();
});
}));
}));
});
client.query('CREATE TEMP TABLE constraint_err_test(a int PRIMARY KEY)')
client.query('INSERT INTO constraint_err_test(a) VALUES (1)')
client.query('INSERT INTO constraint_err_test(a) VALUES (1)', function (err) {
assert.equal(err.severity, 'ERROR')
assert.equal(err.code, '23505')
assert.equal(err.table, 'constraint_err_test')
assert.equal(err.constraint, 'constraint_err_test_pkey')
return client.end()
})
})
)
})
)
})

View File

@ -4,29 +4,44 @@ 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(!err)
pool.connect(
assert.calls(function (err, client, done) {
assert(!err)
helper.versionGTE(client, 90000, assert.success(function (hasRowCount) {
client.query('CREATE TEMP TABLE zugzug(name varchar(10))', assert.calls(function (err, result) {
assert(!err)
assert.equal(result.oid, null)
assert.equal(result.command, 'CREATE')
helper.versionGTE(
client,
90000,
assert.success(function (hasRowCount) {
client.query(
'CREATE TEMP TABLE zugzug(name varchar(10))',
assert.calls(function (err, result) {
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(!err)
assert.equal(result.command, 'INSERT')
assert.equal(result.rowCount, 1)
var q = client.query(
"INSERT INTO zugzug(name) VALUES('more work?')",
assert.calls(function (err, result) {
assert(!err)
assert.equal(result.command, 'INSERT')
assert.equal(result.rowCount, 1)
client.query('SELECT * FROM zugzug', assert.calls(function (err, result) {
assert(!err)
if (hasRowCount) assert.equal(result.rowCount, 1)
assert.equal(result.command, 'SELECT')
done()
process.nextTick(pool.end.bind(pool))
}))
}))
}))
}))
}))
client.query(
'SELECT * FROM zugzug',
assert.calls(function (err, result) {
assert(!err)
if (hasRowCount) assert.equal(result.rowCount, 1)
assert.equal(result.command, 'SELECT')
done()
process.nextTick(pool.end.bind(pool))
})
)
})
)
})
)
})
)
})
)
})

View File

@ -16,16 +16,21 @@ test('returns results as array', function () {
assert.strictEqual(row[2], 'hai')
assert.strictEqual(row[3], null)
}
client.connect(assert.success(function () {
var config = {
text: 'SELECT NOW(), 1::int, $1::text, null',
values: ['hai'],
rowMode: 'array'
}
var query = client.query(config, assert.success(function (result) {
assert.equal(result.rows.length, 1)
checkRow(result.rows[0])
client.end()
}))
}))
client.connect(
assert.success(function () {
var config = {
text: 'SELECT NOW(), 1::int, $1::text, null',
values: ['hai'],
rowMode: 'array',
}
var query = client.query(
config,
assert.success(function (result) {
assert.equal(result.rows.length, 1)
checkRow(result.rows[0])
client.end()
})
)
})
)
})

View File

@ -19,20 +19,32 @@ var checkResult = function (result) {
test('row descriptions on result object', function () {
var client = new Client(conInfo)
client.connect(assert.success(function () {
client.query('SELECT NOW() as now, 1::int as num, $1::text as texty', ['hello'], assert.success(function (result) {
checkResult(result)
client.end()
}))
}))
client.connect(
assert.success(function () {
client.query(
'SELECT NOW() as now, 1::int as num, $1::text as texty',
['hello'],
assert.success(function (result) {
checkResult(result)
client.end()
})
)
})
)
})
test('row description on no rows', function () {
var client = new Client(conInfo)
client.connect(assert.success(function () {
client.query('SELECT NOW() as now, 1::int as num, $1::text as texty LIMIT 0', ['hello'], assert.success(function (result) {
checkResult(result)
client.end()
}))
}))
client.connect(
assert.success(function () {
client.query(
'SELECT NOW() as now, 1::int as num, $1::text as texty LIMIT 0',
['hello'],
assert.success(function (result) {
checkResult(result)
client.end()
})
)
})
)
})

View File

@ -22,7 +22,11 @@ test('simple query interface', function () {
columnCount++
}
if ('length' in row) {
assert.lengthIs(row, columnCount, 'Iterating through the columns gives a different length from calling .length.')
assert.lengthIs(
row,
columnCount,
'Iterating through the columns gives a different length from calling .length.'
)
}
})
})
@ -65,7 +69,7 @@ test('prepared statements do not mutate params', function () {
test('multiple simple queries', function () {
var client = helper.client()
client.query({ text: "create temp table bang(id serial, name varchar(5));insert into bang(name) VALUES('boom');"})
client.query({ text: "create temp table bang(id serial, name varchar(5));insert into bang(name) VALUES('boom');" })
client.query("insert into bang(name) VALUES ('yes');")
var query = client.query(new Query('select name from bang'))
assert.emits(query, 'row', function (row) {
@ -79,9 +83,11 @@ test('multiple simple queries', function () {
test('multiple select statements', function () {
var client = helper.client()
client.query('create temp table boom(age integer); insert into boom(age) values(1); insert into boom(age) values(2); insert into boom(age) values(3)')
client.query({text: "create temp table bang(name varchar(5)); insert into bang(name) values('zoom');"})
var result = client.query(new Query({text: 'select age from boom where age < 2; select name from bang'}))
client.query(
'create temp table boom(age integer); insert into boom(age) values(1); insert into boom(age) values(2); insert into boom(age) values(3)'
)
client.query({ text: "create temp table bang(name varchar(5)); insert into bang(name) values('zoom');" })
var result = client.query(new Query({ text: 'select age from boom where age < 2; select name from bang' }))
assert.emits(result, 'row', function (row) {
assert.strictEqual(row['age'], 1)
assert.emits(result, 'row', function (row) {

View File

@ -4,12 +4,18 @@ var config = require(__dirname + '/test-helper').config
test('can connect with ssl', function () {
return false
config.ssl = {
rejectUnauthorized: false
rejectUnauthorized: false,
}
pg.connect(config, assert.success(function (client) {
return false
client.query('SELECT NOW()', assert.success(function () {
pg.end()
}))
}))
pg.connect(
config,
assert.success(function (client) {
return false
client.query(
'SELECT NOW()',
assert.success(function () {
pg.end()
})
)
})
)
})

View File

@ -6,22 +6,28 @@ var suite = new helper.Suite()
var conInfo = helper.config
function getConInfo (override) {
return Object.assign({}, conInfo, override )
function getConInfo(override) {
return Object.assign({}, conInfo, override)
}
function getStatementTimeout (conf, cb) {
function getStatementTimeout(conf, cb) {
var client = new Client(conf)
client.connect(assert.success(function () {
client.query('SHOW statement_timeout', assert.success(function (res) {
var statementTimeout = res.rows[0].statement_timeout
cb(statementTimeout)
client.end()
}))
}))
client.connect(
assert.success(function () {
client.query(
'SHOW statement_timeout',
assert.success(function (res) {
var statementTimeout = res.rows[0].statement_timeout
cb(statementTimeout)
client.end()
})
)
})
)
}
if (!helper.args.native) { // statement_timeout is not supported with the native client
if (!helper.args.native) {
// statement_timeout is not supported with the native client
suite.test('No default statement_timeout ', function (done) {
getConInfo()
getStatementTimeout({}, function (res) {
@ -32,7 +38,7 @@ if (!helper.args.native) { // statement_timeout is not supported with the native
suite.test('statement_timeout integer is used', function (done) {
var conf = getConInfo({
'statement_timeout': 3000
statement_timeout: 3000,
})
getStatementTimeout(conf, function (res) {
assert.strictEqual(res, '3s')
@ -42,7 +48,7 @@ if (!helper.args.native) { // statement_timeout is not supported with the native
suite.test('statement_timeout float is used', function (done) {
var conf = getConInfo({
'statement_timeout': 3000.7
statement_timeout: 3000.7,
})
getStatementTimeout(conf, function (res) {
assert.strictEqual(res, '3s')
@ -52,7 +58,7 @@ if (!helper.args.native) { // statement_timeout is not supported with the native
suite.test('statement_timeout string is used', function (done) {
var conf = getConInfo({
'statement_timeout': '3000'
statement_timeout: '3000',
})
getStatementTimeout(conf, function (res) {
assert.strictEqual(res, '3s')
@ -62,16 +68,17 @@ if (!helper.args.native) { // statement_timeout is not supported with the native
suite.test('statement_timeout actually cancels long running queries', function (done) {
var conf = getConInfo({
'statement_timeout': '10' // 10ms to keep tests running fast
statement_timeout: '10', // 10ms to keep tests running fast
})
var client = new Client(conf)
client.connect(assert.success(function () {
client.query('SELECT pg_sleep( 1 )', function ( error ) {
client.end()
assert.strictEqual( error.code, '57014' ) // query_cancelled
done()
client.connect(
assert.success(function () {
client.query('SELECT pg_sleep( 1 )', function (error) {
client.end()
assert.strictEqual(error.code, '57014') // query_cancelled
done()
})
})
}))
)
})
}

View File

@ -4,73 +4,96 @@ const suite = new helper.Suite()
const pg = helper.pg
const client = new pg.Client()
client.connect(assert.success(function () {
client.query('begin')
client.connect(
assert.success(function () {
client.query('begin')
var getZed = {
text: 'SELECT * FROM person WHERE name = $1',
values: ['Zed']
}
var getZed = {
text: 'SELECT * FROM person WHERE name = $1',
values: ['Zed'],
}
suite.test('name should not exist in the database', function (done) {
client.query(getZed, assert.calls(function (err, result) {
assert(!err)
assert.empty(result.rows)
done()
}))
suite.test('name should not exist in the database', function (done) {
client.query(
getZed,
assert.calls(function (err, result) {
assert(!err)
assert.empty(result.rows)
done()
})
)
})
suite.test('can insert name', (done) => {
client.query(
'INSERT INTO person(name, age) VALUES($1, $2)',
['Zed', 270],
assert.calls(function (err, result) {
assert(!err)
done()
})
)
})
suite.test('name should exist in the database', function (done) {
client.query(
getZed,
assert.calls(function (err, result) {
assert(!err)
assert.equal(result.rows[0].name, 'Zed')
done()
})
)
})
suite.test('rollback', (done) => {
client.query('rollback', done)
})
suite.test('name should not exist in the database', function (done) {
client.query(
getZed,
assert.calls(function (err, result) {
assert(!err)
assert.empty(result.rows)
client.end(done)
})
)
})
})
suite.test('can insert name', (done) => {
client.query('INSERT INTO person(name, age) VALUES($1, $2)', ['Zed', 270], assert.calls(function (err, result) {
assert(!err)
done()
}))
})
suite.test('name should exist in the database', function (done) {
client.query(getZed, assert.calls(function (err, result) {
assert(!err)
assert.equal(result.rows[0].name, 'Zed')
done()
}))
})
suite.test('rollback', (done) => {
client.query('rollback', done)
})
suite.test('name should not exist in the database', function (done) {
client.query(getZed, assert.calls(function (err, result) {
assert(!err)
assert.empty(result.rows)
client.end(done)
}))
})
}))
)
suite.test('gh#36', function (cb) {
const pool = new pg.Pool()
pool.connect(assert.success(function (client, done) {
client.query('BEGIN')
client.query({
name: 'X',
text: 'SELECT $1::INTEGER',
values: [0]
}, assert.calls(function (err, result) {
if (err) throw err
assert.equal(result.rows.length, 1)
}))
client.query({
name: 'X',
text: 'SELECT $1::INTEGER',
values: [0]
}, assert.calls(function (err, result) {
if (err) throw err
assert.equal(result.rows.length, 1)
}))
client.query('COMMIT', function () {
done()
pool.end(cb)
pool.connect(
assert.success(function (client, done) {
client.query('BEGIN')
client.query(
{
name: 'X',
text: 'SELECT $1::INTEGER',
values: [0],
},
assert.calls(function (err, result) {
if (err) throw err
assert.equal(result.rows.length, 1)
})
)
client.query(
{
name: 'X',
text: 'SELECT $1::INTEGER',
values: [0],
},
assert.calls(function (err, result) {
if (err) throw err
assert.equal(result.rows.length, 1)
})
)
client.query('COMMIT', function () {
done()
pool.end(cb)
})
})
}))
)
})

View File

@ -9,102 +9,130 @@ var testForTypeCoercion = function (type) {
suite.test(`test type coercion ${type.name}`, (cb) => {
pool.connect(function (err, client, done) {
assert(!err)
client.query('create temp table test_type(col ' + type.name + ')', assert.calls(function (err, result) {
assert(!err)
client.query(
'create temp table test_type(col ' + type.name + ')',
assert.calls(function (err, result) {
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(!err)
}))
type.values.forEach(function (val) {
var insertQuery = client.query(
'insert into test_type(col) VALUES($1)',
[val],
assert.calls(function (err, result) {
assert(!err)
})
)
var query = client.query(new pg.Query({
name: 'get type ' + type.name,
text: 'select col from test_type'
}))
var query = client.query(
new pg.Query({
name: 'get type ' + type.name,
text: 'select col from test_type',
})
)
query.on('error', function (err) {
console.log(err)
throw err
query.on('error', function (err) {
console.log(err)
throw err
})
assert.emits(
query,
'row',
function (row) {
var expected = val + ' (' + typeof val + ')'
var returned = row.col + ' (' + typeof row.col + ')'
assert.strictEqual(row.col, val, 'expected ' + type.name + ' of ' + expected + ' but got ' + returned)
},
'row should have been called for ' + type.name + ' of ' + val
)
client.query('delete from test_type')
})
assert.emits(query, 'row', function (row) {
var expected = val + ' (' + typeof val + ')'
var returned = row.col + ' (' + typeof row.col + ')'
assert.strictEqual(row.col, val, 'expected ' + type.name + ' of ' + expected + ' but got ' + returned)
}, 'row should have been called for ' + type.name + ' of ' + val)
client.query('delete from test_type')
client.query('drop table test_type', function () {
done()
pool.end(cb)
})
})
client.query('drop table test_type', function () {
done()
pool.end(cb)
})
}))
)
})
})
}
var types = [{
name: 'integer',
values: [-2147483648, -1, 0, 1, 2147483647, null]
}, {
name: 'smallint',
values: [-32768, -1, 0, 1, 32767, null]
}, {
name: 'bigint',
values: [
'-9223372036854775808',
'-9007199254740992',
'0',
'9007199254740992',
'72057594037928030',
'9223372036854775807',
null
]
}, {
name: 'varchar(5)',
values: ['yo', '', 'zomg!', null]
}, {
name: 'oid',
values: [0, 204410, null]
}, {
name: 'bool',
values: [true, false, null]
}, {
name: 'numeric',
values: [
'-12.34',
'0',
'12.34',
'-3141592653589793238462643383279502.1618033988749894848204586834365638',
'3141592653589793238462643383279502.1618033988749894848204586834365638',
null
]
}, {
name: 'real',
values: [-101.3, -1.2, 0, 1.2, 101.1, null]
}, {
name: 'double precision',
values: [-101.3, -1.2, 0, 1.2, 101.1, null]
}, {
name: 'timestamptz',
values: [null]
}, {
name: 'timestamp',
values: [null]
}, {
name: 'timetz',
values: ['13:11:12.1234-05:30', null]
}, {
name: 'time',
values: ['13:12:12.321', null]
}]
var types = [
{
name: 'integer',
values: [-2147483648, -1, 0, 1, 2147483647, null],
},
{
name: 'smallint',
values: [-32768, -1, 0, 1, 32767, null],
},
{
name: 'bigint',
values: [
'-9223372036854775808',
'-9007199254740992',
'0',
'9007199254740992',
'72057594037928030',
'9223372036854775807',
null,
],
},
{
name: 'varchar(5)',
values: ['yo', '', 'zomg!', null],
},
{
name: 'oid',
values: [0, 204410, null],
},
{
name: 'bool',
values: [true, false, null],
},
{
name: 'numeric',
values: [
'-12.34',
'0',
'12.34',
'-3141592653589793238462643383279502.1618033988749894848204586834365638',
'3141592653589793238462643383279502.1618033988749894848204586834365638',
null,
],
},
{
name: 'real',
values: [-101.3, -1.2, 0, 1.2, 101.1, null],
},
{
name: 'double precision',
values: [-101.3, -1.2, 0, 1.2, 101.1, null],
},
{
name: 'timestamptz',
values: [null],
},
{
name: 'timestamp',
values: [null],
},
{
name: 'timetz',
values: ['13:11:12.1234-05:30', null],
},
{
name: 'time',
values: ['13:12:12.321', null],
},
]
// ignore some tests in binary mode
if (helper.config.binary) {
types = types.filter(function (type) {
return !(type.name in { 'real': 1, 'timetz': 1, 'time': 1, 'numeric': 1, 'bigint': 1 })
return !(type.name in { real: 1, timetz: 1, time: 1, numeric: 1, bigint: 1 })
})
}
@ -121,13 +149,15 @@ suite.test('timestampz round trip', function (cb) {
client.query({
text: 'insert into date_tests(name, tstz)VALUES($1, $2)',
name: 'add date',
values: ['now', now]
values: ['now', now],
})
var result = client.query(new pg.Query({
name: 'get date',
text: 'select * from date_tests where name = $1',
values: ['now']
}))
var result = client.query(
new pg.Query({
name: 'get date',
text: 'select * from date_tests where name = $1',
values: ['now'],
})
)
assert.emits(result, 'row', function (row) {
var date = row.tstz
@ -145,21 +175,26 @@ suite.test('timestampz round trip', function (cb) {
})
})
suite.test('selecting nulls', cb => {
suite.test('selecting nulls', (cb) => {
const pool = new pg.Pool()
pool.connect(assert.calls(function (err, client, done) {
assert.ifError(err)
client.query('select null as res;', assert.calls(function (err, res) {
assert(!err)
assert.strictEqual(res.rows[0].res, null)
}))
client.query('select 7 <> $1 as res;', [null], function (err, res) {
assert(!err)
assert.strictEqual(res.rows[0].res, null)
done()
pool.end(cb)
pool.connect(
assert.calls(function (err, client, done) {
assert.ifError(err)
client.query(
'select null as res;',
assert.calls(function (err, res) {
assert(!err)
assert.strictEqual(res.rows[0].res, null)
})
)
client.query('select 7 <> $1 as res;', [null], function (err, res) {
assert(!err)
assert.strictEqual(res.rows[0].res, null)
done()
pool.end(cb)
})
})
}))
)
})
suite.test('date range extremes', function (done) {
@ -169,25 +204,40 @@ suite.test('date range extremes', function (done) {
// otherwise (if server's timezone is ahead of GMT) in
// textParsers.js::parseDate() the timezone offest is added to the date;
// in the case of "275760-09-13 00:00:00 GMT" the timevalue overflows.
client.query('SET TIMEZONE TO GMT', assert.success(function (res) {
// PostgreSQL supports date range of 4713 BCE to 294276 CE
// http://www.postgresql.org/docs/9.2/static/datatype-datetime.html
// ECMAScript supports date range of Apr 20 271821 BCE to Sep 13 275760 CE
// http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.1
client.query('SELECT $1::TIMESTAMPTZ as when', ['275760-09-13 00:00:00 GMT'], assert.success(function (res) {
assert.equal(res.rows[0].when.getFullYear(), 275760)
}))
client.query(
'SET TIMEZONE TO GMT',
assert.success(function (res) {
// PostgreSQL supports date range of 4713 BCE to 294276 CE
// http://www.postgresql.org/docs/9.2/static/datatype-datetime.html
// ECMAScript supports date range of Apr 20 271821 BCE to Sep 13 275760 CE
// http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.1
client.query(
'SELECT $1::TIMESTAMPTZ as when',
['275760-09-13 00:00:00 GMT'],
assert.success(function (res) {
assert.equal(res.rows[0].when.getFullYear(), 275760)
})
)
client.query('SELECT $1::TIMESTAMPTZ as when', ['4713-12-31 12:31:59 BC GMT'], assert.success(function (res) {
assert.equal(res.rows[0].when.getFullYear(), -4712)
}))
client.query(
'SELECT $1::TIMESTAMPTZ as when',
['4713-12-31 12:31:59 BC GMT'],
assert.success(function (res) {
assert.equal(res.rows[0].when.getFullYear(), -4712)
})
)
client.query('SELECT $1::TIMESTAMPTZ as when', ['275760-09-13 00:00:00 -15:00'], assert.success(function (res) {
assert(isNaN(res.rows[0].when.getTime()))
}))
client.query(
'SELECT $1::TIMESTAMPTZ as when',
['275760-09-13 00:00:00 -15:00'],
assert.success(function (res) {
assert(isNaN(res.rows[0].when.getTime()))
})
)
client.on('drain', () => {
client.end(done)
client.on('drain', () => {
client.end(done)
})
})
}))
)
})

Some files were not shown because too many files have changed in this diff Show More