sqlc - Generate Type-Safe Code from SQL

Introduction⌗
sqlc is an open-source code generator developed in Go that generates CRUD code for Go projects based on DDL. Before getting started, you need to write Schema and Query SQL.
If you have an existing database and want to quickly generate a Schema, you can use atlas to export your Schema:
atlas schema inspect -u "mysql://root:pass@localhost:3306/example" --format '{{ sql . }}' > schema.sql
Installation⌗
Installation method on macOS:
brew install sqlc
For other platforms, please refer to the official documentation.
Configuration⌗
First, you need to create a sqlc.yaml
for your project:
version: "2"
sql:
- engine: "mysql"
queries: "query.sql"
schema: "schema.sql"
gen:
go:
out: "tutorial" # Directory for generated code
package: "tutorial" # Package name in Go, can be customized as needed
File extensions can be
yaml
,yml
, orjson
.
Define Schema⌗
Create a schema.sql
file with the following definition (or use atlas to export an existing database structure):
-- Create "users" table
CREATE TABLE `users` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`email` varchar(255) NOT NULL,
`password` varchar(255) NULL,
`created_at` timestamp NULL,
`updated_at` timestamp NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `users_email_unique` (`email`)
) CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;
Define Query⌗
Create a query.sql
file and define the related DML logic:
-- name: GetUser :one
SELECT * FROM users
WHERE id = ? LIMIT 1;
-- name: ListUsers :many
SELECT * FROM users
ORDER BY id DESC;
-- name: CreateUser :execresult
INSERT INTO users (
name, email, password
) VALUES (
?, ?, ?
);
-- name: DeleteUser :exec
DELETE FROM users
WHERE id = ?;
Note that
-- name: {NAME} :{}
is used to declare the method name and return type.
Generate Code⌗
sqlc generate
After generation, the directory structure is as follows:
.
├── query.sql
├── schema.sql
├── sqlc.yaml
└── tutorial
├── db.go
├── models.go
└── query.sql.go
2 directories, 6 files
Contents of the db.go
file:
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.26.0
package tutorial
import (
"context"
"database/sql"
)
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,
}
}
Code in the models.go
file:
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.26.0
package tutorial
import (
"database/sql"
)
type User struct {
ID uint64
Name string
Email string
Password sql.NullString
CreatedAt sql.NullTime
UpdatedAt sql.NullTime
}
Code in the query.sql.go
file:
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.26.0
// source: query.sql
package tutorial
import (
"context"
"database/sql"
)
const createUser = `-- name: CreateUser :execresult
INSERT INTO users (
name, email, password
) VALUES (
?, ?, ?
)
`
type CreateUserParams struct {
Name string
Email string
Password sql.NullString
}
func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (sql.Result, error) {
return q.db.ExecContext(ctx, createUser, arg.Name, arg.Email, arg.Password)
}
const deleteUser = `-- name: DeleteUser :exec
DELETE FROM users
WHERE id = ?
`
func (q *Queries) DeleteUser(ctx context.Context, id uint64) error {
_, err := q.db.ExecContext(ctx, deleteUser, id)
return err
}
const getUser = `-- name: GetUser :one
SELECT id, name, email, password, created_at, updated_at FROM users
WHERE id = ? LIMIT 1
`
func (q *Queries) GetUser(ctx context.Context, id uint64) (User, error) {
row := q.db.QueryRowContext(ctx, getUser, id)
var i User
err := row.Scan(
&i.ID,
&i.Name,
&i.Email,
&i.Password,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const listUsers = `-- name: ListUsers :many
SELECT id, name, email, password, created_at, updated_at FROM users
ORDER BY id DESC
`
func (q *Queries) ListUsers(ctx context.Context) ([]User, error) {
rows, err := q.db.QueryContext(ctx, listUsers)
if err != nil {
return nil, err
}
defer rows.Close()
var items []User
for rows.Next() {
var i User
if err := rows.Scan(
&i.ID,
&i.Name,
&i.Email,
&i.Password,
&i.CreatedAt,
&i.UpdatedAt,
); 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
}
Summary⌗
sqlc currently only supports code generation for Go projects, although it also provides Kotlin, Python, and TypeScript, but these are currently in Beta stage. However, sqlc provides the capability for plugins in other languages, and it can be foreseen that the ecosystem will gradually improve in the future.
Additionally, atlas is also a good DDL management tool.
I hope this is helpful, Happy hacking…