mirror of
https://github.com/brianc/node-postgres.git
synced 2025-12-08 20:16:25 +00:00
commit
2ef5550373
23
.eslintrc
23
.eslintrc
@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
21
package.json
21
package.json
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -1,18 +1,16 @@
|
||||
'use strict'
|
||||
const EventEmitter = require('events').EventEmitter
|
||||
|
||||
const NOOP = function () { }
|
||||
const NOOP = function () {}
|
||||
|
||||
const removeWhere = (list, predicate) => {
|
||||
const i = list.findIndex(predicate)
|
||||
|
||||
return i === -1
|
||||
? undefined
|
||||
: list.splice(i, 1)[0]
|
||||
return i === -1 ? undefined : list.splice(i, 1)[0]
|
||||
}
|
||||
|
||||
class IdleItem {
|
||||
constructor (client, idleListener, timeoutId) {
|
||||
constructor(client, idleListener, timeoutId) {
|
||||
this.client = client
|
||||
this.idleListener = idleListener
|
||||
this.timeoutId = timeoutId
|
||||
@ -20,16 +18,16 @@ class IdleItem {
|
||||
}
|
||||
|
||||
class PendingItem {
|
||||
constructor (callback) {
|
||||
constructor(callback) {
|
||||
this.callback = callback
|
||||
}
|
||||
}
|
||||
|
||||
function throwOnDoubleRelease () {
|
||||
function throwOnDoubleRelease() {
|
||||
throw new Error('Release called on client which has already been released to the pool.')
|
||||
}
|
||||
|
||||
function promisify (Promise, callback) {
|
||||
function promisify(Promise, callback) {
|
||||
if (callback) {
|
||||
return { callback: callback, result: undefined }
|
||||
}
|
||||
@ -45,8 +43,8 @@ function promisify (Promise, callback) {
|
||||
return { callback: cb, result: result }
|
||||
}
|
||||
|
||||
function makeIdleListener (pool, client) {
|
||||
return function idleListener (err) {
|
||||
function makeIdleListener(pool, client) {
|
||||
return function idleListener(err) {
|
||||
err.client = client
|
||||
|
||||
client.removeListener('error', idleListener)
|
||||
@ -61,7 +59,7 @@ function makeIdleListener (pool, client) {
|
||||
}
|
||||
|
||||
class Pool extends EventEmitter {
|
||||
constructor (options, Client) {
|
||||
constructor(options, Client) {
|
||||
super()
|
||||
this.options = Object.assign({}, options)
|
||||
|
||||
@ -72,13 +70,13 @@ class Pool extends EventEmitter {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
value: options.password
|
||||
value: options.password,
|
||||
})
|
||||
}
|
||||
|
||||
this.options.max = this.options.max || this.options.poolSize || 10
|
||||
this.options.maxUses = this.options.maxUses || Infinity
|
||||
this.log = this.options.log || function () { }
|
||||
this.log = this.options.log || function () {}
|
||||
this.Client = this.options.Client || Client || require('pg').Client
|
||||
this.Promise = this.options.Promise || global.Promise
|
||||
|
||||
@ -94,11 +92,11 @@ class Pool extends EventEmitter {
|
||||
this.ended = false
|
||||
}
|
||||
|
||||
_isFull () {
|
||||
_isFull() {
|
||||
return this._clients.length >= this.options.max
|
||||
}
|
||||
|
||||
_pulseQueue () {
|
||||
_pulseQueue() {
|
||||
this.log('pulse queue')
|
||||
if (this.ended) {
|
||||
this.log('pulse queue ended')
|
||||
@ -107,7 +105,7 @@ class Pool extends EventEmitter {
|
||||
if (this.ending) {
|
||||
this.log('pulse queue on ending')
|
||||
if (this._idle.length) {
|
||||
this._idle.slice().map(item => {
|
||||
this._idle.slice().map((item) => {
|
||||
this._remove(item.client)
|
||||
})
|
||||
}
|
||||
@ -141,22 +139,19 @@ class Pool extends EventEmitter {
|
||||
throw new Error('unexpected condition')
|
||||
}
|
||||
|
||||
_remove (client) {
|
||||
const removed = removeWhere(
|
||||
this._idle,
|
||||
item => item.client === client
|
||||
)
|
||||
_remove(client) {
|
||||
const removed = removeWhere(this._idle, (item) => item.client === client)
|
||||
|
||||
if (removed !== undefined) {
|
||||
clearTimeout(removed.timeoutId)
|
||||
}
|
||||
|
||||
this._clients = this._clients.filter(c => c !== client)
|
||||
this._clients = this._clients.filter((c) => c !== client)
|
||||
client.end()
|
||||
this.emit('remove', client)
|
||||
}
|
||||
|
||||
connect (cb) {
|
||||
connect(cb) {
|
||||
if (this.ending) {
|
||||
const err = new Error('Cannot use a pool after calling end on the pool')
|
||||
return cb ? cb(err) : this.Promise.reject(err)
|
||||
@ -202,7 +197,7 @@ class Pool extends EventEmitter {
|
||||
return result
|
||||
}
|
||||
|
||||
newClient (pendingItem) {
|
||||
newClient(pendingItem) {
|
||||
const client = new this.Client(this.options)
|
||||
this._clients.push(client)
|
||||
const idleListener = makeIdleListener(this, client)
|
||||
@ -230,7 +225,7 @@ class Pool extends EventEmitter {
|
||||
if (err) {
|
||||
this.log('client failed to connect', err)
|
||||
// remove the dead client from our list of clients
|
||||
this._clients = this._clients.filter(c => c !== client)
|
||||
this._clients = this._clients.filter((c) => c !== client)
|
||||
if (timeoutHit) {
|
||||
err.message = 'Connection terminated due to connection timeout'
|
||||
}
|
||||
@ -250,7 +245,7 @@ class Pool extends EventEmitter {
|
||||
}
|
||||
|
||||
// acquire a client for a pending work item
|
||||
_acquireClient (client, pendingItem, idleListener, isNew) {
|
||||
_acquireClient(client, pendingItem, idleListener, isNew) {
|
||||
if (isNew) {
|
||||
this.emit('connect', client)
|
||||
}
|
||||
@ -294,7 +289,7 @@ class Pool extends EventEmitter {
|
||||
|
||||
// release a client back to the poll, include an error
|
||||
// to remove it from the pool
|
||||
_release (client, idleListener, err) {
|
||||
_release(client, idleListener, err) {
|
||||
client.on('error', idleListener)
|
||||
|
||||
client._poolUseCount = (client._poolUseCount || 0) + 1
|
||||
@ -322,7 +317,7 @@ class Pool extends EventEmitter {
|
||||
this._pulseQueue()
|
||||
}
|
||||
|
||||
query (text, values, cb) {
|
||||
query(text, values, cb) {
|
||||
// guard clause against passing a function as the first parameter
|
||||
if (typeof text === 'function') {
|
||||
const response = promisify(this.Promise, text)
|
||||
@ -375,7 +370,7 @@ class Pool extends EventEmitter {
|
||||
return response.result
|
||||
}
|
||||
|
||||
end (cb) {
|
||||
end(cb) {
|
||||
this.log('ending')
|
||||
if (this.ending) {
|
||||
const err = new Error('Called end on pool more than once')
|
||||
@ -388,15 +383,15 @@ class Pool extends EventEmitter {
|
||||
return promised.result
|
||||
}
|
||||
|
||||
get waitingCount () {
|
||||
get waitingCount() {
|
||||
return this._pendingQueue.length
|
||||
}
|
||||
|
||||
get idleCount () {
|
||||
get idleCount() {
|
||||
return this._idle.length
|
||||
}
|
||||
|
||||
get totalCount () {
|
||||
get totalCount() {
|
||||
return this._clients.length
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,29 +8,35 @@ const BluebirdPromise = require('bluebird')
|
||||
|
||||
const Pool = require('../')
|
||||
|
||||
const checkType = promise => {
|
||||
const checkType = (promise) => {
|
||||
expect(promise).to.be.a(BluebirdPromise)
|
||||
return promise.catch(e => undefined)
|
||||
return promise.catch((e) => undefined)
|
||||
}
|
||||
|
||||
describe('Bring your own promise', function () {
|
||||
it('uses supplied promise for operations', co.wrap(function * () {
|
||||
const pool = new Pool({ Promise: BluebirdPromise })
|
||||
const client1 = yield checkType(pool.connect())
|
||||
client1.release()
|
||||
yield checkType(pool.query('SELECT NOW()'))
|
||||
const client2 = yield checkType(pool.connect())
|
||||
// TODO - make sure pg supports BYOP as well
|
||||
client2.release()
|
||||
yield checkType(pool.end())
|
||||
}))
|
||||
it(
|
||||
'uses supplied promise for operations',
|
||||
co.wrap(function* () {
|
||||
const pool = new Pool({ Promise: BluebirdPromise })
|
||||
const client1 = yield checkType(pool.connect())
|
||||
client1.release()
|
||||
yield checkType(pool.query('SELECT NOW()'))
|
||||
const client2 = yield checkType(pool.connect())
|
||||
// TODO - make sure pg supports BYOP as well
|
||||
client2.release()
|
||||
yield checkType(pool.end())
|
||||
})
|
||||
)
|
||||
|
||||
it('uses promises in errors', co.wrap(function * () {
|
||||
const pool = new Pool({ Promise: BluebirdPromise, port: 48484 })
|
||||
yield checkType(pool.connect())
|
||||
yield checkType(pool.end())
|
||||
yield checkType(pool.connect())
|
||||
yield checkType(pool.query())
|
||||
yield checkType(pool.end())
|
||||
}))
|
||||
it(
|
||||
'uses promises in errors',
|
||||
co.wrap(function* () {
|
||||
const pool = new Pool({ Promise: BluebirdPromise, port: 48484 })
|
||||
yield checkType(pool.connect())
|
||||
yield checkType(pool.end())
|
||||
yield checkType(pool.connect())
|
||||
yield checkType(pool.query())
|
||||
yield checkType(pool.end())
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@ -15,10 +15,10 @@ describe('Connection strings', function () {
|
||||
connect: function (cb) {
|
||||
cb(new Error('testing'))
|
||||
},
|
||||
on: function () { }
|
||||
on: function () {},
|
||||
}
|
||||
},
|
||||
connectionString: connectionString
|
||||
connectionString: connectionString,
|
||||
})
|
||||
|
||||
pool.connect(function (err, client) {
|
||||
@ -27,4 +27,3 @@ describe('Connection strings', function () {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -43,7 +43,7 @@ describe('connection timeout', () => {
|
||||
|
||||
it('should reject promise with an error if timeout is passed', (done) => {
|
||||
const pool = new Pool({ connectionTimeoutMillis: 10, port: this.port, host: 'localhost' })
|
||||
pool.connect().catch(err => {
|
||||
pool.connect().catch((err) => {
|
||||
expect(err).to.be.an(Error)
|
||||
expect(err.message).to.contain('timeout')
|
||||
expect(pool.idleCount).to.equal(0)
|
||||
@ -51,18 +51,23 @@ describe('connection timeout', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle multiple timeouts', co.wrap(function* () {
|
||||
const errors = []
|
||||
const pool = new Pool({ connectionTimeoutMillis: 1, port: this.port, host: 'localhost' })
|
||||
for (var i = 0; i < 15; i++) {
|
||||
try {
|
||||
yield pool.connect()
|
||||
} catch (e) {
|
||||
errors.push(e)
|
||||
}
|
||||
}
|
||||
expect(errors).to.have.length(15)
|
||||
}.bind(this)))
|
||||
it(
|
||||
'should handle multiple timeouts',
|
||||
co.wrap(
|
||||
function* () {
|
||||
const errors = []
|
||||
const pool = new Pool({ connectionTimeoutMillis: 1, port: this.port, host: 'localhost' })
|
||||
for (var i = 0; i < 15; i++) {
|
||||
try {
|
||||
yield pool.connect()
|
||||
} catch (e) {
|
||||
errors.push(e)
|
||||
}
|
||||
}
|
||||
expect(errors).to.have.length(15)
|
||||
}.bind(this)
|
||||
)
|
||||
)
|
||||
|
||||
it('should timeout on checkout of used connection', (done) => {
|
||||
const pool = new Pool({ connectionTimeoutMillis: 100, max: 1 })
|
||||
@ -153,7 +158,7 @@ describe('connection timeout', () => {
|
||||
const pool = new Pool({
|
||||
Client: Client,
|
||||
connectionTimeoutMillis: 1000,
|
||||
max: 1
|
||||
max: 1,
|
||||
})
|
||||
|
||||
pool.connect((err, client, release) => {
|
||||
@ -199,7 +204,7 @@ describe('connection timeout', () => {
|
||||
const pool = new Pool({
|
||||
Client: Client,
|
||||
connectionTimeoutMillis: 1000,
|
||||
max: 1
|
||||
max: 1,
|
||||
})
|
||||
|
||||
// Direct connect
|
||||
|
||||
@ -17,18 +17,24 @@ describe('pool ending', () => {
|
||||
return new Pool().end()
|
||||
})
|
||||
|
||||
it('ends with clients', co.wrap(function * () {
|
||||
const pool = new Pool()
|
||||
const res = yield pool.query('SELECT $1::text as name', ['brianc'])
|
||||
expect(res.rows[0].name).to.equal('brianc')
|
||||
return pool.end()
|
||||
}))
|
||||
it(
|
||||
'ends with clients',
|
||||
co.wrap(function* () {
|
||||
const pool = new Pool()
|
||||
const res = yield pool.query('SELECT $1::text as name', ['brianc'])
|
||||
expect(res.rows[0].name).to.equal('brianc')
|
||||
return pool.end()
|
||||
})
|
||||
)
|
||||
|
||||
it('allows client to finish', co.wrap(function * () {
|
||||
const pool = new Pool()
|
||||
const query = pool.query('SELECT $1::text as name', ['brianc'])
|
||||
yield pool.end()
|
||||
const res = yield query
|
||||
expect(res.rows[0].name).to.equal('brianc')
|
||||
}))
|
||||
it(
|
||||
'allows client to finish',
|
||||
co.wrap(function* () {
|
||||
const pool = new Pool()
|
||||
const query = pool.query('SELECT $1::text as name', ['brianc'])
|
||||
yield pool.end()
|
||||
const res = yield query
|
||||
expect(res.rows[0].name).to.equal('brianc')
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@ -16,12 +16,15 @@ describe('pool error handling', function () {
|
||||
function runErrorQuery() {
|
||||
shouldGet++
|
||||
return new Promise(function (resolve, reject) {
|
||||
pool.query("SELECT 'asd'+1 ").then(function (res) {
|
||||
reject(res) // this should always error
|
||||
}).catch(function (err) {
|
||||
errors++
|
||||
resolve(err)
|
||||
})
|
||||
pool
|
||||
.query("SELECT 'asd'+1 ")
|
||||
.then(function (res) {
|
||||
reject(res) // this should always error
|
||||
})
|
||||
.catch(function (err) {
|
||||
errors++
|
||||
resolve(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
const ps = []
|
||||
@ -35,14 +38,17 @@ describe('pool error handling', function () {
|
||||
})
|
||||
|
||||
describe('calling release more than once', () => {
|
||||
it('should throw each time', co.wrap(function* () {
|
||||
const pool = new Pool()
|
||||
const client = yield pool.connect()
|
||||
client.release()
|
||||
expect(() => client.release()).to.throwError()
|
||||
expect(() => client.release()).to.throwError()
|
||||
return yield pool.end()
|
||||
}))
|
||||
it(
|
||||
'should throw each time',
|
||||
co.wrap(function* () {
|
||||
const pool = new Pool()
|
||||
const client = yield pool.connect()
|
||||
client.release()
|
||||
expect(() => client.release()).to.throwError()
|
||||
expect(() => client.release()).to.throwError()
|
||||
return yield pool.end()
|
||||
})
|
||||
)
|
||||
|
||||
it('should throw each time with callbacks', function (done) {
|
||||
const pool = new Pool()
|
||||
@ -75,17 +81,16 @@ describe('pool error handling', function () {
|
||||
it('rejects all additional promises', (done) => {
|
||||
const pool = new Pool()
|
||||
const promises = []
|
||||
pool.end()
|
||||
.then(() => {
|
||||
const squash = promise => promise.catch(e => 'okay!')
|
||||
promises.push(squash(pool.connect()))
|
||||
promises.push(squash(pool.query('SELECT NOW()')))
|
||||
promises.push(squash(pool.end()))
|
||||
Promise.all(promises).then(res => {
|
||||
expect(res).to.eql(['okay!', 'okay!', 'okay!'])
|
||||
done()
|
||||
})
|
||||
pool.end().then(() => {
|
||||
const squash = (promise) => promise.catch((e) => 'okay!')
|
||||
promises.push(squash(pool.connect()))
|
||||
promises.push(squash(pool.query('SELECT NOW()')))
|
||||
promises.push(squash(pool.end()))
|
||||
Promise.all(promises).then((res) => {
|
||||
expect(res).to.eql(['okay!', 'okay!', 'okay!'])
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('returns an error on all additional callbacks', (done) => {
|
||||
@ -106,68 +111,74 @@ describe('pool error handling', function () {
|
||||
})
|
||||
|
||||
describe('error from idle client', () => {
|
||||
it('removes client from pool', co.wrap(function* () {
|
||||
const pool = new Pool()
|
||||
const client = yield pool.connect()
|
||||
expect(pool.totalCount).to.equal(1)
|
||||
expect(pool.waitingCount).to.equal(0)
|
||||
expect(pool.idleCount).to.equal(0)
|
||||
client.release()
|
||||
yield new Promise((resolve, reject) => {
|
||||
process.nextTick(() => {
|
||||
let poolError
|
||||
pool.once('error', (err) => {
|
||||
poolError = err
|
||||
it(
|
||||
'removes client from pool',
|
||||
co.wrap(function* () {
|
||||
const pool = new Pool()
|
||||
const client = yield pool.connect()
|
||||
expect(pool.totalCount).to.equal(1)
|
||||
expect(pool.waitingCount).to.equal(0)
|
||||
expect(pool.idleCount).to.equal(0)
|
||||
client.release()
|
||||
yield new Promise((resolve, reject) => {
|
||||
process.nextTick(() => {
|
||||
let poolError
|
||||
pool.once('error', (err) => {
|
||||
poolError = err
|
||||
})
|
||||
|
||||
let clientError
|
||||
client.once('error', (err) => {
|
||||
clientError = err
|
||||
})
|
||||
|
||||
client.emit('error', new Error('expected'))
|
||||
|
||||
expect(clientError.message).to.equal('expected')
|
||||
expect(poolError.message).to.equal('expected')
|
||||
expect(pool.idleCount).to.equal(0)
|
||||
expect(pool.totalCount).to.equal(0)
|
||||
pool.end().then(resolve, reject)
|
||||
})
|
||||
|
||||
let clientError
|
||||
client.once('error', (err) => {
|
||||
clientError = err
|
||||
})
|
||||
|
||||
client.emit('error', new Error('expected'))
|
||||
|
||||
expect(clientError.message).to.equal('expected')
|
||||
expect(poolError.message).to.equal('expected')
|
||||
expect(pool.idleCount).to.equal(0)
|
||||
expect(pool.totalCount).to.equal(0)
|
||||
pool.end().then(resolve, reject)
|
||||
})
|
||||
})
|
||||
}))
|
||||
)
|
||||
})
|
||||
|
||||
describe('error from in-use client', () => {
|
||||
it('keeps the client in the pool', co.wrap(function* () {
|
||||
const pool = new Pool()
|
||||
const client = yield pool.connect()
|
||||
expect(pool.totalCount).to.equal(1)
|
||||
expect(pool.waitingCount).to.equal(0)
|
||||
expect(pool.idleCount).to.equal(0)
|
||||
it(
|
||||
'keeps the client in the pool',
|
||||
co.wrap(function* () {
|
||||
const pool = new Pool()
|
||||
const client = yield pool.connect()
|
||||
expect(pool.totalCount).to.equal(1)
|
||||
expect(pool.waitingCount).to.equal(0)
|
||||
expect(pool.idleCount).to.equal(0)
|
||||
|
||||
yield new Promise((resolve, reject) => {
|
||||
process.nextTick(() => {
|
||||
let poolError
|
||||
pool.once('error', (err) => {
|
||||
poolError = err
|
||||
yield new Promise((resolve, reject) => {
|
||||
process.nextTick(() => {
|
||||
let poolError
|
||||
pool.once('error', (err) => {
|
||||
poolError = err
|
||||
})
|
||||
|
||||
let clientError
|
||||
client.once('error', (err) => {
|
||||
clientError = err
|
||||
})
|
||||
|
||||
client.emit('error', new Error('expected'))
|
||||
|
||||
expect(clientError.message).to.equal('expected')
|
||||
expect(poolError).not.to.be.ok()
|
||||
expect(pool.idleCount).to.equal(0)
|
||||
expect(pool.totalCount).to.equal(1)
|
||||
client.release()
|
||||
pool.end().then(resolve, reject)
|
||||
})
|
||||
|
||||
let clientError
|
||||
client.once('error', (err) => {
|
||||
clientError = err
|
||||
})
|
||||
|
||||
client.emit('error', new Error('expected'))
|
||||
|
||||
expect(clientError.message).to.equal('expected')
|
||||
expect(poolError).not.to.be.ok()
|
||||
expect(pool.idleCount).to.equal(0)
|
||||
expect(pool.totalCount).to.equal(1)
|
||||
client.release()
|
||||
pool.end().then(resolve, reject)
|
||||
})
|
||||
})
|
||||
}))
|
||||
)
|
||||
})
|
||||
|
||||
describe('passing a function to pool.query', () => {
|
||||
@ -182,30 +193,35 @@ describe('pool error handling', function () {
|
||||
})
|
||||
|
||||
describe('pool with lots of errors', () => {
|
||||
it('continues to work and provide new clients', co.wrap(function* () {
|
||||
const pool = new Pool({ max: 1 })
|
||||
const errors = []
|
||||
for (var i = 0; i < 20; i++) {
|
||||
try {
|
||||
yield pool.query('invalid sql')
|
||||
} catch (err) {
|
||||
errors.push(err)
|
||||
it(
|
||||
'continues to work and provide new clients',
|
||||
co.wrap(function* () {
|
||||
const pool = new Pool({ max: 1 })
|
||||
const errors = []
|
||||
for (var i = 0; i < 20; i++) {
|
||||
try {
|
||||
yield pool.query('invalid sql')
|
||||
} catch (err) {
|
||||
errors.push(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
expect(errors).to.have.length(20)
|
||||
expect(pool.idleCount).to.equal(0)
|
||||
expect(pool.query).to.be.a(Function)
|
||||
const res = yield pool.query('SELECT $1::text as name', ['brianc'])
|
||||
expect(res.rows).to.have.length(1)
|
||||
expect(res.rows[0].name).to.equal('brianc')
|
||||
return pool.end()
|
||||
}))
|
||||
expect(errors).to.have.length(20)
|
||||
expect(pool.idleCount).to.equal(0)
|
||||
expect(pool.query).to.be.a(Function)
|
||||
const res = yield pool.query('SELECT $1::text as name', ['brianc'])
|
||||
expect(res.rows).to.have.length(1)
|
||||
expect(res.rows[0].name).to.equal('brianc')
|
||||
return pool.end()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('should continue with queued items after a connection failure', (done) => {
|
||||
const closeServer = net.createServer((socket) => {
|
||||
socket.destroy()
|
||||
}).unref()
|
||||
const closeServer = net
|
||||
.createServer((socket) => {
|
||||
socket.destroy()
|
||||
})
|
||||
.unref()
|
||||
|
||||
closeServer.listen(() => {
|
||||
const pool = new Pool({ max: 1, port: closeServer.address().port, host: 'localhost' })
|
||||
|
||||
@ -31,13 +31,13 @@ describe('events', function () {
|
||||
process.nextTick(() => {
|
||||
cb(new Error('bad news'))
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
}),
|
||||
})
|
||||
pool.on('connect', function () {
|
||||
throw new Error('should never get here')
|
||||
})
|
||||
return pool.connect().catch(e => expect(e.message).to.equal('bad news'))
|
||||
return pool.connect().catch((e) => expect(e.message).to.equal('bad news'))
|
||||
})
|
||||
|
||||
it('emits acquire every time a client is acquired', function (done) {
|
||||
@ -77,7 +77,7 @@ describe('events', function () {
|
||||
})
|
||||
})
|
||||
|
||||
function mockClient (methods) {
|
||||
function mockClient(methods) {
|
||||
return function () {
|
||||
const client = new EventEmitter()
|
||||
Object.assign(client, methods)
|
||||
|
||||
@ -7,7 +7,7 @@ const it = require('mocha').it
|
||||
|
||||
const Pool = require('../')
|
||||
|
||||
const wait = time => new Promise((resolve) => setTimeout(resolve, time))
|
||||
const wait = (time) => new Promise((resolve) => setTimeout(resolve, time))
|
||||
|
||||
describe('idle timeout', () => {
|
||||
it('should timeout and remove the client', (done) => {
|
||||
@ -20,60 +20,68 @@ describe('idle timeout', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('times out and removes clients when others are also removed', co.wrap(function * () {
|
||||
const pool = new Pool({ idleTimeoutMillis: 10 })
|
||||
const clientA = yield pool.connect()
|
||||
const clientB = yield pool.connect()
|
||||
clientA.release()
|
||||
clientB.release(new Error())
|
||||
it(
|
||||
'times out and removes clients when others are also removed',
|
||||
co.wrap(function* () {
|
||||
const pool = new Pool({ idleTimeoutMillis: 10 })
|
||||
const clientA = yield pool.connect()
|
||||
const clientB = yield pool.connect()
|
||||
clientA.release()
|
||||
clientB.release(new Error())
|
||||
|
||||
const removal = new Promise((resolve) => {
|
||||
pool.on('remove', () => {
|
||||
const removal = new Promise((resolve) => {
|
||||
pool.on('remove', () => {
|
||||
expect(pool.idleCount).to.equal(0)
|
||||
expect(pool.totalCount).to.equal(0)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
|
||||
const timeout = wait(100).then(() => Promise.reject(new Error('Idle timeout failed to occur')))
|
||||
|
||||
try {
|
||||
yield Promise.race([removal, timeout])
|
||||
} finally {
|
||||
pool.end()
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
it(
|
||||
'can remove idle clients and recreate them',
|
||||
co.wrap(function* () {
|
||||
const pool = new Pool({ idleTimeoutMillis: 1 })
|
||||
const results = []
|
||||
for (var i = 0; i < 20; i++) {
|
||||
let query = pool.query('SELECT NOW()')
|
||||
expect(pool.idleCount).to.equal(0)
|
||||
expect(pool.totalCount).to.equal(1)
|
||||
results.push(yield query)
|
||||
yield wait(2)
|
||||
expect(pool.idleCount).to.equal(0)
|
||||
expect(pool.totalCount).to.equal(0)
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
expect(results).to.have.length(20)
|
||||
})
|
||||
)
|
||||
|
||||
const timeout = wait(100).then(() =>
|
||||
Promise.reject(new Error('Idle timeout failed to occur')))
|
||||
|
||||
try {
|
||||
yield Promise.race([removal, timeout])
|
||||
} finally {
|
||||
pool.end()
|
||||
}
|
||||
}))
|
||||
|
||||
it('can remove idle clients and recreate them', co.wrap(function * () {
|
||||
const pool = new Pool({ idleTimeoutMillis: 1 })
|
||||
const results = []
|
||||
for (var i = 0; i < 20; i++) {
|
||||
let query = pool.query('SELECT NOW()')
|
||||
expect(pool.idleCount).to.equal(0)
|
||||
expect(pool.totalCount).to.equal(1)
|
||||
results.push(yield query)
|
||||
yield wait(2)
|
||||
expect(pool.idleCount).to.equal(0)
|
||||
expect(pool.totalCount).to.equal(0)
|
||||
}
|
||||
expect(results).to.have.length(20)
|
||||
}))
|
||||
|
||||
it('does not time out clients which are used', co.wrap(function * () {
|
||||
const pool = new Pool({ idleTimeoutMillis: 1 })
|
||||
const results = []
|
||||
for (var i = 0; i < 20; i++) {
|
||||
let client = yield pool.connect()
|
||||
expect(pool.totalCount).to.equal(1)
|
||||
expect(pool.idleCount).to.equal(0)
|
||||
yield wait(10)
|
||||
results.push(yield client.query('SELECT NOW()'))
|
||||
client.release()
|
||||
expect(pool.idleCount).to.equal(1)
|
||||
expect(pool.totalCount).to.equal(1)
|
||||
}
|
||||
expect(results).to.have.length(20)
|
||||
return pool.end()
|
||||
}))
|
||||
it(
|
||||
'does not time out clients which are used',
|
||||
co.wrap(function* () {
|
||||
const pool = new Pool({ idleTimeoutMillis: 1 })
|
||||
const results = []
|
||||
for (var i = 0; i < 20; i++) {
|
||||
let client = yield pool.connect()
|
||||
expect(pool.totalCount).to.equal(1)
|
||||
expect(pool.idleCount).to.equal(0)
|
||||
yield wait(10)
|
||||
results.push(yield client.query('SELECT NOW()'))
|
||||
client.release()
|
||||
expect(pool.idleCount).to.equal(1)
|
||||
expect(pool.totalCount).to.equal(1)
|
||||
}
|
||||
expect(results).to.have.length(20)
|
||||
return pool.end()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@ -167,13 +167,11 @@ describe('pool', function () {
|
||||
|
||||
it('executes a query directly', () => {
|
||||
const pool = new Pool()
|
||||
return pool
|
||||
.query('SELECT $1::text as name', ['hi'])
|
||||
.then(res => {
|
||||
expect(res.rows).to.have.length(1)
|
||||
expect(res.rows[0].name).to.equal('hi')
|
||||
return pool.end()
|
||||
})
|
||||
return pool.query('SELECT $1::text as name', ['hi']).then((res) => {
|
||||
expect(res.rows).to.have.length(1)
|
||||
expect(res.rows[0].name).to.equal('hi')
|
||||
return pool.end()
|
||||
})
|
||||
})
|
||||
|
||||
it('properly pools clients', function () {
|
||||
@ -210,10 +208,9 @@ describe('pool', function () {
|
||||
|
||||
const errors = []
|
||||
const promises = _.times(30, () => {
|
||||
return pool.query('SELECT asldkfjasldkf')
|
||||
.catch(function (e) {
|
||||
errors.push(e)
|
||||
})
|
||||
return pool.query('SELECT asldkfjasldkf').catch(function (e) {
|
||||
errors.push(e)
|
||||
})
|
||||
})
|
||||
return Promise.all(promises).then(() => {
|
||||
expect(errors).to.have.length(30)
|
||||
|
||||
@ -8,78 +8,91 @@ const it = require('mocha').it
|
||||
const Pool = require('../')
|
||||
|
||||
describe('maxUses', () => {
|
||||
it('can create a single client and use it once', co.wrap(function * () {
|
||||
const pool = new Pool({ maxUses: 2 })
|
||||
expect(pool.waitingCount).to.equal(0)
|
||||
const client = yield pool.connect()
|
||||
const res = yield client.query('SELECT $1::text as name', ['hi'])
|
||||
expect(res.rows[0].name).to.equal('hi')
|
||||
client.release()
|
||||
pool.end()
|
||||
}))
|
||||
it(
|
||||
'can create a single client and use it once',
|
||||
co.wrap(function* () {
|
||||
const pool = new Pool({ maxUses: 2 })
|
||||
expect(pool.waitingCount).to.equal(0)
|
||||
const client = yield pool.connect()
|
||||
const res = yield client.query('SELECT $1::text as name', ['hi'])
|
||||
expect(res.rows[0].name).to.equal('hi')
|
||||
client.release()
|
||||
pool.end()
|
||||
})
|
||||
)
|
||||
|
||||
it('getting a connection a second time returns the same connection and releasing it also closes it', co.wrap(function * () {
|
||||
const pool = new Pool({ maxUses: 2 })
|
||||
expect(pool.waitingCount).to.equal(0)
|
||||
const client = yield pool.connect()
|
||||
client.release()
|
||||
const client2 = yield pool.connect()
|
||||
expect(client).to.equal(client2)
|
||||
expect(client2._ending).to.equal(false)
|
||||
client2.release()
|
||||
expect(client2._ending).to.equal(true)
|
||||
return yield pool.end()
|
||||
}))
|
||||
it(
|
||||
'getting a connection a second time returns the same connection and releasing it also closes it',
|
||||
co.wrap(function* () {
|
||||
const pool = new Pool({ maxUses: 2 })
|
||||
expect(pool.waitingCount).to.equal(0)
|
||||
const client = yield pool.connect()
|
||||
client.release()
|
||||
const client2 = yield pool.connect()
|
||||
expect(client).to.equal(client2)
|
||||
expect(client2._ending).to.equal(false)
|
||||
client2.release()
|
||||
expect(client2._ending).to.equal(true)
|
||||
return yield pool.end()
|
||||
})
|
||||
)
|
||||
|
||||
it('getting a connection a third time returns a new connection', co.wrap(function * () {
|
||||
const pool = new Pool({ maxUses: 2 })
|
||||
expect(pool.waitingCount).to.equal(0)
|
||||
const client = yield pool.connect()
|
||||
client.release()
|
||||
const client2 = yield pool.connect()
|
||||
expect(client).to.equal(client2)
|
||||
client2.release()
|
||||
const client3 = yield pool.connect()
|
||||
expect(client3).not.to.equal(client2)
|
||||
client3.release()
|
||||
return yield pool.end()
|
||||
}))
|
||||
it(
|
||||
'getting a connection a third time returns a new connection',
|
||||
co.wrap(function* () {
|
||||
const pool = new Pool({ maxUses: 2 })
|
||||
expect(pool.waitingCount).to.equal(0)
|
||||
const client = yield pool.connect()
|
||||
client.release()
|
||||
const client2 = yield pool.connect()
|
||||
expect(client).to.equal(client2)
|
||||
client2.release()
|
||||
const client3 = yield pool.connect()
|
||||
expect(client3).not.to.equal(client2)
|
||||
client3.release()
|
||||
return yield pool.end()
|
||||
})
|
||||
)
|
||||
|
||||
it('getting a connection from a pending request gets a fresh client when the released candidate is expended', co.wrap(function * () {
|
||||
const pool = new Pool({ max: 1, maxUses: 2 })
|
||||
expect(pool.waitingCount).to.equal(0)
|
||||
const client1 = yield pool.connect()
|
||||
pool.connect()
|
||||
.then(client2 => {
|
||||
it(
|
||||
'getting a connection from a pending request gets a fresh client when the released candidate is expended',
|
||||
co.wrap(function* () {
|
||||
const pool = new Pool({ max: 1, maxUses: 2 })
|
||||
expect(pool.waitingCount).to.equal(0)
|
||||
const client1 = yield pool.connect()
|
||||
pool.connect().then((client2) => {
|
||||
expect(client2).to.equal(client1)
|
||||
expect(pool.waitingCount).to.equal(1)
|
||||
// Releasing the client this time should also expend it since maxUses is 2, causing client3 to be a fresh client
|
||||
client2.release()
|
||||
})
|
||||
const client3Promise = pool.connect()
|
||||
.then(client3 => {
|
||||
const client3Promise = pool.connect().then((client3) => {
|
||||
// client3 should be a fresh client since client2's release caused the first client to be expended
|
||||
expect(pool.waitingCount).to.equal(0)
|
||||
expect(client3).not.to.equal(client1)
|
||||
return client3.release()
|
||||
})
|
||||
// There should be two pending requests since we have 3 connect requests but a max size of 1
|
||||
expect(pool.waitingCount).to.equal(2)
|
||||
// Releasing the client should not yet expend it since maxUses is 2
|
||||
client1.release()
|
||||
yield client3Promise
|
||||
return yield pool.end()
|
||||
}))
|
||||
// There should be two pending requests since we have 3 connect requests but a max size of 1
|
||||
expect(pool.waitingCount).to.equal(2)
|
||||
// Releasing the client should not yet expend it since maxUses is 2
|
||||
client1.release()
|
||||
yield client3Promise
|
||||
return yield pool.end()
|
||||
})
|
||||
)
|
||||
|
||||
it('logs when removing an expended client', co.wrap(function * () {
|
||||
const messages = []
|
||||
const log = function (msg) {
|
||||
messages.push(msg)
|
||||
}
|
||||
const pool = new Pool({ maxUses: 1, log })
|
||||
const client = yield pool.connect()
|
||||
client.release()
|
||||
expect(messages).to.contain('remove expended client')
|
||||
return yield pool.end()
|
||||
}))
|
||||
it(
|
||||
'logs when removing an expended client',
|
||||
co.wrap(function* () {
|
||||
const messages = []
|
||||
const log = function (msg) {
|
||||
messages.push(msg)
|
||||
}
|
||||
const pool = new Pool({ maxUses: 1, log })
|
||||
const client = yield pool.connect()
|
||||
client.release()
|
||||
expect(messages).to.contain('remove expended client')
|
||||
return yield pool.end()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
const crash = reason => {
|
||||
process.on(reason, err => {
|
||||
const crash = (reason) => {
|
||||
process.on(reason, (err) => {
|
||||
console.error(reason, err.stack)
|
||||
process.exit(-1)
|
||||
})
|
||||
|
||||
@ -8,43 +8,51 @@ const it = require('mocha').it
|
||||
const Pool = require('../')
|
||||
|
||||
describe('pool size of 1', () => {
|
||||
it('can create a single client and use it once', co.wrap(function * () {
|
||||
const pool = new Pool({ max: 1 })
|
||||
expect(pool.waitingCount).to.equal(0)
|
||||
const client = yield pool.connect()
|
||||
const res = yield client.query('SELECT $1::text as name', ['hi'])
|
||||
expect(res.rows[0].name).to.equal('hi')
|
||||
client.release()
|
||||
pool.end()
|
||||
}))
|
||||
it(
|
||||
'can create a single client and use it once',
|
||||
co.wrap(function* () {
|
||||
const pool = new Pool({ max: 1 })
|
||||
expect(pool.waitingCount).to.equal(0)
|
||||
const client = yield pool.connect()
|
||||
const res = yield client.query('SELECT $1::text as name', ['hi'])
|
||||
expect(res.rows[0].name).to.equal('hi')
|
||||
client.release()
|
||||
pool.end()
|
||||
})
|
||||
)
|
||||
|
||||
it('can create a single client and use it multiple times', co.wrap(function * () {
|
||||
const pool = new Pool({ max: 1 })
|
||||
expect(pool.waitingCount).to.equal(0)
|
||||
const client = yield pool.connect()
|
||||
const wait = pool.connect()
|
||||
expect(pool.waitingCount).to.equal(1)
|
||||
client.release()
|
||||
const client2 = yield wait
|
||||
expect(client).to.equal(client2)
|
||||
client2.release()
|
||||
return yield pool.end()
|
||||
}))
|
||||
it(
|
||||
'can create a single client and use it multiple times',
|
||||
co.wrap(function* () {
|
||||
const pool = new Pool({ max: 1 })
|
||||
expect(pool.waitingCount).to.equal(0)
|
||||
const client = yield pool.connect()
|
||||
const wait = pool.connect()
|
||||
expect(pool.waitingCount).to.equal(1)
|
||||
client.release()
|
||||
const client2 = yield wait
|
||||
expect(client).to.equal(client2)
|
||||
client2.release()
|
||||
return yield pool.end()
|
||||
})
|
||||
)
|
||||
|
||||
it('can only send 1 query at a time', co.wrap(function * () {
|
||||
const pool = new Pool({ max: 1 })
|
||||
it(
|
||||
'can only send 1 query at a time',
|
||||
co.wrap(function* () {
|
||||
const pool = new Pool({ max: 1 })
|
||||
|
||||
// the query text column name changed in PostgreSQL 9.2
|
||||
const versionResult = yield pool.query('SHOW server_version_num')
|
||||
const version = parseInt(versionResult.rows[0].server_version_num, 10)
|
||||
const queryColumn = version < 90200 ? 'current_query' : 'query'
|
||||
// the query text column name changed in PostgreSQL 9.2
|
||||
const versionResult = yield pool.query('SHOW server_version_num')
|
||||
const version = parseInt(versionResult.rows[0].server_version_num, 10)
|
||||
const queryColumn = version < 90200 ? 'current_query' : 'query'
|
||||
|
||||
const queryText = 'SELECT COUNT(*) as counts FROM pg_stat_activity WHERE ' + queryColumn + ' = $1'
|
||||
const queries = _.times(20, () =>
|
||||
pool.query(queryText, [queryText]))
|
||||
const results = yield Promise.all(queries)
|
||||
const counts = results.map(res => parseInt(res.rows[0].counts, 10))
|
||||
expect(counts).to.eql(_.times(20, i => 1))
|
||||
return yield pool.end()
|
||||
}))
|
||||
const queryText = 'SELECT COUNT(*) as counts FROM pg_stat_activity WHERE ' + queryColumn + ' = $1'
|
||||
const queries = _.times(20, () => pool.query(queryText, [queryText]))
|
||||
const results = yield Promise.all(queries)
|
||||
const counts = results.map((res) => parseInt(res.rows[0].counts, 10))
|
||||
expect(counts).to.eql(_.times(20, (i) => 1))
|
||||
return yield pool.end()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@ -12,7 +12,7 @@ describe('verify', () => {
|
||||
verify: (client, cb) => {
|
||||
client.release()
|
||||
cb(new Error('nope'))
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
pool.connect((err, client) => {
|
||||
|
||||
@ -1,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()
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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)])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
@ -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 }
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
})
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 }
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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()
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -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', () => {
|
||||
|
||||
@ -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()
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -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()
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,69 +1,69 @@
|
||||
const pg = require("./lib");
|
||||
const pg = require('./lib')
|
||||
const pool = new pg.Pool()
|
||||
|
||||
const params = {
|
||||
text:
|
||||
"select typname, typnamespace, typowner, typlen, typbyval, typcategory, typispreferred, typisdefined, typdelim, typrelid, typelem, typarray from pg_type where typtypmod = $1 and typisdefined = $2",
|
||||
values: [-1, true]
|
||||
};
|
||||
'select typname, typnamespace, typowner, typlen, typbyval, typcategory, typispreferred, typisdefined, typdelim, typrelid, typelem, typarray from pg_type where typtypmod = $1 and typisdefined = $2',
|
||||
values: [-1, true],
|
||||
}
|
||||
|
||||
const insert = {
|
||||
text: 'INSERT INTO foobar(name, age) VALUES ($1, $2)',
|
||||
values: ['brian', 100]
|
||||
values: ['brian', 100],
|
||||
}
|
||||
|
||||
const seq = {
|
||||
text: 'SELECT * FROM generate_series(1, 1000)'
|
||||
text: 'SELECT * FROM generate_series(1, 1000)',
|
||||
}
|
||||
|
||||
const exec = async (client, q) => {
|
||||
const result = await client.query({
|
||||
text: q.text,
|
||||
values: q.values,
|
||||
rowMode: "array"
|
||||
});
|
||||
};
|
||||
rowMode: 'array',
|
||||
})
|
||||
}
|
||||
|
||||
const bench = async (client, q, time) => {
|
||||
let start = Date.now();
|
||||
let count = 0;
|
||||
let start = Date.now()
|
||||
let count = 0
|
||||
while (true) {
|
||||
await exec(client, q);
|
||||
count++;
|
||||
await exec(client, q)
|
||||
count++
|
||||
if (Date.now() - start > time) {
|
||||
return count;
|
||||
return count
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const run = async () => {
|
||||
const client = new pg.Client();
|
||||
await client.connect();
|
||||
const client = new pg.Client()
|
||||
await client.connect()
|
||||
await client.query('CREATE TEMP TABLE foobar(name TEXT, age NUMERIC)')
|
||||
await bench(client, params, 1000);
|
||||
console.log("warmup done");
|
||||
const seconds = 5;
|
||||
await bench(client, params, 1000)
|
||||
console.log('warmup done')
|
||||
const seconds = 5
|
||||
|
||||
let queries = await bench(client, params, seconds * 1000);
|
||||
let queries = await bench(client, params, seconds * 1000)
|
||||
console.log('')
|
||||
console.log("little queries:", queries);
|
||||
console.log("qps", queries / seconds);
|
||||
console.log("on my laptop best so far seen 733 qps")
|
||||
console.log('little queries:', queries)
|
||||
console.log('qps', queries / seconds)
|
||||
console.log('on my laptop best so far seen 733 qps')
|
||||
|
||||
console.log('')
|
||||
queries = await bench(client, seq, seconds * 1000);
|
||||
console.log("sequence queries:", queries);
|
||||
console.log("qps", queries / seconds);
|
||||
console.log("on my laptop best so far seen 1309 qps")
|
||||
queries = await bench(client, seq, seconds * 1000)
|
||||
console.log('sequence queries:', queries)
|
||||
console.log('qps', queries / seconds)
|
||||
console.log('on my laptop best so far seen 1309 qps')
|
||||
|
||||
console.log('')
|
||||
queries = await bench(client, insert, seconds * 1000);
|
||||
console.log("insert queries:", queries);
|
||||
console.log("qps", queries / seconds);
|
||||
console.log("on my laptop best so far seen 5799 qps")
|
||||
queries = await bench(client, insert, seconds * 1000)
|
||||
console.log('insert queries:', queries)
|
||||
console.log('qps', queries / seconds)
|
||||
console.log('on my laptop best so far seen 5799 qps')
|
||||
console.log()
|
||||
await client.end();
|
||||
await client.end();
|
||||
};
|
||||
await client.end()
|
||||
await client.end()
|
||||
}
|
||||
|
||||
run().catch(e => console.error(e) || process.exit(-1));
|
||||
run().catch((e) => console.error(e) || process.exit(-1))
|
||||
|
||||
@ -37,7 +37,7 @@ var Client = function (config) {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
value: this.connectionParameters.password
|
||||
value: this.connectionParameters.password,
|
||||
})
|
||||
|
||||
this.replication = this.connectionParameters.replication
|
||||
@ -52,13 +52,15 @@ var Client = function (config) {
|
||||
this._connectionError = false
|
||||
this._queryable = true
|
||||
|
||||
this.connection = c.connection || new Connection({
|
||||
stream: c.stream,
|
||||
ssl: this.connectionParameters.ssl,
|
||||
keepAlive: c.keepAlive || false,
|
||||
keepAliveInitialDelayMillis: c.keepAliveInitialDelayMillis || 0,
|
||||
encoding: this.connectionParameters.client_encoding || 'utf8'
|
||||
})
|
||||
this.connection =
|
||||
c.connection ||
|
||||
new Connection({
|
||||
stream: c.stream,
|
||||
ssl: this.connectionParameters.ssl,
|
||||
keepAlive: c.keepAlive || false,
|
||||
keepAliveInitialDelayMillis: c.keepAliveInitialDelayMillis || 0,
|
||||
encoding: this.connectionParameters.client_encoding || 'utf8',
|
||||
})
|
||||
this.queryQueue = []
|
||||
this.binary = c.binary || defaults.binary
|
||||
this.processID = null
|
||||
@ -127,9 +129,10 @@ Client.prototype._connect = function (callback) {
|
||||
function checkPgPass(cb) {
|
||||
return function (msg) {
|
||||
if (typeof self.password === 'function') {
|
||||
self._Promise.resolve()
|
||||
self._Promise
|
||||
.resolve()
|
||||
.then(() => self.password())
|
||||
.then(pass => {
|
||||
.then((pass) => {
|
||||
if (pass !== undefined) {
|
||||
if (typeof pass !== 'string') {
|
||||
con.emit('error', new TypeError('Password must be a string'))
|
||||
@ -140,7 +143,8 @@ Client.prototype._connect = function (callback) {
|
||||
self.connectionParameters.password = self.password = null
|
||||
}
|
||||
cb(msg)
|
||||
}).catch(err => {
|
||||
})
|
||||
.catch((err) => {
|
||||
con.emit('error', err)
|
||||
})
|
||||
} else if (self.password !== null) {
|
||||
@ -157,22 +161,31 @@ Client.prototype._connect = function (callback) {
|
||||
}
|
||||
|
||||
// password request handling
|
||||
con.on('authenticationCleartextPassword', checkPgPass(function () {
|
||||
con.password(self.password)
|
||||
}))
|
||||
con.on(
|
||||
'authenticationCleartextPassword',
|
||||
checkPgPass(function () {
|
||||
con.password(self.password)
|
||||
})
|
||||
)
|
||||
|
||||
// password request handling
|
||||
con.on('authenticationMD5Password', checkPgPass(function (msg) {
|
||||
con.password(utils.postgresMd5PasswordHash(self.user, self.password, msg.salt))
|
||||
}))
|
||||
con.on(
|
||||
'authenticationMD5Password',
|
||||
checkPgPass(function (msg) {
|
||||
con.password(utils.postgresMd5PasswordHash(self.user, self.password, msg.salt))
|
||||
})
|
||||
)
|
||||
|
||||
// password request handling (SASL)
|
||||
var saslSession
|
||||
con.on('authenticationSASL', checkPgPass(function (msg) {
|
||||
saslSession = sasl.startSession(msg.mechanisms)
|
||||
con.on(
|
||||
'authenticationSASL',
|
||||
checkPgPass(function (msg) {
|
||||
saslSession = sasl.startSession(msg.mechanisms)
|
||||
|
||||
con.sendSASLInitialResponseMessage(saslSession.mechanism, saslSession.response)
|
||||
}))
|
||||
con.sendSASLInitialResponseMessage(saslSession.mechanism, saslSession.response)
|
||||
})
|
||||
)
|
||||
|
||||
// password request handling (SASL)
|
||||
con.on('authenticationSASLContinue', function (msg) {
|
||||
@ -259,9 +272,7 @@ Client.prototype._connect = function (callback) {
|
||||
})
|
||||
|
||||
con.once('end', () => {
|
||||
const error = this._ending
|
||||
? new Error('Connection terminated')
|
||||
: new Error('Connection terminated unexpectedly')
|
||||
const error = this._ending ? new Error('Connection terminated') : new Error('Connection terminated unexpectedly')
|
||||
|
||||
clearTimeout(connectionTimeoutHandle)
|
||||
this._errorAllQueries(error)
|
||||
@ -367,7 +378,7 @@ Client.prototype.getStartupConf = function () {
|
||||
|
||||
var data = {
|
||||
user: params.user,
|
||||
database: params.database
|
||||
database: params.database,
|
||||
}
|
||||
|
||||
var appName = params.application_name || params.fallback_application_name
|
||||
@ -422,11 +433,11 @@ Client.prototype.escapeIdentifier = function (str) {
|
||||
// Ported from PostgreSQL 9.2.4 source code in src/interfaces/libpq/fe-exec.c
|
||||
Client.prototype.escapeLiteral = function (str) {
|
||||
var hasBackslash = false
|
||||
var escaped = '\''
|
||||
var escaped = "'"
|
||||
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
var c = str[i]
|
||||
if (c === '\'') {
|
||||
if (c === "'") {
|
||||
escaped += c + c
|
||||
} else if (c === '\\') {
|
||||
escaped += c + c
|
||||
@ -436,7 +447,7 @@ Client.prototype.escapeLiteral = function (str) {
|
||||
}
|
||||
}
|
||||
|
||||
escaped += '\''
|
||||
escaped += "'"
|
||||
|
||||
if (hasBackslash === true) {
|
||||
escaped = ' E' + escaped
|
||||
@ -488,7 +499,7 @@ Client.prototype.query = function (config, values, callback) {
|
||||
query = new Query(config, values, callback)
|
||||
if (!query.callback) {
|
||||
result = new this._Promise((resolve, reject) => {
|
||||
query.callback = (err, res) => err ? reject(err) : resolve(res)
|
||||
query.callback = (err, res) => (err ? reject(err) : resolve(res))
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -507,7 +518,7 @@ Client.prototype.query = function (config, values, callback) {
|
||||
|
||||
// we already returned an error,
|
||||
// just do nothing if query completes
|
||||
query.callback = () => { }
|
||||
query.callback = () => {}
|
||||
|
||||
// Remove from queue
|
||||
var index = this.queryQueue.indexOf(query)
|
||||
|
||||
@ -80,14 +80,18 @@ Connection.prototype.connect = function (port, host) {
|
||||
case 'N': // Server does not support SSL connections
|
||||
self.stream.end()
|
||||
return self.emit('error', new Error('The server does not support SSL connections'))
|
||||
default: // Any other response byte, including 'E' (ErrorResponse) indicating a server error
|
||||
default:
|
||||
// Any other response byte, including 'E' (ErrorResponse) indicating a server error
|
||||
self.stream.end()
|
||||
return self.emit('error', new Error('There was an error establishing an SSL connection'))
|
||||
}
|
||||
var tls = require('tls')
|
||||
const options = Object.assign({
|
||||
socket: self.stream
|
||||
}, self.ssl)
|
||||
const options = Object.assign(
|
||||
{
|
||||
socket: self.stream,
|
||||
},
|
||||
self.ssl
|
||||
)
|
||||
if (net.isIP(host) === 0) {
|
||||
options.servername = host
|
||||
}
|
||||
|
||||
@ -22,9 +22,7 @@ var val = function (key, config, envVar) {
|
||||
envVar = process.env[envVar]
|
||||
}
|
||||
|
||||
return config[key] ||
|
||||
envVar ||
|
||||
defaults[key]
|
||||
return config[key] || envVar || defaults[key]
|
||||
}
|
||||
|
||||
var useSsl = function () {
|
||||
@ -66,7 +64,7 @@ var ConnectionParameters = function (config) {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
value: val('password', config)
|
||||
value: val('password', config),
|
||||
})
|
||||
|
||||
this.binary = val('binary', config)
|
||||
@ -74,7 +72,7 @@ var ConnectionParameters = function (config) {
|
||||
this.client_encoding = val('client_encoding', config)
|
||||
this.replication = val('replication', config)
|
||||
// a domain socket begins with '/'
|
||||
this.isDomainSocket = (!(this.host || '').indexOf('/'))
|
||||
this.isDomainSocket = !(this.host || '').indexOf('/')
|
||||
|
||||
this.application_name = val('application_name', config, 'PGAPPNAME')
|
||||
this.fallback_application_name = val('fallback_application_name', config, false)
|
||||
|
||||
@ -35,7 +35,7 @@ var Connection = function (config) {
|
||||
this._emitMessage = false
|
||||
this._reader = new Reader({
|
||||
headerSize: 1,
|
||||
lengthPadding: -4
|
||||
lengthPadding: -4,
|
||||
})
|
||||
var self = this
|
||||
this.on('newListener', function (eventName) {
|
||||
@ -88,14 +88,18 @@ Connection.prototype.connect = function (port, host) {
|
||||
case 'N': // Server does not support SSL connections
|
||||
self.stream.end()
|
||||
return self.emit('error', new Error('The server does not support SSL connections'))
|
||||
default: // Any other response byte, including 'E' (ErrorResponse) indicating a server error
|
||||
default:
|
||||
// Any other response byte, including 'E' (ErrorResponse) indicating a server error
|
||||
self.stream.end()
|
||||
return self.emit('error', new Error('There was an error establishing an SSL connection'))
|
||||
}
|
||||
var tls = require('tls')
|
||||
const options = Object.assign({
|
||||
socket: self.stream
|
||||
}, self.ssl)
|
||||
const options = Object.assign(
|
||||
{
|
||||
socket: self.stream,
|
||||
},
|
||||
self.ssl
|
||||
)
|
||||
if (net.isIP(host) === 0) {
|
||||
options.servername = host
|
||||
}
|
||||
@ -127,23 +131,16 @@ Connection.prototype.attachListeners = function (stream) {
|
||||
}
|
||||
|
||||
Connection.prototype.requestSsl = function () {
|
||||
var bodyBuffer = this.writer
|
||||
.addInt16(0x04D2)
|
||||
.addInt16(0x162F).flush()
|
||||
var bodyBuffer = this.writer.addInt16(0x04d2).addInt16(0x162f).flush()
|
||||
|
||||
var length = bodyBuffer.length + 4
|
||||
|
||||
var buffer = new Writer()
|
||||
.addInt32(length)
|
||||
.add(bodyBuffer)
|
||||
.join()
|
||||
var buffer = new Writer().addInt32(length).add(bodyBuffer).join()
|
||||
this.stream.write(buffer)
|
||||
}
|
||||
|
||||
Connection.prototype.startup = function (config) {
|
||||
var writer = this.writer
|
||||
.addInt16(3)
|
||||
.addInt16(0)
|
||||
var writer = this.writer.addInt16(3).addInt16(0)
|
||||
|
||||
Object.keys(config).forEach(function (key) {
|
||||
var val = config[key]
|
||||
@ -157,27 +154,16 @@ Connection.prototype.startup = function (config) {
|
||||
|
||||
var length = bodyBuffer.length + 4
|
||||
|
||||
var buffer = new Writer()
|
||||
.addInt32(length)
|
||||
.add(bodyBuffer)
|
||||
.join()
|
||||
var buffer = new Writer().addInt32(length).add(bodyBuffer).join()
|
||||
this.stream.write(buffer)
|
||||
}
|
||||
|
||||
Connection.prototype.cancel = function (processID, secretKey) {
|
||||
var bodyBuffer = this.writer
|
||||
.addInt16(1234)
|
||||
.addInt16(5678)
|
||||
.addInt32(processID)
|
||||
.addInt32(secretKey)
|
||||
.flush()
|
||||
var bodyBuffer = this.writer.addInt16(1234).addInt16(5678).addInt32(processID).addInt32(secretKey).flush()
|
||||
|
||||
var length = bodyBuffer.length + 4
|
||||
|
||||
var buffer = new Writer()
|
||||
.addInt32(length)
|
||||
.add(bodyBuffer)
|
||||
.join()
|
||||
var buffer = new Writer().addInt32(length).add(bodyBuffer).join()
|
||||
this.stream.write(buffer)
|
||||
}
|
||||
|
||||
@ -188,18 +174,14 @@ Connection.prototype.password = function (password) {
|
||||
|
||||
Connection.prototype.sendSASLInitialResponseMessage = function (mechanism, initialResponse) {
|
||||
// 0x70 = 'p'
|
||||
this.writer
|
||||
.addCString(mechanism)
|
||||
.addInt32(Buffer.byteLength(initialResponse))
|
||||
.addString(initialResponse)
|
||||
this.writer.addCString(mechanism).addInt32(Buffer.byteLength(initialResponse)).addString(initialResponse)
|
||||
|
||||
this._send(0x70)
|
||||
}
|
||||
|
||||
Connection.prototype.sendSCRAMClientFinalMessage = function (additionalData) {
|
||||
// 0x70 = 'p'
|
||||
this.writer
|
||||
.addString(additionalData)
|
||||
this.writer.addString(additionalData)
|
||||
|
||||
this._send(0x70)
|
||||
}
|
||||
@ -263,13 +245,17 @@ Connection.prototype.bind = function (config, more) {
|
||||
var values = config.values || []
|
||||
var len = values.length
|
||||
var useBinary = false
|
||||
for (var j = 0; j < len; j++) { useBinary |= values[j] instanceof Buffer }
|
||||
var buffer = this.writer
|
||||
.addCString(config.portal)
|
||||
.addCString(config.statement)
|
||||
if (!useBinary) { buffer.addInt16(0) } else {
|
||||
for (var j = 0; j < len; j++) {
|
||||
useBinary |= values[j] instanceof Buffer
|
||||
}
|
||||
var buffer = this.writer.addCString(config.portal).addCString(config.statement)
|
||||
if (!useBinary) {
|
||||
buffer.addInt16(0)
|
||||
} else {
|
||||
buffer.addInt16(len)
|
||||
for (j = 0; j < len; j++) { buffer.addInt16(values[j] instanceof Buffer) }
|
||||
for (j = 0; j < len; j++) {
|
||||
buffer.addInt16(values[j] instanceof Buffer)
|
||||
}
|
||||
}
|
||||
buffer.addInt16(len)
|
||||
for (var i = 0; i < len; i++) {
|
||||
@ -301,9 +287,7 @@ Connection.prototype.execute = function (config, more) {
|
||||
config = config || {}
|
||||
config.portal = config.portal || ''
|
||||
config.rows = config.rows || ''
|
||||
this.writer
|
||||
.addCString(config.portal)
|
||||
.addInt32(config.rows)
|
||||
this.writer.addCString(config.portal).addInt32(config.rows)
|
||||
|
||||
// 0x45 = 'E'
|
||||
this._send(0x45, more)
|
||||
|
||||
@ -70,7 +70,7 @@ module.exports = {
|
||||
|
||||
keepalives: 1,
|
||||
|
||||
keepalives_idle: 0
|
||||
keepalives_idle: 0,
|
||||
}
|
||||
|
||||
var pgTypes = require('pg-types')
|
||||
|
||||
@ -14,7 +14,7 @@ var Pool = require('pg-pool')
|
||||
|
||||
const poolFactory = (Client) => {
|
||||
return class BoundPool extends Pool {
|
||||
constructor (options) {
|
||||
constructor(options) {
|
||||
super(options, Client)
|
||||
}
|
||||
}
|
||||
@ -54,10 +54,10 @@ if (typeof process.env.NODE_PG_FORCE_NATIVE !== 'undefined') {
|
||||
|
||||
// overwrite module.exports.native so that getter is never called again
|
||||
Object.defineProperty(module.exports, 'native', {
|
||||
value: native
|
||||
value: native,
|
||||
})
|
||||
|
||||
return native
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ assert(semver.gte(Native.version, pkg.minNativeVersion), msg)
|
||||
|
||||
var NativeQuery = require('./query')
|
||||
|
||||
var Client = module.exports = function (config) {
|
||||
var Client = (module.exports = function (config) {
|
||||
EventEmitter.call(this)
|
||||
config = config || {}
|
||||
|
||||
@ -30,7 +30,7 @@ var Client = module.exports = function (config) {
|
||||
this._types = new TypeOverrides(config.types)
|
||||
|
||||
this.native = new Native({
|
||||
types: this._types
|
||||
types: this._types,
|
||||
})
|
||||
|
||||
this._queryQueue = []
|
||||
@ -41,7 +41,7 @@ var Client = module.exports = function (config) {
|
||||
|
||||
// keep these on the object for legacy reasons
|
||||
// for the time being. TODO: deprecate all this jazz
|
||||
var cp = this.connectionParameters = new ConnectionParameters(config)
|
||||
var cp = (this.connectionParameters = new ConnectionParameters(config))
|
||||
this.user = cp.user
|
||||
|
||||
// "hiding" the password so it doesn't show up in stack traces
|
||||
@ -50,7 +50,7 @@ var Client = module.exports = function (config) {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
value: cp.password
|
||||
value: cp.password,
|
||||
})
|
||||
this.database = cp.database
|
||||
this.host = cp.host
|
||||
@ -58,7 +58,7 @@ var Client = module.exports = function (config) {
|
||||
|
||||
// a hash to hold named queries
|
||||
this.namedQueries = {}
|
||||
}
|
||||
})
|
||||
|
||||
Client.Query = NativeQuery
|
||||
|
||||
@ -115,7 +115,7 @@ Client.prototype._connect = function (cb) {
|
||||
self.native.on('notification', function (msg) {
|
||||
self.emit('notification', {
|
||||
channel: msg.relname,
|
||||
payload: msg.extra
|
||||
payload: msg.extra,
|
||||
})
|
||||
})
|
||||
|
||||
@ -180,7 +180,7 @@ Client.prototype.query = function (config, values, callback) {
|
||||
resolveOut = resolve
|
||||
rejectOut = reject
|
||||
})
|
||||
query.callback = (err, res) => err ? rejectOut(err) : resolveOut(res)
|
||||
query.callback = (err, res) => (err ? rejectOut(err) : resolveOut(res))
|
||||
}
|
||||
}
|
||||
|
||||
@ -248,7 +248,7 @@ Client.prototype.end = function (cb) {
|
||||
var result
|
||||
if (!cb) {
|
||||
result = new this._Promise(function (resolve, reject) {
|
||||
cb = (err) => err ? reject(err) : resolve()
|
||||
cb = (err) => (err ? reject(err) : resolve())
|
||||
})
|
||||
}
|
||||
this.native.end(function () {
|
||||
|
||||
@ -11,7 +11,7 @@ var EventEmitter = require('events').EventEmitter
|
||||
var util = require('util')
|
||||
var utils = require('../utils')
|
||||
|
||||
var NativeQuery = module.exports = function (config, values, callback) {
|
||||
var NativeQuery = (module.exports = function (config, values, callback) {
|
||||
EventEmitter.call(this)
|
||||
config = utils.normalizeQueryConfig(config, values, callback)
|
||||
this.text = config.text
|
||||
@ -27,27 +27,30 @@ var NativeQuery = module.exports = function (config, values, callback) {
|
||||
// this has almost no meaning because libpq
|
||||
// reads all rows into memory befor returning any
|
||||
this._emitRowEvents = false
|
||||
this.on('newListener', function (event) {
|
||||
if (event === 'row') this._emitRowEvents = true
|
||||
}.bind(this))
|
||||
}
|
||||
this.on(
|
||||
'newListener',
|
||||
function (event) {
|
||||
if (event === 'row') this._emitRowEvents = true
|
||||
}.bind(this)
|
||||
)
|
||||
})
|
||||
|
||||
util.inherits(NativeQuery, EventEmitter)
|
||||
|
||||
var errorFieldMap = {
|
||||
/* eslint-disable quote-props */
|
||||
'sqlState': 'code',
|
||||
'statementPosition': 'position',
|
||||
'messagePrimary': 'message',
|
||||
'context': 'where',
|
||||
'schemaName': 'schema',
|
||||
'tableName': 'table',
|
||||
'columnName': 'column',
|
||||
'dataTypeName': 'dataType',
|
||||
'constraintName': 'constraint',
|
||||
'sourceFile': 'file',
|
||||
'sourceLine': 'line',
|
||||
'sourceFunction': 'routine'
|
||||
sqlState: 'code',
|
||||
statementPosition: 'position',
|
||||
messagePrimary: 'message',
|
||||
context: 'where',
|
||||
schemaName: 'schema',
|
||||
tableName: 'table',
|
||||
columnName: 'column',
|
||||
dataTypeName: 'dataType',
|
||||
constraintName: 'constraint',
|
||||
sourceFile: 'file',
|
||||
sourceLine: 'line',
|
||||
sourceFunction: 'routine',
|
||||
}
|
||||
|
||||
NativeQuery.prototype.handleError = function (err) {
|
||||
@ -77,10 +80,12 @@ NativeQuery.prototype.catch = function (callback) {
|
||||
|
||||
NativeQuery.prototype._getPromise = function () {
|
||||
if (this._promise) return this._promise
|
||||
this._promise = new Promise(function (resolve, reject) {
|
||||
this._once('end', resolve)
|
||||
this._once('error', reject)
|
||||
}.bind(this))
|
||||
this._promise = new Promise(
|
||||
function (resolve, reject) {
|
||||
this._once('end', resolve)
|
||||
this._once('error', reject)
|
||||
}.bind(this)
|
||||
)
|
||||
return this._promise
|
||||
}
|
||||
|
||||
@ -105,7 +110,7 @@ NativeQuery.prototype.submit = function (client) {
|
||||
if (self._emitRowEvents) {
|
||||
if (results.length > 1) {
|
||||
rows.forEach((rowOfRows, i) => {
|
||||
rowOfRows.forEach(row => {
|
||||
rowOfRows.forEach((row) => {
|
||||
self.emit('row', row, results[i])
|
||||
})
|
||||
})
|
||||
|
||||
@ -42,14 +42,22 @@ class Query extends EventEmitter {
|
||||
|
||||
requiresPreparation() {
|
||||
// named queries must always be prepared
|
||||
if (this.name) { return true }
|
||||
if (this.name) {
|
||||
return true
|
||||
}
|
||||
// always prepare if there are max number of rows expected per
|
||||
// portal execution
|
||||
if (this.rows) { return true }
|
||||
if (this.rows) {
|
||||
return true
|
||||
}
|
||||
// don't prepare empty text queries
|
||||
if (!this.text) { return false }
|
||||
if (!this.text) {
|
||||
return false
|
||||
}
|
||||
// prepare if there are values
|
||||
if (!this.values) { return false }
|
||||
if (!this.values) {
|
||||
return false
|
||||
}
|
||||
return this.values.length > 0
|
||||
}
|
||||
|
||||
@ -168,10 +176,13 @@ class Query extends EventEmitter {
|
||||
}
|
||||
|
||||
_getRows(connection, rows) {
|
||||
connection.execute({
|
||||
portal: this.portal,
|
||||
rows: rows
|
||||
}, true)
|
||||
connection.execute(
|
||||
{
|
||||
portal: this.portal,
|
||||
rows: rows,
|
||||
},
|
||||
true
|
||||
)
|
||||
connection.flush()
|
||||
}
|
||||
|
||||
@ -181,11 +192,14 @@ class Query extends EventEmitter {
|
||||
this.isPreparedStatement = true
|
||||
// TODO refactor this poor encapsulation
|
||||
if (!this.hasBeenParsed(connection)) {
|
||||
connection.parse({
|
||||
text: this.text,
|
||||
name: this.name,
|
||||
types: this.types
|
||||
}, true)
|
||||
connection.parse(
|
||||
{
|
||||
text: this.text,
|
||||
name: this.name,
|
||||
types: this.types,
|
||||
},
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
if (this.values) {
|
||||
@ -198,17 +212,23 @@ class Query extends EventEmitter {
|
||||
}
|
||||
|
||||
// http://developer.postgresql.org/pgdocs/postgres/protocol-flow.html#PROTOCOL-FLOW-EXT-QUERY
|
||||
connection.bind({
|
||||
portal: this.portal,
|
||||
statement: this.name,
|
||||
values: this.values,
|
||||
binary: this.binary
|
||||
}, true)
|
||||
connection.bind(
|
||||
{
|
||||
portal: this.portal,
|
||||
statement: this.name,
|
||||
values: this.values,
|
||||
binary: this.binary,
|
||||
},
|
||||
true
|
||||
)
|
||||
|
||||
connection.describe({
|
||||
type: 'P',
|
||||
name: this.portal || ''
|
||||
}, true)
|
||||
connection.describe(
|
||||
{
|
||||
type: 'P',
|
||||
name: this.portal || '',
|
||||
},
|
||||
true
|
||||
)
|
||||
|
||||
this._getRows(connection, this.rows)
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
'use strict'
|
||||
const crypto = require('crypto')
|
||||
|
||||
function startSession (mechanisms) {
|
||||
function startSession(mechanisms) {
|
||||
if (mechanisms.indexOf('SCRAM-SHA-256') === -1) {
|
||||
throw new Error('SASL: Only mechanism SCRAM-SHA-256 is currently supported')
|
||||
}
|
||||
@ -12,11 +12,11 @@ function startSession (mechanisms) {
|
||||
mechanism: 'SCRAM-SHA-256',
|
||||
clientNonce,
|
||||
response: 'n,,n=*,r=' + clientNonce,
|
||||
message: 'SASLInitialResponse'
|
||||
message: 'SASLInitialResponse',
|
||||
}
|
||||
}
|
||||
|
||||
function continueSession (session, password, serverData) {
|
||||
function continueSession(session, password, serverData) {
|
||||
if (session.message !== 'SASLInitialResponse') {
|
||||
throw new Error('SASL: Last message was not SASLInitialResponse')
|
||||
}
|
||||
@ -53,42 +53,46 @@ function continueSession (session, password, serverData) {
|
||||
session.response = clientFinalMessageWithoutProof + ',p=' + clientProof
|
||||
}
|
||||
|
||||
function finalizeSession (session, serverData) {
|
||||
function finalizeSession(session, serverData) {
|
||||
if (session.message !== 'SASLResponse') {
|
||||
throw new Error('SASL: Last message was not SASLResponse')
|
||||
}
|
||||
|
||||
var serverSignature
|
||||
|
||||
String(serverData).split(',').forEach(function (part) {
|
||||
switch (part[0]) {
|
||||
case 'v':
|
||||
serverSignature = part.substr(2)
|
||||
break
|
||||
}
|
||||
})
|
||||
String(serverData)
|
||||
.split(',')
|
||||
.forEach(function (part) {
|
||||
switch (part[0]) {
|
||||
case 'v':
|
||||
serverSignature = part.substr(2)
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
if (serverSignature !== session.serverSignature) {
|
||||
throw new Error('SASL: SCRAM-SERVER-FINAL-MESSAGE: server signature does not match')
|
||||
}
|
||||
}
|
||||
|
||||
function extractVariablesFromFirstServerMessage (data) {
|
||||
function extractVariablesFromFirstServerMessage(data) {
|
||||
var nonce, salt, iteration
|
||||
|
||||
String(data).split(',').forEach(function (part) {
|
||||
switch (part[0]) {
|
||||
case 'r':
|
||||
nonce = part.substr(2)
|
||||
break
|
||||
case 's':
|
||||
salt = part.substr(2)
|
||||
break
|
||||
case 'i':
|
||||
iteration = parseInt(part.substr(2), 10)
|
||||
break
|
||||
}
|
||||
})
|
||||
String(data)
|
||||
.split(',')
|
||||
.forEach(function (part) {
|
||||
switch (part[0]) {
|
||||
case 'r':
|
||||
nonce = part.substr(2)
|
||||
break
|
||||
case 's':
|
||||
salt = part.substr(2)
|
||||
break
|
||||
case 'i':
|
||||
iteration = parseInt(part.substr(2), 10)
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
if (!nonce) {
|
||||
throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: nonce missing')
|
||||
@ -105,11 +109,11 @@ function extractVariablesFromFirstServerMessage (data) {
|
||||
return {
|
||||
nonce,
|
||||
salt,
|
||||
iteration
|
||||
iteration,
|
||||
}
|
||||
}
|
||||
|
||||
function xorBuffers (a, b) {
|
||||
function xorBuffers(a, b) {
|
||||
if (!Buffer.isBuffer(a)) a = Buffer.from(a)
|
||||
if (!Buffer.isBuffer(b)) b = Buffer.from(b)
|
||||
var res = []
|
||||
@ -125,11 +129,11 @@ function xorBuffers (a, b) {
|
||||
return Buffer.from(res)
|
||||
}
|
||||
|
||||
function createHMAC (key, msg) {
|
||||
function createHMAC(key, msg) {
|
||||
return crypto.createHmac('sha256', key).update(msg).digest()
|
||||
}
|
||||
|
||||
function Hi (password, saltBytes, iterations) {
|
||||
function Hi(password, saltBytes, iterations) {
|
||||
var ui1 = createHMAC(password, Buffer.concat([saltBytes, Buffer.from([0, 0, 0, 1])]))
|
||||
var ui = ui1
|
||||
for (var i = 0; i < iterations - 1; i++) {
|
||||
@ -143,5 +147,5 @@ function Hi (password, saltBytes, iterations) {
|
||||
module.exports = {
|
||||
startSession,
|
||||
continueSession,
|
||||
finalizeSession
|
||||
finalizeSession,
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
|
||||
var types = require('pg-types')
|
||||
|
||||
function TypeOverrides (userTypes) {
|
||||
function TypeOverrides(userTypes) {
|
||||
this._types = userTypes || types
|
||||
this.text = {}
|
||||
this.binary = {}
|
||||
@ -17,9 +17,12 @@ function TypeOverrides (userTypes) {
|
||||
|
||||
TypeOverrides.prototype.getOverrides = function (format) {
|
||||
switch (format) {
|
||||
case 'text': return this.text
|
||||
case 'binary': return this.binary
|
||||
default: return {}
|
||||
case 'text':
|
||||
return this.text
|
||||
case 'binary':
|
||||
return this.binary
|
||||
default:
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -11,10 +11,8 @@ const crypto = require('crypto')
|
||||
|
||||
const defaults = require('./defaults')
|
||||
|
||||
function escapeElement (elementRepresentation) {
|
||||
var escaped = elementRepresentation
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/"/g, '\\"')
|
||||
function escapeElement(elementRepresentation) {
|
||||
var escaped = elementRepresentation.replace(/\\/g, '\\\\').replace(/"/g, '\\"')
|
||||
|
||||
return '"' + escaped + '"'
|
||||
}
|
||||
@ -22,7 +20,7 @@ function escapeElement (elementRepresentation) {
|
||||
// convert a JS array to a postgres array literal
|
||||
// uses comma separator so won't work for types like box that use
|
||||
// a different array separator.
|
||||
function arrayString (val) {
|
||||
function arrayString(val) {
|
||||
var result = '{'
|
||||
for (var i = 0; i < val.length; i++) {
|
||||
if (i > 0) {
|
||||
@ -76,7 +74,7 @@ var prepareValue = function (val, seen) {
|
||||
return val.toString()
|
||||
}
|
||||
|
||||
function prepareObject (val, seen) {
|
||||
function prepareObject(val, seen) {
|
||||
if (val && typeof val.toPostgres === 'function') {
|
||||
seen = seen || []
|
||||
if (seen.indexOf(val) !== -1) {
|
||||
@ -89,48 +87,66 @@ function prepareObject (val, seen) {
|
||||
return JSON.stringify(val)
|
||||
}
|
||||
|
||||
function pad (number, digits) {
|
||||
function pad(number, digits) {
|
||||
number = '' + number
|
||||
while (number.length < digits) { number = '0' + number }
|
||||
while (number.length < digits) {
|
||||
number = '0' + number
|
||||
}
|
||||
return number
|
||||
}
|
||||
|
||||
function dateToString (date) {
|
||||
function dateToString(date) {
|
||||
var offset = -date.getTimezoneOffset()
|
||||
|
||||
var year = date.getFullYear()
|
||||
var isBCYear = year < 1
|
||||
if (isBCYear) year = Math.abs(year) + 1 // negative years are 1 off their BC representation
|
||||
|
||||
var ret = pad(year, 4) + '-' +
|
||||
pad(date.getMonth() + 1, 2) + '-' +
|
||||
pad(date.getDate(), 2) + 'T' +
|
||||
pad(date.getHours(), 2) + ':' +
|
||||
pad(date.getMinutes(), 2) + ':' +
|
||||
pad(date.getSeconds(), 2) + '.' +
|
||||
var ret =
|
||||
pad(year, 4) +
|
||||
'-' +
|
||||
pad(date.getMonth() + 1, 2) +
|
||||
'-' +
|
||||
pad(date.getDate(), 2) +
|
||||
'T' +
|
||||
pad(date.getHours(), 2) +
|
||||
':' +
|
||||
pad(date.getMinutes(), 2) +
|
||||
':' +
|
||||
pad(date.getSeconds(), 2) +
|
||||
'.' +
|
||||
pad(date.getMilliseconds(), 3)
|
||||
|
||||
if (offset < 0) {
|
||||
ret += '-'
|
||||
offset *= -1
|
||||
} else { ret += '+' }
|
||||
} else {
|
||||
ret += '+'
|
||||
}
|
||||
|
||||
ret += pad(Math.floor(offset / 60), 2) + ':' + pad(offset % 60, 2)
|
||||
if (isBCYear) ret += ' BC'
|
||||
return ret
|
||||
}
|
||||
|
||||
function dateToStringUTC (date) {
|
||||
function dateToStringUTC(date) {
|
||||
var year = date.getUTCFullYear()
|
||||
var isBCYear = year < 1
|
||||
if (isBCYear) year = Math.abs(year) + 1 // negative years are 1 off their BC representation
|
||||
|
||||
var ret = pad(year, 4) + '-' +
|
||||
pad(date.getUTCMonth() + 1, 2) + '-' +
|
||||
pad(date.getUTCDate(), 2) + 'T' +
|
||||
pad(date.getUTCHours(), 2) + ':' +
|
||||
pad(date.getUTCMinutes(), 2) + ':' +
|
||||
pad(date.getUTCSeconds(), 2) + '.' +
|
||||
var ret =
|
||||
pad(year, 4) +
|
||||
'-' +
|
||||
pad(date.getUTCMonth() + 1, 2) +
|
||||
'-' +
|
||||
pad(date.getUTCDate(), 2) +
|
||||
'T' +
|
||||
pad(date.getUTCHours(), 2) +
|
||||
':' +
|
||||
pad(date.getUTCMinutes(), 2) +
|
||||
':' +
|
||||
pad(date.getUTCSeconds(), 2) +
|
||||
'.' +
|
||||
pad(date.getUTCMilliseconds(), 3)
|
||||
|
||||
ret += '+00:00'
|
||||
@ -138,9 +154,9 @@ function dateToStringUTC (date) {
|
||||
return ret
|
||||
}
|
||||
|
||||
function normalizeQueryConfig (config, values, callback) {
|
||||
function normalizeQueryConfig(config, values, callback) {
|
||||
// can take in strings or config objects
|
||||
config = (typeof (config) === 'string') ? { text: config } : config
|
||||
config = typeof config === 'string' ? { text: config } : config
|
||||
if (values) {
|
||||
if (typeof values === 'function') {
|
||||
config.callback = values
|
||||
@ -166,12 +182,12 @@ const postgresMd5PasswordHash = function (user, password, salt) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
prepareValue: function prepareValueWrapper (value) {
|
||||
prepareValue: function prepareValueWrapper(value) {
|
||||
// this ensures that extra arguments do not get passed into prepareValue
|
||||
// by accident, eg: from calling values.map(utils.prepareValue)
|
||||
return prepareValue(value)
|
||||
},
|
||||
normalizeQueryConfig,
|
||||
postgresMd5PasswordHash,
|
||||
md5
|
||||
md5,
|
||||
}
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -3,32 +3,32 @@ var args = require(__dirname + '/../test/cli')
|
||||
var pg = require(__dirname + '/../lib')
|
||||
|
||||
var people = [
|
||||
{name: 'Aaron', age: 10},
|
||||
{name: 'Brian', age: 20},
|
||||
{name: 'Chris', age: 30},
|
||||
{name: 'David', age: 40},
|
||||
{name: 'Elvis', age: 50},
|
||||
{name: 'Frank', age: 60},
|
||||
{name: 'Grace', age: 70},
|
||||
{name: 'Haley', age: 80},
|
||||
{name: 'Irma', age: 90},
|
||||
{name: 'Jenny', age: 100},
|
||||
{name: 'Kevin', age: 110},
|
||||
{name: 'Larry', age: 120},
|
||||
{name: 'Michelle', age: 130},
|
||||
{name: 'Nancy', age: 140},
|
||||
{name: 'Olivia', age: 150},
|
||||
{name: 'Peter', age: 160},
|
||||
{name: 'Quinn', age: 170},
|
||||
{name: 'Ronda', age: 180},
|
||||
{name: 'Shelley', age: 190},
|
||||
{name: 'Tobias', age: 200},
|
||||
{name: 'Uma', age: 210},
|
||||
{name: 'Veena', age: 220},
|
||||
{name: 'Wanda', age: 230},
|
||||
{name: 'Xavier', age: 240},
|
||||
{name: 'Yoyo', age: 250},
|
||||
{name: 'Zanzabar', age: 260}
|
||||
{ name: 'Aaron', age: 10 },
|
||||
{ name: 'Brian', age: 20 },
|
||||
{ name: 'Chris', age: 30 },
|
||||
{ name: 'David', age: 40 },
|
||||
{ name: 'Elvis', age: 50 },
|
||||
{ name: 'Frank', age: 60 },
|
||||
{ name: 'Grace', age: 70 },
|
||||
{ name: 'Haley', age: 80 },
|
||||
{ name: 'Irma', age: 90 },
|
||||
{ name: 'Jenny', age: 100 },
|
||||
{ name: 'Kevin', age: 110 },
|
||||
{ name: 'Larry', age: 120 },
|
||||
{ name: 'Michelle', age: 130 },
|
||||
{ name: 'Nancy', age: 140 },
|
||||
{ name: 'Olivia', age: 150 },
|
||||
{ name: 'Peter', age: 160 },
|
||||
{ name: 'Quinn', age: 170 },
|
||||
{ name: 'Ronda', age: 180 },
|
||||
{ name: 'Shelley', age: 190 },
|
||||
{ name: 'Tobias', age: 200 },
|
||||
{ name: 'Uma', age: 210 },
|
||||
{ name: 'Veena', age: 220 },
|
||||
{ name: 'Wanda', age: 230 },
|
||||
{ name: 'Xavier', age: 240 },
|
||||
{ name: 'Yoyo', age: 250 },
|
||||
{ name: 'Zanzabar', age: 260 },
|
||||
]
|
||||
|
||||
var con = new pg.Client({
|
||||
@ -36,7 +36,7 @@ var con = new pg.Client({
|
||||
port: args.port,
|
||||
user: args.user,
|
||||
password: args.password,
|
||||
database: args.database
|
||||
database: args.database,
|
||||
})
|
||||
|
||||
con.connect((err) => {
|
||||
@ -45,8 +45,7 @@ con.connect((err) => {
|
||||
}
|
||||
|
||||
con.query(
|
||||
'DROP TABLE IF EXISTS person;'
|
||||
+ ' CREATE TABLE person (id serial, name varchar(10), age integer)',
|
||||
'DROP TABLE IF EXISTS person;' + ' CREATE TABLE person (id serial, name varchar(10), age integer)',
|
||||
(err) => {
|
||||
if (err) {
|
||||
throw err
|
||||
@ -56,10 +55,8 @@ con.connect((err) => {
|
||||
console.log('Filling it with people')
|
||||
|
||||
con.query(
|
||||
'INSERT INTO person (name, age) VALUES'
|
||||
+ people
|
||||
.map((person) => ` ('${person.name}', ${person.age})`)
|
||||
.join(','),
|
||||
'INSERT INTO person (name, age) VALUES' +
|
||||
people.map((person) => ` ('${person.name}', ${person.age})`).join(','),
|
||||
(err, result) => {
|
||||
if (err) {
|
||||
throw err
|
||||
@ -67,6 +64,8 @@ con.connect((err) => {
|
||||
|
||||
console.log(`Inserted ${result.rowCount} people`)
|
||||
con.end()
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@ -2,22 +2,17 @@
|
||||
var pg = require(__dirname + '/../lib')
|
||||
var args = require(__dirname + '/../test/cli')
|
||||
|
||||
var queries = [
|
||||
'select CURRENT_TIMESTAMP',
|
||||
"select interval '1 day' + interval '1 hour'",
|
||||
"select TIMESTAMP 'today'"]
|
||||
var queries = ['select CURRENT_TIMESTAMP', "select interval '1 day' + interval '1 hour'", "select TIMESTAMP 'today'"]
|
||||
|
||||
queries.forEach(function (query) {
|
||||
var client = new pg.Client({
|
||||
user: args.user,
|
||||
database: args.database,
|
||||
password: args.password
|
||||
password: args.password,
|
||||
})
|
||||
client.connect()
|
||||
client
|
||||
.query(query)
|
||||
.on('row', function (row) {
|
||||
console.log(row)
|
||||
client.end()
|
||||
})
|
||||
client.query(query).on('row', function (row) {
|
||||
console.log(row)
|
||||
client.end()
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
'use strict'
|
||||
var helper = require(__dirname + '/../test/integration/test-helper')
|
||||
var pg = helper.pg
|
||||
pg.connect(helper.config, assert.success(function (client) {
|
||||
var query = client.query('select oid, typname from pg_type where typtype = \'b\' order by oid')
|
||||
query.on('row', console.log)
|
||||
}))
|
||||
pg.connect(
|
||||
helper.config,
|
||||
assert.success(function (client) {
|
||||
var query = client.query("select oid, typname from pg_type where typtype = 'b' order by oid")
|
||||
query.on('row', console.log)
|
||||
})
|
||||
)
|
||||
|
||||
@ -10,7 +10,7 @@ p.add = function (buffer, front) {
|
||||
}
|
||||
|
||||
p.addInt16 = function (val, front) {
|
||||
return this.add(Buffer.from([(val >>> 8), (val >>> 0)]), front)
|
||||
return this.add(Buffer.from([val >>> 8, val >>> 0]), front)
|
||||
}
|
||||
|
||||
p.getByteLength = function (initial) {
|
||||
@ -20,12 +20,10 @@ p.getByteLength = function (initial) {
|
||||
}
|
||||
|
||||
p.addInt32 = function (val, first) {
|
||||
return this.add(Buffer.from([
|
||||
(val >>> 24 & 0xFF),
|
||||
(val >>> 16 & 0xFF),
|
||||
(val >>> 8 & 0xFF),
|
||||
(val >>> 0 & 0xFF)
|
||||
]), first)
|
||||
return this.add(
|
||||
Buffer.from([(val >>> 24) & 0xff, (val >>> 16) & 0xff, (val >>> 8) & 0xff, (val >>> 0) & 0xff]),
|
||||
first
|
||||
)
|
||||
}
|
||||
|
||||
p.addCString = function (val, front) {
|
||||
|
||||
@ -9,13 +9,15 @@ suite.test('null and undefined are both inserted as NULL', function (done) {
|
||||
pool.connect(
|
||||
assert.calls(function (err, client, release) {
|
||||
assert(!err)
|
||||
client.query(
|
||||
'CREATE TEMP TABLE my_nulls(a varchar(1), b varchar(1), c integer, d integer, e date, f date)'
|
||||
)
|
||||
client.query(
|
||||
'INSERT INTO my_nulls(a,b,c,d,e,f) VALUES ($1,$2,$3,$4,$5,$6)',
|
||||
[null, undefined, null, undefined, null, undefined]
|
||||
)
|
||||
client.query('CREATE TEMP TABLE my_nulls(a varchar(1), b varchar(1), c integer, d integer, e date, f date)')
|
||||
client.query('INSERT INTO my_nulls(a,b,c,d,e,f) VALUES ($1,$2,$3,$4,$5,$6)', [
|
||||
null,
|
||||
undefined,
|
||||
null,
|
||||
undefined,
|
||||
null,
|
||||
undefined,
|
||||
])
|
||||
client.query(
|
||||
'SELECT * FROM my_nulls',
|
||||
assert.calls(function (err, result) {
|
||||
@ -36,7 +38,7 @@ suite.test('null and undefined are both inserted as NULL', function (done) {
|
||||
)
|
||||
})
|
||||
|
||||
suite.test('pool callback behavior', done => {
|
||||
suite.test('pool callback behavior', (done) => {
|
||||
// test weird callback behavior with node-pool
|
||||
const pool = new pg.Pool()
|
||||
pool.connect(function (err) {
|
||||
@ -50,51 +52,63 @@ suite.test('pool callback behavior', done => {
|
||||
suite.test('query timeout', (cb) => {
|
||||
const pool = new pg.Pool({ query_timeout: 1000 })
|
||||
pool.connect().then((client) => {
|
||||
client.query('SELECT pg_sleep(2)', assert.calls(function (err, result) {
|
||||
assert(err)
|
||||
assert(err.message === 'Query read timeout')
|
||||
client.release()
|
||||
pool.end(cb)
|
||||
}))
|
||||
client.query(
|
||||
'SELECT pg_sleep(2)',
|
||||
assert.calls(function (err, result) {
|
||||
assert(err)
|
||||
assert(err.message === 'Query read timeout')
|
||||
client.release()
|
||||
pool.end(cb)
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
suite.test('query recover from timeout', (cb) => {
|
||||
const pool = new pg.Pool({ query_timeout: 1000 })
|
||||
pool.connect().then((client) => {
|
||||
client.query('SELECT pg_sleep(20)', assert.calls(function (err, result) {
|
||||
assert(err)
|
||||
assert(err.message === 'Query read timeout')
|
||||
client.release(err)
|
||||
pool.connect().then((client) => {
|
||||
client.query('SELECT 1', assert.calls(function (err, result) {
|
||||
assert(!err)
|
||||
client.release(err)
|
||||
pool.end(cb)
|
||||
}))
|
||||
client.query(
|
||||
'SELECT pg_sleep(20)',
|
||||
assert.calls(function (err, result) {
|
||||
assert(err)
|
||||
assert(err.message === 'Query read timeout')
|
||||
client.release(err)
|
||||
pool.connect().then((client) => {
|
||||
client.query(
|
||||
'SELECT 1',
|
||||
assert.calls(function (err, result) {
|
||||
assert(!err)
|
||||
client.release(err)
|
||||
pool.end(cb)
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
}))
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
suite.test('query no timeout', (cb) => {
|
||||
const pool = new pg.Pool({ query_timeout: 10000 })
|
||||
pool.connect().then((client) => {
|
||||
client.query('SELECT pg_sleep(1)', assert.calls(function (err, result) {
|
||||
assert(!err)
|
||||
client.release()
|
||||
pool.end(cb)
|
||||
}))
|
||||
client.query(
|
||||
'SELECT pg_sleep(1)',
|
||||
assert.calls(function (err, result) {
|
||||
assert(!err)
|
||||
client.release()
|
||||
pool.end(cb)
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
suite.test('callback API', done => {
|
||||
suite.test('callback API', (done) => {
|
||||
const client = new helper.Client()
|
||||
client.query('CREATE TEMP TABLE peep(name text)')
|
||||
client.query('INSERT INTO peep(name) VALUES ($1)', ['brianc'])
|
||||
const config = {
|
||||
text: 'INSERT INTO peep(name) VALUES ($1)',
|
||||
values: ['brian']
|
||||
values: ['brian'],
|
||||
}
|
||||
client.query(config)
|
||||
client.query('INSERT INTO peep(name) VALUES ($1)', ['aaron'])
|
||||
@ -104,18 +118,18 @@ suite.test('callback API', done => {
|
||||
assert.equal(res.rowCount, 3)
|
||||
assert.deepEqual(res.rows, [
|
||||
{
|
||||
name: 'aaron'
|
||||
name: 'aaron',
|
||||
},
|
||||
{
|
||||
name: 'brian'
|
||||
name: 'brian',
|
||||
},
|
||||
{
|
||||
name: 'brianc'
|
||||
}
|
||||
name: 'brianc',
|
||||
},
|
||||
])
|
||||
done()
|
||||
})
|
||||
client.connect(err => {
|
||||
client.connect((err) => {
|
||||
assert(!err)
|
||||
client.once('drain', () => client.end())
|
||||
})
|
||||
@ -175,8 +189,7 @@ suite.test('query errors are handled and do not bubble if callback is provided',
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
suite.test('callback is fired once and only once', function (done) {
|
||||
const pool = new pg.Pool()
|
||||
@ -189,14 +202,10 @@ suite.test('callback is fired once and only once', function (done) {
|
||||
[
|
||||
"INSERT INTO boom(name) VALUES('hai')",
|
||||
"INSERT INTO boom(name) VALUES('boom')",
|
||||
"INSERT INTO boom(name) VALUES('zoom')"
|
||||
"INSERT INTO boom(name) VALUES('zoom')",
|
||||
].join(';'),
|
||||
function (err, callback) {
|
||||
assert.equal(
|
||||
callCount++,
|
||||
0,
|
||||
'Call count should be 0. More means this callback fired more than once.'
|
||||
)
|
||||
assert.equal(callCount++, 0, 'Call count should be 0. More means this callback fired more than once.')
|
||||
release()
|
||||
pool.end(done)
|
||||
}
|
||||
@ -213,7 +222,7 @@ suite.test('can provide callback and config object', function (done) {
|
||||
client.query(
|
||||
{
|
||||
name: 'boom',
|
||||
text: 'select NOW()'
|
||||
text: 'select NOW()',
|
||||
},
|
||||
assert.calls(function (err, result) {
|
||||
assert(!err)
|
||||
@ -232,7 +241,7 @@ suite.test('can provide callback and config and parameters', function (done) {
|
||||
assert.calls(function (err, client, release) {
|
||||
assert(!err)
|
||||
var config = {
|
||||
text: 'select $1::text as val'
|
||||
text: 'select $1::text as val',
|
||||
}
|
||||
client.query(
|
||||
config,
|
||||
|
||||
@ -6,24 +6,29 @@ var suite = new helper.Suite()
|
||||
|
||||
var conInfo = helper.config
|
||||
|
||||
function getConInfo (override) {
|
||||
return Object.assign({}, conInfo, override )
|
||||
function getConInfo(override) {
|
||||
return Object.assign({}, conInfo, override)
|
||||
}
|
||||
|
||||
function getAppName (conf, cb) {
|
||||
function getAppName(conf, cb) {
|
||||
var client = new Client(conf)
|
||||
client.connect(assert.success(function () {
|
||||
client.query('SHOW application_name', assert.success(function (res) {
|
||||
var appName = res.rows[0].application_name
|
||||
cb(appName)
|
||||
client.end()
|
||||
}))
|
||||
}))
|
||||
client.connect(
|
||||
assert.success(function () {
|
||||
client.query(
|
||||
'SHOW application_name',
|
||||
assert.success(function (res) {
|
||||
var appName = res.rows[0].application_name
|
||||
cb(appName)
|
||||
client.end()
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
suite.test('No default appliation_name ', function (done) {
|
||||
var conf = getConInfo()
|
||||
getAppName({ }, function (res) {
|
||||
getAppName({}, function (res) {
|
||||
assert.strictEqual(res, '')
|
||||
done()
|
||||
})
|
||||
@ -32,7 +37,7 @@ suite.test('No default appliation_name ', function (done) {
|
||||
suite.test('fallback_application_name is used', function (done) {
|
||||
var fbAppName = 'this is my app'
|
||||
var conf = getConInfo({
|
||||
'fallback_application_name': fbAppName
|
||||
fallback_application_name: fbAppName,
|
||||
})
|
||||
getAppName(conf, function (res) {
|
||||
assert.strictEqual(res, fbAppName)
|
||||
@ -43,7 +48,7 @@ suite.test('fallback_application_name is used', function (done) {
|
||||
suite.test('application_name is used', function (done) {
|
||||
var appName = 'some wired !@#$% application_name'
|
||||
var conf = getConInfo({
|
||||
'application_name': appName
|
||||
application_name: appName,
|
||||
})
|
||||
getAppName(conf, function (res) {
|
||||
assert.strictEqual(res, appName)
|
||||
@ -55,8 +60,8 @@ suite.test('application_name has precedence over fallback_application_name', fun
|
||||
var appName = 'some wired !@#$% application_name'
|
||||
var fbAppName = 'some other strange $$test$$ appname'
|
||||
var conf = getConInfo({
|
||||
'application_name': appName,
|
||||
'fallback_application_name': fbAppName
|
||||
application_name: appName,
|
||||
fallback_application_name: fbAppName,
|
||||
})
|
||||
getAppName(conf, function (res) {
|
||||
assert.strictEqual(res, appName)
|
||||
@ -82,8 +87,8 @@ suite.test('application_name from connection string', function (done) {
|
||||
// TODO: make the test work for native client too
|
||||
if (!helper.args.native) {
|
||||
suite.test('application_name is read from the env', function (done) {
|
||||
var appName = process.env.PGAPPNAME = 'testest'
|
||||
getAppName({ }, function (res) {
|
||||
var appName = (process.env.PGAPPNAME = 'testest')
|
||||
getAppName({}, function (res) {
|
||||
delete process.env.PGAPPNAME
|
||||
assert.strictEqual(res, appName)
|
||||
done()
|
||||
|
||||
@ -6,172 +6,226 @@ var suite = new helper.Suite()
|
||||
|
||||
const pool = new pg.Pool()
|
||||
|
||||
pool.connect(assert.calls(function (err, client, release) {
|
||||
assert(!err)
|
||||
|
||||
suite.test('nulls', function (done) {
|
||||
client.query('SELECT $1::text[] as array', [[null]], assert.success(function (result) {
|
||||
var array = result.rows[0].array
|
||||
assert.lengthIs(array, 1)
|
||||
assert.isNull(array[0])
|
||||
done()
|
||||
}))
|
||||
})
|
||||
|
||||
suite.test('elements containing JSON-escaped characters', function (done) {
|
||||
var param = '\\"\\"'
|
||||
|
||||
for (var i = 1; i <= 0x1f; i++) {
|
||||
param += String.fromCharCode(i)
|
||||
}
|
||||
|
||||
client.query('SELECT $1::text[] as array', [[param]], assert.success(function (result) {
|
||||
var array = result.rows[0].array
|
||||
assert.lengthIs(array, 1)
|
||||
assert.equal(array[0], param)
|
||||
done()
|
||||
}))
|
||||
})
|
||||
|
||||
suite.test('cleanup', () => release())
|
||||
|
||||
pool.connect(assert.calls(function (err, client, release) {
|
||||
pool.connect(
|
||||
assert.calls(function (err, client, release) {
|
||||
assert(!err)
|
||||
client.query('CREATE TEMP TABLE why(names text[], numbors integer[])')
|
||||
client.query(new pg.Query('INSERT INTO why(names, numbors) VALUES(\'{"aaron", "brian","a b c" }\', \'{1, 2, 3}\')')).on('error', console.log)
|
||||
suite.test('numbers', function (done) {
|
||||
// client.connection.on('message', console.log)
|
||||
client.query('SELECT numbors FROM why', assert.success(function (result) {
|
||||
assert.lengthIs(result.rows[0].numbors, 3)
|
||||
assert.equal(result.rows[0].numbors[0], 1)
|
||||
assert.equal(result.rows[0].numbors[1], 2)
|
||||
assert.equal(result.rows[0].numbors[2], 3)
|
||||
done()
|
||||
}))
|
||||
})
|
||||
|
||||
suite.test('parses string arrays', function (done) {
|
||||
client.query('SELECT names FROM why', assert.success(function (result) {
|
||||
var names = result.rows[0].names
|
||||
assert.lengthIs(names, 3)
|
||||
assert.equal(names[0], 'aaron')
|
||||
assert.equal(names[1], 'brian')
|
||||
assert.equal(names[2], 'a b c')
|
||||
done()
|
||||
}))
|
||||
})
|
||||
|
||||
suite.test('empty array', function (done) {
|
||||
client.query("SELECT '{}'::text[] as names", assert.success(function (result) {
|
||||
var names = result.rows[0].names
|
||||
assert.lengthIs(names, 0)
|
||||
done()
|
||||
}))
|
||||
})
|
||||
|
||||
suite.test('element containing comma', function (done) {
|
||||
client.query("SELECT '{\"joe,bob\",jim}'::text[] as names", assert.success(function (result) {
|
||||
var names = result.rows[0].names
|
||||
assert.lengthIs(names, 2)
|
||||
assert.equal(names[0], 'joe,bob')
|
||||
assert.equal(names[1], 'jim')
|
||||
done()
|
||||
}))
|
||||
})
|
||||
|
||||
suite.test('bracket in quotes', function (done) {
|
||||
client.query("SELECT '{\"{\",\"}\"}'::text[] as names", assert.success(function (result) {
|
||||
var names = result.rows[0].names
|
||||
assert.lengthIs(names, 2)
|
||||
assert.equal(names[0], '{')
|
||||
assert.equal(names[1], '}')
|
||||
done()
|
||||
}))
|
||||
})
|
||||
|
||||
suite.test('null value', function (done) {
|
||||
client.query("SELECT '{joe,null,bob,\"NULL\"}'::text[] as names", assert.success(function (result) {
|
||||
var names = result.rows[0].names
|
||||
assert.lengthIs(names, 4)
|
||||
assert.equal(names[0], 'joe')
|
||||
assert.equal(names[1], null)
|
||||
assert.equal(names[2], 'bob')
|
||||
assert.equal(names[3], 'NULL')
|
||||
done()
|
||||
}))
|
||||
})
|
||||
|
||||
suite.test('element containing quote char', function (done) {
|
||||
client.query("SELECT ARRAY['joe''', 'jim', 'bob\"'] AS names", assert.success(function (result) {
|
||||
var names = result.rows[0].names
|
||||
assert.lengthIs(names, 3)
|
||||
assert.equal(names[0], 'joe\'')
|
||||
assert.equal(names[1], 'jim')
|
||||
assert.equal(names[2], 'bob"')
|
||||
done()
|
||||
}))
|
||||
})
|
||||
|
||||
suite.test('nested array', function (done) {
|
||||
client.query("SELECT '{{1,joe},{2,bob}}'::text[] as names", assert.success(function (result) {
|
||||
var names = result.rows[0].names
|
||||
assert.lengthIs(names, 2)
|
||||
|
||||
assert.lengthIs(names[0], 2)
|
||||
assert.equal(names[0][0], '1')
|
||||
assert.equal(names[0][1], 'joe')
|
||||
|
||||
assert.lengthIs(names[1], 2)
|
||||
assert.equal(names[1][0], '2')
|
||||
assert.equal(names[1][1], 'bob')
|
||||
done()
|
||||
}))
|
||||
})
|
||||
|
||||
suite.test('integer array', function (done) {
|
||||
client.query("SELECT '{1,2,3}'::integer[] as names", assert.success(function (result) {
|
||||
var names = result.rows[0].names
|
||||
assert.lengthIs(names, 3)
|
||||
assert.equal(names[0], 1)
|
||||
assert.equal(names[1], 2)
|
||||
assert.equal(names[2], 3)
|
||||
done()
|
||||
}))
|
||||
})
|
||||
|
||||
suite.test('integer nested array', function (done) {
|
||||
client.query("SELECT '{{1,100},{2,100},{3,100}}'::integer[] as names", assert.success(function (result) {
|
||||
var names = result.rows[0].names
|
||||
assert.lengthIs(names, 3)
|
||||
assert.equal(names[0][0], 1)
|
||||
assert.equal(names[0][1], 100)
|
||||
|
||||
assert.equal(names[1][0], 2)
|
||||
assert.equal(names[1][1], 100)
|
||||
|
||||
assert.equal(names[2][0], 3)
|
||||
assert.equal(names[2][1], 100)
|
||||
done()
|
||||
}))
|
||||
})
|
||||
|
||||
suite.test('JS array parameter', function (done) {
|
||||
client.query('SELECT $1::integer[] as names', [[[1, 100], [2, 100], [3, 100]]], assert.success(function (result) {
|
||||
var names = result.rows[0].names
|
||||
assert.lengthIs(names, 3)
|
||||
assert.equal(names[0][0], 1)
|
||||
assert.equal(names[0][1], 100)
|
||||
|
||||
assert.equal(names[1][0], 2)
|
||||
assert.equal(names[1][1], 100)
|
||||
|
||||
assert.equal(names[2][0], 3)
|
||||
assert.equal(names[2][1], 100)
|
||||
release()
|
||||
pool.end(() => {
|
||||
suite.test('nulls', function (done) {
|
||||
client.query(
|
||||
'SELECT $1::text[] as array',
|
||||
[[null]],
|
||||
assert.success(function (result) {
|
||||
var array = result.rows[0].array
|
||||
assert.lengthIs(array, 1)
|
||||
assert.isNull(array[0])
|
||||
done()
|
||||
})
|
||||
}))
|
||||
)
|
||||
})
|
||||
}))
|
||||
}))
|
||||
|
||||
suite.test('elements containing JSON-escaped characters', function (done) {
|
||||
var param = '\\"\\"'
|
||||
|
||||
for (var i = 1; i <= 0x1f; i++) {
|
||||
param += String.fromCharCode(i)
|
||||
}
|
||||
|
||||
client.query(
|
||||
'SELECT $1::text[] as array',
|
||||
[[param]],
|
||||
assert.success(function (result) {
|
||||
var array = result.rows[0].array
|
||||
assert.lengthIs(array, 1)
|
||||
assert.equal(array[0], param)
|
||||
done()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
suite.test('cleanup', () => release())
|
||||
|
||||
pool.connect(
|
||||
assert.calls(function (err, client, release) {
|
||||
assert(!err)
|
||||
client.query('CREATE TEMP TABLE why(names text[], numbors integer[])')
|
||||
client
|
||||
.query(new pg.Query('INSERT INTO why(names, numbors) VALUES(\'{"aaron", "brian","a b c" }\', \'{1, 2, 3}\')'))
|
||||
.on('error', console.log)
|
||||
suite.test('numbers', function (done) {
|
||||
// client.connection.on('message', console.log)
|
||||
client.query(
|
||||
'SELECT numbors FROM why',
|
||||
assert.success(function (result) {
|
||||
assert.lengthIs(result.rows[0].numbors, 3)
|
||||
assert.equal(result.rows[0].numbors[0], 1)
|
||||
assert.equal(result.rows[0].numbors[1], 2)
|
||||
assert.equal(result.rows[0].numbors[2], 3)
|
||||
done()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
suite.test('parses string arrays', function (done) {
|
||||
client.query(
|
||||
'SELECT names FROM why',
|
||||
assert.success(function (result) {
|
||||
var names = result.rows[0].names
|
||||
assert.lengthIs(names, 3)
|
||||
assert.equal(names[0], 'aaron')
|
||||
assert.equal(names[1], 'brian')
|
||||
assert.equal(names[2], 'a b c')
|
||||
done()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
suite.test('empty array', function (done) {
|
||||
client.query(
|
||||
"SELECT '{}'::text[] as names",
|
||||
assert.success(function (result) {
|
||||
var names = result.rows[0].names
|
||||
assert.lengthIs(names, 0)
|
||||
done()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
suite.test('element containing comma', function (done) {
|
||||
client.query(
|
||||
'SELECT \'{"joe,bob",jim}\'::text[] as names',
|
||||
assert.success(function (result) {
|
||||
var names = result.rows[0].names
|
||||
assert.lengthIs(names, 2)
|
||||
assert.equal(names[0], 'joe,bob')
|
||||
assert.equal(names[1], 'jim')
|
||||
done()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
suite.test('bracket in quotes', function (done) {
|
||||
client.query(
|
||||
'SELECT \'{"{","}"}\'::text[] as names',
|
||||
assert.success(function (result) {
|
||||
var names = result.rows[0].names
|
||||
assert.lengthIs(names, 2)
|
||||
assert.equal(names[0], '{')
|
||||
assert.equal(names[1], '}')
|
||||
done()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
suite.test('null value', function (done) {
|
||||
client.query(
|
||||
'SELECT \'{joe,null,bob,"NULL"}\'::text[] as names',
|
||||
assert.success(function (result) {
|
||||
var names = result.rows[0].names
|
||||
assert.lengthIs(names, 4)
|
||||
assert.equal(names[0], 'joe')
|
||||
assert.equal(names[1], null)
|
||||
assert.equal(names[2], 'bob')
|
||||
assert.equal(names[3], 'NULL')
|
||||
done()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
suite.test('element containing quote char', function (done) {
|
||||
client.query(
|
||||
"SELECT ARRAY['joe''', 'jim', 'bob\"'] AS names",
|
||||
assert.success(function (result) {
|
||||
var names = result.rows[0].names
|
||||
assert.lengthIs(names, 3)
|
||||
assert.equal(names[0], "joe'")
|
||||
assert.equal(names[1], 'jim')
|
||||
assert.equal(names[2], 'bob"')
|
||||
done()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
suite.test('nested array', function (done) {
|
||||
client.query(
|
||||
"SELECT '{{1,joe},{2,bob}}'::text[] as names",
|
||||
assert.success(function (result) {
|
||||
var names = result.rows[0].names
|
||||
assert.lengthIs(names, 2)
|
||||
|
||||
assert.lengthIs(names[0], 2)
|
||||
assert.equal(names[0][0], '1')
|
||||
assert.equal(names[0][1], 'joe')
|
||||
|
||||
assert.lengthIs(names[1], 2)
|
||||
assert.equal(names[1][0], '2')
|
||||
assert.equal(names[1][1], 'bob')
|
||||
done()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
suite.test('integer array', function (done) {
|
||||
client.query(
|
||||
"SELECT '{1,2,3}'::integer[] as names",
|
||||
assert.success(function (result) {
|
||||
var names = result.rows[0].names
|
||||
assert.lengthIs(names, 3)
|
||||
assert.equal(names[0], 1)
|
||||
assert.equal(names[1], 2)
|
||||
assert.equal(names[2], 3)
|
||||
done()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
suite.test('integer nested array', function (done) {
|
||||
client.query(
|
||||
"SELECT '{{1,100},{2,100},{3,100}}'::integer[] as names",
|
||||
assert.success(function (result) {
|
||||
var names = result.rows[0].names
|
||||
assert.lengthIs(names, 3)
|
||||
assert.equal(names[0][0], 1)
|
||||
assert.equal(names[0][1], 100)
|
||||
|
||||
assert.equal(names[1][0], 2)
|
||||
assert.equal(names[1][1], 100)
|
||||
|
||||
assert.equal(names[2][0], 3)
|
||||
assert.equal(names[2][1], 100)
|
||||
done()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
suite.test('JS array parameter', function (done) {
|
||||
client.query(
|
||||
'SELECT $1::integer[] as names',
|
||||
[
|
||||
[
|
||||
[1, 100],
|
||||
[2, 100],
|
||||
[3, 100],
|
||||
],
|
||||
],
|
||||
assert.success(function (result) {
|
||||
var names = result.rows[0].names
|
||||
assert.lengthIs(names, 3)
|
||||
assert.equal(names[0][0], 1)
|
||||
assert.equal(names[0][1], 100)
|
||||
|
||||
assert.equal(names[1][0], 2)
|
||||
assert.equal(names[1][1], 100)
|
||||
|
||||
assert.equal(names[2][0], 3)
|
||||
assert.equal(names[2][1], 100)
|
||||
release()
|
||||
pool.end(() => {
|
||||
done()
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -25,7 +25,7 @@ suite.test('default values are used in new clients', function () {
|
||||
ssl: false,
|
||||
application_name: undefined,
|
||||
fallback_application_name: undefined,
|
||||
parseInputDatesAsUTC: false
|
||||
parseInputDatesAsUTC: false,
|
||||
})
|
||||
|
||||
var client = new pg.Client()
|
||||
@ -33,7 +33,7 @@ suite.test('default values are used in new clients', function () {
|
||||
user: process.env.USER,
|
||||
database: process.env.USER,
|
||||
password: null,
|
||||
port: 5432
|
||||
port: 5432,
|
||||
})
|
||||
})
|
||||
|
||||
@ -50,7 +50,7 @@ suite.test('modified values are passed to created clients', function () {
|
||||
password: 'zap',
|
||||
database: 'pow',
|
||||
port: 1234,
|
||||
host: 'blam'
|
||||
host: 'blam',
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -10,26 +10,26 @@ const options = {
|
||||
port: 54321,
|
||||
connectionTimeoutMillis: 2000,
|
||||
user: 'not',
|
||||
database: 'existing'
|
||||
database: 'existing',
|
||||
}
|
||||
|
||||
const serverWithConnectionTimeout = (timeout, callback) => {
|
||||
const sockets = new Set()
|
||||
|
||||
const server = net.createServer(socket => {
|
||||
const server = net.createServer((socket) => {
|
||||
sockets.add(socket)
|
||||
socket.once('end', () => sockets.delete(socket))
|
||||
|
||||
socket.on('data', data => {
|
||||
socket.on('data', (data) => {
|
||||
// deny request for SSL
|
||||
if (data.length === 8) {
|
||||
socket.write(Buffer.from('N', 'utf8'))
|
||||
// consider all authentication requests as good
|
||||
// consider all authentication requests as good
|
||||
} else if (!data[0]) {
|
||||
socket.write(buffers.authenticationOk())
|
||||
// send ReadyForQuery `timeout` ms after authentication
|
||||
setTimeout(() => socket.write(buffers.readyForQuery()), timeout).unref()
|
||||
// respond with our canned response
|
||||
// respond with our canned response
|
||||
} else {
|
||||
socket.write(buffers.readyForQuery())
|
||||
}
|
||||
@ -37,7 +37,7 @@ const serverWithConnectionTimeout = (timeout, callback) => {
|
||||
})
|
||||
|
||||
let closing = false
|
||||
const closeServer = done => {
|
||||
const closeServer = (done) => {
|
||||
if (closing) return
|
||||
closing = true
|
||||
|
||||
@ -50,32 +50,34 @@ const serverWithConnectionTimeout = (timeout, callback) => {
|
||||
server.listen(options.port, options.host, () => callback(closeServer))
|
||||
}
|
||||
|
||||
suite.test('successful connection', done => {
|
||||
serverWithConnectionTimeout(0, closeServer => {
|
||||
suite.test('successful connection', (done) => {
|
||||
serverWithConnectionTimeout(0, (closeServer) => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
throw new Error('Client should have connected successfully but it did not.')
|
||||
}, 3000)
|
||||
|
||||
const client = new helper.Client(options)
|
||||
client.connect()
|
||||
client
|
||||
.connect()
|
||||
.then(() => client.end())
|
||||
.then(() => closeServer(done))
|
||||
.catch(err => closeServer(() => done(err)))
|
||||
.catch((err) => closeServer(() => done(err)))
|
||||
.then(() => clearTimeout(timeoutId))
|
||||
})
|
||||
})
|
||||
|
||||
suite.test('expired connection timeout', done => {
|
||||
serverWithConnectionTimeout(options.connectionTimeoutMillis * 2, closeServer => {
|
||||
suite.test('expired connection timeout', (done) => {
|
||||
serverWithConnectionTimeout(options.connectionTimeoutMillis * 2, (closeServer) => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
throw new Error('Client should have emitted an error but it did not.')
|
||||
}, 3000)
|
||||
|
||||
const client = new helper.Client(options)
|
||||
client.connect()
|
||||
client
|
||||
.connect()
|
||||
.then(() => client.end())
|
||||
.then(() => closeServer(() => done(new Error('Connection timeout should have expired but it did not.'))))
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
assert(err instanceof Error)
|
||||
assert(/timeout expired\s*/.test(err.message))
|
||||
closeServer(done)
|
||||
|
||||
@ -4,19 +4,21 @@ const Client = helper.pg.Client
|
||||
const suite = new helper.Suite()
|
||||
|
||||
const customTypes = {
|
||||
getTypeParser: () => () => 'okay!'
|
||||
getTypeParser: () => () => 'okay!',
|
||||
}
|
||||
|
||||
suite.test('custom type parser in client config', (done) => {
|
||||
const client = new Client({ types: customTypes })
|
||||
|
||||
client.connect()
|
||||
.then(() => {
|
||||
client.query('SELECT NOW() as val', assert.success(function (res) {
|
||||
client.connect().then(() => {
|
||||
client.query(
|
||||
'SELECT NOW() as val',
|
||||
assert.success(function (res) {
|
||||
assert.equal(res.rows[0].val, 'okay!')
|
||||
client.end().then(done)
|
||||
}))
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
// Custom type-parsers per query are not supported in native
|
||||
@ -24,15 +26,17 @@ if (!helper.args.native) {
|
||||
suite.test('custom type parser in query', (done) => {
|
||||
const client = new Client()
|
||||
|
||||
client.connect()
|
||||
.then(() => {
|
||||
client.query({
|
||||
client.connect().then(() => {
|
||||
client.query(
|
||||
{
|
||||
text: 'SELECT NOW() as val',
|
||||
types: customTypes
|
||||
}, assert.success(function (res) {
|
||||
types: customTypes,
|
||||
},
|
||||
assert.success(function (res) {
|
||||
assert.equal(res.rows[0].val, 'okay!')
|
||||
client.end().then(done)
|
||||
}))
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ suite.test('empty query message handling', function (done) {
|
||||
assert.emits(client, 'drain', function () {
|
||||
client.end(done)
|
||||
})
|
||||
client.query({text: ''})
|
||||
client.query({ text: '' })
|
||||
})
|
||||
|
||||
suite.test('callback supported', function (done) {
|
||||
|
||||
@ -33,7 +33,7 @@ suite.test('sending non-array argument as values causes an error callback', (don
|
||||
suite.test('re-using connections results in error callback', (done) => {
|
||||
const client = new Client()
|
||||
client.connect(() => {
|
||||
client.connect(err => {
|
||||
client.connect((err) => {
|
||||
assert(err instanceof Error)
|
||||
client.end(done)
|
||||
})
|
||||
@ -43,7 +43,7 @@ suite.test('re-using connections results in error callback', (done) => {
|
||||
suite.test('re-using connections results in promise rejection', (done) => {
|
||||
const client = new Client()
|
||||
client.connect().then(() => {
|
||||
client.connect().catch(err => {
|
||||
client.connect().catch((err) => {
|
||||
assert(err instanceof Error)
|
||||
client.end().then(done)
|
||||
})
|
||||
@ -53,33 +53,43 @@ suite.test('re-using connections results in promise rejection', (done) => {
|
||||
suite.test('using a client after closing it results in error', (done) => {
|
||||
const client = new Client()
|
||||
client.connect(() => {
|
||||
client.end(assert.calls(() => {
|
||||
client.query('SELECT 1', assert.calls((err) => {
|
||||
assert.equal(err.message, 'Client was closed and is not queryable')
|
||||
done()
|
||||
}))
|
||||
}))
|
||||
client.end(
|
||||
assert.calls(() => {
|
||||
client.query(
|
||||
'SELECT 1',
|
||||
assert.calls((err) => {
|
||||
assert.equal(err.message, 'Client was closed and is not queryable')
|
||||
done()
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
suite.test('query receives error on client shutdown', function (done) {
|
||||
var client = new Client()
|
||||
client.connect(assert.success(function () {
|
||||
const config = {
|
||||
text: 'select pg_sleep(5)',
|
||||
name: 'foobar'
|
||||
}
|
||||
let queryError
|
||||
client.query(new pg.Query(config), assert.calls(function (err, res) {
|
||||
assert(err instanceof Error)
|
||||
queryError = err
|
||||
}))
|
||||
setTimeout(() => client.end(), 50)
|
||||
client.once('end', () => {
|
||||
assert(queryError instanceof Error)
|
||||
done()
|
||||
client.connect(
|
||||
assert.success(function () {
|
||||
const config = {
|
||||
text: 'select pg_sleep(5)',
|
||||
name: 'foobar',
|
||||
}
|
||||
let queryError
|
||||
client.query(
|
||||
new pg.Query(config),
|
||||
assert.calls(function (err, res) {
|
||||
assert(err instanceof Error)
|
||||
queryError = err
|
||||
})
|
||||
)
|
||||
setTimeout(() => client.end(), 50)
|
||||
client.once('end', () => {
|
||||
assert(queryError instanceof Error)
|
||||
done()
|
||||
})
|
||||
})
|
||||
}))
|
||||
)
|
||||
})
|
||||
|
||||
var ensureFuture = function (testClient, done) {
|
||||
@ -95,11 +105,13 @@ suite.test('when query is parsing', (done) => {
|
||||
|
||||
var q = client.query({ text: 'CREATE TEMP TABLE boom(age integer); INSERT INTO boom (age) VALUES (28);' })
|
||||
|
||||
// this query wont parse since there isn't a table named bang
|
||||
var query = client.query(new pg.Query({
|
||||
text: 'select * from bang where name = $1',
|
||||
values: ['0']
|
||||
}))
|
||||
// this query wont parse since there isn't a table named bang
|
||||
var query = client.query(
|
||||
new pg.Query({
|
||||
text: 'select * from bang where name = $1',
|
||||
values: ['0'],
|
||||
})
|
||||
)
|
||||
|
||||
assert.emits(query, 'error', function (err) {
|
||||
ensureFuture(client, done)
|
||||
@ -111,10 +123,12 @@ suite.test('when a query is binding', function (done) {
|
||||
|
||||
var q = client.query({ text: 'CREATE TEMP TABLE boom(age integer); INSERT INTO boom (age) VALUES (28);' })
|
||||
|
||||
var query = client.query(new pg.Query({
|
||||
text: 'select * from boom where age = $1',
|
||||
values: ['asldkfjasdf']
|
||||
}))
|
||||
var query = client.query(
|
||||
new pg.Query({
|
||||
text: 'select * from boom where age = $1',
|
||||
values: ['asldkfjasdf'],
|
||||
})
|
||||
)
|
||||
|
||||
assert.emits(query, 'error', function (err) {
|
||||
assert.equal(err.severity, 'ERROR')
|
||||
@ -124,12 +138,14 @@ suite.test('when a query is binding', function (done) {
|
||||
|
||||
suite.test('non-query error with callback', function (done) {
|
||||
var client = new Client({
|
||||
user: 'asldkfjsadlfkj'
|
||||
user: 'asldkfjsadlfkj',
|
||||
})
|
||||
client.connect(assert.calls(function (error, client) {
|
||||
assert(error instanceof Error)
|
||||
done()
|
||||
}))
|
||||
client.connect(
|
||||
assert.calls(function (error, client) {
|
||||
assert(error instanceof Error)
|
||||
done()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
suite.test('non-error calls supplied callback', function (done) {
|
||||
@ -138,18 +154,20 @@ suite.test('non-error calls supplied callback', function (done) {
|
||||
password: helper.args.password,
|
||||
host: helper.args.host,
|
||||
port: helper.args.port,
|
||||
database: helper.args.database
|
||||
database: helper.args.database,
|
||||
})
|
||||
|
||||
client.connect(assert.calls(function (err) {
|
||||
assert.ifError(err)
|
||||
client.end(done)
|
||||
}))
|
||||
client.connect(
|
||||
assert.calls(function (err) {
|
||||
assert.ifError(err)
|
||||
client.end(done)
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
suite.test('when connecting to an invalid host with callback', function (done) {
|
||||
var client = new Client({
|
||||
user: 'very invalid username'
|
||||
user: 'very invalid username',
|
||||
})
|
||||
client.on('error', () => {
|
||||
assert.fail('unexpected error event when connecting')
|
||||
@ -162,7 +180,7 @@ suite.test('when connecting to an invalid host with callback', function (done) {
|
||||
|
||||
suite.test('when connecting to invalid host with promise', function (done) {
|
||||
var client = new Client({
|
||||
user: 'very invalid username'
|
||||
user: 'very invalid username',
|
||||
})
|
||||
client.on('error', () => {
|
||||
assert.fail('unexpected error event when connecting')
|
||||
@ -172,13 +190,12 @@ suite.test('when connecting to invalid host with promise', function (done) {
|
||||
|
||||
suite.test('non-query error', function (done) {
|
||||
var client = new Client({
|
||||
user: 'asldkfjsadlfkj'
|
||||
user: 'asldkfjsadlfkj',
|
||||
})
|
||||
client.connect().catch((e) => {
|
||||
assert(e instanceof Error)
|
||||
done()
|
||||
})
|
||||
client.connect()
|
||||
.catch(e => {
|
||||
assert(e instanceof Error)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
suite.test('within a simple query', (done) => {
|
||||
@ -199,7 +216,7 @@ suite.test('connected, idle client error', (done) => {
|
||||
throw new Error('Should not receive error callback after connection')
|
||||
}
|
||||
setImmediate(() => {
|
||||
(client.connection || client.native).emit('error', new Error('expected'))
|
||||
;(client.connection || client.native).emit('error', new Error('expected'))
|
||||
})
|
||||
})
|
||||
client.on('error', (err) => {
|
||||
@ -211,9 +228,9 @@ suite.test('connected, idle client error', (done) => {
|
||||
suite.test('cannot pass non-string values to query as text', (done) => {
|
||||
const client = new Client()
|
||||
client.connect()
|
||||
client.query({ text: { } }, (err) => {
|
||||
client.query({ text: {} }, (err) => {
|
||||
assert(err)
|
||||
client.query({ }, (err) => {
|
||||
client.query({}, (err) => {
|
||||
client.on('drain', () => {
|
||||
client.end(done)
|
||||
})
|
||||
|
||||
@ -2,21 +2,26 @@
|
||||
var helper = require('./test-helper')
|
||||
const pool = new helper.pg.Pool()
|
||||
|
||||
pool.connect(assert.success(function (client, done) {
|
||||
var types = require('pg-types')
|
||||
// 1231 = numericOID
|
||||
types.setTypeParser(1700, function () {
|
||||
return 'yes'
|
||||
pool.connect(
|
||||
assert.success(function (client, done) {
|
||||
var types = require('pg-types')
|
||||
// 1231 = numericOID
|
||||
types.setTypeParser(1700, function () {
|
||||
return 'yes'
|
||||
})
|
||||
types.setTypeParser(1700, 'binary', function () {
|
||||
return 'yes'
|
||||
})
|
||||
var bignum = '294733346389144765940638005275322203805'
|
||||
client.query('CREATE TEMP TABLE bignumz(id numeric)')
|
||||
client.query('INSERT INTO bignumz(id) VALUES ($1)', [bignum])
|
||||
client.query(
|
||||
'SELECT * FROM bignumz',
|
||||
assert.success(function (result) {
|
||||
assert.equal(result.rows[0].id, 'yes')
|
||||
done()
|
||||
pool.end()
|
||||
})
|
||||
)
|
||||
})
|
||||
types.setTypeParser(1700, 'binary', function () {
|
||||
return 'yes'
|
||||
})
|
||||
var bignum = '294733346389144765940638005275322203805'
|
||||
client.query('CREATE TEMP TABLE bignumz(id numeric)')
|
||||
client.query('INSERT INTO bignumz(id) VALUES ($1)', [bignum])
|
||||
client.query('SELECT * FROM bignumz', assert.success(function (result) {
|
||||
assert.equal(result.rows[0].id, 'yes')
|
||||
done()
|
||||
pool.end()
|
||||
}))
|
||||
}))
|
||||
)
|
||||
|
||||
@ -6,38 +6,54 @@ var suite = new helper.Suite()
|
||||
|
||||
var conInfo = helper.config
|
||||
|
||||
function getConInfo (override) {
|
||||
return Object.assign({}, conInfo, override )
|
||||
function getConInfo(override) {
|
||||
return Object.assign({}, conInfo, override)
|
||||
}
|
||||
|
||||
function testClientVersion(cb) {
|
||||
var client = new Client({})
|
||||
client.connect(assert.success(function () {
|
||||
helper.versionGTE(client, 100000, assert.success(function(isGreater) {
|
||||
return client.end(assert.success(function () {
|
||||
if (!isGreater) {
|
||||
console.log('skip idle_in_transaction_session_timeout at client-level is only available in v10 and above');
|
||||
return;
|
||||
}
|
||||
cb();
|
||||
}))
|
||||
}))
|
||||
}))
|
||||
client.connect(
|
||||
assert.success(function () {
|
||||
helper.versionGTE(
|
||||
client,
|
||||
100000,
|
||||
assert.success(function (isGreater) {
|
||||
return client.end(
|
||||
assert.success(function () {
|
||||
if (!isGreater) {
|
||||
console.log(
|
||||
'skip idle_in_transaction_session_timeout at client-level is only available in v10 and above'
|
||||
)
|
||||
return
|
||||
}
|
||||
cb()
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
function getIdleTransactionSessionTimeout (conf, cb) {
|
||||
function getIdleTransactionSessionTimeout(conf, cb) {
|
||||
var client = new Client(conf)
|
||||
client.connect(assert.success(function () {
|
||||
client.query('SHOW idle_in_transaction_session_timeout', assert.success(function (res) {
|
||||
var timeout = res.rows[0].idle_in_transaction_session_timeout
|
||||
cb(timeout)
|
||||
client.end()
|
||||
}))
|
||||
}))
|
||||
client.connect(
|
||||
assert.success(function () {
|
||||
client.query(
|
||||
'SHOW idle_in_transaction_session_timeout',
|
||||
assert.success(function (res) {
|
||||
var timeout = res.rows[0].idle_in_transaction_session_timeout
|
||||
cb(timeout)
|
||||
client.end()
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (!helper.args.native) { // idle_in_transaction_session_timeout is not supported with the native client
|
||||
testClientVersion(function(){
|
||||
if (!helper.args.native) {
|
||||
// idle_in_transaction_session_timeout is not supported with the native client
|
||||
testClientVersion(function () {
|
||||
suite.test('No default idle_in_transaction_session_timeout ', function (done) {
|
||||
getConInfo()
|
||||
getIdleTransactionSessionTimeout({}, function (res) {
|
||||
@ -48,7 +64,7 @@ if (!helper.args.native) { // idle_in_transaction_session_timeout is not support
|
||||
|
||||
suite.test('idle_in_transaction_session_timeout integer is used', function (done) {
|
||||
var conf = getConInfo({
|
||||
'idle_in_transaction_session_timeout': 3000
|
||||
idle_in_transaction_session_timeout: 3000,
|
||||
})
|
||||
getIdleTransactionSessionTimeout(conf, function (res) {
|
||||
assert.strictEqual(res, '3s')
|
||||
@ -58,7 +74,7 @@ if (!helper.args.native) { // idle_in_transaction_session_timeout is not support
|
||||
|
||||
suite.test('idle_in_transaction_session_timeout float is used', function (done) {
|
||||
var conf = getConInfo({
|
||||
'idle_in_transaction_session_timeout': 3000.7
|
||||
idle_in_transaction_session_timeout: 3000.7,
|
||||
})
|
||||
getIdleTransactionSessionTimeout(conf, function (res) {
|
||||
assert.strictEqual(res, '3s')
|
||||
@ -68,7 +84,7 @@ if (!helper.args.native) { // idle_in_transaction_session_timeout is not support
|
||||
|
||||
suite.test('idle_in_transaction_session_timeout string is used', function (done) {
|
||||
var conf = getConInfo({
|
||||
'idle_in_transaction_session_timeout': '3000'
|
||||
idle_in_transaction_session_timeout: '3000',
|
||||
})
|
||||
getIdleTransactionSessionTimeout(conf, function (res) {
|
||||
assert.strictEqual(res, '3s')
|
||||
|
||||
@ -3,26 +3,35 @@ var helper = require('./test-helper')
|
||||
var assert = require('assert')
|
||||
|
||||
const pool = new helper.pg.Pool()
|
||||
pool.connect(assert.success(function (client, done) {
|
||||
helper.versionGTE(client, 90200, assert.success(function (jsonSupported) {
|
||||
if (!jsonSupported) {
|
||||
console.log('skip json test on older versions of postgres')
|
||||
done()
|
||||
return pool.end()
|
||||
}
|
||||
client.query('CREATE TEMP TABLE stuff(id SERIAL PRIMARY KEY, data JSON)')
|
||||
var value = { name: 'Brian', age: 250, alive: true, now: new Date() }
|
||||
client.query('INSERT INTO stuff (data) VALUES ($1)', [value])
|
||||
client.query('SELECT * FROM stuff', assert.success(function (result) {
|
||||
assert.equal(result.rows.length, 1)
|
||||
assert.equal(typeof result.rows[0].data, 'object')
|
||||
var row = result.rows[0].data
|
||||
assert.strictEqual(row.name, value.name)
|
||||
assert.strictEqual(row.age, value.age)
|
||||
assert.strictEqual(row.alive, value.alive)
|
||||
assert.equal(JSON.stringify(row.now), JSON.stringify(value.now))
|
||||
done()
|
||||
pool.end()
|
||||
}))
|
||||
}))
|
||||
}))
|
||||
pool.connect(
|
||||
assert.success(function (client, done) {
|
||||
helper.versionGTE(
|
||||
client,
|
||||
90200,
|
||||
assert.success(function (jsonSupported) {
|
||||
if (!jsonSupported) {
|
||||
console.log('skip json test on older versions of postgres')
|
||||
done()
|
||||
return pool.end()
|
||||
}
|
||||
client.query('CREATE TEMP TABLE stuff(id SERIAL PRIMARY KEY, data JSON)')
|
||||
var value = { name: 'Brian', age: 250, alive: true, now: new Date() }
|
||||
client.query('INSERT INTO stuff (data) VALUES ($1)', [value])
|
||||
client.query(
|
||||
'SELECT * FROM stuff',
|
||||
assert.success(function (result) {
|
||||
assert.equal(result.rows.length, 1)
|
||||
assert.equal(typeof result.rows[0].data, 'object')
|
||||
var row = result.rows[0].data
|
||||
assert.strictEqual(row.name, value.name)
|
||||
assert.strictEqual(row.age, value.age)
|
||||
assert.strictEqual(row.alive, value.alive)
|
||||
assert.equal(JSON.stringify(row.now), JSON.stringify(value.now))
|
||||
done()
|
||||
pool.end()
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
@ -6,64 +6,73 @@ const helper = require('./test-helper')
|
||||
|
||||
const suite = new helper.Suite('multiple result sets')
|
||||
|
||||
suite.test('two select results work', co.wrap(function * () {
|
||||
const client = new helper.Client()
|
||||
yield client.connect()
|
||||
suite.test(
|
||||
'two select results work',
|
||||
co.wrap(function* () {
|
||||
const client = new helper.Client()
|
||||
yield client.connect()
|
||||
|
||||
const results = yield client.query(`SELECT 'foo'::text as name; SELECT 'bar'::text as baz`)
|
||||
assert(Array.isArray(results))
|
||||
const results = yield client.query(`SELECT 'foo'::text as name; SELECT 'bar'::text as baz`)
|
||||
assert(Array.isArray(results))
|
||||
|
||||
assert.equal(results[0].fields[0].name, 'name')
|
||||
assert.deepEqual(results[0].rows, [{ name: 'foo' }])
|
||||
assert.equal(results[0].fields[0].name, 'name')
|
||||
assert.deepEqual(results[0].rows, [{ name: 'foo' }])
|
||||
|
||||
assert.equal(results[1].fields[0].name, 'baz')
|
||||
assert.deepEqual(results[1].rows, [{ baz: 'bar' }])
|
||||
assert.equal(results[1].fields[0].name, 'baz')
|
||||
assert.deepEqual(results[1].rows, [{ baz: 'bar' }])
|
||||
|
||||
return client.end()
|
||||
}))
|
||||
return client.end()
|
||||
})
|
||||
)
|
||||
|
||||
suite.test('multiple selects work', co.wrap(function * () {
|
||||
const client = new helper.Client()
|
||||
yield client.connect()
|
||||
suite.test(
|
||||
'multiple selects work',
|
||||
co.wrap(function* () {
|
||||
const client = new helper.Client()
|
||||
yield client.connect()
|
||||
|
||||
const text = `
|
||||
const text = `
|
||||
SELECT * FROM generate_series(2, 4) as foo;
|
||||
SELECT * FROM generate_series(8, 10) as bar;
|
||||
SELECT * FROM generate_series(20, 22) as baz;
|
||||
`
|
||||
|
||||
const results = yield client.query(text)
|
||||
assert(Array.isArray(results))
|
||||
const results = yield client.query(text)
|
||||
assert(Array.isArray(results))
|
||||
|
||||
assert.equal(results[0].fields[0].name, 'foo')
|
||||
assert.deepEqual(results[0].rows, [{ foo: 2 }, { foo: 3 }, { foo: 4 }])
|
||||
assert.equal(results[0].fields[0].name, 'foo')
|
||||
assert.deepEqual(results[0].rows, [{ foo: 2 }, { foo: 3 }, { foo: 4 }])
|
||||
|
||||
assert.equal(results[1].fields[0].name, 'bar')
|
||||
assert.deepEqual(results[1].rows, [{ bar: 8 }, { bar: 9 }, { bar: 10 }])
|
||||
assert.equal(results[1].fields[0].name, 'bar')
|
||||
assert.deepEqual(results[1].rows, [{ bar: 8 }, { bar: 9 }, { bar: 10 }])
|
||||
|
||||
assert.equal(results[2].fields[0].name, 'baz')
|
||||
assert.deepEqual(results[2].rows, [{ baz: 20 }, { baz: 21 }, { baz: 22 }])
|
||||
assert.equal(results[2].fields[0].name, 'baz')
|
||||
assert.deepEqual(results[2].rows, [{ baz: 20 }, { baz: 21 }, { baz: 22 }])
|
||||
|
||||
assert.equal(results.length, 3)
|
||||
assert.equal(results.length, 3)
|
||||
|
||||
return client.end()
|
||||
}))
|
||||
return client.end()
|
||||
})
|
||||
)
|
||||
|
||||
suite.test('mixed queries and statements', co.wrap(function * () {
|
||||
const client = new helper.Client()
|
||||
yield client.connect()
|
||||
suite.test(
|
||||
'mixed queries and statements',
|
||||
co.wrap(function* () {
|
||||
const client = new helper.Client()
|
||||
yield client.connect()
|
||||
|
||||
const text = `
|
||||
const text = `
|
||||
CREATE TEMP TABLE weather(type text);
|
||||
INSERT INTO weather(type) VALUES ('rain');
|
||||
SELECT * FROM weather;
|
||||
`
|
||||
|
||||
const results = yield client.query(text)
|
||||
assert(Array.isArray(results))
|
||||
assert.equal(results[0].command, 'CREATE')
|
||||
assert.equal(results[1].command, 'INSERT')
|
||||
assert.equal(results[2].command, 'SELECT')
|
||||
const results = yield client.query(text)
|
||||
assert(Array.isArray(results))
|
||||
assert.equal(results[0].command, 'CREATE')
|
||||
assert.equal(results[1].command, 'INSERT')
|
||||
assert.equal(results[2].command, 'SELECT')
|
||||
|
||||
return client.end()
|
||||
}))
|
||||
return client.end()
|
||||
})
|
||||
)
|
||||
|
||||
@ -16,29 +16,34 @@ Server.prototype.start = function (cb) {
|
||||
// it responds with our specified response immediatley after receiving every buffer
|
||||
// this is sufficient into convincing the client its connectet to a valid backend
|
||||
// if we respond with a readyForQuery message
|
||||
this.server = net.createServer(function (socket) {
|
||||
this.socket = socket
|
||||
if (this.response) {
|
||||
this.socket.on('data', function (data) {
|
||||
// deny request for SSL
|
||||
if (data.length == 8) {
|
||||
this.socket.write(Buffer.from('N', 'utf8'))
|
||||
// consider all authentication requests as good
|
||||
} else if (!data[0]) {
|
||||
this.socket.write(buffers.authenticationOk())
|
||||
// respond with our canned response
|
||||
} else {
|
||||
this.socket.write(this.response)
|
||||
}
|
||||
}.bind(this))
|
||||
}
|
||||
}.bind(this))
|
||||
this.server = net.createServer(
|
||||
function (socket) {
|
||||
this.socket = socket
|
||||
if (this.response) {
|
||||
this.socket.on(
|
||||
'data',
|
||||
function (data) {
|
||||
// deny request for SSL
|
||||
if (data.length == 8) {
|
||||
this.socket.write(Buffer.from('N', 'utf8'))
|
||||
// consider all authentication requests as good
|
||||
} else if (!data[0]) {
|
||||
this.socket.write(buffers.authenticationOk())
|
||||
// respond with our canned response
|
||||
} else {
|
||||
this.socket.write(this.response)
|
||||
}
|
||||
}.bind(this)
|
||||
)
|
||||
}
|
||||
}.bind(this)
|
||||
)
|
||||
|
||||
var port = 54321
|
||||
|
||||
var options = {
|
||||
host: 'localhost',
|
||||
port: port
|
||||
port: port,
|
||||
}
|
||||
this.server.listen(options.port, options.host, function () {
|
||||
cb(options)
|
||||
@ -58,12 +63,11 @@ var testServer = function (server, cb) {
|
||||
server.start(function (options) {
|
||||
// connect a client to it
|
||||
var client = new helper.Client(options)
|
||||
client.connect()
|
||||
.catch((err) => {
|
||||
assert(err instanceof Error)
|
||||
clearTimeout(timeoutId)
|
||||
server.close(cb)
|
||||
})
|
||||
client.connect().catch((err) => {
|
||||
assert(err instanceof Error)
|
||||
clearTimeout(timeoutId)
|
||||
server.close(cb)
|
||||
})
|
||||
|
||||
server.server.on('connection', () => {
|
||||
// after 50 milliseconds, drop the client
|
||||
|
||||
@ -7,33 +7,39 @@ suite.test('noData message handling', function () {
|
||||
|
||||
var q = client.query({
|
||||
name: 'boom',
|
||||
text: 'create temp table boom(id serial, size integer)'
|
||||
text: 'create temp table boom(id serial, size integer)',
|
||||
})
|
||||
|
||||
client.query({
|
||||
name: 'insert',
|
||||
text: 'insert into boom(size) values($1)',
|
||||
values: [100]
|
||||
}, function (err, result) {
|
||||
if (err) {
|
||||
console.log(err)
|
||||
throw err
|
||||
client.query(
|
||||
{
|
||||
name: 'insert',
|
||||
text: 'insert into boom(size) values($1)',
|
||||
values: [100],
|
||||
},
|
||||
function (err, result) {
|
||||
if (err) {
|
||||
console.log(err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
client.query({
|
||||
name: 'insert',
|
||||
values: [101]
|
||||
values: [101],
|
||||
})
|
||||
|
||||
var query = client.query({
|
||||
name: 'fetch',
|
||||
text: 'select size from boom where size < $1',
|
||||
values: [101]
|
||||
}, (err, res) => {
|
||||
var row = res.rows[0]
|
||||
assert.strictEqual(row.size, 100)
|
||||
})
|
||||
var query = client.query(
|
||||
{
|
||||
name: 'fetch',
|
||||
text: 'select size from boom where size < $1',
|
||||
values: [101],
|
||||
},
|
||||
(err, res) => {
|
||||
var row = res.rows[0]
|
||||
assert.strictEqual(row.size, 100)
|
||||
}
|
||||
)
|
||||
|
||||
client.on('drain', client.end.bind(client))
|
||||
})
|
||||
|
||||
@ -15,11 +15,13 @@ suite.test('can access results when no rows are returned', function (done) {
|
||||
pool.connect(
|
||||
assert.success(function (client, release) {
|
||||
const q = new pg.Query('select $1::text as val limit 0', ['hi'])
|
||||
var query = client.query(q, assert.success(function (result) {
|
||||
checkResult(result)
|
||||
release()
|
||||
pool.end(done)
|
||||
})
|
||||
var query = client.query(
|
||||
q,
|
||||
assert.success(function (result) {
|
||||
checkResult(result)
|
||||
release()
|
||||
pool.end(done)
|
||||
})
|
||||
)
|
||||
|
||||
assert.emits(query, 'end', checkResult)
|
||||
|
||||
@ -5,31 +5,40 @@ const suite = new helper.Suite()
|
||||
|
||||
suite.test('emits notify message', function (done) {
|
||||
const client = helper.client()
|
||||
client.query('LISTEN boom', assert.calls(function () {
|
||||
const otherClient = helper.client()
|
||||
let bothEmitted = -1
|
||||
otherClient.query('LISTEN boom', assert.calls(function () {
|
||||
assert.emits(client, 'notification', function (msg) {
|
||||
// make sure PQfreemem doesn't invalidate string pointers
|
||||
setTimeout(function () {
|
||||
assert.equal(msg.channel, 'boom')
|
||||
assert.ok(msg.payload == 'omg!' /* 9.x */ || msg.payload == '' /* 8.x */, 'expected blank payload or correct payload but got ' + msg.message)
|
||||
client.end(++bothEmitted ? done : undefined)
|
||||
}, 100)
|
||||
})
|
||||
assert.emits(otherClient, 'notification', function (msg) {
|
||||
assert.equal(msg.channel, 'boom')
|
||||
otherClient.end(++bothEmitted ? done : undefined)
|
||||
})
|
||||
client.query(
|
||||
'LISTEN boom',
|
||||
assert.calls(function () {
|
||||
const otherClient = helper.client()
|
||||
let bothEmitted = -1
|
||||
otherClient.query(
|
||||
'LISTEN boom',
|
||||
assert.calls(function () {
|
||||
assert.emits(client, 'notification', function (msg) {
|
||||
// make sure PQfreemem doesn't invalidate string pointers
|
||||
setTimeout(function () {
|
||||
assert.equal(msg.channel, 'boom')
|
||||
assert.ok(
|
||||
msg.payload == 'omg!' /* 9.x */ || msg.payload == '' /* 8.x */,
|
||||
'expected blank payload or correct payload but got ' + msg.message
|
||||
)
|
||||
client.end(++bothEmitted ? done : undefined)
|
||||
}, 100)
|
||||
})
|
||||
assert.emits(otherClient, 'notification', function (msg) {
|
||||
assert.equal(msg.channel, 'boom')
|
||||
otherClient.end(++bothEmitted ? done : undefined)
|
||||
})
|
||||
|
||||
client.query("NOTIFY boom, 'omg!'", function (err, q) {
|
||||
if (err) {
|
||||
// notify not supported with payload on 8.x
|
||||
client.query('NOTIFY boom')
|
||||
}
|
||||
})
|
||||
}))
|
||||
}))
|
||||
client.query("NOTIFY boom, 'omg!'", function (err, q) {
|
||||
if (err) {
|
||||
// notify not supported with payload on 8.x
|
||||
client.query('NOTIFY boom')
|
||||
}
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
// this test fails on travis due to their config
|
||||
|
||||
@ -7,23 +7,31 @@ const suite = new helper.Suite()
|
||||
const pool = new pg.Pool(helper.config)
|
||||
suite.test('ability to turn on and off parser', function () {
|
||||
if (helper.args.binary) return false
|
||||
pool.connect(assert.success(function (client, done) {
|
||||
pg.defaults.parseInt8 = true
|
||||
client.query('CREATE TEMP TABLE asdf(id SERIAL PRIMARY KEY)')
|
||||
client.query('SELECT COUNT(*) as "count", \'{1,2,3}\'::bigint[] as array FROM asdf', assert.success(function (res) {
|
||||
assert.strictEqual(0, res.rows[0].count)
|
||||
assert.strictEqual(1, res.rows[0].array[0])
|
||||
assert.strictEqual(2, res.rows[0].array[1])
|
||||
assert.strictEqual(3, res.rows[0].array[2])
|
||||
pg.defaults.parseInt8 = false
|
||||
client.query('SELECT COUNT(*) as "count", \'{1,2,3}\'::bigint[] as array FROM asdf', assert.success(function (res) {
|
||||
done()
|
||||
assert.strictEqual('0', res.rows[0].count)
|
||||
assert.strictEqual('1', res.rows[0].array[0])
|
||||
assert.strictEqual('2', res.rows[0].array[1])
|
||||
assert.strictEqual('3', res.rows[0].array[2])
|
||||
pool.end()
|
||||
}))
|
||||
}))
|
||||
}))
|
||||
pool.connect(
|
||||
assert.success(function (client, done) {
|
||||
pg.defaults.parseInt8 = true
|
||||
client.query('CREATE TEMP TABLE asdf(id SERIAL PRIMARY KEY)')
|
||||
client.query(
|
||||
'SELECT COUNT(*) as "count", \'{1,2,3}\'::bigint[] as array FROM asdf',
|
||||
assert.success(function (res) {
|
||||
assert.strictEqual(0, res.rows[0].count)
|
||||
assert.strictEqual(1, res.rows[0].array[0])
|
||||
assert.strictEqual(2, res.rows[0].array[1])
|
||||
assert.strictEqual(3, res.rows[0].array[2])
|
||||
pg.defaults.parseInt8 = false
|
||||
client.query(
|
||||
'SELECT COUNT(*) as "count", \'{1,2,3}\'::bigint[] as array FROM asdf',
|
||||
assert.success(function (res) {
|
||||
done()
|
||||
assert.strictEqual('0', res.rows[0].count)
|
||||
assert.strictEqual('1', res.rows[0].array[0])
|
||||
assert.strictEqual('2', res.rows[0].array[1])
|
||||
assert.strictEqual('3', res.rows[0].array[2])
|
||||
pool.end()
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@ -12,11 +12,13 @@ var suite = new helper.Suite()
|
||||
var parseCount = 0
|
||||
|
||||
suite.test('first named prepared statement', function (done) {
|
||||
var query = client.query(new Query({
|
||||
text: 'select name from person where age <= $1 and name LIKE $2',
|
||||
values: [20, 'Bri%'],
|
||||
name: queryName
|
||||
}))
|
||||
var query = client.query(
|
||||
new Query({
|
||||
text: 'select name from person where age <= $1 and name LIKE $2',
|
||||
values: [20, 'Bri%'],
|
||||
name: queryName,
|
||||
})
|
||||
)
|
||||
|
||||
assert.emits(query, 'row', function (row) {
|
||||
assert.equal(row.name, 'Brian')
|
||||
@ -26,11 +28,13 @@ var suite = new helper.Suite()
|
||||
})
|
||||
|
||||
suite.test('second named prepared statement with same name & text', function (done) {
|
||||
var cachedQuery = client.query(new Query({
|
||||
text: 'select name from person where age <= $1 and name LIKE $2',
|
||||
name: queryName,
|
||||
values: [10, 'A%']
|
||||
}))
|
||||
var cachedQuery = client.query(
|
||||
new Query({
|
||||
text: 'select name from person where age <= $1 and name LIKE $2',
|
||||
name: queryName,
|
||||
values: [10, 'A%'],
|
||||
})
|
||||
)
|
||||
|
||||
assert.emits(cachedQuery, 'row', function (row) {
|
||||
assert.equal(row.name, 'Aaron')
|
||||
@ -40,10 +44,12 @@ var suite = new helper.Suite()
|
||||
})
|
||||
|
||||
suite.test('with same name, but without query text', function (done) {
|
||||
var q = client.query(new Query({
|
||||
name: queryName,
|
||||
values: [30, '%n%']
|
||||
}))
|
||||
var q = client.query(
|
||||
new Query({
|
||||
name: queryName,
|
||||
values: [30, '%n%'],
|
||||
})
|
||||
)
|
||||
|
||||
assert.emits(q, 'row', function (row) {
|
||||
assert.equal(row.name, 'Aaron')
|
||||
@ -58,17 +64,22 @@ var suite = new helper.Suite()
|
||||
})
|
||||
|
||||
suite.test('with same name, but with different text', function (done) {
|
||||
client.query(new Query({
|
||||
text: 'select name from person where age >= $1 and name LIKE $2',
|
||||
name: queryName,
|
||||
values: [30, '%n%']
|
||||
}), assert.calls(err => {
|
||||
assert.equal(err.message, `Prepared statements must be unique - '${queryName}' was used for a different statement`)
|
||||
done()
|
||||
}))
|
||||
client.query(
|
||||
new Query({
|
||||
text: 'select name from person where age >= $1 and name LIKE $2',
|
||||
name: queryName,
|
||||
values: [30, '%n%'],
|
||||
}),
|
||||
assert.calls((err) => {
|
||||
assert.equal(
|
||||
err.message,
|
||||
`Prepared statements must be unique - '${queryName}' was used for a different statement`
|
||||
)
|
||||
done()
|
||||
})
|
||||
)
|
||||
})
|
||||
})()
|
||||
|
||||
;(function () {
|
||||
var statementName = 'differ'
|
||||
var statement1 = 'select count(*)::int4 as count from person'
|
||||
@ -78,22 +89,27 @@ var suite = new helper.Suite()
|
||||
var client2 = helper.client()
|
||||
|
||||
suite.test('client 1 execution', function (done) {
|
||||
var query = client1.query({
|
||||
name: statementName,
|
||||
text: statement1
|
||||
}, (err, res) => {
|
||||
assert(!err)
|
||||
assert.equal(res.rows[0].count, 26)
|
||||
done()
|
||||
})
|
||||
var query = client1.query(
|
||||
{
|
||||
name: statementName,
|
||||
text: statement1,
|
||||
},
|
||||
(err, res) => {
|
||||
assert(!err)
|
||||
assert.equal(res.rows[0].count, 26)
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
suite.test('client 2 execution', function (done) {
|
||||
var query = client2.query(new Query({
|
||||
name: statementName,
|
||||
text: statement2,
|
||||
values: [11]
|
||||
}))
|
||||
var query = client2.query(
|
||||
new Query({
|
||||
name: statementName,
|
||||
text: statement2,
|
||||
values: [11],
|
||||
})
|
||||
)
|
||||
|
||||
assert.emits(query, 'row', function (row) {
|
||||
assert.equal(row.count, 1)
|
||||
@ -108,7 +124,6 @@ var suite = new helper.Suite()
|
||||
return client1.end().then(() => client2.end())
|
||||
})
|
||||
})()
|
||||
|
||||
;(function () {
|
||||
var client = helper.client()
|
||||
client.query('CREATE TEMP TABLE zoom(name varchar(100));')
|
||||
@ -131,21 +146,31 @@ var suite = new helper.Suite()
|
||||
}
|
||||
|
||||
suite.test('with small row count', function (done) {
|
||||
var query = client.query(new Query({
|
||||
name: 'get names',
|
||||
text: 'SELECT name FROM zoom ORDER BY name COLLATE "C"',
|
||||
rows: 1
|
||||
}, done))
|
||||
var query = client.query(
|
||||
new Query(
|
||||
{
|
||||
name: 'get names',
|
||||
text: 'SELECT name FROM zoom ORDER BY name COLLATE "C"',
|
||||
rows: 1,
|
||||
},
|
||||
done
|
||||
)
|
||||
)
|
||||
|
||||
checkForResults(query)
|
||||
})
|
||||
|
||||
suite.test('with large row count', function (done) {
|
||||
var query = client.query(new Query({
|
||||
name: 'get names',
|
||||
text: 'SELECT name FROM zoom ORDER BY name COLLATE "C"',
|
||||
rows: 1000
|
||||
}, done))
|
||||
var query = client.query(
|
||||
new Query(
|
||||
{
|
||||
name: 'get names',
|
||||
text: 'SELECT name FROM zoom ORDER BY name COLLATE "C"',
|
||||
rows: 1000,
|
||||
},
|
||||
done
|
||||
)
|
||||
)
|
||||
checkForResults(query)
|
||||
})
|
||||
|
||||
|
||||
@ -7,43 +7,37 @@ const suite = new helper.Suite()
|
||||
|
||||
suite.test('valid connection completes promise', () => {
|
||||
const client = new pg.Client()
|
||||
return client.connect()
|
||||
.then(() => {
|
||||
return client.end()
|
||||
.then(() => { })
|
||||
})
|
||||
return client.connect().then(() => {
|
||||
return client.end().then(() => {})
|
||||
})
|
||||
})
|
||||
|
||||
suite.test('valid connection completes promise', () => {
|
||||
const client = new pg.Client()
|
||||
return client.connect()
|
||||
.then(() => {
|
||||
return client.end()
|
||||
.then(() => { })
|
||||
})
|
||||
return client.connect().then(() => {
|
||||
return client.end().then(() => {})
|
||||
})
|
||||
})
|
||||
|
||||
suite.test('invalid connection rejects promise', (done) => {
|
||||
const client = new pg.Client({ host: 'alksdjflaskdfj' })
|
||||
return client.connect()
|
||||
.catch(e => {
|
||||
assert(e instanceof Error)
|
||||
done()
|
||||
})
|
||||
return client.connect().catch((e) => {
|
||||
assert(e instanceof Error)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
suite.test('connected client does not reject promise after connection', (done) => {
|
||||
const client = new pg.Client()
|
||||
return client.connect()
|
||||
.then(() => {
|
||||
setTimeout(() => {
|
||||
client.on('error', (e) => {
|
||||
assert(e instanceof Error)
|
||||
client.end()
|
||||
done()
|
||||
})
|
||||
// manually kill the connection
|
||||
client.emit('error', new Error('something bad happened...but not really'))
|
||||
}, 50)
|
||||
})
|
||||
return client.connect().then(() => {
|
||||
setTimeout(() => {
|
||||
client.on('error', (e) => {
|
||||
assert(e instanceof Error)
|
||||
client.end()
|
||||
done()
|
||||
})
|
||||
// manually kill the connection
|
||||
client.emit('error', new Error('something bad happened...but not really'))
|
||||
}, 50)
|
||||
})
|
||||
})
|
||||
|
||||
@ -13,22 +13,21 @@ const suite = new helper.Suite()
|
||||
suite.test('promise API', (cb) => {
|
||||
const pool = new pg.Pool()
|
||||
pool.connect().then((client) => {
|
||||
client.query('SELECT $1::text as name', ['foo'])
|
||||
client
|
||||
.query('SELECT $1::text as name', ['foo'])
|
||||
.then(function (result) {
|
||||
assert.equal(result.rows[0].name, 'foo')
|
||||
return client
|
||||
})
|
||||
.then(function (client) {
|
||||
client.query('ALKJSDF')
|
||||
.catch(function (e) {
|
||||
assert(e instanceof Error)
|
||||
client.query('SELECT 1 as num')
|
||||
.then(function (result) {
|
||||
assert.equal(result.rows[0].num, 1)
|
||||
client.release()
|
||||
pool.end(cb)
|
||||
})
|
||||
client.query('ALKJSDF').catch(function (e) {
|
||||
assert(e instanceof Error)
|
||||
client.query('SELECT 1 as num').then(function (result) {
|
||||
assert.equal(result.rows[0].num, 1)
|
||||
client.release()
|
||||
pool.end(cb)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -52,4 +51,4 @@ suite.test('promise API with configurable promise type', (cb) => {
|
||||
throw error
|
||||
})
|
||||
})
|
||||
});
|
||||
})
|
||||
|
||||
@ -4,12 +4,17 @@ var pg = helper.pg
|
||||
|
||||
new helper.Suite().test('support for complex column names', function () {
|
||||
const pool = new pg.Pool()
|
||||
pool.connect(assert.success(function (client, done) {
|
||||
client.query("CREATE TEMP TABLE t ( \"complex''column\" TEXT )")
|
||||
client.query('SELECT * FROM t', assert.success(function (res) {
|
||||
done()
|
||||
assert.strictEqual(res.fields[0].name, "complex''column")
|
||||
pool.end()
|
||||
}))
|
||||
}))
|
||||
pool.connect(
|
||||
assert.success(function (client, done) {
|
||||
client.query('CREATE TEMP TABLE t ( "complex\'\'column" TEXT )')
|
||||
client.query(
|
||||
'SELECT * FROM t',
|
||||
assert.success(function (res) {
|
||||
done()
|
||||
assert.strictEqual(res.fields[0].name, "complex''column")
|
||||
pool.end()
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@ -7,57 +7,79 @@ var suite = new helper.Suite()
|
||||
|
||||
suite.test('client end during query execution of prepared statement', function (done) {
|
||||
var client = new Client()
|
||||
client.connect(assert.success(function () {
|
||||
var sleepQuery = 'select pg_sleep($1)'
|
||||
client.connect(
|
||||
assert.success(function () {
|
||||
var sleepQuery = 'select pg_sleep($1)'
|
||||
|
||||
var queryConfig = {
|
||||
name: 'sleep query',
|
||||
text: sleepQuery,
|
||||
values: [5]
|
||||
}
|
||||
var queryConfig = {
|
||||
name: 'sleep query',
|
||||
text: sleepQuery,
|
||||
values: [5],
|
||||
}
|
||||
|
||||
var queryInstance = new Query(queryConfig, assert.calls(function (err, result) {
|
||||
assert.equal(err.message, 'Connection terminated')
|
||||
done()
|
||||
}))
|
||||
var queryInstance = new Query(
|
||||
queryConfig,
|
||||
assert.calls(function (err, result) {
|
||||
assert.equal(err.message, 'Connection terminated')
|
||||
done()
|
||||
})
|
||||
)
|
||||
|
||||
var query1 = client.query(queryInstance)
|
||||
var query1 = client.query(queryInstance)
|
||||
|
||||
query1.on('error', function (err) {
|
||||
assert.fail('Prepared statement should not emit error')
|
||||
query1.on('error', function (err) {
|
||||
assert.fail('Prepared statement should not emit error')
|
||||
})
|
||||
|
||||
query1.on('row', function (row) {
|
||||
assert.fail('Prepared statement should not emit row')
|
||||
})
|
||||
|
||||
query1.on('end', function (err) {
|
||||
assert.fail('Prepared statement when executed should not return before being killed')
|
||||
})
|
||||
|
||||
client.end()
|
||||
})
|
||||
|
||||
query1.on('row', function (row) {
|
||||
assert.fail('Prepared statement should not emit row')
|
||||
})
|
||||
|
||||
query1.on('end', function (err) {
|
||||
assert.fail('Prepared statement when executed should not return before being killed')
|
||||
})
|
||||
|
||||
client.end()
|
||||
}))
|
||||
)
|
||||
})
|
||||
|
||||
function killIdleQuery (targetQuery, cb) {
|
||||
function killIdleQuery(targetQuery, cb) {
|
||||
var client2 = new Client(helper.args)
|
||||
var pidColName = 'procpid'
|
||||
var queryColName = 'current_query'
|
||||
client2.connect(assert.success(function () {
|
||||
helper.versionGTE(client2, 90200, assert.success(function (isGreater) {
|
||||
if (isGreater) {
|
||||
pidColName = 'pid'
|
||||
queryColName = 'query'
|
||||
}
|
||||
var killIdleQuery = 'SELECT ' + pidColName + ', (SELECT pg_terminate_backend(' + pidColName + ')) AS killed FROM pg_stat_activity WHERE ' + queryColName + ' = $1'
|
||||
client2.query(killIdleQuery, [targetQuery], assert.calls(function (err, res) {
|
||||
assert.ifError(err)
|
||||
assert.equal(res.rows.length, 1)
|
||||
client2.end(cb)
|
||||
assert.emits(client2, 'end')
|
||||
}))
|
||||
}))
|
||||
}))
|
||||
client2.connect(
|
||||
assert.success(function () {
|
||||
helper.versionGTE(
|
||||
client2,
|
||||
90200,
|
||||
assert.success(function (isGreater) {
|
||||
if (isGreater) {
|
||||
pidColName = 'pid'
|
||||
queryColName = 'query'
|
||||
}
|
||||
var killIdleQuery =
|
||||
'SELECT ' +
|
||||
pidColName +
|
||||
', (SELECT pg_terminate_backend(' +
|
||||
pidColName +
|
||||
')) AS killed FROM pg_stat_activity WHERE ' +
|
||||
queryColName +
|
||||
' = $1'
|
||||
client2.query(
|
||||
killIdleQuery,
|
||||
[targetQuery],
|
||||
assert.calls(function (err, res) {
|
||||
assert.ifError(err)
|
||||
assert.equal(res.rows.length, 1)
|
||||
client2.end(cb)
|
||||
assert.emits(client2, 'end')
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
suite.test('query killed during query execution of prepared statement', function (done) {
|
||||
@ -65,34 +87,39 @@ suite.test('query killed during query execution of prepared statement', function
|
||||
return done()
|
||||
}
|
||||
var client = new Client(helper.args)
|
||||
client.connect(assert.success(function () {
|
||||
var sleepQuery = 'select pg_sleep($1)'
|
||||
client.connect(
|
||||
assert.success(function () {
|
||||
var sleepQuery = 'select pg_sleep($1)'
|
||||
|
||||
const queryConfig = {
|
||||
name: 'sleep query',
|
||||
text: sleepQuery,
|
||||
values: [5]
|
||||
}
|
||||
const queryConfig = {
|
||||
name: 'sleep query',
|
||||
text: sleepQuery,
|
||||
values: [5],
|
||||
}
|
||||
|
||||
// client should emit an error because it is unexpectedly disconnected
|
||||
assert.emits(client, 'error')
|
||||
// client should emit an error because it is unexpectedly disconnected
|
||||
assert.emits(client, 'error')
|
||||
|
||||
var query1 = client.query(new Query(queryConfig), assert.calls(function (err, result) {
|
||||
assert.equal(err.message, 'terminating connection due to administrator command')
|
||||
}))
|
||||
var query1 = client.query(
|
||||
new Query(queryConfig),
|
||||
assert.calls(function (err, result) {
|
||||
assert.equal(err.message, 'terminating connection due to administrator command')
|
||||
})
|
||||
)
|
||||
|
||||
query1.on('error', function (err) {
|
||||
assert.fail('Prepared statement should not emit error')
|
||||
query1.on('error', function (err) {
|
||||
assert.fail('Prepared statement should not emit error')
|
||||
})
|
||||
|
||||
query1.on('row', function (row) {
|
||||
assert.fail('Prepared statement should not emit row')
|
||||
})
|
||||
|
||||
query1.on('end', function (err) {
|
||||
assert.fail('Prepared statement when executed should not return before being killed')
|
||||
})
|
||||
|
||||
killIdleQuery(sleepQuery, done)
|
||||
})
|
||||
|
||||
query1.on('row', function (row) {
|
||||
assert.fail('Prepared statement should not emit row')
|
||||
})
|
||||
|
||||
query1.on('end', function (err) {
|
||||
assert.fail('Prepared statement when executed should not return before being killed')
|
||||
})
|
||||
|
||||
killIdleQuery(sleepQuery, done)
|
||||
}))
|
||||
)
|
||||
})
|
||||
|
||||
@ -1,88 +1,115 @@
|
||||
"use strict";
|
||||
var helper = require('./test-helper');
|
||||
var util = require('util');
|
||||
var Query = helper.pg.Query;
|
||||
'use strict'
|
||||
var helper = require('./test-helper')
|
||||
var util = require('util')
|
||||
var Query = helper.pg.Query
|
||||
|
||||
test('error during query execution', function() {
|
||||
var client = new Client(helper.args);
|
||||
client.connect(assert.success(function() {
|
||||
var queryText = 'select pg_sleep(10)'
|
||||
var sleepQuery = new Query(queryText);
|
||||
var pidColName = 'procpid'
|
||||
var queryColName = 'current_query';
|
||||
helper.versionGTE(client, 90200, assert.success(function(isGreater) {
|
||||
if(isGreater) {
|
||||
pidColName = 'pid';
|
||||
queryColName = 'query';
|
||||
}
|
||||
var query1 = client.query(sleepQuery, assert.calls(function(err, result) {
|
||||
assert(err);
|
||||
client.end();
|
||||
}));
|
||||
//ensure query1 does not emit an 'end' event
|
||||
//because it was killed and received an error
|
||||
//https://github.com/brianc/node-postgres/issues/547
|
||||
query1.on('end', function() {
|
||||
assert.fail('Query with an error should not emit "end" event')
|
||||
})
|
||||
setTimeout(function() {
|
||||
var client2 = new Client(helper.args);
|
||||
client2.connect(assert.success(function() {
|
||||
var killIdleQuery = `SELECT ${pidColName}, (SELECT pg_cancel_backend(${pidColName})) AS killed FROM pg_stat_activity WHERE ${queryColName} LIKE $1`;
|
||||
client2.query(killIdleQuery, [queryText], assert.calls(function(err, res) {
|
||||
assert.ifError(err);
|
||||
assert(res.rows.length > 0);
|
||||
client2.end();
|
||||
assert.emits(client2, 'end');
|
||||
}));
|
||||
}));
|
||||
}, 300)
|
||||
}));
|
||||
}));
|
||||
});
|
||||
test('error during query execution', function () {
|
||||
var client = new Client(helper.args)
|
||||
client.connect(
|
||||
assert.success(function () {
|
||||
var queryText = 'select pg_sleep(10)'
|
||||
var sleepQuery = new Query(queryText)
|
||||
var pidColName = 'procpid'
|
||||
var queryColName = 'current_query'
|
||||
helper.versionGTE(
|
||||
client,
|
||||
90200,
|
||||
assert.success(function (isGreater) {
|
||||
if (isGreater) {
|
||||
pidColName = 'pid'
|
||||
queryColName = 'query'
|
||||
}
|
||||
var query1 = client.query(
|
||||
sleepQuery,
|
||||
assert.calls(function (err, result) {
|
||||
assert(err)
|
||||
client.end()
|
||||
})
|
||||
)
|
||||
//ensure query1 does not emit an 'end' event
|
||||
//because it was killed and received an error
|
||||
//https://github.com/brianc/node-postgres/issues/547
|
||||
query1.on('end', function () {
|
||||
assert.fail('Query with an error should not emit "end" event')
|
||||
})
|
||||
setTimeout(function () {
|
||||
var client2 = new Client(helper.args)
|
||||
client2.connect(
|
||||
assert.success(function () {
|
||||
var killIdleQuery = `SELECT ${pidColName}, (SELECT pg_cancel_backend(${pidColName})) AS killed FROM pg_stat_activity WHERE ${queryColName} LIKE $1`
|
||||
client2.query(
|
||||
killIdleQuery,
|
||||
[queryText],
|
||||
assert.calls(function (err, res) {
|
||||
assert.ifError(err)
|
||||
assert(res.rows.length > 0)
|
||||
client2.end()
|
||||
assert.emits(client2, 'end')
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
}, 300)
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
if (helper.config.native) {
|
||||
return
|
||||
}
|
||||
|
||||
test('9.3 column error fields', function() {
|
||||
var client = new Client(helper.args);
|
||||
client.connect(assert.success(function() {
|
||||
helper.versionGTE(client, 90300, assert.success(function(isGreater) {
|
||||
if(!isGreater) {
|
||||
return client.end();
|
||||
}
|
||||
test('9.3 column error fields', function () {
|
||||
var client = new Client(helper.args)
|
||||
client.connect(
|
||||
assert.success(function () {
|
||||
helper.versionGTE(
|
||||
client,
|
||||
90300,
|
||||
assert.success(function (isGreater) {
|
||||
if (!isGreater) {
|
||||
return client.end()
|
||||
}
|
||||
|
||||
client.query('CREATE TEMP TABLE column_err_test(a int NOT NULL)');
|
||||
client.query('INSERT INTO column_err_test(a) VALUES (NULL)', function (err) {
|
||||
assert.equal(err.severity, 'ERROR');
|
||||
assert.equal(err.code, '23502');
|
||||
assert.equal(err.table, 'column_err_test');
|
||||
assert.equal(err.column, 'a');
|
||||
return client.end();
|
||||
});
|
||||
}));
|
||||
}));
|
||||
});
|
||||
client.query('CREATE TEMP TABLE column_err_test(a int NOT NULL)')
|
||||
client.query('INSERT INTO column_err_test(a) VALUES (NULL)', function (err) {
|
||||
assert.equal(err.severity, 'ERROR')
|
||||
assert.equal(err.code, '23502')
|
||||
assert.equal(err.table, 'column_err_test')
|
||||
assert.equal(err.column, 'a')
|
||||
return client.end()
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test('9.3 constraint error fields', function() {
|
||||
var client = new Client(helper.args);
|
||||
client.connect(assert.success(function() {
|
||||
helper.versionGTE(client, 90300, assert.success(function(isGreater) {
|
||||
if(!isGreater) {
|
||||
console.log('skip 9.3 error field on older versions of postgres');
|
||||
return client.end();
|
||||
}
|
||||
test('9.3 constraint error fields', function () {
|
||||
var client = new Client(helper.args)
|
||||
client.connect(
|
||||
assert.success(function () {
|
||||
helper.versionGTE(
|
||||
client,
|
||||
90300,
|
||||
assert.success(function (isGreater) {
|
||||
if (!isGreater) {
|
||||
console.log('skip 9.3 error field on older versions of postgres')
|
||||
return client.end()
|
||||
}
|
||||
|
||||
client.query('CREATE TEMP TABLE constraint_err_test(a int PRIMARY KEY)');
|
||||
client.query('INSERT INTO constraint_err_test(a) VALUES (1)');
|
||||
client.query('INSERT INTO constraint_err_test(a) VALUES (1)', function (err) {
|
||||
assert.equal(err.severity, 'ERROR');
|
||||
assert.equal(err.code, '23505');
|
||||
assert.equal(err.table, 'constraint_err_test');
|
||||
assert.equal(err.constraint, 'constraint_err_test_pkey');
|
||||
return client.end();
|
||||
});
|
||||
}));
|
||||
}));
|
||||
});
|
||||
client.query('CREATE TEMP TABLE constraint_err_test(a int PRIMARY KEY)')
|
||||
client.query('INSERT INTO constraint_err_test(a) VALUES (1)')
|
||||
client.query('INSERT INTO constraint_err_test(a) VALUES (1)', function (err) {
|
||||
assert.equal(err.severity, 'ERROR')
|
||||
assert.equal(err.code, '23505')
|
||||
assert.equal(err.table, 'constraint_err_test')
|
||||
assert.equal(err.constraint, 'constraint_err_test_pkey')
|
||||
return client.end()
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@ -4,29 +4,44 @@ var pg = helper.pg
|
||||
|
||||
const pool = new pg.Pool()
|
||||
new helper.Suite().test('should return insert metadata', function () {
|
||||
pool.connect(assert.calls(function (err, client, done) {
|
||||
assert(!err)
|
||||
pool.connect(
|
||||
assert.calls(function (err, client, done) {
|
||||
assert(!err)
|
||||
|
||||
helper.versionGTE(client, 90000, assert.success(function (hasRowCount) {
|
||||
client.query('CREATE TEMP TABLE zugzug(name varchar(10))', assert.calls(function (err, result) {
|
||||
assert(!err)
|
||||
assert.equal(result.oid, null)
|
||||
assert.equal(result.command, 'CREATE')
|
||||
helper.versionGTE(
|
||||
client,
|
||||
90000,
|
||||
assert.success(function (hasRowCount) {
|
||||
client.query(
|
||||
'CREATE TEMP TABLE zugzug(name varchar(10))',
|
||||
assert.calls(function (err, result) {
|
||||
assert(!err)
|
||||
assert.equal(result.oid, null)
|
||||
assert.equal(result.command, 'CREATE')
|
||||
|
||||
var q = client.query("INSERT INTO zugzug(name) VALUES('more work?')", assert.calls(function (err, result) {
|
||||
assert(!err)
|
||||
assert.equal(result.command, 'INSERT')
|
||||
assert.equal(result.rowCount, 1)
|
||||
var q = client.query(
|
||||
"INSERT INTO zugzug(name) VALUES('more work?')",
|
||||
assert.calls(function (err, result) {
|
||||
assert(!err)
|
||||
assert.equal(result.command, 'INSERT')
|
||||
assert.equal(result.rowCount, 1)
|
||||
|
||||
client.query('SELECT * FROM zugzug', assert.calls(function (err, result) {
|
||||
assert(!err)
|
||||
if (hasRowCount) assert.equal(result.rowCount, 1)
|
||||
assert.equal(result.command, 'SELECT')
|
||||
done()
|
||||
process.nextTick(pool.end.bind(pool))
|
||||
}))
|
||||
}))
|
||||
}))
|
||||
}))
|
||||
}))
|
||||
client.query(
|
||||
'SELECT * FROM zugzug',
|
||||
assert.calls(function (err, result) {
|
||||
assert(!err)
|
||||
if (hasRowCount) assert.equal(result.rowCount, 1)
|
||||
assert.equal(result.command, 'SELECT')
|
||||
done()
|
||||
process.nextTick(pool.end.bind(pool))
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@ -16,16 +16,21 @@ test('returns results as array', function () {
|
||||
assert.strictEqual(row[2], 'hai')
|
||||
assert.strictEqual(row[3], null)
|
||||
}
|
||||
client.connect(assert.success(function () {
|
||||
var config = {
|
||||
text: 'SELECT NOW(), 1::int, $1::text, null',
|
||||
values: ['hai'],
|
||||
rowMode: 'array'
|
||||
}
|
||||
var query = client.query(config, assert.success(function (result) {
|
||||
assert.equal(result.rows.length, 1)
|
||||
checkRow(result.rows[0])
|
||||
client.end()
|
||||
}))
|
||||
}))
|
||||
client.connect(
|
||||
assert.success(function () {
|
||||
var config = {
|
||||
text: 'SELECT NOW(), 1::int, $1::text, null',
|
||||
values: ['hai'],
|
||||
rowMode: 'array',
|
||||
}
|
||||
var query = client.query(
|
||||
config,
|
||||
assert.success(function (result) {
|
||||
assert.equal(result.rows.length, 1)
|
||||
checkRow(result.rows[0])
|
||||
client.end()
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@ -19,20 +19,32 @@ var checkResult = function (result) {
|
||||
|
||||
test('row descriptions on result object', function () {
|
||||
var client = new Client(conInfo)
|
||||
client.connect(assert.success(function () {
|
||||
client.query('SELECT NOW() as now, 1::int as num, $1::text as texty', ['hello'], assert.success(function (result) {
|
||||
checkResult(result)
|
||||
client.end()
|
||||
}))
|
||||
}))
|
||||
client.connect(
|
||||
assert.success(function () {
|
||||
client.query(
|
||||
'SELECT NOW() as now, 1::int as num, $1::text as texty',
|
||||
['hello'],
|
||||
assert.success(function (result) {
|
||||
checkResult(result)
|
||||
client.end()
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test('row description on no rows', function () {
|
||||
var client = new Client(conInfo)
|
||||
client.connect(assert.success(function () {
|
||||
client.query('SELECT NOW() as now, 1::int as num, $1::text as texty LIMIT 0', ['hello'], assert.success(function (result) {
|
||||
checkResult(result)
|
||||
client.end()
|
||||
}))
|
||||
}))
|
||||
client.connect(
|
||||
assert.success(function () {
|
||||
client.query(
|
||||
'SELECT NOW() as now, 1::int as num, $1::text as texty LIMIT 0',
|
||||
['hello'],
|
||||
assert.success(function (result) {
|
||||
checkResult(result)
|
||||
client.end()
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@ -22,7 +22,11 @@ test('simple query interface', function () {
|
||||
columnCount++
|
||||
}
|
||||
if ('length' in row) {
|
||||
assert.lengthIs(row, columnCount, 'Iterating through the columns gives a different length from calling .length.')
|
||||
assert.lengthIs(
|
||||
row,
|
||||
columnCount,
|
||||
'Iterating through the columns gives a different length from calling .length.'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -65,7 +69,7 @@ test('prepared statements do not mutate params', function () {
|
||||
|
||||
test('multiple simple queries', function () {
|
||||
var client = helper.client()
|
||||
client.query({ text: "create temp table bang(id serial, name varchar(5));insert into bang(name) VALUES('boom');"})
|
||||
client.query({ text: "create temp table bang(id serial, name varchar(5));insert into bang(name) VALUES('boom');" })
|
||||
client.query("insert into bang(name) VALUES ('yes');")
|
||||
var query = client.query(new Query('select name from bang'))
|
||||
assert.emits(query, 'row', function (row) {
|
||||
@ -79,9 +83,11 @@ test('multiple simple queries', function () {
|
||||
|
||||
test('multiple select statements', function () {
|
||||
var client = helper.client()
|
||||
client.query('create temp table boom(age integer); insert into boom(age) values(1); insert into boom(age) values(2); insert into boom(age) values(3)')
|
||||
client.query({text: "create temp table bang(name varchar(5)); insert into bang(name) values('zoom');"})
|
||||
var result = client.query(new Query({text: 'select age from boom where age < 2; select name from bang'}))
|
||||
client.query(
|
||||
'create temp table boom(age integer); insert into boom(age) values(1); insert into boom(age) values(2); insert into boom(age) values(3)'
|
||||
)
|
||||
client.query({ text: "create temp table bang(name varchar(5)); insert into bang(name) values('zoom');" })
|
||||
var result = client.query(new Query({ text: 'select age from boom where age < 2; select name from bang' }))
|
||||
assert.emits(result, 'row', function (row) {
|
||||
assert.strictEqual(row['age'], 1)
|
||||
assert.emits(result, 'row', function (row) {
|
||||
|
||||
@ -4,12 +4,18 @@ var config = require(__dirname + '/test-helper').config
|
||||
test('can connect with ssl', function () {
|
||||
return false
|
||||
config.ssl = {
|
||||
rejectUnauthorized: false
|
||||
rejectUnauthorized: false,
|
||||
}
|
||||
pg.connect(config, assert.success(function (client) {
|
||||
return false
|
||||
client.query('SELECT NOW()', assert.success(function () {
|
||||
pg.end()
|
||||
}))
|
||||
}))
|
||||
pg.connect(
|
||||
config,
|
||||
assert.success(function (client) {
|
||||
return false
|
||||
client.query(
|
||||
'SELECT NOW()',
|
||||
assert.success(function () {
|
||||
pg.end()
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@ -6,22 +6,28 @@ var suite = new helper.Suite()
|
||||
|
||||
var conInfo = helper.config
|
||||
|
||||
function getConInfo (override) {
|
||||
return Object.assign({}, conInfo, override )
|
||||
function getConInfo(override) {
|
||||
return Object.assign({}, conInfo, override)
|
||||
}
|
||||
|
||||
function getStatementTimeout (conf, cb) {
|
||||
function getStatementTimeout(conf, cb) {
|
||||
var client = new Client(conf)
|
||||
client.connect(assert.success(function () {
|
||||
client.query('SHOW statement_timeout', assert.success(function (res) {
|
||||
var statementTimeout = res.rows[0].statement_timeout
|
||||
cb(statementTimeout)
|
||||
client.end()
|
||||
}))
|
||||
}))
|
||||
client.connect(
|
||||
assert.success(function () {
|
||||
client.query(
|
||||
'SHOW statement_timeout',
|
||||
assert.success(function (res) {
|
||||
var statementTimeout = res.rows[0].statement_timeout
|
||||
cb(statementTimeout)
|
||||
client.end()
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (!helper.args.native) { // statement_timeout is not supported with the native client
|
||||
if (!helper.args.native) {
|
||||
// statement_timeout is not supported with the native client
|
||||
suite.test('No default statement_timeout ', function (done) {
|
||||
getConInfo()
|
||||
getStatementTimeout({}, function (res) {
|
||||
@ -32,7 +38,7 @@ if (!helper.args.native) { // statement_timeout is not supported with the native
|
||||
|
||||
suite.test('statement_timeout integer is used', function (done) {
|
||||
var conf = getConInfo({
|
||||
'statement_timeout': 3000
|
||||
statement_timeout: 3000,
|
||||
})
|
||||
getStatementTimeout(conf, function (res) {
|
||||
assert.strictEqual(res, '3s')
|
||||
@ -42,7 +48,7 @@ if (!helper.args.native) { // statement_timeout is not supported with the native
|
||||
|
||||
suite.test('statement_timeout float is used', function (done) {
|
||||
var conf = getConInfo({
|
||||
'statement_timeout': 3000.7
|
||||
statement_timeout: 3000.7,
|
||||
})
|
||||
getStatementTimeout(conf, function (res) {
|
||||
assert.strictEqual(res, '3s')
|
||||
@ -52,7 +58,7 @@ if (!helper.args.native) { // statement_timeout is not supported with the native
|
||||
|
||||
suite.test('statement_timeout string is used', function (done) {
|
||||
var conf = getConInfo({
|
||||
'statement_timeout': '3000'
|
||||
statement_timeout: '3000',
|
||||
})
|
||||
getStatementTimeout(conf, function (res) {
|
||||
assert.strictEqual(res, '3s')
|
||||
@ -62,16 +68,17 @@ if (!helper.args.native) { // statement_timeout is not supported with the native
|
||||
|
||||
suite.test('statement_timeout actually cancels long running queries', function (done) {
|
||||
var conf = getConInfo({
|
||||
'statement_timeout': '10' // 10ms to keep tests running fast
|
||||
statement_timeout: '10', // 10ms to keep tests running fast
|
||||
})
|
||||
var client = new Client(conf)
|
||||
client.connect(assert.success(function () {
|
||||
client.query('SELECT pg_sleep( 1 )', function ( error ) {
|
||||
client.end()
|
||||
assert.strictEqual( error.code, '57014' ) // query_cancelled
|
||||
done()
|
||||
client.connect(
|
||||
assert.success(function () {
|
||||
client.query('SELECT pg_sleep( 1 )', function (error) {
|
||||
client.end()
|
||||
assert.strictEqual(error.code, '57014') // query_cancelled
|
||||
done()
|
||||
})
|
||||
})
|
||||
}))
|
||||
)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@ -4,73 +4,96 @@ const suite = new helper.Suite()
|
||||
const pg = helper.pg
|
||||
|
||||
const client = new pg.Client()
|
||||
client.connect(assert.success(function () {
|
||||
client.query('begin')
|
||||
client.connect(
|
||||
assert.success(function () {
|
||||
client.query('begin')
|
||||
|
||||
var getZed = {
|
||||
text: 'SELECT * FROM person WHERE name = $1',
|
||||
values: ['Zed']
|
||||
}
|
||||
var getZed = {
|
||||
text: 'SELECT * FROM person WHERE name = $1',
|
||||
values: ['Zed'],
|
||||
}
|
||||
|
||||
suite.test('name should not exist in the database', function (done) {
|
||||
client.query(getZed, assert.calls(function (err, result) {
|
||||
assert(!err)
|
||||
assert.empty(result.rows)
|
||||
done()
|
||||
}))
|
||||
suite.test('name should not exist in the database', function (done) {
|
||||
client.query(
|
||||
getZed,
|
||||
assert.calls(function (err, result) {
|
||||
assert(!err)
|
||||
assert.empty(result.rows)
|
||||
done()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
suite.test('can insert name', (done) => {
|
||||
client.query(
|
||||
'INSERT INTO person(name, age) VALUES($1, $2)',
|
||||
['Zed', 270],
|
||||
assert.calls(function (err, result) {
|
||||
assert(!err)
|
||||
done()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
suite.test('name should exist in the database', function (done) {
|
||||
client.query(
|
||||
getZed,
|
||||
assert.calls(function (err, result) {
|
||||
assert(!err)
|
||||
assert.equal(result.rows[0].name, 'Zed')
|
||||
done()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
suite.test('rollback', (done) => {
|
||||
client.query('rollback', done)
|
||||
})
|
||||
|
||||
suite.test('name should not exist in the database', function (done) {
|
||||
client.query(
|
||||
getZed,
|
||||
assert.calls(function (err, result) {
|
||||
assert(!err)
|
||||
assert.empty(result.rows)
|
||||
client.end(done)
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
suite.test('can insert name', (done) => {
|
||||
client.query('INSERT INTO person(name, age) VALUES($1, $2)', ['Zed', 270], assert.calls(function (err, result) {
|
||||
assert(!err)
|
||||
done()
|
||||
}))
|
||||
})
|
||||
|
||||
suite.test('name should exist in the database', function (done) {
|
||||
client.query(getZed, assert.calls(function (err, result) {
|
||||
assert(!err)
|
||||
assert.equal(result.rows[0].name, 'Zed')
|
||||
done()
|
||||
}))
|
||||
})
|
||||
|
||||
suite.test('rollback', (done) => {
|
||||
client.query('rollback', done)
|
||||
})
|
||||
|
||||
suite.test('name should not exist in the database', function (done) {
|
||||
client.query(getZed, assert.calls(function (err, result) {
|
||||
assert(!err)
|
||||
assert.empty(result.rows)
|
||||
client.end(done)
|
||||
}))
|
||||
})
|
||||
}))
|
||||
)
|
||||
|
||||
suite.test('gh#36', function (cb) {
|
||||
const pool = new pg.Pool()
|
||||
pool.connect(assert.success(function (client, done) {
|
||||
client.query('BEGIN')
|
||||
client.query({
|
||||
name: 'X',
|
||||
text: 'SELECT $1::INTEGER',
|
||||
values: [0]
|
||||
}, assert.calls(function (err, result) {
|
||||
if (err) throw err
|
||||
assert.equal(result.rows.length, 1)
|
||||
}))
|
||||
client.query({
|
||||
name: 'X',
|
||||
text: 'SELECT $1::INTEGER',
|
||||
values: [0]
|
||||
}, assert.calls(function (err, result) {
|
||||
if (err) throw err
|
||||
assert.equal(result.rows.length, 1)
|
||||
}))
|
||||
client.query('COMMIT', function () {
|
||||
done()
|
||||
pool.end(cb)
|
||||
pool.connect(
|
||||
assert.success(function (client, done) {
|
||||
client.query('BEGIN')
|
||||
client.query(
|
||||
{
|
||||
name: 'X',
|
||||
text: 'SELECT $1::INTEGER',
|
||||
values: [0],
|
||||
},
|
||||
assert.calls(function (err, result) {
|
||||
if (err) throw err
|
||||
assert.equal(result.rows.length, 1)
|
||||
})
|
||||
)
|
||||
client.query(
|
||||
{
|
||||
name: 'X',
|
||||
text: 'SELECT $1::INTEGER',
|
||||
values: [0],
|
||||
},
|
||||
assert.calls(function (err, result) {
|
||||
if (err) throw err
|
||||
assert.equal(result.rows.length, 1)
|
||||
})
|
||||
)
|
||||
client.query('COMMIT', function () {
|
||||
done()
|
||||
pool.end(cb)
|
||||
})
|
||||
})
|
||||
}))
|
||||
)
|
||||
})
|
||||
|
||||
@ -9,102 +9,130 @@ var testForTypeCoercion = function (type) {
|
||||
suite.test(`test type coercion ${type.name}`, (cb) => {
|
||||
pool.connect(function (err, client, done) {
|
||||
assert(!err)
|
||||
client.query('create temp table test_type(col ' + type.name + ')', assert.calls(function (err, result) {
|
||||
assert(!err)
|
||||
client.query(
|
||||
'create temp table test_type(col ' + type.name + ')',
|
||||
assert.calls(function (err, result) {
|
||||
assert(!err)
|
||||
|
||||
type.values.forEach(function (val) {
|
||||
var insertQuery = client.query('insert into test_type(col) VALUES($1)', [val], assert.calls(function (err, result) {
|
||||
assert(!err)
|
||||
}))
|
||||
type.values.forEach(function (val) {
|
||||
var insertQuery = client.query(
|
||||
'insert into test_type(col) VALUES($1)',
|
||||
[val],
|
||||
assert.calls(function (err, result) {
|
||||
assert(!err)
|
||||
})
|
||||
)
|
||||
|
||||
var query = client.query(new pg.Query({
|
||||
name: 'get type ' + type.name,
|
||||
text: 'select col from test_type'
|
||||
}))
|
||||
var query = client.query(
|
||||
new pg.Query({
|
||||
name: 'get type ' + type.name,
|
||||
text: 'select col from test_type',
|
||||
})
|
||||
)
|
||||
|
||||
query.on('error', function (err) {
|
||||
console.log(err)
|
||||
throw err
|
||||
query.on('error', function (err) {
|
||||
console.log(err)
|
||||
throw err
|
||||
})
|
||||
|
||||
assert.emits(
|
||||
query,
|
||||
'row',
|
||||
function (row) {
|
||||
var expected = val + ' (' + typeof val + ')'
|
||||
var returned = row.col + ' (' + typeof row.col + ')'
|
||||
assert.strictEqual(row.col, val, 'expected ' + type.name + ' of ' + expected + ' but got ' + returned)
|
||||
},
|
||||
'row should have been called for ' + type.name + ' of ' + val
|
||||
)
|
||||
|
||||
client.query('delete from test_type')
|
||||
})
|
||||
|
||||
assert.emits(query, 'row', function (row) {
|
||||
var expected = val + ' (' + typeof val + ')'
|
||||
var returned = row.col + ' (' + typeof row.col + ')'
|
||||
assert.strictEqual(row.col, val, 'expected ' + type.name + ' of ' + expected + ' but got ' + returned)
|
||||
}, 'row should have been called for ' + type.name + ' of ' + val)
|
||||
|
||||
client.query('delete from test_type')
|
||||
client.query('drop table test_type', function () {
|
||||
done()
|
||||
pool.end(cb)
|
||||
})
|
||||
})
|
||||
|
||||
client.query('drop table test_type', function () {
|
||||
done()
|
||||
pool.end(cb)
|
||||
})
|
||||
}))
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
var types = [{
|
||||
name: 'integer',
|
||||
values: [-2147483648, -1, 0, 1, 2147483647, null]
|
||||
}, {
|
||||
name: 'smallint',
|
||||
values: [-32768, -1, 0, 1, 32767, null]
|
||||
}, {
|
||||
name: 'bigint',
|
||||
values: [
|
||||
'-9223372036854775808',
|
||||
'-9007199254740992',
|
||||
'0',
|
||||
'9007199254740992',
|
||||
'72057594037928030',
|
||||
'9223372036854775807',
|
||||
null
|
||||
]
|
||||
}, {
|
||||
name: 'varchar(5)',
|
||||
values: ['yo', '', 'zomg!', null]
|
||||
}, {
|
||||
name: 'oid',
|
||||
values: [0, 204410, null]
|
||||
}, {
|
||||
name: 'bool',
|
||||
values: [true, false, null]
|
||||
}, {
|
||||
name: 'numeric',
|
||||
values: [
|
||||
'-12.34',
|
||||
'0',
|
||||
'12.34',
|
||||
'-3141592653589793238462643383279502.1618033988749894848204586834365638',
|
||||
'3141592653589793238462643383279502.1618033988749894848204586834365638',
|
||||
null
|
||||
]
|
||||
}, {
|
||||
name: 'real',
|
||||
values: [-101.3, -1.2, 0, 1.2, 101.1, null]
|
||||
}, {
|
||||
name: 'double precision',
|
||||
values: [-101.3, -1.2, 0, 1.2, 101.1, null]
|
||||
}, {
|
||||
name: 'timestamptz',
|
||||
values: [null]
|
||||
}, {
|
||||
name: 'timestamp',
|
||||
values: [null]
|
||||
}, {
|
||||
name: 'timetz',
|
||||
values: ['13:11:12.1234-05:30', null]
|
||||
}, {
|
||||
name: 'time',
|
||||
values: ['13:12:12.321', null]
|
||||
}]
|
||||
var types = [
|
||||
{
|
||||
name: 'integer',
|
||||
values: [-2147483648, -1, 0, 1, 2147483647, null],
|
||||
},
|
||||
{
|
||||
name: 'smallint',
|
||||
values: [-32768, -1, 0, 1, 32767, null],
|
||||
},
|
||||
{
|
||||
name: 'bigint',
|
||||
values: [
|
||||
'-9223372036854775808',
|
||||
'-9007199254740992',
|
||||
'0',
|
||||
'9007199254740992',
|
||||
'72057594037928030',
|
||||
'9223372036854775807',
|
||||
null,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'varchar(5)',
|
||||
values: ['yo', '', 'zomg!', null],
|
||||
},
|
||||
{
|
||||
name: 'oid',
|
||||
values: [0, 204410, null],
|
||||
},
|
||||
{
|
||||
name: 'bool',
|
||||
values: [true, false, null],
|
||||
},
|
||||
{
|
||||
name: 'numeric',
|
||||
values: [
|
||||
'-12.34',
|
||||
'0',
|
||||
'12.34',
|
||||
'-3141592653589793238462643383279502.1618033988749894848204586834365638',
|
||||
'3141592653589793238462643383279502.1618033988749894848204586834365638',
|
||||
null,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'real',
|
||||
values: [-101.3, -1.2, 0, 1.2, 101.1, null],
|
||||
},
|
||||
{
|
||||
name: 'double precision',
|
||||
values: [-101.3, -1.2, 0, 1.2, 101.1, null],
|
||||
},
|
||||
{
|
||||
name: 'timestamptz',
|
||||
values: [null],
|
||||
},
|
||||
{
|
||||
name: 'timestamp',
|
||||
values: [null],
|
||||
},
|
||||
{
|
||||
name: 'timetz',
|
||||
values: ['13:11:12.1234-05:30', null],
|
||||
},
|
||||
{
|
||||
name: 'time',
|
||||
values: ['13:12:12.321', null],
|
||||
},
|
||||
]
|
||||
|
||||
// ignore some tests in binary mode
|
||||
if (helper.config.binary) {
|
||||
types = types.filter(function (type) {
|
||||
return !(type.name in { 'real': 1, 'timetz': 1, 'time': 1, 'numeric': 1, 'bigint': 1 })
|
||||
return !(type.name in { real: 1, timetz: 1, time: 1, numeric: 1, bigint: 1 })
|
||||
})
|
||||
}
|
||||
|
||||
@ -121,13 +149,15 @@ suite.test('timestampz round trip', function (cb) {
|
||||
client.query({
|
||||
text: 'insert into date_tests(name, tstz)VALUES($1, $2)',
|
||||
name: 'add date',
|
||||
values: ['now', now]
|
||||
values: ['now', now],
|
||||
})
|
||||
var result = client.query(new pg.Query({
|
||||
name: 'get date',
|
||||
text: 'select * from date_tests where name = $1',
|
||||
values: ['now']
|
||||
}))
|
||||
var result = client.query(
|
||||
new pg.Query({
|
||||
name: 'get date',
|
||||
text: 'select * from date_tests where name = $1',
|
||||
values: ['now'],
|
||||
})
|
||||
)
|
||||
|
||||
assert.emits(result, 'row', function (row) {
|
||||
var date = row.tstz
|
||||
@ -145,21 +175,26 @@ suite.test('timestampz round trip', function (cb) {
|
||||
})
|
||||
})
|
||||
|
||||
suite.test('selecting nulls', cb => {
|
||||
suite.test('selecting nulls', (cb) => {
|
||||
const pool = new pg.Pool()
|
||||
pool.connect(assert.calls(function (err, client, done) {
|
||||
assert.ifError(err)
|
||||
client.query('select null as res;', assert.calls(function (err, res) {
|
||||
assert(!err)
|
||||
assert.strictEqual(res.rows[0].res, null)
|
||||
}))
|
||||
client.query('select 7 <> $1 as res;', [null], function (err, res) {
|
||||
assert(!err)
|
||||
assert.strictEqual(res.rows[0].res, null)
|
||||
done()
|
||||
pool.end(cb)
|
||||
pool.connect(
|
||||
assert.calls(function (err, client, done) {
|
||||
assert.ifError(err)
|
||||
client.query(
|
||||
'select null as res;',
|
||||
assert.calls(function (err, res) {
|
||||
assert(!err)
|
||||
assert.strictEqual(res.rows[0].res, null)
|
||||
})
|
||||
)
|
||||
client.query('select 7 <> $1 as res;', [null], function (err, res) {
|
||||
assert(!err)
|
||||
assert.strictEqual(res.rows[0].res, null)
|
||||
done()
|
||||
pool.end(cb)
|
||||
})
|
||||
})
|
||||
}))
|
||||
)
|
||||
})
|
||||
|
||||
suite.test('date range extremes', function (done) {
|
||||
@ -169,25 +204,40 @@ suite.test('date range extremes', function (done) {
|
||||
// otherwise (if server's timezone is ahead of GMT) in
|
||||
// textParsers.js::parseDate() the timezone offest is added to the date;
|
||||
// in the case of "275760-09-13 00:00:00 GMT" the timevalue overflows.
|
||||
client.query('SET TIMEZONE TO GMT', assert.success(function (res) {
|
||||
// PostgreSQL supports date range of 4713 BCE to 294276 CE
|
||||
// http://www.postgresql.org/docs/9.2/static/datatype-datetime.html
|
||||
// ECMAScript supports date range of Apr 20 271821 BCE to Sep 13 275760 CE
|
||||
// http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.1
|
||||
client.query('SELECT $1::TIMESTAMPTZ as when', ['275760-09-13 00:00:00 GMT'], assert.success(function (res) {
|
||||
assert.equal(res.rows[0].when.getFullYear(), 275760)
|
||||
}))
|
||||
client.query(
|
||||
'SET TIMEZONE TO GMT',
|
||||
assert.success(function (res) {
|
||||
// PostgreSQL supports date range of 4713 BCE to 294276 CE
|
||||
// http://www.postgresql.org/docs/9.2/static/datatype-datetime.html
|
||||
// ECMAScript supports date range of Apr 20 271821 BCE to Sep 13 275760 CE
|
||||
// http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.1
|
||||
client.query(
|
||||
'SELECT $1::TIMESTAMPTZ as when',
|
||||
['275760-09-13 00:00:00 GMT'],
|
||||
assert.success(function (res) {
|
||||
assert.equal(res.rows[0].when.getFullYear(), 275760)
|
||||
})
|
||||
)
|
||||
|
||||
client.query('SELECT $1::TIMESTAMPTZ as when', ['4713-12-31 12:31:59 BC GMT'], assert.success(function (res) {
|
||||
assert.equal(res.rows[0].when.getFullYear(), -4712)
|
||||
}))
|
||||
client.query(
|
||||
'SELECT $1::TIMESTAMPTZ as when',
|
||||
['4713-12-31 12:31:59 BC GMT'],
|
||||
assert.success(function (res) {
|
||||
assert.equal(res.rows[0].when.getFullYear(), -4712)
|
||||
})
|
||||
)
|
||||
|
||||
client.query('SELECT $1::TIMESTAMPTZ as when', ['275760-09-13 00:00:00 -15:00'], assert.success(function (res) {
|
||||
assert(isNaN(res.rows[0].when.getTime()))
|
||||
}))
|
||||
client.query(
|
||||
'SELECT $1::TIMESTAMPTZ as when',
|
||||
['275760-09-13 00:00:00 -15:00'],
|
||||
assert.success(function (res) {
|
||||
assert(isNaN(res.rows[0].when.getTime()))
|
||||
})
|
||||
)
|
||||
|
||||
client.on('drain', () => {
|
||||
client.end(done)
|
||||
client.on('drain', () => {
|
||||
client.end(done)
|
||||
})
|
||||
})
|
||||
}))
|
||||
)
|
||||
})
|
||||
|
||||
@ -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())
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
@ -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
Loading…
x
Reference in New Issue
Block a user