Documentation
¶
Index ¶
- Variables
- func Attach[T any](ctx context.Context, db Queryer, row *T, relation string, ids ...any) error
- func Delete[T any](ctx context.Context, db Queryer, row *T) error
- func Detach[T any](ctx context.Context, db Queryer, row *T, relation string, ids ...any) error
- func Exec(ctx context.Context, db Queryer, sqlText string, args ...any) (sql.Result, error)
- func Find[T any](ctx context.Context, db Queryer, key ...any) (*T, error)
- func ForceDelete[T any](ctx context.Context, db Queryer, row *T) error
- func Insert[T any](ctx context.Context, db Queryer, row *T) error
- func InsertAll[T any](ctx context.Context, db Queryer, rows []T) error
- func Pluck[V any, T any](ctx context.Context, db Queryer, q Query[T], column string) ([]V, error)
- func Restore[T any](ctx context.Context, db Queryer, row *T) error
- func SyncRelation[T any, K any](ctx context.Context, db Queryer, row *T, relation string, ids []K) error
- func Update[T any](ctx context.Context, db Queryer, row *T, cols ...string) error
- func Upsert[T any](ctx context.Context, db Queryer, row *T, opts ...UpsertOption) error
- func UpsertAll[T any](ctx context.Context, db Queryer, rows []T, opts ...UpsertOption) error
- func WriteColumns(w io.Writer, pkgName string, models ...any) error
- type BelongsTo
- type Compiled
- func (c *Compiled[T]) All(ctx context.Context, db Queryer, args ...any) ([]T, error)
- func (c *Compiled[T]) Count(ctx context.Context, db Queryer, args ...any) (int64, error)
- func (c *Compiled[T]) Exists(ctx context.Context, db Queryer, args ...any) (bool, error)
- func (c *Compiled[T]) First(ctx context.Context, db Queryer, args ...any) (*T, error)
- func (c *Compiled[T]) Rows(ctx context.Context, db Queryer, args ...any) iter.Seq2[T, error]
- func (c *Compiled[T]) Sole(ctx context.Context, db Queryer, args ...any) (*T, error)
- type DB
- type Dialect
- type Expr
- type HasMany
- type HasOne
- type ManyToMany
- type Option
- type Query
- func (q Query[T]) All(ctx context.Context, db Queryer) ([]T, error)
- func (q Query[T]) AllRows() Query[T]
- func (q Query[T]) Count(ctx context.Context, db Queryer) (int64, error)
- func (q Query[T]) CreateOrFirst(ctx context.Context, db Queryer, row *T) error
- func (q Query[T]) DeleteAll(ctx context.Context, db Queryer) (int64, error)
- func (q Query[T]) Exists(ctx context.Context, db Queryer) (bool, error)
- func (q Query[T]) First(ctx context.Context, db Queryer) (*T, error)
- func (q Query[T]) FirstOrCreate(ctx context.Context, db Queryer, row *T) error
- func (q Query[T]) ForUpdate() Query[T]
- func (q Query[T]) ForceDeleteAll(ctx context.Context, db Queryer) (int64, error)
- func (q Query[T]) GroupBy(expr string) Query[T]
- func (q Query[T]) Having(expr string, args ...any) Query[T]
- func (q Query[T]) Join(clause string) Query[T]
- func (q Query[T]) Limit(n int) Query[T]
- func (q Query[T]) Offset(n int) Query[T]
- func (q Query[T]) OnlyTrashed() Query[T]
- func (q Query[T]) OrderBy(expr string) Query[T]
- func (q Query[T]) Restore(ctx context.Context, db Queryer) (int64, error)
- func (q Query[T]) Rows(ctx context.Context, db Queryer) iter.Seq2[T, error]
- func (q Query[T]) Scope(fns ...func(Query[T]) Query[T]) Query[T]
- func (q Query[T]) Sole(ctx context.Context, db Queryer) (*T, error)
- func (q Query[T]) UpdateAll(ctx context.Context, db Queryer, set Set) (int64, error)
- func (q Query[T]) Where(expr string, args ...any) Query[T]
- func (q Query[T]) WhereHas(path string, opts ...RelOption) Query[T]
- func (q Query[T]) WhereHasNot(path string, opts ...RelOption) Query[T]
- func (q Query[T]) With(path string, opts ...RelOption) Query[T]
- func (q Query[T]) WithCount(relation string) Query[T]
- func (q Query[T]) WithTrashed() Query[T]
- type QueryEvent
- type QueryHook
- type Queryer
- type RawQuery
- type RelOption
- type Set
- type TableNamer
- type Tx
- type UpsertOption
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNotFound is returned by First, Find, Sole and friends when no row // matches. It wraps sql.ErrNoRows, so both // errors.Is(err, rio.ErrNotFound) and errors.Is(err, sql.ErrNoRows) hold. ErrNotFound = fmt.Errorf("rio: record not found (%w)", sql.ErrNoRows) // ErrMultipleRows is returned by Sole when more than one row matches. ErrMultipleRows = errors.New("rio: expected exactly one row, found more") // ErrStaleObject is returned by Update and Delete when the row's version // column no longer matches, meaning another writer got there first — // or the row is gone entirely. ErrStaleObject = errors.New("rio: stale object: version conflict or row deleted") // ErrMissingWhere guards set-based writes: UpdateAll and DeleteAll refuse // to touch a whole table unless AllRows was called explicitly. ErrMissingWhere = errors.New("rio: UpdateAll/DeleteAll without conditions; call AllRows() to affect the whole table") // ErrDuplicateKey reports a unique constraint violation, translated from // the driver error (which remains in the chain for errors.As). ErrDuplicateKey = errors.New("rio: duplicate key violates unique constraint") // ErrForeignKeyViolated reports a foreign key constraint violation. ErrForeignKeyViolated = errors.New("rio: foreign key constraint violated") // ErrNoPrimaryKey is returned when Find, Update, or Delete is called on // a model that declares no primary key. ErrNoPrimaryKey = errors.New("rio: model has no primary key") )
Sentinel errors. Every error returned by rio can be inspected with errors.Is; none of them are ever logged by rio itself.
Functions ¶
func Attach ¶ added in v0.2.0
Attach links rows to a ManyToMany relation by inserting join-table rows — idempotently: existing links are left alone (bare ON CONFLICT DO NOTHING; a no-op assignment on MySQL), assuming the join table's standard composite unique key. Nothing here is implicit: this is the explicit inverse of hand-writing the join-table INSERT. Attaching zero ids is a no-op.
func Delete ¶
Delete removes a row by primary key. Models with a softdelete column get an UPDATE setting the deletion timestamp instead; ForceDelete really deletes. The version column, when present, is checked like Update.
func Detach ¶ added in v0.2.0
Detach unlinks rows from a ManyToMany relation by deleting join-table rows. The ids are required — detaching "everything" must be written as an explicit set-based delete on the join table, never implied by an empty call.
func Exec ¶
Exec runs a hand-written statement through the shared pipeline and returns the driver result.
func Find ¶
Find fetches a row by primary key. Composite keys pass every part in struct-field declaration order. The statement shape is fixed, so the SQL is rendered once per grammar and cached — Find is the hottest read there is.
func ForceDelete ¶
ForceDelete removes the row with a real DELETE even when the model soft deletes.
func Insert ¶
Insert writes one row and fills in what the database generated: the whole row via RETURNING on PostgreSQL/SQLite, the auto-increment ID on MySQL — never a hidden second SELECT. Zero values are written as-is; columns tagged omitzero are skipped when zero so DB defaults apply, and a zero auto-increment PK is skipped implicitly. CreatedAt/UpdatedAt are set to the clock when zero; a zero version column starts at 1.
func InsertAll ¶
InsertAll writes rows in multi-VALUES statements, auto-chunked to the dialect's bind-parameter ceiling. Outside a transaction each chunk commits independently — a failure in chunk N leaves earlier chunks written; wrap the call in db.Tx for atomicity (rio adds no hidden transaction).
Backfill promises only what the dialects can keep: auto-increment PKs on PostgreSQL (RETURNING, positional) and SQLite (RETURNING sorted by PK — its output order is documented as undefined), nothing on MySQL (its default interleaved autoinc mode makes first-ID+i arithmetic wrong). Timestamps, version, and every explicit value are already in your slice. omitzero does not apply on the batch path: one statement, one column list.
func Pluck ¶ added in v0.2.0
Pluck extracts a single column under the query's conditions: emails, err := rio.Pluck[string](ctx, db, q, "email"). The column must be one of T's mapped columns — expressions go through Raw.
func Restore ¶
Restore clears the deletion timestamp of one soft-deleted row by primary key — the entity-form inverse of Delete. The version column, when present, is checked and bumped like any other write.
func SyncRelation ¶ added in v0.3.0
func SyncRelation[T any, K any](ctx context.Context, db Queryer, row *T, relation string, ids []K) error
SyncRelation makes the ManyToMany relation match ids exactly, inside one transaction (or savepoint, when db already is one): links not in ids are removed, missing ones are added idempotently. An empty ids slice explicitly empties the relation — unlike Detach, where "no ids" is a refused footgun, Sync's whole meaning is "converge on this set".
Concurrent SyncRelation calls on the same owner serialize on a row lock (SELECT ... FOR UPDATE on the owner; SQLite's single-writer model serializes anyway) — without it, two syncs interleaving their DELETE and INSERT under READ COMMITTED would converge on the union of both sets, which is exactly not what "sync" promises.
func Update ¶
Update writes a row by primary key. With no column list it writes every column — honestly, zero values included (partial scans beware: unscanned fields overwrite with zeros). With a list it updates exactly those columns plus UpdatedAt. A version column is checked and incremented atomically; losing the race returns ErrStaleObject.
func Upsert ¶
Upsert inserts the row or updates it on unique-key conflict, in one statement. All four elements ship together: conflict target, update whitelist, RETURNING backfill (PG/SQLite; MySQL fills the auto-increment ID only on the insert path), and timestamp maintenance.
Soft-delete invariant: a successful DoUpdate upsert leaves the row visible — deleted_at is cleared unless KeepTrashed is given. The explicit softdelete tag opted the model into deletion semantics; resurrect-on-upsert is its consistent extension (the alternative is Eloquent's famous trap: "upsert succeeded but the row is invisible").
func UpsertAll ¶
UpsertAll upserts rows in multi-VALUES statements with the same conflict clause as Upsert. It never backfills: DoNothing shrinks the returned row set, so positional matching would silently misalign (the batch-backfill killer). Reload rows you need generated values for.
func WriteColumns ¶ added in v0.3.0
WriteColumns generates Go source declaring column-name constants for the given models — typo-proof, IDE-completable column references for query strings:
users, err := rio.From[User]().Where(UserCols.Email+" = ?", e).All(ctx, db)
The generator runs on rio's own mapping plans, so the output can never drift from runtime behavior; there is no source parsing and no build-time tool chain. Wire it up with go:generate and a three-line main:
//go:generate sh -c "go run ./internal/gencols > cols_gen.go"
// internal/gencols/main.go
func main() {
if err := rio.WriteColumns(os.Stdout, "models", models.User{}, models.Post{}); err != nil {
log.Fatal(err)
}
}
Each model yields a <Name>Table constant (the convention- or TableName-derived name; a WithTableNamer handle may rename it at runtime) and a <Name>Cols struct value with one string field per mapped column.
Types ¶
type BelongsTo ¶
type BelongsTo[T any] struct { // contains filtered or unexported fields }
BelongsTo holds the parent row referenced by a foreign key on this row. After preloading, a NULL foreign key yields the loaded-nil state — Row returns nil instead of panicking, preserving "With makes access safe".
func (BelongsTo[T]) MarshalJSON ¶
MarshalJSON behaves like HasOne.MarshalJSON.
func (BelongsTo[T]) Row ¶
func (r BelongsTo[T]) Row() *T
Row returns the loaded parent, or nil when the foreign key was NULL. It panics if the relation was never loaded.
func (*BelongsTo[T]) Set ¶
func (r *BelongsTo[T]) Set(row *T)
Set marks the relation loaded. A nil row means "loaded, no parent".
func (*BelongsTo[T]) UnmarshalJSON ¶
UnmarshalJSON behaves like HasOne.UnmarshalJSON.
type Compiled ¶
type Compiled[T any] struct { // contains filtered or unexported fields }
Compiled is a query compiled once and executed many times, in the spirit of regexp.MustCompile: declare it at package level, bind parameters at the call site. Structural validation runs eagerly; SQL renders lazily per grammar (dialect + namer) and is cached, so the hot path only binds.
func MustCompile ¶
MustCompile compiles a connection-free query for reuse, panicking on structural problems — bad tags, unknown relation paths, mixed inline and exec-time parameters. Passing MustCompile does not mean execution cannot fail: dialect capabilities, exact placeholder arity, and driver errors surface at the execution point. Queries are either fully inline (every ? already has its argument — a frozen constant query) or fully exec-parameterized (no inline arguments; every ? binds at the call). Slice expansion inside IN (?) needs value shapes and is inline-only.
func (*Compiled[T]) Count ¶
Count runs the compiled conditions under SELECT count(*). The count SQL renders per grammar on first use like everything else.
func (*Compiled[T]) First ¶
First executes the compiled query and returns the first row or ErrNotFound. Compile with Limit(1) to avoid over-fetching.
type DB ¶
type DB struct {
// contains filtered or unexported fields
}
DB wraps a *sql.DB with a dialect. rio never replaces or tunes the connection pool — configure pooling on the *sql.DB you pass in.
func New ¶
New wraps an existing *sql.DB. Driver modules (go-rio/postgres, go-rio/mysql, go-rio/sqlite) call this for you and add driver-specific error translation; use New directly when you bring your own driver.
func (*DB) Close ¶
Close closes the prepared-statement cache (if enabled) and the underlying *sql.DB.
type Dialect ¶
type Dialect interface {
// contains filtered or unexported methods
}
Dialect identifies one of the built-in SQL dialects. The interface is deliberately opaque (all methods unexported): rio's renderers evolve together with the grammars, and a cross-module implementation would freeze them. Driver modules pick a built-in value; they never implement this.
type Expr ¶
type Expr string
Expr is a raw SQL fragment used as an UpdateAll value — the escape hatch for database-side arithmetic: rio.Set{"count": rio.Expr("count + 1")}. It is spliced into the statement verbatim; never build one from user input.
type HasMany ¶
type HasMany[T any] struct { // contains filtered or unexported fields }
HasMany holds the "child rows pointing at this row" side of a one-to-many relation. It is a container rather than a bare slice so that "not loaded" and "loaded, empty" are different states: rio never returns silently empty data and never lazy-loads. Structs containing relation containers are not comparable; compare with cmp.Diff in tests.
func (HasMany[T]) MarshalJSON ¶
MarshalJSON encodes unloaded relations as null and loaded ones as arrays, so API payloads distinguish "not fetched" from "none".
func (HasMany[T]) Rows ¶
func (r HasMany[T]) Rows() []T
Rows returns the loaded children. It panics if the relation was never loaded — accessing unloaded data is a programming error, not an empty result.
func (*HasMany[T]) Set ¶
func (r *HasMany[T]) Set(rows []T)
Set marks the relation loaded with the given rows. Manual assembly (from a custom query or fixture) is a supported use.
func (*HasMany[T]) UnmarshalJSON ¶
UnmarshalJSON accepts null (leaving the relation unloaded) or an array.
type HasOne ¶
type HasOne[T any] struct { // contains filtered or unexported fields }
HasOne holds the "single child row pointing at this row" side of a one-to-one relation.
func (HasOne[T]) MarshalJSON ¶
MarshalJSON encodes unloaded as null; loaded-none also encodes as null.
func (HasOne[T]) Row ¶
func (r HasOne[T]) Row() *T
Row returns the loaded child, or nil when the parent has none. It panics if the relation was never loaded.
func (*HasOne[T]) Set ¶
func (r *HasOne[T]) Set(row *T)
Set marks the relation loaded. A nil row means "loaded, has none".
func (*HasOne[T]) UnmarshalJSON ¶
UnmarshalJSON accepts null (leaving the relation unloaded) or an object.
type ManyToMany ¶
type ManyToMany[T any] struct { // contains filtered or unexported fields }
ManyToMany is HasMany across a join table.
func (ManyToMany[T]) Loaded ¶
func (r ManyToMany[T]) Loaded() bool
Loaded reports whether the relation has been populated by With or Set.
func (ManyToMany[T]) MarshalJSON ¶
func (r ManyToMany[T]) MarshalJSON() ([]byte, error)
MarshalJSON behaves like HasMany.MarshalJSON.
func (ManyToMany[T]) Rows ¶
func (r ManyToMany[T]) Rows() []T
Rows returns the loaded rows, panicking when the relation was never loaded.
func (*ManyToMany[T]) Set ¶
func (r *ManyToMany[T]) Set(rows []T)
Set marks the relation loaded with the given rows.
func (*ManyToMany[T]) UnmarshalJSON ¶
func (r *ManyToMany[T]) UnmarshalJSON(b []byte) error
UnmarshalJSON behaves like HasMany.UnmarshalJSON.
type Option ¶
type Option func(*config)
Option configures a DB handle at construction time.
func WithClock ¶
WithClock replaces the time source used for CreatedAt/UpdatedAt and soft deletes. Intended for tests.
func WithErrorTranslator ¶
WithErrorTranslator installs a driver-specific error translator, mapping driver errors to rio sentinels (ErrDuplicateKey, ErrForeignKeyViolated). The go-rio driver modules install one automatically; the translator runs before the dialect's SQLSTATE fallback. Returning nil means "not mine".
func WithQueryHook ¶
WithQueryHook installs an observability hook. Hooks see every statement rio executes — entity CRUD, builder queries, compiled queries, Raw, and transaction control — and cannot alter them.
func WithStmtCache ¶
WithStmtCache enables the prepared-statement cache on this DB handle.
Off by default: connection poolers in transaction/statement mode (PgBouncer et al.) break server-side prepared statements. Enable it only when rio talks to the database directly. Statements are cached per SQL text on the DB only — transactions bypass the cache — and the cache is LRU-bounded (cap configurable) because IN (?) expansion makes every slice length a distinct statement. On schema-change errors the entry is evicted and the error propagates; rio never retries on its own.
func WithTableNamer ¶
WithTableNamer overrides the convention-based struct-name→table-name derivation for this DB handle. Models implementing TableName() string still win. The namer is part of the DB's grammar identity: SQL caches are keyed by it, so two handles with different namers never share rendered SQL.
func WithoutArgs ¶
func WithoutArgs() Option
WithoutArgs redacts bind arguments from QueryEvent before hooks see them.
type Query ¶
type Query[T any] struct { // contains filtered or unexported fields }
Query is an immutable, connection-free query description. Every builder method returns a derived copy; deriving several queries from one shared base — including concurrently, including package-level bases — never cross-contaminates. Queries hold no rendered SQL: rendering happens at the execution point against the handle's grammar (Compiled caches it).
func (Query[T]) AllRows ¶
AllRows is the explicit opt-in for UpdateAll/DeleteAll without conditions.
func (Query[T]) Count ¶
Count runs SELECT count(*) with the query's conditions. Combined with GroupBy the intent (rows vs groups) is ambiguous — use Raw for that.
func (Query[T]) CreateOrFirst ¶
CreateOrFirst inserts row, and on unique-key conflict returns the existing row instead — the race-honest inverse of FirstOrCreate (INSERT first, so the unique constraint arbitrates instead of a racy SELECT).
func (Query[T]) DeleteAll ¶
DeleteAll deletes every matching row in one statement — as an UPDATE setting the deletion timestamp on soft-delete models (already-trashed rows are excluded by the default filter), a real DELETE otherwise. It refuses to run without conditions unless AllRows() was called.
func (Query[T]) First ¶
First returns the first matching row, or ErrNotFound. No implicit ORDER BY is added: like SQL itself, order is undefined unless you ask for one.
func (Query[T]) FirstOrCreate ¶
FirstOrCreate returns the first row matching the query, inserting row when none exists. SELECT-then-INSERT races: a concurrent creator makes the INSERT hit ErrDuplicateKey, and FirstOrCreate then re-reads. When even the re-read misses, a soft-deleted row is probably squatting on the unique key (WithTrashed reveals it) and the duplicate-key error is returned as-is.
func (Query[T]) ForUpdate ¶
ForUpdate renders SELECT ... FOR UPDATE for read-modify-write inside a transaction. SQLite locks the whole database anyway; there it is a no-op.
func (Query[T]) ForceDeleteAll ¶
ForceDeleteAll hard-deletes matching rows even on soft-delete models. Combined with OnlyTrashed it empties the recycle bin.
func (Query[T]) GroupBy ¶
GroupBy appends a GROUP BY term. Entity queries with GROUP BY are almost always projections — prefer Raw — but filtering EXISTS-style uses remain.
func (Query[T]) Join ¶
Join appends a verbatim JOIN clause, for filtering through other tables: Join("INNER JOIN orgs ON orgs.id = users.org_id"). Projections across joins go through Raw — entity queries always select exactly T's columns.
func (Query[T]) OnlyTrashed ¶
OnlyTrashed selects only soft-deleted rows.
func (Query[T]) Restore ¶
Restore clears the deletion timestamp on every matching soft-deleted row. Conditions are required (or AllRows); combine with OnlyTrashed as needed.
func (Query[T]) Rows ¶ added in v0.2.0
Rows streams the result without materializing it, for result sets too large to hold: for u, err := range q.Rows(ctx, db). Iteration stops on the first error (yielded with a zero T) and closing happens automatically, including on early break. Preloading needs the full set and cannot stream — With/WithCount on a streamed query yields an error immediately.
func (Query[T]) Scope ¶ added in v0.3.0
Scope applies reusable query functions in order, keeping the chain readable — a scope is just func(Query[T]) Query[T], no registry and no magic:
func Active(q rio.Query[User]) rio.Query[User] { return q.Where("active") }
users, err := rio.From[User]().Scope(Active, Recent).All(ctx, db)
func (Query[T]) Sole ¶
Sole returns the single matching row; ErrNotFound when none match and ErrMultipleRows when more than one does.
func (Query[T]) UpdateAll ¶
UpdateAll updates every matching row in one statement, returning the affected count. It refuses to run without conditions unless AllRows() was called. UpdatedAt is maintained automatically (override by assigning it yourself). Set-based writes bypass the version column — like every bulk path, and documented as such: optimistic locking guards entity Update.
func (Query[T]) Where ¶
Where adds an AND-ed condition written in SQL with ? placeholders. Slice arguments expand inside IN (?).
func (Query[T]) WhereHas ¶ added in v0.2.0
WhereHas keeps only rows whose relation at path has at least one matching row, rendered as EXISTS (...). Options constrain the related rows; nested paths ("Posts.Comments") nest the EXISTS. Related soft-delete models are filtered like preloading; RelWithTrashed applies to the leaf.
func (Query[T]) WhereHasNot ¶ added in v0.2.0
WhereHasNot keeps only rows whose relation at path has no matching row — NOT EXISTS (...).
func (Query[T]) With ¶
With preloads a relation after the main query, using one IN query per relation (never JOINs — no row explosion, pagination stays correct). Paths nest with dots: With("Posts.Comments"). Options customize the leaf.
func (Query[T]) WithCount ¶ added in v0.2.0
WithCount fills the relation's count target field — declared as `PostsCount int64 \`rio:",countof:Posts"\“ — with one GROUP BY query per relation, the aggregate sibling of With. HasMany and ManyToMany only.
func (Query[T]) WithTrashed ¶
WithTrashed includes soft-deleted rows.
type QueryEvent ¶
type QueryEvent struct {
// Op is a stable label usable as a metrics dimension without parsing
// SQL: "select", "insert", "update", "delete", "upsert", "raw", "exec",
// "begin", "commit", "rollback", "savepoint".
Op string
// Model is the Go struct name behind the statement, "" for Raw/Exec and
// transaction control.
Model string
// Query is the rendered, dialect-form SQL.
Query string
// Args are the bind arguments, nil when the DB was built WithoutArgs.
Args []any
// Err is the translated execution error, nil on success. After only.
Err error
// Duration is the execution wall time. After only.
Duration time.Duration
// RowsAffected is the driver-reported count for writes, -1 when unknown
// (row-returning queries). After only.
RowsAffected int64
}
QueryEvent describes one statement execution. Hooks receive the same event pointer in Before and After; After sees Err, Duration, and RowsAffected filled in.
type QueryHook ¶
type QueryHook interface {
BeforeQuery(ctx context.Context, e *QueryEvent) context.Context
AfterQuery(ctx context.Context, e *QueryEvent)
}
QueryHook observes statement execution. BeforeQuery may derive a context (tracing spans); AfterQuery completes it. Hooks must not retain the event past the call and cannot modify the statement — rio has no mutating middleware by design.
The event covers statement execution: for row-returning queries, Err and Duration describe sending the query, not consuming the rows — scan and iteration failures surface only through the call's returned error.
type Queryer ¶
type Queryer interface {
// Tx runs fn inside a transaction (on *DB) or a savepoint (on *Tx),
// committing when fn returns nil and rolling back when it returns an
// error or panics.
Tx(ctx context.Context, fn func(tx *Tx) error) error
// contains filtered or unexported methods
}
Queryer is what every execution point accepts: a *DB or a *Tx. Data-access code written against Queryer runs unchanged inside and outside transactions. Tx opens a transaction on a DB and a savepoint on a Tx, so transactional helpers compose transparently.
type RawQuery ¶
type RawQuery[T any] struct { // contains filtered or unexported fields }
RawQuery is the escape hatch: hand-written SQL through the same rebind pipeline, hooks, error translation, and scanner as everything else, into any target shape — DTO structs, scalars, entities. Like builders it is a connection-free value; placeholders are ? with IN (?) expansion.
func Raw ¶
Raw builds a raw query. Scanning into a struct matches by column name and errors on result columns with no matching field: silently dropped data is how schema drift hides. Scanning half an entity and then calling Update writes zero values to the columns you did not select — project into DTOs.
type RelOption ¶
type RelOption func(*relQuery)
RelOption customizes how one preloaded relation is fetched.
func RelLimit ¶ added in v0.2.0
RelLimit caps the preloaded rows per parent (not overall), via ROW_NUMBER() OVER (PARTITION BY the foreign key) — the query stays one round trip and pagination-correct. Order within each parent follows RelOrder, defaulting to the target's primary key for determinism. Requires window functions: PostgreSQL, MySQL 8+, SQLite 3.25+.
func RelWhere ¶
RelWhere restricts the preloaded rows. The condition runs inside the preload's own query, so it can only reference the related table's columns.
func RelWithTrashed ¶
func RelWithTrashed() RelOption
RelWithTrashed includes soft-deleted rows in the preload when the target model declares a softdelete column.
type Set ¶
Set is the column assignment map for UpdateAll. Keys are database column names; assignments render in sorted key order so the SQL is deterministic (stable goldens, stable statement-cache keys). Values bind as parameters unless they are Expr.
type TableNamer ¶
type TableNamer interface {
TableName() string
}
TableNamer overrides the convention-derived table name for one model.
type Tx ¶
type Tx struct {
// contains filtered or unexported fields
}
Tx is a transaction handle. It satisfies Queryer, so every rio entry point accepts it in place of a *DB. Like *sql.Tx it is bound to one connection and must not be used concurrently.
type UpsertOption ¶
type UpsertOption func(*upsertSpec)
UpsertOption shapes the conflict clause.
func DoNothing ¶
func DoNothing() UpsertOption
DoNothing turns conflicts into no-ops. On MySQL this renders as a no-op assignment rather than INSERT IGNORE, which would swallow unrelated errors. A conflicting soft-deleted row stays deleted.
func DoUpdate ¶
func DoUpdate(cols ...string) UpsertOption
DoUpdate lists the columns to overwrite on conflict. With no columns, every non-PK, non-CreatedAt, non-conflict-target column updates.
func KeepTrashed ¶
func KeepTrashed() UpsertOption
KeepTrashed opts out of the restore-on-upsert invariant: with it, an upsert hitting a soft-deleted row updates the tombstone without clearing deleted_at — and the row stays invisible to default queries.
func OnConflict ¶
func OnConflict(cols ...string) UpsertOption
OnConflict names the conflict target columns (the unique index). Required with DoUpdate on PostgreSQL/SQLite; MySQL has no conflict target — its ON DUPLICATE KEY reacts to any unique index, which is a documented semantic difference, not something rio papers over.