sqlc · git:20260914.9932053 · 2026-09-14 · sha256 2e8feffdccaeb82f
sqlc git:20260914.9932053A
Immutable. This exact content is served forever at /api/v1/blob/2e8feffdccaeb82f.
---
name: sqlc
description: Enforce sqlc codegen conventions for Go + PostgreSQL. Use when editing sqlc.yaml, **/queries/*.sql, or generated *.sql.go, or when the user mentions sqlc, codegen, Querier interface, db.Queries, sqlc generate, or sqlc vet. Keeps SQL as the source of truth, routes every call through the generated Querier, uses v2 config, and regenerates rather than hand-editing generated files.
paths:
- "**/sqlc.yaml"
- "**/sqlc.yml"
- "**/queries/*.sql"
- "**/queries/**/*.sql"
allowed-tools:
- Read
- Grep
---
> Targets sqlc 1.31+ · verified 2026-09 (latest v1.31.1, released 2026-04-22).
This skill enforces sqlc 1.31+ conventions for Go projects with PostgreSQL. The rule: SQL is the source of truth, Go calls go through the generated `Querier` interface only.
Apply only when sqlc is configured (`sqlc.yaml` exists). If the project uses `sqlx` / raw `database/sql` everywhere, **STOP** and ask the user before introducing sqlc.
## sqlc.yaml requirements
```yaml
version: "2"
sql:
- engine: postgresql
queries: db/queries
schema: db/migrations
gen:
go:
package: db
out: db/sqlc
emit_interface: true
emit_json_tags: true
emit_db_tags: false
emit_pointers_for_null_types: true
emit_empty_slices: true
sql_package: pgx/v5
```
- **`version: "2"`** required. v1 is the older format, and plugins only work with v2.
- **`emit_interface: true`** generates the `Querier` interface so handlers can mock easily.
- **`sql_package: pgx/v5`** preferred over `database/sql` for PostgreSQL.
- **`emit_pointers_for_null_types: true`** also turns nullable enum columns into pointers since v1.31; set `emit_pointers_for_null_enum_types: false` to keep the `NullXxx` wrapper structs.
- **`schema:` points to migrations**, not to a hand-written schema file. sqlc parses migrations to derive the schema.
## Query naming convention
| Prefix | Returns | Example |
|---|---|---|
| `Get` | exactly one row (`:one`); zero value + `ErrNoRows` if missing | `GetUserByID` |
| `List` | many rows | `ListUsersByOrg` |
| `Count` | scalar count | `CountActiveUsers` |
| `Create` | inserts and returns the created row | `CreateUser` |
| `Update` | updates and returns the updated row | `UpdateUserEmail` |
| `Delete` | deletes, no return | `DeleteUser` |
Each query starts with `-- name: <PascalCase> :one|:many|:exec|:execrows`:
```sql
-- name: GetUserByID :one
SELECT id, email, created_at FROM users WHERE id = $1;
-- name: ListUsersByOrg :many
SELECT id, email FROM users WHERE org_id = $1 ORDER BY created_at DESC;
-- name: CreateUser :one
INSERT INTO users (email, org_id) VALUES ($1, $2) RETURNING *;
```
## Forbidden patterns
- Hand-written `db.Query("SELECT ...")` / `db.Exec("INSERT ...")` from Go when sqlc could generate it. Add a query to `db/queries/*.sql` and run `sqlc generate`.
- `version: "1"` in `sqlc.yaml`. Migrate to v2.
- Modifying generated files in `db/sqlc/` (e.g. `models.go`, `queries.sql.go`). They will be overwritten. Modify the SQL source and regenerate.
- Defining the schema twice (once in migrations, once in a separate `schema.sql`). Point sqlc at migrations.
- Mixing pgx and `database/sql` in the same project. Pick one—`pgx/v5` preferred.
- String concatenation to build dynamic queries. Use `sqlc.narg()` for optional parameters (e.g. `coalesce(sqlc.narg('name'), name)`), or write multiple named queries.
- Query names that do not start with one of the prefixes above. `FetchUser`, `RetrieveUserByEmail` — pick the canonical prefix.
- A new query when an existing one in `db/queries/*.sql` already covers the use case. grep query names and the underlying `SELECT/INSERT/UPDATE/DELETE` first; reuse or extend if found.
## When sqlc cannot express what you need
sqlc handles most CRUD plus aggregates, CTEs, and window functions. If you need:
- runtime dynamic column selection
- variable-arity `IN ()` clauses without an array
- prepared statement caching control
**STOP** and report:
> Need to express [query shape]. sqlc patterns checked: [`sqlc.arg`, `sqlc.narg`, `sqlc.embed`, `ANY($1::int[])`]. None covers [specific gap]. Approve one of: (A) restructure with `ANY(?::type[])` for `IN`, (B) add a hand-written method on the `*db.Queries` receiver in a separate non-generated file (with comment), (C) different approach.
## After every SQL change
1. Run `sqlc generate`
2. Run `sqlc diff` (reports generated code that is stale or hand-edited)
3. Run `sqlc vet` if `rules:` are configured in `sqlc.yaml` (it runs the lint rules defined there)
4. Commit both the SQL change and the generated Go output
5. Run `go test ./...`
## Verification (grep after every sqlc-related change)
```bash
# config version
find . \( -name 'sqlc.yaml' -o -name 'sqlc.yml' \) -not -path '*/node_modules/*' -exec grep -HnE '^version:[[:space:]]*"?1' {} +
# hand-written SQL calls (should be sqlc queries)
grep -rniE '\.(Query|QueryRow|Exec)(Context)?\(([^,"`]*,[[:space:]]*)?["`][[:space:]]*(SELECT|INSERT|UPDATE|DELETE|WITH)' --include='*.go' .
# query naming convention
grep -nE '^-- name:[[:space:]]+[A-Za-z0-9_]+' db/queries/*.sql | grep -vE ':[[:space:]]+(Get|List|Count|Create|Update|Delete)[A-Za-z0-9_]+'
```
Reference: https://docs.sqlc.dev/en/latest/reference/config.html