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 without review bikeshedding.

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, and go tool <name> builds and runs the pinned version from the module cache. Compared to go install ...@latest, the version a person runs and the version CI runs are the same version, because both come from go.mod. Compared to a repo-local cmd/pgfmt invoked with go run ./cmd/pgfmt, the formatter is a dependency rather than a copy, so several repos share one implementation and upgrade by a go get -u commit.

There is no separate install step and nothing to put on $PATH. A fresh clone can run the formatter after go mod download, and a formatter upgrade shows up in a diff instead of appearing one morning after somebody re-ran go install.

The binary is cached after the first build, so go tool pgfmt costs about what running the binary directly costs.

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 non-zero if any file would change, and writes nothing. It prints the exact command to fix each file:

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

-c reports only real formatting violations, so it does not need 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 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 re-lexes its own 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. Standalone comments on their own line survive; trailing comments and comments inside parentheses are dropped.

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