rt

package
v0.0.0-...-2e69cdf Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 23, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

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

Constants

This section is empty.

Variables

View Source
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

func CountRender[M any](table string, preds []Pred[M]) (string, []any, error)

CountRender renders "SELECT count(*)" filtered by the predicates; none counts the whole table.

func DeleteRender

func DeleteRender[M any](table string, preds []Pred[M]) (string, []any, error)

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

func ExistsRender[M any](table string, preds []Pred[M]) (string, []any, error)

ExistsRender renders "SELECT EXISTS (...)" over the predicates.

func Open

func Open(driver, dsn string) (*sql.DB, error)

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

func SelectRender[M any](table, columns string, opts []Opt[M]) (string, []any, error)

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

func Tx(ctx context.Context, db DBTX, fn func(tx *sql.Tx) error) (err error)

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

func UpdateRender[M any](table string, set []Assign[M], preds []Pred[M]) (string, []any, error)

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

type Column[M, T any] struct {
	// Name is the SQL column name, unquoted.
	Name string
}

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]) Asc

func (c Column[M, T]) Asc() Order[M]

Asc orders by this column, ascending.

func (Column[M, T]) Desc

func (c Column[M, T]) Desc() Order[M]

Desc orders by this column, descending.

func (Column[M, T]) Eq

func (c Column[M, T]) Eq(v T) Pred[M]

Eq is "column = v". NULL never matches (SQL three-valued logic); test NULL with IsNull.

func (Column[M, T]) EqCol

func (c Column[M, T]) EqCol(o Column[M, T]) Pred[M]

EqCol compares two columns of the same model: "a = b" (same-shape column comparison is runtime-builder material, D32).

func (Column[M, T]) Ge

func (c Column[M, T]) Ge(v T) Pred[M]

Ge is "column >= v".

func (Column[M, T]) GeCol

func (c Column[M, T]) GeCol(o Column[M, T]) Pred[M]

GeCol is "a >= b".

func (Column[M, T]) Gt

func (c Column[M, T]) Gt(v T) Pred[M]

Gt is "column > v".

func (Column[M, T]) GtCol

func (c Column[M, T]) GtCol(o Column[M, T]) Pred[M]

GtCol is "a > b".

func (Column[M, T]) In

func (c Column[M, T]) In(vs ...T) Pred[M]

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]) IsNotNull

func (c Column[M, T]) IsNotNull() Pred[M]

IsNotNull is "column IS NOT NULL".

func (Column[M, T]) IsNull

func (c Column[M, T]) IsNull() Pred[M]

IsNull is "column IS NULL".

func (Column[M, T]) Le

func (c Column[M, T]) Le(v T) Pred[M]

Le is "column <= v".

func (Column[M, T]) LeCol

func (c Column[M, T]) LeCol(o Column[M, T]) Pred[M]

LeCol is "a <= b".

func (Column[M, T]) Like

func (c Column[M, T]) Like(pattern string) Pred[M]

Like is "column LIKE pattern" (SQLite LIKE: case-insensitive ASCII, '%' and '_' wildcards).

func (Column[M, T]) Lt

func (c Column[M, T]) Lt(v T) Pred[M]

Lt is "column < v".

func (Column[M, T]) LtCol

func (c Column[M, T]) LtCol(o Column[M, T]) Pred[M]

LtCol is "a < b".

func (Column[M, T]) Ne

func (c Column[M, T]) Ne(v T) Pred[M]

Ne is "column <> v". Rows where the column is NULL never match.

func (Column[M, T]) NeCol

func (c Column[M, T]) NeCol(o Column[M, T]) Pred[M]

NeCol is "a <> b".

func (Column[M, T]) NotIn

func (c Column[M, T]) NotIn(vs ...T) Pred[M]

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.

func (Column[M, T]) NotLike

func (c Column[M, T]) NotLike(pattern string) Pred[M]

NotLike is "column NOT LIKE pattern".

func (Column[M, T]) Set

func (c Column[M, T]) Set(v T) Assign[M]

Set assigns v to this column in an UpdateWhere.

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

type Null[T any] struct {
	V     T
	Valid bool
}

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 Some

func Some[T any](v T) Null[T]

Some wraps a value in a valid Null.

func (Null[T]) Get

func (n Null[T]) Get() (T, bool)

Get returns the value and whether it is present.

func (Null[T]) MarshalJSON

func (n Null[T]) MarshalJSON() ([]byte, error)

MarshalJSON writes the value, or null.

func (Null[T]) Or

func (n Null[T]) Or(def T) T

Or returns the value, or def when NULL.

func (*Null[T]) Scan

func (n *Null[T]) Scan(src any) error

Scan implements sql.Scanner. Conversion from the driver value is delegated to database/sql's own rules via sql.Null.

func (*Null[T]) UnmarshalJSON

func (n *Null[T]) UnmarshalJSON(data []byte) error

UnmarshalJSON reads null as NULL and anything else as the value.

func (Null[T]) Value

func (n Null[T]) Value() (driver.Value, error)

Value implements driver.Valuer: the value, or nil for NULL.

type NullColumn

type NullColumn[M, T any] struct {
	Column[M, T]
}

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

func After[M any](key ...any) Opt[M]

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 Distinct

func Distinct[M any]() Opt[M]

Distinct deduplicates the result rows (SELECT DISTINCT).

func Limit

func Limit[M any](n int) Opt[M]

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).

func Offset

func Offset[M any](n int) Opt[M]

Offset skips n rows; the last Offset wins. OFFSET degrades linearly with depth — keyset pagination (After) is the scalable page mechanism (D34).

func OrderBy

func OrderBy[M any](terms ...Order[M]) Opt[M]

OrderBy sorts the result by the given terms, in order. Repeated OrderBy options append.

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

func And[M any](ps ...Pred[M]) Pred[M]

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 Not

func Not[M any](p Pred[M]) Pred[M]

Not negates a predicate. Not of the empty predicate is empty.

func Or

func Or[M any](ps ...Pred[M]) Pred[M]

Or is the disjunction of the given predicates, with the same empty- predicate normalization as And.

func Raw

func Raw[M any](sql string, args ...any) Pred[M]

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

func NewStmtCache(max int) *StmtCache

NewStmtCache returns a cache holding at most max prepared statements. max <= 0 means an unbounded cache.

func (*StmtCache) Close

func (c *StmtCache) Close() error

Close closes every cached statement and rejects further use.

func (*StmtCache) Len

func (c *StmtCache) Len() int

Len reports the number of cached statements.

func (*StmtCache) Prepare

func (c *StmtCache) Prepare(ctx context.Context, db DBTX, query string) (*sql.Stmt, error)

Prepare returns the cached statement for query, preparing and caching it on first use. The returned statement is shared: callers must not Close it (Close the cache instead).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL