Writing SQL is easy; maintaining deterministic SQL formatting across a team of twenty engineers using different IDEs, dialects, and database engines is notoriously difficult. But what happens when the formatter you trust to clean up your migration script silently corrupts your syntax?
Most rudimentary formatters found online use basic regular expression string substitution. While regex works for elementary SELECT id FROM users WHERE status = 1 statements, it breaks down catastrophically when encountering modern SQL dialect features.
The Failure Modes of Regular Expression Formatting
1. Dialect-Specific Operators Mangling
In PostgreSQL, querying JSONB documents heavily relies on specialized operators such as ->, ->>, #>, and ?|. A regex formatter that treats hyphens and angle brackets as arithmetic operators will insert spaces in the middle of your operators:
-- What you wrote:
SELECT payload->>'user_id' FROM events;
-- How a broken regex formatter mangles it:
SELECT payload - > > 'user_id' FROM events; -- Syntax error!
2. Nested Subqueries and CTE Scope Confusion
Common Table Expressions (WITH RECURSIVE) and nested subqueries introduce parent-child scope hierarchies. Regular expressions have no memory stack — they cannot track whether a parenthesis belongs to a correlated subquery, an arithmetic expression, or an inline IN (...) clause. The result is erratic indentation that confuses code reviewers.
3. String Literal Invasions
If a SQL query contains a string literal with reserved keywords — for example: WHERE message LIKE '%SELECT ORDER BY%' — a regex replacer will frequently split the string literal across newlines and capitalize the interior words, permanently altering your application's search predicate!
The Abstract Syntax Tree (AST) Alternative
Modern formatters treat SQL not as a flat string of characters, but as a formal grammar. The formatting engine operates in three distinct phases:
- Lexical Analysis (Tokenization): The raw SQL string is converted into a continuous stream of tokens categorized as keywords, identifiers, literals, operators, and comments. Quoted strings and dialect-specific identifiers (such as MySQL backticks
`id`or T-SQL square brackets[id]) are treated as immutable atomic units. - Grammar Graph Construction: Tokens are organized into an Abstract Syntax Tree that understands clause nesting. A
FROMclause is recognized as a sibling toSELECT, while a correlated subquery within aWHEREclause is recognized as a child tree requiring an increment in indentation depth. - Deterministic Code Generation: The tree is traversed and emitted according to strict dialect rules (e.g. 2-space or 4-space indentation, uppercase keywords, and dialect-specific line breaks).
Dialect Matrix: PostgreSQL, MySQL, and T-SQL Nuances
Different relational databases have evolved disparate syntax rules over four decades of ANSI SQL revisions:
- PostgreSQL: Requires explicit support for type casting syntax (
column::timestamp), array slicing, and JSONB path queries. - Transact-SQL (SQL Server): Relies on
TOP (n)clauses, square bracket identifiers ([dbo].[Users]), and batch separators (GO). - Snowflake / BigQuery: Features nested semi-structured expressions (
FLATTEN,PARSE_JSON), qualified wildcard queries (SELECT * EXCEPT(id)), and partitioned analytical windows.
By running dialect-aware AST formatting directly in the browser thread, developers achieve clean, deterministic formatting without risking syntactical regressions.