mirror of
https://github.com/brianc/node-postgres.git
synced 2025-12-08 20:16:25 +00:00
Lint pg & turn off semicolons
This commit is contained in:
parent
6adbcabf50
commit
c13cf81ee8
@ -9,9 +9,7 @@
|
||||
],
|
||||
"ignorePatterns": [
|
||||
"node_modules",
|
||||
"packages/pg",
|
||||
"packages/pg-protocol/dist/**/*",
|
||||
"packages/pg-pool"
|
||||
"packages/pg-protocol/dist/**/*"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2017,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"semi": true,
|
||||
"semi": false,
|
||||
"printWidth": 120,
|
||||
"trailingComma": "es5",
|
||||
"singleQuote": true
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -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()
|
||||
})
|
||||
})
|
||||
|
||||
@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -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()
|
||||
})
|
||||
})
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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())
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@ -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 () {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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')
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@ -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' })
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@ -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)
|
||||
})
|
||||
|
||||
@ -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()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@ -12,7 +12,7 @@ describe('verify', () => {
|
||||
verify: (client, cb) => {
|
||||
client.release()
|
||||
cb(new Error('nope'))
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
pool.connect((err, client) => {
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -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 }
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 }
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
2
packages/pg-protocol/src/types/chunky.d.ts
vendored
2
packages/pg-protocol/src/types/chunky.d.ts
vendored
@ -1 +1 @@
|
||||
declare module 'chunky';
|
||||
declare module 'chunky'
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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')
|
||||
}
|
||||
|
||||
@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -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()
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -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()
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -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()
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -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()
|
||||
})
|
||||
})
|
||||
|
||||
@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -70,7 +70,7 @@ module.exports = {
|
||||
|
||||
keepalives: 1,
|
||||
|
||||
keepalives_idle: 0
|
||||
keepalives_idle: 0,
|
||||
}
|
||||
|
||||
var pgTypes = require('pg-types')
|
||||
|
||||
@ -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
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -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 () {
|
||||
|
||||
@ -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])
|
||||
})
|
||||
})
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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,
|
||||
}
|
||||
|
||||
@ -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 {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
}
|
||||
|
||||
@ -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()
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@ -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()
|
||||
})
|
||||
})
|
||||
|
||||
@ -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)
|
||||
})
|
||||
)
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
@ -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',
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
}))
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)
|
||||
})
|
||||
|
||||
@ -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()
|
||||
}))
|
||||
}))
|
||||
)
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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()
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
@ -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()
|
||||
})
|
||||
)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
})
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@ -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)
|
||||
})
|
||||
|
||||
|
||||
@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@ -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
|
||||
})
|
||||
})
|
||||
});
|
||||
})
|
||||
|
||||
@ -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()
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@ -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)
|
||||
}))
|
||||
)
|
||||
})
|
||||
|
||||
@ -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()
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@ -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))
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@ -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()
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@ -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()
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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()
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@ -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()
|
||||
})
|
||||
})
|
||||
}))
|
||||
)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@ -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)
|
||||
})
|
||||
})
|
||||
}))
|
||||
)
|
||||
})
|
||||
|
||||
@ -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
Loading…
x
Reference in New Issue
Block a user