Commit a36458e6 authored by Xavier Guimard's avatar Xavier Guimard

New upstream version 7.12.1

parent cc1a02d2
{
"extends": "standard",
"plugins": [
"node"
],
"extends": [
"standard",
"eslint:recommended",
"plugin:node/recommended"
],
"parserOptions": {
"ecmaVersion": 2017
},
"env": {
"node": true,
"es6": true
},
"rules": {
"no-new-func": "off"
}
}
......@@ -6,3 +6,5 @@ node_modules/
script/
*.swp
test/
.travis.yml
ci_scripts/
language: node_js
sudo: false
sudo: true
dist: trusty
before_script:
- node script/create-test-tables.js pg://postgres@127.0.0.1:5432/postgres
before_install:
- if [ $TRAVIS_OS_NAME == "linux" ]; then
if [[ $(node -v) =~ v[1-9][0-9] ]]; then
source ./ci_scripts/build.sh;
fi
fi
env:
- CC=clang CXX=clang++ npm_config_clang=1 PGUSER=postgres PGDATABASE=postgres
......@@ -17,7 +26,7 @@ matrix:
- node_js: "10"
addons:
postgresql: "9.6"
- node_js: "11"
- node_js: "12"
addons:
postgresql: "9.6"
- node_js: "lts/carbon"
......
......@@ -4,6 +4,27 @@ For richer information consult the commit log on github with referenced pull req
We do not include break-fix version release in this file.
### 7.12.0
- Add support for [async password lookup](https://github.com/brianc/node-postgres/pull/1926).
### 7.11.0
- Add support for [connection_timeout](https://github.com/brianc/node-postgres/pull/1847/files#diff-5391bde944956870128be1136e7bc176R63) and [keepalives_idle](https://github.com/brianc/node-postgres/pull/1847).
### 7.10.0
- Add support for [per-query types](https://github.com/brianc/node-postgres/pull/1825).
### 7.9.0
- Add support for [sasl/scram authentication](https://github.com/brianc/node-postgres/pull/1835).
### 7.8.0
- Add support for passing [secureOptions](https://github.com/brianc/node-postgres/pull/1804) SSL config.
- Upgrade [pg-types](https://github.com/brianc/node-postgres/pull/1806) to 2.0.
### 7.7.0
- Add support for configurable [query timeout](https://github.com/brianc/node-postgres/pull/1760) on a client level.
......
MIT License
Copyright (c) 2010 - 2018 Brian Carlson
Copyright (c) 2010 - 2019 Brian Carlson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
......
......@@ -62,4 +62,6 @@ test-pool:
lint:
@echo "***Starting lint***"
node_modules/.bin/eslint lib
node -e "process.exit(Number(process.versions.node.split('.')[0]) < 8 ? 0 : 1)" \
&& echo "***Skipping lint (node version too old)***" \
|| node_modules/.bin/eslint lib
......@@ -67,7 +67,7 @@ The causes and solutions to common errors can be found among the [Frequently Ask
## License
Copyright (c) 2010-2018 Brian Carlson (brian.m.carlson@gmail.com)
Copyright (c) 2010-2019 Brian Carlson (brian.m.carlson@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
......
node-postgres is made possible by the helpful contributors from the community well as the following generous supporters on [Patreon](https://www.patreon.com/node_postgres).
# Leaders
- [MadKudu](https://www.madkudu.com) - [@madkudu](https://twitter.com/madkudu)
- [Third Iron](https://thirdiron.com/)
- [Timescale](https://timescale.com)
# Supporters
- John Fawcett
- Lalit Kapoor [@lalitkapoor](https://twitter.com/lalitkapoor)
- Paul Frazee [@pfrazee](https://twitter.com/pfrazee)
......@@ -17,3 +21,5 @@ node-postgres is made possible by the helpful contributors from the community we
- Benjie Gillam
- David Hanson
- Franklin Davenport
- [Eventbot](https://geteventbot.com/)
- Chuck T
#!/bin/sh
BUILD_DIR="$(pwd)"
source ./ci_scripts/install_openssl.sh 1.1.1b
sudo updatedb
source ./ci_scripts/install_libpq.sh
sudo updatedb
sudo ldconfig
cd $BUILD_DIR
#!/bin/bash
set -e
OPENSSL_DIR="$(pwd)/openssl-1.1.1b"
POSTGRES_VERSION="11.3"
POSTGRES_DIR="$(pwd)/postgres-${POSTGRES_VERSION}"
TMP_DIR="/tmp/postgres"
JOBS="-j$(nproc || echo 1)"
if [ -d "${TMP_DIR}" ]; then
rm -rf "${TMP_DIR}"
fi
mkdir -p "${TMP_DIR}"
curl https://ftp.postgresql.org/pub/source/v${POSTGRES_VERSION}/postgresql-${POSTGRES_VERSION}.tar.gz | \
tar -C "${TMP_DIR}" -xzf -
cd "${TMP_DIR}/postgresql-${POSTGRES_VERSION}"
if [ -d "${POSTGRES_DIR}" ]; then
rm -rf "${POSTGRES_DIR}"
fi
mkdir -p $POSTGRES_DIR
./configure --prefix=$POSTGRES_DIR --with-openssl --with-includes=${OPENSSL_DIR}/include --with-libraries=${OPENSSL_DIR}/lib --without-readline
cd src/interfaces/libpq; make; make install; cd -
cd src/bin/pg_config; make install; cd -
cd src/backend; make generated-headers; cd -
cd src/include; make install; cd -
export PATH="${POSTGRES_DIR}/bin:${PATH}"
export CFLAGS="-I${POSTGRES_DIR}/include"
export LDFLAGS="-L${POSTGRES_DIR}/lib"
export LD_LIBRARY_PATH="${POSTGRES_DIR}/lib:$LD_LIBRARY_PATH"
export PKG_CONFIG_PATH="${POSTGRES_DIR}/lib/pkgconfig:$PKG_CONFIG_PATH"
#!/bin/sh
if [ ${#} -lt 1 ]; then
echo "OpenSSL version required." 1>&2
exit 1
fi
OPENSSL_VERSION="${1}"
OPENSSL_DIR="$(pwd)/openssl-${OPENSSL_VERSION}"
TMP_DIR="/tmp/openssl"
JOBS="-j$(nproc)"
if [ -d "${TMP_DIR}" ]; then
rm -rf "${TMP_DIR}"
fi
mkdir -p "${TMP_DIR}"
curl -s https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz | \
tar -C "${TMP_DIR}" -xzf -
pushd "${TMP_DIR}/openssl-${OPENSSL_VERSION}"
if [ -d "${OPENSSL_DIR}" ]; then
rm -rf "${OPENSSL_DIR}"
fi
./Configure \
--prefix=${OPENSSL_DIR} \
enable-crypto-mdebug enable-crypto-mdebug-backtrace \
linux-x86_64
make -s $JOBS
make install_sw
popd
export PATH="${OPENSSL_DIR}/bin:${PATH}"
export CFLAGS="-I${OPENSSL_DIR}/include"
export LDFLAGS="-L${OPENSSL_DIR}/lib"
export LD_LIBRARY_PATH="${OPENSSL_DIR}/lib:$LD_LIBRARY_PATH"
export PKG_CONFIG_PATH="${OPENSSL_DIR}/lib/pkgconfig:$PKG_CONFIG_PATH"
......@@ -10,6 +10,7 @@
var EventEmitter = require('events').EventEmitter
var util = require('util')
var utils = require('./utils')
var sasl = require('./sasl')
var pgPass = require('pgpass')
var TypeOverrides = require('./type-overrides')
......@@ -43,6 +44,7 @@ var Client = function (config) {
stream: c.stream,
ssl: this.connectionParameters.ssl,
keepAlive: c.keepAlive || false,
keepAliveInitialDelayMillis: c.keepAliveInitialDelayMillis || 0,
encoding: this.connectionParameters.client_encoding || 'utf8'
})
this.queryQueue = []
......@@ -50,6 +52,7 @@ var Client = function (config) {
this.processID = null
this.secretKey = null
this.ssl = this.connectionParameters.ssl || false
this._connectionTimeoutMillis = c.connectionTimeoutMillis || 0
}
util.inherits(Client, EventEmitter)
......@@ -82,6 +85,14 @@ Client.prototype._connect = function (callback) {
}
this._connecting = true
var connectionTimeoutHandle
if (this._connectionTimeoutMillis > 0) {
connectionTimeoutHandle = setTimeout(() => {
con._ending = true
con.stream.destroy(new Error('timeout expired'))
}, this._connectionTimeoutMillis)
}
if (this.host && this.host.indexOf('/') === 0) {
con.connect(this.host + '/.s.PGSQL.' + this.port)
} else {
......@@ -103,7 +114,24 @@ Client.prototype._connect = function (callback) {
function checkPgPass (cb) {
return function (msg) {
if (self.password !== null) {
if (typeof self.password === 'function') {
self._Promise.resolve()
.then(() => self.password())
.then(pass => {
if (pass !== undefined) {
if (typeof pass !== 'string') {
con.emit('error', new TypeError('Password must be a string'))
return
}
self.connectionParameters.password = self.password = pass
} else {
self.connectionParameters.password = self.password = null
}
cb(msg)
}).catch(err => {
con.emit('error', err)
})
} else if (self.password !== null) {
cb(msg)
} else {
pgPass(self.connectionParameters, function (pass) {
......@@ -126,6 +154,28 @@ Client.prototype._connect = function (callback) {
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.sendSASLInitialResponseMessage(saslSession.mechanism, saslSession.response)
}))
// password request handling (SASL)
con.on('authenticationSASLContinue', function (msg) {
sasl.continueSession(saslSession, self.password, msg.data)
con.sendSCRAMClientFinalMessage(saslSession.response)
})
// password request handling (SASL)
con.on('authenticationSASLFinal', function (msg) {
sasl.finalizeSession(saslSession, msg.data)
saslSession = null
})
con.once('backendKeyData', function (msg) {
self.processID = msg.processID
self.secretKey = msg.secretKey
......@@ -136,6 +186,7 @@ Client.prototype._connect = function (callback) {
return
}
this._connectionError = true
clearTimeout(connectionTimeoutHandle)
if (callback) {
return callback(err)
}
......@@ -173,6 +224,7 @@ Client.prototype._connect = function (callback) {
con.removeListener('errorMessage', connectingErrorHandler)
con.on('error', connectedErrorHandler)
con.on('errorMessage', connectedErrorMessageHandler)
clearTimeout(connectionTimeoutHandle)
// process possible callback argument to Client#connect
if (callback) {
......@@ -257,11 +309,13 @@ Client.prototype._attachListeners = function (con) {
})
// delegate portalSuspended to active query
// eslint-disable-next-line no-unused-vars
con.on('portalSuspended', function (msg) {
self.activeQuery.handlePortalSuspended(con)
})
// deletagate emptyQuery to active query
// delegate emptyQuery to active query
// eslint-disable-next-line no-unused-vars
con.on('emptyQuery', function (msg) {
self.activeQuery.handleEmptyQuery(con)
})
......@@ -274,12 +328,14 @@ Client.prototype._attachListeners = function (con) {
// if a prepared statement has a name and properly parses
// we track that its already been executed so we don't parse
// it again on the same client
// eslint-disable-next-line no-unused-vars
con.on('parseComplete', function (msg) {
if (self.activeQuery.name) {
con.parsedStatements[self.activeQuery.name] = true
con.parsedStatements[self.activeQuery.name] = self.activeQuery.text
}
})
// eslint-disable-next-line no-unused-vars
con.on('copyInResponse', function (msg) {
self.activeQuery.handleCopyInResponse(self.connection)
})
......@@ -455,8 +511,9 @@ Client.prototype.query = function (config, values, callback) {
if (this.binary && !query.binary) {
query.binary = true
}
if (query._result) {
query._result._getTypeParser = this._types.getTypeParser.bind(this._types)
if (query._result && !query._result._types) {
query._result._types = this._types
}
if (!this._queryable) {
......
......@@ -15,11 +15,11 @@ var parse = require('pg-connection-string').parse // parses a connection string
var val = function (key, config, envVar) {
if (envVar === undefined) {
envVar = process.env[ 'PG' + key.toUpperCase() ]
envVar = process.env['PG' + key.toUpperCase()]
} else if (envVar === false) {
// do nothing ... use false
} else {
envVar = process.env[ envVar ]
envVar = process.env[envVar]
}
return config[key] ||
......@@ -66,6 +66,22 @@ var ConnectionParameters = function (config) {
this.fallback_application_name = val('fallback_application_name', config, false)
this.statement_timeout = val('statement_timeout', config, false)
this.query_timeout = val('query_timeout', config, false)
if (config.connectionTimeoutMillis === undefined) {
this.connect_timeout = process.env.PGCONNECT_TIMEOUT || 0
} else {
this.connect_timeout = Math.floor(config.connectionTimeoutMillis / 1000)
}
if (config.keepAlive === false) {
this.keepalives = 0
} else if (config.keepAlive === true) {
this.keepalives = 1
}
if (typeof config.keepAliveInitialDelayMillis === 'number') {
this.keepalives_idle = Math.floor(config.keepAliveInitialDelayMillis / 1000)
}
}
// Convert arg to a string, surround in single quotes, and escape single quotes and backslashes
......@@ -75,7 +91,7 @@ var quoteParamValue = function (value) {
var add = function (params, config, paramName) {
var value = config[paramName]
if (value) {
if (value !== undefined && value !== null) {
params.push(paramName + '=' + quoteParamValue(value))
}
}
......@@ -87,8 +103,9 @@ ConnectionParameters.prototype.getLibpqConnectionString = function (cb) {
add(params, this, 'port')
add(params, this, 'application_name')
add(params, this, 'fallback_application_name')
add(params, this, 'connect_timeout')
var ssl = typeof this.ssl === 'object' ? this.ssl : { sslmode: this.ssl }
var ssl = typeof this.ssl === 'object' ? this.ssl : this.ssl ? { sslmode: this.ssl } : {}
add(params, ssl, 'sslmode')
add(params, ssl, 'sslca')
add(params, ssl, 'sslkey')
......
......@@ -21,6 +21,7 @@ var Connection = function (config) {
config = config || {}
this.stream = config.stream || new net.Socket()
this._keepAlive = config.keepAlive
this._keepAliveInitialDelayMillis = config.keepAliveInitialDelayMillis
this.lastBuffer = false
this.lastOffset = 0
this.buffer = null
......@@ -47,17 +48,17 @@ var Connection = function (config) {
util.inherits(Connection, EventEmitter)
Connection.prototype.connect = function (port, host) {
var self = this
if (this.stream.readyState === 'closed') {
this.stream.connect(port, host)
} else if (this.stream.readyState === 'open') {
this.emit('connect')
}
var self = this
this.stream.on('connect', function () {
if (self._keepAlive) {
self.stream.setKeepAlive(true)
self.stream.setKeepAlive(true, self._keepAliveInitialDelayMillis)
}
self.emit('connect')
})
......@@ -101,6 +102,7 @@ Connection.prototype.connect = function (port, host) {
key: self.ssl.key,
passphrase: self.ssl.passphrase,
cert: self.ssl.cert,
secureOptions: self.ssl.secureOptions,
NPNProtocols: self.ssl.NPNProtocols
})
self.attachListeners(self.stream)
......@@ -190,6 +192,24 @@ Connection.prototype.password = function (password) {
this._send(0x70, this.writer.addCString(password))
}
Connection.prototype.sendSASLInitialResponseMessage = function (mechanism, initialResponse) {
// 0x70 = 'p'
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._send(0x70)
}
Connection.prototype._send = function (code, more) {
if (!this.stream.writable) {
return false
......@@ -217,9 +237,11 @@ Connection.prototype.parse = function (query, more) {
// normalize missing query names to allow for null
query.name = query.name || ''
if (query.name.length > 63) {
/* eslint-disable no-console */
console.error('Warning! Postgres only supports 63 characters for query names.')
console.error('You supplied', query.name, '(', query.name.length, ')')
console.error('You supplied %s (%s)', query.name, query.name.length)
console.error('This can cause conflicts and silent errors executing queries')
/* eslint-enable no-console */
}
// normalize null type array
query.types = query.types || []
......@@ -420,25 +442,53 @@ Connection.prototype.parseMessage = function (buffer) {
}
Connection.prototype.parseR = function (buffer, length) {
var code = 0
var code = this.parseInt32(buffer)
var msg = new Message('authenticationOk', length)
if (msg.length === 8) {
code = this.parseInt32(buffer)
if (code === 3) {
msg.name = 'authenticationCleartextPassword'
}
return msg
}
if (msg.length === 12) {
code = this.parseInt32(buffer)
if (code === 5) { // md5 required
msg.name = 'authenticationMD5Password'
msg.salt = Buffer.alloc(4)
buffer.copy(msg.salt, 0, this.offset, this.offset + 4)
this.offset += 4
switch (code) {
case 0: // AuthenticationOk
return msg
case 3: // AuthenticationCleartextPassword
if (msg.length === 8) {
msg.name = 'authenticationCleartextPassword'
return msg
}
break
case 5: // AuthenticationMD5Password
if (msg.length === 12) {
msg.name = 'authenticationMD5Password'
msg.salt = Buffer.alloc(4)
buffer.copy(msg.salt, 0, this.offset, this.offset + 4)
this.offset += 4
return msg
}
break
case 10: // AuthenticationSASL
msg.name = 'authenticationSASL'
msg.mechanisms = []
do {
var mechanism = this.parseCString(buffer)
if (mechanism) {
msg.mechanisms.push(mechanism)
}
} while (mechanism)
return msg
case 11: // AuthenticationSASLContinue
msg.name = 'authenticationSASLContinue'
msg.data = this.readString(buffer, length - 4)
return msg
case 12: // AuthenticationSASLFinal
msg.name = 'authenticationSASLFinal'
msg.data = this.readString(buffer, length - 4)
return msg
}
}
throw new Error('Unknown authenticationOk message type' + util.inspect(msg))
}
......@@ -554,7 +604,7 @@ Connection.prototype.parseE = function (buffer, length) {
msg = new Error(fields.M)
for (item in input) {
// copy input properties to the error
if (input.hasOwnProperty(item)) {
if (Object.prototype.hasOwnProperty.call(input, item)) {
msg[item] = input[item]
}
}
......
......@@ -50,15 +50,23 @@ module.exports = {
ssl: false,
application_name: undefined,
fallback_application_name: undefined,
parseInputDatesAsUTC: false,
// max milliseconds any query using this connection will execute for before timing out in error. false=unlimited
// max milliseconds any query using this connection will execute for before timing out in error.
// false=unlimited
statement_timeout: false,
// max miliseconds to wait for query to complete (client side)
query_timeout: false
// max milliseconds to wait for query to complete (client side)
query_timeout: false,
connect_timeout: 0,
keepalives: 1,
keepalives_idle: 0
}
var pgTypes = require('pg-types')
......
......@@ -49,7 +49,9 @@ if (typeof process.env.NODE_PG_FORCE_NATIVE !== 'undefined') {
if (err.code !== 'MODULE_NOT_FOUND') {
throw err
}
/* eslint-disable no-console */
console.error(err.message)
/* eslint-enable no-console */
}
module.exports.native = native
return native
......
......@@ -7,6 +7,7 @@
* README.md file in the root directory of this source tree.
*/
// eslint-disable-next-line
var Native = require('pg-native')
var TypeOverrides = require('../type-overrides')
var semver = require('semver')
......
......@@ -35,6 +35,7 @@ var NativeQuery = module.exports = function (config, values, callback) {
util.inherits(NativeQuery, EventEmitter)
var errorFieldMap = {
/* eslint-disable quote-props */
'sqlState': 'code',
'statementPosition': 'position',
'messagePrimary': 'message',
......@@ -130,21 +131,27 @@ NativeQuery.prototype.submit = function (client) {
// named query
if (this.name) {
if (this.name.length > 63) {
/* eslint-disable no-console */
console.error('Warning! Postgres only supports 63 characters for query names.')
console.error('You supplied', this.name, '(', this.name.length, ')')
console.error('You supplied %s (%s)', this.name, this.name.length)
console.error('This can cause conflicts and silent errors executing queries')
/* eslint-enable no-console */
}
var values = (this.values || []).map(utils.prepareValue)
// check if the client has already executed this named query
// if so...just execute it again - skip the planning phase
if (client.namedQueries[this.name]) {
if (this.text && client.namedQueries[this.name] !== this.text) {
const err = new Error(`Prepared statements must be unique - '${this.name}' was used for a different statement`)
return after(err)
}
return client.native.execute(this.name, values, after)
}
// plan the named query the first time, then execute it
return client.native.prepare(this.name, this.text, values.length, function (err) {
if (err) return after(err)
client.namedQueries[self.name] = true
client.namedQueries[self.name] = self.text
return self.native.execute(self.name, values, after)
})
} else if (this.values) {
......
......@@ -148,6 +148,10 @@ Query.prototype.submit = function (connection) {
if (typeof this.text !== 'string' && typeof this.name !== 'string') {
return new Error('A query must have either text or a name. Supplying neither is unsupported.')
}
const previous = connection.parsedStatements[this.name]
if (this.text && previous && this.text !== previous) {
return new Error(`Prepared statements must be unique - '${this.name}' was used for a different statement`)
}
if (this.values && !Array.isArray(this.values)) {
return new Error('Query values must be an array')
}
......@@ -190,7 +194,12 @@ Query.prototype.prepare = function (connection) {
}
if (self.values) {
self.values = self.values.map(utils.prepareValue)
try {
self.values = self.values.map(utils.prepareValue)
} catch (err) {
this.handleError(err, connection)
return
}
}
// http://developer.postgresql.org/pgdocs/postgres/protocol-flow.html#PROTOCOL-FLOW-EXT-QUERY
......@@ -213,6 +222,7 @@ Query.prototype.handleCopyInResponse = function (connection) {
connection.sendCopyFail('No source stream defined')
}
// eslint-disable-next-line no-unused-vars
Query.prototype.handleCopyData = function (msg, connection) {
// noop
}
......
......@@ -12,13 +12,14 @@ var types = require('pg-types')
// result object returned from query
// in the 'end' event and also
// passed as second argument to provided callback
var Result = function (rowMode) {
var Result = function (rowMode, types) {
this.command = null
this.rowCount = null
this.oid = null
this.rows = []
this.fields = []
this._parsers = []
this._types = types
this.RowCtor = null
this.rowAsArray = rowMode === 'array'
if (this.rowAsArray) {
......@@ -94,11 +95,9 @@ Result.prototype.addFields = function (fieldDescriptions) {
for (var i = 0; i < fieldDescriptions.length; i++) {
var desc = fieldDescriptions[i]