sqlc requires each query to have a small comment indicating the name and command. The format of this comment is as follows:
-- name: <name> <command>The generated method will return the error from ExecContext.
-- 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
}The generated method will return the sql.Result returned by ExecContext.
-- name: DeleteAllAuthors :execresult
DELETE FROM authors;func (q *Queries) DeleteAllAuthors(ctx context.Context) (sql.Result, error) {
return q.db.ExecContext(ctx, deleteAllAuthors)
}The generated method will return the number of affected rows from the result returned by ExecContext.
-- name: DeleteAllAuthors :execrows
DELETE FROM authors;func (q *Queries) DeleteAllAuthors(ctx context.Context) (int64, error) {
_, err := q.db.ExecContext(ctx, deleteAllAuthors)
// ...
}The generated method will return the number generated by the database from the result returned by ExecContext.
-- name: InsertAuthor :execlastid
INSERT INTO authors (name) VALUES (?);func (q *Queries) InsertAuthor(ctx context.Context, name string) (int64, error) {
_, err := q.db.ExecContext(ctx, insertAuthor, name)
// ...
}The generated method will return a slice of records via QueryContext.
-- name: ListAuthors :many
SELECT * FROM authors
ORDER BY name;func (q *Queries) ListAuthors(ctx context.Context) ([]Author, error) {
rows, err := q.db.QueryContext(ctx, listAuthors)
// ...
}The generated method will return a single record via QueryRowContext.
-- name: GetAuthor :one
SELECT * 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)
// ...
}The sqlc.optional annotation allows for parts of a SQL query to be conditionally included at runtime. This is useful for building queries with optional filters or other dynamic components.
sqlc.optional provides a way to construct dynamic SQL queries where certain SQL fragments are only appended to the base query if a corresponding Go parameter is non-nil. This avoids the need for complex string manipulation or multiple similar queries for different filtering scenarios.
You include sqlc.optional calls directly in your SQL query comments, after the main query body. Each call specifies a key (which becomes part of the Go function parameter name) and the SQL fragment to include.
-- name: GetItemsByOwner :many
SELECT * FROM items
WHERE owner_id = $1 -- Base condition for mandatory parameter
sqlc.optional('NameFilter', 'AND name LIKE $2')
sqlc.optional('ActiveOnly', 'AND is_active = $3');For each sqlc.optional('Key', 'SQLFragment') annotation, a new parameter is added to the generated Go function. The parameter name is derived from Key (converted to lowerCamelCase, e.g., nameFilter, activeOnly), and its type is interface{}.
Given the SQL example above, the generated Go function signature would be:
func (q *Queries) GetItemsByOwner(ctx context.Context, ownerID int64, nameFilter interface{}, activeOnly interface{}) ([]Item, error)Here, ownerID int64 is the standard parameter corresponding to $1. nameFilter interface{} and activeOnly interface{} are the optional parameters generated due to sqlc.optional.
- The SQL fragment associated with an
sqlc.optionaldirective is appended to the main query (with a preceding space) if the corresponding Go parameter in the generated function is notnil. - If the parameter is
nil, the fragment is ignored. - The database driver receives the fully constructed SQL string and only the parameters that are active (standard parameters + non-
niloptional parameters).
The $N placeholders in any SQL fragment (whether part of the base query or an sqlc.optional fragment) must correspond to the position of the argument in the generated Go function's parameter list.
- Standard (non-optional) parameters are numbered first, based on their order in the SQL query.
- Optional parameters are numbered subsequently, based on the order of their
sqlc.optionalappearance in the SQL query.
Example:
For the query:
-- name: GetItemsByOwner :many
SELECT * FROM items
WHERE owner_id = $1 -- owner_id is the 1st parameter
sqlc.optional('NameFilter', 'AND name LIKE $2') -- nameFilter is the 2nd parameter
sqlc.optional('ActiveOnly', 'AND is_active = $3'); -- activeOnly is the 3rd parameterThe generated Go function is:
func (q *Queries) GetItemsByOwner(ctx context.Context, ownerID int64, nameFilter interface{}, activeOnly interface{})
- In the base query,
$1refers toownerID. - In the
NameFilterfragment,$2refers tonameFilter. - In the
ActiveOnlyfragment,$3refers toactiveOnly.
If nameFilter is nil and activeOnly is provided, the final SQL sent to the driver might look like:
SELECT * FROM items WHERE owner_id = $1 AND is_active = $2
And the parameters passed to the driver would be ownerID and the value of activeOnly. The database driver sees a query with parameters re-numbered sequentially from $1. sqlc handles this re-numbering automatically when constructing the query for the driver.
SQL (query.sql):
-- name: ListUsers :many
SELECT id, name, status FROM users
WHERE 1=1 -- Base condition (can be any valid SQL expression)
sqlc.optional('NameParam', 'AND name LIKE $1')
sqlc.optional('StatusParam', 'AND status = $2');(For this specific example, if NameParam is active, it's $1. If StatusParam is active, it's $2. If both are active, NameParam is $1 and StatusParam is $2 in their respective fragments, but they become $1 and $2 overall if no mandatory params precede them. The parameter numbering in fragments refers to their final position in the argument list passed to the database driver, which sqlc constructs based on active parameters.)
Correction to the above parenthetical note, aligning with the "Parameter Numbering" section:
The $N in the SQL fragments refers to the Go function signature's parameter order.
NameParam(if not nil) corresponds to$1.StatusParam(if not nil) corresponds to$2.
If NameParam is John% and StatusParam is active, the effective SQL is:
SELECT id, name, status FROM users WHERE 1=1 AND name LIKE $1 AND status = $2
And the parameters passed to the driver are John% and active.
If NameParam is nil and StatusParam is active, the effective SQL is:
SELECT id, name, status FROM users WHERE 1=1 AND status = $1
And the parameter passed to the driver is active. sqlc handles mapping the Go parameters to the correct positional placeholders for the final SQL.
Generated Go (query.sql.go):
func (q *Queries) ListUsers(ctx context.Context, nameParam interface{}, statusParam interface{}) ([]User, error) {
// ... implementation using strings.Builder ...
}Example Usage (Go):
package main
import (
"context"
"database/sql"
"fmt"
"log"
// assume models and queries are in package "db"
"yourmodule/db" // Adjust to your actual module path
)
func main() {
ctx := context.Background()
// Assume dbConn is an initialized *sql.DB
var dbConn *sql.DB
// dbConn, err := sql.Open("driver-name", "connection-string")
// if err != nil {
// log.Fatal(err)
// }
// defer dbConn.Close()
queries := db.New(dbConn)
// Example 1: Get all users (both optional parameters are nil)
fmt.Println("Fetching all users...")
allUsers, err := queries.ListUsers(ctx, nil, nil)
if err != nil {
log.Fatalf("Failed to list all users: %v", err)
}
for _, user := range allUsers {
fmt.Printf("User: ID=%d, Name=%s, Status=%s\n", user.ID, user.Name, user.Status)
}
fmt.Println("\nFetching users with name starting with 'J':")
// Example 2: Get users with name starting with "J"
nameFilter := "J%"
jUsers, err := queries.ListUsers(ctx, nameFilter, nil)
if err != nil {
log.Fatalf("Failed to list J-users: %v", err)
}
for _, user := range jUsers {
fmt.Printf("User: ID=%d, Name=%s, Status=%s\n", user.ID, user.Name, user.Status)
}
fmt.Println("\nFetching 'active' users:")
// Example 3: Get 'active' users
statusFilter := "active"
activeUsers, err := queries.ListUsers(ctx, nil, statusFilter)
if err != nil {
log.Fatalf("Failed to list active users: %v", err)
}
for _, user := range activeUsers {
fmt.Printf("User: ID=%d, Name=%s, Status=%s\n", user.ID, user.Name, user.Status)
}
fmt.Println("\nFetching 'inactive' users with name 'Jane Doe':")
// Example 4: Get 'inactive' users with name 'Jane Doe'
nameFilterSpecific := "Jane Doe"
statusFilterSpecific := "inactive"
janeUsers, err := queries.ListUsers(ctx, nameFilterSpecific, statusFilterSpecific)
if err != nil {
log.Fatalf("Failed to list specific Jane users: %v", err)
}
for _, user := range janeUsers {
fmt.Printf("User: ID=%d, Name=%s, Status=%s\n", user.ID, user.Name, user.Status)
}
}This feature provides a powerful way to reduce boilerplate and manage complex queries with multiple optional conditions directly within your SQL files.
NOTE: This command only works with PostgreSQL using the pgx/v4 and pgx/v5 drivers and outputting Go code.
The generated method will return a batch object. The batch object will have the following methods:
Exec, that takes afunc(int, error)parameter,Close, to close the batch operation early.
-- name: DeleteBook :batchexec
DELETE FROM books
WHERE book_id = $1;type DeleteBookBatchResults struct {
br pgx.BatchResults
ind int
}
func (q *Queries) DeleteBook(ctx context.Context, bookID []int32) *DeleteBookBatchResults {
//...
}
func (b *DeleteBookBatchResults) Exec(f func(int, error)) {
//...
}
func (b *DeleteBookBatchResults) Close() error {
//...
}NOTE: This command only works with PostgreSQL using the pgx/v4 and pgx/v5 drivers and outputting Go code.
The generated method will return a batch object. The batch object will have the following methods:
Query, that takes afunc(int, []T, error)parameter, whereTis your query's return typeClose, to close the batch operation early.
-- name: BooksByTitleYear :batchmany
SELECT * FROM books
WHERE title = $1 AND year = $2;type BooksByTitleYearBatchResults struct {
br pgx.BatchResults
ind int
}
type BooksByTitleYearParams struct {
Title string `json:"title"`
Year int32 `json:"year"`
}
func (q *Queries) BooksByTitleYear(ctx context.Context, arg []BooksByTitleYearParams) *BooksByTitleYearBatchResults {
//...
}
func (b *BooksByTitleYearBatchResults) Query(f func(int, []Book, error)) {
//...
}
func (b *BooksByTitleYearBatchResults) Close() error {
//...
}NOTE: This command only works with PostgreSQL using the pgx/v4 and pgx/v5 drivers and outputting Go code.
The generated method will return a batch object. The batch object will have the following methods:
QueryRow, that takes afunc(int, T, error)parameter, whereTis your query's return typeClose, to close the batch operation early.
-- name: CreateBook :batchone
INSERT INTO books (
author_id,
isbn
) VALUES (
$1,
$2
)
RETURNING book_id, author_id, isbntype CreateBookBatchResults struct {
br pgx.BatchResults
ind int
}
type CreateBookParams struct {
AuthorID int32 `json:"author_id"`
Isbn string `json:"isbn"`
}
func (q *Queries) CreateBook(ctx context.Context, arg []CreateBookParams) *CreateBookBatchResults {
//...
}
func (b *CreateBookBatchResults) QueryRow(f func(int, Book, error)) {
//...
}
func (b *CreateBookBatchResults) Close() error {
//...
}__NOTE: This command is driver and package specific, see how to insert
This command is used to insert rows a lot faster than sequential inserts.