Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion packages/pg/lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@
const defaults = require('./defaults')

const { isDate } = require('util/types')
const nodeUtils = require('util')

// see https://github.com/brianc/node-postgres/issues/3318
// invalid Date objects (e.g. new Date(undefined), whose .getTime() is NaN) used to be
// serialized as the meaningless string '0NaN-NaN-NaNTNaN:NaN:NaN.NaN+NaN:NaN', which
// Postgres cannot parse. This is deprecated in pg@8 (serialized as NULL, with a warning)
// and will throw an error in pg@9.
const invalidDateDeprecationNotice = nodeUtils.deprecate(
() => {},
'Passing an invalid Date (i.e. one whose .getTime() is NaN) as a query parameter is deprecated and will throw an error in pg@9.0. It currently serializes to NULL. See https://github.com/brianc/node-postgres/issues/3318'
)

function escapeElement(elementRepresentation) {
const escaped = elementRepresentation.replace(/\\/g, '\\\\').replace(/"/g, '\\"')
Expand Down Expand Up @@ -30,7 +41,15 @@ function arrayString(val) {
}
result += '\\\\x' + item.toString('hex')
} else {
result += escapeElement(prepareValue(item))
// prepareValue can itself return null (e.g. an invalid Date, see #3318),
// so re-check for null on its result rather than assuming item's nullness
// implies the prepared value's nullness.
const preparedItem = prepareValue(item)
if (preparedItem == null) {
result += 'NULL'
} else {
result += escapeElement(preparedItem)
}
}
}
result += '}'
Expand All @@ -54,6 +73,10 @@ const prepareValue = function (val, seen) {
return Buffer.from(val.buffer, val.byteOffset, val.byteLength)
}
if (isDate(val)) {
if (isNaN(val.getTime())) {
invalidDateDeprecationNotice()
return null
}
if (defaults.parseInputDatesAsUTC) {
return dateToStringUTC(val)
} else {
Expand Down
36 changes: 36 additions & 0 deletions packages/pg/test/unit/utils-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,42 @@ test('prepareValues: 1 BC date prepared properly', function () {
helper.resetTimezoneOffset()
})

test('prepareValues: invalid date never serializes to a NaN string and warns of deprecation', async function () {
// GIVEN an invalid Date param (.getTime() is NaN)
const invalidDate = new Date(undefined)
assert.ok(isNaN(invalidDate.getTime()), 'test setup: date must be invalid')

let sawDeprecationWarning = false
const onWarning = (warning) => {
if (warning.name === 'DeprecationWarning' && /invalid date/i.test(warning.message)) {
sawDeprecationWarning = true
}
}
process.on('warning', onWarning)

let out
try {
// WHEN it is serialized by the parameter path
out = utils.prepareValue(invalidDate)
// deprecation warnings are emitted asynchronously (process.emitWarning queues a
// nextTick/immediate task) - give the event loop a turn before asserting on it
await new Promise((resolve) => setImmediate(resolve))
} finally {
process.removeListener('warning', onWarning)
}

// THEN it never produces the "0NaN-NaN-NaNTNaN:NaN:NaN.NaN+NaN:NaN" garbage string
assert.notStrictEqual(out, '0NaN-NaN-NaNTNaN:NaN:NaN.NaN+NaN:NaN')
// AND it emits the blessed pg@8 deprecation warning (pg@9 will throw instead, see #3318)
assert.ok(sawDeprecationWarning, 'expected a DeprecationWarning to be emitted for an invalid date')
})

test('prepareValues: invalid date array element never serializes to a NaN string', function () {
const invalidDate = new Date('not a real date')
const out = utils.prepareValue([invalidDate])
assert.ok(!/NaN/.test(out), 'expected no NaN components in array-serialized invalid date, got: ' + out)
})

test('prepareValues: undefined prepared properly', function () {
const out = utils.prepareValue(void 0)
assert.strictEqual(out, null)
Expand Down
Loading