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
1227: add assertion for basic where clause values #5417
1227: add assertion for basic where clause values #5417
Conversation
6f7c51b
to
56dcbb3
Compare
lib/query/querybuilder.js
Outdated
assert( | ||
!isObject(value), | ||
'The values in where clause must not be object or array.' | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#1227 still applies if value
is 0
the integer.
Adding value.toString()
instead of value
when pushing onto the where statement stack should solve the issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
boolean value false
, if not passed as a string, yields the same result (talking about MySQL here, don't know whether it applies to #1227 ) https://www.db-fiddle.com/f/w8CJ5SAYEKwJqAXyoYPSwC/2
test/unit/query/builder.js
Outdated
mysql: 'select * from `users` where `id` = 1', | ||
pg: 'select * from "users" where "id" = 1', | ||
'pg-redshift': 'select * from "users" where "id" = 1', | ||
mssql: 'select * from [users] where [id] = 1', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of "= 1", it should be "= `1`" because for mysql, if = 0
is used, it may match other values. I'm not sure if it should be this way for other databases though, can someone versed in the other databases comment on this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tested this on db-fiddle, postgre does not allow comparisons between types with =
operator and expectedly returns an error, so that should be fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this is scary but it is rather a MySQL issue than a library concern. Or at least this is a separate case with the type coercion in MySQL while this pr handles the invalid usage of the where clause with objects as plain values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this is scary but it is rather a MySQL issue than a library concern.
I disagree that this is not a library concern. The purpose of knex is to abstract away database-specific quirks to allow safe query-building. Since mysql has the quirk of "= 0" matching all values, knex is responsible for escaping it to "= `0`" to represent the developer's intent.
Or at least this is a separate case with the type coercion in MySQL while this pr handles the invalid usage of the where clause with objects as plain values.
Should adding backticks around values for mysql be added in a different pr then? Two prs addressing the fundamental issue of knex not expecting non-strings through assertions and escaping seems unnecessarily complex, error-prone, and time-consuming compared to calling .toString()
on inputs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since mysql has the quirk of "= 0" matching all values, knex is responsible for escaping it to "=
0
" to represent the developer's intent.
@r2dev2 there are some issues with this.
1 - backticks for values is invalid syntax in MySQL, backticks should only be used for identifiers.
2 - when we consider quotes instead of backticks, even this is a bit contentious, I thought it would break with integers, but MySQL is full of surprises. = 0
is perfectly valid in case of integers, and for reasonable types, probably should be used.
Here are the related tests: https://www.db-fiddle.com/f/vytxD1kFa3xBiPf7TBADxq/4
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1 - backticks for values is invalid syntax in MySQL, backticks should only be used for identifiers
Ah, it appears I got a bit confused with previous experiments. It is the quote that should be used for values, not backtick. I added a quote escaping test to your fiddle (so it does = '0'
instead of = 0
for secret column) and that works properly.
Fiddle: https://www.db-fiddle.com/f/vytxD1kFa3xBiPf7TBADxq/5
Edit: I added db-fiddle tests demonstrating quote-escaping works for postgress and sqlite as well below
Postgres: https://www.db-fiddle.com/f/w8CJ5SAYEKwJqAXyoYPSwC/3
Sqlite: https://www.db-fiddle.com/f/w8CJ5SAYEKwJqAXyoYPSwC/4
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The change is not simple toString. In raw queries you need to convert bindings to string as well, while the collection of bindings could be either array or object (named bindings)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The change is not simple toString. In raw queries you need to convert bindings to string as well, while the collection of bindings could be either array or object (named bindings)
Yea, I tried calling .toString()
at various places in the codebase and it seems like calling .toString()
on all the bindings is not a solution as a binding could map to many different situations such as limit = ?
Perhaps only calling .toString()
for where =
clauses and other operators where the value can be quoted is a solution? One dirty way I did this was by adding
whereBasic(statement) {
if (['number', 'boolean', 'array', 'object'].includes(typeof statement.value)) {
statement.value = statement.value.toString();
}
return super.whereBasic(statement);
}
to lib/dialects/mysql/query/mysql-querycompiler.js
However, I'm sure there is a better way of doing this which someone more familiar with the knex codebase than me can think of.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR !
Check CI + tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for fixing this!
What's the timeline on having this merged in? |
Merging is blocked until @OlivierCavadenti is able to review it. |
}, | ||
}); | ||
|
||
it('should pass with plain values or with emtpy raw bindings', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: This should say "empty"
@kibertoad maybe we could release a version now ? |
Sure, I'll release it in the morning |
Cool |
@kibertoad Thank you. |
This fix breaks |
Meant to mitigate #1227
Checked also
whereJsonObject
and it throws the binding error if nested object is given. So this case is handled, but in other way. I can add the other assert there as well to do smth likeObject.values(object).some(isObject)
so the interface is unified somewhat.