Skip to content

feat: Added 'FOR UPDATE SKIP LOCKED' to SelectQuery#243

Open
bbldn wants to merge 1 commit intocycle:2.xfrom
bbldn:feature/expose-skip-locked
Open

feat: Added 'FOR UPDATE SKIP LOCKED' to SelectQuery#243
bbldn wants to merge 1 commit intocycle:2.xfrom
bbldn:feature/expose-skip-locked

Conversation

@bbldn
Copy link
Contributor

@bbldn bbldn commented Mar 2, 2026

  • Added 'FOR UPDATE SKIP LOCKED' and 'FOR UPDATE NOWAIT' to SelectQuery

🤔 Why?

Issues240

@bbldn bbldn requested review from a team as code owners March 2, 2026 00:35
Copy link
Member

@roxblnfk roxblnfk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's time to increase min PHP version to 8.1 and use enums.

/**
 * Row-level lock strength.
 * PG-specific modes fallback to Update/Share on other drivers.
 */
enum LockMode
{
    /** Exclusive lock. Blocks UPDATE, DELETE, SELECT FOR UPDATE/SHARE. */
    case Update;

    /** Shared lock. Blocks UPDATE, DELETE, SELECT FOR UPDATE. Allows FOR SHARE. */
    case Share;

    /**
     * PG only. Like Update, but doesn't block FOR KEY SHARE.
     * Use when not modifying PK/FK columns.
     * Fallback: Update (MySQL, MSSQL), noop (SQLite).
     */
    case NoKeyUpdate;

    /**
     * PG only. Weakest lock — blocks only DELETE and PK/FK updates.
     * Fallback: Share (MySQL, MSSQL), noop (SQLite).
     */
    case KeyShare;
}

/**
 * Lock wait behavior when row is already locked.
 */
enum LockBehavior
{
    /** Default. Block until lock is released. */
    case Wait;

    /**
     * Fail immediately if row is locked.
     * PG: NOWAIT, MySQL: NOWAIT, MSSQL: SET LOCK_TIMEOUT 0.
     * SQLite: NotSupportedException.
     */
    case NoWait;

    /**
     * Skip locked rows, return only unlocked. Useful for job queues.
     * PG: SKIP LOCKED, MySQL: SKIP LOCKED (8.0+), MSSQL: READPAST hint.
     * SQLite: NotSupportedException.
     */
    case SkipLocked;
}

interface SelectQuery
{
    // ...

    /**
     * Add row-level locking clause.
     *
     * @param LockMode $mode Lock strength. SQLite: ignored, uses BEGIN IMMEDIATE.
     * @param LockBehavior $behavior Wait strategy. SQLite: only Wait supported.
     */
    public function forUpdate(
        LockMode $mode = LockMode::Update,
        LockBehavior $behavior = LockBehavior::Wait,
    ): self;
}

// Usage
$query->forUpdate(LockMode::Update, LockBehavior::SkipLocked);

Feel free to use enums in this PR, the PHP version must be bumped separately.

@bbldn bbldn requested a review from a team as a code owner March 2, 2026 23:48
@bbldn bbldn force-pushed the feature/expose-skip-locked branch from fcab654 to ca3dbb6 Compare March 2, 2026 23:53
@bbldn bbldn force-pushed the feature/expose-skip-locked branch from ca3dbb6 to 1cfa7f8 Compare March 2, 2026 23:58
@bbldn
Copy link
Contributor Author

bbldn commented Mar 3, 2026

Ready. Maybe we should add method lock($mode, $behavior) and deprecate forUpdate()? And rename field $forUpdate to $lock?

@roxblnfk
Copy link
Member

roxblnfk commented Mar 3, 2026

I think this API will be better:

->forUpdate(LockBehavior $behavior = LockBehavior::Wait): static
->forShare(LockBehavior $behavior = LockBehavior::Wait): static

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.

2 participants