kyleconroy / sqlc
- воскресенье, 15 декабря 2019 г. в 00:30:30
Go
Generate type safe Go from SQL
🚨 sqlc is new and under rapid development.
The code it generates is correct and safe for production use, but there is currently no guarantee of stability or backwards-compatibility of the command line interface, configuration file format or generated code.
🚨
And lo, the Great One looked down upon the people and proclaimed:
"SQL is actually pretty great"
sqlc generates fully-type safe idiomatic Go code from SQL. Here's how it works:
Seriously, it's that easy. You don't have to write any boilerplate SQL querying code ever again.
But sqlc doesn't just make you more productive by generating boilerplate for you. sqlc also prevents entire classes of common errors in SQL code. Have you ever:
All of these errors are impossible with sqlc. Wait, what? How?
sqlc parses your all of your queries and the DDL (e.g. CREATE TABLE)
statements during the code generation processes so that it knows the names and
types of every column in your tables and every expression in your queries. If
any of them do not match, sqlc will fail to compile your queries, preventing
entire classes of runtime problems at compile time.
Likewise, the methods that sqlc generates for you have a strict arity and correct Go type definitions that match your columns. So if you change a query's arguments or a column's type but don't update your code, it will fail to compile.
Okay, enough hype, let's see it in action.
First you pass the following SQL to sqlc generate:
CREATE TABLE authors (
id BIGSERIAL PRIMARY KEY,
name text NOT NULL,
bio text
);
-- name: GetAuthor :one
SELECT * FROM authors
WHERE id = $1 LIMIT 1;
-- name: ListAuthors :many
SELECT * FROM authors
ORDER BY name;
-- name: CreateAuthor :one
INSERT INTO authors (
name, bio
) VALUES (
$1, $2
)
RETURNING *;
-- name: DeleteAuthor :exec
DELETE FROM authors
WHERE id = $1;And then in your application code you'd write:
// list all authors
authors, err := db.ListAuthors(ctx)
if err != nil {
return err
}
fmt.Println(authors)
// create an author
insertedAuthor, err := db.CreateAuthor(ctx, &db.CreateAuthorParams{
Name: "Brian Kernighan",
Bio: "Co-author of The C Programming Language and The Go Programming Language",
})
if err != nil {
return err
}
fmt.Println(insertedAuthor)
// get the author we just inserted
fetchedAuthor, err = db.GetAuthor(ctx, author.ID)
if err != nil {
return err
}
// prints true
fmt.Println(reflect.DeepEqual(insertedAuthor, fetchedAuthor))To make that possible, sqlc generates readable, idiomatic Go code that you otherwise would have had to write yourself. Take a look:
package db
import (
"context"
"database/sql"
)
type Author struct {
ID int64
Name string
Bio sql.NullString
}
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 deleteAuthor = `-- name: DeleteAuthor :exec
DELETE FROM authors
WHERE id = $1
`
func (q *Queries) DeleteAuthor(ctx context.Context, id int64) error {
_, err := q.db.ExecContext(ctx, deleteAuthor, id)
return err
}
const getAuthor = `-- name: GetAuthor :one
SELECT id, name, bio FROM authors
WHERE id = $1 LIMIT 1
`
func (q *Queries) GetAuthor(ctx context.Context, id int64) (Author, error) {
row := q.db.QueryRowContext(ctx, getAuthor, id)
var i Author
err := row.Scan(&i.ID, &i.Name, &i.Bio)
return i, err
}
const listAuthors = `-- name: ListAuthors :many
SELECT id, name, bio FROM authors
ORDER BY name
`
func (q *Queries) ListAuthors(ctx context.Context) ([]Author, error) {
rows, err := q.db.QueryContext(ctx, listAuthors)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Author
for rows.Next() {
var i Author
if err := rows.Scan(&i.ID, &i.Name, &i.Bio); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
type DBTX interface {
ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
PrepareContext(context.Context, string) (*sql.Stmt, error)
QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
QueryRowContext(context.Context, string, ...interface{}) *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,
}
}Your favorite PostgreSQL / Go features are supported:
A full, end-to-end example can be found in the sample
ondeck package.
Usage:
sqlc [command]
Available Commands:
compile Statically check SQL for syntax and type errors
generate Generate Go code from SQL
help Help about any command
init Create an empty sqlc.json settings file
version Print the sqlc version number
Flags:
-h, --help help for sqlc
Use "sqlc [command] --help" for more information about a command.
The sqlc tool is configured via a sqlc.json file. This file must be
in the directory where the sqlc command is run.
{
"version": "1",
"packages": [
{
"name": "db",
"emit_json_tags": true,
"emit_prepared_queries": false,
"path": "internal/db",
"queries": "./sql/query/",
"schema": "./sql/schema/"
}
]
}Each package document has the following keys:
name:
path basenameemit_json_tags:
false.emit_prepared_queries:
false.path:
queries:
schema:
The default mapping of PostgreSQL types to Go types only uses packages outside the standard library when it must.
For example, the uuid PostgreSQL type is mapped to github.com/google/uuid.
If a different Go package for UUIDs is required, specify the package in the
overrides array. In this case, I'm going to use the github.com/gofrs/uuid
instead.
{
"version": "1",
"packages": [...],
"overrides": [
{
"go_type": "github.com/gofrs/uuid.UUID",
"postgres_type": "uuid"
}
]
}
Each override document has the following keys:
postgres_type:
go_type:
null:
false.Sometimes you would like to override the Go type used in model or query generation for a specific field of a table and not on a type basis as described in the previous section.
This may be configured by specifying the column property in the override definition. column
should be of the form table.column buy you may be even more specify by specifying schema.table.column
or catalog.schema.table.column.
{
"version": "1",
"packages": [...],
"overrides": [
{
"column": "authors.id",
"go_type": "github.com/segmentio/ksuid.KSUID"
}
]
}
Overrides can be configured globally, as demonstrated in the previous sections, or they can be configured on a per-package which scopes the override behavior to just a single package:
{
"version": "1",
"packages": [
{
...
"overrides": [...]
}
],
}
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 dictionary
to pick a new name. The keys are column names and the values are the struct
field name to use.
{
"version": "1",
"packages": [...],
"rename": {
"spotify_url": "SpotifyURL"
}
}Each commit is deployed to the devel channel on Equinox:
sqlc currently only supports PostgreSQL. If you'd like to support another database, we'd welcome a contribution.
sqlc currently only generates Go code, but if you'd like to build another language backend, we'd welcome a contribution.