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
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:
88
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
Normal file
88
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
Normal 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
|
||||
30
.github/ISSUE_TEMPLATE/FEATURE_REQUEST.yml
vendored
Normal file
30
.github/ISSUE_TEMPLATE/FEATURE_REQUEST.yml
vendored
Normal 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
20
.github/dependabot.yml
vendored
Normal 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
18
.github/workflows/buf.yml
vendored
Normal 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
19
.github/workflows/build.yml
vendored
Normal 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
31
.github/workflows/ci-kotlin.yml
vendored
Normal 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
30
.github/workflows/ci-python.yml
vendored
Normal 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
30
.github/workflows/ci-typescript.yml
vendored
Normal 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
74
.github/workflows/ci.yml
vendored
Normal 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
192
.github/workflows/gen.yml
vendored
Normal 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
11
.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
/.idea/
|
||||
__pycache__
|
||||
.DS_Store
|
||||
.*.swp
|
||||
|
||||
# Devenv
|
||||
.envrc
|
||||
.direnv
|
||||
.devenv*
|
||||
devenv.local.nix
|
||||
|
||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"files.insertFinalNewline": true
|
||||
}
|
||||
340
CLAUDE.md
Normal file
340
CLAUDE.md
Normal 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
19
Dockerfile
Normal 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
21
LICENSE
Normal 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
60
Makefile
Normal 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
48
NOTICE
Normal 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
59
README.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# sqlc: A SQL Compiler
|
||||
|
||||

