Documentation
¶
Index ¶
- Constants
- Variables
- func AttachNodes(handle *gorm.DB, nodes []Node)
- func AttachResolver(db *gorm.DB, replicas []gorm.Dialector) (*gorm.DB, error)
- func ConfigurePool(pool *sql.DB)
- func GuardTransaction(ctx context.Context, base, tx *gorm.DB) error
- func Handle(ctx context.Context, instance *gorm.DB) *gorm.DB
- func InTransaction(ctx context.Context) bool
- func InitDatabase(db *gorm.DB) error
- func InstallErrorTranslation(db *gorm.DB, translate func(err error) error) error
- func InstallTracing(db *gorm.DB)
- func NowUTC() time.Time
- func ParseReplicaEndpoint(endpoint string) (host string, port uint, err error)
- func QuoteIdentifier(dialector gorm.Dialector, ident string) string
- func RenameIndexSQL(dialector gorm.Dialector, table, from, to string) string
- func RenameTableSQL(dialector gorm.Dialector, from, to string) string
- func SQLiteInMemory(cfg config.Sqlite) bool
- func Serialized(ctx context.Context, purpose string, fn func() error) error
- func SetTransactionGuard(guard func(ctx context.Context, base, tx *gorm.DB) error)
- func TxFromContext(ctx context.Context, base *gorm.DB) (*gorm.DB, bool)
- func UTCMillisecond(t time.Time) time.Time
- func Wait() error
- func WithTx(ctx context.Context, tx *gorm.DB, base *gorm.DB) context.Context
- type Node
Constants ¶
const MySQLTableOptions = "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin"
MySQLTableOptions is what a MySQL table the framework creates is created with. It is one definition on purpose: a table created at startup by auto-migration and the same table created by gg migrate must come out identical, and the collation is the half that changes behavior — utf8mb4_bin compares strings byte for byte, while a server default of utf8mb4_0900_ai_ci would make a unique key refuse "a" beside "A" and a lookup find either. A deployment whose tables came from the two paths would otherwise behave differently on the same query.
Variables ¶
var DB *gorm.DB
DB holds the framework-managed default GORM database handle.
The database runtime updates it during initialization, and public packages expose read-only accessors for application code.
var ErrNotNullViolated = errors.New("a column the table requires a value for is null")
ErrNotNullViolated is answered for a write leaving a column the table requires a value for NULL, translated from the dialect's own error the way ErrValueTooLong is; database.ErrNotNullViolated forwards it.
var ErrValueTooLong = errors.New("value too long for its column")
ErrValueTooLong is answered for a write carrying a value longer than its column holds, translated from the dialect's own error the way gorm translates a duplicated key; database.ErrValueTooLong forwards it.
Functions ¶
func AttachNodes ¶
AttachNodes records the nodes behind one handle; nil detaches.
func AttachResolver ¶
AttachResolver wires replica dialectors into a handle and returns the Write-pinned handle the application must keep. It is the one place the whole replica setup happens, shared by the dialect packages:
- the resolver is installed with the framework's pool limits applied to EVERY node — a replica pool with no limits would grow unbounded;
- a role observer stamps each statement's context with the node role that served it, which the SQL logger surfaces as db_role and the active span records as db.role — with replicas in play, "which node answered this query" must never be a guess. The resolver's own TraceResolverMode cannot do this: it only prefixes the logged SQL text with [source] or [replica], keeps the role under a context key it does not export, and leaves the statements of a transaction unmarked;
- the handle's nodes are registered under the pinned handle pointer, so database.Stats and the pool metrics see every pool;
- the returned handle carries the Write pin that keeps reads on the primary unless a model or call site says otherwise, and is a session, safe to chain on again and again.
func ConfigurePool ¶
ConfigurePool applies the framework's connection pool limits — the [database] max_idle_conns, max_open_conns, conn_max_lifetime, and conn_max_idle_time settings — to the pool of a freshly opened handle. It is the one step every dialect's New shares right after opening, so an application-held handle runs under the same pool contract as the default one; AttachResolver applies the same limits to every replica pool.
Left at the database/sql defaults a pool keeps two idle connections and no lifetime: with more than two statements in flight, every connection beyond those two is closed on return and dialed again for the next statement, and a connection once opened is never retired.
A dialect with a narrower contract tightens the result afterwards: sqlite pins its pool to a single connection, see sqlite.New.
func GuardTransaction ¶
GuardTransaction runs the installed guard on tx, the transaction ctx is about to run business statements on and base is the handle it was opened on, and returns what it returns; with no guard installed it returns nil.
func Handle ¶
Handle returns the connection an operation on instance must run through for ctx: the transaction ctx carries for that instance when there is one, and instance itself otherwise.
It is internal on purpose. Application code reaches the database through database.Database[M](ctx), which joins the context transaction already; the only callers that need a raw *gorm.DB are framework integrations wrapping a third-party library whose storage layer can be handed nothing else. Exporting it would offer a second way to reach the database that looks equivalent to the first and silently is not, because a handle kept past the call resumes writing outside the caller's transaction.
func InTransaction ¶
InTransaction reports whether ctx carries a transaction open on any instance.
func InitDatabase ¶
InitDatabase publishes db as the framework's default handle and starts preparing the table of every registered model.
Preparation runs on a goroutine of its own that drains the registration queue as it fills, so models may register at any stage: before, during or after this call. Wait is what blocks until every queued model has its table.
func InstallErrorTranslation ¶
InstallErrorTranslation registers translate on db to run over the error a statement fails with, once the driver's own translation, which gorm runs as it records the error (see gorm.ErrorTranslator), is done with it: an error translate recognizes, one of the constraints a client's data breaks that the driver leaves as it is, is replaced by the sentinel translate returns; any other, for which translate returns nil, stays as it is. The callback is registered after gorm's own callbacks of each of its six operations, create, query, update, delete, row and raw, which is how dbresolver and otelgorm hook a handle too: through callbacks, with the driver's dialector left in place, since gorm.io/datatypes tells the dialect apart by the dialector's own type.
func InstallTracing ¶
InstallTracing instruments a freshly opened handle for tracing when tracing is configured on, the one step every dialect's New shares: from then on each statement the handle runs exports a span parented on the statement's context, which is how SQL spans nest under the framework's database operation spans. A failure only warns: tracing is an observability aid, and a handle that cannot be traced still serves its statements.
With tracing configured off the handle is left bare. The plugin would otherwise run on every statement for nobody: with no tracer provider ever installed it still wraps the statement context twice and boxes a non-recording span, six allocations per statement measured on a List.
The decision reads the configuration rather than otel.IsEnabled: the default handles open during bootstrap before the otel package initializes, when IsEnabled is still false for a deployment that has tracing on, and the configured flag is the very fact that initialization consults first. The plugin binds its tracer when installed, so a handle opened with tracing off stays untraced whatever the process does afterwards.
func NowUTC ¶
NowUTC produces every framework-managed timestamp: it is the gorm.Config NowFunc shared by the dialect packages (the updated_at refresh on Update, the deleted_at stamp on soft delete) and the explicit source database.Create/Upsert stamp rows with, the current time on the time base (see UTCMillisecond).
func ParseReplicaEndpoint ¶
ParseReplicaEndpoint splits one configured replica entry into host and port. The accepted shape is exactly "host:port" — a replica shares every other connection setting with the primary, so the address is all an entry may carry. A malformed entry fails database initialization instead of being skipped: a replica that silently never joined would disguise a configuration typo as "all reads on the primary".
func QuoteIdentifier ¶
QuoteIdentifier returns ident quoted the way dialector quotes it: `records` on MySQL and SQLite, "records" on PostgreSQL, a quote inside the name doubled, and a name qualified by its schema quoted part by part, "analytics"."records" on PostgreSQL.
func RenameIndexSQL ¶
RenameIndexSQL returns the statement renaming the index from of table so that it is named to, for dialector: ALTER TABLE `records` RENAME INDEX `legacy_records_kind` TO `idx_records_kind`; on MySQL, ALTER INDEX "legacy_records_kind" RENAME TO "idx_records_kind"; on PostgreSQL, and on SQLite, which renames no index, DROP INDEX `legacy_records_kind`; for the index to be created under its name at the next start.
func RenameTableSQL ¶
RenameTableSQL returns the statement renaming the table from so that it is named to, for dialector: RENAME TABLE `old_records` TO `records`; on MySQL, ALTER TABLE "old_records" RENAME TO "records"; on PostgreSQL, and ALTER TABLE `old_records` RENAME TO `records`; on SQLite.
func SQLiteInMemory ¶
SQLiteInMemory reports whether cfg selects sqlite's in-memory database rather than a database file: with is_memory set, with no path, with a path naming memory, plainly or as a file URI, with or without parameters, or with a file URI in memory mode. It reports true for {IsMemory: true, Path: "./data.db"}, {Path: ""}, {Path: ":memory:"}, {Path: "file::memory:?cache=shared"} and {Path: "file:data.db?mode=memory"}, and false for {Path: "./data.db"}, {Path: "./data.db?_busy_timeout=1000"} and {Path: "data.db?mode=memory"}.
A path selecting memory counts whatever is_memory says: the two spell the same intent, and honoring the path as a file would open a database that lives as long as one connection and disappears with it, which is not what either of them asked for.
func Serialized ¶
Serialized runs fn while holding the startup lock named purpose on the primary database, so that of the processes of a deployment starting at once only one runs it at a time: the seeding the routes-ready hooks do reads before it writes, and replicas doing so together would each find nothing and each write. Table preparation holds a lock of its own, one table at a time; the two do not exclude each other. A process waits for the lock for as long as the holder takes, or until ctx ends — the process told to stop while it starts — warning every startupLockWaitReport that it is still waiting; see lockStartup for why the wait has no bound of its own. ctx bounds the wait only: fn runs to its end once the lock is held. Where there is no lock to hold — SQLite, or a ClickHouse primary, which has no advisory locks and no transactions or unique keys for seeding to count on either — fn runs as is; a MySQL or PostgreSQL pool of a single connection has none to spare for the lock and fails instead, see lockStartup. A process with no primary database has nothing to coordinate through and runs fn as is too.
func SetTransactionGuard ¶
SetTransactionGuard installs guard. It is called from a package init function, before any transaction opens, which is what lets the slot go without a lock. A second guard panics instead of replacing the first: the slot holds one check, and silently swapping it would drop the other package's. A nil guard clears the slot, for tests.
func TxFromContext ¶
TxFromContext returns the transaction ctx carries for base, if any.
func UTCMillisecond ¶
UTCMillisecond brings t onto the framework's time base, UTC truncated to the millisecond. Two decisions live here:
- UTC, the framework's one time base. A time produced by time.Now carries the server's local zone: drivers with a UTC wire location still store the right instant, but the in-memory model then serializes with a local offset instead of the UTC form read rows carry.
- Millisecond truncation, matching the millisecond storage precision the dialects share (MySQL datetime(3), ClickHouse DateTime64(3)). Without it the in-memory value keeps nanoseconds the row cannot hold, so the time a write hands back differs from what a later read returns — and MySQL rounds half up, which can even shift the stored millisecond.
Every time a model carries goes through it on its way into a row, so a project produces its times with time.Now and reads back what it wrote.
func Wait ¶
func Wait() error
Wait blocks until every model registered so far has its table, waking on each table that finishes and reporting the remaining backlog on a fixed cadence. It reads the pending count rather than the registration queue: a model leaves that queue when its preparation starts, not when its table exists.
It returns the first failure the preparation met, if any: preparation runs on a goroutine of its own, so this is where a table the deployment could not get reaches the caller, in time to end the start through its ordinary exit.
Called before InitDatabase it returns straight away with a warning, because nothing is preparing tables yet and there is no end to wait for.
Wait only observes work already queued. If another subsystem, such as module registration, can still call model.Register, drain that subsystem first and then call Wait so its tables are visible.
func WithTx ¶
WithTx returns a child context carrying tx as the transaction open on base.
Model hooks only receive a context.Context. They do not receive the database wrapper or the raw *gorm.DB transaction, and that is intentional: model code should keep using the framework entry point, for example database.Database[*Sample](ctx).Update(sample). The transaction therefore has to travel through the hook context, and the database chain reads it back to bind itself to the same transaction. The child context is also marked as inside a transaction whatever the instance, see InTransaction.
The value is scoped to this context tree only. It is not global, does not cross requests, and is lost if code replaces the context with context.Background().
Types ¶
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package dbnode names the roles of the nodes a database handle writes to and reads from, and stamps the role that serves a statement on the statement's context, for the SQL log to report which node ran it.
|
Package dbnode names the roles of the nodes a database handle writes to and reads from, and stamps the role that serves a statement on the statement's context, for the SQL log to report which node ran it. |