Merge pull request #2161 from brianc/bmc/lint

Prettier the codebase
This commit is contained in:
Brian C 2020-04-10 12:32:17 -05:00 committed by GitHub
commit 2ef5550373
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
154 changed files with 4083 additions and 3294 deletions

View File

@ -1,14 +1,15 @@
{
"plugins": [
"node"
"prettier"
],
"parser": "@typescript-eslint/parser",
"extends": [
"standard",
"eslint:recommended",
"plugin:node/recommended"
"plugin:prettier/recommended",
"prettier/@typescript-eslint"
],
"ignorePatterns": [
"**/*.ts"
"node_modules",
"packages/pg-protocol/dist/**/*"
],
"parserOptions": {
"ecmaVersion": 2017,
@ -18,17 +19,5 @@
"node": true,
"es6": true,
"mocha": true
},
"rules": {
"space-before-function-paren": "off",
"node/no-unsupported-features/es-syntax": "off",
"node/no-unpublished-require": [
"error",
{
"allowModules": [
"pg"
]
}
]
}
}

View File

@ -10,13 +10,28 @@
"packages/*"
],
"scripts": {
"test": "yarn lerna exec yarn test",
"test": "yarn lint && yarn lerna exec yarn test",
"build": "yarn lerna exec --scope pg-protocol yarn build",
"pretest": "yarn build",
"lint": "yarn lerna exec --parallel yarn lint"
"lint": "!([[ -e node_modules/.bin/prettier ]]) || eslint '*/**/*.{js,ts,tsx}'"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^2.27.0",
"@typescript-eslint/parser": "^2.27.0",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.10.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^3.1.2",
"lerna": "^3.19.0"
},
"dependencies": {}
"optionalDependencies": {
"prettier": "2.0.4"
},
"prettier": {
"semi": false,
"printWidth": 120,
"arrowParens": "always",
"trailingComma": "es5",
"singleQuote": true
}
}

View File