|
||||
[](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
12
buf.gen.yaml
Normal 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
|
||||
3
buf.work.yaml
Normal file
3
buf.work.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
version: v1
|
||||
directories:
|
||||
- protos
|
||||
56
cliff.toml
Normal file
56
cliff.toml
Normal 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
47
cmd/sqlc-gen-json/main.go
Normal 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
717
cmd/sqlc-test-setup/main.go
Normal 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
11
cmd/sqlc/main.go
Normal 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
100
devenv.lock
Normal 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
17
devenv.nix
Normal 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
3
devenv.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
inputs:
|
||||
nixpkgs:
|
||||
url: github:NixOS/nixpkgs/nixpkgs-unstable
|
||||
39
docker-compose.yml
Normal file
39
docker-compose.yml
Normal 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
2
docs/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
_venv
|
||||
_build
|
||||
61
docs/guides/development.md
Normal file
61
docs/guides/development.md
Normal 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
|
||||
```
|
||||
71
docs/guides/migrating-off-hosted-managed-databases.md
Normal file
71
docs/guides/migrating-off-hosted-managed-databases.md
Normal 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.
|
||||
119
docs/guides/migrating-to-sqlc-gen-kotlin.md
Normal file
119
docs/guides/migrating-to-sqlc-gen-kotlin.md
Normal 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.
|
||||
131
docs/guides/migrating-to-sqlc-gen-python.md
Normal file
131
docs/guides/migrating-to-sqlc-gen-python.md
Normal 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
104
docs/guides/plugins.md
Normal 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
55
docs/guides/privacy.md
Normal 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)
|
||||
125
docs/guides/using-go-and-pgx.md
Normal file
125
docs/guides/using-go-and-pgx.md
Normal 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
129
docs/howto/analyze.md
Normal 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
159
docs/howto/ci-cd.md
Normal 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
232
docs/howto/ddl.md
Normal 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
52
docs/howto/delete.md
Normal 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
61
docs/howto/embedding.md
Normal 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
74
docs/howto/fmt.md
Normal 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
76
docs/howto/generate.md
Normal 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
238
docs/howto/insert.md
Normal 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"
|
||||
```
|
||||
101
docs/howto/managed-databases.md
Normal file
101
docs/howto/managed-databases.md
Normal 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
|
||||
```
|
||||
93
docs/howto/named_parameters.md
Normal file
93
docs/howto/named_parameters.md
Normal 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
184
docs/howto/overrides.md
Normal 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
111
docs/howto/parse.md
Normal 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.
|
||||
89
docs/howto/prepared_query.md
Normal file
89
docs/howto/prepared_query.md
Normal 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
70
docs/howto/push.md
Normal 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
83
docs/howto/query_count.md
Normal 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
122
docs/howto/rename.md
Normal 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
393
docs/howto/select.md
Normal 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
53
docs/howto/structs.md
Normal 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
101
docs/howto/transactions.md
Normal 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
115
docs/howto/update.md
Normal 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
105
docs/howto/verify.md
Normal 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
286
docs/howto/vet.md
Normal 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
22
docs/index.md
Normal 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
51
docs/overview/install.md
Normal 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
2936
docs/reference/changelog.md
Normal file
File diff suppressed because it is too large
Load Diff
29
docs/reference/cli.md
Normal file
29
docs/reference/cli.md
Normal 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
501
docs/reference/config.md
Normal 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
388
docs/reference/datatypes.md
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
190
docs/reference/environment-variables.md
Normal file
190
docs/reference/environment-variables.md
Normal 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).
|
||||
34
docs/reference/language-support.md
Normal file
34
docs/reference/language-support.md
Normal 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
119
docs/reference/macros.md
Normal 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).
|
||||
230
docs/reference/query-annotations.md
Normal file
230
docs/reference/query-annotations.md
Normal 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
72
docs/toc.yaml
Normal 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
|
||||
221
docs/tutorials/getting-started-mysql.md
Normal file
221
docs/tutorials/getting-started-mysql.md
Normal 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
|
||||
```
|
||||
248
docs/tutorials/getting-started-postgresql.md
Normal file
248
docs/tutorials/getting-started-postgresql.md
Normal 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
|
||||
```
|
||||
201
docs/tutorials/getting-started-sqlite.md
Normal file
201
docs/tutorials/getting-started-sqlite.md
Normal 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.
|
||||
31
examples/authors/mysql/db.go
Normal file
31
examples/authors/mysql/db.go
Normal 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,
|
||||
}
|
||||
}
|
||||
53
examples/authors/mysql/db_test.go
Normal file
53
examples/authors/mysql/db_test.go
Normal 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)
|
||||
}
|
||||
15
examples/authors/mysql/models.go
Normal file
15
examples/authors/mysql/models.go
Normal 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
|
||||
}
|
||||
18
examples/authors/mysql/query.sql
Normal file
18
examples/authors/mysql/query.sql
Normal 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 = ?;
|
||||
78
examples/authors/mysql/query.sql.go
Normal file
78
examples/authors/mysql/query.sql.go
Normal 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
|
||||
}
|
||||
5
examples/authors/mysql/schema.sql
Normal file
5
examples/authors/mysql/schema.sql
Normal file
@@ -0,0 +1,5 @@
|
||||
CREATE TABLE authors (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
name text NOT NULL,
|
||||
bio text
|
||||
);
|
||||
32
examples/authors/postgresql/db.go
Normal file
32
examples/authors/postgresql/db.go
Normal 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,
|
||||
}
|
||||
}
|
||||
49
examples/authors/postgresql/db_test.go
Normal file
49
examples/authors/postgresql/db_test.go
Normal 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)
|
||||
}
|
||||
15
examples/authors/postgresql/models.go
Normal file
15
examples/authors/postgresql/models.go
Normal 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
|
||||
}
|
||||
19
examples/authors/postgresql/query.sql
Normal file
19
examples/authors/postgresql/query.sql
Normal 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;
|
||||
80
examples/authors/postgresql/query.sql.go
Normal file
80
examples/authors/postgresql/query.sql.go
Normal 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
|
||||
}
|
||||
5
examples/authors/postgresql/schema.sql
Normal file
5
examples/authors/postgresql/schema.sql
Normal file
@@ -0,0 +1,5 @@
|
||||
CREATE TABLE authors (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name text NOT NULL,
|
||||
bio text
|
||||
);
|
||||
52
examples/authors/sqlc.yaml
Normal file
52
examples/authors/sqlc.yaml
Normal 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"
|
||||
31
examples/authors/sqlite/db.go
Normal file
31
examples/authors/sqlite/db.go
Normal 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,
|
||||
}
|
||||
}
|
||||
48
examples/authors/sqlite/db_test.go
Normal file
48
examples/authors/sqlite/db_test.go
Normal 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)
|
||||
}
|
||||
15
examples/authors/sqlite/models.go
Normal file
15
examples/authors/sqlite/models.go
Normal 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
|
||||
}
|
||||
18
examples/authors/sqlite/query.sql
Normal file
18
examples/authors/sqlite/query.sql
Normal 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 = ?;
|
||||
78
examples/authors/sqlite/query.sql.go
Normal file
78
examples/authors/sqlite/query.sql.go
Normal 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
|
||||
}
|
||||
5
examples/authors/sqlite/schema.sql
Normal file
5
examples/authors/sqlite/schema.sql
Normal file
@@ -0,0 +1,5 @@
|
||||
CREATE TABLE authors (
|
||||
id integer PRIMARY KEY AUTOINCREMENT,
|
||||
name text NOT NULL,
|
||||
bio text
|
||||
);
|
||||
404
examples/batch/postgresql/batch.go
Normal file
404
examples/batch/postgresql/batch.go
Normal 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()
|
||||
}
|
||||
33
examples/batch/postgresql/db.go
Normal file
33
examples/batch/postgresql/db.go
Normal 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,
|
||||
}
|
||||
}
|
||||
153
examples/batch/postgresql/db_test.go
Normal file
153
examples/batch/postgresql/db_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
71
examples/batch/postgresql/models.go
Normal file
71
examples/batch/postgresql/models.go
Normal 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"`
|
||||
}
|
||||
26
examples/batch/postgresql/querier.go
Normal file
26
examples/batch/postgresql/querier.go
Normal 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)
|
||||
56
examples/batch/postgresql/query.sql
Normal file
56
examples/batch/postgresql/query.sql
Normal 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;
|
||||
45
examples/batch/postgresql/query.sql.go
Normal file
45
examples/batch/postgresql/query.sql.go
Normal 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
|
||||
}
|
||||
21
examples/batch/postgresql/schema.sql
Normal file
21
examples/batch/postgresql/schema.sql
Normal 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
Reference in New Issue
Block a user