Skip to content

Support DELETE with LIMIT and ORDER BY#365

Open
JanJakes wants to merge 1 commit intotrunkfrom
delete-limit-order-by
Open

Support DELETE with LIMIT and ORDER BY#365
JanJakes wants to merge 1 commit intotrunkfrom
delete-limit-order-by

Conversation

@JanJakes
Copy link
Copy Markdown
Member

@JanJakes JanJakes commented Apr 17, 2026

Summary

Adds support for single-table DELETE statements with ORDER BY and/or LIMIT clauses, which SQLite rejects as a syntax error unless it was compiled with SQLITE_ENABLE_UPDATE_DELETE_LIMIT (not the case for standard builds, including the one bundled with WordPress Playground).

The fix mirrors the existing UPDATE LIMIT rewrite: when ORDER BY or LIMIT is present, the statement is translated to drive deletion off a rowid subquery.

-- MySQL
DELETE FROM t WHERE c = 1 ORDER BY c LIMIT 10

-- Translated SQLite
DELETE FROM `t` WHERE rowid IN (
    SELECT rowid FROM `t` WHERE `c` = 1 ORDER BY `c` ASC LIMIT 10
)

The subquery forwards tableRef, tableAlias, whereClause, orderClause, and simpleLimitClause, so queries that reference an aliased table keep the alias in scope inside the subquery (e.g. DELETE FROM t AS a WHERE a.c = 1 LIMIT 1 works).

Scope

  • In scope: single-table DELETE with ORDER BY, LIMIT, or both — with or without WHERE and alias.
  • Out of scope: multi-table DELETE — MySQL's grammar disallows ORDER BY/LIMIT there, so there's nothing to add.

Tests

  • Translation tests covering plain LIMIT, WHERE + LIMIT, ORDER BY + LIMIT, WHERE + ORDER BY + LIMIT, and alias + WHERE + LIMIT.
  • Runtime tests that insert rows, run DELETE ... LIMIT through the driver, and assert on the surviving rows — including a case that matches multiple rows to verify LIMIT is actually enforced, and an alias case.

Fixes #100

SQLite does not support ORDER BY and LIMIT on DELETE unless
compiled with SQLITE_ENABLE_UPDATE_DELETE_LIMIT, which is not
the case for standard builds. Rewrite single-table DELETE
statements using ORDER BY and/or LIMIT to drive deletion off a
rowid subquery, mirroring the approach already used for UPDATE:

    DELETE FROM t WHERE c = 1 ORDER BY c LIMIT 10

becomes

    DELETE FROM t WHERE rowid IN (
        SELECT rowid FROM t WHERE c = 1 ORDER BY c LIMIT 10
    )

The subquery forwards tableRef, tableAlias, whereClause,
orderClause, and simpleLimitClause, so DELETEs that reference
an aliased table (e.g. DELETE FROM t AS a WHERE a.c = 1 LIMIT 1)
keep the alias in scope inside the subquery.

Multi-table DELETE is unchanged; MySQL's grammar disallows
ORDER BY/LIMIT there.

Fixes #100
@JanJakes JanJakes requested review from a team and ashfame and removed request for a team April 17, 2026 14:36
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.

Support DELETE with LIMIT and ORDER BY

1 participant