Here is a two-line statement. Highlight the first line, press the run shortcut, and watch the table empty.

DELETE FROM orders
WHERE status = 'cancelled';

Nothing malfunctioned. Running a selection means "treat the highlighted text as the whole script", and the highlighted text is DELETE FROM orders — a complete, valid, unqualified statement. Every editor worth using behaves this way, and every one of them hands you this particular loaded foot-gun for free.

The Feature Is Not the Bug

The obvious fix is to stop executing partial selections. It is also the wrong fix. Running one statement out of a script of twenty is the reason the shortcut exists — you iterate on a query, you re-run a single UPDATE after tweaking a value, you check the SELECT above the DELETE before committing to it. Take that away and people go back to a scratch file, which is worse in every direction.

What is actually wrong is the asymmetry. A truncated SELECT is a syntax error and costs you nothing. A truncated DELETE or UPDATE is still grammatical, and the part you cut off was the part limiting the damage. The dangerous case is the one the parser is happiest with.

Warn, Do Not Forbid

So we kept the behaviour and put a gate in front of it. Every path that can send a script — the run button, the run-selection button, and each keyboard shortcut — now goes through a single choke point that scans the exact text about to be transmitted and, if it finds something destructive, shows you what it found before anything leaves the machine.

That "each keyboard shortcut" clause is not filler. Our first version guarded the buttons and left the keybindings wired straight to the executor, which meant the dialog appeared for everyone except the people fast enough to use shortcuts — precisely the users running DELETE statements from memory. One entry point, or you have not built a safety feature; you have built a placebo.

A Scanner, Not a Parser

Recognising destructive SQL sounds like a job for a real parser, and it is not — five engines means five dialects, and a parser that is 98% correct fails in exactly the situations you built it for. We wrote a scanner instead, tuned to be loud: it masks string literals and comments so keywords cannot hide inside them, splits the script into statements, and classifies each one. A false alarm costs one click. A miss costs a table.

Four things in that paragraph turned out to be harder than they read.

Double quotes are not strings

The first version masked anything between quotes, single or double. In standard SQL a double quote is identifier quoting, so masking it erased the table name — and DROP TABLE "orders" classified as nothing at all and ran with no warning. The fix is one character wide: only ' opens a string. Double quotes and backticks stay, because that is where the name lives.

T-SQL's GO is a batch separator

Split a script on semicolons and this is one blob that starts with USE:

USE Shop
GO
DELETE FROM Orders

Every pattern anchored at the start of a statement then looks at USE Shop GO DELETE… and sees a harmless USE. GO is not SQL and not terminated by a semicolon — it is a client-side batch separator on its own line, and a scanner that does not know about it goes blind after the first one.

MERGE always warns

A MERGE can update or delete every matched row inside its WHEN clauses, and there is no way to tell which without parsing the entire statement — the thing we explicitly chose not to build. So it always warns. This is the tuning working as intended: when the scanner cannot be sure, it is loud.

An apostrophe inside a regex hid every drop

The MongoDB side needed the same masking, plus regex literals, because JavaScript has them and SQL does not. Miss that and the apostrophe in find({ tag: /can't stop/ }) opens a string that never closes, and every drop() later in the script disappears from view.

Telling a regex from a division means looking at the previous meaningful character, and ours started out as an empty string. In JavaScript, ''.includes('') is true, so a regex at position zero read as division — and one script-opening regex was enough to blind the scanner for the rest of the file. A scanner whose entire job is not missing a drop, missing every drop. It now starts as a space.

Why SQL Gets a Dialog and MongoDB Gets a Journal

Sutido snapshots MongoDB deletes into a local changelog before they run, so a wrong deleteMany is recoverable per document. No SQL engine offers a client anything comparable — once DELETE FROM orders is committed, the client has nothing left to offer but sympathy.

That difference decides the design. Where we can undo, we capture quietly and stay out of the way. Where we cannot, we spend a click of your attention up front and name what is about to happen: which statement, which table, and whether it has a WHERE clause at all. The same rule applies to the MongoDB operations the journal does not cover — drop(), dropDatabase(), and deletes issued through runCommand all stop and ask.

Nothing here is clever. It is the boring conclusion that safety features have to sit on the last line before the wire, cover every entry point equally, and be tuned to annoy you rather than to be right.

Five Engines, One Set of Guardrails

MongoDB, PostgreSQL, SQL Server, Aerospike and ClickHouse — in one client that reads what you are about to run.

Download Now