@ -44,7 +44,7 @@ Cursor.prototype.submit = function (connection) {
con.parse(
{
text: this.text
text: this.text,
},
true
)
@ -52,7 +52,7 @@ Cursor.prototype.submit = function (connection) {
con.bind(
{
portal: this._portal,
values: this.values
values: this.values,
},
true
)
@ -60,7 +60,7 @@ Cursor.prototype.submit = function (connection) {
con.describe(
{
type: 'P',
name: this._portal // AWS Redshift requires a portal name
name: this._portal, // AWS Redshift requires a portal name
},
true
)
@ -165,7 +165,7 @@ Cursor.prototype._getRows = function (rows, cb) {
this._rows = []
const msg = {
portal: this._portal,
rows: rows
rows: rows,
}
this.connection.execute(msg, true)
this.connection.flush()

View File

@ -7,8 +7,7 @@
"test": "test"
},
"scripts": {
"test": "mocha && eslint .",
"lint": "eslint ."
"test": "mocha"
},
"repository": {
"type": "git",
@ -17,17 +16,7 @@
"author": "Brian M. Carlson",
"license": "MIT",
"devDependencies": {
"eslint": "^6.5.1",
"eslint-config-prettier": "^6.4.0",
"eslint-plugin-prettier": "^3.1.1",
"mocha": "^6.2.2",
"pg": "^8.0.2",
"prettier": "^1.18.2"
},
"prettier": {
"semi": false,
"printWidth": 120,
"trailingComma": "none",
"singleQuote": true
"pg": "^8.0.2"
}
}

View File

@ -5,14 +5,14 @@ const pg = require('pg')
const text = 'SELECT generate_series as num FROM generate_series(0, 4)'
describe('error handling', function() {
it('can continue after error', function(done) {
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'))
cursor.read(1, function(err) {
cursor.read(1, function (err) {
assert(err)
client.query('SELECT NOW()', function(err) {
client.query('SELECT NOW()', function (err) {
assert.ifError(err)
client.end()
done()
@ -22,16 +22,16 @@ describe('error handling', function() {
})
describe('read callback does not fire sync', () => {
it('does not fire error callback sync', done => {
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
cursor.read(1, function(err) {
cursor.read(1, function (err) {
assert(err, 'error should be returned')
assert.strictEqual(after, true, 'should not call read sync')
after = false
cursor.read(1, function(err) {
cursor.read(1, function (err) {
assert(err, 'error should be returned')
assert.strictEqual(after, true, 'should not call read sync')
client.end()
@ -42,18 +42,18 @@ describe('read callback does not fire sync', () => {
after = true
})
it('does not fire result sync after finished', done => {
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
cursor.read(1, function(err) {
cursor.read(1, function (err) {
assert(!err)
assert.strictEqual(after, true, 'should not call read sync')
cursor.read(1, function(err) {
cursor.read(1, function (err) {
assert(!err)
after = false
cursor.read(1, function(err) {
cursor.read(1, function (err) {
assert(!err)
assert.strictEqual(after, true, 'should not call read sync')
client.end()
@ -66,16 +66,16 @@ describe('read callback does not fire sync', () => {
})
})
describe('proper cleanup', function() {
it('can issue multiple cursors on one client', function(done) {
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))
cursor1.read(8, function(err, rows) {
cursor1.read(8, function (err, rows) {
assert.ifError(err)
assert.strictEqual(rows.length, 5)
const cursor2 = client.query(new Cursor(text))
cursor2.read(8, function(err, rows) {
cursor2.read(8, function (err, rows) {
assert.ifError(err)
assert.strictEqual(rows.length, 5)
client.end()

View File

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

View File

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

View File

@ -5,7 +5,7 @@ const pg = require('pg')
const text = 'SELECT generate_series as num FROM generate_series(0, 50)'
function poolQueryPromise (pool, readRowCount) {
function poolQueryPromise(pool, readRowCount) {
return new Promise((resolve, reject) => {
pool.connect((err, client, done) => {
if (err) {
@ -13,12 +13,12 @@ function poolQueryPromise (pool, readRowCount) {
return reject(err)
}
const cursor = client.query(new Cursor(text))
cursor.read(readRowCount, err => {
cursor.read(readRowCount, (err) => {
if (err) {
done(err)
return reject(err)
}
cursor.close(err => {
cursor.close((err) => {
if (err) {
done(err)
return reject(err)
@ -43,7 +43,7 @@ describe('pool', function () {
it('closes cursor early, single pool query', function (done) {
poolQueryPromise(this.pool, 25)
.then(() => done())
.catch(err => {
.catch((err) => {
assert.ifError(err)
done()
})
@ -56,7 +56,7 @@ describe('pool', function () {
}
Promise.all(promises)
.then(() => done())
.catch(err => {
.catch((err) => {
assert.ifError(err)
done()
})
@ -65,7 +65,7 @@ describe('pool', function () {
it('closes exhausted cursor, single pool query', function (done) {
poolQueryPromise(this.pool, 100)
.then(() => done())
.catch(err => {
.catch((err) => {
assert.ifError(err)
done()
})
@ -78,7 +78,7 @@ describe('pool', function () {
}
Promise.all(promises)
.then(() => done())
.catch(err => {
.catch((err) => {
assert.ifError(err)
done()
})
@ -90,7 +90,7 @@ describe('pool', function () {
const cursor = new Cursor(text)
const client = await pool.connect()
client.query(cursor)
await new Promise(resolve => {
await new Promise((resolve) => {
cursor.read(25, function (err) {
assert.ifError(err)
cursor.close(function (err) {

View File

@ -4,7 +4,7 @@ const Cursor = require('../')
const pg = require('pg')
describe('query config passed to result', () => {
it('passes rowMode to result', done => {
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)'
@ -17,12 +17,12 @@ describe('query config passed to result', () => {
})
})
it('passes types to result', 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 types = {
getTypeParser: () => () => 'foo'
getTypeParser: () => () => 'foo',
}
const cursor = client.query(new Cursor(text, null, { types }))
cursor.read(10, (err, rows) => {

View File

@ -23,7 +23,7 @@ describe('transactions', () => {
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 new Promise((resolve) => cursor.close(resolve))
await client.query('ALTER TABLE foobar ADD COLUMN name TEXT')
await client.end()
})
@ -35,7 +35,7 @@ describe('transactions', () => {
// 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))
const err = await new Promise((resolve) => cursor.read(100, resolve))
assert.ifError(err)
await client.query('ALTER TABLE foobar ADD COLUMN name TEXT')
await client.end()

View File

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

View File

@ -8,29 +8,35 @@ const BluebirdPromise = require('bluebird')
const Pool = require('../')
const checkType = promise => {
const checkType = (promise) => {
expect(promise).to.be.a(BluebirdPromise)
return promise.catch(e => undefined)
return promise.catch((e) => undefined)
}
describe('Bring your own promise', function () {
it('uses supplied promise for operations', co.wrap(function * () {
const pool = new Pool({ Promise: BluebirdPromise })
const client1 = yield checkType(pool.connect())
client1.release()
yield checkType(pool.query('SELECT NOW()'))
const client2 = yield checkType(pool.connect())
// TODO - make sure pg supports BYOP as well
client2.release()
yield checkType(pool.end())
}))
it(
'uses supplied promise for operations',
co.wrap(function* () {
const pool = new Pool({ Promise: BluebirdPromise })
const client1 = yield checkType(pool.connect())
client1.release()
yield checkType(pool.query('SELECT NOW()'))
const client2 = yield checkType(pool.connect())
// TODO - make sure pg supports BYOP as well
client2.release()
yield checkType(pool.end())
})
)
it('uses promises in errors', co.wrap(function * () {
const pool = new Pool({ Promise: BluebirdPromise, port: 48484 })
yield checkType(pool.connect())
yield checkType(pool.end())
yield checkType(pool.connect())
yield checkType(pool.query())
yield checkType(pool.end())
}))
it(
'uses promises in errors',
co.wrap(function* () {
const pool = new Pool({ Promise: BluebirdPromise, port: 48484 })
yield checkType(pool.connect())
yield checkType(pool.end())
yield checkType(pool.connect())
yield checkType(pool.query())
yield checkType(pool.end())
})
)
})

View File

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

View File

@ -43,7 +43,7 @@ describe('connection timeout', () => {
it('should reject promise with an error if timeout is passed', (done) => {
const pool = new Pool({ connectionTimeoutMillis: 10, port: this.port, host: 'localhost' })
pool.connect().catch(err => {
pool.connect().catch((err) => {
expect(err).to.be.an(Error)
expect(err.message).to.contain('timeout')
expect(pool.idleCount).to.equal(0)
@ -51,18 +51,23 @@ describe('connection timeout', () => {
})
})
it('should handle multiple timeouts', co.wrap(function* () {
const errors = []
const pool = new Pool({ connectionTimeoutMillis: 1, port: this.port, host: 'localhost' })
for (var i = 0; i < 15; i++) {
try {
yield pool.connect()
} catch (e) {
errors.push(e)
}
}
expect(errors).to.have.length(15)
}.bind(this)))
it(
'should handle multiple timeouts',
co.wrap(
function* () {
const errors = []
const pool = new Pool({ connectionTimeoutMillis: 1, port: this.port, host: 'localhost' })
for (var i = 0; i < 15; i++) {
try {
yield pool.connect()
} catch (e) {
errors.push(e)
}
}
expect(errors).to.have.length(15)
}.bind(this)
)
)
it('should timeout on checkout of used connection', (done) => {
const pool = new Pool({ connectionTimeoutMillis: 100, max: 1 })
@ -153,7 +158,7 @@ describe('connection timeout', () => {
const pool = new Pool({
Client: Client,
connectionTimeoutMillis: 1000,
max: 1
max: 1,
})
pool.connect((err, client, release) => {
@ -199,7 +204,7 @@ describe('connection timeout', () => {
const pool = new Pool({
Client: Client,
connectionTimeoutMillis: 1000,
max: 1
max: 1,
})
// Direct connect

View File

@ -17,18 +17,24 @@ describe('pool ending', () => {
return new Pool().end()
})
it('ends with clients', co.wrap(function * () {
const pool = new Pool()
const res = yield pool.query('SELECT $1::text as name', ['brianc'])
expect(res.rows[0].name).to.equal('brianc')
return pool.end()
}))
it(
'ends with clients',
co.wrap(function* () {
const pool = new Pool()
const res = yield pool.query('SELECT $1::text as name', ['brianc'])
expect(res.rows[0].name).to.equal('brianc')
return pool.end()
})
)
it('allows client to finish', co.wrap(function * () {
const pool = new Pool()
const query = pool.query('SELECT $1::text as name', ['brianc'])
yield pool.end()
const res = yield query
expect(res.rows[0].name).to.equal('brianc')
}))
it(
'allows client to finish',
co.wrap(function* () {
const pool = new Pool()
const query = pool.query('SELECT $1::text as name', ['brianc'])
yield pool.end()
const res = yield query
expect(res.rows[0].name).to.equal('brianc')
})
)
})

View File

@ -16,12 +16,15 @@ describe('pool error handling', function () {
function runErrorQuery() {
shouldGet++
return new Promise(function (resolve, reject) {
pool.query("SELECT 'asd'+1 ").then(function (res) {
reject(res) // this should always error
}).catch(function (err) {
errors++
resolve(err)
})
pool
.query("SELECT 'asd'+1 ")
.then(function (res) {
reject(res) // this should always error
})
.catch(function (err) {
errors++
resolve(err)
})
})
}
const ps = []
@ -35,14 +38,17 @@ describe('pool error handling', function () {
})
describe('calling release more than once', () => {
it('should throw each time', co.wrap(function* () {
const pool = new Pool()
const client = yield pool.connect()
client.release()
expect(() => client.release()).to.throwError()
expect(() => client.release()).to.throwError()
return yield pool.end()
}))
it(
'should throw each time',
co.wrap(function* () {
const pool = new Pool()
const client = yield pool.connect()
client.release()
expect(() => client.release()).to.throwError()
expect(() => client.release()).to.throwError()
return yield pool.end()
})
)
it('should throw each time with callbacks', function (done) {
const pool = new Pool()
@ -75,17 +81,16 @@ describe('pool error handling', function () {
it('rejects all additional promises', (done) => {
const pool = new Pool()
const promises = []
pool.end()
.then(() => {
const squash = promise => promise.catch(e => 'okay!')
promises.push(squash(pool.connect()))
promises.push(squash(pool.query('SELECT NOW()')))
promises.push(squash(pool.end()))
Promise.all(promises).then(res => {
expect(res).to.eql(['okay!', 'okay!', 'okay!'])
done()
})
pool.end().then(() => {
const squash = (promise) => promise.catch((e) => 'okay!')
promises.push(squash(pool.connect()))
promises.push(squash(pool.query('SELECT NOW()')))
promises.push(squash(pool.end()))
Promise.all(promises).then((res) => {
expect(res).to.eql(['okay!', 'okay!', 'okay!'])
done()
})
})
})
it('returns an error on all additional callbacks', (done) => {
@ -106,68 +111,74 @@ describe('pool error handling', function () {
})
describe('error from idle client', () => {
it('removes client from pool', co.wrap(function* () {
const pool = new Pool()
const client = yield pool.connect()
expect(pool.totalCount).to.equal(1)
expect(pool.waitingCount).to.equal(0)
expect(pool.idleCount).to.equal(0)
client.release()
yield new Promise((resolve, reject) => {
process.nextTick(() => {
let poolError
pool.once('error', (err) => {
poolError = err
it(
'removes client from pool',
co.wrap(function* () {
const pool = new Pool()
const client = yield pool.connect()
expect(pool.totalCount).to.equal(1)
expect(pool.waitingCount).to.equal(0)
expect(pool.idleCount).to.equal(0)
client.release()
yield new Promise((resolve, reject) => {
process.nextTick(() => {
let poolError
pool.once('error', (err) => {
poolError = err
})
let clientError
client.once('error', (err) => {
clientError = err
})
client.emit('error', new Error('expected'))
expect(clientError.message).to.equal('expected')
expect(poolError.message).to.equal('expected')
expect(pool.idleCount).to.equal(0)
expect(pool.totalCount).to.equal(0)
pool.end().then(resolve, reject)
})
let clientError
client.once('error', (err) => {
clientError = err
})
client.emit('error', new Error('expected'))
expect(clientError.message).to.equal('expected')
expect(poolError.message).to.equal('expected')
expect(pool.idleCount).to.equal(0)
expect(pool.totalCount).to.equal(0)
pool.end().then(resolve, reject)
})
})
}))
)
})
describe('error from in-use client', () => {
it('keeps the client in the pool', co.wrap(function* () {
const pool = new Pool()
const client = yield pool.connect()
expect(pool.totalCount).to.equal(1)
expect(pool.waitingCount).to.equal(0)
expect(pool.idleCount).to.equal(0)
it(
'keeps the client in the pool',
co.wrap(function* () {
const pool = new Pool()
const client = yield pool.connect()
expect(pool.totalCount).to.equal(1)
expect(pool.waitingCount).to.equal(0)
expect(pool.idleCount).to.equal(0)
yield new Promise((resolve, reject) => {
process.nextTick(() => {
let poolError
pool.once('error', (err) => {
poolError = err
yield new Promise((resolve, reject) => {
process.nextTick(() => {
let poolError
pool.once('error', (err) => {
poolError = err
})
let clientError
client.once('error', (err) => {
clientError = err
})
client.emit('error', new Error('expected'))
expect(clientError.message).to.equal('expected')
expect(poolError).not.to.be.ok()
expect(pool.idleCount).to.equal(0)
expect(pool.totalCount).to.equal(1)
client.release()
pool.end().then(resolve, reject)
})
let clientError
client.once('error', (err) => {
clientError = err
})
client.emit('error', new Error('expected'))
expect(clientError.message).to.equal('expected')
expect(poolError).not.to.be.ok()
expect(pool.idleCount).to.equal(0)
expect(pool.totalCount).to.equal(1)
client.release()
pool.end().then(resolve, reject)
})
})
}))
)
})
describe('passing a function to pool.query', () => {
@ -182,30 +193,35 @@ describe('pool error handling', function () {
})
describe('pool with lots of errors', () => {
it('continues to work and provide new clients', co.wrap(function* () {
const pool = new Pool({ max: 1 })
const errors = []
for (var i = 0; i < 20; i++) {
try {
yield pool.query('invalid sql')
} catch (err) {
errors.push(err)
it(
'continues to work and provide new clients',
co.wrap(function* () {
const pool = new Pool({ max: 1 })
const errors = []
for (var i = 0; i < 20; i++) {
try {
yield pool.query('invalid sql')
} catch (err) {
errors.push(err)
}
}
}
expect(errors).to.have.length(20)
expect(pool.idleCount).to.equal(0)
expect(pool.query).to.be.a(Function)
const res = yield pool.query('SELECT $1::text as name', ['brianc'])
expect(res.rows).to.have.length(1)
expect(res.rows[0].name).to.equal('brianc')
return pool.end()
}))
expect(errors).to.have.length(20)
expect(pool.idleCount).to.equal(0)
expect(pool.query).to.be.a(Function)
const res = yield pool.query('SELECT $1::text as name', ['brianc'])
expect(res.rows).to.have.length(1)
expect(res.rows[0].name).to.equal('brianc')
return pool.end()
})
)
})
it('should continue with queued items after a connection failure', (done) => {
const closeServer = net.createServer((socket) => {
socket.destroy()
}).unref()
const closeServer = net
.createServer((socket) => {
socket.destroy()
})
.unref()
closeServer.listen(() => {
const pool = new Pool({ max: 1, port: closeServer.address().port, host: 'localhost' })

View File

@ -31,13 +31,13 @@ describe('events', function () {
process.nextTick(() => {
cb(new Error('bad news'))
})
}
})
},
}),
})
pool.on('connect', function () {
throw new Error('should never get here')
})
return pool.connect().catch(e => expect(e.message).to.equal('bad news'))
return pool.connect().catch((e) => expect(e.message).to.equal('bad news'))
})
it('emits acquire every time a client is acquired', function (done) {
@ -77,7 +77,7 @@ describe('events', function () {
})
})
function mockClient (methods) {
function mockClient(methods) {
return function () {
const client = new EventEmitter()
Object.assign(client, methods)

View File

@ -7,7 +7,7 @@ const it = require('mocha').it
const Pool = require('../')
const wait = time => new Promise((resolve) => setTimeout(resolve, time))
const wait = (time) => new Promise((resolve) => setTimeout(resolve, time))
describe('idle timeout', () => {
it('should timeout and remove the client', (done) => {
@ -20,60 +20,68 @@ describe('idle timeout', () => {
})
})
it('times out and removes clients when others are also removed', co.wrap(function * () {
const pool = new Pool({ idleTimeoutMillis: 10 })
const clientA = yield pool.connect()
const clientB = yield pool.connect()
clientA.release()
clientB.release(new Error())
it(
'times out and removes clients when others are also removed',
co.wrap(function* () {
const pool = new Pool({ idleTimeoutMillis: 10 })
const clientA = yield pool.connect()
const clientB = yield pool.connect()
clientA.release()
clientB.release(new Error())
const removal = new Promise((resolve) => {
pool.on('remove', () => {
const removal = new Promise((resolve) => {
pool.on('remove', () => {
expect(pool.idleCount).to.equal(0)
expect(pool.totalCount).to.equal(0)
resolve()
})
})
const timeout = wait(100).then(() => Promise.reject(new Error('Idle timeout failed to occur')))
try {
yield Promise.race([removal, timeout])
} finally {
pool.end()
}
})
)
it(
'can remove idle clients and recreate them',
co.wrap(function* () {
const pool = new Pool({ idleTimeoutMillis: 1 })
const results = []
for (var i = 0; i < 20; i++) {
let query = pool.query('SELECT NOW()')
expect(pool.idleCount).to.equal(0)
expect(pool.totalCount).to.equal(1)
results.push(yield query)
yield wait(2)
expect(pool.idleCount).to.equal(0)
expect(pool.totalCount).to.equal(0)
resolve()
})
}
expect(results).to.have.length(20)
})
)
const timeout = wait(100).then(() =>
Promise.reject(new Error('Idle timeout failed to occur')))
try {
yield Promise.race([removal, timeout])
} finally {
pool.end()
}
}))
it('can remove idle clients and recreate them', co.wrap(function * () {
const pool = new Pool({ idleTimeoutMillis: 1 })
const results = []
for (var i = 0; i < 20; i++) {
let query = pool.query('SELECT NOW()')
expect(pool.idleCount).to.equal(0)
expect(pool.totalCount).to.equal(1)
results.push(yield query)
yield wait(2)
expect(pool.idleCount).to.equal(0)
expect(pool.totalCount).to.equal(0)
}
expect(results).to.have.length(20)
}))
it('does not time out clients which are used', co.wrap(function * () {
const pool = new Pool({ idleTimeoutMillis: 1 })
const results = []
for (var i = 0; i < 20; i++) {
let client = yield pool.connect()
expect(pool.totalCount).to.equal(1)
expect(pool.idleCount).to.equal(0)
yield wait(10)
results.push(yield client.query('SELECT NOW()'))
client.release()
expect(pool.idleCount).to.equal(1)
expect(pool.totalCount).to.equal(1)
}
expect(results).to.have.length(20)
return pool.end()
}))
it(
'does not time out clients which are used',
co.wrap(function* () {
const pool = new Pool({ idleTimeoutMillis: 1 })
const results = []
for (var i = 0; i < 20; i++) {
let client = yield pool.connect()
expect(pool.totalCount).to.equal(1)
expect(pool.idleCount).to.equal(0)
yield wait(10)
results.push(yield client.query('SELECT NOW()'))
client.release()
expect(pool.idleCount).to.equal(1)
expect(pool.totalCount).to.equal(1)
}
expect(results).to.have.length(20)
return pool.end()
})
)
})

View File

@ -167,13 +167,11 @@ describe('pool', function () {
it('executes a query directly', () => {
const pool = new Pool()
return pool
.query('SELECT $1::text as name', ['hi'])
.then(res => {
expect(res.rows).to.have.length(1)
expect(res.rows[0].name).to.equal('hi')
return pool.end()
})
return pool.query('SELECT $1::text as name', ['hi']).then((res) => {
expect(res.rows).to.have.length(1)
expect(res.rows[0].name).to.equal('hi')
return pool.end()
})
})
it('properly pools clients', function () {
@ -210,10 +208,9 @@ describe('pool', function () {
const errors = []
const promises = _.times(30, () => {
return pool.query('SELECT asldkfjasldkf')
.catch(function (e) {
errors.push(e)
})
return pool.query('SELECT asldkfjasldkf').catch(function (e) {
errors.push(e)
})
})
return Promise.all(promises).then(() => {
expect(errors).to.have.length(30)

View File

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

View File

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

View File

@ -8,43 +8,51 @@ const it = require('mocha').it
const Pool = require('../')
describe('pool size of 1', () => {
it('can create a single client and use it once', co.wrap(function * () {
const pool = new Pool({ max: 1 })
expect(pool.waitingCount).to.equal(0)
const client = yield pool.connect()
const res = yield client.query('SELECT $1::text as name', ['hi'])
expect(res.rows[0].name).to.equal('hi')
client.release()
pool.end()
}))
it(
'can create a single client and use it once',
co.wrap(function* () {
const pool = new Pool({ max: 1 })
expect(pool.waitingCount).to.equal(0)
const client = yield pool.connect()
const res = yield client.query('SELECT $1::text as name', ['hi'])
expect(res.rows[0].name).to.equal('hi')
client.release()
pool.end()
})
)
it('can create a single client and use it multiple times', co.wrap(function * () {
const pool = new Pool({ max: 1 })
expect(pool.waitingCount).to.equal(0)
const client = yield pool.connect()
const wait = pool.connect()
expect(pool.waitingCount).to.equal(1)
client.release()
const client2 = yield wait
expect(client).to.equal(client2)
client2.release()
return yield pool.end()
}))
it(
'can create a single client and use it multiple times',
co.wrap(function* () {
const pool = new Pool({ max: 1 })
expect(pool.waitingCount).to.equal(0)
const client = yield pool.connect()
const wait = pool.connect()
expect(pool.waitingCount).to.equal(1)
client.release()
const client2 = yield wait
expect(client).to.equal(client2)
client2.release()
return yield pool.end()
})
)
it('can only send 1 query at a time', co.wrap(function * () {
const pool = new Pool({ max: 1 })
it(
'can only send 1 query at a time',
co.wrap(function* () {
const pool = new Pool({ max: 1 })
// the query text column name changed in PostgreSQL 9.2
const versionResult = yield pool.query('SHOW server_version_num')
const version = parseInt(versionResult.rows[0].server_version_num, 10)
const queryColumn = version < 90200 ? 'current_query' : 'query'
// the query text column name changed in PostgreSQL 9.2
const versionResult = yield pool.query('SHOW server_version_num')
const version = parseInt(versionResult.rows[0].server_version_num, 10)
const queryColumn = version < 90200 ? 'current_query' : 'query'
const queryText = 'SELECT COUNT(*) as counts FROM pg_stat_activity WHERE ' + queryColumn + ' = $1'
const queries = _.times(20, () =>
pool.query(queryText, [queryText]))
const results = yield Promise.all(queries)
const counts = results.map(res => parseInt(res.rows[0].counts, 10))
expect(counts).to.eql(_.times(20, i => 1))
return yield pool.end()
}))
const queryText = 'SELECT COUNT(*) as counts FROM pg_stat_activity WHERE ' + queryColumn + ' = $1'
const queries = _.times(20, () => pool.query(queryText, [queryText]))
const results = yield Promise.all(queries)
const counts = results.map((res) => parseInt(res.rows[0].counts, 10))
expect(counts).to.eql(_.times(20, (i) => 1))
return yield pool.end()
})
)
})

View File

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

View File

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

View File

@ -1,54 +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) {
}
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;
const start = this.offset
let end = start
while(this.buffer[end++] !== 0) { };
this.offset = end;
return this.buffer.toString(this.encoding, start, end - 1);
while (this.buffer[end++] !== 0) {}
this.offset = end
return this.buffer.toString(this.encoding, start, end - 1)
}
public bytes(length: number): Buffer {
const result = this.buffer.slice(this.offset, this.offset + length);
this.offset += length;
return result;
const result = this.buffer.slice(this.offset, this.offset + length)
this.offset += length
return result
}
}

View File

@ -1,87 +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)
}
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
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.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;
public addString(string: string = ''): Writer {
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)
}
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;
var result = this.join(code)
this.offset = 5
this.headerPosition = 0
this.buffer = Buffer.allocUnsafe(this.size)
return result;
return result
}
}

View File

@ -15,7 +15,8 @@ var bindCompleteBuffer = buffers.bindComplete()
var portalSuspendedBuffer = buffers.portalSuspended()
var addRow = function (bufferList: BufferList, name: string, offset: number) {
return bufferList.addCString(name) // field name
return bufferList
.addCString(name) // field name
.addInt32(offset++) // table id
.addInt16(offset++) // attribute of column number
.addInt32(offset++) // objectId of field's data type
@ -31,24 +32,25 @@ var row1 = {
dataTypeID: 3,
dataTypeSize: 4,
typeModifier: 5,
formatCode: 0
formatCode: 0,
}
var oneRowDescBuff = buffers.rowDescription([row1])
row1.name = 'bang'
var twoRowBuf = buffers.rowDescription([row1, {
name: 'whoah',
tableID: 10,
attributeNumber: 11,
dataTypeID: 12,
dataTypeSize: 13,
typeModifier: 14,
formatCode: 0
}])
var twoRowBuf = buffers.rowDescription([
row1,
{
name: 'whoah',
tableID: 10,
attributeNumber: 11,
dataTypeID: 12,
dataTypeSize: 13,
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([])
@ -62,32 +64,32 @@ var oneFieldBuf = buffers.dataRow(['test'])
var expectedAuthenticationOkayMessage = {
name: 'authenticationOk',
length: 8
length: 8,
}
var expectedParameterStatusMessage = {
name: 'parameterStatus',
parameterName: 'client_encoding',
parameterValue: 'UTF8',
length: 25
length: 25,
}
var expectedBackendKeyDataMessage = {
name: 'backendKeyData',
processID: 1,
secretKey: 2
secretKey: 2,
}
var expectedReadyForQueryMessage = {
name: 'readyForQuery',
length: 5,
status: 'I'
status: 'I',
}
var expectedCommandCompleteMessage = {
name: 'commandComplete',
length: 13,
text: 'SELECT 3'
text: 'SELECT 3',
}
var emptyRowDescriptionBuffer = new BufferList()
.addInt16(0) // number of fields
@ -103,45 +105,49 @@ var expectedOneRowMessage = {
name: 'rowDescription',
length: 27,
fieldCount: 1,
fields: [{
name: 'id',
tableID: 1,
columnID: 2,
dataTypeID: 3,
dataTypeSize: 4,
dataTypeModifier: 5,
format: 'text'
}]
fields: [
{
name: 'id',
tableID: 1,
columnID: 2,
dataTypeID: 3,
dataTypeSize: 4,
dataTypeModifier: 5,
format: 'text',
},
],
}
var expectedTwoRowMessage = {
name: 'rowDescription',
length: 53,
fieldCount: 2,
fields: [{
name: 'bang',
tableID: 1,
columnID: 2,
dataTypeID: 3,
dataTypeSize: 4,
dataTypeModifier: 5,
format: 'text'
},
{
name: 'whoah',
tableID: 10,
columnID: 11,
dataTypeID: 12,
dataTypeSize: 13,
dataTypeModifier: 14,
format: 'text'
}]
fields: [
{
name: 'bang',
tableID: 1,
columnID: 2,
dataTypeID: 3,
dataTypeSize: 4,
dataTypeModifier: 5,
format: 'text',
},
{
name: 'whoah',
tableID: 10,
columnID: 11,
dataTypeID: 12,
dataTypeSize: 13,
dataTypeModifier: 14,
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 [lastMessage] = messages
for (const key in expectedMessage) {
assert.deepEqual((lastMessage as any)[key], expectedMessage[key])
@ -156,17 +162,17 @@ var SASLContinueBuffer = buffers.authenticationSASLContinue()
var SASLFinalBuffer = buffers.authenticationSASLFinal()
var expectedPlainPasswordMessage = {
name: 'authenticationCleartextPassword'
name: 'authenticationCleartextPassword',
}
var expectedMD5PasswordMessage = {
name: 'authenticationMD5Password',
salt: Buffer.from([1, 2, 3, 4])
salt: Buffer.from([1, 2, 3, 4]),
}
var expectedSASLMessage = {
name: 'authenticationSASL',
mechanisms: ['SCRAM-SHA-256']
mechanisms: ['SCRAM-SHA-256'],
}
var expectedSASLContinueMessage = {
@ -184,15 +190,13 @@ var expectedNotificationResponseMessage = {
name: 'notification',
processId: 4,
channel: 'hi',
payload: 'boom'
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[] = []
@ -219,7 +223,7 @@ describe('PgPacketStream', function () {
})
testForMessage(Buffer.from([0x6e, 0, 0, 0, 4]), {
name: 'noData'
name: 'noData',
})
describe('rowDescription messages', function () {
@ -232,7 +236,7 @@ describe('PgPacketStream', function () {
describe('parsing empty row', function () {
testForMessage(emptyRowFieldBuf, {
name: 'dataRow',
fieldCount: 0
fieldCount: 0,
})
})
@ -240,7 +244,7 @@ describe('PgPacketStream', function () {
testForMessage(oneFieldBuf, {
name: 'dataRow',
fieldCount: 1,
fields: ['test']
fields: ['test'],
})
})
})
@ -250,55 +254,69 @@ describe('PgPacketStream', function () {
var buff = buffers.notice([{ type: 'C', value: 'code' }])
testForMessage(buff, {
name: 'notice',
code: 'code'
code: 'code',
})
})
testForMessage(buffers.error([]), {
name: 'error'
name: 'error',
})
describe('with all the fields', function () {
var buffer = buffers.error([{
type: 'S',
value: 'ERROR'
}, {
type: 'C',
value: 'code'
}, {
type: 'M',
value: 'message'
}, {
type: 'D',
value: 'details'
}, {
type: 'H',
value: 'hint'
}, {
type: 'P',
value: '100'
}, {
type: 'p',
value: '101'
}, {
type: 'q',
value: 'query'
}, {
type: 'W',
value: 'where'
}, {
type: 'F',
value: 'file'
}, {
type: 'L',
value: 'line'
}, {
type: 'R',
value: 'routine'
}, {
type: 'Z', // ignored
value: 'alsdkf'
}])
var buffer = buffers.error([
{
type: 'S',
value: 'ERROR',
},
{
type: 'C',
value: 'code',
},
{
type: 'M',
value: 'message',
},
{
type: 'D',
value: 'details',
},
{
type: 'H',
value: 'hint',
},
{
type: 'P',
value: '100',
},
{
type: 'p',
value: '101',
},
{
type: 'q',
value: 'query',
},
{
type: 'W',
value: 'where',
},
{
type: 'F',
value: 'file',
},
{
type: 'L',
value: 'line',
},
{
type: 'R',
value: 'routine',
},
{
type: 'Z', // ignored
value: 'alsdkf',
},
])
testForMessage(buffer, {
name: 'error',
@ -313,36 +331,36 @@ describe('PgPacketStream', function () {
where: 'where',
file: 'file',
line: 'line',
routine: 'routine'
routine: 'routine',
})
})
testForMessage(parseCompleteBuffer, {
name: 'parseComplete'
name: 'parseComplete',
})
testForMessage(bindCompleteBuffer, {
name: 'bindComplete'
name: 'bindComplete',
})
testForMessage(bindCompleteBuffer, {
name: 'bindComplete'
name: 'bindComplete',
})
testForMessage(buffers.closeComplete(), {
name: 'closeComplete'
name: 'closeComplete',
})
describe('parses portal suspended message', function () {
testForMessage(portalSuspendedBuffer, {
name: 'portalSuspended'
name: 'portalSuspended',
})
})
describe('parses replication start message', function () {
testForMessage(Buffer.from([0x57, 0x00, 0x00, 0x00, 0x04]), {
name: 'replicationStart',
length: 4
length: 4,
})
})
@ -351,28 +369,28 @@ describe('PgPacketStream', function () {
name: 'copyInResponse',
length: 7,
binary: false,
columnTypes: []
columnTypes: [],
})
testForMessage(buffers.copyIn(2), {
name: 'copyInResponse',
length: 11,
binary: false,
columnTypes: [0, 1]
columnTypes: [0, 1],
})
testForMessage(buffers.copyOut(0), {
name: 'copyOutResponse',
length: 7,
binary: false,
columnTypes: []
columnTypes: [],
})
testForMessage(buffers.copyOut(3), {
name: 'copyOutResponse',
length: 13,
binary: false,
columnTypes: [0, 1, 2]
columnTypes: [0, 1, 2],
})
testForMessage(buffers.copyDone(), {
@ -383,11 +401,10 @@ describe('PgPacketStream', function () {
testForMessage(buffers.copyData(Buffer.from([5, 6, 7])), {
name: 'copyData',
length: 7,
chunk: Buffer.from([5, 6, 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
@ -395,7 +412,7 @@ describe('PgPacketStream', function () {
var fullBuffer = buffers.dataRow([null, 'bang', 'zug zug', null, '!'])
it('parses when full buffer comes in', async function () {
const messages = await parseBuffers([fullBuffer]);
const messages = await parseBuffers([fullBuffer])
const message = messages[0] as any
assert.equal(message.fields.length, 5)
assert.equal(message.fields[0], null)
@ -410,7 +427,7 @@ describe('PgPacketStream', function () {
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 messages = await parseBuffers([fullBuffer])
const message = messages[0] as any
assert.equal(message.fields.length, 5)
assert.equal(message.fields[0], null)
@ -448,13 +465,13 @@ describe('PgPacketStream', function () {
name: 'dataRow',
fieldCount: 1,
length: 11,
fields: ['!']
fields: ['!'],
})
assert.equal(messages[0].fields[0], '!')
assert.deepEqual(messages[1], {
name: 'readyForQuery',
length: 5,
status: 'I'
status: 'I',
})
}
// sanity check
@ -480,17 +497,13 @@ describe('PgPacketStream', function () {
return Promise.all([
splitAndVerifyTwoMessages(fullBuffer.length - 1),
splitAndVerifyTwoMessages(fullBuffer.length - 4),
splitAndVerifyTwoMessages(fullBuffer.length - 6)
splitAndVerifyTwoMessages(fullBuffer.length - 6),
])
})
it('at the end', function () {
return Promise.all([
splitAndVerifyTwoMessages(8),
splitAndVerifyTwoMessages(1)
])
return Promise.all([splitAndVerifyTwoMessages(8), splitAndVerifyTwoMessages(1)])
})
})
})
})

View File

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

View File

@ -1,4 +1,4 @@
export type Mode = 'text' | 'binary';
export type Mode = 'text' | 'binary'
export const enum MessageName {
parseComplete = 'parseComplete',
@ -30,14 +30,14 @@ 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,
@ -51,7 +51,7 @@ export const closeComplete: BackendMessage = {
export const noData: BackendMessage = {
name: MessageName.noData,
length: 5
length: 5,
}
export const portalSuspended: BackendMessage = {
@ -75,136 +75,148 @@ export const copyDone: BackendMessage = {
}
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)
}
}
export class CopyDataMessage {
public readonly name = MessageName.copyData;
constructor(public readonly length: number, public readonly chunk: Buffer) {
}
public readonly name = MessageName.copyData
constructor(public readonly length: number, public readonly chunk: Buffer) {}
}
export class CopyResponse {
public readonly columnTypes: number[];
constructor(public readonly length: number, public readonly name: MessageName, public readonly binary: boolean, columnCount: number) {
this.columnTypes = new Array(columnCount);
public readonly columnTypes: number[]
constructor(
public readonly length: number,
public readonly name: MessageName,
public readonly binary: boolean,
columnCount: number
) {
this.columnTypes = new Array(columnCount)
}
}
export class Field {
constructor(public readonly name: string, public readonly tableID: number, public readonly columnID: number, public readonly dataTypeID: number, public readonly dataTypeSize: number, public readonly dataTypeModifier: number, public readonly format: Mode) {
}
constructor(
public readonly name: string,
public readonly tableID: number,
public readonly columnID: number,
public readonly dataTypeID: number,
public readonly dataTypeSize: number,
public readonly dataTypeModifier: number,
public readonly format: Mode
) {}
}
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)
}
}
export class ParameterStatusMessage {
public readonly name: MessageName = MessageName.parameterStatus;
constructor(public readonly length: number, public readonly parameterName: string, public readonly parameterValue: string) {
}
public readonly name: MessageName = MessageName.parameterStatus
constructor(
public readonly length: number,
public readonly parameterName: string,
public readonly parameterValue: string
) {}
}
export class AuthenticationMD5Password implements BackendMessage {
public readonly name: MessageName = MessageName.authenticationMD5Password;
constructor(public readonly length: number, public readonly salt: Buffer) {
}
public readonly name: MessageName = MessageName.authenticationMD5Password
constructor(public readonly length: number, public readonly salt: Buffer) {}
}
export class BackendKeyDataMessage {
public readonly name: MessageName = MessageName.backendKeyData;
constructor(public readonly length: number, public readonly processID: number, public readonly secretKey: number) {
}
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;
constructor(public readonly length: number, public readonly processId: number, public readonly channel: string, public readonly payload: string) {
}
public readonly name: MessageName = MessageName.notification
constructor(
public readonly length: number,
public readonly processId: number,
public readonly channel: string,
public readonly payload: string
) {}
}
export class ReadyForQueryMessage {
public readonly name: MessageName = MessageName.readyForQuery;
constructor(public readonly length: number, public readonly status: string) {
}
public readonly name: MessageName = MessageName.readyForQuery
constructor(public readonly length: number, public readonly status: string) {}
}
export class CommandCompleteMessage {
public readonly name: MessageName = MessageName.commandComplete
constructor(public readonly length: number, public readonly text: string) {
}
constructor(public readonly length: number, public readonly text: string) {}
}
export class DataRowMessage {
public readonly fieldCount: number;
public readonly fieldCount: number
public readonly name: MessageName = MessageName.dataRow
constructor(public length: number, public fields: any[]) {
this.fieldCount = fields.length;
this.fieldCount = fields.length
}
}
export class NoticeMessage implements BackendMessage, NoticeOrError {
constructor(public readonly length: number, public readonly message: string | undefined) {}
public readonly name = MessageName.notice;
public severity: string | undefined;
public code: string | undefined;
public detail: string | undefined;
public hint: string | undefined;
public position: string | undefined;
public internalPosition: string | undefined;
public internalQuery: string | undefined;
public where: string | undefined;
public schema: string | undefined;
public table: string | undefined;
public column: string | undefined;
public dataType: string | undefined;
public constraint: string | undefined;
public file: string | undefined;
public line: string | undefined;
public routine: string | undefined;
public readonly name = MessageName.notice
public severity: string | undefined
public code: string | undefined
public detail: string | undefined
public hint: string | undefined
public position: string | undefined
public internalPosition: string | undefined
public internalQuery: string | undefined
public where: string | undefined
public schema: string | undefined
public table: string | undefined
public column: string | undefined
public dataType: string | undefined
public constraint: string | undefined
public file: string | undefined
public line: string | undefined
public routine: string | undefined
}

View File

@ -6,18 +6,22 @@ describe('serializer', () => {
it('builds startup message', function () {
const actual = serialize.startup({
user: 'brian',
database: 'bang'
database: 'bang',
})
assert.deepEqual(actual, new BufferList()
.addInt16(3)
.addInt16(0)
.addCString('user')
.addCString('brian')
.addCString('database')
.addCString('bang')
.addCString('client_encoding')
.addCString("'utf-8'")
.addCString('').join(true))
assert.deepEqual(
actual,
new BufferList()
.addInt16(3)
.addInt16(0)
.addCString('user')
.addCString('brian')
.addCString('database')
.addCString('bang')
.addCString('client_encoding')
.addCString("'utf-8'")
.addCString('')
.join(true)
)
})
it('builds password message', function () {
@ -28,7 +32,7 @@ describe('serializer', () => {
it('builds request ssl message', function () {
const actual = serialize.requestSsl()
const expected = new BufferList().addInt32(80877103).join(true)
assert.deepEqual(actual, expected);
assert.deepEqual(actual, expected)
})
it('builds SASLInitialResponseMessage message', function () {
@ -36,28 +40,21 @@ describe('serializer', () => {
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'))
})
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'))
})
describe('parse message', () => {
it('builds parse message', function () {
const actual = serialize.parse({ text: '!' })
var expected = new BufferList()
.addCString('')
.addCString('!')
.addInt16(0).join(true, 'P')
var expected = new BufferList().addCString('').addCString('!').addInt16(0).join(true, 'P')
assert.deepEqual(actual, expected)
})
@ -65,12 +62,9 @@ describe('serializer', () => {
const actual = serialize.parse({
name: 'boom',
text: 'select * from boom',
types: []
types: [],
})
var expected = new BufferList()
.addCString('boom')
.addCString('select * from boom')
.addInt16(0).join(true, 'P')
var expected = new BufferList().addCString('boom').addCString('select * from boom').addInt16(0).join(true, 'P')
assert.deepEqual(actual, expected)
})
@ -78,7 +72,7 @@ describe('serializer', () => {
const actual = serialize.parse({
name: 'force',
text: 'select * from bang where name = $1',
types: [1, 2, 3, 4]
types: [1, 2, 3, 4],
})
var expected = new BufferList()
.addCString('force')
@ -87,13 +81,12 @@ describe('serializer', () => {
.addInt32(1)
.addInt32(2)
.addInt32(3)
.addInt32(4).join(true, 'P')
.addInt32(4)
.join(true, 'P')
assert.deepEqual(actual, expected)
})
})
describe('bind messages', function () {
it('with no values', function () {
const actual = serialize.bind()
@ -112,10 +105,10 @@ describe('serializer', () => {
const actual = serialize.bind({
portal: 'bang',
statement: 'woo',
values: ['1', 'hi', null, 'zing']
values: ['1', 'hi', null, 'zing'],
})
var expectedBuffer = new BufferList()
.addCString('bang') // portal name
.addCString('bang') // portal name
.addCString('woo') // statement name
.addInt16(0)
.addInt16(4)
@ -136,16 +129,16 @@ describe('serializer', () => {
const actual = serialize.bind({
portal: 'bang',
statement: 'woo',
values: ['1', 'hi', null, Buffer.from('zing', 'utf8')]
values: ['1', 'hi', null, Buffer.from('zing', 'utf8')],
})
var expectedBuffer = new BufferList()
.addCString('bang') // portal name
.addCString('bang') // portal name
.addCString('woo') // statement name
.addInt16(4)// value count
.addInt16(0)// string
.addInt16(0)// string
.addInt16(0)// string
.addInt16(1)// binary
.addInt16(4) // value count
.addInt16(0) // string
.addInt16(0) // string
.addInt16(0) // string
.addInt16(1) // binary
.addInt16(4)
.addInt32(1)
.add(Buffer.from('1'))
@ -162,22 +155,16 @@ describe('serializer', () => {
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')
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
rows: 100,
})
var expectedBuffer = new BufferList()
.addCString('my favorite portal')
.addInt32(100)
.join(true, 'E')
var expectedBuffer = new BufferList().addCString('my favorite portal').addInt32(100).join(true, 'E')
assert.deepEqual(actual, expectedBuffer)
})
})
@ -231,7 +218,7 @@ describe('serializer', () => {
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')
const expected = new BufferList().add(Buffer.from([1, 2, 3])).join(true, 'd')
assert.deepEqual(actual, expected)
})

View File

@ -1,22 +1,47 @@
import { TransformOptions } from 'stream';
import { Mode, bindComplete, parseComplete, closeComplete, noData, portalSuspended, copyDone, replicationStart, emptyQuery, ReadyForQueryMessage, CommandCompleteMessage, CopyDataMessage, CopyResponse, NotificationResponseMessage, RowDescriptionMessage, Field, DataRowMessage, ParameterStatusMessage, BackendKeyDataMessage, DatabaseError, BackendMessage, MessageName, AuthenticationMD5Password, NoticeMessage } from './messages';
import { BufferReader } from './buffer-reader';
import { TransformOptions } from 'stream'
import {
Mode,
bindComplete,
parseComplete,
closeComplete,
noData,
portalSuspended,
copyDone,
replicationStart,
emptyQuery,
ReadyForQueryMessage,
CommandCompleteMessage,
CopyDataMessage,
CopyResponse,
NotificationResponseMessage,
RowDescriptionMessage,
Field,
DataRowMessage,
ParameterStatusMessage,
BackendKeyDataMessage,
DatabaseError,
BackendMessage,
MessageName,
AuthenticationMD5Password,
NoticeMessage,
} 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
@ -46,118 +71,117 @@ 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')
}
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);
combinedBuffer = Buffer.allocUnsafe(this.remainingBuffer.byteLength + buffer.byteLength)
this.remainingBuffer.copy(combinedBuffer)
buffer.copy(combinedBuffer, this.remainingBuffer.byteLength)
}
let offset = 0;
while ((offset + HEADER_LENGTH) <= combinedBuffer.byteLength) {
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);
const message = this.handlePacket(offset + HEADER_LENGTH, code, length, combinedBuffer)
callback(message)
offset += fullMessageLength;
offset += fullMessageLength
} else {
break;
break
}
}
if (offset === combinedBuffer.byteLength) {
this.remainingBuffer = emptyBuffer;
this.remainingBuffer = emptyBuffer
} else {
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)}`)
}
}
private parseReadyForQueryMessage(offset: number, length: number, bytes: Buffer) {
this.reader.setBuffer(offset, bytes);
const status = this.reader.string(1);
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) {
@ -169,32 +193,32 @@ export class Parser {
}
private parseCopyMessage(offset: number, length: number, bytes: Buffer, messageName: MessageName) {
this.reader.setBuffer(offset, bytes);
const isBinary = this.reader.byte() !== 0;
this.reader.setBuffer(offset, bytes)
const isBinary = this.reader.byte() !== 0
const columnCount = this.reader.int16()
const message = new CopyResponse(length, messageName, isBinary, columnCount);
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);
this.reader.setBuffer(offset, bytes)
const fieldCount = this.reader.int16()
const message = new RowDescriptionMessage(length, fieldCount);
const message = new RowDescriptionMessage(length, fieldCount)
for (let i = 0; i < fieldCount; i++) {
message.fields[i] = this.parseField()
}
return message;
return message
}
private parseField(): Field {
@ -204,49 +228,48 @@ export class Parser {
const dataTypeID = this.reader.int32()
const dataTypeSize = this.reader.int16()
const dataTypeModifier = this.reader.int32()
const mode = this.reader.int16() === 0 ? 'text' : 'binary';
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)
}
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();
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);
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);
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
@ -255,14 +278,14 @@ export class Parser {
case 5: // AuthenticationMD5Password
if (message.length === 12) {
message.name = MessageName.authenticationMD5Password
const salt = this.reader.bytes(4);
return new AuthenticationMD5Password(length, salt);
const salt = this.reader.bytes(4)
return new AuthenticationMD5Password(length, salt)
}
break
case 10: // AuthenticationSASL
message.name = MessageName.authenticationSASL
message.mechanisms = []
let mechanism: string;
let mechanism: string
do {
mechanism = this.reader.cstring()
@ -270,23 +293,23 @@ export class Parser {
message.mechanisms.push(mechanism)
}
} while (mechanism)
break;
break
case 11: // AuthenticationSASLContinue
message.name = MessageName.authenticationSASLContinue
message.data = this.reader.string(length - 4)
break;
break
case 12: // AuthenticationSASLFinal
message.name = MessageName.authenticationSASLFinal
message.data = this.reader.string(length - 4)
break;
break
default:
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);
this.reader.setBuffer(offset, bytes)
const fields: Record<string, string> = {}
let fieldType = this.reader.string(1)
while (fieldType !== '\0') {
@ -296,7 +319,10 @@ export class Parser {
const messageValue = fields.M
const message = name === MessageName.notice ? new NoticeMessage(length, messageValue) : new DatabaseError(messageValue, length, name)
const message =
name === MessageName.notice
? new NoticeMessage(length, messageValue)
: new DatabaseError(messageValue, length, name)
message.severity = fields.S
message.code = fields.C
@ -314,6 +340,6 @@ export class Parser {
message.file = fields.F
message.line = fields.L
message.routine = fields.R
return message;
return message
}
}

View File

@ -13,7 +13,7 @@ const enum code {
describe = 0x44,
copyFromChunk = 0x64,
copyDone = 0x63,
copyFail = 0x66
copyFail = 0x66,
}
const writer = new Writer()
@ -32,15 +32,12 @@ const startup = (opts: Record<string, string>): Buffer => {
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(8, 0)
response.writeInt32BE(80877103, 4)
return response
}
@ -51,10 +48,7 @@ const password = (password: string): Buffer => {
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)
}
@ -68,9 +62,9 @@ const query = (text: string): Buffer => {
}
type ParseOpts = {
name?: string;
types?: number[];
text: string;
name?: string
types?: number[]
text: string
}
const emptyArray: any[] = []
@ -108,10 +102,10 @@ const parse = (query: ParseOpts): Buffer => {
}
type BindOpts = {
portal?: string;
binary?: boolean;
statement?: string;
values?: any[];
portal?: string
binary?: boolean
statement?: string
values?: any[]
}
const bind = (config: BindOpts = {}): Buffer => {
@ -128,9 +122,7 @@ const bind = (config: BindOpts = {}): 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)
} else {
@ -163,16 +155,16 @@ const bind = (config: BindOpts = {}): Buffer => {
}
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 execute = (config?: ExecOpts): Buffer => {
// this is the happy path for most queries
if (!config || !config.portal && !config.rows) {
return emptyExecute;
if (!config || (!config.portal && !config.rows)) {
return emptyExecute
}
const portal = config.portal || ''
@ -185,9 +177,9 @@ const execute = (config?: ExecOpts): Buffer => {
buff[0] = code.execute
buff.writeInt32BE(len, 1)
buff.write(portal, 5, 'utf-8')
buff[portalLength + 5] = 0; // null terminate portal cString
buff[portalLength + 5] = 0 // null terminate portal cString
buff.writeUInt32BE(rows, buff.length - 4)
return buff;
return buff
}
const cancel = (processID: number, secretKey: number): Buffer => {
@ -197,12 +189,12 @@ const cancel = (processID: number, secretKey: number): Buffer => {
buffer.writeInt16BE(5678, 6)
buffer.writeInt32BE(processID, 8)
buffer.writeInt32BE(secretKey, 12)
return buffer;
return buffer
}
type PortalOpts = {
type: 'S' | 'P',
name?: string;
type: 'S' | 'P'
name?: string
}
const cstringMessage = (code: code, string: string): Buffer => {
@ -221,11 +213,11 @@ 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;
return msg.name
? cstringMessage(code.describe, `${msg.type}${msg.name || ''}`)
: msg.type === 'P'
? emptyDescribePortal
: emptyDescribeStatement
}
const close = (msg: PortalOpts): Buffer => {
@ -238,7 +230,7 @@ const copyData = (chunk: Buffer): Buffer => {
}
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])
@ -266,7 +258,7 @@ const serialize = {
copyData,
copyDone: () => copyDoneBuffer,
copyFail,
cancel
cancel,
}
export { serialize }

View File

@ -1,7 +1,5 @@
export default class BufferList {
constructor(public buffers: Buffer[] = []) {
}
constructor(public buffers: Buffer[] = []) {}
public add(buffer: Buffer, front?: boolean) {
this.buffers[front ? 'unshift' : 'push'](buffer)
@ -9,7 +7,7 @@ export default class BufferList {
}
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) {
@ -19,12 +17,10 @@ export default class BufferList {
}
public addInt32(val: number, first?: boolean) {
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
)
}
public addCString(val: string, front?: boolean) {

View File

@ -3,21 +3,15 @@ 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 () {
@ -28,45 +22,27 @@ const buffers = {
},
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[]) {
@ -74,7 +50,8 @@ const buffers = {
var buf = new BufferList()
buf.addInt16(fields.length)
fields.forEach(function (field) {
buf.addCString(field.name)
buf
.addCString(field.name)
.addInt32(field.tableID || 0)
.addInt16(field.attributeNumber || 0)
.addInt32(field.dataTypeID || 0)
@ -116,7 +93,7 @@ const buffers = {
buf.addChar(field.type)
buf.addCString(field.value)
})
return buf.add(Buffer.from([0]))// terminator
return buf.add(Buffer.from([0])) // terminator
},
parseComplete: function () {
@ -128,11 +105,7 @@ const buffers = {
},
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 () {
@ -152,9 +125,9 @@ 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')
},
@ -164,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')
},
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')
}
},
}
export default buffers

View File

@ -3,7 +3,7 @@ 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)

View File

@ -4,8 +4,7 @@
"description": "Postgres query result returned as readable stream",
"main": "index.js",
"scripts": {
"test": "mocha",
"lint": "eslint ."
"test": "mocha"
},
"repository": {
"type": "git",

View File

@ -5,84 +5,87 @@ var QueryStream = require('../')
var helper = require('./helper')
if (process.version.startsWith('v8.')) {
return console.error('warning! node versions less than 10lts no longer supported & stream closing semantics may not behave properly');
}
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)
})
})
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
query.on('readable', function () {
readCount++
query.read()
})
query.once('readable', function () {
query.destroy()
})
query.on('close', function () {
assert(readCount < 10, 'should not have read more than 10 rows')
done()
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)
})
})
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')))
setTimeout(() => {
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
query.on('readable', function () {
readCount++
query.read()
})
query.once('readable', function () {
query.destroy()
})
query.on('close', function () {
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')))
setTimeout(() => {
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')))
setTimeout(() => {
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)
})
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')))
stream.once('error', () => {
stream.destroy()
// wait a bit to let any other errors shake through
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)
}, 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')))
setTimeout(() => {
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)
})
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')))
stream.once('error', () => {
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()
// wait a bit to let any other errors shake through
setTimeout(done, 100)
stream.on('close', done)
})
})
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)
})
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)
})
})
}

View File

@ -9,14 +9,20 @@ 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)
query.pipe(through(function (row) {
this.push(row.num)
})).pipe(concat(function (result) {
var total = result.reduce(function (prev, cur) {
return prev + cur
})
assert.equal(total, 20100)
}))
query
.pipe(
through(function (row) {
this.push(row.num)
})
)
.pipe(
concat(function (result) {
var total = result.reduce(function (prev, cur) {
return prev + cur
})
assert.equal(total, 20100)
})
)
stream.on('end', done)
})
})

View File

@ -5,14 +5,14 @@ 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
batchSize: 88,
})
assert.equal(stream._readableState.highWaterMark, 88)
})
it('sets readable.highWaterMark based on highWaterMark config', () => {
var stream = new QueryStream('SELECT NOW()', [], {
highWaterMark: 88
highWaterMark: 88,
})
assert.equal(stream._readableState.highWaterMark, 88)

View File

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

View File

@ -7,13 +7,15 @@ helper('error', function (client) {
it('receives error on stream', function (done) {
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()
}).on('data', function () {
// noop to kick of reading
})
query
.on('error', function (err) {
assert(err)
assert.equal(err.code, '42P01')
done()
})
.on('data', function () {
// noop to kick of reading
})
})
it('continues to function after stream', function (done) {

View File

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

View File

@ -2,12 +2,12 @@ var assert = require('assert')
var helper = require('./helper')
var QueryStream = require('../')
helper('passing options', function(client) {
it('passes row mode array', function(done) {
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 = []
query.on('data', datum => {
query.on('data', (datum) => {
result.push(datum)
})
query.on('end', () => {
@ -17,14 +17,14 @@ helper('passing options', function(client) {
})
})
it('passes custom types', function(done) {
it('passes custom types', function (done) {
const types = {
getTypeParser: () => string => string,
getTypeParser: () => (string) => string,
}
var stream = new QueryStream('SELECT * FROM generate_series(0, 10) num', [], { types })
var query = client.query(stream)
var result = []
query.on('data', datum => {
query.on('data', (datum) => {
result.push(datum)
})
query.on('end', () => {

View File

@ -7,12 +7,17 @@ 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 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()
}))
query
.pipe(JSONStream.stringify())
.pipe(pauser)
.pipe(
concat(function (json) {
JSON.parse(json)
done()
})
)
})
})

View File

@ -14,13 +14,18 @@ mapper._transform = function (obj, enc, cb) {
helper('slow reader', function (client) {
it('works', function (done) {
this.timeout(50000)
var stream = new QueryStream('SELECT * FROM generate_series(0, 201) num', [], { highWaterMark: 100, batchSize: 50 })
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)
stream.pipe(mapper).pipe(concat(function (res) {
done()
}))
stream.pipe(mapper).pipe(
concat(function (res) {
done()
})
)
})
})

View File

@ -4,15 +4,14 @@ 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 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()
query.on('end', function () {
ended = true
})
spec(query).readable().pausable({ strict: true }).validateOnExit()
var checkListeners = function () {
assert(stream.listeners('end').length < 10)
if (!ended) {

View File

@ -6,10 +6,7 @@ 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()
spec(query).readable().pausable({ strict: true }).validateOnExit()
stream.on('end', done)
})
})

View File

@ -7,7 +7,7 @@ params := $(connectionString)
node-command := xargs -n 1 -I file node file $(params)
.PHONY : test test-connection test-integration bench test-native \
lint publish test-missing-native update-npm
publish test-missing-native update-npm
all:
npm install
@ -17,7 +17,7 @@ help:
test: test-unit
test-all: lint test-missing-native test-unit test-integration test-native
test-all: test-missing-native test-unit test-integration test-native
update-npm:
@ -59,7 +59,3 @@ test-binary: test-connection
test-pool:
@find test/integration/connection-pool -name "*.js" | $(node-command) binary
lint:
@echo "***Starting lint***"
node_modules/.bin/eslint lib

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -32,18 +32,11 @@
"async": "0.9.0",
"bluebird": "3.5.2",
"co": "4.6.0",
"eslint": "^6.0.1",
"eslint-config-standard": "^13.0.1",
"eslint-plugin-import": "^2.18.1",
"eslint-plugin-node": "^9.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.0",
"pg-copy-streams": "0.3.0"
},
"minNativeVersion": "2.0.0",
"scripts": {
"test": "make test-all",
"lint": "make lint"
"test": "make test-all"
},
"files": [
"lib",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,37 +1,44 @@
'use strict'
var helper = require('./test-helper')
function testTypeParser (client, expectedResult, done) {
function testTypeParser(client, expectedResult, done) {
var boolValue = true
client.query('CREATE TEMP TABLE parserOverrideTest(id bool)')
client.query('INSERT INTO parserOverrideTest(id) VALUES ($1)', [boolValue])
client.query('SELECT * FROM parserOverrideTest', assert.success(function (result) {
assert.equal(result.rows[0].id, expectedResult)
done()
}))
client.query(
'SELECT * FROM parserOverrideTest',
assert.success(function (result) {
assert.equal(result.rows[0].id, expectedResult)
done()
})
)
}
const pool = new helper.pg.Pool(helper.config)
pool.connect(assert.success(function (client1, done1) {
pool.connect(assert.success(function (client2, done2) {
var boolTypeOID = 16
client1.setTypeParser(boolTypeOID, function () {
return 'first client'
})
client2.setTypeParser(boolTypeOID, function () {
return 'second client'
})
pool.connect(
assert.success(function (client1, done1) {
pool.connect(
assert.success(function (client2, done2) {
var boolTypeOID = 16
client1.setTypeParser(boolTypeOID, function () {
return 'first client'
})
client2.setTypeParser(boolTypeOID, function () {
return 'second client'
})
client1.setTypeParser(boolTypeOID, 'binary', function () {
return 'first client binary'
})
client2.setTypeParser(boolTypeOID, 'binary', function () {
return 'second client binary'
})
client1.setTypeParser(boolTypeOID, 'binary', function () {
return 'first client binary'
})
client2.setTypeParser(boolTypeOID, 'binary', function () {
return 'second client binary'
})
testTypeParser(client1, 'first client', () => {
done1()
testTypeParser(client2, 'second client', () => done2(), pool.end())
})
}))
}))
testTypeParser(client1, 'first client', () => {
done1()
testTypeParser(client2, 'second client', () => done2(), pool.end())
})
})
)
})
)

View File

@ -6,99 +6,135 @@ const native = helper.args.native
const suite = new helper.Suite()
suite.test('connecting to invalid port', (cb) => {
const pool = new pg.Pool({ port: 13801 })
pool.connect().catch(e => cb())
pool.connect().catch((e) => cb())
})
suite.test('errors emitted on checked-out clients', (cb) => {
// make pool hold 2 clients
const pool = new pg.Pool({ max: 2 })
// get first client
pool.connect(assert.success(function (client, done) {
client.query('SELECT NOW()', function () {
pool.connect(assert.success(function (client2, done2) {
var pidColName = 'procpid'
helper.versionGTE(client2, 90200, assert.success(function (isGreater) {
var killIdleQuery = 'SELECT pid, (SELECT pg_terminate_backend(pid)) AS killed FROM pg_stat_activity WHERE state = $1'
var params = ['idle']
if (!isGreater) {
killIdleQuery = 'SELECT procpid, (SELECT pg_terminate_backend(procpid)) AS killed FROM pg_stat_activity WHERE current_query LIKE $1'
params = ['%IDLE%']
}
pool.connect(
assert.success(function (client, done) {
client.query('SELECT NOW()', function () {
pool.connect(
assert.success(function (client2, done2) {
var pidColName = 'procpid'
helper.versionGTE(
client2,
90200,
assert.success(function (isGreater) {
var killIdleQuery =
'SELECT pid, (SELECT pg_terminate_backend(pid)) AS killed FROM pg_stat_activity WHERE state = $1'
var params = ['idle']
if (!isGreater) {
killIdleQuery =
'SELECT procpid, (SELECT pg_terminate_backend(procpid)) AS killed FROM pg_stat_activity WHERE current_query LIKE $1'
params = ['%IDLE%']
}
client.once('error', (err) => {
client.on('error', (err) => {})
done(err)
cb()
client.once('error', (err) => {
client.on('error', (err) => {})
done(err)
cb()
})
// kill the connection from client
client2.query(
killIdleQuery,
params,
assert.success(function (res) {
// check to make sure client connection actually was killed
// return client2 to the pool
done2()
pool.end()
})
)
})
)
})
// kill the connection from client
client2.query(killIdleQuery, params, assert.success(function (res) {
// check to make sure client connection actually was killed
// return client2 to the pool
done2()
pool.end()
}))
}))
}))
)
})
})
}))
)
})
suite.test('connection-level errors cause queued queries to fail', (cb) => {
const pool = new pg.Pool()
pool.connect(assert.success((client, done) => {
client.query('SELECT pg_terminate_backend(pg_backend_pid())', assert.calls((err) => {
if (helper.args.native) {
assert.ok(err)
} else {
assert.equal(err.code, '57P01')
}
}))
pool.connect(
assert.success((client, done) => {
client.query(
'SELECT pg_terminate_backend(pg_backend_pid())',
assert.calls((err) => {
if (helper.args.native) {
assert.ok(err)
} else {
assert.equal(err.code, '57P01')
}
})
)
client.once('error', assert.calls((err) => {
client.on('error', (err) => {})
}))
client.once(
'error',
assert.calls((err) => {
client.on('error', (err) => {})
})
)
client.query('SELECT 1', assert.calls((err) => {
if (helper.args.native) {
assert.equal(err.message, 'terminating connection due to administrator command')
} else {
assert.equal(err.message, 'Connection terminated unexpectedly')
}
client.query(
'SELECT 1',
assert.calls((err) => {
if (helper.args.native) {
assert.equal(err.message, 'terminating connection due to administrator command')
} else {
assert.equal(err.message, 'Connection terminated unexpectedly')
}
done(err)
pool.end()
cb()
}))
}))
done(err)
pool.end()
cb()
})
)
})
)
})
suite.test('connection-level errors cause future queries to fail', (cb) => {
const pool = new pg.Pool()
pool.connect(assert.success((client, done) => {
client.query('SELECT pg_terminate_backend(pg_backend_pid())', assert.calls((err) => {
if (helper.args.native) {
assert.ok(err)
} else {
assert.equal(err.code, '57P01')
}
}))
pool.connect(
assert.success((client, done) => {
client.query(
'SELECT pg_terminate_backend(pg_backend_pid())',
assert.calls((err) => {
if (helper.args.native) {
assert.ok(err)
} else {
assert.equal(err.code, '57P01')
}
})
)
client.once('error', assert.calls((err) => {
client.on('error', (err) => {})
client.query('SELECT 1', assert.calls((err) => {
if (helper.args.native) {
assert.equal(err.message, 'terminating connection due to administrator command')
} else {
assert.equal(err.message, 'Client has encountered a connection error and is not queryable')
}
client.once(
'error',
assert.calls((err) => {
client.on('error', (err) => {})
client.query(
'SELECT 1',
assert.calls((err) => {
if (helper.args.native) {
assert.equal(err.message, 'terminating connection due to administrator command')
} else {
assert.equal(err.message, 'Client has encountered a connection error and is not queryable')
}
done(err)
pool.end()
cb()
}))
}))
}))
done(err)
pool.end()
cb()
})
)
})
)
})
)
})
suite.test('handles socket error during pool.query and destroys it immediately', (cb) => {

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