Skip to content

fix(format): stop rewriting SQL and only break lines that do not fit - #849

Merged
psteinroe merged 7 commits into
mainfrom
fix/formatter-issues
Oct 13, 2026
Merged

psteinroe merged 7 commits into
mainfrom
fix/formatter-issues

Conversation

@psteinroe

@psteinroe psteinroe commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

The formatter rewrote SQL instead of only laying it out. It dropped pg_catalog. qualifiers, replaced $$ with $function$ and trimmed function bodies, removed argument names from ALTER FUNCTION/GRANT/REVOKE signatures, and printed some keywords and function names in uppercase under keywordCase: lower. It also broke short column definitions and constraint lists across lines. With this PR it changes only whitespace, line breaks, keyword case and redundant quotes.

Source fidelity

  • Function calls are printed with their name exactly as written.
  • SQL-standard syntax (EXTRACT, TRIM, AT TIME ZONE, COLLATION FOR, ...) is only used when the parser flagged the call as written that way (funcformat), and those calls are now printed in that syntax instead of as timezone(...) / btrim(...).
  • Types only collapse to keywords for the pg_catalog names the grammar creates itself, so pg_catalog.text and int4 stay as written.
  • Function and DO bodies are kept byte for byte (they were trimmed before) and always dollar-quoted with $$, or $body$ when the body contains $$, instead of a tag picked per statement kind ($function$, $do$, ...).
  • Signatures are printed from objfuncargs, which keeps argument names and modes.

Stricter verification

normalize_ast no longer hides the differences above. It used to strip pg_catalog, lowercase function names, clear argument names and objfuncargs, lowercase SET values (SET datestyle = 'German' was printed as german) and trim function bodies. The Postgres regression suite test now also requires format(format(x)) == format(x). Every accepted statement for 15–17 passes both checks.

Layout

  • The renderer no longer forces a nested group to break when its flat form is longer than half the line width. A group stays on one line whenever it fits, including the text that follows it up to the next break (such as , or over ().
  • Broken column definitions and constraints continue one level deeper.
  • Parenthesized lists break inside their parentheses instead of after the first item.
  • CREATE INDEX keeps its head, opclasses and a short WHERE on one line.
  • An end-of-line comment after a comma stays on its line.
  • The workspace keeps at most one blank line between statements and none at the start or end of a file.

This changes most pretty-print snapshots, mostly lines that now stay joined and function names that are no longer uppercased.

New entry points

  • format --stdin-file-path=<path> prints only the formatted SQL. Check mode (format without --write) already exited non-zero with a diff. It now also does so when only blank lines change. Both are documented.
  • The WASM package exposes format(sql, options) and Workspace.format / formatSql, with options mirroring the format config section.

Not included: an identifierQuoting: "preserve" option, and a configurable maximum number of blank lines (fixed at one).

The formatter rewrote what was written: it dropped pg_catalog qualifiers,
replaced dollar-quote tags, dropped argument names from signatures,
uppercased some function and privilege names, and broke lines that fit.

- Function calls keep their name as qualified, SQL-standard syntax is
  only used for calls the parser flagged as written in it
- Function and DO bodies keep their source quoting byte for byte
- Signatures are printed from objfuncargs, with names and modes
- Every keyword, privilege and option name follows keywordCase
- Constraint, index and other names lose redundant quotes
- A group stays on one line whenever it fits, broken column
  definitions and constraints continue one level deeper, and
  parenthesized lists break inside their parentheses
- CREATE INDEX keeps its head, opclasses and short predicate together
- End-of-line comments after a comma stay on their line
- Blank lines between statements are normalized to at most one

The semantic check no longer normalizes away qualifiers, argument names,
function name case, SET values or function bodies, and the regression
suite now also checks that formatting is idempotent.
format --stdin-file-path=<path> reads SQL from stdin, applies the
configuration for <path> and prints only the formatted SQL. Check mode
now also fails for files whose only change is between statements.
Workspace.format, Workspace.formatSql and a top-level format() take the
options of the format section of the configuration file.
- Merge emit_word into emit_keyword, which now spells any word in the
  keyword case
- Measure a group's single-line form only until the line is full
- Merge the emit_parenthesized_list imports into the existing ones
- Print OVERLAPS operands as parenthesized lists, so they break inside
  the parentheses
- Drop the stdin ignore check the workspace already performs
…matting

Collapse blank lines with one pass over the gap and trim the start and
end of the file at the call site, instead of a position-aware state
machine. The layout of a reformatted SQL function body now lives in
set_formatted_sql_fn_body.
- Import emit_parenthesized_list the way each file already imported
  node_list helpers, inline where it used full paths
- Reuse emit_keywords for privilege names and emit_parenthesized_list
  for exclusion constraint elements
- Reuse the self-contained expression check of casts for the operands of
  SQL-standard syntax
- Spell EXTRACT fields with emit_keyword
- Drop an unreachable branch for time zone types
- Build the one-off WASM format workspace with createWorkspace
Instead of keeping each body's original quoting, quote every function
and DO body the same way: $$, or $body$ when the body contains $$. The
body itself is still written byte for byte. Without the original quoting
to recover, the emitter no longer needs the statement source.
@psteinroe
psteinroe merged commit 5c2f5d7 into main Oct 13, 2026
9 checks passed
@psteinroe
psteinroe deleted the fix/formatter-issues branch October 13, 2026 18:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant