Documentation
¶
Overview ¶
The dynamic query layer (decisions D28-D34): predicates, orderings, limits and assignments as inert data — plain values forming a small expression tree, never closures and never a mutable builder. Generated terminal methods (UserQuery, UserCount, ...) hand the values to the interpreter here, which walks the tree once and renders SQL text and bound arguments in lockstep. The interpreter is deterministic: identical trees render identical SQL, which is what makes the rendered text a statement-cache key (D31).
The layer only filters, orders and limits an existing row shape (the shape rule, D32). Anything that changes what a row is — joins, aggregates, projections — happens at generation time, where result structs can be minted.
Package rt is the runtime support package for code generated by dbml gen. It is the only non-stdlib import generated code is allowed (decision D03): a nullable value type, the database handle interface, a transaction helper, an opener that applies the SQLite pragmas the generated SQL assumes, a prepared-statement cache, and the dynamic query layer (query.go) — typed column handles, predicates and options as inert data, and their deterministic SQL interpreter (D28-D34).
The package registers no driver. The application chooses one (any database/sql SQLite driver) and passes its name to Open, so rt itself stays dependency-free and cgo-free.
Index ¶
- Variables
- func CountRender[M any](table string, preds []Pred[M]) (string, []any, error)
- func DeleteRender[M any](table string, preds []Pred[M]) (string, []any, error)
- func ExistsRender[M any](table string, preds []Pred[M]) (string, []any, error)
- func Open(driver, dsn string) (*sql.DB, error)
- func RowScan(ctx context.Context, db DBTX, cache *StmtCache, query string, args []any, ...) error
- func SelectRender[M any](table, columns string, opts []Opt[M]) (string, []any, error)
- func StmtExec(ctx context.Context, db DBTX, cache *StmtCache, query string, args ...any) (sql.Result, error)
- func StmtQuery(ctx context.Context, db DBTX, cache *StmtCache, query string, args ...any) (*sql.Rows, error)
- func Tx(ctx context.Context, db DBTX, fn func(tx *sql.Tx) error) (err error)
- func UpdateRender[M any](table string, set []Assign[M], preds []Pred[M]) (string, []any, error)
- type Assign
- type Column
- func (c Column[M, T]) Asc() Order[M]
- func (c Column[M, T]) Desc() Order[M]
- func (c Column[M, T]) Eq(v T) Pred[M]
- func (c Column[M, T]) EqCol(o Column[M, T]) Pred[M]
- func (c Column[M, T]) Ge(v T) Pred[M]
- func (c Column[M, T]) GeCol(o Column[M, T]) Pred[M]
- func (c Column[M, T]) Gt(v T) Pred[M]
- func (c Column[M, T]) GtCol(o Column[M, T]) Pred[M]
- func (c Column[M, T]) In(vs ...T) Pred[M]
- func (c Column[M, T]) IsNotNull() Pred[M]
- func (c Column[M, T]) IsNull() Pred[M]
- func (c Column[M, T]) Le(v T) Pred[M]
- func (c Column[M, T]) LeCol(o Column[M, T]) Pred[M]
- func (c Column[M, T]) Like(pattern string) Pred[M]
- func (c Column[M, T]) Lt(v T) Pred[M]
- func (c Column[M, T]) LtCol(o Column[M, T]) Pred[M]
- func (c Column[M, T]) Ne(v T) Pred[M]
- func (c Column[M, T]) NeCol(o Column[M, T]) Pred[M]
- func (c Column[M, T]) NotIn(vs ...T) Pred[M]
- func (c Column[M, T]) NotLike(pattern string) Pred[M]
- func (c Column[M, T]) Set(v T) Assign[M]
- type DBTX
- type Null
- type NullColumn
- type Opt
- type Order
- type Pred
- type StmtCache
Constants ¶
This section is empty.
Variables ¶
var ErrNotFound = sql.ErrNoRows
ErrNotFound is returned by generated Get/Update/Delete methods when no row matches the given identity. It is sql.ErrNoRows, so existing errors.Is(err, sql.ErrNoRows) checks keep working.
Functions ¶
func CountRender ¶
CountRender renders "SELECT count(*)" filtered by the predicates; none counts the whole table.
func DeleteRender ¶
DeleteRender renders a predicate-guarded DELETE. No effective predicate is an error, not a full-table delete: affecting every row must be written out loud (a trivially true Raw predicate).
func ExistsRender ¶
ExistsRender renders "SELECT EXISTS (...)" over the predicates.
func Open ¶
Open opens a SQLite database via the named database/sql driver and applies the pragmas the generated code is designed for:
journal_mode = WAL readers do not block the writer busy_timeout = 5000 wait instead of failing with SQLITE_BUSY foreign_keys = ON the generated DDL declares real foreign keys
Pragmas are per-connection in SQLite, so Open pins the pool to a single connection. SQLite has a single writer anyway; one connection makes the pragmas hold for every statement and removes in-process busy contention. (Read scaling across many connections can be revisited later.)
func RowScan ¶
func RowScan(ctx context.Context, db DBTX, cache *StmtCache, query string, args []any, dest ...any) error
RowScan runs a single-row statement and scans its columns into dest, through cache when one is given (nil runs directly).
func SelectRender ¶
SelectRender renders a SELECT over the model's fixed shape: table and columns come pre-rendered from generated code, everything else from the options. Deterministic: identical option values render identical SQL (D31). Argument order matches placeholder order by construction.
func StmtExec ¶
func StmtExec(ctx context.Context, db DBTX, cache *StmtCache, query string, args ...any) (sql.Result, error)
StmtExec runs a non-row statement on db, through cache when one is given (nil runs directly).
func StmtQuery ¶
func StmtQuery(ctx context.Context, db DBTX, cache *StmtCache, query string, args ...any) (*sql.Rows, error)
StmtQuery runs a row-returning statement on db, through cache when one is given (nil runs directly).
func Tx ¶
Tx runs fn inside a transaction: begin, fn, commit — with a rollback on error or panic. db is typically a *sql.DB; passing a handle that cannot begin a transaction (such as a *sql.Tx: SQLite has no nested transactions) is an error.
func UpdateRender ¶
UpdateRender renders a predicate-guarded partial UPDATE from typed assignments. No assignments, or no effective predicate, is an error — rewriting every row must be written out loud, like DeleteRender.
Types ¶
type Assign ¶
type Assign[M any] struct { // contains filtered or unexported fields }
Assign is one "column = value" of an UpdateWhere, built by Column.Set and NullColumn.SetNull.
type Column ¶
Column is the typed handle of one column of model M: its methods build the Pred, Order and Assign values of the dynamic query layer. M is a phantom type — no value of it is ever held; it exists so a predicate over one model cannot enter another model's query (a compile error, D29). T is the column's Go value type; operator arguments are T, so a value of the wrong type is a compile error too.
Handles are emitted by the generator as flat per-column vars (UserEmail); the operators live here, once (D29).
func (Column[M, T]) Eq ¶
Eq is "column = v". NULL never matches (SQL three-valued logic); test NULL with IsNull.
func (Column[M, T]) EqCol ¶
EqCol compares two columns of the same model: "a = b" (same-shape column comparison is runtime-builder material, D32).
func (Column[M, T]) In ¶
In is "column IN (vs...)". With no values it renders constant false: membership in the empty set holds for no row.
func (Column[M, T]) Like ¶
Like is "column LIKE pattern" (SQLite LIKE: case-insensitive ASCII, '%' and '_' wildcards).
func (Column[M, T]) NotIn ¶
NotIn is "column NOT IN (vs...)". With no values it renders constant true. Rows where the column is NULL never match a non-empty NOT IN.
type DBTX ¶
type DBTX interface {
ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
}
DBTX is what generated queries need from a database handle. Both *sql.DB and *sql.Tx satisfy it, which is how the same generated method runs inside and outside a transaction.
type Null ¶
Null is the representation of a nullable column (decision D13): a value plus a validity bit, never a pointer. The zero value is NULL. JSON shows the value or null — the wire format never sees the wrapper.
var n rt.Null[string] // NULL
n = rt.Some("x") // 'x'
if n.Valid { use(n.V) }
func (Null[T]) MarshalJSON ¶
MarshalJSON writes the value, or null.
func (*Null[T]) Scan ¶
Scan implements sql.Scanner. Conversion from the driver value is delegated to database/sql's own rules via sql.Null.
func (*Null[T]) UnmarshalJSON ¶
UnmarshalJSON reads null as NULL and anything else as the value.
type NullColumn ¶
NullColumn is the handle of a nullable column. Comparisons take plain present values (T, not Null[T]): SQL comparison with NULL matches nothing, so NULL is handled by its own explicit operators — IsNull/IsNotNull to test, SetNull to assign.
func (NullColumn[M, T]) SetNull ¶
func (c NullColumn[M, T]) SetNull() Assign[M]
SetNull assigns NULL to this column in an UpdateWhere.
type Opt ¶
type Opt[M any] interface { // contains filtered or unexported methods }
Opt is one option of a dynamic query: a predicate (Pred is an Opt), an ordering, a limit, an offset, a keyset position or a distinct flag. Options are inert values; the generated terminal (UserQuery) hands them to the interpreter in one deterministic walk. Value-less options cannot infer M, so the generator emits per-model wrappers — UserLimit, UserOrderBy — over the constructors here (D30).
func After ¶
After positions the query strictly after the row with the given key — keyset pagination (D34): pass the previous page's last row's values for the ORDER BY columns, one per term, in the same order. Rendering requires an OrderBy and one key value per term; for a total, gap-free order include a unique column as the final tiebreak term. NULLs in keyset columns are not paginated over (SQL comparisons skip them); keep keyset columns NOT NULL.
func Limit ¶
Limit caps the number of rows returned; the last Limit wins. The value is bound as a parameter, so page size does not change the SQL text (one cached statement, D31).
type Order ¶
type Order[M any] struct { // contains filtered or unexported fields }
Order is one ORDER BY term, built by Column.Asc/Desc.
type Pred ¶
type Pred[M any] struct { // contains filtered or unexported fields }
Pred is one predicate over model M: a small immutable expression tree. Predicates compose (And/Or/Not), store in variables, append conditionally, and are shared across verbs — the same value drives Query, Count, Exists, DeleteWhere and UpdateWhere (D32).
The zero value is the empty predicate: it filters nothing and vanishes from And, Or, Not and WHERE clauses, so conditional query building needs no special cases:
var p rt.Pred[User]
if search != "" {
p = UserName.Like("%" + search + "%")
}
users, err := q.UserQuery(ctx, p)
func And ¶
And is the conjunction of the given predicates. Empty predicates are dropped; no (surviving) operands is the empty predicate, one is that operand unchanged.
func Or ¶
Or is the disjunction of the given predicates, with the same empty- predicate normalization as And.
func Raw ¶
Raw is the last-resort escape hatch: verbatim SQL as a predicate, with '?' placeholders bound to args. It is outside the safety net (D18) — nothing validates the fragment until SQLite prepares the statement — and it is the one deliberate hole in the typed layer. Column and parameter hygiene are the caller's problem here; prefer promoting the condition to a Select block in the schema.
type StmtCache ¶
type StmtCache struct {
// contains filtered or unexported fields
}
StmtCache is a prepared-statement cache keyed by rendered SQL text (decision D31). The dynamic-query interpreter renders identical SQL for identical option trees, so the text is a stable cache key; static generated SQL benefits the same way. Eviction is LRU; evicted and superseded statements are closed.
A cache is bound to the handle its statements were prepared on: use one StmtCache per *sql.DB and do not feed it transaction handles (prepare on the DB and let database/sql re-prepare inside transactions).
func NewStmtCache ¶
NewStmtCache returns a cache holding at most max prepared statements. max <= 0 means an unbounded cache.