Help us shape the future of Go! Take the 2026 Go Developer Survey

pgc

command module
v0.7.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 5, 2026 License: MIT Imports: 16 Imported by: 0

README

deps.dev Go Reference License Stay with Ukraine

pgc

pgc compiles annotated SQL queries into a type-safe Go package for PostgreSQL. You write plain SQL; pgc asks your development database what every parameter and column really is - the server itself is the type oracle, no SQL parsing involved - and generates code that reads like a person wrote it.

The generated package imports only the standard library (database/sql, context, time, ...). pgc itself has zero third-party dependencies: it speaks the PostgreSQL wire protocol directly.

Installation

go install github.com/goloop/pgc@latest

Building from source requires Go 1.24 or newer. Prebuilt binaries for Linux, macOS and Windows are attached to the releases, with checksums.

Either way, generation needs a PostgreSQL database to ask (a disposable local container is perfect).

Quick start

Write queries next to your migrations:

-- queries/users.sql

-- name: GetUser :one
-- Returns a single user by primary key.
SELECT id, email, name, created_at
FROM users
WHERE id = $1;

-- name: CreateUser :one
INSERT INTO users (email, name)
VALUES (@email, @name)
RETURNING id, email, name, created_at;

Parameters are $1..$N or, when a statement has enough of them that counting becomes the risk, @name - numbered for you, and named after what you wrote.

Point pgc at your development database and generate:

export PGC_DATABASE_URL="postgres://user:pass@localhost:5432/app?sslmode=disable"
pgc generate

Use the result like hand-written code, because it looks like hand-written code:

q := db.New(sqlDB) // *sql.DB

u, err := q.CreateUser(ctx, "ada@example.com", "Ada")
got, err := q.GetUser(ctx, u.ID)

// Inside a transaction - same methods:
tx, _ := sqlDB.BeginTx(ctx, nil)
err = q.WithTx(tx).DeleteUser(ctx, u.ID)

Every parameter and result is typed from the live schema: bigint becomes int64, a nullable text becomes *string, timestamptz becomes time.Time, a PostgreSQL enum becomes a named string type with constants, text[] becomes []string (including WHERE id = ANY($1)), and a domain resolves to its base type. When the full column set of a table is selected, the query returns the table's model struct; projections get their own row struct; a single column comes back as a bare value; a joined table can be nested with -- embed:. Expressions that cannot produce NULL - count(...), coalesce(..., <literal>) - come back as plain values rather than pointers. Optional extras: json:"column" tags, a Querier interface for test doubles, and initialisms for your domain's abbreviations, so seo_title becomes SEOTitle instead of SeoTitle.

Commands

pgc generate [-c pgc.json] [-d url]  compile the queries into a Go package
pgc check    [-c pgc.json] [-d url]  compile without writing, for CI
pgc verify   [-c pgc.json] [-d url]  check pgc.lock.json against the database
pgc migrate  [-c pgc.json] [-d url]  apply pending migrations, in order
pgc migrate status                   list applied and pending migrations
pgc describe [-d url] "SELECT ..."   print parameter and column types
pgc version                          print the version

pgc migrate applies plain-SQL migration files exactly once each, in name order - one transaction per file, an advisory lock against concurrent runs and a bookkeeping table, so the whole database lifecycle lives in one tool: pgc migrate, pgc generate, pgc check. In CI, pgc check plus git diff --exit-code catches queries that no longer match the schema and generated code that drifted from its sources.

Generating needs a database only once. pgc generate records what the server said in pgc.lock.json; commit it, and every later run without a database URL generates the identical package from that record - a fresh clone, a CI job, a container build. The record is refused the moment a query stops matching it, and pgc verify reports drift in the one job that does have a database.

Documentation

New to pgc? The step-by-step TUTORIAL.md (Ukrainian: TUTORIAL.UK.md) takes you from an empty directory to a running program in about twenty minutes - Docker database, migrations, annotations, generation and usage, all copy-pasteable.

Full reference: DOC.md (Ukrainian: DOC.UK.md) - annotations, configuration, the type mapping, nullability rules and the anatomy of the generated code.

Contributing

Before submitting changes, run:

gofmt -l .
go vet ./...
go test ./...

License

MIT - see LICENSE.

Documentation

Overview

Command pgc compiles annotated SQL queries into a type-safe Go package for PostgreSQL, using a live development database as its type oracle: every statement is prepared and described over the wire protocol - never executed and never parsed by pgc itself - so parameter and column types come from the server that will run them.

The generate command writes the package described by pgc.json; check runs the same compilation without writing, for CI; describe prints what the server reports about a single statement. The connection URL comes from PGC_DATABASE_URL, DATABASE_URL or the -d flag.

What the server said is recorded in pgc.lock.json, and generation falls back to that record when no URL is configured - so a fresh clone, a CI job and a container build produce the same package without a PostgreSQL to talk to. The verify command reports when the record and the database disagree.

Directories

Path Synopsis
internal
catalog
Package catalog resolves what Describe reports - bare OIDs - into names and nullability, by reading pg_type, pg_class and pg_attribute over the same connection.
Package catalog resolves what Describe reports - bare OIDs - into names and nullability, by reading pg_type, pg_class and pg_attribute over the same connection.
compile
Package compile orchestrates one pgc run: parse the query files, ask the live database to describe every statement, resolve OIDs through the catalog, decide names and Go types, and hand a fully resolved model to gen.
Package compile orchestrates one pgc run: parse the query files, ask the live database to describe every statement, resolve OIDs through the catalog, decide names and Go types, and hand a fully resolved model to gen.
config
Package config loads pgc.json.
Package config loads pgc.json.
gen
Package gen renders the generated Go source.
Package gen renders the generated Go source.
migrate
Package migrate applies plain-SQL migration files exactly once, in name order, recording what ran in a pgc_migrations table.
Package migrate applies plain-SQL migration files exactly once, in name order, recording what ran in a pgc_migrations table.
pgwire
Package pgwire is a minimal PostgreSQL frontend: enough of the version 3 wire protocol to connect, authenticate and ask the server to describe a query.
Package pgwire is a minimal PostgreSQL frontend: enough of the version 3 wire protocol to connect, authenticate and ask the server to describe a query.
queryfile
Package queryfile reads the annotated .sql files that feed pgc: it splits them into named queries, collects their documentation comments and decodes the per-query annotations (overrides and parameter names).
Package queryfile reads the annotated .sql files that feed pgc: it splits them into named queries, collects their documentation comments and decodes the per-query annotations (overrides and parameter names).
snapshot
Package snapshot records what the database said about a set of queries, so a later run can generate the same code without one.
Package snapshot records what the database said about a set of queries, so a later run can generate the same code without one.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL