Skip to content

feat: add Query Builder lockForUpdate()#10171

Open
memleakd wants to merge 3 commits intocodeigniter4:4.8from
memleakd:feat/query-builder-lock-for-update
Open

feat: add Query Builder lockForUpdate()#10171
memleakd wants to merge 3 commits intocodeigniter4:4.8from
memleakd:feat/query-builder-lock-for-update

Conversation

@memleakd
Copy link
Copy Markdown
Contributor

@memleakd memleakd commented May 7, 2026

Description

This PR proposes adding lockForUpdate() to the Query Builder for pessimistic write locking on supported database drivers.

The goal is to provide a small framework-native API for code that needs to read rows and then safely update them inside the same transaction.

Example:

$db->transaction(static function ($db) use ($accountId): void {
    $account = $db->table('accounts')
        ->where('id', $accountId)
        ->lockForUpdate()
        ->get()
        ->getRow();

    // Use $account to update the locked row safely...
});

Why

Some application workflows need to prevent concurrent writes around the same row while a transaction is deciding what to do next.

Common examples include:

  • checking and updating account balances
  • assigning the next pending job or record
  • preventing two workers from processing the same row
  • updating counters or limited-capacity resources
  • safely reading a row before applying a state transition

Without a Query Builder method, users either need raw SQL per database driver or custom helper code around otherwise builder-based queries.

Behavior

For drivers that support a trailing FOR UPDATE clause, lockForUpdate() appends it to the compiled SELECT.

SQLSRV is handled separately because SQL Server does not use trailing FOR UPDATE. Instead, it applies WITH (UPDLOCK, ROWLOCK) table hints to table references in the FROM clause.

Supported drivers in this PR:

  • MySQLi
  • Postgre
  • OCI8
  • SQLSRV

Unsupported or constrained behavior:

  • SQLite3 throws DatabaseException because SQLite does not support row-level SELECT ... FOR UPDATE.
  • OCI8 throws DatabaseException when lockForUpdate() is combined with limit() or offset(), because Oracle does not allow FOR UPDATE with row limiting.
  • SQLSRV throws DatabaseException when used without a FROM table or on subqueries.
  • SQLSRV does not hint joined tables in this implementation because joins are already stored as compiled SQL strings.

Notes

This PR intentionally keeps the first implementation narrow. It does not add sharedLock(), skipLocked(), nowait() or SQLSRV joined-table hinting.

Those could be considered later, but this PR focuses only on the most common pessimistic write-lock use case.

Testing

Added Query Builder coverage for:

  • generic FOR UPDATE compilation
  • reset and $reset = false behavior
  • countAllResults() suppressing lock clauses
  • OCI8 support and limit/offset restriction
  • SQLite3 unsupported-driver behavior
  • SQLSRV table hints
  • SQLSRV aliases, limits, joins, subqueries, and no-FROM queries

Checklist

  • Securely signed commits
  • Component(s) with PHPDoc blocks, only if necessary or adds value (without duplication)
  • Unit testing, with >80% coverage
  • User guide updated
  • Conforms to style guide

@github-actions github-actions Bot added the 4.8 PRs that target the `4.8` branch. label May 7, 2026
Comment thread user_guide_src/source/database/query_builder.rst
Comment thread system/Database/BaseBuilder.php Outdated
@github-actions github-actions Bot added the stale Pull requests with conflicts label May 7, 2026
memleakd added 3 commits May 7, 2026 18:34
Signed-off-by: memleakd <121398829+memleakd@users.noreply.github.com>
Signed-off-by: memleakd <121398829+memleakd@users.noreply.github.com>
Signed-off-by: memleakd <121398829+memleakd@users.noreply.github.com>
@memleakd memleakd force-pushed the feat/query-builder-lock-for-update branch from 2945994 to b7737be Compare May 7, 2026 16:34
@michalsn michalsn removed the stale Pull requests with conflicts label May 7, 2026
@codeigniter4 codeigniter4 deleted a comment from github-actions Bot May 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

4.8 PRs that target the `4.8` branch.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants