Skip to content

[1c] Rename select_for_update() to for_update() and add lock-mode variants - #87

Open
davegaeddert wants to merge 2 commits into
masterfrom
for-update
Open

[1c] Rename select_for_update() to for_update() and add lock-mode variants#87
davegaeddert wants to merge 2 commits into
masterfrom
for-update

Conversation

@davegaeddert

@davegaeddert davegaeddert commented Jul 23, 2026

Copy link
Copy Markdown
Member

Postgres exposes four row-level lock strengths, but the ORM only offered FOR UPDATE (via select_for_update(), with a no_key= kwarg for the NO KEY variant). This adds all four as explicit QuerySet methods and drops the old name.

API

Model.query.for_update()         # FOR UPDATE
Model.query.for_no_key_update()  # FOR NO KEY UPDATE
Model.query.for_share()          # FOR SHARE
Model.query.for_key_share()      # FOR KEY SHARE

All four take the same nowait=, skip_locked=, and of= options. The old select_for_update() method and its no_key= kwarg are removed (no alias/shim — renames are handled by the upgrade agent). nowait + skip_locked together still raises ValueError; using any lock outside a transaction still raises TransactionManagementError. Chaining two lock modes keeps the last one.

Internals

The four booleans on Query (select_for_update, select_for_update_nowait, select_for_update_skip_locked, select_for_update_of, select_for_no_key_update) collapse into a single lock_mode: LockMode | None (a Literal["update", "no_key_update", "share", "key_share"]) plus lock_nowait, lock_skip_locked, lock_of. The compiler passes lock_mode straight through to dialect.lock_sql(mode, ...), which maps the token to its actual SQL keywords via LOCK_MODE_SQL — the queryset itself never carries raw SQL, only the mode token.

Callers updated

  • plain-jobs worker claim query (.for_update(skip_locked=True))
  • plain-oauthserver token exchange / refresh (.for_update())
  • internal update_or_create and delete paths

Tests

New plain-postgres/tests/public/test_row_locking.py: each mode emits its clause, nowait/skip_locked/of render, nowait+skip_locked raises, last-mode-wins, lock-requires-transaction, and a behavioral check that a lock works inside atomic().

Docs

New "Row-level locking" section in the plain-postgres README under Transactions.

Expose all four Postgres row-lock strengths as QuerySet methods: for_update(),
for_no_key_update(), for_share(), and for_key_share(). Each takes the same
nowait=, skip_locked=, and of= options. The lock strength is carried on the
Query as the literal SQL clause (lock_mode) and emitted by the compiler,
replacing the select_for_update / select_for_no_key_update boolean flags.
QuerySet.for_update() and friends now pass a constrained LockMode
token ("update", "no_key_update", "share", "key_share") instead of
raw SQL text. dialect.lock_sql() maps the token to its SQL keywords,
keeping the locking-clause spelling in one place.
@davegaeddert
davegaeddert marked this pull request as ready for review July 23, 2026 17:19
@pullapprove5

pullapprove5 Bot commented Jul 23, 2026

Copy link
Copy Markdown
PASS: 1 review scope passed
Scope Progress
all 0/0

View in PullApprove

Next steps:

@davegaeddert davegaeddert changed the title Rename select_for_update() to for_update() and add lock-mode variants [merge 3] Rename select_for_update() to for_update() and add lock-mode variants Jul 23, 2026
@davegaeddert davegaeddert changed the title [merge 3] Rename select_for_update() to for_update() and add lock-mode variants [1c] Rename select_for_update() to for_update() and add lock-mode variants Jul 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant