Repository navigation
feat: add lock(value) to the query builder for SKIP LOCKED claims - #346
Open
claude-tmgbedu wants to merge 2 commits into
Open
claude-tmgbedu wants to merge 2 commits into
claude-tmgbedu wants to merge 2 commits into
Conversation
Why: - Concurrent workers claiming pending rows waited on each other under FOR UPDATE. - The builder could not render FOR UPDATE SKIP LOCKED, so claims fell back to raw SQL. How: - QueryBuilder.lock(value=True) follows Laravel: True is the update lock, False the shared lock, a string is the dialect clause rendered verbatim. - lock_for_update() and shared_lock() delegate to it; Model.lock proxies it. - process_locks() renders unknown lock strings verbatim; SQLite renders no lock. - Grammar snapshots on four dialects and a Postgres test where concurrent fetch-then-update claims take disjoint rows. - ADR 063 and its index row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCMGYMYVHdzHAFaDeRWDyD
Why: - A claim can also be a single builder-built UPDATE ... WHERE id = (SELECT ... FOR UPDATE SKIP LOCKED), with no RETURNING and no raw SQL. - _compile_update kept a docstring after #345 touched it, against the no-docstring rule. How: - A Postgres test holds one row in another transaction and asserts that update() through a locked where subquery claims exactly one other row; with a plain lock() subquery the update blocks and the test fails. - Remove the _compile_update docstring. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCMGYLLSTz3VTHCp6XygtL
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Author
Strict review (task #3876): APPROVE at head
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part 2 of task #3864 (task #3876). Follows #344 (ADR 061) and #345 (ADR 062), both released in v0.74.0.
What
QueryBuilder.lock(value=True), as in Laravel.Truetakes the update lock,Falsethe shared lock, and a string is a trusted, code-literal lock clause rendered verbatim, such asFOR UPDATE SKIP LOCKEDor MSSQLWITH(ROWLOCK, UPDLOCK, READPAST). SQLite renders no lock.lock_for_update()andshared_lock()delegate to it, andModel.lock(value)proxies it.BaseGrammar.process_locks()renders an unknown lock string verbatim.SQLiteGrammar.process_locks()renders nothing.lock(value)on the query builder, with its index row after 061 and 062._compile_updatedocstring is removed (a fix: compile update() against the updated table #345 review follow-up).The lock string is SQL, not a binding. ADR 063 requires it to be a literal in application code, never user input.
Not included
UPDATE ... RETURNING. ADR 062 rejected it and it was cut from fix: compile update() against the updated table #345. The other fix: compile update() against the updated table #345 follow-ups were aboutreturningand do not apply tomain.Tests
tests/masoniteorm/query/grammars/test_lock_value_grammar.pysnapshots SQLite, MySQL, Postgres and MSSQL. It coverslock()/lock(True)/lock(False), a verbatim string afterORDER BY/LIMIT(after the table withTOPon MSSQL), a lockedwhere_insubquery, and the last call winning.tests/masoniteorm/postgres/test_postgres_skip_locked.pyruns real concurrent transactions:lock("FOR UPDATE SKIP LOCKED"), update each by id and hold their transaction. They claim 2, 2 and 1 of 5 rows, and no row twice.lock()worker waits for the holder.UPDATE ... WHERE "id" = (SELECT "id" ... LIMIT 1 FOR UPDATE SKIP LOCKED)claims a free row while another transaction holds one. Mutation check: with a plainlock()subquery the update blocks and the test fails.Locally, with a
postgres:17container matching docker-compose (POSTGRES_PORT=15433), the full suite gives 3830 passed, 10 skipped and 380 subtests passed, with coverage at 93.21%. Ruff check and format are clean, and Basedpyright reports 0 errors on the touched modules.The branch is commit 76bccc5 (the approved
lock(value)design) rebased onto v0.74.0, with the ADR index rows 061, 062 and 063 kept in order, plus one follow-up commit.Not merged or released; the user merges and releases framework PRs.
🤖 Generated with Claude Code
https://claude.ai/code/session_01VCMGYLLSTz3VTHCp6XygtL