From e943fd39b525d8a68f9369663ce23fa452a99450 Mon Sep 17 00:00:00 2001 From: Charmander <~@charmander.me> Date: Fri, 28 Feb 2020 03:22:08 -0800 Subject: [PATCH] Convert Query to an ES6 class The last missing `new` deprecation warning for pg 8. --- packages/pg/lib/compat/check-constructor.js | 22 -- packages/pg/lib/compat/warn-deprecation.js | 19 - packages/pg/lib/query.js | 374 ++++++++++---------- 3 files changed, 184 insertions(+), 231 deletions(-) delete mode 100644 packages/pg/lib/compat/check-constructor.js delete mode 100644 packages/pg/lib/compat/warn-deprecation.js diff --git a/packages/pg/lib/compat/check-constructor.js b/packages/pg/lib/compat/check-constructor.js deleted file mode 100644 index 5920633a0..000000000 --- a/packages/pg/lib/compat/check-constructor.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict' - -const warnDeprecation = require('./warn-deprecation') - -// Node 4 doesn’t support new.target. -let hasNewTarget - -try { - // eslint-disable-next-line no-eval - eval('(function () { new.target })') - hasNewTarget = true -} catch (error) { - hasNewTarget = false -} - -const checkConstructor = (name, code, getNewTarget) => { - if (hasNewTarget && getNewTarget() === undefined) { - warnDeprecation(`Constructing a ${name} without new is deprecated and will stop working in pg 8.`, code) - } -} - -module.exports = checkConstructor diff --git a/packages/pg/lib/compat/warn-deprecation.js b/packages/pg/lib/compat/warn-deprecation.js deleted file mode 100644 index 558275900..000000000 --- a/packages/pg/lib/compat/warn-deprecation.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict' - -const util = require('util') - -const dummyFunctions = new Map() - -// Node 4 doesn’t support process.emitWarning(message, 'DeprecationWarning', code). -const warnDeprecation = (message, code) => { - let dummy = dummyFunctions.get(code) - - if (dummy === undefined) { - dummy = util.deprecate(() => {}, message) - dummyFunctions.set(code, dummy) - } - - dummy() -} - -module.exports = warnDeprecation diff --git a/packages/pg/lib/query.js b/packages/pg/lib/query.js index 548380fe1..4fcfe391e 100644 --- a/packages/pg/lib/query.js +++ b/packages/pg/lib/query.js @@ -7,226 +7,220 @@ * README.md file in the root directory of this source tree. */ -var EventEmitter = require('events').EventEmitter -var util = require('util') -const checkConstructor = require('./compat/check-constructor') - -var Result = require('./result') -var utils = require('./utils') - -var Query = function (config, values, callback) { - // use of "new" optional in pg 7 - // eslint-disable-next-line no-eval - checkConstructor('Query', 'PG-QUERY-NEW', () => eval('new.target')) - if (!(this instanceof Query)) { return new Query(config, values, callback) } - - config = utils.normalizeQueryConfig(config, values, callback) - - this.text = config.text - this.values = config.values - this.rows = config.rows - this.types = config.types - this.name = config.name - this.binary = config.binary - // use unique portal name each time - this.portal = config.portal || '' - this.callback = config.callback - this._rowMode = config.rowMode - if (process.domain && config.callback) { - this.callback = process.domain.bind(config.callback) - } - this._result = new Result(this._rowMode, this.types) - - // potential for multiple results - this._results = this._result - this.isPreparedStatement = false - this._canceledDueToError = false - this._promise = null - EventEmitter.call(this) -} - -util.inherits(Query, EventEmitter) - -Query.prototype.requiresPreparation = function () { - // named queries must always be prepared - if (this.name) { return true } - // always prepare if there are max number of rows expected per - // portal execution - if (this.rows) { return true } - // don't prepare empty text queries - if (!this.text) { return false } - // prepare if there are values - if (!this.values) { return false } - return this.values.length > 0 -} - -Query.prototype._checkForMultirow = function () { - // if we already have a result with a command property - // then we've already executed one query in a multi-statement simple query - // turn our results into an array of results - if (this._result.command) { - if (!Array.isArray(this._results)) { - this._results = [this._result] +const { EventEmitter } = require('events') + +const Result = require('./result') +const utils = require('./utils') + +class Query extends EventEmitter { + constructor(config, values, callback) { + super() + + config = utils.normalizeQueryConfig(config, values, callback) + + this.text = config.text + this.values = config.values + this.rows = config.rows + this.types = config.types + this.name = config.name + this.binary = config.binary + // use unique portal name each time + this.portal = config.portal || '' + this.callback = config.callback + this._rowMode = config.rowMode + if (process.domain && config.callback) { + this.callback = process.domain.bind(config.callback) } this._result = new Result(this._rowMode, this.types) - this._results.push(this._result) + + // potential for multiple results + this._results = this._result + this.isPreparedStatement = false + this._canceledDueToError = false + this._promise = null + } + + requiresPreparation() { + // named queries must always be prepared + if (this.name) { return true } + // always prepare if there are max number of rows expected per + // portal execution + if (this.rows) { return true } + // don't prepare empty text queries + if (!this.text) { return false } + // prepare if there are values + if (!this.values) { return false } + return this.values.length > 0 + } + + _checkForMultirow() { + // if we already have a result with a command property + // then we've already executed one query in a multi-statement simple query + // turn our results into an array of results + if (this._result.command) { + if (!Array.isArray(this._results)) { + this._results = [this._result] + } + this._result = new Result(this._rowMode, this.types) + this._results.push(this._result) + } } -} -// associates row metadata from the supplied -// message with this query object -// metadata used when parsing row results -Query.prototype.handleRowDescription = function (msg) { - this._checkForMultirow() - this._result.addFields(msg.fields) - this._accumulateRows = this.callback || !this.listeners('row').length -} + // associates row metadata from the supplied + // message with this query object + // metadata used when parsing row results + handleRowDescription(msg) { + this._checkForMultirow() + this._result.addFields(msg.fields) + this._accumulateRows = this.callback || !this.listeners('row').length + } -Query.prototype.handleDataRow = function (msg) { - var row + handleDataRow(msg) { + let row - if (this._canceledDueToError) { - return - } + if (this._canceledDueToError) { + return + } - try { - row = this._result.parseRow(msg.fields) - } catch (err) { - this._canceledDueToError = err - return - } + try { + row = this._result.parseRow(msg.fields) + } catch (err) { + this._canceledDueToError = err + return + } - this.emit('row', row, this._result) - if (this._accumulateRows) { - this._result.addRow(row) + this.emit('row', row, this._result) + if (this._accumulateRows) { + this._result.addRow(row) + } } -} -Query.prototype.handleCommandComplete = function (msg, con) { - this._checkForMultirow() - this._result.addCommandComplete(msg) - // need to sync after each command complete of a prepared statement - if (this.isPreparedStatement) { - con.sync() + handleCommandComplete(msg, con) { + this._checkForMultirow() + this._result.addCommandComplete(msg) + // need to sync after each command complete of a prepared statement + if (this.isPreparedStatement) { + con.sync() + } } -} -// if a named prepared statement is created with empty query text -// the backend will send an emptyQuery message but *not* a command complete message -// execution on the connection will hang until the backend receives a sync message -Query.prototype.handleEmptyQuery = function (con) { - if (this.isPreparedStatement) { - con.sync() + // if a named prepared statement is created with empty query text + // the backend will send an emptyQuery message but *not* a command complete message + // execution on the connection will hang until the backend receives a sync message + handleEmptyQuery(con) { + if (this.isPreparedStatement) { + con.sync() + } } -} -Query.prototype.handleReadyForQuery = function (con) { - if (this._canceledDueToError) { - return this.handleError(this._canceledDueToError, con) - } - if (this.callback) { - this.callback(null, this._results) + handleReadyForQuery(con) { + if (this._canceledDueToError) { + return this.handleError(this._canceledDueToError, con) + } + if (this.callback) { + this.callback(null, this._results) + } + this.emit('end', this._results) } - this.emit('end', this._results) -} -Query.prototype.handleError = function (err, connection) { - // need to sync after error during a prepared statement - if (this.isPreparedStatement) { - connection.sync() - } - if (this._canceledDueToError) { - err = this._canceledDueToError - this._canceledDueToError = false - } - // if callback supplied do not emit error event as uncaught error - // events will bubble up to node process - if (this.callback) { - return this.callback(err) + handleError(err, connection) { + // need to sync after error during a prepared statement + if (this.isPreparedStatement) { + connection.sync() + } + if (this._canceledDueToError) { + err = this._canceledDueToError + this._canceledDueToError = false + } + // if callback supplied do not emit error event as uncaught error + // events will bubble up to node process + if (this.callback) { + return this.callback(err) + } + this.emit('error', err) } - this.emit('error', err) -} -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`) + submit(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') + } + if (this.requiresPreparation()) { + this.prepare(connection) + } else { + connection.query(this.text) + } + return null } - if (this.values && !Array.isArray(this.values)) { - return new Error('Query values must be an array') + + hasBeenParsed(connection) { + return this.name && connection.parsedStatements[this.name] } - if (this.requiresPreparation()) { - this.prepare(connection) - } else { - connection.query(this.text) + + handlePortalSuspended(connection) { + this._getRows(connection, this.rows) } - return null -} -Query.prototype.hasBeenParsed = function (connection) { - return this.name && connection.parsedStatements[this.name] -} + _getRows(connection, rows) { + connection.execute({ + portal: this.portal, + rows: rows + }, true) + connection.flush() + } + + prepare(connection) { + // prepared statements need sync to be called after each command + // complete or when an error is encountered + this.isPreparedStatement = true + // TODO refactor this poor encapsulation + if (!this.hasBeenParsed(connection)) { + connection.parse({ + text: this.text, + name: this.name, + types: this.types + }, true) + } -Query.prototype.handlePortalSuspended = function (connection) { - this._getRows(connection, this.rows) -} + if (this.values) { + try { + this.values = this.values.map(utils.prepareValue) + } catch (err) { + this.handleError(err, connection) + return + } + } -Query.prototype._getRows = function (connection, rows) { - connection.execute({ - portal: this.portal, - rows: rows - }, true) - connection.flush() -} + // 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) -Query.prototype.prepare = function (connection) { - var self = this - // prepared statements need sync to be called after each command - // complete or when an error is encountered - this.isPreparedStatement = true - // TODO refactor this poor encapsulation - if (!this.hasBeenParsed(connection)) { - connection.parse({ - text: self.text, - name: self.name, - types: self.types + connection.describe({ + type: 'P', + name: this.portal || '' }, true) - } - if (self.values) { - try { - self.values = self.values.map(utils.prepareValue) - } catch (err) { - this.handleError(err, connection) - return - } + this._getRows(connection, this.rows) } - // http://developer.postgresql.org/pgdocs/postgres/protocol-flow.html#PROTOCOL-FLOW-EXT-QUERY - connection.bind({ - portal: self.portal, - statement: self.name, - values: self.values, - binary: self.binary - }, true) - - connection.describe({ - type: 'P', - name: self.portal || '' - }, true) - - this._getRows(connection, this.rows) -} + handleCopyInResponse(connection) { + connection.sendCopyFail('No source stream defined') + } -Query.prototype.handleCopyInResponse = function (connection) { - connection.sendCopyFail('No source stream defined') + // eslint-disable-next-line no-unused-vars + handleCopyData(msg, connection) { + // noop + } } -// eslint-disable-next-line no-unused-vars -Query.prototype.handleCopyData = function (msg, connection) { - // noop -} module.exports = Query