diff --git a/lib/Connection.js b/lib/Connection.js index 600cbdd8a..09b115429 100644 --- a/lib/Connection.js +++ b/lib/Connection.js @@ -179,8 +179,10 @@ Connection.prototype.query = function(sql, values, cb) { if (!(typeof sql == 'object' && 'typeCast' in sql)) { query.typeCast = this.config.typeCast; } - - query.sql = this.format(query.sql, query.values); + if (!query.isFormatted) { + query.sql = this.format(query.sql, query.values); + query.isFormatted = true; + } return this._protocol._enqueue(query); }; diff --git a/lib/Pool.js b/lib/Pool.js index 5c589eb00..f024f819d 100644 --- a/lib/Pool.js +++ b/lib/Pool.js @@ -195,6 +195,10 @@ Pool.prototype.query = function (sql, values, cb) { conn.query(query); }); + if (!query.isFormatted) { + query.isFormatted = true; + query.sql = this.format(query.sql, query.values); + } return query; }; @@ -255,6 +259,13 @@ Pool.prototype.escapeId = function escapeId(value) { return mysql.escapeId(value, false); }; +Pool.prototype.format = function(sql, values) { + if (typeof this.config.connectionConfig.queryFormat == "function") { + return this.config.connectionConfig.queryFormat.call(this, sql, values, this.config.connectionConfig.timezone); + } + return mysql.format(sql, values, this.config.connectionConfig.stringifyObjects, this.config.connectionConfig.timezone); +}; + function spliceConnection(array, connection) { var index; if ((index = array.indexOf(connection)) !== -1) { diff --git a/lib/protocol/sequences/Query.js b/lib/protocol/sequences/Query.js index 0be2914f2..0e70c1c10 100644 --- a/lib/protocol/sequences/Query.js +++ b/lib/protocol/sequences/Query.js @@ -11,6 +11,7 @@ Util.inherits(Query, Sequence); function Query(options, callback) { Sequence.call(this, options, callback); + this.isFormatted = false; this.sql = options.sql; this.values = options.values; this.typeCast = (options.typeCast === undefined) diff --git a/test/unit/pool/test-query-return-formatted.js b/test/unit/pool/test-query-return-formatted.js new file mode 100644 index 000000000..b126ba633 --- /dev/null +++ b/test/unit/pool/test-query-return-formatted.js @@ -0,0 +1,60 @@ +var assert = require('assert'); +var common = require('../../common'); +var pool1 = common.createPool({port: common.fakeServerPort}); +var pool2 = common.createPool({port: common.fakeServerPort, queryFormat: queryFormat}); + + +function queryFormat(query, values, tz) { + if (!values) { + return query; + } + + var escape = this.escape.bind(this); + + return query.replace(/\:(\w+)/g, function (txt, key) { + if (values.hasOwnProperty(key)) { + return escape(values[key]); + } + return txt; + }); +} + +var server = common.createFakeServer(); + +server.listen(common.fakeServerPort, function (err) { + assert.ifError(err); + + + + // When the fix is applied, this query demonstrates that connection.query(), + // which gets invoked internally by pool.query(), will not re-format the query + // a second time. Re-formatting the query a second time in this case would cause + // an error when the question marks inside the string 'John ? J ?' are interpreted + // as placeholders for values. + + assert.equal(pool1.query('SELECT ?? FROM ?? WHERE id=? OR name=?', [['name', 'rdate'], 'dummytable', 1, "John ? J ?"]).sql, 'SELECT `name`, `rdate` FROM `dummytable` WHERE id=1 OR name=\'John ? J ?\''); + + + + // This one will will fail before the fix is applied + + assert.equal(pool2.query('SELECT :a1, :a2', { a1: 1, a2: 'two' }).sql, 'SELECT 1, \'two\''); + + + + // These last 2 will work before and after the fix is applied as there is no transformation performed + + assert.equal(pool2.query('SELECT :a1', []).sql, 'SELECT :a1'); + assert.equal(pool2.query('SELECT :a1').sql, 'SELECT :a1'); + + + + pool1.end(function (err) { + assert.ifError(err); + pool2.end(function (err) { + assert.ifError(err); + server.destroy(); + }); + }); + +});