diff --git a/lib/connection.js b/lib/connection.js index 56153d28eb..467405593f 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -550,6 +550,8 @@ class Connection extends EventEmitter { if (convertNamedPlaceholders === null) { convertNamedPlaceholders = require('named-placeholders')(); } + // Store list of named parameters in the order they appear in the sql so we can reference against value array if needed + options.names = options.sql.match(/:\w*/g); unnamed = convertNamedPlaceholders(options.sql, options.values); options.sql = unnamed[0]; options.values = unnamed[1]; @@ -640,25 +642,24 @@ class Connection extends EventEmitter { 'Bind parameters must be array if namedPlaceholders parameter is not enabled' ); } - options.values.forEach(val => { - //If namedPlaceholder is not enabled and object is passed as bind parameters - if (!Array.isArray(options.values)) { - throw new TypeError( - 'Bind parameters must be array if namedPlaceholders parameter is not enabled' - ); - } - if (val === undefined) { - throw new TypeError( - 'Bind parameters must not contain undefined. To pass SQL NULL specify JS null' - ); + if(options.values.some(v => typeof v === 'function')) { + throw new TypeError('Bind parameters must not contain function(s). To pass the body of a function as a string call .toString() first'); + } + if (options.names) { + // Create deduped list of parameter names that are undefined by cross referencing the named list with the values array + const undefinedParams = options.names.filter((n, i, a) => a.indexOf(n) === i && options.values[i] === undefined).join(', '); + if(undefinedParams.length) { + throw new TypeError(`Bind parameters must not contain undefined (parameters ${undefinedParams}). To pass SQL NULL specify JS null`); } - if (typeof val === 'function') { - throw new TypeError( - 'Bind parameters must not contain function(s). To pass the body of a function as a string call .toString() first' - ); + } else { + // Create list of the indexes of any undefined values + const undefinedIndexes = options.values.reduce((acc, v, i) => v === undefined ? acc.concat(i) : acc, []).join(', '); + if (undefinedIndexes.length) { + throw new TypeError(`Bind parameters must not contain undefined (indexes ${undefinedIndexes}). To pass SQL NULL specify JS null`); } - }); + } } + const executeCommand = new Commands.Execute(options, cb); const prepareCommand = new Commands.Prepare(options, (err, stmt) => { if (err) { diff --git a/package.json b/package.json index 790a474ce5..6e7f74b03a 100644 --- a/package.json +++ b/package.json @@ -90,4 +90,4 @@ "urun": "0.0.8", "utest": "0.0.8" } -} +} \ No newline at end of file diff --git a/test/integration/connection/test-execute-undefined-errors.js b/test/integration/connection/test-execute-undefined-errors.js new file mode 100644 index 0000000000..d0fc3e5287 --- /dev/null +++ b/test/integration/connection/test-execute-undefined-errors.js @@ -0,0 +1,56 @@ +'use strict'; + +const createConnection = require('../../common.js').createConnection; +const test = require('utest'); +const assert = require('assert'); + +test('Test error messages for undefined parameters are correctly reported ', { + 'Error message lists named parameter that was undefined': () => { + const conn = createConnection({namedPlaceholders: true}); + try { + conn.execute({sql: 'select id, email from test_table where id = :id and email = :email and name = :name'}, {email: 'test@email.com'}, err => { + assert.fail(`Expected error to be thrown, but got ${err}`); + }); + } catch (err) { + if (err.message.indexOf("(parameters :id, :name)") === -1) { + assert.fail( + `Expected error message to list undefined named parameter (:id):\n ${err}` + ); + } + } finally { + conn.end(); + } + }, + 'Error message lists undefined named parameter once if it appears multiple times in the query': () => { + const conn = createConnection({namedPlaceholders: true}); + try { + conn.execute({sql: 'select id, email from test_table where id = :id and created < :day and created > :day - interval 7 day'}, {}, err => { + assert.fail(`Expected error to be thrown, but got ${err}`); + }); + } catch (err) { + if (err.message.indexOf("(parameters :id, :day)") === -1) { + assert.fail( + `Expected error message to list undefined named parameter (:id):\n ${err}` + ); + } + } finally { + conn.end(); + } + }, + 'Error message lists parameter indexes that were undefined': () => { + const conn = createConnection({namedPlaceholders: true}); + try { + conn.execute({sql: 'select id, email from test_table where id = ?, email = ?, name = ?'}, [undefined, 'test@test.com', undefined], err => { + assert.fail(`Expected error to be thrown, but got ${err}`); + }); + } catch (err) { + if (err.message.indexOf("(indexes 0, 2)") === -1) { + assert.fail( + `Expected error message to list undefined parameter indexes (0,2):\n ${err}` + ); + } + } finally { + conn.end(); + } + } +}); \ No newline at end of file