SQL Minification Deletes Your Comments — All of Them, Quietly
What actually happens
A comment is a thing that lives between two points in text, and a minifier's job is to remove the space between things. This minifier resolves that conflict by deleting the comment. All three comment styles, every time:
IN select a,
-- audit note
b from t
OUT select a, b from t
IN select a,
# mysql audit note
b from t
OUT select a, b from t
IN select /*keep*/ 1 from t
OUT select 1 from t
That is the honest summary, and it is a different failure from the one this page originally described. The earlier version of the tool collapsed newlines first and left the -- marker standing, so -- audit note b from t became one comment that swallowed the rest of the statement — a query that parsed while quietly losing its FROM. The current code strips comments before reflow, so nothing is swallowed and FROM survives. The comment is gone instead.
Both behaviours lose information. The new one is less likely to produce a query that runs against the wrong table, and more likely to produce a diff that looks like nothing happened when an audit trail has been erased.
If a comment carries meaning — a trace ID, a ticket reference, /* pg_hint_plan */, an owner's name — minifying destroys it. Strip or move those comments deliberately, or don't minify. There is no comment style that survives here, including block comments.
The case that still truncates
An unterminated /* is not an error; it is the end of the query.
IN select /* unterminated from t
OUT select
Everything after the opener disappears and the result is still syntactically a statement. This is what an unbalanced comment in pasted, machine-generated, or truncated-log SQL does to a minified probe: the query gets shorter, not angrier.
Nested block comments do something stranger. No database supports nesting — the comment ends at the first */ — but the tool does not error, it reassembles the leftovers into operators:
IN select /* a /* b */ c */ d from t
OUT select c * / d from t
c * / d is not what you wrote and not what any engine would read. Treat "garbage in, garbage out" here as "garbage in, plausible garbage out", which is the property that makes this class of formatter dangerous in a pipeline.
Keyword uppercasing is a dictionary, not a parser
Still true, partially fixed. The formatter uppercases keywords it knows and passes through anything it does not, so output can look half-finished in ways that are not semantic. Re-run today:
Fragment typed after select * from t | Rendered as |
|---|---|
distinct on (a) a | DISTINCT ON (a) a |
returning id | RETURNING id |
on conflict (a) do nothing | ON CONFLICT (a) DO NOTHING |
lateral | lateral |
RETURNING and ON CONFLICT … DO NOTHING used to come out inconsistently cased (returning id, ON conflict (a) do nothing) because they were missing from the keyword set. They are in it now. LATERAL is still absent, and QUALIFY is not a clause here at all — it stays glued to the line it was on, because a keyword-anchored formatter has no idea what a Snowflake/DuckDB clause boundary is.
SQL keywords are case-insensitive, so none of this changes what the database does. It is a legibility failure, and it is why "reformat the file" commits show churn that is not semantic.
Literals are protected, and that is the part worth trusting
formatSql uppercases outside a literal and not inside it:
IN select $$keep me plain$$ as x
OUT SELECT $$keep me plain$$ AS x
IN select $$has';--in it$$ as x
OUT SELECT $$has';--in it$$ AS x
IN select '--not a comment' as a
OUT SELECT '--not a comment' AS a
IN select "MixedCase" from t
OUT SELECT "MixedCase" FROM t
$$ ... $$ bodies are data and stay byte-identical, including the in that must not be uppercased. -- and # inside quotes are not comments. "MixedCase" keeps its case, which matters because quoted identifiers are case-sensitive in Postgres and uppercasing one can change which table you query.
One identifier hazard remains: an unquoted column that happens to be a keyword gets uppercased, so SELECT order FROM orders AS order returns SELECT ORDER ... AS ORDER. Quote it or leave the UPPERCASE toggle off — with the toggle off, casing is reproduced exactly.
# is an operator and a comment, and that used to lose data
PostgreSQL's path and JSONB operators #>, #>> and #- all begin with #, which is MySQL's line-comment introducer. The tokenizer checked for comments before operators, so:
IN select meta #- '{a}' from t
OUT select meta -- before the fix
OUT select meta #- '{a}' from t -- now
SELECT meta is valid SQL. It returns a column instead of applying a predicate, and it never complains. A bare # is a MySQL line comment in Auto and MySQL modes — reading it as PostgreSQL's integer XOR would mean guessing, and a wrong guess silently deletes the rest of the statement — so avoid #-prefixed operators in a minified probe either way. If the query genuinely uses # as XOR, switch the dialect box to PostgreSQL, which reads it as an operator.
How to re-run every claim on this page
These outputs are not recalled from documentation and not transcribed from a browser pane. They come from apps/sqlformat/src/core.js, which the build inlines verbatim into the shipped page — the check at the end proves the file under test is the file that deploys:
node scripts/build.js && node -e '
const fs=require("fs");
const c=require("./apps/sqlformat/src/core.js");
const M=c.minifySqlQuery, F=q=>c.formatSqlQuery(q,{indentSize:4,uppercase:true});
console.log(M("select a,\n-- audit note\nb from t"));
console.log(M("select /*keep*/ 1 from t"));
console.log(M("select /* unterminated from t"));
console.log(M("select /* a /* b */ c */ d from t"));
console.log(F("select * from t returning id"));
console.log(F("select meta #- \x27{a}\x27 from t"));
const core=fs.readFileSync("apps/sqlformat/src/core.js","utf8");
const dist=fs.readFileSync("apps/sqlformat/dist/index.html","utf8");
const body=/function minifySqlQuery[\s\S]*?\n}\n/.exec(core)[0].trim();
console.log("tested code is the deployed code:", dist.includes(body));'
Ran 2026-10-19, output in order: select a, b from t · select 1 from t · select · select c * / d from t · SELECT * / FROM t / RETURNING id (three lines) · SELECT meta #- '{a}' / FROM t (two lines) · true.
The last line prints true only if the minifier in dist/index.html — what the browser actually receives — is byte-identical to the function you just called. If a future refactor moves the SQL logic somewhere else, that assertion fails and this page needs re-measuring.
Not verified, and therefore not claimed: behaviour on dialects or constructs not listed above. If one of them mangles, say so with the input and I will measure it and date the fix.