A VS Code extension by Ondrej Bartak that formats T-SQL and Databricks
SQL according to a JSON style configuration with 115 formatting options
— all of them honored. The formatting engine is custom-built
(tokenizer → parser → printer) with a strict safety model: it can change
whitespace and letter case, and nothing else.
Using it
- Open a
.sql file (language mode sql).
- Run Format Document (or Format Document With… → SQL River Formatter, then
Configure Default Formatter to make it the default). Format Selection
works too and expands the selection to whole statements.
- On first use you'll be prompted to create a style file. It is scaffolded
with every option at its default value and JSON IntelliSense via the bundled
schema.
Default style: sqlstyle.guide
The default option values follow sqlstyle.guide:
uppercase keywords right-aligned to a "river", lowercase identifiers, trailing
commas, spaces (4-wide):
SELECT f.species_name,
AVG(f.height) AS average_height
FROM flora AS f
WHERE f.species_name = 'Banksia'
AND f.height > 100;
Case-sensitive collations: the default casing.identifiers: "lowercase"
rewrites unquoted identifier casing (quoted identifiers like [Name],
`name` are never touched). If your database resolves identifiers
case-sensitively, set "identifiers": "leaveAsIs" in your style file.
Style files
- Styles live in a folder owned by the extension (under VS Code's
globalStorage, so they survive extension updates). Use the commands:
- SQL River Formatter: Select Style — pick the active style (also shown
in the status bar for SQL editors).
- SQL River Formatter: Create New Style — scaffold a new style file.
- SQL River Formatter: Open Styles Folder — reveal the folder.
- A style file may set any subset of options; everything else falls back to
the defaults from
resources/formattingstyle-schema.json.
- Invalid files produce a friendly error with Open style file / Use
defaults choices; formatting never destroys code on a bad config.
- The style file owns all whitespace decisions — VS Code's
tabSize /
insertSpaces settings are intentionally ignored.
Dialects
sqlRiverFormatter.dialect: auto (default) | tsql | databricks.
auto detects per document (backticks, ${var}, QUALIFY, LATERAL VIEW →
Databricks; [brackets], GO, @@vars, N'…', TOP → T-SQL; tie → T-SQL).
Key lexing differences handled: "…" is an identifier in T-SQL but a string
in Databricks; backtick identifiers and \' escapes (Databricks); [brackets],
GO batch separators, @/@@ variables, N'…', nested block comments
(T-SQL); ${var} / :param pass through opaquely (Databricks).
Safety model
- Statement isolation: the file is split into statements before parsing.
A statement the engine cannot parse is passed through verbatim (with
casing still applied); one bad statement never affects its neighbors.
Failures are listed in the SQL River Formatter output channel and the status
bar tooltip — never modal dialogs.
- Token safety: after parsing, the AST's token walk is checked against the
input token stream; the printer therefore can never add, drop, or reorder
code — only whitespace and letter case change.
- Comments round-trip: comments (including trailing
-- comments) are
preserved through formatting.
- Idempotency:
format(format(x)) === format(x) is enforced by tests on
every fixture.
Parsed & fully formatted: SELECT (DISTINCT/TOP, all JOINs, set operators,
subqueries, CASE, IN, BETWEEN, window OVER), INSERT … VALUES /
INSERT … SELECT, UPDATE, DELETE, WITH CTEs, ORDER/GROUP BY,
LIMIT / OFFSET-FETCH tails, DECLARE / SET @var, control-flow blocks
(IF/WHILE … BEGIN … END, BEGIN TRY/CATCH, nested statements inside blocks),
CREATE TABLE (column defs, constraints, filegroup tails), and
CREATE [OR ALTER] PROCEDURE (parameters + body).
Passed through verbatim (casing still applied): MERGE, EXEC, cursors,
table variables, SET NOCOUNT ON-style options, single-statement IF without
BEGIN, other DDL (CREATE INDEX/VIEW, ALTER TABLE, DROP, CTAS), and
anything else the parser does not model. These pass through silently — they
are not reported as errors.
Options
All 115 options in every group are honored — whitespace, lists,
parentheses (full 9-value parenthesisStyle, indent, collapse), casing
(incl. useObjectDefinitionCase), dml (incl. clauseAlignment: toFirstListItem), ddl, controlFlow, cte, variables,
joinStatements, insertStatements, functionCalls, caseExpressions, and
operators (incl. comparison.align and in.alignment).
The repository's docs/showcase.html demonstrates every option on real
engine output: unformatted input → formatted output for every enum value /
boolean / representative numbers, organized by the option tree, with a
coverage summary and an audit of
sqlstyle.guide rules (covered / out-of-scope).
The same runs are pinned as golden tests, and every option must provably
change the output across its demonstrated values.
Roadmap
Possible future work:
MERGE statement formatting.
ELSE IF chains and single-statement IF/WHILE bodies (today they pass
through verbatim).
- Layouts for the remaining DDL forms:
CREATE INDEX/VIEW, ALTER TABLE,
CTAS.
- AS-keyword insertion for aliases (deliberately deferred: inserting tokens
would break the token-safety invariant, so it needs a separate opt-in
mechanism).
- Marketplace publication.