Notes › Tech Notes › 6 Elements Stay Put, the 7th Breaks the Line: What SQL Beautify and Minify Really Do

6 Elements Stay Put, the 7th Breaks the Line: What SQL Beautify and Minify Really Do

The minifier ran and the comment block disappeared

A reporting query that had run for months went in at 967 characters over 14 lines and three statements. Beautified, it came back as 1376 characters across 72 lines; minified, as 914 characters on three lines. The size fell, and with it the two header lines that recorded how each field was defined: not one character survived.

On a second pass I unticked the option that removes comments during minification and ran the same input. Take SELECT a -- 行注释 with FROM t on the next line: ticked, the output was 15 characters on one line; unticked, it came back as the 24 character SELECT a /* 行注释*/ FROM t. The whole difference was one checkbox, and its default is ticked.

Comments land in three places, not two

A line comment outside a statement stays where it was: an input of 23 characters carrying a header note came out as 5 lines and 31 characters, the note still on its own line. A comment inside parentheses, or inside CASE, is rewritten on the spot into a block comment: the input SELECT f(a -- 注 is 29 characters going in and comes back as the 38 character f(a /* 注*/), because once -- has become /* */ it stops swallowing the rest of the line. A comment opened with # walks the same branch.

The third case is the minifier: while the box is ticked it skips line comments and block comments together, and the 24 character form above appears only once you untick. The listing page sentence about rewriting line comments into block comments describes the unticked branch, not the default one.

An IN list wraps on strictly greater than

The threshold defaults to 6 and the rule is that the comma count plus one must exceed it; equal does not wrap. Under that default 6 elements stay on one line and the 7th breaks it open. The box declares 1 to 99, but the code guards only the lower bound: one IN carrying 20 elements typed at 99, 100 or 999 gives identical output, 6 lines and 111 characters each time, so an out of range value is honoured rather than rejected.

At a list of 100 elements the branches separate:

  • Wrapped: 107 lines and 1238 characters, one element per line, each with a prefix 8 characters wide.
  • Threshold set to 100 so it stays flat: 6 lines and 432 characters, 101 fewer lines and 806 fewer characters.
  • A threshold of 1 does not mean always wrap: 2 elements fold into 9 lines and 68 characters, while an IN holding one element stays at 6 lines and 43 characters.
  • An IN with a subquery never consults the threshold: IN (SELECT b FROM u) opens into 11 lines even at 999.
lines 107 lines 6 lines characters 1238 characters 432 characters Fig. 1: 100 elements, wrapped versus flat

The whitespace-only mode leaves every option inert

This mode builds its output straight from the raw token text and never reads the options object, and only minify greys the option bar out. Indentation, keyword case, the wrap threshold, operator spacing, leading commas and comment removal, those 6 controls, are decoration: switching operator spacing off left the output at the same 24 characters.

It also refuses to add missing spaces. x=1, y = 2, z= 3, w =4 measures 36 characters going in and coming back, and only surplus whitespace is normalised, which is how one passage of 26 characters shrinks to 18. The same sentence through the beautifier becomes 7 lines and 60 characters. Line structure goes too: leading indentation is wiped, the 43 character passage coming out at 31, and blank lines deleted, 4 lines and 23 characters becoming 2 lines and 15. Normalising surplus whitespace and clearing indentation and blank lines is everything it does.

Two word lists decide what changes case

Casing touches only the 174 words in SQ_KW and the 75 function names in SQ_FN, so myFun(1), tab(a, b) and a table name such as TBL come back as typed, while a::text is lifted to a::TEXT because that cast word sits in the keyword list. Line breaks come from elsewhere: the 17 entries of SQ_OWN, the 16 of SQ_TAIL and the 11 of SQ_JOIN, combining into 44 phrases.

Two places where the lists bite. from is in the keyword table and not in the function table, so my SELECT from(1) is read as a clause and turned into 3 lines with (1) hanging under FROM; the claim that a name followed immediately by an opening parenthesis counts as a function call does not hold. Minify also ignores indentation, threshold, leading commas and operator spacing, so three settings give the same single line, yet case is still rewritten, which buries the claim that minification moves whitespace only.

A three word JOIN gap and indentation that multiplies

None of the 11 entries in SQ_JOIN is a three word combination, so NATURAL LEFT JOIN misses the list and splits into FROM, t1 NATURAL and LEFT JOIN t2, leaving NATURAL on the content line under FROM; a bare OUTER JOIN is not recognised either. LEFT OUTER JOIN behaves, coming back on 6 lines at lvl=2 with ON one level deeper, and CROSS JOIN behaves on 5 lines.

Nesting multiplies a level instead of adding one: derived tables nested 3 deep reach lvl=5 at the bottom, and with indentation set to 2 spaces, 4 spaces, 8 spaces or a Tab the deepest prefix measures 10, 20, 40 and 5 characters while the block measures 123, 191, 327 and 89 characters, always on 14 lines. The official sample never exceeds indentation level 3, so taking your own third level at 8 spaces more than doubles the block with no new logic in it.

2 spaces 123 characters, prefix 10 4 spaces 191 characters, prefix 20 8 spaces 327 characters, prefix 40 Tab 89 characters, prefix 5 Fig. 2: The size cost of multiplied indentation

Create table statements are not laid out, and placeholders get split

Among the 44 phrases there is no CREATE TABLE, ALTER TABLE, CREATE INDEX or DROP TABLE, so a create statement of 6 lines and 4 columns weighing 86 characters collapses into one line of 74 characters, with no error and no hint. PostgreSQL dollar quoting is blunter: SELECT $$ it's ok $$ throws a complaint that the apostrophe quote is never closed.

Placeholders are subtler. In WHERE b=? AND c=:uid AND d=@@global.x both @ and : count as operator characters, so glued against the equals sign they merge into one token and the space goes back inside that token: the output reads d =@@ global.x and c =: uid, splitting the variable name in half. Leave one space before the equals sign and the same sentence is fine, the sample of 59 characters coming out as 8 lines and 79 characters.

The same sample measured four ways

ModeResult charactersLinesUTF-8 bytes
input as typed967141101
beautify1376721510
minify91431048
whitespace only956141090

The statistics row carries 5 items and includes the result character count; for this sample the row measured is 3 / 72 / 1376 / 3 / 967. Beautifying an already beautified result reproduces it character for character, 1376 turning back into 1376, and running the minifier twice is just as stable. Send the minified output through the beautifier and you get 1378 characters, 2 away from beautifying directly: the round trip is not an identity, and a diff taken after one will move.

Volume is not the worry. A paste of 300135 characters holding 1819 statements takes 164 milliseconds to beautify into 49112 lines and 542060 characters, and 82 milliseconds to minify into 292858 characters. Nothing is uploaded and no local storage is written, so there is no history, no batch entry point and one input box; the download prefixes the content with U+FEFF, so the file is 3 bytes larger than the character count on screen. Between the three modes of the SQL beautifier, the checkbox and the threshold are what deserve attention.

Q: Can comments deleted by the minifier be recovered?

Only by unticking the option that removes comments during minification and running the input again, which yields the /* */ spelling at 24 characters. With no local storage and no history, text minified away and kept nowhere else is gone. The beautifier does not eat comments, but a line comment inside parentheses or inside CASE becomes a block comment, and that enlarges the diff.

Q: Does a smaller threshold mean more wrapping and a bigger one less?

The direction is right and the boundaries are not. The test is strict greater than, so an element standing alone at threshold 1 still does not wrap and the block stays at 6 lines and 43 characters; the upper bound has no guard, so 999 behaves exactly like 99; and an IN with a subquery ignores the threshold, opening into 11 lines regardless.

Q: Why did the same SQL come out with different line counts?

Three suspects in order: the same mode both runs, the same IN threshold both runs, and whether either run contained a subquery. What is left belongs to indentation, and it is not small: between 8 spaces and a Tab, a nesting 3 levels deep takes the deepest prefix from 40 characters down to 5.

📎 Related articles

Same 70 Bytes, 13 MIME Types: file-info Reads the Extension, Never the Content 2026-10-23 My Transparent Logo Came Back as a White JPG, and 91 Quality Levels Changed Nothing on the PNG Side 2026-10-23 Remove Everything Still Left 553 Bytes, and Location Only Never Touched the Thumbnail 2026-10-23 Pad Width Is Only a Floor: Where (2) Lands When a Batch Rename Hits 2000 Files 2026-10-23 Two Certificate Fingerprints, 95 Characters and 64: The Missing 31 Are All Colons 2026-10-23