snapshot of https://github.com/sqlc-dev/sqlc.git (main, 2026-09-16; blobs > 131072B omitted)
Some checks failed
kotlin / test (push) Has been skipped
python / test (push) Has been skipped
typescript / test (push) Has been skipped
go / build darwin/amd64 (push) Has been cancelled
go / build linux/amd64 (push) Has been cancelled
go / build windows/amd64 (push) Has been cancelled
go / build darwin/arm64 (push) Has been cancelled
go / build linux/arm64 (push) Has been cancelled
go / build windows/arm64 (push) Has been cancelled
go / test (push) Has been cancelled
go / vuln_check (push) Has been cancelled

This commit is contained in:
upstream-mirror
2026-09-16 02:17:34 +05:00
commit 0f4df23290
7340 changed files with 238743 additions and 0 deletions

88
.github/ISSUE_TEMPLATE/BUG_REPORT.yml vendored Normal file
View File

@@ -0,0 +1,88 @@
name: Bug Report
description: File a bug report
labels: [bug]
body:
- type: dropdown
id: version
attributes:
label: Version
description: What version of sqlc are you running? If you don't know, run `sqlc version`.
multiple: false
options:
- 1.31.1
- 1.31.0
- 1.30.0
- 1.29.0
- 1.28.0
- 1.27.0
- 1.26.0
- 1.25.0
- Other
validations:
required: true
- type: textarea
id: what-happened
attributes:
label: What happened?
description: Also tell us, what did you expect to happen?
placeholder: Tell us what you see!
value: "A bug happened!"
validations:
required: true
- type: textarea
id: logs
attributes:
label: Relevant log output
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
render: shell
- type: textarea
id: schema
attributes:
label: Database schema
description: Please include definitions for the relevant database tables. This will be automatically formatted as SQL, so no need for backticks.
render: sql
- type: textarea
id: queries
attributes:
label: SQL queries
description: Please include the SQL queries causing issues. This will be automatically formatted as SQL, so no need for backticks.
render: sql
- type: textarea
id: config
attributes:
label: Configuration
description: Please include the sqlc.(yaml|yml) or sqlc.json file you using in your project. This will be automatically formatted, so no need for backticks.
render: yaml
- type: input
id: playground
attributes:
label: Playground URL
description: "Link to a reproduction of the issue on the sqlc playground"
placeholder: "https://play.sqlc.dev/"
- type: dropdown
id: os
attributes:
label: What operating system are you using?
multiple: true
options:
- Linux
- Windows
- macOS
- type: dropdown
id: engines
attributes:
label: What database engines are you using?
multiple: true
options:
- MySQL
- PostgreSQL
- SQLite
- type: dropdown
id: languages
attributes:
label: What type of code are you generating?
multiple: true
options:
- Go
- Python
- Kotlin

View File

@@ -0,0 +1,30 @@
name: Feature Request
description: Request a new feature or a change to an existing feature
labels: [enhancement]
body:
- type: textarea
id: feature
attributes:
label: What do you want to change?
placeholder: Tell us what you want
value: "Free unicorns!"
validations:
required: true
- type: dropdown
id: engines
attributes:
label: What database engines need to be changed?
multiple: true
options:
- PostgreSQL
- MySQL
- SQLite
- type: dropdown
id: languages
attributes:
label: What programming language backends need to be changed?
multiple: true
options:
- Go
- Python
- Kotlin

20
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
version: 2
updates:
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "daily"
groups:
production-dependencies:
dependency-type: "production"
development-dependencies:
dependency-type: "development"
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"

18
.github/workflows/buf.yml vendored Normal file
View File

@@ -0,0 +1,18 @@
name: buf
on:
pull_request:
paths-ignore:
- 'docs/**'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: bufbuild/buf-setup-action@v1
with:
# buf-setup-action resolves the release download URL through the
# GitHub API. Without a token that request is anonymous and shares
# a rate limit with every other job on the runner's IP, so setup
# intermittently fails with "API rate limit exceeded".
github_token: ${{ github.token }}
- uses: bufbuild/buf-lint-action@v1

19
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
name: build
on:
workflow_dispatch:
jobs:
build:
strategy:
matrix:
os: [ubuntu-24.04, macos-14, windows-2022]
name: build ${{ matrix.os }}
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version: '1.26.5'
- name: install ./...
run: go build ./...
env:
CGO_ENABLED: "0"

31
.github/workflows/ci-kotlin.yml vendored Normal file
View File

@@ -0,0 +1,31 @@
name: kotlin
on:
push:
branches:
- main
paths-ignore:
- 'docs/**'
pull_request:
paths-ignore:
- 'docs/**'
jobs:
build:
if: false
name: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version: '1.26.5'
- name: install ./...
run: go install ./...
- uses: actions/checkout@v7
with:
repository: sqlc-dev/sqlc-gen-kotlin
path: kotlin
- run: make test
working-directory: kotlin
- run: sqlc diff
working-directory: kotlin/examples

30
.github/workflows/ci-python.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
name: python
on:
push:
branches:
- main
paths-ignore:
- 'docs/**'
pull_request:
paths-ignore:
- 'docs/**'
jobs:
build:
if: false
name: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version: '1.26.5'
- name: install ./...
run: go install ./...
- uses: actions/checkout@v7
with:
repository: sqlc-dev/sqlc-gen-python
path: python
- run: make test
working-directory: python
- run: sqlc diff
working-directory: python/examples

30
.github/workflows/ci-typescript.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
name: typescript
on:
push:
branches:
- main
paths-ignore:
- 'docs/**'
pull_request:
paths-ignore:
- 'docs/**'
jobs:
build:
if: false
name: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version: '1.26.5'
- name: install ./...
run: go install ./...
- uses: actions/checkout@v7
with:
repository: sqlc-dev/sqlc-gen-typescript
path: typescript
# v0.1.3
ref: daaf539092421adc15f6c3164279a3470716b560
- run: sqlc diff
working-directory: typescript/examples

74
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,74 @@
name: go
on:
push:
branches:
- main
pull_request:
jobs:
build:
strategy:
matrix:
goos: [darwin, linux, windows]
goarch: [amd64, arm64]
name: build ${{ matrix.goos }}/${{ matrix.goarch }}
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version: '1.26.5'
- run: go build ./...
env:
CGO_ENABLED: "0"
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
test:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version: '1.26.5'
- name: install gotestsum
run: go install gotest.tools/gotestsum@latest
- name: install sqlc-gen-test
run: go install github.com/sqlc-dev/sqlc-gen-test@v0.1.0
- name: install test-json-process-plugin
run: go install ./scripts/test-json-process-plugin/
- name: install ./...
run: go install ./...
env:
CGO_ENABLED: "0"
- name: build internal/endtoend
run: go build ./...
working-directory: internal/endtoend/testdata
env:
CGO_ENABLED: "0"
- name: install databases
run: go run ./cmd/sqlc-test-setup install
- name: start databases
run: go run ./cmd/sqlc-test-setup start
- name: test ./...
run: gotestsum --junitfile junit.xml -- --tags=examples -timeout 20m -failfast ./...
env:
CI_SQLC_PROJECT_ID: ${{ secrets.CI_SQLC_PROJECT_ID }}
CI_SQLC_AUTH_TOKEN: ${{ secrets.CI_SQLC_AUTH_TOKEN }}
SQLC_AUTH_TOKEN: ${{ secrets.CI_SQLC_AUTH_TOKEN }}
POSTGRESQL_SERVER_URI: "postgres://postgres:postgres@127.0.0.1:5432/postgres?sslmode=disable"
MYSQL_SERVER_URI: "root:mysecretpassword@tcp(127.0.0.1:3306)/mysql?multiStatements=true&parseTime=true"
CGO_ENABLED: "0"
vuln_check:
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- uses: golang/govulncheck-action@v1

192
.github/workflows/gen.yml vendored Normal file
View File

@@ -0,0 +1,192 @@
name: gen
on:
workflow_dispatch:
jobs:
postgresql:
name: generate postgresql dialect
runs-on: ubuntu-24.04
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
ports:
- 5432:5432
# needed because the postgres container does not provide a healthcheck
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: internal/goldeneye/go.mod
check-latest: true
- run: go run ./cmd/goldeneye generate postgresql
working-directory: internal/goldeneye
env:
POSTGRESQL_SERVER_URI: postgres://postgres:postgres@localhost:${{ job.services.postgres.ports['5432'] }}/postgres?sslmode=disable
- name: Save results
uses: actions/upload-artifact@v7
with:
name: dialect-postgresql
path: internal/engine/postgresql/dialect
- name: Fail if the committed dialect differs
run: git add -N internal/engine/postgresql && git diff --exit-code --stat -- internal/engine/postgresql
mysql:
name: generate mysql dialect
runs-on: ubuntu-24.04
services:
mysql:
image: mysql:26.7
env:
MYSQL_ROOT_PASSWORD: mysecretpassword
ports:
- 3306:3306
options: --health-cmd "mysqladmin ping -h 127.0.0.1 -pmysecretpassword" --health-interval 10s --health-timeout 5s --health-retries 5
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: internal/goldeneye/go.mod
check-latest: true
- run: go run ./cmd/goldeneye generate mysql
working-directory: internal/goldeneye
env:
MYSQL_SERVER_URI: root:mysecretpassword@tcp(localhost:${{ job.services.mysql.ports['3306'] }})/mysql
- name: Save results
uses: actions/upload-artifact@v7
with:
name: dialect-mysql
path: internal/engine/dolphin/dialect
- name: Fail if the committed dialect differs
run: git add -N internal/engine/dolphin && git diff --exit-code --stat -- internal/engine/dolphin
clickhouse:
name: generate clickhouse dialect
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: internal/goldeneye/go.mod
check-latest: true
- run: go run ./cmd/goldeneye install clickhouse
working-directory: internal/goldeneye
- run: go run ./cmd/goldeneye generate clickhouse
working-directory: internal/goldeneye
- name: Save results
uses: actions/upload-artifact@v7
with:
name: dialect-clickhouse
path: internal/engine/clickhouse/dialect
- name: Fail if the committed dialect differs
run: git add -N internal/engine/clickhouse && git diff --exit-code --stat -- internal/engine/clickhouse
sqlite:
name: generate sqlite dialect
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: internal/goldeneye/go.mod
check-latest: true
- run: go run ./cmd/goldeneye install sqlite
working-directory: internal/goldeneye
- run: go run ./cmd/goldeneye generate sqlite
working-directory: internal/goldeneye
- name: Save results
uses: actions/upload-artifact@v7
with:
name: dialect-sqlite
path: internal/engine/sqlite/dialect
- name: Fail if the committed dialect differs
run: git add -N internal/engine/sqlite && git diff --exit-code --stat -- internal/engine/sqlite
mssql:
name: generate mssql dialect
runs-on: ubuntu-24.04
services:
mssql:
image: mcr.microsoft.com/mssql/server:2025-latest
env:
ACCEPT_EULA: Y
MSSQL_SA_PASSWORD: Mysecretpassword1!
ports:
- 1433:1433
options: --health-cmd "/opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P 'Mysecretpassword1!' -Q 'SELECT 1'" --health-interval 10s --health-timeout 5s --health-retries 10
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: internal/goldeneye/go.mod
check-latest: true
- run: go run ./cmd/goldeneye generate mssql
working-directory: internal/goldeneye
env:
MSSQL_SERVER_URI: sqlserver://sa:Mysecretpassword1!@localhost:${{ job.services.mssql.ports['1433'] }}?encrypt=disable
- name: Save results
uses: actions/upload-artifact@v7
with:
name: dialect-mssql
path: internal/engine/mssql/dialect
- name: Fail if the committed dialect differs
run: git add -N internal/engine/mssql && git diff --exit-code --stat -- internal/engine/mssql
spanner:
name: generate googlesql dialect
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: internal/goldeneye/go.mod
check-latest: true
# Spanner Omni is run the way its quickstart runs it, on the host
# network, since a service container cannot be given a command.
- name: Start Spanner Omni
run: |
docker run -d --name spanneromni --network host us-docker.pkg.dev/spanner-omni/images/spanner-omni:2026.r2.1-beta start-single-server
for i in $(seq 1 60); do
if docker exec spanneromni /google/spanner/bin/spanner databases list >/dev/null 2>&1; then exit 0; fi
sleep 5
done
docker logs spanneromni
exit 1
- run: go run ./cmd/goldeneye generate spanner
working-directory: internal/goldeneye
env:
SPANNER_SERVER_URI: localhost:15000
- name: Save results
uses: actions/upload-artifact@v7
with:
name: dialect-googlesql
path: internal/engine/googlesql/dialect
- name: Fail if the committed dialect differs
run: git add -N internal/engine/googlesql && git diff --exit-code --stat -- internal/engine/googlesql
duckdb:
name: generate duckdb dialect
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: internal/goldeneye/go.mod
check-latest: true
# The current build of DuckDB's v2.0 preview channel, until 2.0 is
# released: a later build than the dialect was generated from shows up
# as a difference.
- run: go run ./cmd/goldeneye install duckdb
working-directory: internal/goldeneye
- run: go run ./cmd/goldeneye generate duckdb
working-directory: internal/goldeneye
- name: Save results
uses: actions/upload-artifact@v7
with:
name: dialect-duckdb
path: internal/engine/duckdb/dialect
- name: Fail if the committed dialect differs
run: git add -N internal/engine/duckdb && git diff --exit-code --stat -- internal/engine/duckdb

11
.gitignore vendored Normal file
View File

@@ -0,0 +1,11 @@
/.idea/
__pycache__
.DS_Store
.*.swp
# Devenv
.envrc
.direnv
.devenv*
devenv.local.nix

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"files.insertFinalNewline": true
}

340
CLAUDE.md Normal file
View File

@@ -0,0 +1,340 @@
# Claude Code Development Guide for sqlc
This document provides essential information for working with the sqlc codebase, including testing, development workflow, and code structure.
## Quick Start
### Prerequisites
- **Go 1.26.4+** - Required for building and testing
- **Docker & Docker Compose** - Required for integration tests with databases (local development)
- **Git** - For version control
## Database Setup with sqlc-test-setup
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.
### Install databases
```bash
go run ./cmd/sqlc-test-setup install
```
This will:
- Configure the apt proxy (if `http_proxy` is set, e.g. in Claude Code remote environments)
- Install PostgreSQL via apt
- Download and install MySQL 26.7 from Oracle's deb bundle
- Resolve all dependencies automatically
- Skip anything already installed
### Start databases
```bash
go run ./cmd/sqlc-test-setup start
```
This will:
- Start PostgreSQL and configure password auth (`postgres`/`postgres`)
- Start MySQL via `mysqld_safe` and 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`
### Run tests
```bash
# Full test suite (requires databases running)
go test --tags=examples -timeout 20m ./...
```
## Running Tests
### Basic Unit Tests (No Database Required)
```bash
go test ./...
```
### Full Test Suite with Docker (Local Development)
```bash
docker compose up -d
go test --tags=examples -timeout 20m ./...
```
### Full Test Suite without Docker (Remote / CI)
```bash
go run ./cmd/sqlc-test-setup install
go run ./cmd/sqlc-test-setup start
go test --tags=examples -timeout 20m ./...
```
### Running Specific Tests
```bash
# 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/config
```
## Testing Strategy
**Cover 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.
### End-to-End Tests
- **Location:** `/internal/endtoend/`
- **Requirements:** `--tags=examples` flag and running databases
- **Tests:**
- `TestExamples` - Main end-to-end tests
- `TestReplay` - Replay tests
- `TestFormat` - Code formatting tests
- `TestJsonSchema` - JSON schema validation
- `TestExamplesVet` - 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.json` (or `stdout.txt` for a command that does not
print JSON). 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:
```bash
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'`).
### Dialect Checks
The dialect seeds under `/internal/engine/<engine>/dialect/` are generated
from a live database by `/internal/goldeneye`, a nested module, and its tests
verify the committed files against one byte for byte. The same module checks
the `analyze_*` cases under `/internal/endtoend/testdata/` against what the
database itself reports for them, so a `fixture.sql` next to a case's schema
gives the queries rows to run against. ClickHouse, DuckDB, MySQL, SQLite,
SQL Server and Spanner have the check today; engines whose database is not
available skip.
```bash
cd internal/goldeneye
go run ./cmd/goldeneye install clickhouse # download the pinned clickhouse binary once
go run ./cmd/goldeneye install duckdb # download the current DuckDB 2.0 preview build once
go run ./cmd/goldeneye install sqlite # build the pinned sqlite3 shells once; needs a C compiler
POSTGRESQL_SERVER_URI="postgres://postgres:postgres@127.0.0.1:5432/postgres?sslmode=disable" \
MYSQL_SERVER_URI="root:mysecretpassword@tcp(127.0.0.1:3306)/mysql" \
MSSQL_SERVER_URI="sqlserver://sa:Mysecretpassword1!@127.0.0.1:1433?encrypt=disable" \
SPANNER_SERVER_URI="localhost:15000" go test ./...
go run ./cmd/goldeneye generate postgresql # rewrite the files after a change
```
The checks are not part of CI. The `gen` workflow
(`.github/workflows/gen.yml`) generates the files on demand and uploads
them as an artifact.
### Example Tests
- **Location:** `/examples/` directory
- **Requirements:** Tagged with "examples", requires live databases
- **Databases:** PostgreSQL, MySQL, SQLite examples
## Database Services
The `docker-compose.yml` provides test databases:
- **PostgreSQL 16** - Port 5432
- User: `postgres`
- Password: `mysecretpassword`
- Database: `postgres`
- **MySQL 26.7** - Port 3306
- User: `root`
- Password: `mysecretpassword`
- Database: `dinotest`
## Makefile Targets
```bash
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
```
## CI/CD Configuration
### GitHub Actions Workflow
- **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:** `govulncheck` for vulnerability scanning
## Development Workflow
### Building Development Versions
```bash
# 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-json
```
### Environment Variables for Tests
You can override database connections via environment variables:
```bash
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"
```
## Code Structure
### Key Directories
- `/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)
- `<engine>/dialect/` - The engine's type system and standard library, as
JSONL read by `/internal/core/seed`; the generated parts come from
`/internal/goldeneye`
- `/internal/goldeneye/` - Nested module that generates the dialect seeds
under `/internal/engine/<engine>/dialect/` from a live database, checks
the committed ones against it, and checks the analyze cases under
`/internal/endtoend/testdata/` against what the database reports, one
package per engine; see its README
- `/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
### Important Files
- `/Makefile` - Build and test targets
- `/docker-compose.yml` - Database services for testing
- `/.github/workflows/ci.yml` - CI configuration
## Common Issues & Solutions
### Network Connectivity Issues
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.
### Test Timeouts
End-to-end tests can take a while. Use longer timeouts:
```bash
go test -timeout 20m --tags=examples ./...
```
### Race Conditions
Always run tests with the race detector when working on concurrent code:
```bash
go test -race ./...
```
### Database Connection Failures
If using Docker:
```bash
docker compose ps
docker compose up -d
```
If using sqlc-test-setup:
```bash
go run ./cmd/sqlc-test-setup start
```
## Tips for Contributors
1. **Run tests before committing:** `go test --tags=examples -timeout 20m ./...`
2. **Cover new behavior end to end:** Add a case under `/internal/endtoend/testdata/`
3. **Check for race conditions:** Use `-race` flag when testing concurrent code
4. **Iterate on one case:** `go test ./internal/endtoend -run 'TestReplay/base/<case>'`
5. **Read existing cases:** `/internal/endtoend/testdata/` has one per feature
## Git Workflow
### Branch Naming
- Feature branches should start with `claude/` for Claude Code work
- Branch names should be descriptive and end with the session ID
### Committing Changes
```bash
git add <files>
git commit -m "Brief description of changes"
git push -u origin <branch-name>
```
### Rebasing
```bash
git checkout main
git pull origin main
git checkout <feature-branch>
git rebase main
git push --force-with-lease origin <feature-branch>
```
## Resources
- **Main Documentation:** `/docs/`
- **Development Guide:** `/docs/guides/development.md`
- **CI Configuration:** `/.github/workflows/ci.yml`
- **Docker Compose:** `/docker-compose.yml`

19
Dockerfile Normal file
View File

@@ -0,0 +1,19 @@
# STEP 1: Build sqlc
FROM golang:1.27.1 AS builder
COPY . /workspace
WORKDIR /workspace
ARG github_ref
ARG github_sha
ARG version
ENV GITHUB_REF=$github_ref
ENV GITHUB_SHA=$github_sha
ENV VERSION=$version
RUN go run scripts/release.go -docker
# STEP 2: Build a tiny image
FROM gcr.io/distroless/base-debian12
COPY --from=builder /workspace/sqlc /workspace/sqlc
ENTRYPOINT ["/workspace/sqlc"]

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Riza, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

60
Makefile Normal file
View File

@@ -0,0 +1,60 @@
.PHONY: build build-endtoend test test-ci test-examples test-endtoend test-goldeneye start psql mysqlsh proto
build:
go build ./...
install:
go install ./...
test:
go test ./...
test-managed:
MYSQL_SERVER_URI="invalid" POSTGRESQL_SERVER_URI="postgres://postgres:mysecretpassword@localhost:5432/postgres" go test -v ./...
vet:
go vet ./...
test-examples:
go test --tags=examples ./...
build-endtoend:
cd ./internal/endtoend/testdata && go build ./...
test-ci: test-examples build-endtoend vet
sqlc-dev:
go build -o ~/bin/sqlc-dev ./cmd/sqlc/
goldeneye:
cd ./internal/goldeneye && go build -o ~/bin/goldeneye ./cmd/goldeneye
test-goldeneye:
cd ./internal/goldeneye && go test ./...
sqlc-gen-json:
go build -o ~/bin/sqlc-gen-json ./cmd/sqlc-gen-json
test-json-process-plugin:
go build -o ~/bin/test-json-process-plugin ./scripts/test-json-process-plugin/
start:
docker compose up -d
fmt:
go fmt ./...
psql:
PGPASSWORD=mysecretpassword psql --host=127.0.0.1 --port=5432 --username=postgres dinotest
mysqlsh:
mysqlsh --sql --user root --password mysecretpassword --database dinotest 127.0.0.1:3306
proto:
buf generate
remote-proto:
protoc \
--go_out=. --go_opt="Minternal/remote/gen.proto=github.com/sqlc-dev/sqlc/internal/remote" --go_opt=module=github.com/sqlc-dev/sqlc \
--go-grpc_out=. --go-grpc_opt="Minternal/remote/gen.proto=github.com/sqlc-dev/sqlc/internal/remote" --go-grpc_opt=module=github.com/sqlc-dev/sqlc \
internal/remote/gen.proto

48
NOTICE Normal file
View File

@@ -0,0 +1,48 @@
This distribution of sqlc includes software licensed under the Apache
License, Version 2.0, from projects that ship a NOTICE file. As required
by section 4(d) of that license, their attribution notices are reproduced
below.
================================================================================
github.com/tetratelabs/wazero
================================================================================
wazero
Copyright 2020-2023 wazero authors
================================================================================
google.golang.org/grpc
================================================================================
Copyright 2014 gRPC authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================================================
gopkg.in/yaml.v3
go.yaml.in/yaml/v3
================================================================================
Copyright 2011-2016 Canonical Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

59
README.md Normal file
View File

