Skip to content

Get formatted query from Pool.query() method #991

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

surfbuds
Copy link

Solution to resolve #984

var p= Pool.query(sql,data, function (){});
console.log(p.sql); // p.sql is not formatted

This commit changes the behavior of Pool.query() method so as to return the formatted SQL statement as expected, as in Connection.query().

Preventing side effects

Since Pool.query() method uses Connection.query() internally, Connection.query() needs to know if the SQL statement has already been formatted so as to prevent double formatting and corrupting the query.

A new Query object property isFormatted keeps track of formatting status.

Additional method

The Pool.prototype.format method was added to Pool module. Similar to Connection.prototype.format method and modified to work inside the Pool class.

Testing with error inducing query

SELECT name, rdate FROM contacts WHERE id = 1 OR name = 'John ? J ?'

Using the Pool.query method this query could be passed this way

var columns = ['name', 'rdate'];
var userId = 1;
var userName="John ? J ?";
var sql_query = 'SELECT ?? FROM ?? WHERE id=? OR name=?';

var P = pool.query(search, [columns, 'contacts', userId, userName], function(err, rows, fields) {
        if (err) throw err;
        // do whatever
});

In the absence of the isFormatted property, if we allow the Pool.query() method to format (escape and replace placeholders) a query before it is passed to the Connection.query method, the latter will try to reformat the query a second time by replacing the ? inside 'John ? J ?' with another copy of the same string, resulting in an SQL error.

The addition of the isFormatted property to the Query object is crucial for this fix to work. It is undefined by default and evaluates to false in a boolean operation. Therefore no change was made to the lib/protocol/sequences/Query.js file. The property is set after Connection.createQuery() method returns a Query object.

Note: Connection.createQuery() doesn't return a new Query object if it is passed a Query object, thus conserving the isFormatted property if already set.

Address issue mysqljs#984

var p=Pool.query( sql, vdata, function() {});
console.log(p.sql); // unformatted

This commit changes the behavior and returns the formatted sql statement
immediately.

Also prevents side effect of Connection.query() re-formatting the SQL
statement so as not to corrupt the SQL statement.

A new connection config variable, isFormatted, keeps track of query
formatting.

The Pool.prototype.format method added. Similar to
Connection.prototype.format, except adapted to use
config.connectionConfig.
The format() method uses mysql.format() so as not to require SqlString
module.
### Addresses issue mysqljs#984
```javascript
var p= Pool.query(sql,data, function (){});
console.log(p.sql); // un-formatted sql statement
```
This commit changes the behavior of `Pool.query()` method so as to
return the **formatted SQL statement** as expected, as in
`Connection.query()`.
#### Preventing side effects
Since `Pool.query()` method uses `Connection.query()` internally,
`Connection.query()` needs to know if  the SQL statement has already
been formatted so as to prevent double formatting and corrupting the
query.

A new connection config variable `isFormatted` keeps track of formatting
status.
#### Additional method
The `Pool.prototype.format` method added to Pool module. Similar to
`Connection.prototype.format` method and modified to work inside Pool
class.
redundant assignment operation moved inside a conditional
lib/Pool.js Outdated
@@ -171,6 +171,8 @@ Pool.prototype.end = function (cb) {
Pool.prototype.query = function (sql, values, cb) {
var query = Connection.createQuery(sql, values, cb);

query.isFormatted = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this so it always exists, otherwise we are altering the hidden class and fracturing it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i.e. it should be set within createQuery

@dougwilson
Copy link
Member

Thanks for this! I won't be able to merge it until there are tests, though. If I have time, I can add some tests, but if you can first, that would be great :)

@surfbuds
Copy link
Author

@dougwilson I will have to rewrite my tests to make them easily replicable. I used real tables and data for my test so will need to make self sufficient queries with no table dependency. Do you have some recommendation for the kind of tests you'd like to see?

@dougwilson
Copy link
Member

The minimum is a test for the issue you referenced that fails without this and passes with this. For patterns, there are lots and lots of tests to look at in the tests folder :)

If you were wondering, the reason I cannot put things in without tests is because if they don't go in at the same time, they get forgotten and eventually something regresses the fix.

Use 2 Pool instances to test if formatted SQL query is returned from the
pool.query() method.

pool1 uses default transformation and pool2 applies a custom
queryFormat.
Moved default Query property isFormatted from Pool module to Query
module.

When a Query object is first instantiated, isFormatted is false by
default.
@surfbuds
Copy link
Author

@dougwilson I've included a unit test file: test/unit/pool/test-query-return-formatted.js which passes the test when the fix is applied.

Also I moved the isFormatted property default value setting to its own Class, which is Query. The Query object is returned by the Connection.createQuery() method. The isFormatted property is now always set to false by default, and can be changed by both the Pool.query() and Connection.query() methods when they format the query.

@dougwilson dougwilson self-assigned this Feb 16, 2015
@dougwilson dougwilson added this to the 2.6 milestone Feb 16, 2015
@sidorares
Copy link
Member

I'm not sure if I'm missing something but it looks like one could just move formatting to createQuery() (maybe if (!(typeof sql == 'object' && 'typeCast' in sql)) { code as well as its duplicated in pool and connection) - it's simple sync code, no need for "isFormatted" flag

@dougwilson
Copy link
Member

Yep, I agree with @sidorares if it's possible :) Also, if you're going to introduce a new pool.format method, it probably needs to be added to the documentation at least.

@surfbuds
Copy link
Author

@sidorares and @dougwilson if so shouldn't the call to this.format() be just above return new Query(options, bindToCurrentDomain(cb)); in Connection.createQuery ?

One problem seems to be that Connection.createQuery() is a static method not an instance method like Connection.prototype.format(). It could still call the format method by binding createQuery() to the instance of Connection but it seems like it would require more code refactoring overall, especially if createQuery() is used or will be used in other places.

What do you think?

@sidorares
Copy link
Member

I think createQuery() should be instance method as well like format() is, to be able to see changes to instance config

@surfbuds
Copy link
Author

@sidorares The problem with making createQuery() an instance method is that it is used by other modules or script files expecting it to be a static method since they don't have a Connection instance available at the time the Query object is created.

  • test/unit/query/test-stream-before-queue.js (the test would fail)
  • index.js
  • lib/Pool.js

Any suggestion?

@dougwilson
Copy link
Member

In the end, it's really impossible to make this solution look good without it not being backwards-compatible :)

@@ -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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to think about if it's acceptable that the context of the queryFormat call is not going to be a Connection object in these cases and if this is going to be an issue.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did find a solution to keep createQuery as a static method of Connection and provide the right context for instance config, which keeps things backward compatible. I'll commit the changes later from another machine.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it take care of this context issue, though?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Otherwise there is no need to add a new commit, really. I've already adjusted your commit to make it an instance method (still under your name). The problem right now is backwards compatibility with this function context.

@dougwilson dougwilson force-pushed the master branch 3 times, most recently from 5847ec8 to be37e88 Compare May 8, 2016 05:09
@dougwilson dougwilson force-pushed the master branch 3 times, most recently from 65c4c0c to fa96a75 Compare June 8, 2016 02:00
@dougwilson dougwilson force-pushed the master branch 2 times, most recently from 638d79d to 88bade0 Compare June 29, 2017 18:13
@dougwilson dougwilson force-pushed the master branch 2 times, most recently from 946727b to 37fbbdd Compare November 18, 2018 02:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

Successfully merging this pull request may close these issues.

query.sql unformatted until query sent to connection
3 participants