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
WIP: Implement "skipLocked()" and "noWait()" #2961
Conversation
src/query/builder.js
Outdated
// Causes error when acessing a locked row instead of waiting for it to be released. | ||
noWait() { | ||
const { _method } = this; | ||
if (!includes(['pluck', 'first', 'select'], _method)) { |
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.
any reasons why duplicate code can't be reused? do you envision it branching out eventually?
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'm actually not sure how to best handle this. I just copied this piece from the first() method, since all databases only allow "skip locked" and "nowait" on select queries. I could, however, just leave it up for the database driver to return the error directly to the calling code, without validating it first.
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.
@kibertoad handled in commit e9b152d, what do you think?
Edit: sorry, the commit id was wrong
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'm also thinking about moving the "_isSelectQuery" checks to the "forShare" and "forUpdate" methods
@kukiric Try mysql2 driver, it should support MySQL 8.0 |
The mysql2 driver worked with MariaDB, but it seems something is wrong with my setup since I still get a "client does not support authentication protocol" error when trying to use either driver with the MySQL 8.0 docker image. MySQL 5.7 works, but it doesn't support these statements. Also, I just tested this with MariaDB, and I've found out that it supports "no wait", but not "skip locked" like MySQL. How should the tests be handled in this scenario? |
Pinging @kibertoad Should I drop MySQL + MariaDB support for this PR? The CI set up for this repo doesn't run a supported database version, and it's really inconvenient that MariaDB doesn't support this feature. PostgreSQL works like a charm, though. |
@kukiric Better to disable tests on these DBs temporary with ToDo to enable again when we use newer DB versions for testing. Extra bonus points if you could open an issue to add MySQL 8.0 to our test set. |
@kibertoad I'll open the issue later today. Currently, Travis doesn't support MySQL 8.0 directly, nor a recent enough Ubuntu version that can install it via apt. Also, both the mysql and mysql2 packages for node lack support for the new password algorithm that they added in 8.0.4. So for now, this is Postgres-only, with noWait() also incidentally working on newer versions of MariaDB. |
@kukiric Heya! Any chance this PR could be finished? (skipping all the incompatible DBs) |
Sorry, I got busy and forgot to finish the PR, mainly because I worked around it with a raw query at my company. I'll wrap it up with docs + error messages on other DB drivers this weekend. |
Awesome! |
Reasoning: while this feature is useful, it's untestable without the proper framework for testing on MySQL 8.0, and testing MySQL and MariaDB separately. See: #2961
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.
Well, you could have kept the functionality and only skipped tests, no?
The removed functionality is just a pair of functions that return the strings to be appended to the SQL (in exactly the same format as Postgres), so it can be easily put back in when tests are in. It's fine by me if you want to re-enable it before merging, but I don't like to lead people into "here be dragons" territory when dealing with databases. |
I guess I'll leave that up to the documentation. I'm going to write the the first draft and make a PR for it as well. |
Documentation PR is up at knex/documentation#186 |
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.
Most of the code paths throwing errors seem to be missing the tests. Those should be tested as well as the happy paths.
@kukiric Would you consider finishing this, now that MySQL 8 is used in CI by default? |
Sure! I kinda forgot about it as I solved the original issue with a raw query and moved onto other things, but I can pick this up again. Should I update the branch by merging or rebasing master, or leave it alone until it's done? |
@kukiric Either approach works, we squash PRs anyway, so whatever is more convenient for you. |
(For real this time, without squashing into a mess of merge conflicts)
MySQL support is back, but it seems to not be locking rows correctly with a limit clause on the latest (Docker-published) version, so the test will always fail. Edit: https://stackoverflow.com/questions/5694658/how-many-rows-will-be-locked-by-select-order-by-xxx-limit-1-for-update - This is exactly what I'm getting here, so it looks like I'll have to re-write the test for MySQL (unless I'm using it incorrectly, which is also likely) |
src/query/builder.js
Outdated
'.noWait() can only be used after a call to .forShare() or .forUpdate()!' | ||
); | ||
} | ||
if (this._single.waitMode === 'skipLocked') { |
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.
probably these could be constants?
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 agree, but I can't find a good place for query builder constants. Is it worth it to create one now, or would this be more fitting for a future refactor?
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.
We don't get much refactoring PRs, as people have no good reason for submitting these :D. Would be awesome if you created something for 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.
Ok, I'll try
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.
Made it a separate module in c0b1cd4, how do you like it?
I'm OK with merging it after code review comments are addressed. |
Thanks a lot! |
Feature is available in 0.18.4 |
@kukiric Apparently there is a problem with tests,
Is never returned or awaited, so promise ends up being an unhandled rejection behind the scenes. Any chance you could fix either the test or the implementation, whichever is needed to make sure it passes? (simple awaiting is not enough, promise does end up reaching unexpected state) You can see error here: https://travis-ci.org/tgriesser/knex/jobs/572000778 And if you want rejected promises to fail your tests, you can use #3393 as a base. |
Sure, give me a day to submit a PR to fix it. |
@kukiric Much appreciated! |
@kibertoad PR submitted: #3398 |
This PR aims to resolve issue #1937.
It adds two chainable methods into the query builder: skipLocked() and noWait(). They can only be used in a select-like query, and only after a lock mode has been specified with forShare() or forUpdate(). These methods simply append the requested lock mode onto the query, leaving the actual lock handling to the RDMS.
The main use case for these methods is to create a job queue-type system in the database, where each job can only be taken by a single process or thread. Without them, it's possible to create deadlocks with multiple consumers taking exclusive locks on the same rows, when skipping locked rows would otherwise be more ideal.
Currently, this only works with the PostgreSQL dialect. I wanted to add support for MySQL and MariaDB as well, but I can't get the test suite to connect to the latest version of those databases (the client drivers don't seem to support the new authentication mode added in MySQL 8.0), and older versions don't support these statements.
As a WIP Pull Request, I'm open to comments and suggestions. Help is also appreciated.
TODO: