Paste a query or a script: every statement is reviewed as text in this browser, with comments and string literals masked so a keyword inside them is not read as SQL. The findings keep the real statement type through a WITH, require a top-level WHERE for DELETE and UPDATE, and count parameter placeholders outside literals and casts.
Runs locally in your browserThe page splits the pasted script into statements and reviews each one as text: the leading keyword is read at the top level, so WITH old AS (...) DELETE FROM users is reported as DELETE, not SELECT. Nothing is parsed into an execution plan and nothing is sent to a database.
The checks cover what a code review usually looks at first: DELETE and UPDATE without a WHERE on the statement itself, DROP and TRUNCATE, SELECT * and selects with no row bound, a literal compared in a filter, a top-level 1=1 or OR TRUE, a ${...} template placeholder and a value assembled with string concatenation.
Writes are counted for INSERT, UPDATE, DELETE, MERGE, REPLACE, CREATE, ALTER, DROP and TRUNCATE. A DELETE or UPDATE without a WHERE on the statement itself is a danger finding — a WHERE inside a subquery does not guard the outer write. DROP and TRUNCATE are danger findings of their own; the other writes are warnings.
A SELECT is checked for a star in its select list (including t.*) and for a row bound: LIMIT, FETCH FIRST or NEXT ... ROWS ONLY, TOP n and ROWNUM <= n all count, so the informational no-bound note appears only when a SELECT could return an unbounded number of rows.
The placeholder count covers ?, $1, :name and @name outside comments and string literals; a cast such as created_at::text and a SQL Server identifier such as @@ROWCOUNT are not counted. A literal compared in a WHERE, AND or OR filter is a warning that points at the parameter syntax of your driver.
Two string-building patterns are reported separately: a ${...} template placeholder means a value is interpolated into the statement, and a concatenation such as a literal plus an identifier means the statement text is assembled outside the driver. Neither is a bound parameter, which is the distinction that matters for the text that reaches the server.
The splitter understands doubled quotes and MySQL backslash escapes, double-quoted and backtick identifiers, -- and /* */ comments, # line comments, PostgreSQL dollar-quoted bodies and DELIMITER directives, so an escaped apostrophe or a function body does not produce a false statement count. A script with more than one statement gets a warning, and every finding carries its statement number.
The page does not connect to a database, does not execute anything and cannot see your schema, permissions, row-level security or transactions: a statement that passes here can still be destructive in your environment. Treat it as a text-level pre-check before a query review, next to EXPLAIN and a read of the surrounding code.