diff --git a/packages/pg/lib/utils.js b/packages/pg/lib/utils.js index 638b43970..55dc6824d 100644 --- a/packages/pg/lib/utils.js +++ b/packages/pg/lib/utils.js @@ -143,7 +143,8 @@ function dateToStringUTC(date) { function normalizeQueryConfig(config, values, callback) { // can take in strings or config objects - config = typeof config === 'string' ? { text: config } : config + // Copy config so normalization does not mutate the caller's object. + config = typeof config === 'string' ? { text: config } : cloneQueryConfig(config) if (values) { if (typeof values === 'function') { config.callback = values @@ -157,6 +158,13 @@ function normalizeQueryConfig(config, values, callback) { return config } +function cloneQueryConfig(config) { + if (config == null) { + return config + } + return Object.defineProperties(Object.create(Object.getPrototypeOf(config)), Object.getOwnPropertyDescriptors(config)) +} + // Ported from PostgreSQL 9.2.4 source code in src/interfaces/libpq/fe-exec.c const escapeIdentifier = function (str) { return '"' + str.replace(/"/g, '""') + '"' diff --git a/packages/pg/test/unit/client/simple-query-tests.js b/packages/pg/test/unit/client/simple-query-tests.js index 8cc550830..bb1d54a89 100644 --- a/packages/pg/test/unit/client/simple-query-tests.js +++ b/packages/pg/test/unit/client/simple-query-tests.js @@ -150,4 +150,18 @@ test('executing query', function () { ) }) }) + + test('reusing a config object across calls', function () { + // Regression test for https://github.com/brianc/node-postgres/issues/2651. + test('does not leak callback state into a later promise-style call', function () { + const client = helper.client() + const config = { text: 'SELECT $1', values: [1] } + + client.query(config, function () {}) + const result = client.query(config) + + assert.ok(result instanceof Promise, 'expected client.query() to return a Promise') + result.catch(() => {}) + }) + }) }) diff --git a/packages/pg/test/unit/utils-tests.js b/packages/pg/test/unit/utils-tests.js index 5f75f6c2d..5274a497e 100644 --- a/packages/pg/test/unit/utils-tests.js +++ b/packages/pg/test/unit/utils-tests.js @@ -33,6 +33,41 @@ test('normalizing query configs', function () { assert.deepEqual(config, { text: 'TEXT', values: [10], callback: callback }) }) +test('normalizeQueryConfig does not mutate the passed-in config object', function () { + // Regression test for https://github.com/brianc/node-postgres/issues/2651. + const original = { text: 'TEXT' } + const callback = function () {} + + const normalized = utils.normalizeQueryConfig(original, [10], callback) + + assert.equal(original.callback, undefined) + assert.equal(original.values, undefined) + assert.deepEqual(normalized, { text: 'TEXT', values: [10], callback: callback }) +}) + +test('normalizeQueryConfig preserves inherited config properties', function () { + class QueryConfig { + constructor() { + this._text = 'TEXT' + } + + get text() { + return this._text + } + } + + const original = new QueryConfig() + const callback = function () {} + + const normalized = utils.normalizeQueryConfig(original, [10], callback) + + assert.equal(original.callback, undefined) + assert.equal(original.values, undefined) + assert.equal(normalized.text, 'TEXT') + assert.deepEqual(normalized.values, [10]) + assert.equal(normalized.callback, callback) +}) + test('prepareValues: buffer prepared properly', function () { const buf = Buffer.from('quack') const out = utils.prepareValue(buf)