This document provides essential information for working with the sqlc codebase, including testing, development workflow, and code structure.
- Go 1.26.4+ - Required for building and testing
- Docker & Docker Compose - Required for integration tests with databases (local development)
- Git - For version control
The sqlc-test-setup tool (cmd/sqlc-test-setup/) automates installing and starting PostgreSQL and MySQL for tests. Both commands are idempotent and safe to re-run.
go run ./cmd/sqlc-test-setup installThis will:
- Configure the apt proxy (if
http_proxyis set, e.g. in Claude Code remote environments) - Install PostgreSQL via apt
- Download and install MySQL 9 from Oracle's deb bundle
- Resolve all dependencies automatically
- Skip anything already installed
go run ./cmd/sqlc-test-setup startThis will:
- Start PostgreSQL and configure password auth (
postgres/postgres) - Start MySQL via
mysqld_safeand set root password (mysecretpassword) - Verify both connections
- Skip steps that are already done (running services, existing config)
Connection URIs after start:
- PostgreSQL:
postgres://postgres:postgres@127.0.0.1:5432/postgres?sslmode=disable - MySQL:
root:mysecretpassword@tcp(127.0.0.1:3306)/mysql
# Full test suite (requires databases running)
go test --tags=examples -timeout 20m ./...go test ./...docker compose up -d
go test --tags=examples -timeout 20m ./...go run ./cmd/sqlc-test-setup install
go run ./cmd/sqlc-test-setup start
go test --tags=examples -timeout 20m ./...# Test a specific package
go test ./internal/config
# Run with verbose output
go test -v ./internal/config
# Run a specific test function
go test -v ./internal/config -run TestConfig
# Run with race detector (recommended for concurrency changes)
go test -race ./internal/configCover new work with end-to-end tests, not unit tests. A change to the compiler, an engine, the analysis core or codegen is exercised by running sqlc the way a user does — a schema, a query file and a committed golden output — so the test says what sqlc produces rather than what an internal function returns. Internal APIs move around; the SQL that goes in and the output that comes out is the contract worth pinning down.
Adding coverage means adding a directory under /internal/endtoend/testdata/,
not a *_test.go next to the code. Reach for a unit test only when the
behavior genuinely cannot be reached through the CLI, and say why in the test.
Some *_test.go files predate this and remain; they are not a precedent for
new ones.
- Location:
/internal/endtoend/ - Requirements:
--tags=examplesflag and running databases - Tests:
TestExamples- Main end-to-end testsTestReplay- Replay testsTestFormat- Code formatting testsTestJsonSchema- JSON schema validationTestExamplesVet- Static analysis tests
A case is a directory holding the inputs and the expected output. exec.json
names the command and its arguments — omit it and the case runs generate,
comparing the generated files against the ones committed alongside; give it
{"command": "analyze", "args": [...]} and the case compares the command's
stdout against stdout.txt. A case that is expected to fail commits its
stderr.txt. Regenerate a golden by running the command in its directory and
writing the output back over the committed file.
TestReplay runs the whole corpus once per context. base runs each case as
committed and managed-db reruns it against a live database, so a context can
change the config a case is generated with and the experiments it is generated
under. A case restricts itself to some of them with "contexts": [...] in its
exec.json, and commits per-context expected errors as stderr/<context>.txt.
There is only one set of committed golden files, so every context is expected
to generate identical code.
The core context generates every case through the analysis core
(SQLCEXPERIMENT=coreanalyzer). The two paths still disagree, so it is opt-in
and needs no database:
SQLC_TEST_CORE=1 go test ./internal/endtoend -run 'TestReplay/core'Go aborts a test binary on panic, so a case that panics the core analyzer ends
the run early. Run a subset to get past one (-run 'TestReplay/core/^select').
- Location:
/examples/directory - Requirements: Tagged with "examples", requires live databases
- Databases: PostgreSQL, MySQL, SQLite examples
The docker-compose.yml provides test databases:
-
PostgreSQL 16 - Port 5432
- User:
postgres - Password:
mysecretpassword - Database:
postgres
- User:
-
MySQL 9 - Port 3306
- User:
root - Password:
mysecretpassword - Database:
dinotest
- User:
make test # Basic unit tests only
make test-examples # Tests with examples tag
make build-endtoend # Build end-to-end test data
make test-ci # Full CI suite (examples + endtoend + vet)
make vet # Run go vet
make start # Start database containers- File:
.github/workflows/ci.yml - Go Version: 1.26.4
- Database Setup: Uses
sqlc-test-setup(not Docker) to install and start PostgreSQL and MySQL directly on the runner - Test Command:
gotestsum --junitfile junit.xml -- --tags=examples -timeout 20m ./... - Additional Checks:
govulncheckfor vulnerability scanning
# Build main sqlc binary for development
go build -o ~/go/bin/sqlc-dev ./cmd/sqlc
# Build JSON plugin (required for some tests)
go build -o ~/go/bin/sqlc-gen-json ./cmd/sqlc-gen-jsonYou can override database connections via environment variables:
POSTGRESQL_SERVER_URI="postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable"
MYSQL_SERVER_URI="root:mysecretpassword@tcp(127.0.0.1:3306)/mysql?multiStatements=true&parseTime=true"/cmd/- Main binaries (sqlc, sqlc-gen-json, sqlc-test-setup)/internal/cmd/- Command implementations (vet, generate, etc.)/internal/engine/- Database engine implementations/postgresql/- PostgreSQL parser and converter/dolphin/- MySQL parser (uses TiDB parser)/sqlite/- SQLite parser/duckdb/- DuckDB 2.0 parser (uses darkwing, the pure Go port of DuckDB's PEG parser); its dialect seeds are generated by/internal/tools/sqlc-duckdb-genfrom a live DuckDB CLI<engine>/dialect/- The engine's type system and standard library, as JSONL read by/internal/core/seed
/internal/core/- The analysis core: catalog, analyzer and dialect seeds/internal/compiler/- Query compilation logic/internal/codegen/- Code generation for different languages/internal/config/- Configuration file parsing/internal/endtoend/- End-to-end tests/internal/sqltest/- Test database setup (Docker, native, local detection)/examples/- Example projects for testing
/Makefile- Build and test targets/docker-compose.yml- Database services for testing/.github/workflows/ci.yml- CI configuration
If you see errors about storage.googleapis.com, the Go proxy may be unreachable. Use GOPROXY=direct go mod download to fetch modules directly from source.
End-to-end tests can take a while. Use longer timeouts:
go test -timeout 20m --tags=examples ./...Always run tests with the race detector when working on concurrent code:
go test -race ./...If using Docker:
docker compose ps
docker compose up -dIf using sqlc-test-setup:
go run ./cmd/sqlc-test-setup start- Run tests before committing:
go test --tags=examples -timeout 20m ./... - Cover new behavior end to end: Add a case under
/internal/endtoend/testdata/ - Check for race conditions: Use
-raceflag when testing concurrent code - Iterate on one case:
go test ./internal/endtoend -run 'TestReplay/base/<case>' - Read existing cases:
/internal/endtoend/testdata/has one per feature
- Feature branches should start with
claude/for Claude Code work - Branch names should be descriptive and end with the session ID
git add <files>
git commit -m "Brief description of changes"
git push -u origin <branch-name>git checkout main
git pull origin main
git checkout <feature-branch>
git rebase main
git push --force-with-lease origin <feature-branch>- Main Documentation:
/docs/ - Development Guide:
/docs/guides/development.md - CI Configuration:
/.github/workflows/ci.yml - Docker Compose:
/docker-compose.yml