cmd / pgfmt

pgfmt is a small SQL formatter for Postgres query files. For package-local SQL query layout and embedding, see go / postgres. For a static check over the same query files, see cmd / nullscan.

I run it on */queries/*.sql so SQL style stays consistent across code reviews.

Why

When SQL lives in many package-local directories, style drifts fast. Diffs get noisy. Reviews spend time on formatting.

A formatter keeps diffs focused on behavior.

Install

pgfmt lives in its own module. Add it to a project as a module tool:

go get -tool github.com/croaky/pgfmt/cmd/pgfmt

That writes a tool line to go.mod next to the require line that pins the version:

tool github.com/croaky/pgfmt/cmd/pgfmt

Why go tool

Go 1.24 added the tool directive. go tool <name> builds and runs the pinned version from the module cache. Local commands and CI use the same version from go.mod, unlike go install ...@latest. Managing pgfmt as a module dependency also lets several repos share one implementation, rather than copying code into a local cmd/pgfmt.

There is no separate install step and nothing to put on $PATH. A fresh clone can run the formatter after go mod download. A tool upgrade appears in git history instead of changing silently after someone re-runs go install.

Go caches the binary after the first build, so go tool pgfmt runs as fast as a direct binary.

Command

Format in place:

go tool pgfmt -w <pkg>/queries/*.sql

Format a named file to stdout:

go tool pgfmt query.sql

Format stdin to stdout:

cat query.sql | go tool pgfmt

CI behavior

The -c flag checks formatting, exits with an error if a file needs changes, and writes nothing. It prints the exact command to fix each file:

FAIL: <path> needs formatting. Run: pgfmt -w <path>

The -c flag reports only real formatting violations, so it does not require a clean working tree. -c and -w are mutually exclusive.

In a cibot Checkfile, that is one line:

sqlfmt: git ls-files -z '*queries/*.sql' | xargs -0 go tool pgfmt -c

Style it enforces

DDL keywords like index, key, and add become uppercase only inside CREATE/ALTER/DROP. In DML, where they are often column names, they stay lowercase.

pgfmt is idempotent. Running it twice produces the same output.

Safety

After formatting, pgfmt parses its output and compares the token stream to the input. If they differ, it fails and writes nothing. The formatter cannot silently corrupt a query.

Comments are the one exception. pgfmt keeps standalone comments on their own line, but drops trailing comments and comments inside parentheses.

Workflow

For package-local queries:

  1. Write or edit SQL in <pkg>/queries/*.sql.
  2. Run go tool pgfmt -w <pkg>/queries/*.sql.
  3. Commit the formatted SQL with the Go call-site changes.

← All articles