A SQL formatter for Postgres and SQLite, built on a lossless CST.
$ echo 'SELECT id,name FROM users WHERE org_id=$1 ORDER BY name;' | squill fmt --stdin
select id, name from users where org_id = $1 order by name;
$ echo 'SELECT u.id,count(*) FILTER (WHERE o.active) AS n FROM users u LEFT JOIN orgs o ON o.id=u.org_id GROUP BY u.id ORDER BY n DESC;' | squill fmt --stdin
select u.id, count(*) filter (where o.active) as n
from users u left join orgs o on o.id = u.org_id
group by u.id
order by n desc;Short statements collapse onto one line; long ones break clause-per-line
at max-width (default 80).
cargo install --git https://tree.ht/birds/squill.git cli --bin squillIn CI, skip the Rust toolchain and pull the static binary off a
release (built by .forgejo/workflows/release.yml on every v* tag):
curl -fsSLo squill https://tree.ht/birds/squill/releases/download/v0.1.0/squill-x86_64-linux
chmod +x squill
./squill fmt --check .Or from a checkout:
cargo run -p cli --bin squill -- fmt path/to/queries/ # format in place
cargo run -p cli --bin squill -- fmt --check . # CI mode: diff + exit 1
squill fmt --help # the full flag surfaceOptions (via the nearest squill.toml or flags — flags win): --dialect postgres|sqlite,
--indent tab|spaces, --indent-width N (default 2), --max-width N
(default 80), --keyword-case lower|upper, --quote-idents as-needed|always, --at-params for
sqlc-style @name parameters, --strict to fail on statements that
could not be parsed.
SQL embedded in host code formats too: squill fmt src/queries.rs
rewrites the string literals inside sqlx::query!-family macros, with
built-in support for Rust, Go (database/sql calls), Python
(.execute-family and text(...), with %s / %(name)s params
preserved), JavaScript/TypeScript/TSX (.query/.execute/.prepare
and sql-tagged templates), and Gleam (sqlight.query as SQLite,
pog.query etc. as the session dialect). Only multiline string
syntaxes — raw strings, backticks, triple quotes, templates, Gleam
strings — are reformatted, always into a vertical block: quotes on
their own lines, one clause per line. Plain single-line strings stay
byte-identical, and so do Python f-strings and ${}-interpolated
templates (SQL with holes is never touched). Directories include host
files with --embedded; --embedded-query custom.scm swaps the tree-sitter
extraction query.
crates/parser— hand-written dual-dialect lexer (lossless: every byte is a token, including comments), recursive-descent parser with Pratt expressions, error recovery into verbatimErrorStatementnodes, and a codegen'd CST layer oncstree(seesyntax.def).crates/formatter— Wadler/Prettier doc IR and renderer, the CST-to-doc rules, vendored Postgres/SQLite keyword tables, and the semantics-preserving identifier-quoting transform.crates/embed— formats SQL embedded in host files (Rust sqlx macros, Go database/sql calls) located via tree-sitter queries.crates/cli— thesquillbinary and thecorpus-reportcoverage harness.
Every formatted statement is re-lexed and compared with its input: token streams must match modulo whitespace, keyword case, and sanctioned identifier-quote changes, and comments must survive in order. On any mismatch the original text passes through verbatim — output is never less correct than input. CI enforces the safety oracle over the whole vendored coder/coder corpus (1,168 files, 4,174 statements, 100% parsed and formatted):
- Idempotence —
format(format(x)) == format(x). - Token equivalence — the meaning-bearing token stream never changes.
- Comment conservation — never dropped, duplicated, or reordered.
cargo run -p cli --bin corpus-report -- --summary prints the current
coverage numbers.