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:
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;
|
||||
```
|
||||
Reference in New Issue
Block a user