GEN.L1.DML_WITHOUT_WHERE — the predicate you meant to type
- Category: safety
- Level: 1
- Confidence: deterministic
- Downtime class:
online - Stability: stable
- Suites: lint
- Applies to: PostgreSQL 18 and MySQL 8.4
An UPDATE or DELETE with no WHERE clause changes or removes every row. Nothing about the
statement is malformed, so nothing stops it — a forgotten predicate turns a targeted fix into a
whole-table sweep, and the first sign is usually the row count in the deploy log.
Two problems, one statement
The obvious one is scope: the wrong rows were touched, and in a migration there is no undo.
The quieter one is lock volume. A predicate-free write takes a row lock on every row in the table and holds them all until the migration's transaction commits. On a large table that is enough to stall unrelated traffic that happens to touch the same rows — and on MySQL it is also enough to blow past the undo-log size the server was tuned for.
Flagged
DB::statement('DELETE FROM audit_log');
Preferred
// Scope the write, and for a large table delete in bounded batches:
DB::table('audit_log')->where('created_at', '<', now()->subDays(30))->limit(1000)->delete();
Batching is not decoration. A bounded write takes bounded locks, produces a bounded amount of undo, and — when something goes wrong — leaves a bounded amount of mess.
When you really do mean every row
Say so, and say why:
#[SqlensAllowDestructive('audit_log is rebuilt from the event stream on every import')]
final class ResetAuditLog extends Migration { /* … */ }
That reads differently from a missing WHERE, which is exactly the point — to a reviewer and to the
next person to touch the file.
Related
PG.L1.TRUNCATE/MY.L1.TRUNCATE— emptying a table with a statement that cannot take a predicate. Different rule, because there is no forgottenWHEREto point at.GEN.L3.DML_ON_SCHEMA_CHANGED_TABLE— the neighboring mistake of writing data in the same migration that changed the schema.
Sources
- PostgreSQL 18 —
DELETE— without aWHEREclause, all rows in the table are deleted - MySQL 8.4 —
DELETE— the same, and the reason--safe-updatesexists as a client-side guard
The fix material this rule carries
A finding from this rule carries a payload whose strategy is none: this rule has looked,
and there is no safe standard sequence. That is a conclusion rather than an omission — a
finding with no payload at all says only that nobody wrote one.
The payload is material for you or an agent to apply. SQLens writes no migration and runs no DDL. See the remediation payload for every field, the placeholder semantics, and the version rules a consumer has to follow.