Search documentation

Find a page in the Andurel docs.

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.