Documentation
¶
Overview ¶
Package gormx provides GORM-based table helpers with pagination support.
Index ¶
- Constants
- func Expr(expr string, args ...any) clause.Expr
- func Inject(db *gorm.DB, name ...string) host.Module
- func IsCheckConstraintViolation(err error) bool
- func IsDuplicateKey(err error) bool
- func IsForeignKeyViolation(err error) bool
- func IsRecordNotFound(err error) bool
- func Lookup(app *host.App, name ...string) (*gorm.DB, bool)
- func Module(driver Driver, name ...string) host.Module
- func NewGormLogger(sl *slog.Logger, levelOverride string) gormLogger.Interface
- func Of(app *host.App, name ...string) *gorm.DB
- func UseTracing(db *gorm.DB) error
- type BaseListRequest
- type BaseRepository
- type Config
- type CursorScoper
- type DB
- type Driver
- type ListRequest
- type OffsetPager
- type OrderByColumn
- type PageOptions
- type PageRequest
- func (r *PageRequest[TEntity, TValue]) CreateNextPageToken(items []TEntity) (*string, error)
- func (r *PageRequest[TEntity, TValue]) CurrentPageData() ([]any, error)
- func (r *PageRequest[TEntity, TValue]) Offset() (offset int, ok bool)
- func (r *PageRequest[TEntity, TValue]) OrderBy() string
- func (r *PageRequest[TEntity, TValue]) OrderDesc() *PageRequest[TEntity, TValue]
- func (r *PageRequest[TEntity, TValue]) PageSize() int
- type Scoper
- type Service
- func (s *Service) Close() error
- func (s *Service) CloseOrder() int
- func (s *Service) DB() *gorm.DB
- func (s *Service) Healthy(ctx context.Context) error
- func (s *Service) Init() error
- func (s *Service) ReadConfig(l configx.Reader) error
- func (s *Service) SetClock(c core.TimeProvider)
- func (s *Service) SetLogger(l *slog.Logger)
- type Table
- func (t *Table[TEntity]) Aggregate(ctx context.Context, expr string, dest any, where ...any) error
- func (t *Table[TEntity]) Avg(ctx context.Context, column string, dest any, where ...any) error
- func (t *Table[TEntity]) Count(ctx context.Context, where ...any) (int64, error)
- func (t *Table[TEntity]) Create(ctx context.Context, item *TEntity) error
- func (t *Table[TEntity]) CreateMany(ctx context.Context, items []*TEntity, batchSize int) (int64, error)
- func (t *Table[TEntity]) Delete(ctx context.Context, conditions ...any) (int64, error)
- func (t *Table[TEntity]) Distinct(args ...any) *Table[TEntity]
- func (t *Table[TEntity]) Exec(ctx context.Context, sql string, values ...any) (int64, error)
- func (t *Table[TEntity]) Exists(ctx context.Context, where ...any) (bool, error)
- func (t *Table[TEntity]) FindInBatches(ctx context.Context, batchSize int, fn func(batch []*TEntity) error) (int64, error)
- func (t *Table[TEntity]) First(ctx context.Context, conditions ...any) (*TEntity, error)
- func (t *Table[TEntity]) Get(ctx context.Context, conditions ...any) (*TEntity, error)
- func (t *Table[TEntity]) Group(name string) *Table[TEntity]
- func (t *Table[TEntity]) Having(query any, args ...any) *Table[TEntity]
- func (t *Table[TEntity]) Joins(query string, args ...any) *Table[TEntity]
- func (t *Table[TEntity]) Last(ctx context.Context, conditions ...any) (*TEntity, error)
- func (t *Table[TEntity]) Limit(n int) *Table[TEntity]
- func (t *Table[TEntity]) List(ctx context.Context, req ListRequest[TEntity]) (*types.PagedResult[TEntity], error)
- func (t *Table[TEntity]) ListAll(ctx context.Context) ([]*TEntity, error)
- func (t *Table[TEntity]) ListPage(ctx context.Context, opts *PageOptions[TEntity]) (*types.PagedResult[TEntity], error)
- func (t *Table[TEntity]) LockForUpdate() *Table[TEntity]
- func (t *Table[TEntity]) LockForUpdateSkipLocked() *Table[TEntity]
- func (t *Table[TEntity]) Max(ctx context.Context, column string, dest any, where ...any) error
- func (t *Table[TEntity]) Min(ctx context.Context, column string, dest any, where ...any) error
- func (t *Table[TEntity]) Offset(n int) *Table[TEntity]
- func (t *Table[TEntity]) Omit(columns ...string) *Table[TEntity]
- func (t *Table[TEntity]) OmitAssociations() *Table[TEntity]
- func (t *Table[TEntity]) Order(value any) *Table[TEntity]
- func (t *Table[TEntity]) Pluck(ctx context.Context, column string, dest any, where ...any) error
- func (t *Table[TEntity]) PluckDistinct(ctx context.Context, column string, dest any, where ...any) error
- func (t *Table[TEntity]) Preload(association string, args ...any) *Table[TEntity]
- func (t *Table[TEntity]) Project(ctx context.Context, dest any) error
- func (t *Table[TEntity]) ProjectFirst(ctx context.Context, dest any) (bool, error)
- func (t *Table[TEntity]) Raw(ctx context.Context, dest any, sql string, values ...any) error
- func (t *Table[TEntity]) Save(ctx context.Context, item *TEntity) (int64, error)
- func (t *Table[TEntity]) Select(query any, args ...any) *Table[TEntity]
- func (t *Table[TEntity]) Sum(ctx context.Context, column string, dest any, where ...any) error
- func (t *Table[TEntity]) Take(ctx context.Context, conditions ...any) (*TEntity, error)
- func (t *Table[TEntity]) Unscoped() *Table[TEntity]
- func (t *Table[TEntity]) Update(ctx context.Context, item *TEntity) (int64, error)
- func (t *Table[TEntity]) UpdateColumn(ctx context.Context, column string, value any, where ...any) (int64, error)
- func (t *Table[TEntity]) UpdateColumns(ctx context.Context, values map[string]any, where ...any) (int64, error)
- func (t *Table[TEntity]) UpdateMap(ctx context.Context, values map[string]any, where ...any) (int64, error)
- func (t *Table[TEntity]) Upsert(ctx context.Context, item *TEntity) (int64, error)
- func (t *Table[TEntity]) Where(query any, args ...any) *Table[TEntity]
- func (t *Table[TEntity]) WhereIf(condition bool, where ...any) *Table[TEntity]
Constants ¶
const Associations = clause.Associations
Associations is clause.Associations ("*"), the wildcard passed to Table.Preload to eager-load every association, or to Table.Omit to skip auto-saving every association on a write.
const DefaultDatabase = "default"
DefaultDatabase names the primary database. It is accepted interchangeably with the empty name, so gormx.Module(d) and gormx.Module(d, gormx.DefaultDatabase) register the same connection and gormx.Of(app) / gormx.Of(app, "default") both resolve it.
Variables ¶
This section is empty.
Functions ¶
func Expr ¶ added in v0.24.0
Expr builds a raw SQL expression. Pass it as an UpdateMap value so the database computes the new value in-place (read-free), which keeps guarded updates atomic:
t.UpdateMap(ctx, map[string]any{"used": gormx.Expr("used + 1")}, "id = ?", id)
It is gorm.Expr.
func Inject ¶ added in v0.19.0
Inject registers an already-open *gorm.DB as a database, bypassing config loading and Init. Use it to supply a connection the caller built directly — typically a shared in-memory database in tests. The host still closes it and includes it in health checks. Pass an optional name for a named database.
func IsCheckConstraintViolation ¶ added in v0.29.0
IsCheckConstraintViolation reports whether err is a check-constraint violation.
func IsDuplicateKey ¶ added in v0.29.0
IsDuplicateKey reports whether err is a unique-constraint (duplicate key) violation — e.g. inserting a row whose unique key already exists.
func IsForeignKeyViolation ¶ added in v0.29.0
IsForeignKeyViolation reports whether err is a foreign-key constraint violation — e.g. referencing a parent row that does not exist, or deleting one still referenced under ON DELETE RESTRICT.
func IsRecordNotFound ¶ added in v0.29.0
IsRecordNotFound reports whether err is a no-rows-found error. Note the Table getters (First/Find/Last/Take) already fold not-found into a (nil, nil) result, so this is mainly for callers working with raw gorm results.
func Lookup ¶ added in v0.23.0
Lookup returns the database connection registered under the optional name and whether one is registered. Use it where absence is an expected, recoverable condition (e.g. a background worker); prefer Of when the database must exist.
func Module ¶ added in v0.19.0
Module registers driver as a database, opening the connection during the host's init phase from the [database] config section (or [database.<name>] when named). Pass a built-in driver (postgres.Driver(), sqlite.Driver()) or any custom Driver; supply an already-open *gorm.DB with Inject instead.
host.MustNew().WithModule(gormx.Module(sqlite.Driver())).MustRun()
Pass an optional name to run several databases side by side, each retrieved with gormx.Of(app, name). Reach the default with gormx.Of(app).
func NewGormLogger ¶ added in v0.33.0
func NewGormLogger(sl *slog.Logger, levelOverride string) gormLogger.Interface
NewGormLogger builds a GORM logger that writes through the host's slog logger. levelOverride, when non-empty, sets the GORM log level explicitly; otherwise the level is derived from what the slog logger has enabled. This lets a database's SQL logging be tuned independently of the global log level — e.g. keep the app at debug while silencing per-query SQL traces with logLevel = "warn" in the [database] section. Valid overrides: debug (or info), warn, error, silent.
Built-in drivers pass Config.LogLevel here; custom Driver implementations can do the same to honor the per-database override.
func Of ¶ added in v0.19.0
Of returns the database connection registered under the optional name, panicking if none is registered.
func UseTracing ¶ added in v0.15.0
UseTracing registers OpenTelemetry callbacks on db so every create, query, update, delete, row, and raw operation runs inside a client span carrying the table, the SQL template (placeholders, never bound values), and any error. Spans are no-ops until a global tracer provider is installed (e.g. via the otelx module), so registering unconditionally is cheap. The host applies it automatically to driver-opened connections.
Types ¶
type BaseListRequest ¶
type BaseListRequest[T any] struct { // contains filtered or unexported fields }
BaseListRequest provides default no-op implementations of all ListRequest methods. Embed it in your own request struct and override only the methods you need. Filter rows by chaining Where/WhereIf on the Table before calling List; for request-driven filtering, implement the optional Scoper interface.
Example:
type MyRequest struct {
db.BaseListRequest[MyEntity]
TenantID string
}
func (r *MyRequest) Scope() func(*gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB { return db.Where("tenant_id = ?", r.TenantID) }
}
func NewBaseListRequest ¶
func NewBaseListRequest[T any](pageSize int, orderBy string) BaseListRequest[T]
func (*BaseListRequest[T]) CreateNextPageToken ¶
func (b *BaseListRequest[T]) CreateNextPageToken(_ []T) (*string, error)
func (*BaseListRequest[T]) CurrentPageData ¶
func (b *BaseListRequest[T]) CurrentPageData() ([]any, error)
func (*BaseListRequest[T]) OrderBy ¶
func (b *BaseListRequest[T]) OrderBy() string
func (*BaseListRequest[T]) PageSize ¶
func (b *BaseListRequest[T]) PageSize() int
type BaseRepository ¶
type BaseRepository struct {
// contains filtered or unexported fields
}
BaseRepository holds a database connection and provides RunTx for transaction management. Embed it in your own repository structs to inherit transaction support without implementing it yourself.
Example:
type OrderRepo struct {
db.BaseRepository
orders *db.Table[Order]
payments *db.Table[Payment]
}
func NewOrderRepo(h *mjhost.App) (*OrderRepo, error) {
base, err := db.NewBaseRepository(h)
if err != nil {
return nil, err
}
return &OrderRepo{
BaseRepository: *base,
orders: db.NewTableFor[Order](base),
payments: db.NewTableFor[Payment](base),
}, nil
}
func (s *OrderService) PlaceOrder(ctx context.Context, ...) error {
return s.repo.RunTx(ctx, func(ctx context.Context) error {
...
})
}
func NewBaseRepository ¶
func NewBaseRepository(db *gorm.DB) BaseRepository
type Config ¶ added in v0.4.0
type Config struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
User string `mapstructure:"user"`
Password string `mapstructure:"password"`
Name string `mapstructure:"name"`
SSLMode string `mapstructure:"sslMode"`
// LogLevel optionally overrides the SQL-logging level for this database,
// independent of the global log level. Leave empty to derive it from the
// host logger (the default). Set it to "warn" or "error" to silence
// per-query SQL traces while the app runs at debug. Valid values: debug
// (or info), warn, error, silent.
LogLevel string `mapstructure:"logLevel"`
}
Config is the database connection configuration. Individual [database] and [database.<name>] sections are loaded by Service.LoadConfig.
type CursorScoper ¶ added in v0.5.0
CursorScoper is an optional interface for ListRequest. If implemented, CursorScope() takes precedence over CurrentPageData().
type DB ¶ added in v0.24.0
DB is gorm.DB, re-exported for callers who write Where/Order/Scope closures or implement Scoper — their func(*gormx.DB) *gormx.DB satisfies the func(*gorm.DB) *gorm.DB that gormx expects, since this is an alias rather than a new type.
type Driver ¶ added in v0.7.0
Driver opens a *gorm.DB from a resolved Config. It is the extension point for plugging a database engine into the host. Implement Open and pass the value to gormx.Module (optionally named). The host owns config resolution and lifecycle (Close, health checks); a Driver is responsible only for dialing.
Use Postgres() or SQLite() for the built-in engines.
type ListRequest ¶
type ListRequest[T any] interface { CurrentPageData() (where []any, err error) CreateNextPageToken(items []T) (*string, error) PageSize() int OrderBy() string }
ListRequest is the interface Table.List requires. Use PageRequest[TEntity, TValue] for cursor-based pagination, or implement this interface directly for custom pagination strategies.
type OffsetPager ¶ added in v0.18.0
OffsetPager is an optional interface for ListRequest. If implemented and Offset returns ok, Table.List uses offset pagination instead of cursor pagination: it skips offset rows, ignores the cursor entirely, and computes TotalCount so callers can render "page X of Y". Use this for user-driven page-number jumps.
type OrderByColumn ¶ added in v0.24.0
type OrderByColumn = clause.OrderByColumn
OrderByColumn is clause.OrderByColumn, the typed alternative to a raw "col DESC" string accepted by Table.Order.
type PageOptions ¶ added in v0.13.0
type PageOptions[T any] struct { // contains filtered or unexported fields }
PageOptions configures a ListPage call. Build it fluently with NewPageOptions:
NewPageOptions[Order]().PageSize(20).OrderBy("created_at DESC")
For cursor-based pagination, also set Cursor (the current-page filter) and NextToken (builds the token for the following page):
NewPageOptions[Order]().
PageSize(20).
OrderBy("created_at DESC").
Cursor(func() ([]any, error) {
if token == "" {
return nil, nil
}
return []any{"created_at < ?", token}, nil
}).
NextToken(func(items []Order) (*string, error) {
s := items[len(items)-1].CreatedAt.Format(time.RFC3339)
return &s, nil
})
func NewPageOptions ¶ added in v0.13.0
func NewPageOptions[T any]() *PageOptions[T]
NewPageOptions returns an empty PageOptions ready to configure.
func (*PageOptions[T]) Cursor ¶ added in v0.13.0
func (o *PageOptions[T]) Cursor(fn func() ([]any, error)) *PageOptions[T]
Cursor sets the WHERE condition that filters to the current page; decode the page token here and return it as {query, args...}, or nil for the first page. It runs after the table's accumulated Where/WhereIf scopes.
func (*PageOptions[T]) NextToken ¶ added in v0.13.0
func (o *PageOptions[T]) NextToken(fn func(items []T) (*string, error)) *PageOptions[T]
NextToken sets the function that builds the next-page token from the page's items. When set, ListPage fetches one extra row to decide whether a next page exists.
func (*PageOptions[T]) OrderBy ¶ added in v0.13.0
func (o *PageOptions[T]) OrderBy(s string) *PageOptions[T]
OrderBy sets the ORDER BY clause and returns o for chaining.
func (*PageOptions[T]) PageSize ¶ added in v0.13.0
func (o *PageOptions[T]) PageSize(n int) *PageOptions[T]
PageSize sets the maximum number of rows returned and returns o for chaining.
type PageRequest ¶
type PageRequest[TEntity any, TValue any] struct { *types.PagedResultRequest // contains filtered or unexported fields }
PageRequest implements cursor-based pagination for Table.List. TEntity is the row type; TValue is the type of the cursor column (e.g. time.Time, int64). Using a typed cursor avoids the JSON float64 coercion issue that occurs with any.
Example:
req := db.NewPageRequest(
pagedReq, // *types.PagedResultRequest from the HTTP handler
"created_at", // cursor column
func(e Order) time.Time { return e.CreatedAt },
).OrderDesc()
result, err := orderTable.Where("tenant_id = ?", tenantID).List(ctx, req)
func NewPageRequest ¶
func NewPageRequest[TEntity any, TValue any]( req *types.PagedResultRequest, cursorCol string, cursorFn func(TEntity) TValue, ) *PageRequest[TEntity, TValue]
NewPageRequest creates a PageRequest. cursorCol is the SQL column name used for ordering and cursor position (e.g. "id", "created_at"). cursorFn extracts the cursor value from a row; its return type must match TValue.
func (*PageRequest[TEntity, TValue]) CreateNextPageToken ¶
func (r *PageRequest[TEntity, TValue]) CreateNextPageToken(items []TEntity) (*string, error)
func (*PageRequest[TEntity, TValue]) CurrentPageData ¶
func (r *PageRequest[TEntity, TValue]) CurrentPageData() ([]any, error)
func (*PageRequest[TEntity, TValue]) Offset ¶ added in v0.18.0
func (r *PageRequest[TEntity, TValue]) Offset() (offset int, ok bool)
Offset reports the row offset for page-number pagination. ok is false when no Page is set, in which case Table.List uses cursor pagination instead. Page is 1-based; values below 1 are clamped to the first page.
func (*PageRequest[TEntity, TValue]) OrderBy ¶
func (r *PageRequest[TEntity, TValue]) OrderBy() string
func (*PageRequest[TEntity, TValue]) OrderDesc ¶
func (r *PageRequest[TEntity, TValue]) OrderDesc() *PageRequest[TEntity, TValue]
OrderDesc switches the sort order to descending and flips the cursor operator accordingly. By default results are ordered ascending.
func (*PageRequest[TEntity, TValue]) PageSize ¶
func (r *PageRequest[TEntity, TValue]) PageSize() int
type Scoper ¶ added in v0.5.0
Scoper is an optional interface for ListRequest. If implemented, its Scope() is applied on top of the Table's accumulated Where/WhereIf scopes — use it for request-driven filtering that can't be expressed by chaining Where on the Table.
type Service ¶ added in v0.9.0
type Service struct {
// contains filtered or unexported fields
}
Service manages the lifecycle of a single *gorm.DB connection. It implements configx.Configurable, core.Initer, core.Closer, and core.HealthChecker, so the host drives config loading, dialing, and shutdown automatically. When a connection is provided up front (NewServiceFromDB), config loading and Init are no-ops, but Close and health checks still run.
func NewService ¶ added in v0.9.0
NewService creates a Service that opens a connection via driver during Init. name is used in health-check error messages; section is the config key (e.g. "database" or "database.secondary").
func NewServiceFromDB ¶ added in v0.9.0
NewServiceFromDB creates a Service around an already-open *gorm.DB. The Service participates in Close and health checks but skips config loading and Init.
func (*Service) Close ¶ added in v0.9.0
Close implements core.Closer, closing the underlying SQL connection.
func (*Service) CloseOrder ¶ added in v0.19.0
CloseOrder closes the database late (as a backend), after the edges that use it (HTTP servers, subscribers) have drained.
func (*Service) Healthy ¶ added in v0.9.0
Healthy implements core.HealthChecker, pinging the underlying connection so the host's /readyz can report the database. A self-describing error names the database for multi-database setups.
func (*Service) Init ¶ added in v0.9.0
Init implements core.Initer. It is a no-op for injected connections.
func (*Service) ReadConfig ¶ added in v0.11.0
ReadConfig implements configx.Configurable. It is a no-op for injected connections.
func (*Service) SetClock ¶ added in v0.32.0
func (s *Service) SetClock(c core.TimeProvider)
SetClock sets the time source GORM uses to stamp CreatedAt/UpdatedAt, wiring it to db.Config.NowFunc. For driver-opened connections it takes effect during Init; for an already-open (injected) connection it applies immediately. A nil clock is ignored, leaving GORM's default (time.Now).
type Table ¶
type Table[TEntity any] struct { // contains filtered or unexported fields }
Table is a generic GORM wrapper for a single database table. Create one per entity type via NewTable. Call Preload to eager-load associations before executing a query. Call WhereIf to accumulate conditional WHERE clauses.
func NewTableFor ¶
func NewTableFor[TEntity any](r *BaseRepository) *Table[TEntity]
NewTableFor creates a Table derived from the same database connection as the BaseRepository. Use this during repository construction so all tables share the same connection.
func (*Table[TEntity]) Aggregate ¶ added in v0.21.0
Aggregate runs an arbitrary SQL aggregate expression over the rows matching the optional where conditions and scans the scalar result into dest (a pointer). It is the general primitive behind Sum/Avg/Max/Min — reach for it directly for aggregates without a dedicated helper, e.g.:
var spread int table.Aggregate(ctx, "MAX(price) - MIN(price)", &spread, "in_stock = ?", true)
Accepts the same GORM-style where conditions as Count and composes with accumulated Where/WhereIf scopes. The expression is raw SQL, so when it can be NULL (e.g. over an empty set) wrap it in COALESCE to give dest a default rather than failing the scan.
func (*Table[TEntity]) Avg ¶ added in v0.21.0
Avg averages the numeric column across matching rows into dest (typically a *float64). An empty set yields zero rather than NULL (via COALESCE), so dest is always set.
func (*Table[TEntity]) Count ¶
Count returns the number of rows matching the optional where conditions.
func (*Table[TEntity]) Create ¶
Create inserts a new row. GORM populates auto-generated fields (ID, CreatedAt, etc.) on item.
func (*Table[TEntity]) CreateMany ¶
func (t *Table[TEntity]) CreateMany(ctx context.Context, items []*TEntity, batchSize int) (int64, error)
CreateMany inserts items in batches of batchSize and reports how many rows were inserted. Use for bulk inserts. The count equals len(items) on a plain insert, so it is mainly a check against a conflict clause silently skipping rows.
func (*Table[TEntity]) Delete ¶ added in v0.11.0
Delete removes rows matching conditions and reports how many rows were deleted. Accepts the same GORM condition formats as Find.
The count distinguishes "the row was already gone" from "the delete failed": a zero return with a nil error means nothing matched, which is how a caller detects an idempotent re-delete or a WHERE guard that rejected the row. Callers that don't need the count can discard it.
On a soft-delete entity (one carrying gorm.DeletedAt) this is the number of rows marked deleted, not physically removed; chain Unscoped for a hard delete.
func (*Table[TEntity]) Distinct ¶ added in v0.20.0
Distinct returns a copy of the Table that selects distinct rows, optionally over the given columns. With no args it applies SELECT DISTINCT to the selected columns.
func (*Table[TEntity]) Exec ¶ added in v0.24.0
Exec runs a raw SQL statement that returns no rows (INSERT/UPDATE/DELETE/DDL) and reports how many rows were affected. Like Raw, the SQL is engine-specific, the Table's builder scopes do not apply, and it participates in the ctx transaction when present.
func (*Table[TEntity]) Exists ¶ added in v0.20.0
Exists reports whether any row matches the optional where conditions (combined with the Table's accumulated scopes).
func (*Table[TEntity]) FindInBatches ¶ added in v0.24.0
func (t *Table[TEntity]) FindInBatches(ctx context.Context, batchSize int, fn func(batch []*TEntity) error) (int64, error)
FindInBatches streams matching rows to fn in batches of batchSize, so large result sets can be processed without loading every row into memory at once, and reports how many rows were processed in total across all batches. Chain Where/WhereIf/Order/Preload on the Table to scope the query first. fn is called once per batch; returning a non-nil error from it stops the iteration and surfaces that error, and the count then covers only the batches processed up to that point. The batch slice is GORM-managed and reused between calls — copy out any elements you need to keep beyond the current call.
func (*Table[TEntity]) First ¶ added in v0.20.0
First returns the first matching row ordered by primary key, or by the Table's accumulated Order when set, or nil if none matches (no error). Use Get when a missing row should be an error. Accepts GORM condition formats: primary key value, "col = ?", or map[string]any.
func (*Table[TEntity]) Get ¶ added in v0.20.0
Get is like First but treats a missing row as an error: it returns an errorx.ErrNotFound (a NotFound-category *errorx.Error, subject set to the entity type name) when no row matches. Use Get when the row must exist — e.g. loading by primary key behind a request handler, where absence should surface as a 404 — and use First when absence is an expected outcome. Other database errors are returned unchanged. Match with errors.Is(err, errorx.ErrNotFound).
func (*Table[TEntity]) Group ¶ added in v0.20.0
Group returns a copy of the Table with a GROUP BY clause. Combine with Having and Select for aggregate queries.
func (*Table[TEntity]) Having ¶ added in v0.20.0
Having returns a copy of the Table with a HAVING clause, filtering grouped rows. query and args accept the same formats as gorm.DB.Having.
func (*Table[TEntity]) Joins ¶ added in v0.20.0
Joins returns a copy of the Table with a JOIN clause. query and args accept the same formats as gorm.DB.Joins: a raw join expression, or an association name for a struct join.
func (*Table[TEntity]) Last ¶ added in v0.20.0
Last returns the last matching row ordered by primary key, or by the Table's accumulated Order (reversed) when set, or nil if none matches (no error). Accepts the same GORM condition formats as First.
func (*Table[TEntity]) Limit ¶ added in v0.20.0
Limit returns a copy of the Table capped to n rows. A negative n cancels a previously applied limit. The cap applies to multi-row reads (Find, ListAll); the single-row getters (First, Last, Take) already limit to one row.
func (*Table[TEntity]) List ¶
func (t *Table[TEntity]) List(ctx context.Context, req ListRequest[TEntity]) (*types.PagedResult[TEntity], error)
List returns one page of results. TotalCount is always -1 (not computed). Filter and eager-load by chaining Where/WhereIf/Preload on the Table before calling; the request supplies page size, ordering, and the cursor. If the request implements Scoper, its Scope() is applied on top of those scopes. If it implements CursorScoper, its CursorScope() replaces CurrentPageData().
func (*Table[TEntity]) ListAll ¶
ListAll returns all matching rows. Use only when the result set is known to be small. Chain Where/WhereIf/Order/Preload on the Table to filter, sort, and eager-load before calling; without an Order the row order is database-defined.
func (*Table[TEntity]) ListPage ¶ added in v0.13.0
func (t *Table[TEntity]) ListPage(ctx context.Context, opts *PageOptions[TEntity]) (*types.PagedResult[TEntity], error)
ListPage returns one page of results using the table's accumulated scopes. Chain Where/WhereIf/Preload on the Table to filter and eager-load before calling. Set Cursor/NextToken on opts for cursor-based pagination; otherwise NextPageToken is nil.
func (*Table[TEntity]) LockForUpdate ¶ added in v0.24.0
LockForUpdate returns a copy of the Table whose reads emit SELECT … FOR UPDATE, taking a row-level pessimistic lock on the matched rows until the surrounding transaction commits or rolls back. Use it inside RunTx for read-modify-write sequences that can't be expressed as a single guarded UpdateMap, e.g.:
repo.RunTx(ctx, func(ctx context.Context) error {
acct, err := t.LockForUpdate().Get(ctx, "id = ?", id) // row locked here
if err != nil {
return err
}
acct.Balance = recompute(acct.Balance)
_, err = t.Update(ctx, acct)
return err
})
Engine caveat: this is a Postgres/MySQL feature. The SQLite driver silently drops the locking clause (the query still runs, but no lock is taken), so on SQLite the lock provides no protection — write safety there comes from SQLite serializing writers at the transaction level instead. Outside a transaction the clause is a no-op on every engine, since any lock would be released immediately.
Prefer a single guarded UpdateMap (see its docs) when the change can be expressed as a SQL expression — it is atomic and portable without any locking.
func (*Table[TEntity]) LockForUpdateSkipLocked ¶ added in v0.31.0
LockForUpdateSkipLocked is LockForUpdate with SKIP LOCKED: reads emit SELECT … FOR UPDATE SKIP LOCKED, taking row-level locks on the matched rows but skipping (rather than waiting on) any row another transaction already locks. Use it inside a transaction so competing workers each claim a disjoint set of rows — the standard way to fan a queue or outbox table out across several concurrent consumers without any two of them grabbing the same row.
Engine caveat: SKIP LOCKED is a Postgres/MySQL 8+ feature. The SQLite driver silently drops the locking clause, so no rows are skipped there and every worker sees the whole set — gate on db.Dialector.Name() and keep a single-consumer path for SQLite. Outside a transaction the clause is a no-op on every engine, since any lock would be released immediately.
func (*Table[TEntity]) Max ¶ added in v0.21.0
Max writes the largest value of the numeric column across matching rows into dest. An empty set yields zero rather than NULL (via COALESCE), so dest is always set.
func (*Table[TEntity]) Min ¶ added in v0.21.0
Min writes the smallest value of the numeric column across matching rows into dest. An empty set yields zero rather than NULL (via COALESCE), so dest is always set.
func (*Table[TEntity]) Offset ¶ added in v0.20.0
Offset returns a copy of the Table that skips the first n rows. A negative n cancels a previously applied offset. Pair with Order and Limit for manual offset paging; for page-number jumps prefer the OffsetPager path on List.
func (*Table[TEntity]) Omit ¶ added in v0.27.0
Omit returns a copy of the Table that omits the given columns, wrapping GORM's Omit. On reads it excludes those columns from the SELECT; on Create/Save/Update it excludes them from the written columns. It is the complement of Select. Pass Associations ("*") to skip auto-saving all associations on a write.
func (*Table[TEntity]) OmitAssociations ¶ added in v0.32.0
OmitAssociations returns a copy of the Table that skips auto-saving associations on a Create/Save/Update — only the entity's own columns are written. It is shorthand for Omit(Associations).
Use it when the entity was loaded with associations preloaded but only its own row changed: GORM would otherwise re-upsert each association with ON CONFLICT DO NOTHING, which is wasted work and, since it never updates an existing associated row, a trap if you expected those child changes to persist (save them explicitly instead).
func (*Table[TEntity]) Order ¶ added in v0.20.0
Order returns a copy of the Table with an ORDER BY clause. Calls accumulate, so multiple Order calls produce ordering by the first column, then the second, etc. value accepts the same formats as gorm.DB.Order: a string ("created_at DESC") or a clause.OrderByColumn. The accumulated order applies to every read on the Table, including First, Last, Find, ListAll, and ListPage.
func (*Table[TEntity]) Pluck ¶ added in v0.24.0
Pluck collects the values of a single column into dest (a pointer to a slice), one element per matching row with duplicates preserved. Accepts optional GORM-style where conditions to scope the query. Use PluckDistinct to deduplicate.
func (*Table[TEntity]) PluckDistinct ¶
func (t *Table[TEntity]) PluckDistinct(ctx context.Context, column string, dest any, where ...any) error
PluckDistinct collects unique values from column into dest (a pointer to a slice). Accepts optional GORM-style where conditions to scope the query.
func (*Table[TEntity]) Preload ¶
Preload returns a copy of the Table that eager-loads association on every query. args mirrors GORM's Preload: a SQL condition string + values, or a func(*gorm.DB) *gorm.DB, or nothing for an unconditional preload. Use clause.Associations ("*") to preload all. Multiple calls accumulate; preloads do not modify the original Table.
func (*Table[TEntity]) Project ¶ added in v0.21.0
Project runs the Table's query — including any chained Select/Where/Order/Group/Joins/etc scopes — but maps the rows into dest instead of the entity type. dest is a pointer to a slice of your result type. Use it for read models / DTOs that don't match the entity struct: pair it with Select to pick or compute the columns, which map onto dest's fields by name (or `gorm:"column:..."` tag). dest may also be a *[]map[string]any to receive rows as column→value maps when the shape isn't known ahead of time. Preloads are ignored — associations belong to the entity type, not the projection.
type tally struct {
CampaignID uint
Total uint64
}
var rows []tally
err := table.Select("campaign_id, COALESCE(SUM(amount), 0) AS total").
Where("is_confirmed = ?", true).
Group("campaign_id").
Project(ctx, &rows)
func (*Table[TEntity]) ProjectFirst ¶ added in v0.21.0
ProjectFirst is the single-row form of Project: it maps the first matching row into dest (a pointer to your result struct) and reports whether a row was found. No match leaves dest untouched and returns (false, nil) — the missing row is not an error. Chain Order on the Table to control which row "first" selects; otherwise it is the entity's primary key.
func (*Table[TEntity]) Raw ¶ added in v0.24.0
Raw runs a raw SQL query and scans the result into dest — a pointer to a struct, slice of structs, map, or scalar. Use it as an escape hatch for queries the Table builder can't express (CTEs, window functions, engine-specific syntax). The SQL is sent verbatim, so it is engine-specific, and the Table's accumulated Where/Order/ Preload scopes do NOT apply. It runs on the transaction in ctx when one is present (see RunTx).
var report []SalesRow err := t.Raw(ctx, &report, "SELECT region, SUM(total) AS total FROM orders GROUP BY region")
func (*Table[TEntity]) Save ¶
Save performs a full upsert — all fields are written — and reports how many rows were written. Use Update or UpdateMap for partial updates.
func (*Table[TEntity]) Select ¶ added in v0.20.0
Select returns a copy of the Table restricted to the given columns or expressions. query and args accept the same formats as gorm.DB.Select: column names, a comma list, or a raw expression with placeholders.
func (*Table[TEntity]) Sum ¶ added in v0.21.0
Sum totals the numeric column across rows matching the optional where conditions, writing the result into dest (a pointer to a numeric type). No matching rows — or an empty table — yields zero rather than NULL (via COALESCE), so dest is always set.
func (*Table[TEntity]) Take ¶ added in v0.20.0
Take returns a single matching row without any implicit ordering (the Table's accumulated Order still applies), or nil if none matches (no error). Use it when you want any one row and do not need primary-key ordering. Accepts the same GORM condition formats as First.
func (*Table[TEntity]) Unscoped ¶ added in v0.20.0
Unscoped returns a copy of the Table that ignores GORM's soft-delete filter, so reads include soft-deleted rows and Delete becomes a permanent (hard) delete.
func (*Table[TEntity]) Update ¶
Update performs a partial update — only non-zero fields in item are written — and reports how many rows were updated.
A zero count with a nil error means no row matched: the primary key on item is absent from the table, or an accumulated Where scope excluded it. Treat it as the not-found signal rather than assuming the write landed. Callers that don't need the count can discard it.
Only non-zero fields are written, so a field cleared to its zero value ("", 0, false) is skipped — use UpdateMap to write zero values explicitly.
func (*Table[TEntity]) UpdateColumn ¶ added in v0.24.0
func (t *Table[TEntity]) UpdateColumn(ctx context.Context, column string, value any, where ...any) (int64, error)
UpdateColumn sets a single column to value and reports how many rows were updated. Unlike UpdateMap it writes the raw column WITHOUT running model hooks or auto-updating tracked timestamps (e.g. UpdatedAt). Accepts optional GORM-style where conditions. Reach for it on bulk maintenance writes where hook/timestamp side effects are unwanted; prefer UpdateMap for ordinary updates.
func (*Table[TEntity]) UpdateColumns ¶ added in v0.24.0
func (t *Table[TEntity]) UpdateColumns(ctx context.Context, values map[string]any, where ...any) (int64, error)
UpdateColumns applies a column→value map and reports how many rows were updated. It is the multi-column form of UpdateColumn: like it, the raw columns are written WITHOUT model hooks or timestamp auto-updates — the no-side-effects counterpart to UpdateMap.
func (*Table[TEntity]) UpdateMap ¶
func (t *Table[TEntity]) UpdateMap(ctx context.Context, values map[string]any, where ...any) (int64, error)
UpdateMap applies a column→value map as a partial update and reports how many rows were updated. Accepts optional GORM-style where conditions (same format as Find/Count) to scope which rows are updated.
The affected-rows count matters for capacity- or version-guarded updates whose WHERE clause may match no rows (e.g. an atomic counter increment bounded by a limit): a zero return means the guard rejected the update rather than a failure. Callers that don't need the count can discard it.
For lock-free atomic updates, pass a gormx.Expr value so the new value is computed by the database in-place rather than read-then-written, and put the precondition in the WHERE clause. The whole thing is a single atomic statement and is portable across engines (SQLite included):
n, err := t.UpdateMap(ctx,
map[string]any{"used": gormx.Expr("used + 1")},
"id = ? AND used < capacity", id)
// n == 0 means the row was missing or at capacity.
func (*Table[TEntity]) Upsert ¶
Upsert inserts item or updates all columns if the primary key already exists, and reports how many rows were written. Uses a database-level ON CONFLICT clause, making it safe under concurrent writes. The count does not distinguish an insert from an update — both write one row.
func (*Table[TEntity]) Where ¶ added in v0.13.0
Where returns a copy of the Table with an additional WHERE clause. Calls accumulate; the original Table is not modified. Accepts the same formats as gorm.DB.Where.
func (*Table[TEntity]) WhereIf ¶
WhereIf returns a copy of the Table with an additional WHERE clause that is applied only when condition is true. Calls accumulate; the original Table is not modified.
Example:
results, err := table.
WhereIf(req.TenantID != "", "tenant_id = ?", req.TenantID).
WhereIf(req.Active, "active = true").
Order("created_at DESC").
ListAll(ctx)