@@ -0,0 +1,59 @@
# sqlc: A SQL Compiler
![go](https://github.com/sqlc-dev/sqlc/workflows/go/badge.svg)
[![Go Report Card](https://goreportcard.com/badge/github.com/sqlc-dev/sqlc)](https://goreportcard.com/report/github.com/sqlc-dev/sqlc)
sqlc generates **type-safe code** from SQL. Here's how it works:
1. You write queries in SQL.
1. You run sqlc to generate code with type-safe interfaces to those queries.
1. You write application code that calls the generated code.
Check out [an interactive example](https://play.sqlc.dev/) to see it in action, and the [introductory blog post](https://conroy.org/introducing-sqlc) for the motivation behind sqlc.
## Overview
- [Documentation](https://docs.sqlc.dev)
- [Installation](https://docs.sqlc.dev/en/latest/overview/install.html)
- [Playground](https://play.sqlc.dev)
- [Website](https://sqlc.dev)
- [Downloads](https://downloads.sqlc.dev/)
- [Community](https://discord.gg/EcXzGe5SEs)
## Supported languages
- [sqlc-gen-go](https://github.com/sqlc-dev/sqlc-gen-go)
- [sqlc-gen-kotlin](https://github.com/sqlc-dev/sqlc-gen-kotlin)
- [sqlc-gen-python](https://github.com/sqlc-dev/sqlc-gen-python)
- [sqlc-gen-typescript](https://github.com/sqlc-dev/sqlc-gen-typescript)
Additional languages can be added via [plugins](https://docs.sqlc.dev/en/latest/reference/language-support.html#community-language-support).
## Sponsors
Development is possible thanks to our sponsors. If you would like to support sqlc,
please consider [sponsoring on GitHub](https://github.com/sponsors/kyleconroy).
<p align="center">
<a href="https://coder.com?utm_source=sqlc+readme"><img width=200 src="https://sqlc.dev/sponsors/coder-readme.png" alt="Coder.com" /></a>
<a href="https://mint.fun?utm_source=sqlc+readme"><img width=200 src="https://sqlc.dev/sponsors/mint-readme.png" alt="Mint.fun" /></a>
<a href="https://mux.com?utm_source=sqlc+readme"><img width=200 src="https://sqlc.dev/sponsors/mux-readme.png" alt="Mux.com" /></a>
</p>
<p align="center">
<a href="https://github.com/Cyberax">Cyberax</a> -
<a href="https://github.com/NaNuNaNu">NaNuNaNu</a> -
<a href="https://github.com/Stumble">Stumble</a> -
<a href="https://github.com/WestfalNamur">WestfalNamur</a> -
<a href="https://github.com/alecthomas">alecthomas</a> -
<a href="https://github.com/cameronnewman">cameronnewman</a> -
<a href="https://github.com/danielbprice">danielbprice</a> -
<a href="https://github.com/davherrmann">davherrmann</a> -
<a href="https://github.com/dvob">dvob</a> -
<a href="https://github.com/gilcrest">gilcrest</a> -
<a href="https://github.com/gzuidhof">gzuidhof</a> -
<a href="https://github.com/jeffreylo">jeffreylo</a> -
<a href="https://github.com/mmcloughlin">mmcloughlin</a> -
<a href="https://github.com/ryohei1216">ryohei1216</a> -
<a href="https://github.com/sgielen">sgielen</a>
</p>

12
buf.gen.yaml Normal file
View File

@@ -0,0 +1,12 @@
version: v1
managed:
enabled: true
go_package_prefix:
default: "github.com/sqlc-dev/sqlc/internal"
plugins:
- plugin: buf.build/protocolbuffers/go:v1.30.0
out: internal
opt: paths=source_relative
- plugin: buf.build/grpc/go:v1.3.0
out: internal
opt: paths=source_relative

2
buf.lock Normal file
View File

@@ -0,0 +1,2 @@
# Generated by buf. DO NOT EDIT.
version: v1

3
buf.work.yaml Normal file
View File

@@ -0,0 +1,3 @@
version: v1
directories:
- protos

56
cliff.toml Normal file
View File

@@ -0,0 +1,56 @@
# configuration file for git-cliff (0.1.0)
[changelog]
# changelog header
header = """
# Changelog
All notable changes to this project will be documented in this file.\n
"""
# template for the changelog body
# https://tera.netlify.app/docs/#introduction
body = """
{% if version %}\
## [{{ version | trim_start_matches(pat="v") }}](https://github.com/sqlc-dev/sqlc/releases/tag/{{ version }})
{% else %}\
## [unreleased]
{% endif %}\
Released {{ timestamp | date(format="%Y-%m-%d") }}
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | upper_first }}
{% for commit in commits %}
- {%if commit.scope %}({{commit.scope}}) {% endif %}{{ commit.message | upper_first }}\
{% endfor %}
{% endfor %}\n
"""
# remove the leading and trailing whitespaces from the template
trim = true
# changelog footer
footer = """
<!-- generated by git-cliff -->
"""
[git]
# allow only conventional commits
# https://www.conventionalcommits.org
conventional_commits = true
# regex for parsing and grouping commits
commit_parsers = [
{ message = "^feat", group = "Features"},
{ message = "^fix", group = "Bug Fixes"},
{ message = "^doc", group = "Documentation"},
{ message = "^perf", group = "Performance"},
{ message = "^refactor", group = "Refactor"},
{ message = "^style", group = "Styling"},
{ message = "^test", group = "Testing"},
{ message = "^chore\\(release\\): prepare for", skip = true},
{ message = "^chore", group = "Miscellaneous Tasks"},
{ body = ".*security", group = "Security"},
]
# filter out the commits that are not matched by commit parsers
filter_commits = false
# glob pattern for matching git tags
tag_pattern = "v[0-9]*"
# regex for skipping tags
skip_tags = "v*beta"

47
cmd/sqlc-gen-json/main.go Normal file
View File

@@ -0,0 +1,47 @@
package main
import (
"bufio"
"context"
"fmt"
"io"
"os"
"github.com/sqlc-dev/sqlc/internal/codegen/json"
"github.com/sqlc-dev/sqlc/internal/plugin"
"google.golang.org/protobuf/proto"
)
func main() {
if err := run(); err != nil {
fmt.Fprintf(os.Stderr, "error generating JSON: %s", err)
os.Exit(2)
}
}
func run() error {
var req plugin.GenerateRequest
reqBlob, err := io.ReadAll(os.Stdin)
if err != nil {
return err
}
if err := proto.Unmarshal(reqBlob, &req); err != nil {
return err
}
resp, err := json.Generate(context.Background(), &req)
if err != nil {
return err
}
respBlob, err := proto.Marshal(resp)
if err != nil {
return err
}
w := bufio.NewWriter(os.Stdout)
if _, err := w.Write(respBlob); err != nil {
return err
}
if err := w.Flush(); err != nil {
return err
}
return nil
}

717
cmd/sqlc-test-setup/main.go Normal file
View File

@@ -0,0 +1,717 @@
package main
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"time"
)
const (
// pgVersion is the PostgreSQL version to install.
pgVersion = "18.2.0"
)
// pgBinary contains the download information for a PostgreSQL binary release.
type pgBinary struct {
URL string
SHA256 string
}
// pgBinaries maps "<GOOS>/<GOARCH>" to the corresponding binary download info.
var pgBinaries = map[string]pgBinary{
"linux/amd64": {
URL: "https://github.com/theseus-rs/postgresql-binaries/releases/download/" + pgVersion + "/postgresql-" + pgVersion + "-x86_64-unknown-linux-gnu.tar.gz",
SHA256: "cc2674e1641aa2a62b478971a22c131a768eb783f313e6a3385888f58a604074",
},
"linux/arm64": {
URL: "https://github.com/theseus-rs/postgresql-binaries/releases/download/" + pgVersion + "/postgresql-" + pgVersion + "-aarch64-unknown-linux-gnu.tar.gz",
SHA256: "8b415a11c7a5484e5fbf7a57fca71554d2d1d7acd34faf066606d2fee1261854",
},
}
func main() {
log.SetFlags(log.Ltime)
log.SetPrefix("[sqlc-test-setup] ")
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "usage: sqlc-test-setup <install|start>")
os.Exit(1)
}
switch os.Args[1] {
case "install":
if err := runInstall(); err != nil {
log.Fatalf("install failed: %s", err)
}
case "start":
if err := runStart(); err != nil {
log.Fatalf("start failed: %s", err)
}
default:
fmt.Fprintf(os.Stderr, "unknown command: %s\nusage: sqlc-test-setup <install|start>\n", os.Args[1])
os.Exit(1)
}
}
// run executes a command with verbose logging, streaming output to stderr.
func run(name string, args ...string) error {
log.Printf("exec: %s %s", name, strings.Join(args, " "))
cmd := exec.Command(name, args...)
cmd.Stdout = os.Stderr
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
return cmd.Run()
}
// runOutput executes a command and returns its combined output.
func runOutput(name string, args ...string) (string, error) {
log.Printf("exec: %s %s", name, strings.Join(args, " "))
cmd := exec.Command(name, args...)
out, err := cmd.CombinedOutput()
return string(out), err
}
// commandExists checks if a binary is available in PATH.
func commandExists(name string) bool {
_, err := exec.LookPath(name)
return err == nil
}
// The MySQL release the tests run against. Everything that names a MySQL
// version — the docker-compose service, the goldeneye dialect generator and
// its gen workflow — is pinned to the same release.
const (
mysqlMajor = 26
mysqlVersion = "26.7.0"
)
// isMySQLVersionOK checks if the mysqld --version output indicates the
// pinned major release or a later one.
// Example version string: "/usr/sbin/mysqld Ver 8.0.44-0ubuntu0.24.04.2 ..."
func isMySQLVersionOK(versionOutput string) bool {
// Look for "Ver X.Y.Z" pattern
fields := strings.Fields(versionOutput)
for i, f := range fields {
if strings.EqualFold(f, "Ver") && i+1 < len(fields) {
ver := strings.Split(fields[i+1], ".")
if len(ver) > 0 {
major, err := strconv.Atoi(ver[0])
if err != nil {
return false
}
return major >= mysqlMajor
}
}
}
return false
}
// pgBaseDir returns the sqlc-specific directory where PostgreSQL is installed,
// using the user's cache directory (~/.cache/sqlc/postgresql on Linux).
func pgBaseDir() string {
cacheDir, err := os.UserCacheDir()
if err != nil {
cacheDir = filepath.Join(os.Getenv("HOME"), ".cache")
}
return filepath.Join(cacheDir, "sqlc", "postgresql")
}
// pgBinDir returns the path to the PostgreSQL bin directory.
func pgBinDir() string {
return filepath.Join(pgBaseDir(), "bin")
}
// pgDataDir returns the path to the PostgreSQL data directory.
func pgDataDir() string {
return filepath.Join(pgBaseDir(), "data")
}
// pgBin returns the full path to a PostgreSQL binary.
func pgBin(name string) string {
return filepath.Join(pgBinDir(), name)
}
// ---- install ----
func runInstall() error {
log.Println("=== Installing PostgreSQL and MySQL for test setup ===")
if err := installAptProxy(); err != nil {
return fmt.Errorf("configuring apt proxy: %w", err)
}
if err := installPostgreSQL(); err != nil {
return fmt.Errorf("installing postgresql: %w", err)
}
if err := installMySQL(); err != nil {
return fmt.Errorf("installing mysql: %w", err)
}
log.Println("=== Install complete ===")
return nil
}
func installAptProxy() error {
proxy := os.Getenv("http_proxy")
if proxy == "" {
log.Println("http_proxy is not set, skipping apt proxy configuration")
return nil
}
const confPath = "/etc/apt/apt.conf.d/99proxy"
if _, err := os.Stat(confPath); err == nil {
log.Printf("apt proxy config already exists at %s, skipping", confPath)
return nil
}
log.Printf("configuring apt proxy to use %s", proxy)
proxyConf := fmt.Sprintf("Acquire::http::Proxy \"%s\";", proxy)
cmd := fmt.Sprintf("echo '%s' | sudo tee /etc/apt/apt.conf.d/99proxy", proxyConf)
return run("bash", "-c", cmd)
}
func installPostgreSQL() error {
log.Println("--- Installing PostgreSQL ---")
// Install runtime dependencies needed by PostgreSQL extensions (e.g.
// uuid-ossp requires libossp-uuid16).
if err := installPgDeps(); err != nil {
return fmt.Errorf("installing postgresql dependencies: %w", err)
}
// Check if already installed in our directory
if _, err := os.Stat(pgBin("postgres")); err == nil {
out, err := runOutput(pgBin("postgres"), "--version")
if err == nil {
log.Printf("postgresql is already installed: %s", strings.TrimSpace(out))
log.Println("skipping postgresql installation")
return nil
}
}
platform := runtime.GOOS + "/" + runtime.GOARCH
bin, ok := pgBinaries[platform]
if !ok {
return fmt.Errorf("unsupported platform: %s (supported: %s)", platform, supportedPlatforms())
}
// Download to a temp file
tarball := filepath.Join(os.TempDir(), fmt.Sprintf("postgresql-%s.tar.gz", pgVersion))
if _, err := os.Stat(tarball); err != nil {
log.Printf("downloading PostgreSQL %s from %s", pgVersion, bin.URL)
if err := downloadFile(tarball, bin.URL); err != nil {
os.Remove(tarball)
return fmt.Errorf("downloading postgresql: %w", err)
}
} else {
log.Printf("postgresql tarball already downloaded at %s", tarball)
}
// Verify SHA256 checksum
log.Printf("verifying SHA256 checksum")
actualHash, err := sha256File(tarball)
if err != nil {
return fmt.Errorf("computing sha256: %w", err)
}
if actualHash != bin.SHA256 {
os.Remove(tarball)
return fmt.Errorf("SHA256 mismatch: expected %s, got %s", bin.SHA256, actualHash)
}
log.Printf("SHA256 checksum verified: %s", actualHash)
baseDir := pgBaseDir()
// Create the base directory in the user cache
if err := os.MkdirAll(baseDir, 0o755); err != nil {
return fmt.Errorf("creating %s: %w", baseDir, err)
}
// Extract the tarball - it contains a top-level directory like
// postgresql-18.2.0-x86_64-unknown-linux-gnu/ with bin/, lib/, share/ inside.
// We strip that top-level directory and extract directly into the base dir.
log.Printf("extracting postgresql to %s", baseDir)
if err := run("tar", "-xzf", tarball, "-C", baseDir, "--strip-components=1"); err != nil {
return fmt.Errorf("extracting postgresql: %w", err)
}
// Verify the binary works
out, err := runOutput(pgBin("postgres"), "--version")
if err != nil {
return fmt.Errorf("postgres --version failed after install: %w", err)
}
log.Printf("postgresql installed successfully: %s", strings.TrimSpace(out))
return nil
}
// installPgDeps installs shared libraries required by PostgreSQL extensions at
// runtime (e.g. libossp-uuid16 for uuid-ossp).
func installPgDeps() error {
log.Println("installing postgresql runtime dependencies")
if err := run("sudo", "apt-get", "install", "-y", "--no-install-recommends", "libossp-uuid16"); err != nil {
return fmt.Errorf("apt-get install libossp-uuid16: %w", err)
}
return nil
}
// supportedPlatforms returns a comma-separated list of supported platforms.
func supportedPlatforms() string {
platforms := make([]string, 0, len(pgBinaries))
for p := range pgBinaries {
platforms = append(platforms, p)
}
return strings.Join(platforms, ", ")
}
// downloadFile downloads a URL to a local file path.
func downloadFile(filepath string, url string) error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("HTTP %d: %s", resp.StatusCode, resp.Status)
}
out, err := os.Create(filepath)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, resp.Body)
return err
}
// sha256File computes the SHA256 hash of a file and returns the hex string.
func sha256File(path string) (string, error) {
f, err := os.Open(path)
if err != nil {
return "", err
}
defer f.Close()
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
return "", err
}
return hex.EncodeToString(h.Sum(nil)), nil
}
func installMySQL() error {
log.Printf("--- Installing MySQL %s ---", mysqlVersion)
if commandExists("mysqld") {
out, err := runOutput("mysqld", "--version")
if err == nil {
version := strings.TrimSpace(out)
log.Printf("mysql is already installed: %s", version)
if isMySQLVersionOK(version) {
log.Printf("mysql version is %d+, skipping installation", mysqlMajor)
return nil
}
log.Printf("mysql version is too old, upgrading to MySQL %s", mysqlVersion)
// Stop existing MySQL before upgrading
_ = exec.Command("sudo", "service", "mysql", "stop").Run()
_ = exec.Command("sudo", "pkill", "-f", "mysqld").Run()
time.Sleep(2 * time.Second)
// Remove old MySQL packages to avoid conflicts
log.Println("removing old mysql packages")
_ = run("sudo", "apt-get", "remove", "-y", "mysql-server", "mysql-client", "mysql-common",
"mysql-server-core-*", "mysql-client-core-*")
// Clear old data directory so the new release can initialize fresh
log.Println("clearing old mysql data directory")
_ = run("sudo", "rm", "-rf", "/var/lib/mysql")
_ = run("sudo", "mkdir", "-p", "/var/lib/mysql")
_ = run("sudo", "chown", "mysql:mysql", "/var/lib/mysql")
}
}
major, minor, _ := strings.Cut(mysqlVersion, ".")
minor, _, _ = strings.Cut(minor, ".")
bundleURL := fmt.Sprintf("https://dev.mysql.com/get/Downloads/MySQL-%s.%s/mysql-server_%s-1ubuntu24.04_amd64.deb-bundle.tar", major, minor, mysqlVersion)
bundleTar := fmt.Sprintf("/tmp/mysql-server-%s-bundle.tar", mysqlVersion)
extractDir := "/tmp/mysql-server-" + mysqlVersion
if _, err := os.Stat(bundleTar); err != nil {
log.Printf("downloading MySQL %s bundle from %s", mysqlVersion, bundleURL)
if err := run("curl", "-L", "-o", bundleTar, bundleURL); err != nil {
return fmt.Errorf("downloading mysql bundle: %w", err)
}
} else {
log.Printf("mysql bundle already downloaded at %s, skipping download", bundleTar)
}
log.Printf("extracting bundle to %s", extractDir)
if err := os.MkdirAll(extractDir, 0o755); err != nil {
return fmt.Errorf("creating extract dir: %w", err)
}
if err := run("tar", "-xf", bundleTar, "-C", extractDir); err != nil {
return fmt.Errorf("extracting mysql bundle: %w", err)
}
// Install packages in dependency order using dpkg.
// Some packages may fail due to missing dependencies, which is expected.
// We fix them all at the end with apt-get install -f.
packages := []string{
"mysql-common_*.deb",
"mysql-community-client-plugins_*.deb",
"mysql-community-client-core_*.deb",
"mysql-community-client_*.deb",
"mysql-client_*.deb",
"mysql-community-server-core_*.deb",
"mysql-community-server_*.deb",
"mysql-server_*.deb",
}
for _, pkg := range packages {
log.Printf("installing %s (dependency errors will be fixed afterwards)", pkg)
cmd := fmt.Sprintf("sudo dpkg -i %s/%s", extractDir, pkg)
if err := run("bash", "-c", cmd); err != nil {
log.Printf("dpkg reported errors for %s (will fix with apt-get install -f)", pkg)
}
}
log.Println("fixing missing dependencies with apt-get install -f")
if err := run("sudo", "apt-get", "install", "-f", "-y"); err != nil {
return fmt.Errorf("apt-get install -f: %w", err)
}
log.Printf("mysql %s installed successfully", mysqlVersion)
return nil
}
// ---- start ----
func runStart() error {
log.Println("=== Starting PostgreSQL and MySQL ===")
if err := startPostgreSQL(); err != nil {
return fmt.Errorf("starting postgresql: %w", err)
}
if err := startMySQL(); err != nil {
return fmt.Errorf("starting mysql: %w", err)
}
log.Println("=== Both databases are running and configured ===")
log.Println("PostgreSQL: postgres://postgres:postgres@127.0.0.1:5432/postgres?sslmode=disable")
log.Println("MySQL: root:mysecretpassword@tcp(127.0.0.1:3306)/mysql")
return nil
}
func startPostgreSQL() error {
log.Println("--- Starting PostgreSQL ---")
dataDir := pgDataDir()
logFile := filepath.Join(pgBaseDir(), "postgresql.log")
// Check if already running
if pgIsReady() {
log.Println("postgresql is already running and accepting connections")
return nil
}
// Initialize data directory if needed
if _, err := os.Stat(filepath.Join(dataDir, "PG_VERSION")); os.IsNotExist(err) {
log.Println("initializing postgresql data directory")
if err := os.MkdirAll(dataDir, 0o700); err != nil {
return fmt.Errorf("creating data directory: %w", err)
}
if err := run(pgBin("initdb"),
"-D", dataDir,
"--username=postgres",
"--auth=trust",
); err != nil {
return fmt.Errorf("initdb: %w", err)
}
// Configure pg_hba.conf for md5 password authentication on TCP
hbaPath := filepath.Join(dataDir, "pg_hba.conf")
if err := configurePgHBA(hbaPath); err != nil {
return fmt.Errorf("configuring pg_hba.conf: %w", err)
}
// Configure postgresql.conf to listen on localhost
confPath := filepath.Join(dataDir, "postgresql.conf")
if err := appendToFile(confPath,
"\n# sqlc-test-setup configuration\n"+
"listen_addresses = '127.0.0.1'\n"+
"port = 5432\n",
); err != nil {
return fmt.Errorf("configuring postgresql.conf: %w", err)
}
} else {
log.Println("postgresql data directory already initialized")
}
// Start PostgreSQL using pg_ctl
log.Println("starting postgresql")
if err := run(pgBin("pg_ctl"),
"-D", dataDir,
"-l", logFile,
"-o", fmt.Sprintf("-k %s", dataDir),
"start",
); err != nil {
return fmt.Errorf("pg_ctl start: %w", err)
}
// Wait for PostgreSQL to be ready
log.Println("waiting for postgresql to accept connections")
if err := waitForPostgreSQL(30 * time.Second); err != nil {
return fmt.Errorf("postgresql did not start in time: %w", err)
}
// Set the postgres user password
log.Println("setting password for postgres user")
if err := run(pgBin("psql"),
"-h", "127.0.0.1",
"-U", "postgres",
"-c", "ALTER USER postgres PASSWORD 'postgres';",
); err != nil {
return fmt.Errorf("setting postgres password: %w", err)
}
// Update pg_hba.conf to require md5 auth now that password is set
hbaPath := filepath.Join(dataDir, "pg_hba.conf")
if err := configurePgHBAWithMD5(hbaPath); err != nil {
return fmt.Errorf("updating pg_hba.conf for md5: %w", err)
}
// Reload configuration
log.Println("reloading postgresql configuration")
if err := run(pgBin("pg_ctl"), "-D", dataDir, "reload"); err != nil {
return fmt.Errorf("pg_ctl reload: %w", err)
}
// Verify connection with password
log.Println("verifying postgresql connection")
cmd := exec.Command(pgBin("psql"),
"-h", "127.0.0.1",
"-U", "postgres",
"-c", "SELECT 1;",
)
cmd.Env = append(os.Environ(), "PGPASSWORD=postgres")
cmd.Stdout = os.Stderr
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("postgresql connection test failed: %w", err)
}
log.Println("postgresql is running and configured")
return nil
}
// configurePgHBA writes a pg_hba.conf that allows trust auth initially (for
// setting the password), then we switch to md5.
func configurePgHBA(hbaPath string) error {
content := `# pg_hba.conf - generated by sqlc-test-setup
# TYPE DATABASE USER ADDRESS METHOD
local all all trust
host all all 127.0.0.1/32 trust
host all all ::1/128 trust
`
return os.WriteFile(hbaPath, []byte(content), 0o600)
}
// configurePgHBAWithMD5 rewrites pg_hba.conf to use md5 for TCP connections.
func configurePgHBAWithMD5(hbaPath string) error {
content := `# pg_hba.conf - generated by sqlc-test-setup
# TYPE DATABASE USER ADDRESS METHOD
local all all trust
host all all 127.0.0.1/32 md5
host all all ::1/128 md5
`
return os.WriteFile(hbaPath, []byte(content), 0o600)
}
// appendToFile appends text to a file.
func appendToFile(path, text string) error {
f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0o644)
if err != nil {
return err
}
defer f.Close()
_, err = f.WriteString(text)
return err
}
// pgIsReady checks if PostgreSQL is running and accepting connections.
func pgIsReady() bool {
cmd := exec.Command(pgBin("pg_isready"), "-h", "127.0.0.1", "-p", "5432")
return cmd.Run() == nil
}
// waitForPostgreSQL polls until PostgreSQL accepts connections or times out.
func waitForPostgreSQL(timeout time.Duration) error {
deadline := time.Now().Add(timeout)
for time.Now().Before(deadline) {
if pgIsReady() {
return nil
}
time.Sleep(500 * time.Millisecond)
}
return fmt.Errorf("timed out after %s waiting for postgresql", timeout)
}
func startMySQL() error {
log.Println("--- Starting MySQL ---")
// Check if MySQL is already running and accessible with the expected password
if mysqlReady() {
log.Println("mysql is already running and accepting connections")
return verifyMySQL()
}
// Stop any existing MySQL service that might be running (e.g. pre-installed
// on GitHub Actions runners) to avoid port conflicts.
log.Println("stopping any existing mysql service")
_ = exec.Command("sudo", "service", "mysql", "stop").Run()
_ = exec.Command("sudo", "mysqladmin", "shutdown").Run()
// Give MySQL time to fully shut down
time.Sleep(2 * time.Second)
if err := ensureMySQLDirs(); err != nil {
return err
}
// Check if data directory already exists and has been initialized
needsPasswordReset := false
if mysqlInitialized() {
log.Println("mysql data directory already initialized, skipping initialization")
// Existing data dir may have an unknown root password (e.g. pre-installed
// MySQL on GitHub Actions). We'll need to use --skip-grant-tables to reset it.
needsPasswordReset = true
} else {
log.Println("initializing mysql data directory")
if err := run("sudo", "mysqld", "--initialize-insecure", "--user=mysql"); err != nil {
return fmt.Errorf("mysqld --initialize-insecure: %w", err)
}
}
if needsPasswordReset {
// Start with --skip-grant-tables to reset the unknown root password.
if err := startMySQLDaemon("--skip-grant-tables"); err != nil {
return err
}
log.Println("resetting root password via --skip-grant-tables")
resetSQL := "FLUSH PRIVILEGES; ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY 'mysecretpassword';"
if err := run("mysql", "-u", "root", "-e", resetSQL); err != nil {
return fmt.Errorf("resetting mysql root password: %w", err)
}
// Restart without --skip-grant-tables
log.Println("restarting mysql normally")
if err := run("sudo", "mysqladmin", "-u", "root", "-pmysecretpassword", "shutdown"); err != nil {
// If mysqladmin fails, try killing the process directly
_ = run("sudo", "pkill", "-f", "mysqld")
}
time.Sleep(2 * time.Second)
if err := startMySQLDaemon(); err != nil {
return err
}
} else {
// Fresh initialization — start normally and set password
if err := startMySQLDaemon(); err != nil {
return err
}
log.Println("setting mysql root password")
alterSQL := "ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY 'mysecretpassword'; FLUSH PRIVILEGES;"
if err := run("mysql", "-u", "root", "-e", alterSQL); err != nil {
return fmt.Errorf("setting mysql root password: %w", err)
}
}
return verifyMySQL()
}
// ensureMySQLDirs creates the directories MySQL needs at runtime.
func ensureMySQLDirs() error {
if err := run("sudo", "mkdir", "-p", "/var/run/mysqld"); err != nil {
return fmt.Errorf("creating /var/run/mysqld: %w", err)
}
if err := run("sudo", "chown", "mysql:mysql", "/var/run/mysqld"); err != nil {
return fmt.Errorf("chowning /var/run/mysqld: %w", err)
}
return nil
}
// startMySQLDaemon starts mysqld_safe in the background and waits for it to
// accept connections. Extra args (e.g. "--skip-grant-tables") are appended.
func startMySQLDaemon(extraArgs ...string) error {
args := append([]string{"mysqld_safe", "--user=mysql"}, extraArgs...)
log.Printf("starting mysql via mysqld_safe %v", extraArgs)
cmd := exec.Command("sudo", args...)
cmd.Stdout = os.Stderr
cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil {
return fmt.Errorf("starting mysqld_safe: %w", err)
}
log.Println("waiting for mysql to accept connections")
if err := waitForMySQL(30 * time.Second); err != nil {
return fmt.Errorf("mysql did not start in time: %w", err)
}
log.Println("mysql is accepting connections")
return nil
}
// mysqlReady checks if MySQL is running and accepting connections with the expected password.
func mysqlReady() bool {
err := exec.Command("mysqladmin", "-h", "127.0.0.1", "-u", "root", "-pmysecretpassword", "ping").Run()
return err == nil
}
// waitForMySQL polls until MySQL accepts connections or the timeout expires.
func waitForMySQL(timeout time.Duration) error {
deadline := time.Now().Add(timeout)
for time.Now().Before(deadline) {
// Try connecting without password (fresh) or with password (already configured)
if exec.Command("mysqladmin", "-u", "root", "ping").Run() == nil {
return nil
}
if exec.Command("mysqladmin", "-h", "127.0.0.1", "-u", "root", "-pmysecretpassword", "ping").Run() == nil {
return nil
}
time.Sleep(500 * time.Millisecond)
}
return fmt.Errorf("timed out after %s waiting for mysql", timeout)
}
func verifyMySQL() error {
log.Println("verifying mysql connection")
if err := run("mysql", "-h", "127.0.0.1", "-u", "root", "-pmysecretpassword", "-e", "SELECT VERSION();"); err != nil {
return fmt.Errorf("mysql connection test failed: %w", err)
}
log.Println("mysql is running and configured")
return nil
}
// mysqlInitialized checks if the MySQL data directory has been initialized.
// We use sudo ls because /var/lib/mysql is typically only readable by the
// mysql user, so filepath.Glob from a non-root process would silently fail.
func mysqlInitialized() bool {
out, err := exec.Command("sudo", "ls", "/var/lib/mysql").CombinedOutput()
if err != nil {
return false
}
// If the directory has any contents, consider it initialized.
// mysqld --initialize-insecure requires an empty directory.
return strings.TrimSpace(string(out)) != ""
}

11
cmd/sqlc/main.go Normal file
View File

@@ -0,0 +1,11 @@
package main
import (
"os"
"github.com/sqlc-dev/sqlc/internal/cmd"
)
func main() {
os.Exit(cmd.Do(os.Args[1:], os.Stdin, os.Stdout, os.Stderr))
}

100
devenv.lock Normal file
View File

@@ -0,0 +1,100 @@
{
"nodes": {
"devenv": {
"locked": {
"dir": "src/modules",
"lastModified": 1742659231,
"owner": "cachix",
"repo": "devenv",
"rev": "c651cb04013be972767aaecb3e9a98fc930d080e",
"type": "github"
},
"original": {
"dir": "src/modules",
"owner": "cachix",
"repo": "devenv",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1733328505,
"owner": "edolstra",
"repo": "flake-compat",
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"pre-commit-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1742707865,
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "dd613136ee91f67e5dba3f3f41ac99ae89c5406b",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"pre-commit-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1742649964,
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "dcf5072734cb576d2b0c59b2ac44f5050b5eac82",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"root": {
"inputs": {
"devenv": "devenv",
"nixpkgs": "nixpkgs",
"pre-commit-hooks": "pre-commit-hooks"
}
}
},
"root": "root",
"version": 7
}

17
devenv.nix Normal file
View File

@@ -0,0 +1,17 @@
{ pkgs, ... }:
{
# https://devenv.sh/packages/
packages = [
pkgs.buf
pkgs.go_1_24
pkgs.git
pkgs.git-cliff
pkgs.govulncheck
pkgs.gopls
pkgs.golint
pkgs.mysql-shell
pkgs.postgresql_15
pkgs.python311
];
}

3
devenv.yaml Normal file
View File

@@ -0,0 +1,3 @@
inputs:
nixpkgs:
url: github:NixOS/nixpkgs/nixpkgs-unstable

39
docker-compose.yml Normal file
View File

@@ -0,0 +1,39 @@
version: "3.8"
services:
mysql:
image: "mysql:26.7"
ports:
- "3306:3306"
restart: always
environment:
MYSQL_DATABASE: dinotest
MYSQL_ROOT_PASSWORD: mysecretpassword
MYSQL_ROOT_HOST: '%'
postgresql:
image: "postgres:16"
ports:
- "5432:5432"
restart: always
environment:
POSTGRES_DB: postgres
POSTGRES_PASSWORD: mysecretpassword
POSTGRES_USER: postgres
mssql:
image: "mcr.microsoft.com/mssql/server:2025-latest"
ports:
- "1433:1433"
restart: always
environment:
ACCEPT_EULA: "Y"
MSSQL_SA_PASSWORD: "Mysecretpassword1!"
# Spanner Omni, the downloadable Spanner, serves plaintext gRPC on port
# 15000 and its console on 15026; its quickstart runs it on the host
# network.
spanner:
image: "us-docker.pkg.dev/spanner-omni/images/spanner-omni:2026.r2.1-beta"
command: start-single-server
network_mode: host
restart: always

2
docs/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
_venv
_build

View File

@@ -0,0 +1,61 @@
# Developing sqlc
## Building
For local development, install `sqlc` under an alias. We suggest `sqlc-dev`.
```
go build -o ~/go/bin/sqlc-dev ./cmd/sqlc
```
Install `sqlc-gen-json` to avoid test failure.
```
go build -o ~/go/bin/sqlc-gen-json ./cmd/sqlc-gen-json
```
## Running Tests
```
go test ./...
```
To run the tests in the examples folder, use the `examples` tag.
```
go test --tags=examples ./...
```
These tests require locally-running database instances. Run these databases
using [Docker Compose](https://docs.docker.com/compose/).
```
docker compose up -d
```
The tests use the following environment variables to connect to the
database
### For PostgreSQL
```
Variable Default Value
-------------------------
PG_HOST 127.0.0.1
PG_PORT 5432
PG_USER postgres
PG_PASSWORD mysecretpassword
PG_DATABASE dinotest
```
### For MySQL
```
Variable Default Value
-------------------------
MYSQL_HOST 127.0.0.1
MYSQL_PORT 3306
MYSQL_USER root
MYSQL_ROOT_PASSWORD mysecretpassword
MYSQL_DATABASE dinotest
```

View File

@@ -0,0 +1,71 @@
# Migrating off hosted managed databases
Starting in sqlc 1.27.0, [managed databases](../howto/managed-databases.md) will require a database server URI in the configuration file.
This guide walks you through migrating to a locally running database server.
## Run a database server locally
There are many options for running a database server locally, but this guide
will use [Docker Compose](https://docs.docker.com/compose/), as it can support
both MySQL and PostgreSQL.
If you're using macOS and PostgreSQL, [Postgres.app](https://postgresapp.com/) is also a good option.
For MySQL, create a `docker-compose.yml` file with the following contents:
```yaml
version: "3.8"
services:
mysql:
image: "mysql/mysql-server:8.0"
ports:
- "3306:3306"
restart: always
environment:
MYSQL_DATABASE: dinotest
MYSQL_ROOT_PASSWORD: mysecretpassword
MYSQL_ROOT_HOST: '%'
```
For PostgreSQL, create a `docker-compose.yml` file with the following contents:
```yaml
version: "3.8"
services:
postgresql:
image: "postgres:16"
ports:
- "5432:5432"
restart: always
environment:
POSTGRES_DB: postgres
POSTGRES_PASSWORD: mysecretpassword
POSTGRES_USER: postgres
```
```sh
docker compose up -d
```
## Upgrade sqlc
You must be running sqlc v1.31.1 or greater to have access to the `servers`
configuration.
## Add servers to configuration
```diff
version: '2'
cloud:
project: '<PROJECT_ID>'
+ servers:
+ - name: mysql
+ uri: mysql://localhost:3306
+ - name: postgres
+ uri: postgres://localhost:5432/postgres?sslmode=disable
```
## Re-generate the code
Run `sqlc generate`. A database with the `sqlc_managed_` prefix will be automatically created and used for query analysis.

View File

@@ -0,0 +1,119 @@
# Migrating to sqlc-gen-kotlin
Starting in sqlc 1.16.0, built-in Kotlin support has been deprecated. It will
be fully removed in 1.17.0 in favor of sqlc-gen-kotlin.
This guide will walk you through migrating to the [sqlc-gen-kotlin](https://github.com/sqlc-dev/sqlc-gen-kotlin) plugin,
which involves three steps.
1. Add the sqlc-gen-kotlin plugin
2. Migrate each package
3. Re-generate the code
## Add the sqlc-gen-kotlin plugin
In your configuration file, add a `plugins` array if you don't have one
already. Add the following configuration for the plugin:
```json
{
"version": "2",
"plugins": [
{
"name": "kt",
"wasm": {
"url": "https://downloads.sqlc.dev/plugin/sqlc-gen-kotlin_1.0.0.wasm",
"sha256": "7620dc5d462de41fdc90e2011232c842117b416c98fd5c163d27c5738431a45c"
}
}
]
}
```
```yaml
version: "2"
plugins:
- name: "kt"
wasm:
url: "https://downloads.sqlc.dev/plugin/sqlc-gen-kotlin_1.0.0.wasm"
sha256: "7620dc5d462de41fdc90e2011232c842117b416c98fd5c163d27c5738431a45c"
```
## Migrate each package
Your package configuration should currently looks something like this for JSON.
```json
"sql": [
{
"schema": "schema.sql",
"queries": "query.sql",
"engine": "postgresql",
"gen": {
"kotlin": {
"out": "src/main/kotlin/com/example/foo",
"package": "com.example.foo"
}
}
}
]
```
Or this if you're using YAML.
```yaml
sql:
- schema: "schema.sql"
queries: "query.sql"
engine: "postgresql"
gen:
kotlin:
out: "src/main/kotlin/com/example/foo"
package: "com.example.foo"
```
To use the plugin, you'll need to replace the `gen` mapping with the `codegen`
collection. Add the `plugin` field, setting it to `kt`. All fields other than
`out` need to be moved into the `options` mapping.
After you're done, it should look like this for JSON.
```json
"sql": [
{
"schema": "schema.sql",
"queries": "query.sql",
"engine": "postgresql",
"codegen": [
{
"out": "src/main/kotlin/com/example/foo",
"plugin": "kt",
"options": {
"package": "com.example.foo"
}
}
]
}
]
```
Or this for YAML.
```yaml
sql:
- schema: "schema.sql"
queries: "query.sql"
engine: "postgresql"
codegen:
- plugin: "kt"
out: "src/main/kotlin/com/example/foo"
options:
package: "com.example.foo"
```
## Re-generate the code
Run `sqlc generate`. The plugin will produce the same output, so you shouldn't
see any changes. The first time `sqlc generate` is run, the plugin must be
downloaded and compiled, resulting in a slightly longer runtime. Subsequent
`generate` calls will be fast.

View File

@@ -0,0 +1,131 @@
# Migrating to sqlc-gen-python
Starting in sqlc 1.16.0, built-in Python support has been deprecated. It will
be fully removed in 1.17.0 in favor of sqlc-gen-python.
This guide will walk you through migrating to the [sqlc-gen-python](https://github.com/sqlc-dev/sqlc-gen-python) plugin,
which involves three steps.
1. Add the sqlc-gen-python plugin
2. Migrate each package
3. Re-generate the code
## Add the sqlc-gen-python plugin
In your configuration file, add a `plugins` array if you don't have one
already. Add the following configuration for the plugin:
```json
{
"version": "2",
"plugins": [
{
"name": "py",
"wasm": {
"url": "https://downloads.sqlc.dev/plugin/sqlc-gen-python_1.0.0.wasm",
"sha256": "aca83e1f59f8ffdc604774c2f6f9eb321a2b23e07dc83fc12289d25305fa065b"
}
}
]
}
```
```yaml
version: "2"
plugins:
- name: "py"
wasm:
url: "https://downloads.sqlc.dev/plugin/sqlc-gen-python_1.0.0.wasm"
sha256: "aca83e1f59f8ffdc604774c2f6f9eb321a2b23e07dc83fc12289d25305fa065b"
```
## Migrate each package
Your package configuration should currently looks something like this for JSON.
```json
"sql": [
{
"schema": "schema.sql",
"queries": "query.sql",
"engine": "postgresql",
"gen": {
"python": {
"out": "src",
"package": "foo",
"emit_sync_querier": true,
"emit_async_querier": true,
"query_parameter_limit": 5
}
}
}
]
```
Or this if you're using YAML.
```yaml
sql:
- schema: "schema.sql"
queries: "query.sql"
engine: "postgresql"
gen:
python:
out: "src"
package: "foo"
emit_sync_querier: true
emit_async_querier: true
query_parameter_limit: 5
```
To use the plugin, you'll need to replace the `gen` mapping with the `codegen`
collection. Add the `plugin` field, setting it to `py`. All fields other than
`out` need to be moved into the `options` mapping.
After you're done, it should look like this for JSON.
```json
"sql": [
{
"schema": "schema.sql",
"queries": "query.sql",
"engine": "postgresql",
"codegen": [
{
"out": "src",
"plugin": "py",
"options": {
"package": "authors",
"emit_sync_querier": true,
"emit_async_querier": true,
"query_parameter_limit": 5
}
}
]
}
]
```
Or this for YAML.
```yaml
sql:
- schema: "schema.sql"
queries: "query.sql"
engine: "postgresql"
codegen:
- plugin: "py"
out: "src"
options:
package: "foo"
emit_sync_querier: true
emit_async_querier: true
query_parameter_limit: 5
```
## Re-generate the code
Run `sqlc generate`. The plugin will produce the same output, so you shouldn't
see any changes. The first time `sqlc generate` is run, the plugin must be
downloaded and compiled, resulting in a slightly longer runtime. Subsequent
`generate` calls will be fast.

104
docs/guides/plugins.md Normal file
View File

@@ -0,0 +1,104 @@
# Using plugins
To use plugins, you must be using [Version 2](../reference/config.md#version-2) of
the configuration file. The top-level `plugins` array defines the available
plugins.
## WASM plugins
> WASM plugins are fully sandboxed; they do not have access to the network,
> filesystem, or environment variables.
In the `codegen` section, the `out` field dictates what directory will contain
the new files. The `plugin` key must reference a plugin defined in the
top-level `plugins` map. Any `options` are serialized to a string as JSON and
passed on to the plugin itself.
```yaml
version: '2'
plugins:
- name: greeter
wasm:
url: https://github.com/sqlc-dev/sqlc-gen-greeter/releases/download/v0.1.0/sqlc-gen-greeter.wasm
sha256: afc486dac2068d741d7a4110146559d12a013fd0286f42a2fc7dcd802424ad07
sql:
- schema: schema.sql
queries: query.sql
engine: postgresql
codegen:
- out: gen
plugin: greeter
options:
lang: en-US
```
For a complete working example see the following files:
- [sqlc-gen-greeter](https://github.com/sqlc-dev/sqlc-gen-greeter)
- A WASM plugin (written in Rust) that outputs a friendly message
- [wasm_plugin_sqlc_gen_greeter](https://github.com/sqlc-dev/sqlc/tree/main/internal/endtoend/testdata/wasm_plugin_sqlc_gen_greeter)
- An example project showing how to use a WASM plugin
## Process plugins
> Process-based plugins offer minimal security. Only use plugins that you
> trust. Better yet, only use plugins that you've written yourself.
In the `codegen` section, the `out` field dictates what directory will contain
the new files. The `plugin` key must reference a plugin defined in the
top-level `plugins` map. Any `options` are serialized to a string as JSON and
passed on to the plugin itself.
```yaml
version: '2'
plugins:
- name: jsonb
process:
cmd: sqlc-gen-json
sql:
- schema: schema.sql
queries: query.sql
engine: postgresql
codegen:
- out: gen
plugin: jsonb
options:
indent: " "
filename: codegen.json
```
For a complete working example see the following files:
- [sqlc-gen-json](https://github.com/sqlc-dev/sqlc/tree/main/cmd/sqlc-gen-json)
- A process-based plugin that serializes the CodeGenRequest to JSON
- [process_plugin_sqlc_gen_json](https://github.com/sqlc-dev/sqlc/tree/main/internal/endtoend/testdata/process_plugin_sqlc_gen_json)
- An example project showing how to use a process-based plugin
- [process_plugin_sqlc_gen_json](https://github.com/sqlc-dev/sqlc/tree/main/internal/endtoend/testdata/process_plugin_format_json/)
- An example project showing how to use a process-based plugin using json
## Environment variables
By default, plugins do not inherit access to environment variables. Instead,
you can configure access on a per-variable basis. For example, if your plugin
needs the `PATH` environment variable, add `PATH` to the `env` list in the
`plugins` collection.
```yaml
version: '2'
sql:
- schema: schema.sql
queries: query.sql
engine: postgresql
codegen:
- out: gen
plugin: test
plugins:
- name: test
env:
- PATH
wasm:
url: https://github.com/sqlc-dev/sqlc-gen-test/releases/download/v0.1.0/sqlc-gen-test.wasm
sha256: 138220eae508d4b65a5a8cea555edd155eb2290daf576b7a8b96949acfeb3790
```
A variable named `SQLC_VERSION` is always included in the plugin's
environment, set to the version of the `sqlc` executable invoking it.

55
docs/guides/privacy.md Normal file
View File

@@ -0,0 +1,55 @@
# Privacy and data collection
These days, it feels like every piece of software is tracking you. From your
browser, to your phone, to your terminal, programs collect as much data about
you as possible and send it off to the cloud for analysis.
We believe the best way to keep data safe is to never collect it in the first
place.
## Our Privacy Pledge
The `sqlc` command line tool does not collect any information. It
does not send crash reports to a third-party. It does not gather anonymous
aggregate user behaviour analytics.
No analytics.
No finger-printing.
No tracking.
Not now and not in the future.
### Distribution Channels
We distribute sqlc using popular package managers such as
[Homebrew](https://brew.sh/) and [Snapcraft](https://snapcraft.io/). These
package managers and their associated command-line tools do collect usage
metrics.
We use these services to make it easy to for users to install sqlc. There will
always be an option to download sqlc from a stable URL.
## Hosted Services
We provide a few hosted services in addition to the sqlc command line tool.
### sqlc.dev
* Hosted on [GitHub Pages](https://pages.github.com/)
* Analytics with [Plausible](https://plausible.io/privacy-focused-web-analytics)
### docs.sqlc.dev
* Hosted on [Read the Docs](https://readthedocs.org/)
* Analytics with [Plausible](https://plausible.io/privacy-focused-web-analytics)
### play.sqlc.dev
* Hosted on [Heroku](https://heroku.com)
* Playground data stored in [Google Cloud Storage](https://cloud.google.com/storage)
* Automatically deleted after 30 days
### app.sqlc.dev / api.sqlc.dev
* Hosted on [Heroku](https://heroku.com)
* Error tracking and tracing with [Sentry](https://sentry.io)

View File

@@ -0,0 +1,125 @@
# Using Go and pgx
> [!NOTE]
> `pgx/v5` is supported starting from v1.18.0.
pgx is a pure Go driver and toolkit for PostgreSQL. It's become the default
PostgreSQL package for many Gophers since lib/pq was put into maintenance mode.
## Getting started
To start generating code that uses pgx, set the `sql_package` field in
your `sqlc.yaml` configuration file. Valid options are `pgx/v4` or
`pgx/v5`
```yaml
version: "2"
sql:
- engine: "postgresql"
queries: "query.sql"
schema: "query.sql"
gen:
go:
package: "db"
sql_package: "pgx/v5"
out: "db"
```
If you don't have an existing sqlc project on hand, create a directory with the
configuration file above and the following `query.sql` file.
```sql
CREATE TABLE authors (
id BIGSERIAL PRIMARY KEY,
name text NOT NULL,
bio text
);
-- name: GetAuthor :one
SELECT * FROM authors
WHERE id = $1 LIMIT 1;
-- name: ListAuthors :many
SELECT * FROM authors
ORDER BY name;
-- name: CreateAuthor :one
INSERT INTO authors (
name, bio
) VALUES (
$1, $2
)
RETURNING *;
-- name: DeleteAuthor :exec
DELETE FROM authors
WHERE id = $1;
```
Generating the code will now give you pgx-compatible database access methods.
```bash
sqlc generate
```
## Generated code walkthrough
The generated code is very similar to the code generated when using
`lib/pq`. However, instead of using `database/sql`, the code uses
pgx types directly.
```go
package main
import (
"context"
"fmt"
"os"
"github.com/jackc/pgx/v5"
"example.com/sqlc-tutorial/db"
)
func main() {
// urlExample := "postgres://username:password@localhost:5432/database_name"
conn, err := pgx.Connect(context.Background(), os.Getenv("DATABASE_URL"))
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err)
os.Exit(1)
}
defer conn.Close(context.Background())
q := db.New(conn)
author, err := q.GetAuthor(context.Background(), 1)
if err != nil {
fmt.Fprintf(os.Stderr, "GetAuthor failed: %v\n", err)
os.Exit(1)
}
fmt.Println(author.Name)
}
```
> [!NOTE]
> For production applications, consider using pgxpool for connection pooling:
>
> ```go
> import (
> "github.com/jackc/pgx/v5/pgxpool"
> "example.com/sqlc-tutorial/db"
> )
>
> func main() {
> pool, err := pgxpool.New(context.Background(), os.Getenv("DATABASE_URL"))
> if err != nil {
> fmt.Fprintf(os.Stderr, "Unable to create connection pool: %v\n", err)
> os.Exit(1)
> }
> defer pool.Close()
>
> q := db.New(pool)
> // Use q the same way as with single connections
> }
> ```

129
docs/howto/analyze.md Normal file
View File

@@ -0,0 +1,129 @@
# `analyze` - Analyzing query result types
> [!NOTE]
> `analyze` is in beta. Its flags and JSON output may change in a
> future release, and the types it reports can differ from the ones
> `generate` produces.
`sqlc analyze` analyzes a query against a schema and prints the inferred result
columns and parameters as a single JSON document.
Unlike [`generate`](generate.md), this command does not require a configuration
file and does not connect to a database. It uses sqlc's native static analysis
to infer types directly from the provided schema.
Every dialect is analyzed by the same engine-neutral analysis core: the schema
is loaded into a catalog seeded with the dialect's types, operators and
functions, and each query is resolved against it. `generate` still uses each
engine's own analysis path, so the two can report a type differently — most
visibly, `analyze` reports type names as the catalog stores them, in lower
case.
## Usage
```sh
sqlc analyze --dialect <dialect> --schema <schema-file> [query-file]
```
The query is read from the given file, or from standard input when no file is
provided. The schema is always read from the `--schema` file.
## Flags
- `--dialect`, `-d` - The SQL dialect to use. One of `postgresql`, `mysql`,
`sqlite`, `clickhouse`, `googlesql`, `mssql`, or `duckdb`. Required.
- `--schema`, `-s` - Path to the schema (DDL) file. Required.
- `--ast` - Include each statement's AST in the output. Defaults to `false`.
## Examples
Given a schema in `schema.sql`:
```sql
CREATE TABLE authors (
id BIGSERIAL PRIMARY KEY,
name text NOT NULL,
bio text
);
```
and a query in `query.sql`:
```sql
-- name: GetAuthor :one
SELECT * FROM authors WHERE id = $1;
```
Running:
```sh
sqlc analyze --dialect postgresql --schema schema.sql query.sql
```
reports the result columns and parameters:
```json
[
{
"name": "GetAuthor",
"cmd": ":one",
"columns": [
{
"name": "id",
"type": {
"name": "bigint"
},
"table": "authors"
},
{
"name": "name",
"type": {
"name": "text"
},
"table": "authors"
},
{
"name": "bio",
"type": {
"name": "text",
"nullable": true
},
"table": "authors"
}
],
"params": [
{
"number": 1,
"column": {
"name": "id",
"type": {
"name": "bigint"
},
"table": "authors"
}
}
]
}
]
```
A column's `type` is written as a call expression: a `name` applied to
`args`, each of which carries an optional `label` and exactly one of `type`,
`int`, `bool`, `string` or `ident`, with `nullable` set at whatever depth it
applies. A `numeric(10,2)` column is `numeric` applied to `10` and `2`; an
array of text is `array` applied to `text`, and an array of arrays nests
one `array` per dimension; a `Map(String, Nullable(UInt8))` in ClickHouse is
`map` applied to `string` and a nullable `uint8`; a `STRUCT<a INT64>` in
GoogleSQL is `struct` applied to an `int64` labelled `a`; the `MAX` of SQL
Server's `nvarchar(max)` is the identifier `max`.
Types are reported the way the engine itself stores and reports them rather
than the way the schema spelled them: PostgreSQL's `int` and `bigserial` are
`integer` and `bigint`, as `format_type` prints them; MySQL's `BOOLEAN` is
`tinyint(1)`; ClickHouse's `Decimal32(4)` is `decimal(9, 4)`; DuckDB's
`TEXT` is `varchar`; SQL Server's `FLOAT(24)` is `real`. SQLite, which
keeps a declared type as written, is reported as written.
Pass `--ast` to also include each statement's parsed AST under an `ast` key. It
has the same shape as the output of [`parse`](parse.md), with every node tagged
by type.

159
docs/howto/ci-cd.md Normal file
View File

@@ -0,0 +1,159 @@
# Using sqlc in CI/CD
If your project has more than a single developer, we suggest running `sqlc` as
part of your CI/CD pipeline. The four subcommands you'll want to run are `diff`,
`vet`, `verify` and `push`
`sqlc diff` ensures that your generated code is up to date. New developers to a
project may forget to run `sqlc generate` after adding a query or updating a
schema. They also might edit generated code. `sqlc diff` will catch both errors
by comparing the expected output from `sqlc generate` to what's on disk.
```diff
% sqlc diff
--- a/postgresql/query.sql.go
+++ b/postgresql/query.sql.go
@@ -55,7 +55,7 @@
const listAuthors = `-- name: ListAuthors :many
SELECT id, name, bio FROM authors
-ORDER BY name
+ORDER BY bio
`
```
`sqlc vet` runs a set of lint rules against your SQL queries. These rules are
helpful in catching anti-patterns before they make it into production. Please
see the [vet](vet.md) documentation for a complete guide to adding lint rules
for your project.
`sqlc verify` ensures that schema changes do not break production. Existing
queries are checked against new schema changes for correctness. Please see the
[verify](verify.md) documentation for a complete guide.
`sqlc push` pushes your database schema, queries and configuration to sqlc
Cloud. These archives are used by `verify` to catch breaking changes to your
database schema. Learn more about uploading projects [here](push.md)
## General setup
Install `sqlc` using the [suggested instructions](../overview/install.md).
Create three steps in your pipeline for `sqlc diff`, `sqlc vet`, and `sqlc
verify`. Run `sqlc push` after merge on your `main` branch.
## GitHub Actions
We provide the [setup-sqlc](https://github.com/marketplace/actions/setup-sqlc)
GitHub Action to install `sqlc`. The action uses the built-in
[tool-cache](https://github.com/actions/toolkit/blob/main/packages/tool-cache/README.md)
to speed up the installation process.
### diff
The following GitHub Workflow configuration runs `sqlc diff` on every push.
```yaml
name: sqlc
on: [push]
jobs:
diff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: sqlc-dev/setup-sqlc@v3
with:
sqlc-version: '1.31.1'
- run: sqlc diff
```
### vet
The following GitHub Workflow configuration runs [sqlc vet](vet.md) on every push.
You can use `sqlc vet` without a database connection, but you'll need one if your
`sqlc` configuration references the built-in `sqlc/db-prepare` lint rule.
```yaml
name: sqlc
on: [push]
jobs:
vet:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: sqlc-dev/setup-sqlc@v3
with:
sqlc-version: '1.31.1'
# Start a PostgreSQL server
- uses: sqlc-dev/action-setup-postgres@master
with:
postgres-version: "16"
id: postgres
- run: sqlc vet
env:
POSTGRESQL_SERVER_URI: ${{ steps.postgres.outputs.connection-uri }}?sslmode=disable
```
### push
> [!NOTE]
> Pushing a project is powered by [sqlc Cloud](https://dashboard.sqlc.dev). Sign up for [free](https://dashboard.sqlc.dev) today.
The following GitHub Workflow configuration runs [sqlc push](push.md) on
every push to `main`. Create an auth token via the
[dashboard](https://dashboard.sqlc.dev).
```yaml
name: sqlc
on: [push]
jobs:
push:
runs-on: ubuntu-latest
if: ${{ github.ref == 'refs/heads/main' }}
steps:
- uses: actions/checkout@v3
- uses: sqlc-dev/setup-sqlc@v3
with:
sqlc-version: '1.31.1'
- run: sqlc push
env:
SQLC_AUTH_TOKEN: ${{ secrets.SQLC_AUTH_TOKEN }}
```
### verify
> [!NOTE]
> Verify database migrations is powered by [sqlc Cloud](https://dashboard.sqlc.dev). Sign up for [free](https://dashboard.sqlc.dev) today.
```yaml
name: sqlc
on: [push]
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: sqlc-dev/setup-sqlc@v3
with:
sqlc-version: '1.31.1'
- uses: sqlc-dev/action-setup-postgres@master
with:
postgres-version: "16"
id: postgres
- run: sqlc verify
env:
POSTGRESQL_SERVER_URI: ${{ steps.postgres.outputs.connection-uri }}?sslmode=disable
SQLC_AUTH_TOKEN: ${{ secrets.SQLC_AUTH_TOKEN }}
push:
runs-on: ubuntu-latest
if: ${{ github.ref == 'refs/heads/main' }}
steps:
- uses: sqlc-dev/setup-sqlc@v3
with:
sqlc-version: '1.31.1'
- run: sqlc push
env:
SQLC_AUTH_TOKEN: ${{ secrets.SQLC_AUTH_TOKEN }}
``````

232
docs/howto/ddl.md Normal file
View File

@@ -0,0 +1,232 @@
# Modifying the database schema
sqlc parses `CREATE TABLE` and `ALTER TABLE` statements in order to generate
the necessary code.
```sql
CREATE TABLE authors (
id SERIAL PRIMARY KEY,
birth_year int NOT NULL
);
ALTER TABLE authors ADD COLUMN bio text NOT NULL;
ALTER TABLE authors DROP COLUMN birth_year;
ALTER TABLE authors RENAME TO writers;
```
```go
package db
type Writer struct {
ID int
Bio string
}
```
## Handling SQL migrations
sqlc does not perform database migrations for you. However, sqlc is able to
differentiate between up and down migrations. sqlc ignores down migrations when
parsing SQL files.
sqlc supports parsing migrations from the following tools:
- [atlas](https://github.com/ariga/atlas)
- [dbmate](https://github.com/amacneil/dbmate)
- [golang-migrate](https://github.com/golang-migrate/migrate)
- [goose](https://github.com/pressly/goose)
- [sql-migrate](https://github.com/rubenv/sql-migrate)
- [tern](https://github.com/jackc/tern)
To enable migration parsing, specify the migration directory instead of a schema file:
```yaml
version: "2"
sql:
- engine: "postgresql"
queries: "query.sql"
schema: "db/migrations"
gen:
go:
package: "tutorial"
out: "tutorial"
```
### atlas
```sql
-- Create "post" table
CREATE TABLE "public"."post" ("id" integer NOT NULL, "title" text NULL, "body" text NULL, PRIMARY KEY ("id"));
```
```go
package db
type Post struct {
ID int
Title sql.NullString
Body sql.NullString
}
```
### dbmate
```sql
-- migrate:up
CREATE TABLE foo (bar INT NOT NULL);
-- migrate:down
DROP TABLE foo;
```
```go
package db
type Foo struct {
Bar int32
}
```
### golang-migrate
**Warning:**
[golang-migrate interprets](https://github.com/golang-migrate/migrate/blob/master/MIGRATIONS.md#migration-filename-format)
migration filenames numerically. However, sqlc parses migration files in
lexicographic order. If you choose to have sqlc enumerate your migration files,
make sure their numeric ordering matches their lexicographic ordering to avoid
unexpected behavior. This can be done by prepending enough zeroes to the
migration filenames.
This doesn't work as intended.
```
1_initial.up.sql
...
9_foo.up.sql
# this migration file will be parsed BEFORE 9_foo
10_bar.up.sql
```
This worked as intended.
```
001_initial.up.sql
...
009_foo.up.sql
010_bar.up.sql
```
In `20060102.up.sql`:
```sql
CREATE TABLE post (
id int NOT NULL,
title text,
body text,
PRIMARY KEY(id)
);
```
In `20060102.down.sql`:
```sql
DROP TABLE post;
```
```go
package db
type Post struct {
ID int
Title sql.NullString
Body sql.NullString
}
```
### goose
**Warning:**
sqlc parses migration files in lexicographic order. **If you are using numeric filenames for migrations in Goose and you choose to have sqlc enumerate your migration files**,
make sure their numeric ordering matches their lexicographic ordering to avoid
unexpected behavior. This can be done by prepending enough zeroes to the
migration filenames.
This doesn't work as intended.
```
1_initial.sql
...
9_foo.sql
# this migration file will be parsed BEFORE 9_foo
10_bar.sql
```
This worked as intended.
```
001_initial.sql
...
009_foo.sql
010_bar.sql
```
```sql
-- +goose Up
CREATE TABLE post (
id int NOT NULL,
title text,
body text,
PRIMARY KEY(id)
);
-- +goose Down
DROP TABLE post;
```
```go
package db
type Post struct {
ID int
Title sql.NullString
Body sql.NullString
}
```
### sql-migrate
```sql
-- +migrate Up
-- SQL in section 'Up' is executed when this migration is applied
CREATE TABLE people (id int);
-- +migrate Down
-- SQL section 'Down' is executed when this migration is rolled back
DROP TABLE people;
```
```go
package db
type People struct {
ID int32
}
```
### tern
```sql
CREATE TABLE comment (id int NOT NULL, text text NOT NULL);
---- create above / drop below ----
DROP TABLE comment;
```
```go
package db
type Comment struct {
ID int32
Text string
}
```

52
docs/howto/delete.md Normal file
View File

@@ -0,0 +1,52 @@
# Deleting rows
```sql
CREATE TABLE authors (
id SERIAL PRIMARY KEY,
bio text NOT NULL
);
```
The parameter syntax varies by database engine:
**PostgreSQL:**
```sql
-- name: DeleteAuthor :exec
DELETE FROM authors WHERE id = $1;
```
**MySQL and SQLite:**
```sql
-- name: DeleteAuthor :exec
DELETE FROM authors WHERE id = ?;
```
```go
package db
import (
"context"
"database/sql"
)
type DBTX interface {
ExecContext(context.Context, string, ...any) (sql.Result, error)
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
const deleteAuthor = `-- name: DeleteAuthor :exec
DELETE FROM authors WHERE id = $1
`
func (q *Queries) DeleteAuthor(ctx context.Context, id int) error {
_, err := q.db.ExecContext(ctx, deleteAuthor, id)
return err
}
```

61
docs/howto/embedding.md Normal file
View File

@@ -0,0 +1,61 @@
# Embedding structs
Embedding allows you to reuse existing model structs in more queries, resulting
in less manual serialization work. First, imagine we have the following schema
with students and test scores.
```sql
CREATE TABLE students (
id bigserial PRIMARY KEY,
name text NOT NULL,
age integer NOT NULL
);
CREATE TABLE test_scores (
student_id bigint NOT NULL,
score integer NOT NULL,
grade text NOT NULL
);
```
We want to select the student record and the scores they got on a test.
Here's how we'd usually do that:
```sql
-- name: ScoreAndTests :many
SELECT students.*, test_scores.*
FROM students
JOIN test_scores ON test_scores.student_id = students.id
WHERE students.id = $1;
```
When using Go, sqlc will produce a struct like this:
```go
type ScoreAndTestsRow struct {
ID int64
Name string
Age int32
StudentID int64
Score int32
Grade string
}
```
With embedding, the struct will contain a model for both tables instead of a
flattened list of columns.
```sql
-- name: ScoreAndTests :many
SELECT sqlc.embed(students), sqlc.embed(test_scores)
FROM students
JOIN test_scores ON test_scores.student_id = students.id
WHERE students.id = $1;
```
```
type ScoreAndTestsRow struct {
Student Student
TestScore TestScore
}
```

74
docs/howto/fmt.md Normal file
View File

@@ -0,0 +1,74 @@
# `fmt` - Formatting queries
> [!NOTE]
> `fmt` is in beta. It rewrites your query files in place, so commit
> or back them up first. The formatting it produces may change in a
> future release.
`sqlc fmt` rewrites the query files referenced by your configuration file in a
canonical format. Each query is parsed with the engine's parser and printed
back from the syntax tree, so formatting never depends on how the query was
written — only on what it means. PostgreSQL, MySQL and SQLite are supported;
query files for other engines are left unchanged.
Like `gofmt`, the formatter does not impose a maximum line width. A statement
written on a single line stays on a single line, and a statement the author
broke across lines keeps its breaks: the printer notices which clause
boundaries (`FROM`, `WHERE`, `ORDER BY`, ...) and list boundaries the author
broke at and preserves them, normalizing indentation and spacing around them.
Comments inside a statement are formatted along with it: each comment is
anchored to the code around it by source position and printed back there —
a comment trailing a select-list item stays with that item, a comment above
a clause stays above its keyword — and a comment that runs to the end of its
line breaks the statement open around it. Any statement that cannot be
proven to survive formatting unchanged is left exactly as written.
## Usage
```sh
sqlc fmt [--diff]
```
Without flags, the query files are rewritten in place. With `--diff`, the
changes are printed to standard output instead and no files are modified.
## Examples
Given this query file:
```sql
-- name: GetAuthor :one
select id,name , bio
from authors
where id = ? limit 1;
-- name: SearchAuthors :many
SELECT id, -- the primary key
name, bio, created_at FROM authors WHERE name LIKE ? AND bio IS NOT NULL AND id > ? AND created_at > ? AND name <> ? ORDER BY name;
```
running `sqlc fmt` rewrites it to:
```sql
-- name: GetAuthor :one
SELECT id, name, bio
FROM authors
WHERE id = ?
LIMIT 1;
-- name: SearchAuthors :many
SELECT
id, -- the primary key
name,
bio,
created_at
FROM authors
WHERE name LIKE ? AND bio IS NOT NULL AND id > ? AND created_at > ? AND name <> ?
ORDER BY name;
```
`GetAuthor` keeps the line breaks its author wrote; had it been written on
one line, it would stay on one line. In `SearchAuthors`, the line comment
cannot share a line with the code after it, so the statement breaks open
around it, while the `WHERE` chain — written on one line — stays on one.

76
docs/howto/generate.md Normal file
View File

@@ -0,0 +1,76 @@
# `generate` - Generating code
`sqlc generate` parses SQL, analyzes the results, and outputs code. Your schema and queries are stored in separate SQL files. The paths to these files live in a `sqlc.yaml` configuration file.
```yaml
version: "2"
sql:
- engine: "postgresql"
queries: "query.sql"
schema: "schema.sql"
gen:
go:
package: "tutorial"
out: "tutorial"
sql_package: "pgx/v5"
```
We've written extensive docs on [retrieving](select.md), [inserting](insert.md),
[updating](update.md), and [deleting](delete.md) rows.
By default, sqlc runs its analysis using a built-in query analysis engine. While fast, this engine can't handle some complex queries and type-inference.
You can configure sqlc to use a database connection for enhanced analysis using metadata from that database.
The database-backed analyzer currently supports PostgreSQL, with [MySQL](https://github.com/sqlc-dev/sqlc/issues/2902) and [SQLite](https://github.com/sqlc-dev/sqlc/issues/2903)
support planned in the future.
## Enhanced analysis with managed databases
With [managed databases](managed-databases.md) configured, `generate` will automatically create a hosted ephemeral database with your
schema and use that database to improve its query analysis. And sqlc will cache its analysis locally
on a per-query basis to speed up future `generate` runs. This saves you the trouble of running and maintaining a database with
an up-to-date schema. Here's a minimal working configuration:
```yaml
version: "2"
servers:
- engine: postgresql
uri: "postgres://locahost:5432/postgres?sslmode=disable"
sql:
- engine: "postgresql"
queries: "query.sql"
schema: "schema.sql"
database:
managed: true
gen:
go:
out: "db"
sql_package: "pgx/v5"
```
## Enhanced analysis using your own database
You can opt-in to database-backed analysis using your own database, by providing a `uri` in your sqlc
[database](../reference/config.md#database) configuration.
The `uri` string can contain references to environment variables using the `${...}`
syntax. In the following example, the connection string will have the value of
the `PG_PASSWORD` environment variable set as its password.
```yaml
version: "2"
sql:
- engine: "postgresql"
queries: "query.sql"
schema: "schema.sql"
database:
uri: "postgres://postgres:${PG_PASSWORD}@localhost:5432/postgres"
gen:
go:
out: "db"
sql_package: "pgx/v5"
```
Databases configured with a `uri` must have an up-to-date schema for query analysis to work correctly, and `sqlc` does not apply schema migrations your database. Use your migration tool of choice to create the necessary
tables and objects before running `sqlc generate`.

238
docs/howto/insert.md Normal file
View File

@@ -0,0 +1,238 @@
# Inserting rows
```sql
CREATE TABLE authors (
id SERIAL PRIMARY KEY,
bio text NOT NULL
);
```
The parameter syntax varies by database engine:
**PostgreSQL:**
```sql
-- name: CreateAuthor :exec
INSERT INTO authors (bio) VALUES ($1);
```
**MySQL and SQLite:**
```sql
-- name: CreateAuthor :exec
INSERT INTO authors (bio) VALUES (?);
```
```go
package db
import (
"context"
"database/sql"
)
type DBTX interface {
ExecContext(context.Context, string, ...any) (sql.Result, error)
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
const createAuthor = `-- name: CreateAuthor :exec
INSERT INTO authors (bio) VALUES ($1)
`
func (q *Queries) CreateAuthor(ctx context.Context, bio string) error {
_, err := q.db.ExecContext(ctx, createAuthor, bio)
return err
}
```
## Returning columns from inserted rows
sqlc has full support for the `RETURNING` statement.
```sql
-- Example queries for sqlc
CREATE TABLE authors (
id BIGSERIAL PRIMARY KEY,
name text NOT NULL,
bio text
);
```
**PostgreSQL:**
```sql
-- name: CreateAuthor :one
INSERT INTO authors (
name, bio
) VALUES (
$1, $2
)
RETURNING *;
-- name: CreateAuthorAndReturnId :one
INSERT INTO authors (
name, bio
) VALUES (
$1, $2
)
RETURNING id;
```
**SQLite (with RETURNING support):**
```sql
-- name: CreateAuthor :one
INSERT INTO authors (
name, bio
) VALUES (
?, ?
)
RETURNING *;
-- name: CreateAuthorAndReturnId :one
INSERT INTO authors (
name, bio
) VALUES (
?, ?
)
RETURNING id;
```
Note: MySQL does not support the `RETURNING` clause. Use `:execresult` instead to get the last insert ID.
```go
package db
import (
"context"
"database/sql"
)
const createAuthor = `-- name: CreateAuthor :one
INSERT INTO authors (
name, bio
) VALUES (
$1, $2
)
RETURNING id, name, bio
`
type CreateAuthorParams struct {
Name string
Bio sql.NullString
}
func (q *Queries) CreateAuthor(ctx context.Context, arg CreateAuthorParams) (Author, error) {
row := q.db.QueryRowContext(ctx, createAuthor, arg.Name, arg.Bio)
var i Author
err := row.Scan(&i.ID, &i.Name, &i.Bio)
return i, err
}
const createAuthorAndReturnId = `-- name: CreateAuthorAndReturnId :one
INSERT INTO authors (
name, bio
) VALUES (
$1, $2
)
RETURNING id
`
type CreateAuthorAndReturnIdParams struct {
Name string
Bio sql.NullString
}
func (q *Queries) CreateAuthorAndReturnId(ctx context.Context, arg CreateAuthorAndReturnIdParams) (int64, error) {
row := q.db.QueryRowContext(ctx, createAuthorAndReturnId, arg.Name, arg.Bio)
var id int64
err := row.Scan(&id)
return id, err
}
```
## Using CopyFrom
### PostgreSQL
PostgreSQL supports the [COPY protocol](https://www.postgresql.org/docs/current/sql-copy.html) that can insert rows a lot faster than sequential inserts. You can use this easily with sqlc:
```sql
CREATE TABLE authors (
id SERIAL PRIMARY KEY,
name text NOT NULL,
bio text NOT NULL
);
-- name: CreateAuthors :copyfrom
INSERT INTO authors (name, bio) VALUES ($1, $2);
```
```go
type CreateAuthorsParams struct {
Name string
Bio string
}
func (q *Queries) CreateAuthors(ctx context.Context, arg []CreateAuthorsParams) (int64, error) {
...
}
```
The `:copyfrom` command requires either `pgx/v4` or `pgx/v5`.
```yaml
version: "2"
sql:
- engine: "postgresql"
queries: "query.sql"
schema: "query.sql"
gen:
go:
package: "db"
sql_package: "pgx/v5"
out: "db"
```
### MySQL
MySQL supports a similar feature using [LOAD DATA](https://dev.mysql.com/doc/refman/8.0/en/load-data.html).
Errors and duplicate keys are treated as warnings and insertion will
continue, even without an error for some cases. Use this in a transaction
and use SHOW WARNINGS to check for any problems and roll back if necessary.
Check the [error handling](https://dev.mysql.com/doc/refman/8.0/en/load-data.html#load-data-error-handling) documentation for more information.
```sql
CREATE TABLE foo (a text, b integer, c DATETIME, d DATE);
-- name: InsertValues :copyfrom
INSERT INTO foo (a, b, c, d) VALUES (?, ?, ?, ?);
```
```go
func (q *Queries) InsertValues(ctx context.Context, arg []InsertValuesParams) (int64, error) {
...
}
```
The `:copyfrom` command requires setting the `sql_package` and `sql_driver` options.
```yaml
version: "2"
sql:
- engine: "mysql"
queries: "query.sql"
schema: "query.sql"
gen:
go:
package: "db"
sql_package: "database/sql"
sql_driver: "github.com/go-sql-driver/mysql"
out: "db"
```

View File

@@ -0,0 +1,101 @@
# Managed databases
*Added in v1.22.0*
`sqlc` can automatically create read-only databases to power query analysis,
linting and verification. These databases are immediately useful for powering
sqlc's database-connected query analyzer, an opt-in feature that improves upon
sqlc's built-in query analysis engine. PostgreSQL support is available today,
with MySQL on the way.
Once configured, `sqlc` will also use managed databases when linting queries
with [`sqlc vet`](vet.md) in cases where your lint rules require a connection
to a running database.
Managed databases are under active development, and we're interested in
supporting other use-cases.
## Configuring managed databases
To configure `sqlc` to use managed databases, remove the `uri` key from your
`database` configuration and replace it with the `managed` key set to `true`.
Access to a running database server is required. Add a connection string to the `servers` mapping.
```yaml
version: '2'
servers:
- engine: postgresql
uri: "postgres://localhost:5432/postgres?sslmode=disable"
sql:
- schema: schema.sql
queries: query.sql
engine: postgresql
database:
managed: true
```
An environment variable can also be used via the `${}` syntax.
```yaml
version: '2'
servers:
- engine: postgresql
uri: ${DATABASE_URI}
sql:
- schema: schema.sql
queries: query.sql
engine: postgresql
database:
managed: true
```
## Improving codegen
Without a database connection, sqlc does its best to parse, analyze and compile your queries just using
the schema you pass it and what it knows about the various database engines it supports. In many cases
this works just fine, but for more advanced queries sqlc might not have enough information to produce good code.
With managed databases configured, `sqlc generate` will automatically create a hosted ephemeral database with your
schema and use that database to improve its query analysis. And sqlc will cache its analysis locally
on a per-query basis to speed up future codegen runs. Here's a minimal working configuration:
```yaml
version: '2'
servers:
- engine: postgresql
uri: "postgres://localhost:5432/postgres?sslmode=disable"
sql:
- schema: schema.sql
queries: query.sql
engine: postgresql
database:
managed: true
gen:
go:
out: "db"
```
## Linting queries
With managed databases configured, `sqlc vet` will automatically create a hosted ephemeral database with your
schema and use that database when running lint rules that require a
database connection, e.g. any [rule relying on `EXPLAIN ...` output](vet.md#rules-using-explain--output).
If you don't yet have any vet rules, the [built-in sqlc/db-prepare rule](vet.md#sqlcdb-prepare)
is a good place to start. It prepares each of your queries against the database
to ensure the query is valid. Here's a minimal working configuration:
```yaml
version: '2'
servers:
- engine: postgresql
uri: "postgres://localhost:5432/postgres?sslmode=disable"
sql:
- schema: schema.sql
queries: query.sql
engine: postgresql
database:
managed: true
rules:
- sqlc/db-prepare
```

View File

@@ -0,0 +1,93 @@
# Naming parameters
sqlc tries to generate good names for positional parameters, but sometimes it
lacks enough context. The following SQL generates parameters with less than
ideal names:
```sql
-- name: UpsertAuthorName :one
UPDATE author
SET
name = CASE WHEN $1::bool
THEN $2::text
ELSE name
END
RETURNING *;
```
```go
type UpdateAuthorNameParams struct {
Column1 bool `json:""`
Column2_2 string `json:"_2"`
}
```
In these cases, named parameters give you the control over field names on the
Params struct.
```sql
-- name: UpsertAuthorName :one
UPDATE author
SET
name = CASE WHEN sqlc.arg(set_name)::bool
THEN sqlc.arg(name)::text
ELSE name
END
RETURNING *;
```
```go
type UpdateAuthorNameParams struct {
SetName bool `json:"set_name"`
Name string `json:"name"`
}
```
If the `sqlc.arg()` syntax is too verbose for your taste, you can use the `@`
operator as a shortcut.
> [!NOTE]
> The `@` operator as a shortcut for `sqlc.arg()` is not supported in MySQL.
```sql
-- name: UpsertAuthorName :one
UPDATE author
SET
name = CASE WHEN @set_name::bool
THEN @name::text
ELSE name
END
RETURNING *;
```
## Nullable parameters
sqlc infers the nullability of any specified parameters, and often does exactly
what you want. If you want finer control over the nullability of your
parameters, you may use `sqlc.narg()` (**n**ullable arg) to override the default
behavior. Using `sqlc.narg` tells sqlc to ignore whatever nullability it has
inferred and generate a nullable parameter instead. There is no nullable
equivalent of the `@` syntax.
Here is an example that uses a single query to allow updating an author's
name, bio or both.
```sql
-- name: UpdateAuthor :one
UPDATE author
SET
name = coalesce(sqlc.narg('name'), name),
bio = coalesce(sqlc.narg('bio'), bio)
WHERE id = sqlc.arg('id')
RETURNING *;
```
The following code is generated:
```go
type UpdateAuthorParams struct {
Name sql.NullString
Bio sql.NullString
ID int64
}
```

184
docs/howto/overrides.md Normal file
View File

@@ -0,0 +1,184 @@
# Overriding types
> [!NOTE]
> Type overrides and field renaming are only fully-supported for Go.
In many cases it's useful to tell `sqlc` explicitly what Go type you want it to
use for a query input or output. For instance, by default when you use
`pgx/v5`, `sqlc` will map a PostgreSQL UUID type to `UUID` from `github.com/jackc/pgx/pgtype`.
But you may want `sqlc` to use `UUID` from `github.com/google/uuid` instead.
To tell `sqlc` to use a different Go type, add an entry to the `overrides` list in your
configuration.
`sqlc` offers two kinds of Go type overrides:
* `db_type` overrides, which override the Go type for a specific database type.
* `column` overrides, which override the Go type for a column or columns by name.
Here's an example including one of each kind:
```yaml
version: "2"
sql:
- schema: "postgresql/schema.sql"
queries: "postgresql/query.sql"
engine: "postgresql"
gen:
go:
package: "authors"
out: "db"
sql_package: "pgx/v5"
overrides:
- db_type: "uuid"
nullable: true
go_type:
import: "github.com/google/uuid"
type: "UUID"
- column: "users.birthday"
go_type: "time.Time"
```
> [!TIP]
> A single `db_type` override configuration applies to either nullable or non-nullable
> columns, but not both. If you want the same Go type to override regardless of
> nullability, you'll need to configure two overrides: one with `nullable: true` and one without.
## The `overrides` list
Each element in the `overrides` list has the following keys:
- `db_type`:
- A database type to override. Find the full list of supported types in [postgresql_type.go](https://github.com/sqlc-dev/sqlc/blob/main/internal/codegen/golang/postgresql_type.go#L12) or [mysql_type.go](https://github.com/sqlc-dev/sqlc/blob/main/internal/codegen/golang/mysql_type.go#L12). Note that for Postgres you must use pg_catalog-prefixed names where available. `db_type` and `column` are mutually exclusive.
- `column`:
- A column name to override. The value should be of the form `table.column` but you can also specify `schema.table.column` or `catalog.schema.table.column`. `column` and `db_type` are mutually exclusive.
- `go_type`:
- The fully-qualified name of a Go type to use in generated code. This is usually a string but can also be [a map](#the-go_type-map) for more complex configurations.
- `go_struct_tag`:
- A reflect-style struct tag to use in generated code, e.g. `a:"b" x:"y,z"`.
If you want `json` or `db` tags for all fields, configure `emit_json_tags` or `emit_db_tags` instead.
- `unsigned`:
- If `true`, sqlc will apply this override when a numeric column is unsigned.
Note that this only applies to `db_type` overrides and has no effect on `column` overrides.
Defaults to `false`.
- `nullable`:
- If `true`, sqlc will apply this override when a column is nullable.
Otherwise `sqlc` will apply this override when a column is non-nullable.
Note that this only applies to `db_type` overrides and has no effect on `column` overrides.
Defaults to `false`.
> [!TIP]
> A single `db_type` override configuration applies to either nullable or non-nullable
> columns, but not both. If you want the same Go type to override regardless of nullability, you'll
> need to configure two overrides: one with `nullable: true` and one without.
> [!NOTE]
> When generating code, `column` override configurations take precedence over `db_type` configurations.
### The `go_type` map
Some overrides may require more detailed configuration. If necessary, `go_type`
can be a map with the following keys:
- `import`:
- The import path for the package where the type is defined.
- `package`:
- The package name where the type is defined. This should only be necessary when your import path doesn't end with the desired package name.
- `type`:
- The type name itself, without any package prefix.
- `pointer`:
- If `true`, generated code will use a pointer to the type rather than the type itself.
- `slice`:
- If `true`, generated code will use a slice of the type rather than the type itself.
An example:
```yaml
version: "2"
sql:
- schema: "postgresql/schema.sql"
queries: "postgresql/query.sql"
engine: "postgresql"
gen:
go:
package: "authors"
out: "db"
sql_package: "pgx/v5"
overrides:
- db_type: "uuid"
go_type:
import: "a/b/v2"
package: "b"
type: "MyType"
pointer: true
```
## Global overrides
To override types in all packages that `sqlc` generates, add an override
configuration to the top-level `overrides` section of your `sqlc` config:
```yaml
version: "2"
overrides:
go:
overrides:
- db_type: "pg_catalog.timestamptz"
nullable: true
engine: "postgresql"
go_type:
import: "gopkg.in/guregu/null.v4"
package: "null"
type: "Time"
sql:
- schema: "service1/schema.sql"
queries: "service1/query.sql"
engine: "postgresql"
gen:
go:
package: "service1"
out: "service1"
- schema: "service2/schema.sql"
queries: "service2/query.sql"
engine: "postgresql"
gen:
go:
package: "service2"
out: "service2"
```
Using this configuration, whenever there is a nullable `timestamp with time zone`
column in a Postgres table, `sqlc` will generate Go code using `null.Time`.
Note that the mapping for global type overrides has a field called `engine` that
is absent in per-package type overrides. This field is only used when there are
multiple `sql` sections using different engines. If you're only generating code
for a single database engine you can omit it.
#### Version 1 configuration
If you are using the older version 1 of the `sqlc` configuration format, override
configurations themselves are unchanged but are nested differently.
Per-package configurations are nested under the `overrides` key within an item
in the `packages` list:
```yaml
version: "1"
packages:
- name: "db"
path: "internal/db"
queries: "./sql/query/"
schema: "./sql/schema/"
engine: "postgresql"
overrides: [...]
```
And global configurations are nested under the top-level `overrides` key:
```yaml
version: "1"
packages: [...]
overrides:
- db_type: "uuid"
go_type: "github.com/gofrs/uuid.UUID"
```

111
docs/howto/parse.md Normal file
View File

@@ -0,0 +1,111 @@
# `parse` - Parsing SQL into an AST
> [!NOTE]
> `parse` is in beta. Its flags and the shape of the JSON AST it
> prints may change in a future release.
`sqlc parse` parses SQL from a file or standard input and prints the abstract
syntax tree (AST) as a single JSON document. It does not require a configuration
file or a database connection.
Each statement is reported with its sqlc query name and command (when the
statement carries a [`-- name:`](../reference/query-annotations.md) annotation)
alongside its AST.
## Usage
```sh
sqlc parse --dialect <dialect> [file]
```
The SQL is read from the given file, or from standard input when no file is
provided.
## Flags
- `--dialect`, `-d` - The SQL dialect to use. One of `postgresql`, `mysql`,
`sqlite`, `clickhouse`, `googlesql`, `mssql`, or `duckdb`. Required.
## Examples
Parse a query file:
```sh
sqlc parse --dialect postgresql query.sql
```
Parse SQL piped via standard input:
```sh
echo "SELECT 1;" | sqlc parse --dialect mysql
```
The output is a JSON array with one object per statement:
```json
[
{
"name": "GetAuthor",
"cmd": ":one",
"ast": {
"tag": "RawStmt",
"stmt": {
"...": "..."
},
"stmt_location": 0,
"stmt_len": 42
}
}
]
```
Statements without a `-- name:` annotation (for example schema DDL) omit the
`name` and `cmd` fields. Field names are `snake_case` versions of the AST node
field names.
## Node types
Every node in the AST carries a `tag` naming its type. Some nodes have no
fields of their own, so without it a star, a null literal and an untranslated
clause would all print as `{}`.
```json
"val": {
"tag": "ColumnRef",
"name": "",
"fields": {
"tag": "List",
"items": [
{
"tag": "A_Star"
}
]
},
"location": 93
}
```
A `tag` of `TODO` marks a clause the dialect's converter does not translate
yet. It means the clause was parsed but is not represented in the AST, not that
the clause was absent from the query.
## Absent fields
A field the statement does not use is left out rather than printed as `null`.
An `A_Const` carrying an integer reports only what it has:
```json
"val": {
"tag": "A_Const",
"val": {
"tag": "Integer",
"ival": 1
},
"location": 30
}
```
Only absent fields — and empty lists, which engines construct differently for
the same SQL — are omitted. A zero keeps its place, because zero is a value the
parser can find: `stmt_location` is 0 for the first statement in a file, and
`LIMIT 0` parses to an `ival` of 0.

View File

@@ -0,0 +1,89 @@
# Preparing queries
If you're using `pgx/v5` you get its
[implicit support](https://github.com/jackc/pgx/wiki/Automatic-Prepared-Statement-Caching)
for prepared statements. No additional `sqlc` configuration is required.
For other drivers, `sqlc` can give you the option to explicitly use prepared
queries. These prepared queries also work with transactions.
You'll need to set `emit_prepared_queries` to `true` in your `sqlc` configuration
to generate code similar to the example below.
```sql
CREATE TABLE records (
id SERIAL PRIMARY KEY
);
-- name: GetRecord :one
SELECT * FROM records
WHERE id = $1;
```
```go
package db
import (
"context"
"database/sql"
"fmt"
)
type Record struct {
ID int32
}
type DBTX interface {
PrepareContext(context.Context, string) (*sql.Stmt, error)
QueryRowContext(context.Context, string, ...any) *sql.Row
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
func Prepare(ctx context.Context, db DBTX) (*Queries, error) {
q := Queries{db: db}
var err error
if q.getRecordStmt, err = db.PrepareContext(ctx, getRecord); err != nil {
return nil, fmt.Errorf("error preparing query GetRecord: %w", err)
}
return &q, nil
}
func (q *Queries) queryRow(ctx context.Context, stmt *sql.Stmt, query string, args ...any) *sql.Row {
switch {
case stmt != nil && q.tx != nil:
return q.tx.StmtContext(ctx, stmt).QueryRowContext(ctx, args...)
case stmt != nil:
return stmt.QueryRowContext(ctx, args...)
default:
return q.db.QueryRowContext(ctx, query, args...)
}
}
type Queries struct {
db DBTX
tx *sql.Tx
getRecordStmt *sql.Stmt
}
func (q *Queries) WithTx(tx *sql.Tx) *Queries {
return &Queries{
db: tx,
tx: tx,
getRecordStmt: q.getRecordStmt,
}
}
const getRecord = `-- name: GetRecord :one
SELECT id FROM records
WHERE id = $1
`
func (q *Queries) GetRecord(ctx context.Context, id int32) (int32, error) {
row := q.queryRow(ctx, q.getRecordStmt, getRecord, id)
err := row.Scan(&id)
return id, err
}
```

70
docs/howto/push.md Normal file
View File

@@ -0,0 +1,70 @@
# `push` - Uploading projects
> [!NOTE]
> `push` is powered by [sqlc Cloud](https://dashboard.sqlc.dev). Sign up for [free](https://dashboard.sqlc.dev) today.
*Added in v1.24.0*
We've renamed the `upload` sub-command to `push`. We've also changed the data sent along in a push request. Upload used to include the configuration file, migrations, queries, and all generated code. Push drops the generated code in favor of including the [plugin.GenerateRequest](https://buf.build/sqlc/sqlc/docs/main:plugin#plugin.GenerateRequest), which is the protocol buffer message we pass to codegen plugins.
## Add configuration
After creating a project, add the project ID to your sqlc configuration file.
```yaml
version: "2"
cloud:
project: "<PROJECT_ID>"
```
You'll also need to create an auth token and make it available via the
`SQLC_AUTH_TOKEN` environment variable.
```shell
export SQLC_AUTH_TOKEN=sqlc_xxxxxxxx
```
## Dry run
You can see what's included when uploading your project by using using the
`--dry-run` flag:
```shell
$ sqlc push --dry-run
2023/11/21 10:39:51 INFO config file=sqlc.yaml bytes=912
2023/11/21 10:39:51 INFO codegen_request queryset=app file=codegen_request.pb
2023/11/21 10:39:51 INFO schema queryset=app file=migrations/00001_initial.sql bytes=3033
2023/11/21 10:39:51 INFO query queryset=app file=queries/app.sql bytes=1150
```
The output is the files `sqlc` would have sent without the `--dry-run` flag.
## Push
Once you're ready to push, remove the `--dry-run` flag.
```shell
$ sqlc push
```
### Tags
You can provide tags to associate with a push, primarily as a convenient reference when using `sqlc verify` with the `against` argument.
Tags only refer to a single push, so if you pass an existing tag to `push` it will overwrite the previous reference.
```shell
$ sqlc push --tag main
```
### Annotations
Annotations are added to each push request. By default, we include these environment variables (if they are present).
```
GITHUB_REPOSITORY
GITHUB_REF
GITHUB_REF_NAME
GITHUB_REF_TYPE
GITHUB_SHA
```

83
docs/howto/query_count.md Normal file
View File

@@ -0,0 +1,83 @@
# Counting rows
```sql
CREATE TABLE authors (
id SERIAL PRIMARY KEY,
hometown text NOT NULL
);
-- name: CountAuthors :one
SELECT count(*) FROM authors;
-- name: CountAuthorsByTown :many
SELECT hometown, count(*) FROM authors
GROUP BY 1
ORDER BY 1;
```
```go
package db
import (
"context"
"database/sql"
)
type DBTX interface {
QueryContext(context.Context, string, ...any) (*sql.Rows, error)
QueryRowContext(context.Context, string, ...any) *sql.Row
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
const countAuthors = `-- name: CountAuthors :one
SELECT count(*) FROM authors
`
func (q *Queries) CountAuthors(ctx context.Context) (int, error) {
row := q.db.QueryRowContext(ctx, countAuthors)
var i int
err := row.Scan(&i)
return i, err
}
const countAuthorsByTown = `-- name: CountAuthorsByTown :many
SELECT hometown, count(*) FROM authors
GROUP BY 1
ORDER BY 1
`
type CountAuthorsByTownRow struct {
Hometown string
Count int
}
func (q *Queries) CountAuthorsByTown(ctx context.Context) ([]CountAuthorsByTownRow, error) {
rows, err := q.db.QueryContext(ctx, countAuthorsByTown)
if err != nil {
return nil, err
}
defer rows.Close()
items := []CountAuthorsByTownRow{}
for rows.Next() {
var i CountAuthorsByTownRow
if err := rows.Scan(&i.Hometown, &i.Count); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
```

122
docs/howto/rename.md Normal file
View File

@@ -0,0 +1,122 @@
# Renaming fields
Struct field names are generated from column names using a simple algorithm:
split the column name on underscores and capitalize the first letter of each
part.
```
account -> Account
spotify_url -> SpotifyUrl
app_id -> AppID
```
If you're not happy with a field's generated name, use the `rename` mapping
to pick a new name. The keys are column names and the values are the struct
field name to use.
```yaml
version: "2"
sql:
- schema: "postgresql/schema.sql"
queries: "postgresql/query.sql"
engine: "postgresql"
gen:
go:
package: "authors"
out: "postgresql"
rename:
spotify_url: "SpotifyURL"
```
## Tables
The output structs associated with tables can also be renamed. By default,
the struct name will be the singular version of the table name. For example,
the `authors` table will generate an `Author` struct and the `book_publishers`
table will generate a `BookPublisher` struct.
```sql
CREATE TABLE authors (
id BIGSERIAL PRIMARY KEY,
name text NOT NULL,
bio text
);
CREATE TABLE book_publishers (
id BIGSERIAL PRIMARY KEY,
name text NOT NULL
);
```
```go
package db
import (
"database/sql"
)
type Author struct {
ID int64
Name string
Bio sql.NullString
}
type Publisher struct {
ID int64
Name string
}
```
To rename these structs, you must use the generated struct name. In this
example, that would be `author` and `book_publisher`. Use the `rename` map to
change the name of these struct to `Writer` and `BookPublisher` (note the
camel-casing and the underscore for multi-worded tables).
```yaml
version: '1'
packages:
- path: db
engine: postgresql
schema: query.sql
queries: query.sql
rename:
author: Writer
book_publisher: Publisher
```
```yaml
version: "2"
sql:
- engine: postgresql
queries: query.sql
schema: query.sql
overrides:
go:
rename:
author: Writer
book_publisher: Publisher
```
```go
package db
import (
"database/sql"
)
type Writer struct {
ID int64
Name string
Bio sql.NullString
}
type Publisher struct {
ID int64
Name string
}
```
## Limitations
Rename mappings apply to an entire package. Therefore, a column named `foo` and
a table name `foo` can't map to different rename values.

393
docs/howto/select.md Normal file
View File

@@ -0,0 +1,393 @@
# Retrieving rows
To generate a database access method, annotate a query with a specific comment.
```sql
CREATE TABLE authors (
id SERIAL PRIMARY KEY,
bio text NOT NULL,
birth_year int NOT NULL
);
```
The parameter syntax varies by database engine:
**PostgreSQL:**
```sql
-- name: GetAuthor :one
SELECT * FROM authors
WHERE id = $1;
-- name: ListAuthors :many
SELECT * FROM authors
ORDER BY id;
```
**MySQL and SQLite:**
```sql
-- name: GetAuthor :one
SELECT * FROM authors
WHERE id = ?;
-- name: ListAuthors :many
SELECT * FROM authors
ORDER BY id;
```
A few new pieces of code are generated beyond the `Author` struct. An interface
for the underlying database is generated. The `*sql.DB` and `*sql.Tx` types
satisfy this interface.
The database access methods are added to a `Queries` struct, which is created
using the `New` method.
Note that the `*` in our query has been replaced with explicit column names.
This change ensures that the query will never return unexpected data.
Our query was annotated with `:one`, meaning that it should only return a
single row. We scan the data from that one into a `Author` struct.
Since the get query has a single parameter, the `GetAuthor` method takes a single
`int` as an argument.
Since the list query has no parameters, the `ListAuthors` method accepts no
arguments.
```go
package db
import (
"context"
"database/sql"
)
type Author struct {
ID int
Bio string
BirthYear int
}
type DBTX interface {
QueryContext(context.Context, string, ...any) (*sql.Rows, error)
QueryRowContext(context.Context, string, ...any) *sql.Row
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
const getAuthor = `-- name: GetAuthor :one
SELECT id, bio, birth_year FROM authors
WHERE id = $1
`
func (q *Queries) GetAuthor(ctx context.Context, id int) (Author, error) {
row := q.db.QueryRowContext(ctx, getAuthor, id)
var i Author
err := row.Scan(&i.ID, &i.Bio, &i.BirthYear)
return i, err
}
const listAuthors = `-- name: ListAuthors :many
SELECT id, bio, birth_year FROM authors
ORDER BY id
`
func (q *Queries) ListAuthors(ctx context.Context) ([]Author, error) {
rows, err := q.db.QueryContext(ctx, listAuthors)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Author
for rows.Next() {
var i Author
if err := rows.Scan(&i.ID, &i.Bio, &i.BirthYear); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
```
## Selecting columns
```sql
CREATE TABLE authors (
id SERIAL PRIMARY KEY,
bio text NOT NULL,
birth_year int NOT NULL
);
-- name: GetBioForAuthor :one
SELECT bio FROM authors
WHERE id = $1;
-- name: GetInfoForAuthor :one
SELECT bio, birth_year FROM authors
WHERE id = $1;
```
When selecting a single column, only that value is returned. The `GetBioForAuthor`
method takes a single `int` as an argument and returns a `string` and an
`error`.
When selecting multiple columns, a row record (method-specific struct) is
returned. In this case, `GetInfoForAuthor` returns a struct with two fields:
`Bio` and `BirthYear`.
If a query result has no row records, a zero value and an `ErrNoRows` error are
returned instead of a zero value and `nil`. For instance, when the `GetBioForAuthor`
result has no rows, it will return `""` and `ErrNoRows`.
```go
package db
import (
"context"
"database/sql"
)
type DBTX interface {
QueryRowContext(context.Context, string, ...any) *sql.Row
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
const getBioForAuthor = `-- name: GetBioForAuthor :one
SELECT bio FROM authors
WHERE id = $1
`
func (q *Queries) GetBioForAuthor(ctx context.Context, id int) (string, error) {
row := q.db.QueryRowContext(ctx, getBioForAuthor, id)
var i string
err := row.Scan(&i)
return i, err
}
const getInfoForAuthor = `-- name: GetInfoForAuthor :one
SELECT bio, birth_year FROM authors
WHERE id = $1
`
type GetInfoForAuthorRow struct {
Bio string
BirthYear int
}
func (q *Queries) GetInfoForAuthor(ctx context.Context, id int) (GetInfoForAuthorRow, error) {
row := q.db.QueryRowContext(ctx, getInfoForAuthor, id)
var i GetInfoForAuthorRow
err := row.Scan(&i.Bio, &i.BirthYear)
return i, err
}
```
## Passing a slice as a parameter to a query
### PostgreSQL
In PostgreSQL,
[ANY](https://www.postgresql.org/docs/current/functions-comparisons.html#id-1.5.8.28.16)
allows you to check if a value exists in an array expression. Queries using ANY
with a single parameter will generate method signatures with slices as
arguments. Use the postgres data types, eg: int, varchar, etc.
```sql
CREATE TABLE authors (
id SERIAL PRIMARY KEY,
bio text NOT NULL,
birth_year int NOT NULL
);
-- name: ListAuthorsByIDs :many
SELECT * FROM authors
WHERE id = ANY($1::int[]);
```
The above SQL will generate the following code:
```go
package db
import (
"context"
"database/sql"
"github.com/lib/pq"
)
type Author struct {
ID int
Bio string
BirthYear int
}
type DBTX interface {
QueryContext(context.Context, string, ...any) (*sql.Rows, error)
QueryRowContext(context.Context, string, ...any) *sql.Row
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
const listAuthors = `-- name: ListAuthorsByIDs :many
SELECT id, bio, birth_year FROM authors
WHERE id = ANY($1::int[])
`
func (q *Queries) ListAuthorsByIDs(ctx context.Context, ids []int) ([]Author, error) {
rows, err := q.db.QueryContext(ctx, listAuthors, pq.Array(ids))
if err != nil {
return nil, err
}
defer rows.Close()
var items []Author
for rows.Next() {
var i Author
if err := rows.Scan(&i.ID, &i.Bio, &i.BirthYear); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
```
### MySQL and SQLite
MySQL and SQLite differ from PostgreSQL in that placeholders must be generated based on
the number of elements in the slice you pass in. Though trivial it is still
something of a nuisance. The passed in slice must not be nil or empty or an
error will be returned (ie not a panic). The placeholder insertion location is
marked by the meta-function `sqlc.slice()` (which is similar to `sqlc.arg()`
that you see documented under [Naming parameters](named_parameters.md)).
To rephrase, the `sqlc.slice('param')` behaves identically to `sqlc.arg()` it
terms of how it maps the explicit argument to the function signature, eg:
* `sqlc.slice('ids')` maps to `ids []GoType` in the function signature
* `sqlc.slice(cust_ids)` maps to `custIds []GoType` in the function signature
(like `sqlc.arg()`, the parameter does not have to be quoted)
This feature is not compatible with `emit_prepared_queries` statement found in the
[Configuration file](../reference/config.md).
```sql
CREATE TABLE authors (
id SERIAL PRIMARY KEY,
bio text NOT NULL,
birth_year int NOT NULL
);
-- name: ListAuthorsByIDs :many
SELECT * FROM authors
WHERE id IN (sqlc.slice('ids'));
```
The above SQL will generate the following code:
```go
package db
import (
"context"
"database/sql"
"fmt"
"strings"
)
type Author struct {
ID int
Bio string
BirthYear int
}
type DBTX interface {
ExecContext(context.Context, string, ...any) (sql.Result, error)
PrepareContext(context.Context, string) (*sql.Stmt, error)
QueryContext(context.Context, string, ...any) (*sql.Rows, error)
QueryRowContext(context.Context, string, ...any) *sql.Row
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
func (q *Queries) WithTx(tx *sql.Tx) *Queries {
return &Queries{
db: tx,
}
}
const listAuthorsByIDs = `-- name: ListAuthorsByIDs :many
SELECT id, bio, birth_year FROM authors
WHERE id IN (/*SLICE:ids*/?)
`
func (q *Queries) ListAuthorsByIDs(ctx context.Context, ids []int64) ([]Author, error) {
sql := listAuthorsByIDs
var queryParams []any
if len(ids) == 0 {
return nil, fmt.Errorf("slice ids must have at least one element")
}
for _, v := range ids {
queryParams = append(queryParams, v)
}
sql = strings.Replace(sql, "/*SLICE:ids*/?", strings.Repeat(",?", len(ids))[1:], 1)
rows, err := q.db.QueryContext(ctx, sql, queryParams...)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Author
for rows.Next() {
var i Author
if err := rows.Scan(&i.ID, &i.Bio, &i.BirthYear); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
```

53
docs/howto/structs.md Normal file
View File

@@ -0,0 +1,53 @@
# Configuring generated structs
## Naming scheme
Structs generated from tables will attempt to use the singular form of a table
name if the table name is pluralized.
```sql
CREATE TABLE authors (
id SERIAL PRIMARY KEY,
name text NOT NULL
);
```
```go
package db
// Struct names use the singular form of table names
type Author struct {
ID int
Name string
}
```
## JSON tags
```sql
CREATE TABLE authors (
id SERIAL PRIMARY KEY,
created_at timestamp NOT NULL
);
```
sqlc can generate structs with JSON tags by adding the `emit_json_tags` key to the configuration file as it shows on [configuration reference](../reference/config.md).
The JSON name for a field matches
the column name in the database.
```go
package db
import (
"time"
)
type Author struct {
ID int `json:"id"`
CreatedAt time.Time `json:"created_at"`
}
```
## More control
See the guide to [Overriding types](./overrides.md) for fine-grained control over struct field types and tags.

101
docs/howto/transactions.md Normal file
View File

@@ -0,0 +1,101 @@
# Using transactions
In the code generated by sqlc, the `WithTx` method allows a `Queries` instance to be associated with a transaction.
For example, with the following SQL structure:
`schema.sql`:
```sql
CREATE TABLE records (
id SERIAL PRIMARY KEY,
counter INT NOT NULL
);
```
`query.sql`
```sql
-- name: GetRecord :one
SELECT * FROM records
WHERE id = $1;
-- name: UpdateRecord :exec
UPDATE records SET counter = $2
WHERE id = $1;
```
And the generated code from sqlc in `db.go`:
```go
package tutorial
import (
"context"
"database/sql"
)
type DBTX interface {
ExecContext(context.Context, string, ...any) (sql.Result, error)
PrepareContext(context.Context, string) (*sql.Stmt, error)
QueryContext(context.Context, string, ...any) (*sql.Rows, error)
QueryRowContext(context.Context, string, ...any) *sql.Row
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
func (q *Queries) WithTx(tx *sql.Tx) *Queries {
return &Queries{
db: tx,
}
}
```
You'd use it like this:
```go
// Using `github/lib/pq` as the driver.
func bumpCounter(ctx context.Context, db *sql.DB, queries *tutorial.Queries, id int32) error {
tx, err := db.Begin()
if err != nil {
return err
}
defer tx.Rollback()
qtx := queries.WithTx(tx)
r, err := qtx.GetRecord(ctx, id)
if err != nil {
return err
}
if err := qtx.UpdateRecord(ctx, tutorial.UpdateRecordParams{
ID: r.ID,
Counter: r.Counter + 1,
}); err != nil {
return err
}
return tx.Commit()
}
// Using `github.com/jackc/pgx/v5` as the driver.
func bumpCounter(ctx context.Context, db *pgx.Conn, queries *tutorial.Queries, id int32) error {
tx, err := db.Begin(ctx)
if err != nil {
return err
}
defer tx.Rollback(ctx)
qtx := queries.WithTx(tx)
r, err := qtx.GetRecord(ctx, id)
if err != nil {
return err
}
if err := qtx.UpdateRecord(ctx, tutorial.UpdateRecordParams{
ID: r.ID,
Counter: r.Counter + 1,
}); err != nil {
return err
}
return tx.Commit(ctx)
}
```

115
docs/howto/update.md Normal file
View File

@@ -0,0 +1,115 @@
# Updating rows
```sql
CREATE TABLE authors (
id SERIAL PRIMARY KEY,
bio text NOT NULL
);
```
## Single parameter
If your query has a single parameter, your Go method will also have a single
parameter.
The parameter syntax varies by database engine:
**PostgreSQL:**
```sql
-- name: UpdateAuthorBios :exec
UPDATE authors SET bio = $1;
```
**MySQL and SQLite:**
```sql
-- name: UpdateAuthorBios :exec
UPDATE authors SET bio = ?;
```
```go
package db
import (
"context"
"database/sql"
)
type DBTX interface {
ExecContext(context.Context, string, ...any) (sql.Result, error)
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
const updateAuthorBios = `-- name: UpdateAuthorBios :exec
UPDATE authors SET bio = $1
`
func (q *Queries) UpdateAuthorBios(ctx context.Context, bio string) error {
_, err := q.db.ExecContext(ctx, updateAuthorBios, bio)
return err
}
```
## Multiple parameters
If your query has more than one parameter, your Go method will accept a
`Params` struct.
**PostgreSQL:**
```sql
-- name: UpdateAuthor :exec
UPDATE authors SET bio = $2
WHERE id = $1;
```
**MySQL and SQLite:**
```sql
-- name: UpdateAuthor :exec
UPDATE authors SET bio = ?
WHERE id = ?;
```
Note: For MySQL and SQLite, parameters are bound in the order they appear in the query, regardless of the order in the function signature.
```go
package db
import (
"context"
"database/sql"
)
type DBTX interface {
ExecContext(context.Context, string, ...any) (sql.Result, error)
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
const updateAuthor = `-- name: UpdateAuthor :exec
UPDATE authors SET bio = $2
WHERE id = $1
`
type UpdateAuthorParams struct {
ID int32
Bio string
}
func (q *Queries) UpdateAuthor(ctx context.Context, arg UpdateAuthorParams) error {
_, err := q.db.ExecContext(ctx, updateAuthor, arg.ID, arg.Bio)
return err
}
```

105
docs/howto/verify.md Normal file
View File

@@ -0,0 +1,105 @@
# `verify` - Verifying schema changes
*Added in v1.24.0*
Schema updates and poorly-written queries often bring down production databases. That’s bad.
Out of the box, `sqlc generate` catches some of these issues. Running `sqlc vet` with the `sqlc/db-prepare` rule catches more subtle problems. But there is a large class of issues that sqlc can’t prevent by looking at current schema and queries alone.
For instance, when a schema change is proposed, existing queries and code running in production might fail when the schema change is applied. Enter `sqlc verify`, which analyzes existing queries against new schema changes and errors if there are any issues.
Let's look at an example. Assume you have these two tables in production.
```sql
CREATE TABLE users (
id UUID PRIMARY KEY
);
CREATE TABLE user_actions (
id UUID PRIMARY KEY,
user_id UUID NOT NULL,
action TEXT,
created_at TIMESTAMP
);
```
Your application contains the following query to join user actions against the users table.
```sql
-- name: GetUserActions :many
SELECT * FROM users u
JOIN user_actions ua ON u.id = ua.user_id
ORDER BY created_at;
```
So far, so good. Then assume you propose this schema change:
```sql
ALTER TABLE users ADD COLUMN created_at TIMESTAMP;
```
Running `sqlc generate` fails with this change, returning a `column reference "created_at" is ambiguous` error. You update your query to fix the issue.
```sql
-- name: GetUserActions :many
SELECT * FROM users u
JOIN user_actions ua ON u.id = ua.user_id
ORDER BY u.created_at;
```
While that change fixes the issue, there's a production outage waiting to happen. When the schema change is applied, the existing `GetUserActions` query will begin to fail. The correct way to fix this is to deploy the updated query before applying the schema migration.
It ensures migrations are safe to deploy by sending your current schema and queries to sqlc cloud. There, we run the queries for your latest push against your new schema changes. This check catches backwards incompatible schema changes for existing queries.
Here `sqlc verify` alerts you to the fact that ORDER BY "created_at" is ambiguous.
```sh
$ sqlc verify
FAIL: app query.sql
=== Failed
=== FAIL: app query.sql GetUserActions
ERROR: column reference "created_at" is ambiguous (SQLSTATE 42702)
```
By the way, this scenario isn't made up! It happened to us a few weeks ago. We've been happily testing early versions of `verify` for the last two weeks and haven't had any issues since.
This type of verification is only the start. If your application is deployed on-prem by your customers, `verify` could tell you if it's safe for your customers to rollback to an older version of your app, even after schema migrations have been run.
Using `verify` requires that you push your queries and schema when you tag a release of your application. We run it on every push to main, as we continuously deploy those commits.
## Authentication
`sqlc` expects to find a valid auth token in the value of the `SQLC_AUTH_TOKEN`
environment variable. You can create an auth token via the [dashboard](https://dashboard.sqlc.dev).
```shell
export SQLC_AUTH_TOKEN=sqlc_xxxxxxxx
```
## Expected workflow
Using `sqlc verify` requires pushing your queries and schema to sqlc Cloud. When
you release a new version of your application, you should push your schema and
queries as well. For example, we run `sqlc push` after any change has been
merged into our `main` branch on Github, as we deploy every commit to
production.
```shell
$ sqlc push --tag main
```
Locally or in pull requests, run `sqlc verify` to check that existing queries
continue to work with your current database schema.
```shell
$ sqlc verify --against main
```
## Picking a tag
Without an `against` argument, `verify` will run its analysis of the provided schema using your most-recently pushed queries. We suggest using the `against` argument to explicitly select a set of queries for comparison.
```shell
$ sqlc verify --against [tag]
```

286
docs/howto/vet.md Normal file
View File

@@ -0,0 +1,286 @@
# `vet` - Linting queries
*Added in v1.19.0*
`sqlc vet` runs queries through a set of lint rules.
Rules are defined in the `sqlc` [configuration](../reference/config.md) file. They
consist of a name, message, and a [Common Expression Language
(CEL)](https://github.com/google/cel-spec) expression. Expressions are evaluated
using [cel-go](https://github.com/cel-expr/cel-go). If an expression evaluates to
`true`, `sqlc vet` will report an error using the given message.
## Defining lint rules
Each lint rule's CEL expression has access to information from your sqlc
configuration and queries via variables defined in the following proto messages.
```proto
message Config
{
string version = 1;
string engine = 2 ;
repeated string schema = 3;
repeated string queries = 4;
}
message Query
{
// SQL body
string sql = 1;
// Name of the query
string name = 2;
// One of "many", "one", "exec", etc.
string cmd = 3;
// Query parameters, if any
repeated Parameter params = 4;
}
message Parameter
{
int32 number = 1;
}
```
In addition to this basic information, when you have a PostgreSQL or MySQL
[database connection configured](../reference/config.md#database)
each CEL expression has access to the output from running `EXPLAIN ...` on your query
via the `postgresql.explain` and `mysql.explain` variables.
This output is quite complex and depends on the structure of your query but sqlc attempts
to parse and provide as much information as it can. See
[Rules using `EXPLAIN ...` output](#rules-using-explain--output) for more information.
Here are a few example rules just using the basic configuration and query information available
to the CEL expression environment. While these examples are simplistic, they give you a flavor
of the types of rules you can write.
```yaml
version: 2
sql:
- schema: "query.sql"
queries: "query.sql"
engine: "postgresql"
gen:
go:
package: "authors"
out: "db"
rules:
- no-pg
- no-delete
- only-one-param
- no-exec
rules:
- name: no-pg
message: "invalid engine: postgresql"
rule: |
config.engine == "postgresql"
- name: no-delete
message: "don't use delete statements"
rule: |
query.sql.contains("DELETE")
- name: only-one-param
message: "too many parameters"
rule: |
query.params.size() > 1
- name: no-exec
message: "don't use exec"
rule: |
query.cmd == "exec"
```
### Rules using `EXPLAIN ...` output
*Added in v1.20.0*
The CEL expression environment has two variables containing `EXPLAIN ...` output,
`postgresql.explain` and `mysql.explain`. `sqlc` only populates the variable associated with
your configured database engine, and only when you have a
[database connection configured](../reference/config.md#database).
For the `postgresql` engine, `sqlc` runs
```sql
EXPLAIN (ANALYZE false, VERBOSE, COSTS, SETTINGS, BUFFERS, FORMAT JSON) ...
```
where `"..."` is your query string, and parses the output into a [`PostgreSQLExplain`](https://buf.build/sqlc/sqlc/docs/v1.20.0:vet#vet.PostgreSQLExplain) proto message.
For the `mysql` engine, `sqlc` runs
```sql
EXPLAIN FORMAT=JSON ...
```
where `"..."` is your query string, and parses the output into a [`MySQLExplain`](https://buf.build/sqlc/sqlc/docs/v1.20.0:vet#vet.MySQLExplain) proto message.
These proto message definitions are too long to include here, but you can find them in the `protos`
directory within the `sqlc` source tree.
The output from `EXPLAIN ...` depends on the structure of your query so it's a bit difficult
to offer generic examples. Refer to the
[PostgreSQL documentation](https://www.postgresql.org/docs/current/using-explain.html) and
[MySQL documentation](https://dev.mysql.com/doc/refman/en/explain-output.html) for more
information.
```yaml
...
rules:
- name: postgresql-query-too-costly
message: "Query cost estimate is too high"
rule: "postgresql.explain.plan.total_cost > 1.0"
- name: postgresql-no-seq-scan
message: "Query plan results in a sequential scan"
rule: "postgresql.explain.plan.node_type == 'Seq Scan'"
- name: mysql-query-too-costly
message: "Query cost estimate is too high"
rule: "has(mysql.explain.query_block.cost_info) && double(mysql.explain.query_block.cost_info.query_cost) > 2.0"
- name: mysql-must-use-primary-key
message: "Query plan doesn't use primary key"
rule: "has(mysql.explain.query_block.table.key) && mysql.explain.query_block.table.key != 'PRIMARY'"
```
When building rules that depend on `EXPLAIN ...` output, it may be helpful to see the actual JSON
returned from the database. `sqlc` will print it When you set the environment variable
`SQLCDEBUG=dumpexplain=1`. Use this environment variable together with a dummy rule to see
`EXPLAIN ...` output for all of your queries.
```yaml
version: 2
sql:
- schema: "query.sql"
queries: "query.sql"
engine: "postgresql"
database:
uri: "postgresql://postgres:postgres@localhost:5432/postgres"
gen:
go:
package: "db"
out: "db"
rules:
- debug
rules:
- name: debug
rule: "!has(postgresql.explain)" # A dummy rule to trigger explain
```
Please note that databases configured with a `uri` must have an up-to-date
schema for `vet` to work correctly, and `sqlc` does not apply schema migrations
to your database. Use your migration tool of choice to create the necessary
tables and objects before running `sqlc vet` with rules that depend on
`EXPLAIN ...` output.
Alternatively, configure [managed databases](managed-databases.md) to have
`sqlc` create hosted ephemeral databases with the correct schema automatically.
## Built-in rules
### sqlc/db-prepare
When a [database](../reference/config.md#database) connection is configured, you can
run the built-in `sqlc/db-prepare` rule. This rule will attempt to prepare
each of your queries against the connected database and report any failures.
```yaml
version: 2
sql:
- schema: "schema.sql"
queries: "query.sql"
engine: "postgresql"
gen:
go:
package: "authors"
out: "db"
database:
uri: "postgresql://postgres:password@localhost:5432/postgres"
rules:
- sqlc/db-prepare
```
Please note that databases configured with a `uri` must have an up-to-date
schema for `vet` to work correctly, and `sqlc` does not apply schema migrations
to your database. Use your migration tool of choice to create the necessary
tables and objects before running `sqlc vet` with the `sqlc/db-prepare` rule.
Alternatively, configure [managed databases](managed-databases.md) to have
`sqlc` create hosted ephemeral databases with the correct schema automatically.
```yaml
version: 2
cloud:
project: "<PROJECT_ID>"
sql:
- schema: "schema.sql"
queries: "query.sql"
engine: "postgresql"
gen:
go:
package: "authors"
out: "db"
database:
managed: true
rules:
- sqlc/db-prepare
```
To see this in action, check out the [authors
example](https://github.com/sqlc-dev/sqlc/blob/main/examples/authors/sqlc.yaml).
## Running lint rules
When you add the name of a defined rule to the rules list
for a [sql package](../reference/config.md#sql),
`sqlc vet` will evaluate that rule against every query in the package.
In the example below, two rules are defined but only one is enabled.
```yaml
version: 2
sql:
- schema: "query.sql"
queries: "query.sql"
engine: "postgresql"
gen:
go:
package: "authors"
out: "db"
rules:
- no-delete
rules:
- name: no-pg
message: "invalid engine: postgresql"
rule: |
config.engine == "postgresql"
- name: no-delete
message: "don't use delete statements"
rule: |
query.sql.contains("DELETE")
```
### Opting-out of lint rules
For any query, you can tell `sqlc vet` not to evaluate lint rules using the
`@sqlc-vet-disable` query annotation. The annotation accepts a list of rules to ignore.
```sql
/* name: GetAuthor :one */
/* @sqlc-vet-disable sqlc/db-prepare no-pg */
SELECT * FROM authors
WHERE id = ? LIMIT 1;
```
The rules can also be split across lines.
```sql
/* name: GetAuthor :one */
/* @sqlc-vet-disable sqlc/db-prepare */
/* @sqlc-vet-disable no-pg */
SELECT * FROM authors
WHERE id = ? LIMIT 1;
```
To skip all rules for a query, you can provide the `@sqlc-vet-disable` annotation without any parameters.
```sql
/* name: GetAuthor :one */
/* @sqlc-vet-disable */
SELECT * FROM authors
WHERE id = ? LIMIT 1;
```

22
docs/index.md Normal file
View File

@@ -0,0 +1,22 @@
# sqlc Documentation
> And lo, the Great One looked down upon the people and proclaimed:
> "SQL is actually pretty great"
sqlc generates **fully type-safe idiomatic Go code** from SQL. Here's how it
works:
1. You write SQL queries
2. You run sqlc to generate Go code that presents type-safe interfaces to those
queries
3. You write application code that calls the methods sqlc generated
Seriously, it's that easy. You don't have to write any boilerplate SQL querying
code ever again.
## Getting started
- [Installing sqlc](overview/install.md)
- [Getting started with MySQL](tutorials/getting-started-mysql.md)
- [Getting started with PostgreSQL](tutorials/getting-started-postgresql.md)
- [Getting started with SQLite](tutorials/getting-started-sqlite.md)

51
docs/overview/install.md Normal file
View File

@@ -0,0 +1,51 @@
# Installing sqlc
sqlc is distributed as a single binary with zero dependencies.
## macOS
```
brew install sqlc
```
## Ubuntu
```
sudo snap install sqlc
```
## go install
Installing recent versions of sqlc requires Go 1.21+.
```
go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
```
## Docker
```
docker pull sqlc/sqlc
```
Run `sqlc` using `docker run`:
```
docker run --rm -v $(pwd):/src -w /src sqlc/sqlc generate
```
Run `sqlc` using `docker run` in the Command Prompt on Windows (`cmd`):
```
docker run --rm -v "%cd%:/src" -w /src sqlc/sqlc generate
```
## Downloads
Get pre-built binaries for *v1.31.1*:
- [Linux](https://downloads.sqlc.dev/sqlc_1.31.1_linux_amd64.tar.gz)
- [macOS](https://downloads.sqlc.dev/sqlc_1.31.1_darwin_amd64.zip)
- [Windows](https://downloads.sqlc.dev/sqlc_1.31.1_windows_amd64.zip)
See [downloads.sqlc.dev](https://downloads.sqlc.dev/) for older versions.

2936
docs/reference/changelog.md Normal file

File diff suppressed because it is too large Load Diff

29
docs/reference/cli.md Normal file
View File

@@ -0,0 +1,29 @@
# CLI
```sh
Usage:
sqlc [command]
Available Commands:
analyze Analyze a query against a schema and output the result columns and parameters
compile Statically check SQL for syntax and type errors
completion Generate the autocompletion script for the specified shell
createdb Create an ephemeral database
diff Compare the generated files to the existing files
fmt Format SQL queries
generate Generate source code from SQL
help Help about any command
init Create an empty sqlc.yaml settings file
parse Parse SQL and output the AST as JSON
push Push the schema, queries, and configuration for this project
verify Verify schema, queries, and configuration for this project
version Print the sqlc version number
vet Vet examines queries
Flags:
-f, --file string specify an alternate config file (default: sqlc.yaml)
-h, --help help for sqlc
--no-database disable database connections (default: false)
Use "sqlc [command] --help" for more information about a command.
```

501
docs/reference/config.md Normal file
View File

@@ -0,0 +1,501 @@
# Configuration
The `sqlc` tool is configured via a `sqlc.(yaml|yml)` or `sqlc.json` file. This
file must be in the directory where the `sqlc` command is run.
## Version 2
```yaml
version: "2"
cloud:
project: "<PROJECT_ID>"
sql:
- schema: "postgresql/schema.sql"
queries: "postgresql/query.sql"
engine: "postgresql"
gen:
go:
package: "authors"
out: "postgresql"
database:
managed: true
rules:
- sqlc/db-prepare
- schema: "mysql/schema.sql"
queries: "mysql/query.sql"
engine: "mysql"
gen:
go:
package: "authors"
out: "mysql"
```
### sql
Each mapping in the `sql` collection has the following keys:
- `name`:
- An human-friendly identifier for this query set. Optional.
- `engine`:
- One of `postgresql`, `mysql` or `sqlite`.
- `schema`:
- Directory of SQL migrations or path to single SQL file; or a list of paths.
- `queries`:
- Directory of SQL queries or path to single SQL file; or a list of paths.
- `codegen`:
- A collection of mappings to configure code generators. See [codegen](#codegen) for the supported keys.
- `gen`:
- A mapping to configure built-in code generators. See [gen](#gen) for the supported keys.
- `database`:
- A mapping to configure database connections. See [database](#database) for the supported keys.
- `rules`:
- A collection of rule names to run via `sqlc vet`. See [rules](#rules) for configuration options.
- `analyzer`:
- A mapping to configure query analysis. See [analyzer](#analyzer) for the supported keys.
- `strict_function_checks`
- If true, return an error if a called SQL function does not exist. Defaults to `false`.
- `strict_order_by`
- If true, return an error if a order by column is ambiguous. Defaults to `true`.
### codegen
The `codegen` mapping supports the following keys:
- `out`:
- Output directory for generated code.
- `plugin`:
- The name of the plugin. Must be defined in the `plugins` collection.
- `options`:
- A mapping of plugin-specific options.
```yaml
version: '2'
plugins:
- name: py
wasm:
url: https://github.com/sqlc-dev/sqlc-gen-python/releases/download/v0.16.0-alpha/sqlc-gen-python.wasm
sha256: 428476c7408fd4c032da4ec74e8a7344f4fa75e0f98a5a3302f238283b9b95f2
sql:
- schema: "schema.sql"
queries: "query.sql"
engine: postgresql
codegen:
- out: src/authors
plugin: py
options:
package: authors
emit_sync_querier: true
emit_async_querier: true
query_parameter_limit: 5
```
### database
The `database` mapping supports the following keys:
- `managed`:
- If true, connect to a [managed database](../howto/managed-databases.md). Defaults to `false`.
- `uri`:
- Database connection URI
The `uri` string can contain references to environment variables using the `${...}`
syntax. In the following example, the connection string will have the value of
the `PG_PASSWORD` environment variable set as its password.
```yaml
version: '2'
sql:
- schema: schema.sql
queries: query.sql
engine: postgresql
database:
uri: postgresql://postgres:${PG_PASSWORD}@localhost:5432/authors
gen:
go:
package: authors
out: postgresql
```
### analyzer
The `analyzer` mapping supports the following keys:
- `database`:
- If false, do not use the configured database for query analysis. Defaults to `true`.
### gen
The `gen` mapping supports the following keys:
#### go
- `package`:
- The package name to use for the generated code. Defaults to `out` basename.
- `out`:
- Output directory for generated code.
- `sql_package`:
- Either `pgx/v4`, `pgx/v5` or `database/sql`. Defaults to `database/sql`.
- `sql_driver`:
- Either `github.com/jackc/pgx/v4`, `github.com/jackc/pgx/v5`, `github.com/lib/pq` or `github.com/go-sql-driver/mysql`. No defaults. Required if query annotation `:copyfrom` is used.
- `emit_db_tags`:
- If true, add DB tags to generated structs. Defaults to `false`.
- `emit_prepared_queries`:
- If true, include support for prepared queries. Defaults to `false`.
- `emit_interface`:
- If true, output a `Querier` interface in the generated package. Defaults to `false`.
- `emit_exact_table_names`:
- If true, struct names will mirror table names. Otherwise, sqlc attempts to singularize plural table names. Defaults to `false`.
- `emit_empty_slices`:
- If true, slices returned by `:many` queries will be empty instead of `nil`. Defaults to `false`.
- `emit_exported_queries`:
- If true, autogenerated SQL statement can be exported to be accessed by another package.
- `emit_json_tags`:
- If true, add JSON tags to generated structs. Defaults to `false`.
- `emit_result_struct_pointers`:
- If true, query results are returned as pointers to structs. Queries returning multiple results are returned as slices of pointers. Defaults to `false`.
- `emit_params_struct_pointers`:
- If true, parameters are passed as pointers to structs. Defaults to `false`.
- `emit_methods_with_db_argument`:
- If true, generated methods will accept a DBTX argument instead of storing a DBTX on the `*Queries` struct. Defaults to `false`.
- `emit_pointers_for_null_types`:
- If true, generated types for nullable columns are emitted as pointers (ie. `*string`) instead of `database/sql` null types (ie. `NullString`). Currently only supported for PostgreSQL if `sql_package` is `pgx/v4` or `pgx/v5`, and for SQLite. Defaults to `false`. Nullable enum columns also follow this setting unless `emit_pointers_for_null_enum_types` is set.
- `emit_pointers_for_null_enum_types`:
- Overrides `emit_pointers_for_null_types` for nullable enum columns only. When `true`, nullable enum columns are emitted as pointers (ie. `*UserRole`). When `false`, nullable enum columns use the generated `NullUserRole` wrapper struct even if `emit_pointers_for_null_types` is true. Set this to `false` to keep the pre-v1.31 behavior when upgrading. Only applies to PostgreSQL with `sql_package` `pgx/v4` or `pgx/v5`.
- `emit_enum_valid_method`:
- If true, generate a Valid method on enum types,
indicating whether a string is a valid enum value.
- `emit_all_enum_values`:
- If true, emit a function per enum type
that returns all valid enum values.
- `emit_sql_as_comment`:
- If true, emits the SQL statement as a code-block comment above the generated function, appending to any existing comments. Defaults to `false`.
- `build_tags`:
- If set, add a `//go:build <build_tags>` directive at the beginning of each generated Go file.
- `initialisms`:
- An array of [initialisms](https://google.github.io/styleguide/go/decisions.html#initialisms) to upper-case. For example, `app_id` becomes `AppID`. Defaults to `["id"]`.
- `json_tags_id_uppercase`:
- If true, "Id" in json tags will be uppercase. If false, will be camelcase. Defaults to `false`
- `json_tags_case_style`:
- `camel` for camelCase, `pascal` for PascalCase, `snake` for snake_case or `none` to use the column name in the DB. Defaults to `none`.
- `omit_unused_structs`:
- If `true`, sqlc won't generate table and enum structs that aren't used in queries for a given package. Defaults to `false`.
- `output_batch_file_name`:
- Customize the name of the batch file. Defaults to `batch.go`.
- `output_db_file_name`:
- Customize the name of the db file. Defaults to `db.go`.
- `output_models_file_name`:
- Customize the name of the models file. Defaults to `models.go`.
- `output_querier_file_name`:
- Customize the name of the querier file. Defaults to `querier.go`.
- `output_copyfrom_file_name`:
- Customize the name of the copyfrom file. Defaults to `copyfrom.go`.
- `output_files_suffix`:
- If specified the suffix will be added to the name of the generated files.
- `query_parameter_limit`:
- The number of positional arguments that will be generated for Go functions. To always emit a parameter struct, set this to `0`. Defaults to `1`.
- `rename`:
- Customize the name of generated struct fields. See [Renaming fields](../howto/rename.md) for usage information.
- `overrides`:
- A collection of configurations to override sqlc's default Go type choices. See [Overriding types](../howto/overrides.md) for usage information.
##### overrides
See [Overriding types](../howto/overrides.md) for an in-depth guide to using type overrides.
#### kotlin
> Removed in v1.17.0 and replaced by the [sqlc-gen-kotlin](https://github.com/sqlc-dev/sqlc-gen-kotlin) plugin. Follow the [migration guide](../guides/migrating-to-sqlc-gen-kotlin.md) to switch.
- `package`:
- The package name to use for the generated code.
- `out`:
- Output directory for generated code.
- `emit_exact_table_names`:
- If true, use the exact table name for generated models. Otherwise, guess a singular form. Defaults to `false`.
#### python
> Removed in v1.17.0 and replaced by the [sqlc-gen-python](https://github.com/sqlc-dev/sqlc-gen-python) plugin. Follow the [migration guide](../guides/migrating-to-sqlc-gen-python.md) to switch.
- `package`:
- The package name to use for the generated code.
- `out`:
- Output directory for generated code.
- `emit_exact_table_names`:
- If true, use the exact table name for generated models. Otherwise, guess a singular form. Defaults to `false`.
- `emit_sync_querier`:
- If true, generate a class with synchronous methods. Defaults to `false`.
- `emit_async_querier`:
- If true, generate a class with asynchronous methods. Defaults to `false`.
- `emit_pydantic_models`:
- If true, generate classes that inherit from `pydantic.BaseModel`. Otherwise, define classes using the `dataclass` decorator. Defaults to `false`.
#### json
- `out`:
- Output directory for the generated JSON.
- `filename`:
- Filename for the generated JSON document. Defaults to `codegen_request.json`.
- `indent`:
- Indent string to use in the JSON document. Defaults to ` `.
### plugins
Each mapping in the `plugins` collection has the following keys:
- `name`:
- The name of this plugin. Required
- `env`
- A list of environment variables to pass to the plugin. By default, no environment variables are passed.
- `process`: A mapping with a single `cmd` key
- `cmd`:
- The executable to call when using this plugin
- `format`:
- The format expected. Supports `json` and `protobuf` formats. Defaults to `protobuf`.
- `wasm`: A mapping with a two keys `url` and `sha256`
- `url`:
- The URL to fetch the WASM file. Supports the `https://` or `file://` schemes.
- `sha256`
- The SHA256 checksum for the downloaded file.
```yaml
version: "2"
plugins:
- name: "py"
wasm:
url: "https://github.com/sqlc-dev/sqlc-gen-python/releases/download/v0.16.0-alpha/sqlc-gen-python.wasm"
sha256: "428476c7408fd4c032da4ec74e8a7344f4fa75e0f98a5a3302f238283b9b95f2"
- name: "js"
env:
- PATH
process:
cmd: "sqlc-gen-json"
```
### rules
Each mapping in the `rules` collection has the following keys:
- `name`:
- The name of this rule. Required
- `rule`:
- A [Common Expression Language (CEL)](https://github.com/google/cel-spec) expression. Required.
- `message`:
- An optional message shown when this rule evaluates to `true`.
See the [vet](../howto/vet.md) documentation for a list of built-in rules and
help writing custom rules.
```yaml
version: "2"
sql:
- schema: "query.sql"
queries: "query.sql"
engine: "postgresql"
gen:
go:
package: "authors"
out: "db"
rules:
- no-pg
- no-delete
- only-one-param
- no-exec
rules:
- name: no-pg
message: "invalid engine: postgresql"
rule: |
config.engine == "postgresql"
- name: no-delete
message: "don't use delete statements"
rule: |
query.sql.contains("DELETE")
- name: only-one-param
message: "too many parameters"
rule: |
query.params.size() > 1
- name: no-exec
message: "don't use exec"
rule: |
query.cmd == "exec"
```
### Global overrides
Sometimes, the same configuration must be done across various specifications of
code generation. Then a global definition for type overriding and field
renaming can be done using the `overrides` mapping the following manner:
```yaml
version: "2"
overrides:
go:
rename:
id: "Identifier"
overrides:
- db_type: "pg_catalog.timestamptz"
nullable: true
engine: "postgresql"
go_type:
import: "gopkg.in/guregu/null.v4"
package: "null"
type: "Time"
sql:
- schema: "postgresql/schema.sql"
queries: "postgresql/query.sql"
engine: "postgresql"
gen:
go:
package: "authors"
out: "postgresql"
- schema: "mysql/schema.sql"
queries: "mysql/query.sql"
engine: "mysql"
gen:
go:
package: "authors"
out: "mysql"
```
With the previous configuration, whenever a struct field is generated from a
table column that is called `id`, it will generated as `Identifier`.
Also, whenever there is a nullable `timestamp with time zone` column in a
Postgres table, it will be generated as `null.Time`. Note that the mapping for
global type overrides has a field called `engine` that is absent in the regular
type overrides. This field is only used when there are multiple definitions
using multiple engines. Otherwise, the value of the `engine` key
defaults to the engine that is currently being used.
Currently, type overrides and field renaming, both global and regular, are only
fully supported in Go.
## Version 1
```yaml
version: "1"
packages:
- name: "db"
path: "internal/db"
queries: "./sql/query/"
schema: "./sql/schema/"
engine: "postgresql"
emit_db_tags: false
emit_prepared_queries: true
emit_interface: false
emit_exact_table_names: false
emit_empty_slices: false
emit_exported_queries: false
emit_json_tags: true
emit_result_struct_pointers: false
emit_params_struct_pointers: false
emit_methods_with_db_argument: false
emit_pointers_for_null_types: false
emit_enum_valid_method: false
emit_all_enum_values: false
build_tags: "some_tag"
json_tags_case_style: "camel"
omit_unused_structs: false
output_batch_file_name: "batch.go"
output_db_file_name: "db.go"
output_models_file_name: "models.go"
output_querier_file_name: "querier.go"
output_copyfrom_file_name: "copyfrom.go"
query_parameter_limit: 1
```
### packages
Each mapping in the `packages` collection has the following keys:
- `name`:
- The package name to use for the generated code. Defaults to `path` basename.
- `path`:
- Output directory for generated code.
- `queries`:
- Directory of SQL queries or path to single SQL file; or a list of paths.
- `schema`:
- Directory of SQL migrations or path to single SQL file; or a list of paths.
- `engine`:
- Either `postgresql` or `mysql`. Defaults to `postgresql`.
- `sql_package`:
- Either `pgx/v4`, `pgx/v5` or `database/sql`. Defaults to `database/sql`.
- `overrides`:
- A list of type override configurations. See the [Overriding types](../howto/overrides.md) guide for details.
- `emit_db_tags`:
- If true, add DB tags to generated structs. Defaults to `false`.
- `emit_prepared_queries`:
- If true, include support for prepared queries. Defaults to `false`.
- `emit_interface`:
- If true, output a `Querier` interface in the generated package. Defaults to `false`.
- `emit_exact_table_names`:
- If true, struct names will mirror table names. Otherwise, sqlc attempts to singularize plural table names. Defaults to `false`.
- `emit_empty_slices`:
- If true, slices returned by `:many` queries will be empty instead of `nil`. Defaults to `false`.
- `emit_exported_queries`:
- If true, autogenerated SQL statement can be exported to be accessed by another package.
- `emit_json_tags`:
- If true, add JSON tags to generated structs. Defaults to `false`.
- `emit_result_struct_pointers`:
- If true, query results are returned as pointers to structs. Queries returning multiple results are returned as slices of pointers. Defaults to `false`.
- `emit_params_struct_pointers`:
- If true, parameters are passed as pointers to structs. Defaults to `false`.
- `emit_methods_with_db_argument`:
- If true, generated methods will accept a DBTX argument instead of storing a DBTX on the `*Queries` struct. Defaults to `false`.
- `emit_pointers_for_null_types`:
- If true and `sql_package` is set to `pgx/v4` or `pgx/v5`, generated types for nullable columns are emitted as pointers (ie. `*string`) instead of `database/sql` null types (ie. `NullString`). Defaults to `false`. Nullable enum columns also follow this setting unless `emit_pointers_for_null_enum_types` is set.
- `emit_pointers_for_null_enum_types`:
- Overrides `emit_pointers_for_null_types` for nullable enum columns only. When `true`, nullable enum columns are emitted as pointers (ie. `*UserRole`). When `false`, nullable enum columns use the generated `NullUserRole` wrapper struct even if `emit_pointers_for_null_types` is true. Set this to `false` to keep the pre-v1.31 behavior when upgrading. Only applies to PostgreSQL with `sql_package` `pgx/v4` or `pgx/v5`.
- `emit_enum_valid_method`:
- If true, generate a Valid method on enum types,
indicating whether a string is a valid enum value.
- `emit_all_enum_values`:
- If true, emit a function per enum type
that returns all valid enum values.
- `build_tags`:
- If set, add a `//go:build <build_tags>` directive at the beginning of each generated Go file.
- `json_tags_case_style`:
- `camel` for camelCase, `pascal` for PascalCase, `snake` for snake_case or `none` to use the column name in the DB. Defaults to `none`.
- `omit_unused_structs`:
- If `true`, sqlc won't generate table and enum structs that aren't used in queries for a given package. Defaults to `false`.
- `output_batch_file_name`:
- Customize the name of the batch file. Defaults to `batch.go`.
- `output_db_file_name`:
- Customize the name of the db file. Defaults to `db.go`.
- `output_models_file_name`:
- Customize the name of the models file. Defaults to `models.go`.
- `output_querier_file_name`:
- Customize the name of the querier file. Defaults to `querier.go`.
- `output_copyfrom_file_name`:
- Customize the name of the copyfrom file. Defaults to `copyfrom.go`.
- `output_files_suffix`:
- If specified the suffix will be added to the name of the generated files.
- `query_parameter_limit`:
- Positional arguments that will be generated in Go functions (`>= 0`). To always emit a parameter struct, you would need to set it to `0`. Defaults to `1`.
### overrides
See the version 1 configuration section of the [Overriding types](../howto/overrides.md#version-1-configuration) guide for details.
### rename
Struct field names are generated from column names using a simple algorithm:
split the column name on underscores and capitalize the first letter of each
part.
```
account -> Account
spotify_url -> SpotifyUrl
app_id -> AppID
```
If you're not happy with a field's generated name, use the `rename` mapping
to pick a new name. The keys are column names and the values are the struct
field name to use.
```yaml
version: "1"
packages: [...]
rename:
spotify_url: "SpotifyURL"
```

388
docs/reference/datatypes.md Normal file
View File

@@ -0,0 +1,388 @@
# Datatypes
`sqlc` attempts to make reasonable default choices when mapping internal
database types to Go types. Choices for more complex types are described below.
If you're unsatisfied with the default, you can override any type using the
[overrides list](config.md#overrides) in your `sqlc` config file.
## Arrays
PostgreSQL [arrays](https://www.postgresql.org/docs/current/arrays.html) are
materialized as Go slices.
```sql
CREATE TABLE places (
name text not null,
tags text[]
);
```
```go
package db
type Place struct {
Name string
Tags []string
}
```
## Dates and times
All date and time types are returned as `time.Time` structs. For
null time or date values, the `NullTime` type from `database/sql` is used.
The `pgx/v5` sql package uses the appropriate pgx types.
For MySQL users relying on `github.com/go-sql-driver/mysql`, ensure that
`parseTime=true` is added to your database connection string.
```sql
CREATE TABLE authors (
id SERIAL PRIMARY KEY,
created_at timestamp NOT NULL DEFAULT NOW(),
updated_at timestamp
);
```
```go
package db
import (
"database/sql"
"time"
)
type Author struct {
ID int
CreatedAt time.Time
UpdatedAt sql.NullTime
}
```
## Enums
PostgreSQL [enums](https://www.postgresql.org/docs/current/datatype-enum.html) are
mapped to an aliased string type.
```sql
CREATE TYPE status AS ENUM (
'open',
'closed'
);
CREATE TABLE stores (
name text PRIMARY KEY,
status status NOT NULL
);
```
```go
package db
type Status string
const (
StatusOpen Status = "open"
StatusClosed Status = "closed"
)
type Store struct {
Name string
Status Status
}
```
## Null
For structs, null values are represented using the appropriate type from the
`database/sql` or `pgx` package.
```sql
CREATE TABLE authors (
id SERIAL PRIMARY KEY,
name text NOT NULL,
bio text
);
```
```go
package db
import (
"database/sql"
)
type Author struct {
ID int
Name string
Bio sql.NullString
}
```
## UUIDs
For UUID support, sqlc uses the `github.com/google/uuid` package by default.
The pgx/v5 sql package uses `pgtype.UUID`.
```sql
CREATE TABLE records (
id uuid PRIMARY KEY
);
```
```go
package db
import (
"github.com/google/uuid"
)
type Author struct {
ID uuid.UUID
}
```
### Using the standard library uuid package
Go 1.27 added a [`uuid`](https://pkg.go.dev/uuid) package to the standard
library. sqlc will use this package by default in a future release. Until
then, use it today with type overrides
(see [Overriding types](../howto/overrides.md) for details).
Two overrides are required: one for non-nullable columns and one for nullable
columns. The standard library does not include a `NullUUID` type, so nullable
columns map to `*uuid.UUID` instead.
```yaml
version: "2"
sql:
- engine: "postgresql"
schema: "schema.sql"
queries: "query.sql"
gen:
go:
package: "db"
out: "db"
sql_package: "pgx/v5"
overrides:
- db_type: "uuid"
go_type: "uuid.UUID"
- db_type: "uuid"
nullable: true
go_type:
import: "uuid"
type: "UUID"
pointer: true
```
With this configuration, a table with both nullable and non-nullable `uuid`
columns:
```sql
CREATE TABLE records (
id uuid PRIMARY KEY,
external_id uuid
);
```
generates:
```go
package db
import (
"uuid"
)
type Record struct {
ID uuid.UUID
ExternalID *uuid.UUID
}
```
These overrides work with both the `pgx/v5` and `database/sql` sql packages.
pgx supports any type whose underlying type is `[16]byte`, and as of Go 1.27
`database/sql` converts `uuid.UUID` values in both directions: parameters are
bound via `driver.DefaultParameterConverter` and results are scanned into
`uuid.UUID` and `*uuid.UUID` destinations, with `nil` representing `NULL`.
For MySQL, there is no native `uuid` data type. When using `UUID_TO_BIN` to store a `UUID()`, the underlying field type is `BINARY(16)` which by default sqlc would map to `sql.NullString`. To have sqlc automatically convert these fields to a `uuid.UUID` type, use an overide on the column storing the `uuid`
(see [Overriding types](../howto/overrides.md) for details).
```json
{
"overrides": [
{
"column": "*.uuid",
"go_type": "github.com/google/uuid.UUID"
}
]
}
```
## JSON
By default, sqlc will generate the `[]byte`, `pgtype.JSON` or `json.RawMessage` for JSON column type.
But if you use the `pgx/v5` sql package then you can specify a struct instead of the default type
(see [Overriding types](../howto/overrides.md) for details).
The `pgx` implementation will marshal/unmarshal the struct automatically.
```go
package dto
type BookData struct {
Genres []string `json:"genres"`
Title string `json:"title"`
Published bool `json:"published"`
}
```
```sql
CREATE TABLE books (
data jsonb
);
```
```json
{
"overrides": [
{
"column": "books.data",
"go_type": {
"import":"example.com/db",
"package": "dto",
"type":"BookData",
"pointer": true
}
}
]
}
```
```go
package db
import (
"example.com/db/dto"
)
type Book struct {
Data *dto.BookData
}
```
## TEXT
In PostgreSQL, when you have a column with the TEXT type, sqlc will map it to a Go string by default. This default mapping applies to `TEXT` columns that are not nullable. However, for nullable `TEXT` columns, sqlc maps them to `pgtype.Text` when using the pgx/v5 driver. This distinction is crucial for developers looking to handle null values appropriately in their Go applications.
To accommodate nullable strings and map them to `*string` in Go, you can use the `emit_pointers_for_null_types` option in your sqlc configuration. This option ensures that nullable SQL columns are represented as pointer types in Go, allowing for a clear distinction between null and non-null values. Another way to do this is by passing the option `pointer: true` when you are overriding the `TEXT` datatype in your sqlc config file (see [Overriding types](../howto/overrides.md) for details).
## Geometry
### PostGIS
#### Using `github.com/twpayne/go-geos` (pgx/v5 only)
sqlc can be configured to use the [geos](https://github.com/twpayne/go-geos)
package for working with PostGIS geometry types in [GEOS](https://libgeos.org/).
There are three steps:
1. Configure sqlc to use `*github.com/twpayne/go-geos.Geom` for geometry types (see [Overriding types](../howto/overrides.md) for details).
2. Call `github.com/twpayne/pgx-geos.Register` on each
`*github.com/jackc/pgx/v5.Conn`.
3. Annotate your SQL with `::geometry` typecasts, if needed.
```sql
-- Multipolygons in British National Grid (epsg:27700)
create table shapes(
id serial,
name varchar,
geom geometry(Multipolygon, 27700)
);
-- name: GetCentroids :many
SELECT id, name, ST_Centroid(geom)::geometry FROM shapes;
```
```json
{
"version": 2,
"gen": {
"go": {
"overrides": [
{
"db_type": "geometry",
"go_type": {
"import": "github.com/twpayne/go-geos",
"package": "geos",
"pointer": true,
"type": "Geom"
},
"nullable": true
}
]
}
}
}
```
```go
import (
"github.com/twpayne/go-geos"
pgxgeos "github.com/twpayne/pgx-geos"
)
// ...
config.AfterConnect = func(ctx context.Context, conn *pgx.Conn) error {
if err := pgxgeos.Register(ctx, conn, geos.NewContext()); err != nil {
return err
}
return nil
}
```
#### Using `github.com/twpayne/go-geom`
sqlc can be configured to use the [geom](https://github.com/twpayne/go-geom)
package for working with PostGIS geometry types. See [Overriding types](../howto/overrides.md) for more information.
```sql
-- Multipolygons in British National Grid (epsg:27700)
create table shapes(
id serial,
name varchar,
geom geometry(Multipolygon, 27700)
);
-- name: GetShapes :many
SELECT * FROM shapes;
```
```json
{
"version": "1",
"packages": [
{
"path": "db",
"engine": "postgresql",
"schema": "query.sql",
"queries": "query.sql"
}
],
"overrides": [
{
"db_type": "geometry",
"go_type": "github.com/twpayne/go-geom.MultiPolygon"
},
{
"db_type": "geometry",
"go_type": "github.com/twpayne/go-geom.MultiPolygon",
"nullable": true
}
]
}
```

View File

@@ -0,0 +1,190 @@
# Environment variables
## SQLCEXPERIMENT
The `SQLCEXPERIMENT` variable controls experimental features within sqlc. It is
a comma-separated list of experiment names. This is modeled after Go's
[GOEXPERIMENT](https://pkg.go.dev/internal/goexperiment) environment variable.
Experiment names can be prefixed with `no` to explicitly disable them.
```
SQLCEXPERIMENT=foo,bar # enable foo and bar experiments
SQLCEXPERIMENT=nofoo # explicitly disable foo experiment
SQLCEXPERIMENT=foo,nobar # enable foo, disable bar
```
The following experiments are defined:
### coreanalyzer
Routes `sqlc generate` through the core catalog and analyzer instead of each
engine's own analysis path. This is the same analysis used by `sqlc analyze`,
and the only analysis path for the ClickHouse and GoogleSQL engines.
```
SQLCEXPERIMENT=coreanalyzer
```
## SQLCCACHE
The `SQLCCACHE` environment variable dictates where `sqlc` will store cached
data. By default `sqlc` follows the [XDG Base Directory
Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html).
The cache is designed after Bazel's local disk cache and has three parts:
- `cas/` — a content-addressable store holding blobs (query analysis
results, WASM plugin binaries, compiled WASM machine code) keyed by the
SHA-256 hash of their contents. A remotely fetched plugin's address is
exactly the checksum declared in the configuration file, so it is loaded
directly by that address.
- `ac/` — an action cache mapping the digest of a unit of cacheable work and
its inputs (analyzing a query against a schema, compiling a WASM module to
machine code) to the CAS digests of its outputs.
- `exec/` — per-action directories where cached output trees are
materialized for tools that read them from disk, such as the
[wazero](https://wazero.io) runtime's compilation cache.
The entire directory is safe to delete at any time; sqlc will rebuild it as
needed.
## SQLCDEBUG
The `SQLCDEBUG` variable controls debugging variables within the runtime. It is
a comma-separated list of name=val pairs settings.
### dumpast
The `dumpast` command shows the SQL AST that was generated by the parser. Note
that this is the generic SQL AST, not the engine-specific SQL AST.
```
SQLCDEBUG=dumpast=1
```
```
([]interface {}) (len=1 cap=1) {
(*catalog.Catalog)(0xc0004f48c0)({
Comment: (string) "",
DefaultSchema: (string) (len=6) "public",
Name: (string) "",
Schemas: ([]*catalog.Schema) (len=3 cap=4) {
(*catalog.Schema)(0xc0004f4930)({
Name: (string) (len=6) "public",
Tables: ([]*catalog.Table) (len=1 cap=1) {
(*catalog.Table)(0xc00052ff20)({
Rel: (*ast.TableName)(0xc00052fda0)({
Catalog: (string) "",
Schema: (string) "",
Name: (string) (len=7) "authors"
}),
```
### dumpcatalog
The `dumpcatalog` command outputs the entire catalog. If you're using MySQL or
PostgreSQL, this can be a bit overwhelming. Expect this output to change in
future versions.
```
SQLCDEBUG=dumpcatalog=1
```
```
([]interface {}) (len=1 cap=1) {
(*catalog.Catalog)(0xc00050d1f0)({
Comment: (string) "",
DefaultSchema: (string) (len=6) "public",
Name: (string) "",
Schemas: ([]*catalog.Schema) (len=3 cap=4) {
(*catalog.Schema)(0xc00050d260)({
Name: (string) (len=6) "public",
Tables: ([]*catalog.Table) (len=1 cap=1) {
(*catalog.Table)(0xc0000c0840)({
Rel: (*ast.TableName)(0xc0000c06c0)({
Catalog: (string) "",
Schema: (string) "",
Name: (string) (len=7) "authors"
}),
```
### trace
The `trace` command is helpful for tracking down performance issues.
`SQLCDEBUG=trace=1`
By default, the trace output is written to `trace.out` in the current working
directory. You can configure a different path if needed.
`SQLCDEBUG=trace=name.out`
View the execution trace using the Go `trace` tool.
```
go tool trace trace.out
```
There's a ton of different views for the trace output, but here's an example
log showing the execution time for each package.
```
0.000043897 . 1 task sqlc (id 1, parent 0) created
0.000144923 . 101026 1 region generate started (duration: 47.619781ms)
0.001048975 . 904052 1 region package started (duration: 14.588456ms)
0.001054616 . 5641 1 name=authors dir=/Users/kyle/projects/sqlc/examples/python language=python
0.001071257 . 16641 1 region parse started (duration: 7.966549ms)
0.009043960 . 7972703 1 region codegen started (duration: 6.587086ms)
0.009171704 . 127744 1 new goroutine 35: text/template/parse.lex·dwrap·1
0.010361654 . 1189950 1 new goroutine 36: text/template/parse.lex·dwrap·1
0.015641815 . 5280161 1 region package started (duration: 10.904938ms)
0.015644943 . 3128 1 name=booktest dir=/Users/kyle/projects/sqlc/examples/python language=python
0.015647431 . 2488 1 region parse started (duration: 4.207749ms)
0.019860308 . 4212877 1 region codegen started (duration: 6.681624ms)
0.020028488 . 168180 1 new goroutine 37: text/template/parse.lex·dwrap·1
0.021020310 . 991822 1 new goroutine 8: text/template/parse.lex·dwrap·1
0.026551163 . 5530853 1 region package started (duration: 9.217294ms)
0.026554368 . 3205 1 name=jets dir=/Users/kyle/projects/sqlc/examples/python language=python
0.026556804 . 2436 1 region parse started (duration: 3.491005ms)
0.030051911 . 3495107 1 region codegen started (duration: 5.711931ms)
0.030213937 . 162026 1 new goroutine 20: text/template/parse.lex·dwrap·1
0.031099938 . 886001 1 new goroutine 38: text/template/parse.lex·dwrap·1
0.035772637 . 4672699 1 region package started (duration: 10.267039ms)
0.035775688 . 3051 1 name=ondeck dir=/Users/kyle/projects/sqlc/examples/python language=python
0.035778150 . 2462 1 region parse started (duration: 4.094518ms)
0.039877181 . 4099031 1 region codegen started (duration: 6.156341ms)
0.040010771 . 133590 1 new goroutine 39: text/template/parse.lex·dwrap·1
0.040894567 . 883796 1 new goroutine 40: text/template/parse.lex·dwrap·1
0.046042779 . 5148212 1 region writefiles started (duration: 1.718259ms)
0.047767781 . 1725002 1 task end
```
### processplugins
Setting this value to `0` disables process-based plugins. If a process-based
plugin is declared in the configuration file, running any `sqlc` command will
return an error.
`SQLCDEBUG=processplugins=0`
### dumpvetenv
The `dumpvetenv` command prints the variables available to a `sqlc vet` rule
during evaluation.
`SQLCDEBUG=dumpvetenv=1`
### dumpexplain
The `dumpexplain` command prints the JSON-formatted result from running
`EXPLAIN ...` on a query when a `sqlc vet` rule evaluation requires its output.
`SQLCDEBUG=dumpexplain=1`
## SQLCTMPDIR
If specified, use the given directory as the base for temporary folders. Only
applies when using WASM-based codegen plugins. When not specified, this
defaults to passing an empty string to
[`os.MkdirTemp`](https://pkg.go.dev/os#MkdirTemp).

View File

@@ -0,0 +1,34 @@
# Database and language support
| Language | Plugin | MySQL | PostgreSQL | SQLite |
| ---------- | ---------------------------------------------------------------------- | ------ | ---------- | --------------- |
| Go | (built-in) | Stable | Stable | Beta |
| Go | [sqlc-gen-go](https://github.com/sqlc-dev/sqlc-gen-go) | Stable | Stable | Beta |
| Kotlin | [sqlc-gen-kotlin](https://github.com/sqlc-dev/sqlc-gen-kotlin) | Beta | Beta | Not implemented |
| Python | [sqlc-gen-python](https://github.com/sqlc-dev/sqlc-gen-python) | Beta | Beta | Not implemented |
| TypeScript | [sqlc-gen-typescript](https://github.com/sqlc-dev/sqlc-gen-typescript) | Beta | Beta | Not implemented |
## Community language support
New languages can be added via [plugins](../guides/plugins.md).
| Language | Plugin | MySQL | PostgreSQL | SQLite |
| -------- | ------------------------------------------------------------------------------------- | ------ | ---------- | ------ |
| C# | [DaredevilOSS/sqlc-gen-csharp](https://github.com/DaredevilOSS/sqlc-gen-csharp) | Stable | Stable | Stable |
| F# | [kaashyapan/sqlc-gen-fsharp](https://github.com/kaashyapan/sqlc-gen-fsharp) | N/A | Beta | Beta |
| Java | [tandemdude/sqlc-gen-java](https://github.com/tandemdude/sqlc-gen-java) | Beta | Beta | N/A |
| PHP | [lcarilla/sqlc-plugin-php-dbal](https://github.com/lcarilla/sqlc-plugin-php-dbal) | Beta | N/A | N/A |
| Ruby | [DaredevilOSS/sqlc-gen-ruby](https://github.com/DaredevilOSS/sqlc-gen-ruby) | Beta | Beta | Beta |
| Zig | [tinyzimmer/sqlc-gen-zig](https://github.com/tinyzimmer/sqlc-gen-zig) | N/A | Beta | Beta |
| Python | [rayakame/sqlc-gen-better-python](https://github.com/rayakame/sqlc-gen-better-python) | Beta | Stable | Stable |
| Rust | [mathematic-inc/sqlc-gen-sqlx](https://github.com/mathematic-inc/sqlc-gen-sqlx) | N/A | Beta | N/A |
| \[Any\] | [fdietze/sqlc-gen-from-template](https://github.com/fdietze/sqlc-gen-from-template) | Stable | Stable | Stable |
Plugins developed by our Community can also be found using our
[github topic](https://github.com/topics/sqlc-plugin).
## Community projects
| Language | Project | MySQL | PostgreSQL | SQLite |
| -------- | ----------------------------------------------------------------- | ------ | ---------- | ------ |
| Gleam | [daniellionel01/parrot](https://github.com/daniellionel01/parrot) | Stable | Stable | Stable |

119
docs/reference/macros.md Normal file
View File

@@ -0,0 +1,119 @@
# Macros
## `sqlc.arg`
Attach a name to a parameter in a SQL query. This macro expands to an
engine-specific parameter placeholder. The name of the parameter is noted and
used during code generation.
```sql
-- name: GetAuthorByName :one
SELECT *
FROM authors
WHERE lower(name) = sqlc.arg(name);
-- >>> EXPANDS TO >>>
-- name: GetAuthorByName :one
SELECT *
FROM authors
WHERE lower(name) = ?;
```
See more examples in [Naming parameters](../howto/named_parameters.md).
## `sqlc.embed`
Embedding allows you to reuse existing model structs in more queries, resulting
in less manual serialization work. First, imagine we have the following schema
with students and test scores.
```sql
CREATE TABLE students (
id bigserial PRIMARY KEY,
name text,
age integer
);
CREATE TABLE test_scores (
student_id bigint,
score integer,
grade text
);
```
```sql
-- name: GetStudentAndScore :one
SELECT sqlc.embed(students), sqlc.embed(test_scores)
FROM students
JOIN test_scores ON test_scores.student_id = students.id
WHERE students.id = $1;
-- >>> EXPANDS TO >>>
-- name: GetStudentAndScore :one
SELECT students.*, test_scores.*
FROM students
JOIN test_scores ON test_scores.student_id = students.id
WHERE students.id = $1;
```
The Go method will return a struct with a field for the `Student` and field for
the test `TestScore` instead of each column existing on the struct.
```go
type GetStudentAndScoreRow struct {
Student Student
TestScore TestScore
}
func (q *Queries) GetStudentAndScore(ctx context.Context, id int64) (GetStudentAndScoreRow, error) {
// ...
}
```
See a full example in [Embedding structs](../howto/embedding.md).
## `sqlc.narg`
The same as `sqlc.arg`, but always marks the parameter as nullable.
```sql
-- name: GetAuthorByName :one
SELECT *
FROM authors
WHERE lower(name) = sqlc.narg(name);
-- >>> EXPANDS TO >>>
-- name: GetAuthorByName :one
SELECT *
FROM authors
WHERE LOWER(name) = ?;
```
See more examples in [Naming parameters](../howto/named_parameters.md).
## `sqlc.slice`
For drivers that do not support passing slices to the IN operator, the
`sqlc.slice` macro generates a dynamic query at runtime with the correct
number of parameters.
```sql
/* name: SelectStudents :many */
SELECT * FROM students
WHERE age IN (sqlc.slice("ages"))
-- >>> EXPANDS TO >>>
/* name: SelectStudents :many */
SELECT id, name, age FROM authors
WHERE age IN (/*SLICE:ages*/?)
```
Since the `/*SLICE:ages*/` placeholder is dynamically replaced on a per-query
basis, this macro can't be used with prepared statements.
See a full example in [Passing a slice as a parameter to a
query](../howto/select.md#mysql-and-sqlite).

View File

@@ -0,0 +1,230 @@
# Query annotations
sqlc requires each query to have a small comment indicating the name and
command. The format of this comment is as follows:
```sql
-- name: <name> <command>
```
## `:exec`
The generated method will return the error from
[ExecContext](https://golang.org/pkg/database/sql/#DB.ExecContext).
```sql
-- name: DeleteAuthor :exec
DELETE FROM authors
WHERE id = $1;
```
```go
func (q *Queries) DeleteAuthor(ctx context.Context, id int64) error {
_, err := q.db.ExecContext(ctx, deleteAuthor, id)
return err
}
```
## `:execresult`
The generated method will return the [sql.Result](https://golang.org/pkg/database/sql/#Result) returned by
[ExecContext](https://golang.org/pkg/database/sql/#DB.ExecContext).
```sql
-- name: DeleteAllAuthors :execresult
DELETE FROM authors;
```
```go
func (q *Queries) DeleteAllAuthors(ctx context.Context) (sql.Result, error) {
return q.db.ExecContext(ctx, deleteAllAuthors)
}
```
## `:execrows`
The generated method will return the number of affected rows from the
[result](https://golang.org/pkg/database/sql/#Result) returned by
[ExecContext](https://golang.org/pkg/database/sql/#DB.ExecContext).
```sql
-- name: DeleteAllAuthors :execrows
DELETE FROM authors;
```
```go
func (q *Queries) DeleteAllAuthors(ctx context.Context) (int64, error) {
_, err := q.db.ExecContext(ctx, deleteAllAuthors)
// ...
}
```
## `:execlastid`
The generated method will return the number generated by the database from the
[result](https://golang.org/pkg/database/sql/#Result) returned by
[ExecContext](https://golang.org/pkg/database/sql/#DB.ExecContext).
```sql
-- name: InsertAuthor :execlastid
INSERT INTO authors (name) VALUES (?);
```
```go
func (q *Queries) InsertAuthor(ctx context.Context, name string) (int64, error) {
_, err := q.db.ExecContext(ctx, insertAuthor, name)
// ...
}
```
## `:many`
The generated method will return a slice of records via
[QueryContext](https://golang.org/pkg/database/sql/#DB.QueryContext).
```sql
-- name: ListAuthors :many
SELECT * FROM authors
ORDER BY name;
```
```go
func (q *Queries) ListAuthors(ctx context.Context) ([]Author, error) {
rows, err := q.db.QueryContext(ctx, listAuthors)
// ...
}
```
## `:one`
The generated method will return a single record via
[QueryRowContext](https://golang.org/pkg/database/sql/#DB.QueryRowContext).
```sql
-- name: GetAuthor :one
SELECT * FROM authors
WHERE id = $1 LIMIT 1;
```
```go
func (q *Queries) GetAuthor(ctx context.Context, id int64) (Author, error) {
row := q.db.QueryRowContext(ctx, getAuthor, id)
// ...
}
```
## `:batchexec`
__NOTE: This command only works with PostgreSQL using the `pgx/v4` and `pgx/v5` drivers and outputting Go code.__
The generated method will return a batch object. The batch object will have
the following methods:
- `Exec`, that takes a `func(int, error)` parameter,
- `Close`, to close the batch operation early.
```sql
-- name: DeleteBook :batchexec
DELETE FROM books
WHERE book_id = $1;
```
```go
type DeleteBookBatchResults struct {
br pgx.BatchResults
ind int
}
func (q *Queries) DeleteBook(ctx context.Context, bookID []int32) *DeleteBookBatchResults {
//...
}
func (b *DeleteBookBatchResults) Exec(f func(int, error)) {
//...
}
func (b *DeleteBookBatchResults) Close() error {
//...
}
```
## `:batchmany`
__NOTE: This command only works with PostgreSQL using the `pgx/v4` and `pgx/v5` drivers and outputting Go code.__
The generated method will return a batch object. The batch object will have
the following methods:
- `Query`, that takes a `func(int, []T, error)` parameter, where `T` is your query's return type
- `Close`, to close the batch operation early.
```sql
-- name: BooksByTitleYear :batchmany
SELECT * FROM books
WHERE title = $1 AND year = $2;
```
```go
type BooksByTitleYearBatchResults struct {
br pgx.BatchResults
ind int
}
type BooksByTitleYearParams struct {
Title string `json:"title"`
Year int32 `json:"year"`
}
func (q *Queries) BooksByTitleYear(ctx context.Context, arg []BooksByTitleYearParams) *BooksByTitleYearBatchResults {
//...
}
func (b *BooksByTitleYearBatchResults) Query(f func(int, []Book, error)) {
//...
}
func (b *BooksByTitleYearBatchResults) Close() error {
//...
}
```
## `:batchone`
__NOTE: This command only works with PostgreSQL using the `pgx/v4` and `pgx/v5` drivers and outputting Go code.__
The generated method will return a batch object. The batch object will have
the following methods:
- `QueryRow`, that takes a `func(int, T, error)` parameter, where `T` is your query's return type
- `Close`, to close the batch operation early.
```sql
-- name: CreateBook :batchone
INSERT INTO books (
author_id,
isbn
) VALUES (
$1,
$2
)
RETURNING book_id, author_id, isbn
```
```go
type CreateBookBatchResults struct {
br pgx.BatchResults
ind int
}
type CreateBookParams struct {
AuthorID int32 `json:"author_id"`
Isbn string `json:"isbn"`
}
func (q *Queries) CreateBook(ctx context.Context, arg []CreateBookParams) *CreateBookBatchResults {
//...
}
func (b *CreateBookBatchResults) QueryRow(f func(int, Book, error)) {
//...
}
func (b *CreateBookBatchResults) Close() error {
//...
}
```
## `:copyfrom`
__NOTE: This command is driver and package specific, see [how to insert](../howto/insert.md#using-copyfrom)
This command is used to insert rows a lot faster than sequential inserts.

72
docs/toc.yaml Normal file
View File

@@ -0,0 +1,72 @@
# Navigation for the sqlc documentation site.
#
# Every page in docs/ must appear exactly once, either in a section or in
# the unlisted set. Unlisted pages are published but do not appear in the
# sidebar.
index: index.md
sections:
- title: Overview
pages:
- overview/install.md
- title: Tutorials
pages:
- tutorials/getting-started-mysql.md
- tutorials/getting-started-postgresql.md
- tutorials/getting-started-sqlite.md
- title: Commands
pages:
- howto/analyze.md
- howto/fmt.md
- howto/generate.md
- howto/parse.md
- howto/push.md
- howto/verify.md
- howto/vet.md
- title: How-to Guides
pages:
- howto/select.md
- howto/query_count.md
- howto/insert.md
- howto/update.md
- howto/delete.md
- howto/prepared_query.md
- howto/transactions.md
- howto/named_parameters.md
- howto/ddl.md
- howto/structs.md
- howto/embedding.md
- howto/overrides.md
- howto/rename.md
- title: sqlc Cloud
pages:
- howto/managed-databases.md
- title: Reference
pages:
- reference/changelog.md
- reference/cli.md
- reference/config.md
- reference/datatypes.md
- reference/environment-variables.md
- reference/language-support.md
- reference/macros.md
- reference/query-annotations.md
- title: Conceptual Guides
pages:
- howto/ci-cd.md
- guides/using-go-and-pgx.md
- guides/plugins.md
- guides/development.md
- guides/privacy.md
unlisted:
- guides/migrating-off-hosted-managed-databases.md
- guides/migrating-to-sqlc-gen-kotlin.md
- guides/migrating-to-sqlc-gen-python.md

View File

@@ -0,0 +1,221 @@
# Getting started with MySQL
This tutorial assumes that the latest version of sqlc is
[installed](../overview/install.md) and ready to use.
We'll generate Go code here, but other
[language plugins](../reference/language-support.md) are available. You'll
naturally need the Go toolchain if you want to build and run a program with the
code sqlc generates, but sqlc itself has no dependencies.
At the end, you'll push your SQL queries to [sqlc Cloud](https://dashboard.sqlc.dev/) for further insights and analysis.
## Setting up
Create a new directory called `sqlc-tutorial` and open it up.
Initialize a new Go module named `tutorial.sqlc.dev/app`
```shell
go mod init tutorial.sqlc.dev/app
```
sqlc looks for either a `sqlc.(yaml|yml)` or `sqlc.json` file in the current
directory. In our new directory, create a file named `sqlc.yaml` with the
following contents:
```yaml
version: "2"
sql:
- engine: "mysql"
queries: "query.sql"
schema: "schema.sql"
gen:
go:
package: "tutorial"
out: "tutorial"
```
## Schema and queries
sqlc needs to know your database schema and queries in order to generate code.
In the same directory, create a file named `schema.sql` with the following
content:
```sql
CREATE TABLE authors (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
name text NOT NULL,
bio text
);
```
Next, create a `query.sql` file with the following four queries:
```sql
-- name: GetAuthor :one
SELECT * FROM authors
WHERE id = ? LIMIT 1;
-- name: ListAuthors :many
SELECT * FROM authors
ORDER BY name;
-- name: CreateAuthor :execresult
INSERT INTO authors (
name, bio
) VALUES (
?, ?
);
-- name: DeleteAuthor :exec
DELETE FROM authors
WHERE id = ?;
```
## Generating code
You are now ready to generate code. You shouldn't see any output when you run
the `generate` subcommand, unless something goes wrong:
```shell
sqlc generate
```
You should now have a `tutorial` subdirectory with three files containing Go
source code. These files comprise a Go package named `tutorial`:
```
├── go.mod
├── query.sql
├── schema.sql
├── sqlc.yaml
└── tutorial
├── db.go
├── models.go
└── query.sql.go
```
## Using generated code
You can use your newly-generated `tutorial` package from any Go program.
Create a file named `tutorial.go` and add the following contents:
```go
package main
import (
"context"
"database/sql"
"log"
"reflect"
_ "github.com/go-sql-driver/mysql"
"tutorial.sqlc.dev/app/tutorial"
)
func run() error {
ctx := context.Background()
db, err := sql.Open("mysql", "user:password@/dbname?parseTime=true")
if err != nil {
return err
}
queries := tutorial.New(db)
// list all authors
authors, err := queries.ListAuthors(ctx)
if err != nil {
return err
}
log.Println(authors)
// create an author
result, err := queries.CreateAuthor(ctx, tutorial.CreateAuthorParams{
Name: "Brian Kernighan",
Bio: sql.NullString{String: "Co-author of The C Programming Language and The Go Programming Language", Valid: true},
})
if err != nil {
return err
}
insertedAuthorID, err := result.LastInsertId()
if err != nil {
return err
}
log.Println(insertedAuthorID)
// get the author we just inserted
fetchedAuthor, err := queries.GetAuthor(ctx, insertedAuthorID)
if err != nil {
return err
}
// prints true
log.Println(reflect.DeepEqual(insertedAuthorID, fetchedAuthor.ID))
return nil
}
func main() {
if err := run(); err != nil {
log.Fatal(err)
}
}
```
Before this code will compile you'll need to fetch the relevant MySQL driver:
```shell
go get github.com/go-sql-driver/mysql
go build .
```
The program should compile without errors. To make that possible, sqlc generates
readable, **idiomatic** Go code that you otherwise would've had to write
yourself. Take a look in `tutorial/query.sql.go`.
Of course for this program to run successfully you'll need
to compile after replacing the database connection parameters in the call to
`sql.Open()` with the correct parameters for your database. And your
database must have the `authors` table as defined in `schema.sql`.
You should now have a working program using sqlc's generated Go source code,
and hopefully can see how you'd use sqlc in your own real-world applications.
## Query verification
[sqlc Cloud](https://dashboard.sqlc.dev) provides additional verification, catching subtle bugs. To get started, create a
[dashboard account](https://dashboard.sqlc.dev). Once you've signed in, create a
project and generate an auth token. Add your project's ID to the `cloud` block
to your sqlc.yaml.
```yaml
version: "2"
cloud:
# Replace <PROJECT_ID> with your project ID from the sqlc Cloud dashboard
project: "<PROJECT_ID>"
sql:
- engine: "mysql"
queries: "query.sql"
schema: "schema.sql"
gen:
go:
package: "tutorial"
out: "tutorial"
```
Replace `<PROJECT_ID>` with your project ID from the sqlc Cloud dashboard. It
will look something like `01HA8SZH31HKYE9RR3N3N3TSJM`.
And finally, set the `SQLC_AUTH_TOKEN` environment variable:
```shell
export SQLC_AUTH_TOKEN="<your sqlc auth token>"
```
```shell
$ sqlc push --tag tutorial
```

View File

@@ -0,0 +1,248 @@
# Getting started with PostgreSQL
This tutorial assumes that the latest version of sqlc is
[installed](../overview/install.md) and ready to use.
We'll generate Go code here, but other
[language plugins](../reference/language-support.md) are available. You'll
naturally need the Go toolchain if you want to build and run a program with the
code sqlc generates, but sqlc itself has no dependencies.
At the end, you'll push your SQL queries to [sqlc
Cloud](https://dashboard.sqlc.dev/) for further insights and analysis.
## Setting up
Create a new directory called `sqlc-tutorial` and open it up.
Initialize a new Go module named `tutorial.sqlc.dev/app`:
```shell
go mod init tutorial.sqlc.dev/app
```
sqlc looks for either a `sqlc.(yaml|yml)` or `sqlc.json` file in the current
directory. In our new directory, create a file named `sqlc.yaml` with the
following contents:
```yaml
version: "2"
sql:
- engine: "postgresql"
queries: "query.sql"
schema: "schema.sql"
gen:
go:
package: "tutorial"
out: "tutorial"
sql_package: "pgx/v5"
```
## Schema and queries
sqlc needs to know your database schema and queries in order to generate code.
In the same directory, create a file named `schema.sql` with the following
content:
```sql
CREATE TABLE authors (
id BIGSERIAL PRIMARY KEY,
name text NOT NULL,
bio text
);
```
Next, create a `query.sql` file with the following five queries:
```sql
-- name: GetAuthor :one
SELECT * FROM authors
WHERE id = $1 LIMIT 1;
-- name: ListAuthors :many
SELECT * FROM authors
ORDER BY name;
-- name: CreateAuthor :one
INSERT INTO authors (
name, bio
) VALUES (
$1, $2
)
RETURNING *;
-- name: UpdateAuthor :exec
UPDATE authors
set name = $2,
bio = $3
WHERE id = $1;
-- name: DeleteAuthor :exec
DELETE FROM authors
WHERE id = $1;
```
If you prefer, you can alter the `UpdateAuthor` query to return the updated
record:
```sql
-- name: UpdateAuthor :one
UPDATE authors
set name = $2,
bio = $3
WHERE id = $1
RETURNING *;
```
## Generating code
You are now ready to generate code. You shouldn't see any output when you run
the `generate` subcommand, unless something goes wrong:
```shell
sqlc generate
```
You should now have a `tutorial` subdirectory with three files containing Go
source code. These files comprise a Go package named `tutorial`:
```
├── go.mod
├── query.sql
├── schema.sql
├── sqlc.yaml
└── tutorial
├── db.go
├── models.go
└── query.sql.go
```
## Using generated code
You can use your newly-generated `tutorial` package from any Go program.
Create a file named `tutorial.go` and add the following contents:
```go
package main
import (
"context"
"log"
"reflect"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
"tutorial.sqlc.dev/app/tutorial"
)
func run() error {
ctx := context.Background()
conn, err := pgx.Connect(ctx, "user=pqgotest dbname=pqgotest sslmode=verify-full")
if err != nil {
return err
}
defer conn.Close(ctx)
queries := tutorial.New(conn)
// list all authors
authors, err := queries.ListAuthors(ctx)
if err != nil {
return err
}
log.Println(authors)
// create an author
insertedAuthor, err := queries.CreateAuthor(ctx, tutorial.CreateAuthorParams{
Name: "Brian Kernighan",
Bio: pgtype.Text{String: "Co-author of The C Programming Language and The Go Programming Language", Valid: true},
})
if err != nil {
return err
}
log.Println(insertedAuthor)
// get the author we just inserted
fetchedAuthor, err := queries.GetAuthor(ctx, insertedAuthor.ID)
if err != nil {
return err
}
// prints true
log.Println(reflect.DeepEqual(insertedAuthor, fetchedAuthor))
return nil
}
func main() {
if err := run(); err != nil {
log.Fatal(err)
}
}
```
Before this code will compile you'll need to fetch the relevant PostgreSQL
driver. You can use `lib/pq` with the standard library's `database/sql`
package, but in this tutorial we've used `pgx/v5`:
```shell
go get github.com/jackc/pgx/v5
go build .
```
The program should compile without errors. To make that possible, sqlc generates
readable, **idiomatic** Go code that you otherwise would've had to write
yourself. Take a look in `tutorial/query.sql.go`.
Of course for this program to run successfully you'll need
to compile after replacing the database connection parameters in the call to
`pgx.Connect()` with the correct parameters for your database. And your
database must have the `authors` table as defined in `schema.sql`.
You should now have a working program using sqlc's generated Go source code,
and hopefully can see how you'd use sqlc in your own real-world applications.
## Query verification
[sqlc Cloud](https://dashboard.sqlc.dev) provides additional verification, catching subtle bugs. To get started, create a
[dashboard account](https://dashboard.sqlc.dev). Once you've signed in, create a
project and generate an auth token. Add your project's ID to the `cloud` block
to your sqlc.yaml.
```yaml
version: "2"
cloud:
# Replace <PROJECT_ID> with your project ID from the sqlc Cloud dashboard
project: "<PROJECT_ID>"
sql:
- engine: "postgresql"
queries: "query.sql"
schema: "schema.sql"
gen:
go:
package: "tutorial"
out: "tutorial"
sql_package: "pgx/v5"
```
Replace `<PROJECT_ID>` with your project ID from the sqlc Cloud dashboard. It
will look something like `01HA8SZH31HKYE9RR3N3N3TSJM`.
And finally, set the `SQLC_AUTH_TOKEN` environment variable:
```shell
export SQLC_AUTH_TOKEN="<your sqlc auth token>"
```
```shell
$ sqlc push --tag tutorial
```
In the sidebar, go to the "Queries" section to see your published queries. Run
`verify` to ensure that previously published queries continue to work against
updated database schema.
```shell
$ sqlc verify --against tutorial
```

View File

@@ -0,0 +1,201 @@
# Getting started with SQLite
This tutorial assumes that the latest version of sqlc is
[installed](../overview/install.md) and ready to use.
We'll generate Go code here, but other
[language plugins](../reference/language-support.md) are available. You'll
naturally need the Go toolchain if you want to build and run a program with the
code sqlc generates, but sqlc itself has no dependencies.
## Setting up
Create a new directory called `sqlc-tutorial` and open it up.
Initialize a new Go module named `tutorial.sqlc.dev/app`
```shell
go mod init tutorial.sqlc.dev/app
```
sqlc looks for either a `sqlc.(yaml|yml)` or `sqlc.json` file in the current
directory. In our new directory, create a file named `sqlc.yaml` with the
following contents:
```yaml
version: "2"
sql:
- engine: "sqlite"
queries: "query.sql"
schema: "schema.sql"
gen:
go:
package: "tutorial"
out: "tutorial"
```
## Schema and queries
sqlc needs to know your database schema and queries in order to generate code.
In the same directory, create a file named `schema.sql` with the following
content:
```sql
CREATE TABLE authors (
id INTEGER PRIMARY KEY,
name text NOT NULL,
bio text
);
```
Next, create a `query.sql` file with the following five queries:
```sql
-- name: GetAuthor :one
SELECT * FROM authors
WHERE id = ? LIMIT 1;
-- name: ListAuthors :many
SELECT * FROM authors
ORDER BY name;
-- name: CreateAuthor :one
INSERT INTO authors (
name, bio
) VALUES (
?, ?
)
RETURNING *;
-- name: UpdateAuthor :exec
UPDATE authors
set name = ?,
bio = ?
WHERE id = ?;
-- name: DeleteAuthor :exec
DELETE FROM authors
WHERE id = ?;
```
If you prefer, you can alter the `UpdateAuthor` query to return the updated
record:
```sql
-- name: UpdateAuthor :one
UPDATE authors
set name = ?,
bio = ?
WHERE id = ?
RETURNING *;
```
## Generating code
You are now ready to generate code. You shouldn't see any output when you run
the `generate` subcommand, unless something goes wrong:
```shell
sqlc generate
```
You should now have a `tutorial` subdirectory with three files containing Go
source code. These files comprise a Go package named `tutorial`:
```
├── go.mod
├── query.sql
├── schema.sql
├── sqlc.yaml
└── tutorial
├── db.go
├── models.go
└── query.sql.go
```
## Using generated code
You can use your newly-generated `tutorial` package from any Go program.
Create a file named `tutorial.go` and add the following contents:
```go
package main
import (
"context"
"database/sql"
_ "embed"
"log"
"reflect"
_ "modernc.org/sqlite"
"tutorial.sqlc.dev/app/tutorial"
)
//go:embed schema.sql
var ddl string
func run() error {
ctx := context.Background()
db, err := sql.Open("sqlite", ":memory:")
if err != nil {
return err
}
// create tables
if _, err := db.ExecContext(ctx, ddl); err != nil {
return err
}
queries := tutorial.New(db)
// list all authors
authors, err := queries.ListAuthors(ctx)
if err != nil {
return err
}
log.Println(authors)
// create an author
insertedAuthor, err := queries.CreateAuthor(ctx, tutorial.CreateAuthorParams{
Name: "Brian Kernighan",
Bio: sql.NullString{String: "Co-author of The C Programming Language and The Go Programming Language", Valid: true},
})
if err != nil {
return err
}
log.Println(insertedAuthor)
// get the author we just inserted
fetchedAuthor, err := queries.GetAuthor(ctx, insertedAuthor.ID)
if err != nil {
return err
}
// prints true
log.Println(reflect.DeepEqual(insertedAuthor, fetchedAuthor))
return nil
}
func main() {
if err := run(); err != nil {
log.Fatal(err)
}
}
```
Before this code will compile you'll need to fetch the relevant SQLite driver:
```shell
go get modernc.org/sqlite
go build .
```
The program should compile without errors, and run successfully. To make that
possible, sqlc generates readable, **idiomatic** Go code that you
otherwise would've had to write yourself. Take a look in `tutorial/query.sql.go`.
You should now have a working program using sqlc's generated Go source code,
and hopefully can see how you'd use sqlc in your own real-world applications.

View File

@@ -0,0 +1,31 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
package authors
import (
"context"
"database/sql"
)
type DBTX interface {
ExecContext(context.Context, string, ...any) (sql.Result, error)
PrepareContext(context.Context, string) (*sql.Stmt, error)
QueryContext(context.Context, string, ...any) (*sql.Rows, error)
QueryRowContext(context.Context, string, ...any) *sql.Row
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
func (q *Queries) WithTx(tx *sql.Tx) *Queries {
return &Queries{
db: tx,
}
}

View File

@@ -0,0 +1,53 @@
//go:build examples
package authors
import (
"context"
"database/sql"
"testing"
_ "github.com/go-sql-driver/mysql"
"github.com/sqlc-dev/sqlc/internal/sqltest/local"
)
func TestAuthors(t *testing.T) {
ctx := context.Background()
uri := local.MySQL(t, []string{"schema.sql"})
sdb, err := sql.Open("mysql", uri)
if err != nil {
t.Fatal(err)
}
defer sdb.Close()
db := New(sdb)
// list all authors
authors, err := db.ListAuthors(ctx)
if err != nil {
t.Fatal(err)
}
t.Log(authors)
// create an author
result, err := db.CreateAuthor(ctx, CreateAuthorParams{
Name: "Brian Kernighan",
Bio: sql.NullString{String: "Co-author of The C Programming Language and The Go Programming Language", Valid: true},
})
if err != nil {
t.Fatal(err)
}
authorID, err := result.LastInsertId()
if err != nil {
t.Fatal(err)
}
t.Log(authorID)
// get the author we just inserted
fetchedAuthor, err := db.GetAuthor(ctx, authorID)
if err != nil {
t.Fatal(err)
}
t.Log(fetchedAuthor)
}

View File

@@ -0,0 +1,15 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
package authors
import (
"database/sql"
)
type Author struct {
ID int64
Name string
Bio sql.NullString
}

View File

@@ -0,0 +1,18 @@
/* name: GetAuthor :one */
SELECT * FROM authors
WHERE id = ? LIMIT 1;
/* name: ListAuthors :many */
SELECT * FROM authors
ORDER BY name;
/* name: CreateAuthor :execresult */
INSERT INTO authors (
name, bio
) VALUES (
?, ?
);
/* name: DeleteAuthor :exec */
DELETE FROM authors
WHERE id = ?;

View File

@@ -0,0 +1,78 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: query.sql
package authors
import (
"context"
"database/sql"
)
const createAuthor = `-- name: CreateAuthor :execresult
INSERT INTO authors (
name, bio
) VALUES (
?, ?
)
`
type CreateAuthorParams struct {
Name string
Bio sql.NullString
}
func (q *Queries) CreateAuthor(ctx context.Context, arg CreateAuthorParams) (sql.Result, error) {
return q.db.ExecContext(ctx, createAuthor, arg.Name, arg.Bio)
}
const deleteAuthor = `-- name: DeleteAuthor :exec
DELETE FROM authors
WHERE id = ?
`
func (q *Queries) DeleteAuthor(ctx context.Context, id int64) error {
_, err := q.db.ExecContext(ctx, deleteAuthor, id)
return err
}
const getAuthor = `-- name: GetAuthor :one
SELECT id, name, bio FROM authors
WHERE id = ? LIMIT 1
`
func (q *Queries) GetAuthor(ctx context.Context, id int64) (Author, error) {
row := q.db.QueryRowContext(ctx, getAuthor, id)
var i Author
err := row.Scan(&i.ID, &i.Name, &i.Bio)
return i, err
}
const listAuthors = `-- name: ListAuthors :many
SELECT id, name, bio FROM authors
ORDER BY name
`
func (q *Queries) ListAuthors(ctx context.Context) ([]Author, error) {
rows, err := q.db.QueryContext(ctx, listAuthors)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Author
for rows.Next() {
var i Author
if err := rows.Scan(&i.ID, &i.Name, &i.Bio); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}

View File

@@ -0,0 +1,5 @@
CREATE TABLE authors (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name text NOT NULL,
bio text
);

View File

@@ -0,0 +1,32 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
package authors
import (
"context"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
)
type DBTX interface {
Exec(context.Context, string, ...any) (pgconn.CommandTag, error)
Query(context.Context, string, ...any) (pgx.Rows, error)
QueryRow(context.Context, string, ...any) pgx.Row
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
func (q *Queries) WithTx(tx pgx.Tx) *Queries {
return &Queries{
db: tx,
}
}

View File

@@ -0,0 +1,49 @@
//go:build examples
package authors
import (
"context"
"testing"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
"github.com/sqlc-dev/sqlc/internal/sqltest/local"
)
func TestAuthors(t *testing.T) {
ctx := context.Background()
uri := local.PostgreSQL(t, []string{"schema.sql"})
db, err := pgx.Connect(ctx, uri)
if err != nil {
t.Fatal(err)
}
defer db.Close(ctx)
q := New(db)
// list all authors
authors, err := q.ListAuthors(ctx)
if err != nil {
t.Fatal(err)
}
t.Log(authors)
// create an author
insertedAuthor, err := q.CreateAuthor(ctx, CreateAuthorParams{
Name: "Brian Kernighan",
Bio: pgtype.Text{String: "Co-author of The C Programming Language and The Go Programming Language", Valid: true},
})
if err != nil {
t.Fatal(err)
}
t.Log(insertedAuthor)
// get the author we just inserted
fetchedAuthor, err := q.GetAuthor(ctx, insertedAuthor.ID)
if err != nil {
t.Fatal(err)
}
t.Log(fetchedAuthor)
}

View File

@@ -0,0 +1,15 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
package authors
import (
"github.com/jackc/pgx/v5/pgtype"
)
type Author struct {
ID int64
Name string
Bio pgtype.Text
}

View File

@@ -0,0 +1,19 @@
-- name: GetAuthor :one
SELECT * FROM authors
WHERE id = $1 LIMIT 1;
-- name: ListAuthors :many
SELECT * FROM authors
ORDER BY name;
-- name: CreateAuthor :one
INSERT INTO authors (
name, bio
) VALUES (
$1, $2
)
RETURNING *;
-- name: DeleteAuthor :exec
DELETE FROM authors
WHERE id = $1;

View File

@@ -0,0 +1,80 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: query.sql
package authors
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const createAuthor = `-- name: CreateAuthor :one
INSERT INTO authors (
name, bio
) VALUES (
$1, $2
)
RETURNING id, name, bio
`
type CreateAuthorParams struct {
Name string
Bio pgtype.Text
}
func (q *Queries) CreateAuthor(ctx context.Context, arg CreateAuthorParams) (Author, error) {
row := q.db.QueryRow(ctx, createAuthor, arg.Name, arg.Bio)
var i Author
err := row.Scan(&i.ID, &i.Name, &i.Bio)
return i, err
}
const deleteAuthor = `-- name: DeleteAuthor :exec
DELETE FROM authors
WHERE id = $1
`
func (q *Queries) DeleteAuthor(ctx context.Context, id int64) error {
_, err := q.db.Exec(ctx, deleteAuthor, id)
return err
}
const getAuthor = `-- name: GetAuthor :one
SELECT id, name, bio FROM authors
WHERE id = $1 LIMIT 1
`
func (q *Queries) GetAuthor(ctx context.Context, id int64) (Author, error) {
row := q.db.QueryRow(ctx, getAuthor, id)
var i Author
err := row.Scan(&i.ID, &i.Name, &i.Bio)
return i, err
}
const listAuthors = `-- name: ListAuthors :many
SELECT id, name, bio FROM authors
ORDER BY name
`
func (q *Queries) ListAuthors(ctx context.Context) ([]Author, error) {
rows, err := q.db.Query(ctx, listAuthors)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Author
for rows.Next() {
var i Author
if err := rows.Scan(&i.ID, &i.Name, &i.Bio); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}

View File

@@ -0,0 +1,5 @@
CREATE TABLE authors (
id BIGSERIAL PRIMARY KEY,
name text NOT NULL,
bio text
);

View File

@@ -0,0 +1,52 @@
version: '2'
cloud:
project: "01HAQMMECEYQYKFJN8MP16QC41"
sql:
- name: postgresql
schema: postgresql/schema.sql
queries: postgresql/query.sql
engine: postgresql
database:
uri: "${VET_TEST_EXAMPLES_POSTGRES_AUTHORS}"
analyzer:
database: false
rules:
- sqlc/db-prepare
- postgresql-query-too-costly
gen:
go:
package: authors
sql_package: pgx/v5
out: postgresql
- name: mysql
schema: mysql/schema.sql
queries: mysql/query.sql
engine: mysql
database:
uri: "${VET_TEST_EXAMPLES_MYSQL_AUTHORS}"
rules:
- sqlc/db-prepare
# - mysql-query-too-costly
gen:
go:
package: authors
out: mysql
- name: sqlite
schema: sqlite/schema.sql
queries: sqlite/query.sql
engine: sqlite
database:
uri: file:authors?mode=memory&cache=shared
rules:
- sqlc/db-prepare
gen:
go:
package: authors
out: sqlite
rules:
- name: postgresql-query-too-costly
message: "Too costly"
rule: "postgresql.explain.plan.total_cost > 300.0"
- name: mysql-query-too-costly
message: "Too costly"
rule: "has(mysql.explain.query_block.cost_info) && double(mysql.explain.query_block.cost_info.query_cost) > 2.0"

View File

@@ -0,0 +1,31 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
package authors
import (
"context"
"database/sql"
)
type DBTX interface {
ExecContext(context.Context, string, ...any) (sql.Result, error)
PrepareContext(context.Context, string) (*sql.Stmt, error)
QueryContext(context.Context, string, ...any) (*sql.Rows, error)
QueryRowContext(context.Context, string, ...any) *sql.Row
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
func (q *Queries) WithTx(tx *sql.Tx) *Queries {
return &Queries{
db: tx,
}
}

View File

@@ -0,0 +1,48 @@
//go:build examples
package authors
import (
"context"
"database/sql"
"testing"
"github.com/sqlc-dev/sqlc/internal/sqltest"
)
func TestAuthors(t *testing.T) {
sdb, cleanup := sqltest.SQLite(t, []string{"schema.sql"})
defer sdb.Close()
defer cleanup()
ctx := context.Background()
db := New(sdb)
// list all authors
authors, err := db.ListAuthors(ctx)
if err != nil {
t.Fatal(err)
}
t.Log(authors)
// create an author
result, err := db.CreateAuthor(ctx, CreateAuthorParams{
Name: "Brian Kernighan",
Bio: sql.NullString{String: "Co-author of The C Programming Language and The Go Programming Language", Valid: true},
})
if err != nil {
t.Fatal(err)
}
authorID, err := result.LastInsertId()
if err != nil {
t.Fatal(err)
}
t.Log(authorID)
// get the author we just inserted
fetchedAuthor, err := db.GetAuthor(ctx, authorID)
if err != nil {
t.Fatal(err)
}
t.Log(fetchedAuthor)
}

View File

@@ -0,0 +1,15 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
package authors
import (
"database/sql"
)
type Author struct {
ID int64
Name string
Bio sql.NullString
}

View File

@@ -0,0 +1,18 @@
/* name: GetAuthor :one */
SELECT * FROM authors
WHERE id = ? LIMIT 1;
/* name: ListAuthors :many */
SELECT * FROM authors
ORDER BY name;
/* name: CreateAuthor :execresult */
INSERT INTO authors (
name, bio
) VALUES (
?, ?
);
/* name: DeleteAuthor :exec */
DELETE FROM authors
WHERE id = ?;

View File

@@ -0,0 +1,78 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: query.sql
package authors
import (
"context"
"database/sql"
)
const createAuthor = `-- name: CreateAuthor :execresult
INSERT INTO authors (
name, bio
) VALUES (
?, ?
)
`
type CreateAuthorParams struct {
Name string
Bio sql.NullString
}
func (q *Queries) CreateAuthor(ctx context.Context, arg CreateAuthorParams) (sql.Result, error) {
return q.db.ExecContext(ctx, createAuthor, arg.Name, arg.Bio)
}
const deleteAuthor = `-- name: DeleteAuthor :exec
DELETE FROM authors
WHERE id = ?
`
func (q *Queries) DeleteAuthor(ctx context.Context, id int64) error {
_, err := q.db.ExecContext(ctx, deleteAuthor, id)
return err
}
const getAuthor = `-- name: GetAuthor :one
SELECT id, name, bio FROM authors
WHERE id = ? LIMIT 1
`
func (q *Queries) GetAuthor(ctx context.Context, id int64) (Author, error) {
row := q.db.QueryRowContext(ctx, getAuthor, id)
var i Author
err := row.Scan(&i.ID, &i.Name, &i.Bio)
return i, err
}
const listAuthors = `-- name: ListAuthors :many
SELECT id, name, bio FROM authors
ORDER BY name
`
func (q *Queries) ListAuthors(ctx context.Context) ([]Author, error) {
rows, err := q.db.QueryContext(ctx, listAuthors)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Author
for rows.Next() {
var i Author
if err := rows.Scan(&i.ID, &i.Name, &i.Bio); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}

View File

@@ -0,0 +1,5 @@
CREATE TABLE authors (
id integer PRIMARY KEY AUTOINCREMENT,
name text NOT NULL,
bio text
);

View File

@@ -0,0 +1,404 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: batch.go
package batch
import (
"context"
"errors"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
)
var (
ErrBatchAlreadyClosed = errors.New("batch already closed")
)
const booksByYear = `-- name: BooksByYear :batchmany
SELECT book_id, author_id, isbn, book_type, title, year, available, tags FROM books
WHERE year = $1
`
type BooksByYearBatchResults struct {
br pgx.BatchResults
tot int
closed bool
}
func (q *Queries) BooksByYear(ctx context.Context, year []int32) *BooksByYearBatchResults {
batch := &pgx.Batch{}
for _, a := range year {
vals := []any{
a,
}
batch.Queue(booksByYear, vals...)
}
br := q.db.SendBatch(ctx, batch)
return &BooksByYearBatchResults{br, len(year), false}
}
func (b *BooksByYearBatchResults) Query(f func(int, []Book, error)) {
defer b.br.Close()
for t := 0; t < b.tot; t++ {
var items []Book
if b.closed {
if f != nil {
f(t, items, ErrBatchAlreadyClosed)
}
continue
}
err := func() error {
rows, err := b.br.Query()
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var i Book
if err := rows.Scan(
&i.BookID,
&i.AuthorID,
&i.Isbn,
&i.BookType,
&i.Title,
&i.Year,
&i.Available,
&i.Tags,
); err != nil {
return err
}
items = append(items, i)
}
return rows.Err()
}()
if f != nil {
f(t, items, err)
}
}
}
func (b *BooksByYearBatchResults) Close() error {
b.closed = true
return b.br.Close()
}
const createBook = `-- name: CreateBook :batchone
INSERT INTO books (
author_id,
isbn,
book_type,
title,
year,
available,
tags
) VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7
)
RETURNING book_id, author_id, isbn, book_type, title, year, available, tags
`
type CreateBookBatchResults struct {
br pgx.BatchResults
tot int
closed bool
}
type CreateBookParams struct {
AuthorID int32 `json:"author_id"`
Isbn string `json:"isbn"`
BookType BookType `json:"book_type"`
Title string `json:"title"`
Year int32 `json:"year"`
Available pgtype.Timestamptz `json:"available"`
Tags []string `json:"tags"`
}
func (q *Queries) CreateBook(ctx context.Context, arg []CreateBookParams) *CreateBookBatchResults {
batch := &pgx.Batch{}
for _, a := range arg {
vals := []any{
a.AuthorID,
a.Isbn,
a.BookType,
a.Title,
a.Year,
a.Available,
a.Tags,
}
batch.Queue(createBook, vals...)
}
br := q.db.SendBatch(ctx, batch)
return &CreateBookBatchResults{br, len(arg), false}
}
func (b *CreateBookBatchResults) QueryRow(f func(int, Book, error)) {
defer b.br.Close()
for t := 0; t < b.tot; t++ {
var i Book
if b.closed {
if f != nil {
f(t, i, ErrBatchAlreadyClosed)
}
continue
}
row := b.br.QueryRow()
err := row.Scan(
&i.BookID,
&i.AuthorID,
&i.Isbn,
&i.BookType,
&i.Title,
&i.Year,
&i.Available,
&i.Tags,
)
if f != nil {
f(t, i, err)
}
}
}
func (b *CreateBookBatchResults) Close() error {
b.closed = true
return b.br.Close()
}
const deleteBook = `-- name: DeleteBook :batchexec
DELETE FROM books
WHERE book_id = $1
`
type DeleteBookBatchResults struct {
br pgx.BatchResults
tot int
closed bool
}
func (q *Queries) DeleteBook(ctx context.Context, bookID []int32) *DeleteBookBatchResults {
batch := &pgx.Batch{}
for _, a := range bookID {
vals := []any{
a,
}
batch.Queue(deleteBook, vals...)
}
br := q.db.SendBatch(ctx, batch)
return &DeleteBookBatchResults{br, len(bookID), false}
}
func (b *DeleteBookBatchResults) Exec(f func(int, error)) {
defer b.br.Close()
for t := 0; t < b.tot; t++ {
if b.closed {
if f != nil {
f(t, ErrBatchAlreadyClosed)
}
continue
}
_, err := b.br.Exec()
if f != nil {
f(t, err)
}
}
}
func (b *DeleteBookBatchResults) Close() error {
b.closed = true
return b.br.Close()
}
const deleteBookNamedFunc = `-- name: DeleteBookNamedFunc :batchexec
DELETE FROM books
WHERE book_id = $1
`
type DeleteBookNamedFuncBatchResults struct {
br pgx.BatchResults
tot int
closed bool
}
func (q *Queries) DeleteBookNamedFunc(ctx context.Context, bookID []int32) *DeleteBookNamedFuncBatchResults {
batch := &pgx.Batch{}
for _, a := range bookID {
vals := []any{
a,
}
batch.Queue(deleteBookNamedFunc, vals...)
}
br := q.db.SendBatch(ctx, batch)
return &DeleteBookNamedFuncBatchResults{br, len(bookID), false}
}
func (b *DeleteBookNamedFuncBatchResults) Exec(f func(int, error)) {
defer b.br.Close()
for t := 0; t < b.tot; t++ {
if b.closed {
if f != nil {
f(t, ErrBatchAlreadyClosed)
}
continue
}
_, err := b.br.Exec()
if f != nil {
f(t, err)
}
}
}
func (b *DeleteBookNamedFuncBatchResults) Close() error {
b.closed = true
return b.br.Close()
}
const deleteBookNamedSign = `-- name: DeleteBookNamedSign :batchexec
DELETE FROM books
WHERE book_id = $1
`
type DeleteBookNamedSignBatchResults struct {
br pgx.BatchResults
tot int
closed bool
}
func (q *Queries) DeleteBookNamedSign(ctx context.Context, bookID []int32) *DeleteBookNamedSignBatchResults {
batch := &pgx.Batch{}
for _, a := range bookID {
vals := []any{
a,
}
batch.Queue(deleteBookNamedSign, vals...)
}
br := q.db.SendBatch(ctx, batch)
return &DeleteBookNamedSignBatchResults{br, len(bookID), false}
}
func (b *DeleteBookNamedSignBatchResults) Exec(f func(int, error)) {
defer b.br.Close()
for t := 0; t < b.tot; t++ {
if b.closed {
if f != nil {
f(t, ErrBatchAlreadyClosed)
}
continue
}
_, err := b.br.Exec()
if f != nil {
f(t, err)
}
}
}
func (b *DeleteBookNamedSignBatchResults) Close() error {
b.closed = true
return b.br.Close()
}
const getBiography = `-- name: GetBiography :batchone
SELECT biography FROM authors
WHERE author_id = $1
`
type GetBiographyBatchResults struct {
br pgx.BatchResults
tot int
closed bool
}
func (q *Queries) GetBiography(ctx context.Context, authorID []int32) *GetBiographyBatchResults {
batch := &pgx.Batch{}
for _, a := range authorID {
vals := []any{
a,
}
batch.Queue(getBiography, vals...)
}
br := q.db.SendBatch(ctx, batch)
return &GetBiographyBatchResults{br, len(authorID), false}
}
func (b *GetBiographyBatchResults) QueryRow(f func(int, []byte, error)) {
defer b.br.Close()
for t := 0; t < b.tot; t++ {
var biography []byte
if b.closed {
if f != nil {
f(t, biography, ErrBatchAlreadyClosed)
}
continue
}
row := b.br.QueryRow()
err := row.Scan(&biography)
if f != nil {
f(t, biography, err)
}
}
}
func (b *GetBiographyBatchResults) Close() error {
b.closed = true
return b.br.Close()
}
const updateBook = `-- name: UpdateBook :batchexec
UPDATE books
SET title = $1, tags = $2
WHERE book_id = $3
`
type UpdateBookBatchResults struct {
br pgx.BatchResults
tot int
closed bool
}
type UpdateBookParams struct {
Title string `json:"title"`
Tags []string `json:"tags"`
BookID int32 `json:"book_id"`
}
func (q *Queries) UpdateBook(ctx context.Context, arg []UpdateBookParams) *UpdateBookBatchResults {
batch := &pgx.Batch{}
for _, a := range arg {
vals := []any{
a.Title,
a.Tags,
a.BookID,
}
batch.Queue(updateBook, vals...)
}
br := q.db.SendBatch(ctx, batch)
return &UpdateBookBatchResults{br, len(arg), false}
}
func (b *UpdateBookBatchResults) Exec(f func(int, error)) {
defer b.br.Close()
for t := 0; t < b.tot; t++ {
if b.closed {
if f != nil {
f(t, ErrBatchAlreadyClosed)
}
continue
}
_, err := b.br.Exec()
if f != nil {
f(t, err)
}
}
}
func (b *UpdateBookBatchResults) Close() error {
b.closed = true
return b.br.Close()
}

View File

@@ -0,0 +1,33 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
package batch
import (
"context"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
)
type DBTX interface {
Exec(context.Context, string, ...any) (pgconn.CommandTag, error)
Query(context.Context, string, ...any) (pgx.Rows, error)
QueryRow(context.Context, string, ...any) pgx.Row
SendBatch(context.Context, *pgx.Batch) pgx.BatchResults
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
func (q *Queries) WithTx(tx pgx.Tx) *Queries {
return &Queries{
db: tx,
}
}

View File

@@ -0,0 +1,153 @@
//go:build examples
package batch
import (
"context"
"testing"
"time"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
"github.com/sqlc-dev/sqlc/internal/sqltest/local"
)
func TestBatchBooks(t *testing.T) {
uri := local.PostgreSQL(t, []string{"schema.sql"})
ctx := context.Background()
db, err := pgx.Connect(ctx, uri)
if err != nil {
t.Fatal(err)
}
defer db.Close(ctx)
dq := New(db)
// create an author
a, err := dq.CreateAuthor(ctx, "Unknown Master")
if err != nil {
t.Fatal(err)
}
now := pgtype.Timestamptz{Time: time.Now(), Valid: true}
// batch insert new books
newBooksParams := []CreateBookParams{
{
AuthorID: a.AuthorID,
Isbn: "1",
Title: "my book title",
BookType: BookTypeFICTION,
Year: 2016,
Available: now,
Tags: []string{},
},
{
AuthorID: a.AuthorID,
Isbn: "2",
Title: "the second book",
BookType: BookTypeFICTION,
Year: 2016,
Available: now,
Tags: []string{"cool", "unique"},
},
{
AuthorID: a.AuthorID,
Isbn: "3",
Title: "the third book",
BookType: BookTypeFICTION,
Year: 2001,
Available: now,
Tags: []string{"cool"},
},
{
AuthorID: a.AuthorID,
Isbn: "4",
Title: "4th place finisher",
BookType: BookTypeNONFICTION,
Year: 2011,
Available: now,
Tags: []string{"other"},
},
}
newBooks := make([]Book, len(newBooksParams))
var cnt int
dq.CreateBook(ctx, newBooksParams).QueryRow(func(i int, b Book, err error) {
if err != nil {
t.Fatalf("failed inserting book (%s): %s", b.Title, err)
}
newBooks[i] = b
cnt = i
})
// first i was 0, so add 1
cnt++
numBooksExpected := len(newBooks)
if cnt != numBooksExpected {
t.Fatalf("expected to insert %d books; got %d", numBooksExpected, cnt)
}
// batch update the title and tags
updateBooksParams := []UpdateBookParams{
{
BookID: newBooks[1].BookID,
Title: "changed second title",
Tags: []string{"cool", "disastor"},
},
}
dq.UpdateBook(ctx, updateBooksParams).Exec(func(i int, err error) {
if err != nil {
t.Fatalf("error updating book %d: %s", updateBooksParams[i].BookID, err)
}
})
// batch many to retrieve books by year
selectBooksByTitleYearParams := []int32{2001, 2016}
var books0 []Book
dq.BooksByYear(ctx, selectBooksByTitleYearParams).Query(func(i int, books []Book, err error) {
if err != nil {
t.Fatal(err)
}
t.Logf("num books for %d: %d", selectBooksByTitleYearParams[i], len(books))
books0 = append(books0, books...)
})
for _, book := range books0 {
t.Logf("Book %d (%s): %s available: %s\n", book.BookID, book.BookType, book.Title, book.Available.Time.Format(time.RFC822Z))
author, err := dq.GetAuthor(ctx, book.AuthorID)
if err != nil {
t.Fatal(err)
}
t.Logf("Book %d author: %s\n", book.BookID, author.Name)
}
// batch delete books
deleteBooksParams := make([]int32, len(newBooks))
for i, book := range newBooks {
deleteBooksParams[i] = book.BookID
}
batchDelete := dq.DeleteBook(ctx, deleteBooksParams)
numDeletesProcessed := 0
wantNumDeletesProcessed := 2
batchDelete.Exec(func(i int, err error) {
if err != nil && err.Error() != "batch already closed" {
t.Fatalf("error deleting book %d: %s", deleteBooksParams[i], err)
}
if err == nil {
numDeletesProcessed++
}
if i == wantNumDeletesProcessed-1 {
// close batch operation before processing all errors from delete operation
if err := batchDelete.Close(); err != nil {
t.Fatalf("failed to close batch operation: %s", err)
}
}
})
if numDeletesProcessed != wantNumDeletesProcessed {
t.Fatalf("expected Close to short-circuit record processing (expected %d; got %d)", wantNumDeletesProcessed, numDeletesProcessed)
}
}

View File

@@ -0,0 +1,71 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
package batch
import (
"database/sql/driver"
"fmt"
"github.com/jackc/pgx/v5/pgtype"
)
type BookType string
const (
BookTypeFICTION BookType = "FICTION"
BookTypeNONFICTION BookType = "NONFICTION"
)
func (e *BookType) Scan(src any) error {
switch s := src.(type) {
case []byte:
*e = BookType(s)
case string:
*e = BookType(s)
default:
return fmt.Errorf("unsupported scan type for BookType: %T", src)
}
return nil
}
type NullBookType struct {
BookType BookType `json:"book_type"`
Valid bool `json:"valid"` // Valid is true if BookType is not NULL
}
// Scan implements the Scanner interface.
func (ns *NullBookType) Scan(value any) error {
if value == nil {
ns.BookType, ns.Valid = "", false
return nil
}
ns.Valid = true
return ns.BookType.Scan(value)
}
// Value implements the driver Valuer interface.
func (ns NullBookType) Value() (driver.Value, error) {
if !ns.Valid {
return nil, nil
}
return string(ns.BookType), nil
}
type Author struct {
AuthorID int32 `json:"author_id"`
Name string `json:"name"`
Biography []byte `json:"biography"`
}
type Book struct {
BookID int32 `json:"book_id"`
AuthorID int32 `json:"author_id"`
Isbn string `json:"isbn"`
BookType BookType `json:"book_type"`
Title string `json:"title"`
Year int32 `json:"year"`
Available pgtype.Timestamptz `json:"available"`
Tags []string `json:"tags"`
}

View File

@@ -0,0 +1,26 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
package batch
import (
"context"
"github.com/jackc/pgx/v5/pgconn"
)
type Querier interface {
BooksByYear(ctx context.Context, year []int32) *BooksByYearBatchResults
CreateAuthor(ctx context.Context, name string) (Author, error)
CreateBook(ctx context.Context, arg []CreateBookParams) *CreateBookBatchResults
DeleteBook(ctx context.Context, bookID []int32) *DeleteBookBatchResults
DeleteBookExecResult(ctx context.Context, bookID int32) (pgconn.CommandTag, error)
DeleteBookNamedFunc(ctx context.Context, bookID []int32) *DeleteBookNamedFuncBatchResults
DeleteBookNamedSign(ctx context.Context, bookID []int32) *DeleteBookNamedSignBatchResults
GetAuthor(ctx context.Context, authorID int32) (Author, error)
GetBiography(ctx context.Context, authorID []int32) *GetBiographyBatchResults
UpdateBook(ctx context.Context, arg []UpdateBookParams) *UpdateBookBatchResults
}
var _ Querier = (*Queries)(nil)

View File

@@ -0,0 +1,56 @@
-- name: GetAuthor :one
SELECT * FROM authors
WHERE author_id = $1;
-- name: DeleteBookExecResult :execresult
DELETE FROM books
WHERE book_id = $1;
-- name: DeleteBook :batchexec
DELETE FROM books
WHERE book_id = $1;
-- name: DeleteBookNamedFunc :batchexec
DELETE FROM books
WHERE book_id = sqlc.arg (book_id);
-- name: DeleteBookNamedSign :batchexec
DELETE FROM books
WHERE book_id = @book_id;
-- name: BooksByYear :batchmany
SELECT * FROM books
WHERE year = $1;
-- name: CreateAuthor :one
INSERT INTO authors (name) VALUES ($1)
RETURNING *;
-- name: CreateBook :batchone
INSERT INTO books (
author_id,
isbn,
book_type,
title,
year,
available,
tags
) VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7
)
RETURNING *;
-- name: UpdateBook :batchexec
UPDATE books
SET title = $1, tags = $2
WHERE book_id = $3;
-- name: GetBiography :batchone
SELECT biography FROM authors
WHERE author_id = $1;

View File

@@ -0,0 +1,45 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: query.sql
package batch
import (
"context"
"github.com/jackc/pgx/v5/pgconn"
)
const createAuthor = `-- name: CreateAuthor :one
INSERT INTO authors (name) VALUES ($1)
RETURNING author_id, name, biography
`
func (q *Queries) CreateAuthor(ctx context.Context, name string) (Author, error) {
row := q.db.QueryRow(ctx, createAuthor, name)
var i Author
err := row.Scan(&i.AuthorID, &i.Name, &i.Biography)
return i, err
}
const deleteBookExecResult = `-- name: DeleteBookExecResult :execresult
DELETE FROM books
WHERE book_id = $1
`
func (q *Queries) DeleteBookExecResult(ctx context.Context, bookID int32) (pgconn.CommandTag, error) {
return q.db.Exec(ctx, deleteBookExecResult, bookID)
}
const getAuthor = `-- name: GetAuthor :one
SELECT author_id, name, biography FROM authors
WHERE author_id = $1
`
func (q *Queries) GetAuthor(ctx context.Context, authorID int32) (Author, error) {
row := q.db.QueryRow(ctx, getAuthor, authorID)
var i Author
err := row.Scan(&i.AuthorID, &i.Name, &i.Biography)
return i, err
}

View File

@@ -0,0 +1,21 @@
CREATE TABLE authors (
author_id SERIAL PRIMARY KEY,
name text NOT NULL DEFAULT '',
biography JSONB
);
CREATE TYPE book_type AS ENUM (
'FICTION',
'NONFICTION'
);
CREATE TABLE books (
book_id SERIAL PRIMARY KEY,
author_id integer NOT NULL REFERENCES authors(author_id),
isbn text NOT NULL DEFAULT '' UNIQUE,
book_type book_type NOT NULL DEFAULT 'FICTION',
title text NOT NULL DEFAULT '',
year integer NOT NULL DEFAULT 2000,
available timestamp with time zone NOT NULL DEFAULT 'NOW()',
tags varchar[] NOT NULL DEFAULT '{}'
);

Some files were not shown because too many files have changed in this diff Show More