Documentation
¶
Overview ¶
Package rio is a type-safe ORM built around immutable, connection-free query values. A query touches the database only when a terminal method receives a context and Queryer:
users, err := rio.From[User]().
Where("age > ?", 18).
OrderBy("created_at DESC").
Limit(10).
With("Posts").
All(ctx, db)
Queryer is implemented by DB and Tx, so the same query can run inside or outside a transaction. Use ? placeholders for every dialect; slice arguments in IN (?) expressions are expanded at execution time, and a Query.Sub argument splices a subquery in place.
Models are ordinary structs. The rio tag configures column names, primary keys, optimistic locking, soft deletion, JSON, timestamp maintenance, omitted zero values, count targets, and relations. By convention ID is the primary key, CreatedAt and UpdatedAt are maintained timestamps, and a TableName method overrides the pluralized table name.
Relations load only when requested. Raw and Exec provide SQL escape hatches; Query.Validate and Query.Must validate reusable query templates; and WithStmtCache enables prepared-statement reuse. Sentinel errors support errors.Is while preserving translated driver errors in the chain.
Index ¶
- Variables
- func Attach[T any, K any](ctx context.Context, db Queryer, row *T, relation string, ids ...K) error
- func ClearRelation[T any](ctx context.Context, db Queryer, row *T, relation string) error
- func Delete[T any](ctx context.Context, db Queryer, row *T) error
- func Detach[T any, K any](ctx context.Context, db Queryer, row *T, relation string, ids ...K) 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 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 TableName(structName string) string
- 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 BatchStatement
- type BelongsTo
- type ColumnSchema
- type Cursor
- type DB
- func (d *DB) Close() error
- func (d *DB) DescribeModel(model any) (*TableSchema, error)
- func (d *DB) Dialect() Dialect
- func (d *DB) DriverHandle() any
- func (d *DB) Native() any
- func (d *DB) Tx(ctx context.Context, fn func(tx *Tx) error) error
- func (d *DB) TxWith(ctx context.Context, opts *sql.TxOptions, fn func(tx *Tx) error) (err error)
- func (d *DB) Unwrap() *sql.DB
- func (d *DB) WithoutStamps() *DB
- type Dialect
- type Expr
- type HasMany
- type HasOne
- type LockOption
- type ManyToMany
- type NativeBatchResults
- type NativeBatcher
- type NativeCell
- type NativeConfig
- type NativeCopier
- type NativeDB
- type NativeLastInserter
- type NativeRows
- type NativeScanKind
- type NativeTx
- type Option
- func WithClock(now func() time.Time) Option
- func WithDriverHandle(h any) Option
- func WithErrorTranslator(f func(error) error) Option
- func WithQueryHook(h QueryHook) Option
- func WithStmtCache(capacity ...int) Option
- func WithTableNamer(f func(structName string) string) Option
- func WithoutArgs() Option
- func WithoutStmtCache() Option
- type Query
- func (q Query[T]) After(c Cursor) Query[T]
- func (q Query[T]) All(ctx context.Context, db Queryer, args ...any) ([]T, error)
- func (q Query[T]) AllRows() Query[T]
- func (q Query[T]) Avg[V any](ctx context.Context, db Queryer, column string, args ...any) (V, error)
- func (q Query[T]) Before(c Cursor) Query[T]
- func (q Query[T]) Chunk(ctx context.Context, db Queryer, size int, args ...any) iter.Seq2[[]T, error]
- func (q Query[T]) Count(ctx context.Context, db Queryer, args ...any) (int64, error)
- func (q Query[T]) CreateOrFirst(ctx context.Context, db Queryer, row *T, args ...any) error
- func (q Query[T]) CursorAt(row *T) (Cursor, error)
- func (q Query[T]) DeleteAll(ctx context.Context, db Queryer, args ...any) (int64, error)
- func (q Query[T]) DeleteAllReturning(ctx context.Context, db Queryer, args ...any) ([]T, error)
- func (q Query[T]) Distinct() Query[T]
- func (q Query[T]) Exists(ctx context.Context, db Queryer, args ...any) (bool, error)
- func (q Query[T]) Final() Query[T]
- func (q Query[T]) Find(ctx context.Context, db Queryer, key ...any) (*T, error)
- func (q Query[T]) First(ctx context.Context, db Queryer, args ...any) (*T, error)
- func (q Query[T]) FirstOrCreate(ctx context.Context, db Queryer, row *T, args ...any) error
- func (q Query[T]) ForShare(opts ...LockOption) Query[T]
- func (q Query[T]) ForUpdate(opts ...LockOption) Query[T]
- func (q Query[T]) ForceDeleteAll(ctx context.Context, db Queryer, args ...any) (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]) Max[V any](ctx context.Context, db Queryer, column string, args ...any) (V, error)
- func (q Query[T]) Min[V any](ctx context.Context, db Queryer, column string, args ...any) (V, error)
- func (q Query[T]) Must() 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]) OrderKeys(keys ...SortKey) Query[T]
- func (q Query[T]) Pluck[V any](ctx context.Context, db Queryer, column string, args ...any) ([]V, error)
- func (q Query[T]) RestoreAll(ctx context.Context, db Queryer, args ...any) (int64, error)
- func (q Query[T]) Rows(ctx context.Context, db Queryer, args ...any) iter.Seq2[T, error]
- func (q Query[T]) SQL(db Queryer, args ...any) (string, []any, error)
- func (q Query[T]) Scope(fns ...func(Query[T]) Query[T]) Query[T]
- func (q Query[T]) Sole(ctx context.Context, db Queryer, args ...any) (*T, error)
- func (q Query[T]) Sub(column string) Subquery
- func (q Query[T]) Sum[V any](ctx context.Context, db Queryer, column string, args ...any) (V, error)
- func (q Query[T]) UpdateAll(ctx context.Context, db Queryer, set Set, args ...any) (int64, error)
- func (q Query[T]) UpdateAllReturning(ctx context.Context, db Queryer, set Set, args ...any) ([]T, error)
- func (q Query[T]) Validate() 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, opts ...RelOption) Query[T]
- func (q Query[T]) WithTrashed() Query[T]
- type QueryEvent
- type QueryHook
- type Queryer
- type RawQuery
- type RelOption
- type Set
- type SortKey
- type Subquery
- type TableNamer
- type TableSchema
- type Tx
- type UpsertOption
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNotFound reports that no row matched. It wraps sql.ErrNoRows. 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 reports an optimistic-lock conflict or deleted row. ErrStaleObject = errors.New("rio: stale object: version conflict or row deleted") // ErrMissingWhere reports a set-based write without conditions or AllRows. ErrMissingWhere = errors.New("rio: UpdateAll/DeleteAll without conditions; call AllRows() to affect the whole table") // ErrDuplicateKey reports a translated unique-constraint violation. 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 reports an operation that requires a model primary key. ErrNoPrimaryKey = errors.New("rio: model has no primary key") )
Functions ¶
func Attach ¶ added in v0.2.0
Attach links rows to a ManyToMany relation by inserting join-table rows. It is idempotent — existing links are left alone (ON CONFLICT DO NOTHING; a no-op assignment on MySQL), assuming the join table's standard composite unique key. Attaching zero ids is a no-op.
Large id sets are chunked to the dialect's bind-parameter ceiling; outside a transaction each chunk commits independently — wrap the call in db.Tx, or retry (idempotency makes a rerun converge).
func ClearRelation ¶ added in v0.16.0
ClearRelation unlinks every row of a ManyToMany relation.
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; ids must be non-empty.
func Exec ¶
Exec runs a hand-written statement through the shared pipeline and returns the driver result. The SQL is verbatim; never build it from untrusted input.
func Find ¶
Find fetches a row by primary key. Pass composite key parts in struct-field declaration order.
func ForceDelete ¶
ForceDelete removes a row even when its model supports soft deletion.
func Insert ¶
Insert writes one row and backfills generated columns supported by the dialect. Zero omitzero fields use database defaults; zero auto-increment keys are omitted. It initializes timestamps and a zero version before execution, so a failed call may still modify those fields. Trigger changes not returned by the statement are not loaded.
Example ¶
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/go-rio/rio"
)
// db stands for a handle opened by a driver module, for example
// sqlite.Open("file:app.db"). The core has no driver, so these examples
// compile but do not run.
var db *rio.DB
// User is a model: ID is the auto-increment primary key by convention,
// CreatedAt is maintained on insert, PostCount is the WithCount("Posts")
// target, and Posts loads only through With.
type User struct {
ID int64
Email string
Age int
Active bool
LoginCount int64
PostCount int64 `rio:",countof:Posts"`
CreatedAt time.Time
Posts rio.HasMany[Post]
}
// Post belongs to a User through the conventional user_id foreign key.
type Post struct {
ID int64
UserID int64
Title string
Published bool
Score int64
CreatedAt time.Time
}
func main() {
ctx := context.Background()
user := User{Email: "alice@example.com", Age: 30, Active: true}
if err := rio.Insert(ctx, db, &user); err != nil {
log.Fatal(err)
}
// ID is backfilled where the dialect generates it; CreatedAt is stamped.
fmt.Println(user.ID, user.CreatedAt.IsZero())
}
Output:
func InsertAll ¶
InsertAll inserts rows in chunks within the dialect's bind limit. Chunks commit independently unless the caller supplies a transaction. It backfills generated keys only where ordering is reliable; omitzero does not apply.
On a native channel whose driver streams bulk loads (go-rio/postgres via COPY), an explicit-key batch goes through the copy protocol instead: one exchange, atomic as a whole, values bound through the same encoding.
func Restore ¶
Restore clears the deletion timestamp of one soft-deleted row by primary key. 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 a ManyToMany relation match ids exactly in a transaction. No ids clears the relation.
func TableName ¶ added in v0.15.0
TableName derives the conventional table name for a struct type: User → users, APIKey → api_keys, Person → people.
func Update ¶
Update writes a row by primary key. Without cols it writes every eligible field, including zero values; otherwise it writes only cols and UpdatedAt. It enforces optimistic locking and may stamp the struct before a failed call.
func Upsert ¶
Upsert inserts a row or updates it on unique-key conflict in one statement. Unless KeepTrashed is set, a successful update restores a soft-deleted row. Zero omitzero fields are excluded from both insert and the default update set; naming one explicitly in DoUpdate is an error.
PostgreSQL and SQLite backfill the conflict result. MySQL backfills an auto-increment key only on insert and cannot refresh a server-incremented version; reload before updating the same versioned struct. MySQL DoUpdate requires MySQL 8.0.19 or later and is not supported by MariaDB.
Timestamp and version initialization occurs before execution, so a failed call may still modify the struct.
Example ¶
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/go-rio/rio"
)
// db stands for a handle opened by a driver module, for example
// sqlite.Open("file:app.db"). The core has no driver, so these examples
// compile but do not run.
var db *rio.DB
// User is a model: ID is the auto-increment primary key by convention,
// CreatedAt is maintained on insert, PostCount is the WithCount("Posts")
// target, and Posts loads only through With.
type User struct {
ID int64
Email string
Age int
Active bool
LoginCount int64
PostCount int64 `rio:",countof:Posts"`
CreatedAt time.Time
Posts rio.HasMany[Post]
}
// Post belongs to a User through the conventional user_id foreign key.
type Post struct {
ID int64
UserID int64
Title string
Published bool
Score int64
CreatedAt time.Time
}
func main() {
ctx := context.Background()
user := User{Email: "alice@example.com", Age: 31, Active: true}
err := rio.Upsert(ctx, db, &user,
rio.OnConflict("email"),
rio.DoUpdate("age", "active"),
rio.DoUpdateSet(rio.Set{"login_count": rio.Expr("users.login_count + 1")}),
)
if err != nil {
log.Fatal(err)
}
fmt.Println(user.ID)
}
Output:
func UpsertAll ¶
UpsertAll applies Upsert conflict behavior in chunked multi-VALUES statements. It does not backfill generated values, and omitzero does not apply. MySQL DoUpdate requires MySQL 8.0.19 or later and excludes MariaDB.
func WriteColumns ¶ added in v0.3.0
WriteColumns generates Go source declaring column-name constants for the given models: per model, 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 BatchStatement ¶ added in v0.13.0
BatchStatement is one rendered statement of a batch: SQL in the dialect's placeholder form and its bind values, as NativeDB.Query receives them.
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. A NULL foreign key preloads as loaded-nil: Row returns nil, no panic.
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 ColumnSchema ¶ added in v0.12.0
type ColumnSchema struct {
// Name is the column name.
Name string
// Field is the Go struct field's name.
Field string
// GoType is the field's Go type.
GoType reflect.Type
// Nullable reports whether rio can scan a NULL into the field: a pointer
// field, a sql.Scanner (which receives NULL itself), or the softdelete
// column's NULL↔zero-time exception.
Nullable bool
// PrimaryKey marks the primary-key columns.
PrimaryKey bool
// JSON marks columns stored as serialized JSON text.
JSON bool
// Scanner marks columns delegated to the type's own sql.Scanner; their
// database representation is undecidable to schema tooling.
Scanner bool
}
ColumnSchema is one mapped column of a model, as rio's plan sees it — the expectation side of a schema comparison.
type Cursor ¶ added in v0.12.0
type Cursor struct {
// contains filtered or unexported fields
}
Cursor marks a position in a keyset-ordered result: the sort-key values of the row it points past, plus a fingerprint of the ordering that issued it. The zero Cursor is "no position" and After rejects it. Tokens are opaque but not tamper-proof: values bind as parameters, so a forged token can move the page window, never change the query.
func ParseCursor ¶ added in v0.12.0
ParseCursor decodes a token produced by String. Malformed input fails here; a token for a different ordering fails at After's fingerprint check.
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. Panics on a nil db or dialect, and on WithStmtCache with a dialect that cannot prepare statements (ClickHouse).
func NewNative ¶ added in v0.8.0
func NewNative(nc NativeConfig, dialect Dialect, opts ...Option) *DB
NewNative constructs a *DB on a driver-native execution channel. Driver-module SPI: applications construct through the driver module (postgres.OpenNative). Close on the returned DB closes the SQLView first, then the channel. Panics if NativeConfig.DB or dialect is nil, or if opts include WithStmtCache — statement caching belongs to the native driver.
func (*DB) Close ¶
Close closes the prepared-statement cache (if enabled) and the underlying *sql.DB.
func (*DB) DescribeModel ¶ added in v0.12.0
func (d *DB) DescribeModel(model any) (*TableSchema, error)
DescribeModel reports how this handle maps model: its resolved table name and column expectations. The model must be a mappable struct (or pointer to one); relations and countof targets are not columns and do not appear.
func (*DB) Dialect ¶ added in v0.13.0
Dialect returns this handle's dialect identity — one of rio.Postgres, rio.MySQL, rio.SQLite, or rio.ClickHouse — a comparable value for dispatch.
func (*DB) DriverHandle ¶ added in v0.12.0
DriverHandle returns the driver-owned handle attached through WithDriverHandle or NativeConfig.Handle, or nil when none was attached.
func (*DB) Native ¶ added in v0.8.0
Native returns NativeConfig.Handle on the native channel and nil on the database/sql channel.
func (*DB) Tx ¶
Tx runs fn in a transaction with default options.
Example ¶
package main
import (
"context"
"log"
"time"
"github.com/go-rio/rio"
)
// db stands for a handle opened by a driver module, for example
// sqlite.Open("file:app.db"). The core has no driver, so these examples
// compile but do not run.
var db *rio.DB
// User is a model: ID is the auto-increment primary key by convention,
// CreatedAt is maintained on insert, PostCount is the WithCount("Posts")
// target, and Posts loads only through With.
type User struct {
ID int64
Email string
Age int
Active bool
LoginCount int64
PostCount int64 `rio:",countof:Posts"`
CreatedAt time.Time
Posts rio.HasMany[Post]
}
// Post belongs to a User through the conventional user_id foreign key.
type Post struct {
ID int64
UserID int64
Title string
Published bool
Score int64
CreatedAt time.Time
}
func main() {
ctx := context.Background()
err := db.Tx(ctx, func(tx *rio.Tx) error {
users, err := rio.From[User]().
Where("active").
ForUpdate(rio.SkipLocked).
Limit(10).
All(ctx, tx)
if err != nil {
return err // rolls back
}
for i := range users {
users[i].Age++
if err := rio.Update(ctx, tx, &users[i], "age"); err != nil {
return err
}
}
return nil // commits
})
if err != nil {
log.Fatal(err)
}
}
Output:
func (*DB) TxWith ¶
TxWith runs fn in a transaction with the given options (isolation level, read-only).
func (*DB) Unwrap ¶
Unwrap returns the underlying *sql.DB. On the native channel it is the driver module's database/sql view over the same pool (NativeConfig.SQLView), or nil when none was supplied; never tune pooling on that view.
func (*DB) WithoutStamps ¶ added in v0.18.0
WithoutStamps returns a handle that generates no CreatedAt or UpdatedAt value. Statements carrying the caller's row (Insert, InsertAll, full-column Update, both Upsert branches) bind the struct's values as they are; the ones rio composes itself (column-list Update, UpdateAll, Delete, Restore) drop the assignment. Versions and softdelete stamps are unaffected. The handle shares its parent's pool and caches; its transactions inherit it.
type Dialect ¶
type Dialect interface {
// contains filtered or unexported methods
}
Dialect identifies one of the built-in SQL dialects. All methods are unexported: driver modules pick a built-in value, never implement one.
var ( // Postgres renders $n placeholders, RETURNING, and ON CONFLICT (columns), // and binds a typed key slice as one array parameter. Postgres Dialect = postgresDialect{} // MySQL renders ? placeholders and ON DUPLICATE KEY UPDATE; it has no // RETURNING and no conflict target. MySQL Dialect = mysqlDialect{} // SQLite renders ? placeholders, RETURNING, and ON CONFLICT (columns); // ForUpdate is elided because the whole database locks, times bind as // sqliteTimeFormat text, and statements chunk under 999 parameters. SQLite Dialect = sqliteDialect{} // ClickHouse is append-only OLAP: no transactions, row locks, unique keys, // or generated keys, and every argument interpolates client-side; see // clickhouseDialect.caps for the exact surface. ClickHouse Dialect = clickhouseDialect{} )
Built-in dialects: driver modules select one, and New and NewNative take it.
type Expr ¶
type Expr string
Expr is a verbatim UpdateAll value for database-side expressions; never construct it from untrusted 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. "Not loaded" and "loaded, empty" are distinct states: rio never lazy-loads and never returns silently empty data.
func (HasMany[T]) MarshalJSON ¶
MarshalJSON encodes unloaded relations as null and loaded ones as arrays.
func (HasMany[T]) Rows ¶
func (r HasMany[T]) Rows() []T
Rows returns the loaded children, panicking if the relation was never loaded.
func (*HasMany[T]) Set ¶
func (r *HasMany[T]) Set(rows []T)
Set marks the relation loaded with the given rows.
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 LockOption ¶ added in v0.16.0
type LockOption uint8
LockOption refines a row lock: what happens when a row is already locked.
const ( // NoWait fails immediately instead of waiting for a locked row. NoWait LockOption = iota + 1 // SkipLocked leaves out rows another transaction holds locked. SkipLocked )
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 NativeBatchResults ¶ added in v0.13.0
type NativeBatchResults interface {
Rows() (rows NativeRows, done bool, err error)
Close() error
}
NativeBatchResults yields each batched statement's rows in submission order. Rows returns the next statement's result — its NativeRows must be fully consumed and closed before the next call — and reports done when every statement's result has been handed out. Close releases the batch and surfaces any deferred protocol error; it must be called once, after consumption stops (early on failure is fine).
type NativeBatcher ¶ added in v0.13.0
type NativeBatcher interface {
QueryBatch(ctx context.Context, stmts []BatchStatement) (NativeBatchResults, error)
}
NativeBatcher is an optional capability of a NativeDB or NativeTx: executing a group of independent row-returning statements in one driver round trip. Implementations queue every statement and flush once; results are consumed strictly in submission order.
type NativeCell ¶ added in v0.8.0
type NativeCell interface {
sql.Scanner // fallback: accepts driver-canonical values
ScanKind() NativeScanKind
SetInt64(int64) error
SetUint64(uint64) error
SetFloat64(float64) error
SetBool(bool) error
SetString(string) error
SetBytes([]byte) error
SetTime(time.Time) error
SetNull() error
// contains filtered or unexported methods
}
NativeCell is the typed sink a NativeRows implementation feeds decoded column values into, one cell per column. Sealed: drivers consume it, never implement it, so rio may add Set methods in a minor version without breaking a driver.
Every Set method is exactly Scan with the interface boxing removed — SetInt64(v) behaves like Scan(int64(v)), SetNull like Scan(nil) — same conversion, overflow, and NULL rules, same error shapes, mismatched-kind fallback included. SetBytes never retains its argument. SetString stores its argument as-is, so hand over an owned string, never an unsafe view of driver memory. ScanKind reports the cell's strategy; pointer fields report their element's kind, and SetNull stores nil.
type NativeConfig ¶ added in v0.8.0
type NativeConfig struct {
// DB is the native execution channel. Required.
DB NativeDB
// Handle is the driver-native pool handle, returned by
// (*DB).DriverHandle and (*DB).Native.
Handle any
// SQLView is an optional database/sql view over the same pool, returned
// by (*DB).Unwrap (nil without one). (*DB).Close closes it before DB.
SQLView *sql.DB
}
NativeConfig carries what a driver module hands NewNative, all wired to the same underlying pool.
type NativeCopier ¶ added in v0.13.0
type NativeCopier interface {
CopyIn(ctx context.Context, table []string, columns []string, next func() ([]any, error)) (int64, error)
}
NativeCopier is an optional capability of a NativeDB or NativeTx: bulk-loading rows through the driver's streaming copy protocol (PostgreSQL COPY FROM). table is the resolved, unquoted table name split into schema segments ([]string{"app", "users"}); the driver quotes each segment. next returns one row's bind values in columns order, (nil, nil) when the batch is exhausted, or a non-nil error to abort the copy; the returned slice is valid only until the next call, so encode it before pulling the next row.
type NativeDB ¶ added in v0.8.0
type NativeDB interface {
Query(ctx context.Context, sql string, args []any) (NativeRows, error)
Exec(ctx context.Context, sql string, args []any) (rowsAffected int64, err error)
Begin(ctx context.Context, opts *sql.TxOptions) (NativeTx, error)
Close() error
}
NativeDB is a driver-native execution channel: what rio needs from a driver pool. SQL arrives fully rendered in the dialect's placeholder form; args are the bind values rio would hand database/sql. Exec returns the driver's affected-row count. Begin maps *sql.TxOptions (possibly nil) onto the driver's transaction options. Close releases the channel's resources.
type NativeLastInserter ¶ added in v0.17.0
type NativeLastInserter interface {
ExecLastInsert(ctx context.Context, sql string, args []any) (rowsAffected, lastInsertID int64, err error)
}
NativeLastInserter is an optional capability of a NativeDB or NativeTx: Exec that also reports the last inserted row id, which the SQLite dialect uses to backfill a lone auto-increment key. rio prefers it over Exec.
type NativeRows ¶ added in v0.8.0
type NativeRows interface {
Columns() []string
Next() bool
Scan(dest ...any) error
Err() error
Close()
}
NativeRows is a driver-native result set. Close returns nothing; errors — including those Close itself discovers — converge in Err, which rio reads after Close. rio passes the same dest slots, in the same order, for every row of one result; each slot is either a NativeCell or a plain pointer (scan it as the driver natively would), so implementations may classify the dest list on the first Scan and reuse it.
type NativeScanKind ¶ added in v0.8.0
type NativeScanKind uint8
NativeScanKind names the plan-time scan strategy of one NativeCell, so a NativeRows implementation can pick a typed decode path per column. The enum can grow: treat any unrecognized kind as NativeKindScanner — the fallback is correct for every kind, only slower.
const ( // NativeKindScanner is the fallback and zero value: pass the cell // itself to the driver's sql.Scanner path. NativeKindScanner NativeScanKind = iota // NativeKindInt marks an integer field; SetInt64 is its direct path. NativeKindInt // NativeKindUint marks an unsigned integer field; SetUint64 is its direct path. NativeKindUint // NativeKindFloat marks a float field; SetFloat64 is its direct path. NativeKindFloat // NativeKindBool marks a bool field; SetBool is its direct path. NativeKindBool // NativeKindString marks a string field; SetString is its direct path. NativeKindString // NativeKindBytes marks a []byte field; SetBytes is its direct path. NativeKindBytes // NativeKindTime marks a time.Time field; SetTime is its direct path. NativeKindTime // NativeKindJSON takes the column's raw JSON payload through SetBytes or // SetString, not a value decoded driver-side. NativeKindJSON )
type NativeTx ¶ added in v0.8.0
type NativeTx interface {
Query(ctx context.Context, sql string, args []any) (NativeRows, error)
Exec(ctx context.Context, sql string, args []any) (int64, error)
Commit(ctx context.Context) error
Rollback(ctx context.Context) error
}
NativeTx is one driver-native transaction. Once the transaction has ended — committed, rolled back, or destroyed by the driver on its own — Commit and Rollback must return an error satisfying errors.Is(err, sql.ErrTxDone), translating the driver's own sentinel where needed.
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; nil is ignored. Intended for tests.
func WithDriverHandle ¶ added in v0.12.0
WithDriverHandle attaches a driver-owned handle to the DB, retrievable through DB.DriverHandle. rio never touches the value; it exists for driver modules' typed accessors.
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 a read-only hook for executed statements and transaction control; a nil hook is ignored.
func WithStmtCache ¶
WithStmtCache enables bounded prepared-statement caches; the DB and each transaction own separate caches. The sqlite and mysql modules enable it by default; WithoutStmtCache opts out for transaction/statement-mode poolers. Schema-change errors evict entries and are not retried. New panics if used with ClickHouse, which cannot prepare general queries.
func WithTableNamer ¶
WithTableNamer overrides conventional table names for this handle; a model's TableName method still takes precedence. The function must be a pure, stable mapping — rendered SQL is cached per handle — so for dynamic tenancy construct one *DB per naming universe.
func WithoutArgs ¶
func WithoutArgs() Option
WithoutArgs redacts bind arguments from QueryEvent before hooks see them.
func WithoutStmtCache ¶ added in v0.16.0
func WithoutStmtCache() Option
WithoutStmtCache disables the prepared-statement caches, overriding a driver module's default.
type Query ¶
type Query[T any] struct { // contains filtered or unexported fields }
Query is an immutable, connection-free query description safe for concurrent reuse; builder methods return derived values.
func From ¶
From starts a query for T's table.
Example ¶
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/go-rio/rio"
)
// db stands for a handle opened by a driver module, for example
// sqlite.Open("file:app.db"). The core has no driver, so these examples
// compile but do not run.
var db *rio.DB
// User is a model: ID is the auto-increment primary key by convention,
// CreatedAt is maintained on insert, PostCount is the WithCount("Posts")
// target, and Posts loads only through With.
type User struct {
ID int64
Email string
Age int
Active bool
LoginCount int64
PostCount int64 `rio:",countof:Posts"`
CreatedAt time.Time
Posts rio.HasMany[Post]
}
// Post belongs to a User through the conventional user_id foreign key.
type Post struct {
ID int64
UserID int64
Title string
Published bool
Score int64
CreatedAt time.Time
}
func main() {
ctx := context.Background()
users, err := rio.From[User]().
Where("age >= ?", 18).
Where("active").
OrderBy("created_at DESC").
Limit(10).
All(ctx, db)
if err != nil {
log.Fatal(err)
}
for _, u := range users {
fmt.Println(u.Email)
}
}
Output:
func (Query[T]) After ¶ added in v0.12.0
After resumes past the position c marks: rows strictly after it in the OrderKeys ordering. c must come from CursorAt under the same OrderKeys; a different ordering fails loudly.
func (Query[T]) All ¶
All runs the query and returns every matching row. args fill deferred placeholders from Where and Having fragments in final SQL order.
func (Query[T]) AllRows ¶
AllRows is the explicit opt-in for UpdateAll/DeleteAll without conditions.
func (Query[T]) Avg ¶ added in v0.16.0
func (q Query[T]) Avg[V any](ctx context.Context, db Queryer, column string, args ...any) (V, error)
Avg averages a mapped column under the query's conditions; over no rows it returns V's zero value.
func (Query[T]) Before ¶ added in v0.16.0
Before selects the page ending at the position c marks: the rows strictly before it, still in OrderKeys order. It runs the reversed query and turns the page around, so Rows cannot stream it.
func (Query[T]) Chunk ¶ added in v0.16.0
func (q Query[T]) Chunk(ctx context.Context, db Queryer, size int, args ...any) iter.Seq2[[]T, error]
Chunk yields the matching rows in keyset pages of size rows: one bounded query per page, the connection released between pages, With and WithCount applied per page. Pages follow OrderKeys, defaulting to the primary key; Limit, Offset, After, and Before are refused.
Example ¶
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/go-rio/rio"
)
// db stands for a handle opened by a driver module, for example
// sqlite.Open("file:app.db"). The core has no driver, so these examples
// compile but do not run.
var db *rio.DB
// Post belongs to a User through the conventional user_id foreign key.
type Post struct {
ID int64
UserID int64
Title string
Published bool
Score int64
CreatedAt time.Time
}
func main() {
ctx := context.Background()
// One bounded query per page, in primary-key order, the connection
// released between pages.
for posts, err := range rio.From[Post]().Where("published = ?", true).Chunk(ctx, db, 500) {
if err != nil {
log.Fatal(err)
}
fmt.Println(len(posts))
}
}
Output:
func (Query[T]) Count ¶
Count returns the number of matching rows. GroupBy, Having, Limit, and Offset are rejected; use Raw for those queries.
func (Query[T]) CreateOrFirst ¶
CreateOrFirst inserts row or returns the existing match after a unique-key conflict.
func (Query[T]) CursorAt ¶ added in v0.16.0
CursorAt issues the cursor marking row's position under the query's OrderKeys: After the last row of a page for the next page, Before the first row for the previous one. The row must hold the values the database stores (any row rio scanned does).
func (Query[T]) DeleteAll ¶
DeleteAll deletes matching rows, using soft deletion when configured. It requires conditions or AllRows.
func (Query[T]) DeleteAllReturning ¶ added in v0.16.0
DeleteAllReturning is DeleteAll returning the deleted rows, as stored after a soft delete. Dialects without RETURNING (MySQL) reject it.
func (Query[T]) Distinct ¶ added in v0.16.0
Distinct renders SELECT DISTINCT: entity rows deduplicate across joins, Pluck values deduplicate, Count counts distinct primary keys, and Sum and Avg aggregate distinct values.
func (Query[T]) Final ¶ added in v0.7.0
Final applies ClickHouse's FINAL modifier to the main SELECT. It does not affect preloads, WithCount, or WhereHas subqueries. Other dialects reject it.
func (Query[T]) Find ¶ added in v0.16.0
Find fetches a row by primary key under the query's clauses: WithTrashed, With, WithCount, and inline Where all apply, and Must caches the shape. Composite key parts follow struct-field declaration order; the package Find is the plain cached lookup.
func (Query[T]) First ¶
First returns the first matching row or ErrNotFound. It adds LIMIT 1 only when no limit was set and never adds an order.
func (Query[T]) FirstOrCreate ¶
FirstOrCreate returns the first match or inserts row. If a concurrent insert wins, it re-reads after ErrDuplicateKey; if that still misses, it returns the duplicate-key error, which may identify a hidden soft-deleted row.
func (Query[T]) ForShare ¶ added in v0.16.0
func (q Query[T]) ForShare(opts ...LockOption) Query[T]
ForShare renders SELECT ... FOR SHARE, with at most one LockOption. A no-op on SQLite; rejected on ClickHouse.
func (Query[T]) ForUpdate ¶
func (q Query[T]) ForUpdate(opts ...LockOption) Query[T]
ForUpdate renders SELECT ... FOR UPDATE, with at most one LockOption. A no-op on SQLite; rejected on ClickHouse.
func (Query[T]) ForceDeleteAll ¶
ForceDeleteAll permanently deletes matching rows. It requires conditions or AllRows, including on soft-delete models.
func (Query[T]) GroupBy ¶
GroupBy appends a verbatim GROUP BY term; never build it from untrusted input.
func (Query[T]) Having ¶
Having adds an AND-ed HAVING condition. The expression is verbatim — never build it from untrusted input.
func (Query[T]) Join ¶
Join appends a verbatim JOIN clause; entity queries still select only T's columns. Never build the clause from untrusted input.
func (Query[T]) Max ¶ added in v0.16.0
func (q Query[T]) Max[V any](ctx context.Context, db Queryer, column string, args ...any) (V, error)
Max returns the largest value of a mapped column under the query's conditions; over no rows it returns V's zero value.
func (Query[T]) Min ¶ added in v0.16.0
func (q Query[T]) Min[V any](ctx context.Context, db Queryer, column string, args ...any) (V, error)
Min returns the smallest value of a mapped column under the query's conditions; over no rows it returns V's zero value.
func (Query[T]) Must ¶ added in v0.10.0
Must panics if Validate fails. The returned query carries a private render cache keyed per executing handle; a handle's entries are reclaimed with it.
Example ¶
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/go-rio/rio"
)
// db stands for a handle opened by a driver module, for example
// sqlite.Open("file:app.db"). The core has no driver, so these examples
// compile but do not run.
var db *rio.DB
// User is a model: ID is the auto-increment primary key by convention,
// CreatedAt is maintained on insert, PostCount is the WithCount("Posts")
// target, and Posts loads only through With.
type User struct {
ID int64
Email string
Age int
Active bool
LoginCount int64
PostCount int64 `rio:",countof:Posts"`
CreatedAt time.Time
Posts rio.HasMany[Post]
}
// Post belongs to a User through the conventional user_id foreign key.
type Post struct {
ID int64
UserID int64
Title string
Published bool
Score int64
CreatedAt time.Time
}
// adults is a package-level template: validated once by Must, reused
// concurrently, and executed with its deferred argument per call.
var adults = rio.From[User]().
Where("age >= ?").
OrderBy("created_at DESC").
Limit(10).
Must()
func main() {
ctx := context.Background()
users, err := adults.All(ctx, db, 18)
if err != nil {
log.Fatal(err)
}
emails, err := adults.Pluck[string](ctx, db, "email", 21)
if err != nil {
log.Fatal(err)
}
fmt.Println(len(users), len(emails))
}
Output:
func (Query[T]) OnlyTrashed ¶
OnlyTrashed selects only soft-deleted rows.
func (Query[T]) OrderBy ¶
OrderBy appends an ORDER BY term, verbatim SQL ("created_at DESC"); never build it from untrusted input.
func (Query[T]) OrderKeys ¶ added in v0.12.0
OrderKeys sets the structured ordering cursor pagination requires, rendered as the query's ORDER BY; it cannot mix with verbatim OrderBy. Primary-key columns missing from keys are appended as tie-breakers, following the last declared direction.
Example ¶
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/go-rio/rio"
)
// db stands for a handle opened by a driver module, for example
// sqlite.Open("file:app.db"). The core has no driver, so these examples
// compile but do not run.
var db *rio.DB
// Post belongs to a User through the conventional user_id foreign key.
type Post struct {
ID int64
UserID int64
Title string
Published bool
Score int64
CreatedAt time.Time
}
func main() {
ctx := context.Background()
q := rio.From[Post]().
Where("published = ?", true).
OrderKeys(
rio.SortKey{Column: "score", Desc: true},
rio.SortKey{Column: "created_at"},
) // "id" is appended as the tie-breaker
page, err := q.Limit(20).All(ctx, db)
if err != nil {
log.Fatal(err)
}
if len(page) == 0 {
return
}
// Next page: the cursor at the last row.
last, err := q.CursorAt(&page[len(page)-1])
if err != nil {
log.Fatal(err)
}
next, err := q.After(last).Limit(20).All(ctx, db)
if err != nil {
log.Fatal(err)
}
// Previous page: the cursor at the first row; Before reads backwards
// and turns the page around, so it arrives in OrderKeys order.
first, err := q.CursorAt(&page[0])
if err != nil {
log.Fatal(err)
}
prev, err := q.Before(first).Limit(20).All(ctx, db)
if err != nil {
log.Fatal(err)
}
// Tokens round-trip through URL-safe strings.
token := last.String()
parsed, err := rio.ParseCursor(token)
if err != nil {
log.Fatal(err)
}
fmt.Println(len(next), len(prev), parsed.IsZero())
}
Output:
func (Query[T]) Pluck ¶ added in v0.10.0
func (q Query[T]) Pluck[V any](ctx context.Context, db Queryer, column string, args ...any) ([]V, error)
Pluck extracts a single column under the query's conditions. The column must be one of T's mapped columns — expressions go through Raw.
func (Query[T]) RestoreAll ¶ added in v0.4.0
RestoreAll restores matching soft-deleted rows. It requires conditions or AllRows.
func (Query[T]) Rows ¶ added in v0.2.0
Rows streams results and closes them on completion or early break. It yields a zero T with the first error. With and WithCount cannot be streamed.
func (Query[T]) SQL ¶ added in v0.16.0
SQL renders the statement All would run on db, with its bound arguments, without executing it.
func (Query[T]) Sole ¶
Sole returns the only matching row, ErrNotFound for none, or ErrMultipleRows for more than one. It adds LIMIT 2 only when no limit is set.
func (Query[T]) Sub ¶ added in v0.16.0
Sub embeds the query as a ? argument projecting one mapped column, for IN (?), EXISTS (?), and scalar comparisons; the caller writes the parentheses. The query's own Where arguments must be inline.
Example ¶
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/go-rio/rio"
)
// db stands for a handle opened by a driver module, for example
// sqlite.Open("file:app.db"). The core has no driver, so these examples
// compile but do not run.
var db *rio.DB
// User is a model: ID is the auto-increment primary key by convention,
// CreatedAt is maintained on insert, PostCount is the WithCount("Posts")
// target, and Posts loads only through With.
type User struct {
ID int64
Email string
Age int
Active bool
LoginCount int64
PostCount int64 `rio:",countof:Posts"`
CreatedAt time.Time
Posts rio.HasMany[Post]
}
// Post belongs to a User through the conventional user_id foreign key.
type Post struct {
ID int64
UserID int64
Title string
Published bool
Score int64
CreatedAt time.Time
}
func main() {
ctx := context.Background()
// The subquery renders in place of the ? with its own arguments spliced
// in; the caller writes the parentheses.
authors := rio.From[Post]().Where("published = ?", true).Sub("user_id")
users, err := rio.From[User]().Where("id IN (?)", authors).All(ctx, db)
if err != nil {
log.Fatal(err)
}
fmt.Println(len(users))
}
Output:
func (Query[T]) Sum ¶ added in v0.16.0
func (q Query[T]) Sum[V any](ctx context.Context, db Queryer, column string, args ...any) (V, error)
Sum totals a mapped column under the query's conditions; over no rows it returns V's zero value (use sql.Null[V] to tell the two apart).
func (Query[T]) UpdateAll ¶
UpdateAll updates matching rows and returns the affected count. It requires conditions or AllRows. UpdatedAt is maintained unless explicitly assigned; set-based writes do not use optimistic locking.
func (Query[T]) UpdateAllReturning ¶ added in v0.16.0
func (q Query[T]) UpdateAllReturning(ctx context.Context, db Queryer, set Set, args ...any) ([]T, error)
UpdateAllReturning is UpdateAll returning the updated rows. Dialects without RETURNING (MySQL) reject it.
Example ¶
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/go-rio/rio"
)
// db stands for a handle opened by a driver module, for example
// sqlite.Open("file:app.db"). The core has no driver, so these examples
// compile but do not run.
var db *rio.DB
// User is a model: ID is the auto-increment primary key by convention,
// CreatedAt is maintained on insert, PostCount is the WithCount("Posts")
// target, and Posts loads only through With.
type User struct {
ID int64
Email string
Age int
Active bool
LoginCount int64
PostCount int64 `rio:",countof:Posts"`
CreatedAt time.Time
Posts rio.HasMany[Post]
}
// Post belongs to a User through the conventional user_id foreign key.
type Post struct {
ID int64
UserID int64
Title string
Published bool
Score int64
CreatedAt time.Time
}
func main() {
ctx := context.Background()
// Set-based writes need a condition (or AllRows); the returning form
// hands the affected rows back on dialects with RETURNING.
deactivated, err := rio.From[User]().
Where("age < ?", 18).
UpdateAllReturning(ctx, db, rio.Set{"active": false})
if err != nil {
log.Fatal(err)
}
for _, u := range deactivated {
fmt.Println(u.Email, u.Active)
}
}
Output:
func (Query[T]) Validate ¶ added in v0.10.0
Validate checks q without accessing a database. Deferred Where and Having arguments are checked by the terminal method under its dialect.
func (Query[T]) Where ¶
Where adds an AND-ed condition in SQL with ? placeholders; slice arguments expand inside IN (?). The expression is verbatim — never build it from untrusted input.
func (Query[T]) WhereHas ¶ added in v0.2.0
WhereHas keeps rows whose relation path has a matching row. Nested paths nest EXISTS predicates, and RelWithTrashed applies to the leaf relation.
func (Query[T]) WhereHasNot ¶ added in v0.2.0
WhereHasNot keeps rows whose relation path has no matching row.
func (Query[T]) With ¶
With preloads a relation with a separate query. Dot-separated paths preload nested relations; options apply to the leaf.
Example ¶
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/go-rio/rio"
)
// db stands for a handle opened by a driver module, for example
// sqlite.Open("file:app.db"). The core has no driver, so these examples
// compile but do not run.
var db *rio.DB
// User is a model: ID is the auto-increment primary key by convention,
// CreatedAt is maintained on insert, PostCount is the WithCount("Posts")
// target, and Posts loads only through With.
type User struct {
ID int64
Email string
Age int
Active bool
LoginCount int64
PostCount int64 `rio:",countof:Posts"`
CreatedAt time.Time
Posts rio.HasMany[Post]
}
// Post belongs to a User through the conventional user_id foreign key.
type Post struct {
ID int64
UserID int64
Title string
Published bool
Score int64
CreatedAt time.Time
}
func main() {
ctx := context.Background()
users, err := rio.From[User]().
With("Posts",
rio.RelWhere("published = ?", true),
rio.RelOrderBy("created_at DESC"),
rio.RelLimit(3)).
WithCount("Posts", rio.RelWhere("published = ?", true)).
All(ctx, db)
if err != nil {
log.Fatal(err)
}
for _, u := range users {
// Rows would panic had Posts not been loaded with With.
fmt.Println(u.Email, u.PostCount, len(u.Posts.Rows()))
}
}
Output:
func (Query[T]) WithCount ¶ added in v0.2.0
WithCount fills the tagged int64 count target for a HasMany or ManyToMany relation using one GROUP BY query. RelWhere and RelWithTrashed narrow what is counted; a filtered count never reuses a preloaded relation.
func (Query[T]) WithTrashed ¶
WithTrashed includes soft-deleted rows.
type QueryEvent ¶
type QueryEvent struct {
// Op is a stable statement label: "select", "insert", "update",
// "delete", "upsert", "copy", "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; a write whose
// Result.RowsAffected fails carries that failure here. After only.
Err error
// Duration is the execution wall time; for row-returning queries it runs
// through row consumption. After only.
Duration time.Duration
// RowsAffected is the driver-reported count for writes, -1 when unknown
// (row-returning queries, or a report failure carried in Err). After only.
RowsAffected int64
// RowsReturned is how many rows the statement handed back, -1 for
// statements that return none. Count and Exists report their result-set
// rows (one), not the value they carry. After only.
RowsReturned int64
// Phase labels statements rio itself derives — "preload" and "count"
// for With/WithCount relation queries, "probe" for internal write
// probes; "" for everything else.
Phase string
}
QueryEvent describes one statement execution. Hooks receive the same event pointer in Before and After; After sees Err, Duration, RowsAffected, and RowsReturned 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. The context BeforeQuery returns is the execution context: the statement and its row consumption run under it, and AfterQuery receives it; returning nil leaves the incoming context in force. Hooks must not retain the event past the call and cannot alter the statement.
For row-returning queries AfterQuery fires once the rows are consumed, so Err includes scan and iteration failures. Exception: a First/Find/Sole miss reports Err = nil — ErrNotFound is a successfully executed query. Batched or streamed native execution still fires events per logical statement: every BeforeQuery runs before the one wire exchange, contexts chaining into the single execution context, and a mid-batch failure reports the remaining statements' AfterQuery with the same error.
The method set is fixed: new hook capabilities arrive as optional interfaces discovered by type assertion, never as methods added here.
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 the execution target every rio entry point accepts: a *DB or a *Tx.
type RawQuery ¶
type RawQuery[T any] struct { // contains filtered or unexported fields }
RawQuery is hand-written SQL through the shared pipeline, scanning into any target shape — DTO structs, scalars, entities. It is a connection-free value; placeholders are ? with IN (?) expansion.
func Raw ¶
Raw builds a raw query. Struct scanning matches by column name and errors on result columns with no matching field. Scanning half an entity and then calling Update writes zero values to the unselected columns — project into DTOs. The SQL is verbatim; never build it from untrusted input.
func (RawQuery[T]) First ¶
First returns the first row or ErrNotFound. rio does not append LIMIT to hand-written SQL; add your own when it matters.
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. Order within each parent follows RelOrderBy, defaulting to the target's primary key. Requires window functions (PostgreSQL, MySQL 8+, SQLite 3.25+). RelLimit(0) loads no children, not all of them.
func RelOrderBy ¶ added in v0.16.0
RelOrderBy orders the preloaded rows before they are grouped per parent. The term is included verbatim; never build it from untrusted input.
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. The expression is included verbatim; never build it from untrusted input.
func RelWithTrashed ¶
func RelWithTrashed() RelOption
RelWithTrashed includes soft-deleted rows in the preload when the target model declares a softdelete column.
type Set ¶
Set maps database column names to UpdateAll values. Expr values are inserted verbatim; never construct them from untrusted input.
type SortKey ¶ added in v0.12.0
SortKey is one column of a structured ordering. It names a mapped column — not verbatim SQL — so rio can read its value back out of rows to issue cursors.
type Subquery ¶ added in v0.16.0
type Subquery struct {
// contains filtered or unexported fields
}
Subquery is a query embedded as a ? argument: it renders in place of the placeholder, its own arguments spliced into the statement's, with the caller's parentheses around it. Query.Sub builds one.
type TableNamer ¶
type TableNamer interface {
TableName() string
}
TableNamer overrides the convention-derived table name for one model.
type TableSchema ¶ added in v0.12.0
type TableSchema struct {
// Struct is the model's Go type name.
Struct string
// Table is the table name under this handle's naming.
Table string
// Columns are the mapped columns in plan order; the primary key is the
// PrimaryKey subset in order.
Columns []ColumnSchema
}
TableSchema is a model's mapping under one handle: the resolved table name (TableName override, then WithTableNamer, then convention) and every mapped column in plan order. Read-only.
type Tx ¶
type Tx struct {
// contains filtered or unexported fields
}
Tx is a transaction handle. Like *sql.Tx it is bound to one connection and must not be used concurrently.
func (*Tx) NativeTx ¶ added in v0.8.0
NativeTx returns the NativeTx SPI adapter this transaction runs on, or nil on the database/sql channel.
func (*Tx) Tx ¶
Tx runs fn inside a savepoint: released when fn returns nil, rolled back when fn returns an error or panics, leaving the outer transaction usable.
func (*Tx) WithoutStamps ¶ added in v0.18.0
WithoutStamps returns a view of this transaction that leaves CreatedAt and UpdatedAt to the caller; see DB.WithoutStamps.
type UpsertOption ¶
type UpsertOption func(*upsertSpec)
UpsertOption shapes the conflict clause.
func DoNothing ¶
func DoNothing() UpsertOption
DoNothing turns conflicts into no-ops without suppressing unrelated errors. It remains compatible with MariaDB and MySQL before 8.0.19.
func DoUpdate ¶
func DoUpdate(cols ...string) UpsertOption
DoUpdate selects columns to overwrite on conflict. Without columns it uses every eligible column; explicit columns are deduplicated in model order.
func DoUpdateSet ¶ added in v0.16.0
func DoUpdateSet(set Set) UpsertOption
DoUpdateSet assigns columns on conflict: an Expr renders verbatim (the incoming row is "excluded" on PostgreSQL and SQLite, "_rio_new" on MySQL), any other value binds. Calls merge; columns rio maintains, readonly columns, and columns also named in DoUpdate are rejected.
func KeepTrashed ¶
func KeepTrashed() UpsertOption
KeepTrashed preserves deleted_at on both insert and conflict-update paths.
func OnConflict ¶
func OnConflict(cols ...string) UpsertOption
OnConflict names the unique-index columns. DoUpdate requires it on PostgreSQL and SQLite; MySQL reacts to any unique index.
Source Files
¶
- aggregate.go
- batch.go
- columns.go
- cursor.go
- describe.go
- dialect.go
- dialect_clickhouse.go
- doc.go
- engine.go
- errors.go
- hooks.go
- inflect.go
- model.go
- native.go
- options.go
- preload.go
- query.go
- query_bind.go
- query_cache.go
- query_validate.go
- raw.go
- rebind.go
- relations.go
- relwrite.go
- rio.go
- scan.go
- setops.go
- stmtcache.go
- upsert.go
- write.go
Directories
¶
| Path | Synopsis |
|---|---|
|
Package lint compares rio model expectations against a live database schema and reports the drift: missing tables/columns, nullability and primary-key disagreements, and type mismatches the dialect's equivalence classes can rule on.
|
Package lint compares rio model expectations against a live database schema and reports the drift: missing tables/columns, nullability and primary-key disagreements, and type mismatches the dialect's equivalence classes can rule on. |