Queries
Andurel v2 persists through narsilc: you write annotated SQL under models/queries/, and the tool generates Go that fills application-owned result structs under models/internal/queries.
Generate a query file
1andurel generate query UserReport
2andurel generate query UserReport --table users
3andurel generate query UserReport --dry-run --json
The command creates models/queries/user_report.sql. Without --table, the file contains commented annotation examples. With --table, it includes a starter annotated query for the existing table.
After editing the SQL, compile Go code:
1andurel sync queries
Output is written under models/internal/queries. The command is a no-op when models/queries contains no annotated SQL files. andurel run, andurel build, scaffolding, and related flows also regenerate narsilc output when query files exist. The narsilc binary version comes from andurel.toml [tools]; digests live in andurel.lock.
Annotate SQL
narsilc queries use name annotations and result cardinality:
1-- name: GetProduct :one
2SELECT *
3FROM products
4WHERE id = $1
5LIMIT 1;
6
7-- name: ListProducts :many
8-- @order id
9SELECT *
10FROM products;
11
12-- name: CountProducts :one
13SELECT count(*)
14FROM products;
15
16-- name: CreateProduct :one
17INSERT INTO products (id, sku, name, created_at, updated_at)
18VALUES ($1, $2, $3, $4, $5)
19RETURNING *;
20
21-- name: DeleteProduct :exec
22DELETE FROM products
23WHERE id = $1;
| Annotation | Meaning |
|---|---|
:one |
Exactly one row (or error) |
:many |
Zero or more rows |
:exec |
Statement with no result set |
-- @order |
Stable ordering hint for list queries |
Model generators emit a matching *.sql file beside CRUD methods. Hand-written reports follow the same annotation style.
Keep generated queries behind models
Only model packages should import the internal generated query package. Controllers and services depend on application model APIs and projection types, not database-shaped rows or parameters.
1func (p Products) Find(ctx context.Context, id uuid.UUID) (Product, error) {
2 entity, err := p.queries.GetProduct[Product](ctx, id)
3 if err != nil {
4 if errors.Is(err, pgx.ErrNoRows) {
5 return Product{}, ErrNotFound
6 }
7 return Product{}, err
8 }
9 return entity, nil
10}
Map generated rows to an application-owned projection inside the model method when the public API should differ from the SQL shape.
Share transactions
narsilc clients accept storage.Connection and storage.Transaction directly because both implement pgx DBTX:
1err := storage.RunInTransaction(ctx, connection,
2 func(ctx context.Context, tx storage.Transaction) error {
3 return queries.New(tx).RecordProductCreated(ctx, productID)
4 },
5)
See Getting Started and Models.