This guide is for engineers, architects, and library integrators who need to understand how GORM works internally, how to configure it for production, how to extend it, and how to operate applications that depend on it. It complements the User Guide, which covers day-to-day API usage.
GORM (gorm.io/gorm) is a pure-Go ORM library — there is no server, daemon, CLI, or background process to deploy. "Operating" GORM means configuring it correctly inside your own Go application and tuning the database connection it manages.
1. Executive Summary
| Property | Value |
|---|---|
| Module path | gorm.io/gorm |
| Language | Go (go 1.18 minimum; slog logger requires 1.21+) |
| Type | Embeddable ORM library |
| Runtime dependencies | github.com/jinzhu/inflection, github.com/jinzhu/now, golang.org/x/text |
| Database support | Via separate driver packages (gorm.io/driver/*): MySQL, PostgreSQL, SQLite, SQL Server, GaussDB, … |
| License | MIT |
| Source layout | Core package + callbacks/, clause/, schema/, migrator/, logger/, utils/, internal/ |
| Analyzed commit | d0ee5e2 (2026-05-11) |
GORM's design centers on three ideas:
- Structs are the schema. Models are plain Go structs; reflection plus
gormtags drive table/column mapping, constraints, indexes, and relationships. - Everything is a callback pipeline. Create, Query, Update, Delete, Row, and Raw operations each run an ordered list of registered callback functions. This is the primary extension point.
- SQL is built from composable clauses. Each statement is assembled from
clause.Expressionobjects (SELECT,WHERE,LIMIT,ON CONFLICT, …) that dialectors render to database-specific SQL.
2. Architecture
2.1 Component overview
Your application code
│
┌────────▼─────────┐
│ *gorm.DB │ chainable + finisher API (gorm.go,
│ (Session/clone) │ chainable_api.go, finisher_api.go,
└────────┬─────────┘ generics.go, association.go)
│ builds
┌────────▼─────────┐
│ Statement │ per-operation state: Model, Schema,
│ (statement.go) │ Clauses, Vars, ReflectValue, SQL
└────────┬─────────┘
│ executed by
┌────────▼─────────┐ ┌──────────────────────┐
│ callbacks │◄────►│ schema.Schema cache │
│ (processor/ │ │ (schema/*.go) │
│ callback) │ │ fields, relations, │
│ callbacks/*.go │ │ indexes, constraints │
└────────┬─────────┘ └──────────────────────┘
│ builds SQL from
┌────────▼─────────┐
│ clause.* │ SELECT/WHERE/JOIN/LIMIT/ON CONFLICT/…
│ (clause/*.go) │
└────────┬─────────┘
│ rendered + executed by
┌────────▼─────────┐ ┌──────────────────────┐
│ Dialector │ │ ConnPool │
│ (driver package) │─────►│ database/sql pool │
│ Explain/QuoteTo/ │ │ or PreparedStmtDB │
│ BindVarTo/... │ │ (prepare_stmt.go) │
└──────────────────┘ └──────────────────────┘
│
Your database
2.2 The core types
| Type | File | Role |
|---|---|---|
DB |
gorm.go |
The handle users interact with. Wraps *Config, Error, RowsAffected, Statement, and an internal clone counter for chain semantics. |
Config |
gorm.go |
All global behavior knobs (see Section 4). |
Session |
gorm.go |
A set of per-scope overrides applied via db.Session(...). |
Statement |
statement.go |
Mutable per-operation builder: the model, parsed schema, accumulated clauses, bind vars, reflect value, and the strings.Builder for SQL. |
Dialector |
interfaces.go |
The driver contract — Name, Initialize, Migrator, DataTypeOf, BindVarTo, QuoteTo, Explain. |
ConnPool |
interfaces.go |
The minimal database/sql-style execution surface (ExecContext, QueryContext, …). |
Schema |
schema/schema.go |
Parsed, cached representation of a model: fields, relationships, indexes, constraints. |
2.3 The clone / chain model
DB methods return a *DB, but they do not mutate the original handle. The clone field (set to 1 at Open) governs this: when clone > 0, getInstance() produces a fresh *DB with a new (or cloned) Statement before applying the call. This is what makes the following safe:
base := db.Where("active = ?", true) // base carries one WHERE
adults := base.Where("age >= ?", 18) // a distinct chain
minors := base.Where("age < ?", 18) // another distinct chain — base is untouched
Important
A
*gorm.DBobtained from a finisher should not be reused as a "clean" handle — it carries the accumulated statement. For a fresh, condition-free handle, start a new session:db.Session(&gorm.Session{NewDB: true})(or simply use the rootdb).
2.4 The callback pipeline (the heart of GORM)
Every high-level operation dispatches to one of six processors — create, query, update, delete, row, raw — each holding an ordered list of callbacks. RegisterDefaultCallbacks (in callbacks/callbacks.go) wires the defaults:
| Processor | Default callback order |
|---|---|
| Create | begin_transaction → before_create → save_before_associations → create → save_after_associations → after_create → commit_or_rollback_transaction |
| Query | query → preload → after_query |
| Update | begin_transaction → setup_reflect_value → before_update → save_before_associations → update → save_after_associations → after_update → commit_or_rollback_transaction |
| Delete | begin_transaction → before_delete → delete_before_associations → delete → after_delete → commit_or_rollback_transaction |
| Row | row |
| Raw | raw |
The begin_transaction / commit_or_rollback_transaction pair is only registered when SkipDefaultTransaction is false (via processor.Match(enableTransaction)), which is why every single write is, by default, atomic.
processor.Execute (callbacks.go) is the engine: it runs pending scopes, parses the model into a Schema, sets up the reflect value, runs each callback function in order, then emits a Logger.Trace line with the final SQL, elapsed time, rows affected, and any error.
2.5 Callback ordering algorithm
Callbacks register with optional Before(name) / After(name) constraints (and the special "*" wildcard for "first"/"last"). sortCallbacks (callbacks.go) performs a constraint-driven topological sort. Conflicting constraints (e.g. a cycle) produce errors like conflicting callback %s with before %s, and duplicate names emit warnings. This is how a plugin can insert itself, say, after gorm:create but before gorm:after_create.
2.6 SQL building via clauses
A Statement accumulates named clauses (statement.AddClause). At execution, callbacks call statement.Build("SELECT", "FROM", "WHERE", ...) to emit each clause in order. The clause/ package defines every clause type — Select, From, Where (with AndConditions/OrConditions/NotConditions), Joins, GroupBy, OrderBy, Limit, Locking, OnConflict, Returning, Insert, Values, Update, Set, Expr, NamedExpr, IN, Eq, With (CTEs), etc. Each implements clause.Expression / clause.Interface and writes dialector-quoted SQL plus bind variables.
2.7 Schema parsing and caching
schema.Parse (schema/schema.go) reflects over a model once and caches the resulting *Schema in a sync.Map (Config.cacheStore), keyed by type. The parse resolves:
- Fields (
schema/field.go) — data type, size, defaults, auto-time, serializers, read/write permissions, all fromParseTagSetting. - Relationships (
schema/relationship.go) — Belongs To, Has One, Has Many, Many To Many, and Polymorphic, with foreign-key/reference resolution. - Indexes (
schema/index.go) and Constraints (schema/constraint.go) —CheckConstraint,UniqueConstraint. - Naming (
schema/naming.go) — theNamingStrategythat converts Go names to table/column names.
Note
Schema parsing is the most reflection-heavy step. Because results are cached per type, the cost is paid once per model per process. Several open performance issues (e.g. #7787) propose
singleflightto de-duplicate concurrent first-time parses.
3. Requirements
| Requirement | Detail |
|---|---|
| Go toolchain | 1.18+ for the core and the gorm.G[T] generics API; 1.21+ to use logger.NewSlogLogger |
| C compiler | Only if you choose the SQLite driver (cgo via mattn/go-sqlite3); MySQL/Postgres/SQL Server drivers are pure Go |
| Database server | Whatever your chosen driver targets; SQLite needs only a writable file path |
| Build tags | slog.go is guarded by //go:build go1.21 |
GORM itself imposes no minimum hardware footprint beyond your application's; its memory use scales with the number of parsed model types and the configured connection pool size.
4. Configuration Reference
Every option lives on gorm.Config (gorm.go) and is passed to gorm.Open. Sessions (gorm.Session) can override a subset per-scope.
4.1 gorm.Config fields
| Field | Type | Default | Purpose |
|---|---|---|---|
SkipDefaultTransaction |
bool |
false |
If true, single Create/Update/Delete are not wrapped in a transaction (faster, less safe). |
DefaultTransactionTimeout |
time.Duration |
0 |
Applies a timeout context to transactions begun via Begin. |
DefaultContextTimeout |
time.Duration |
0 |
Applies a timeout to each operation's context if none is set. |
NamingStrategy |
schema.Namer |
NamingStrategy{IdentifierMaxLength: 64} |
Controls table/column/index naming. |
FullSaveAssociations |
bool |
false |
If true, association writes upsert the full associated record, not just the FK. |
Logger |
logger.Interface |
logger.Default (Warn level) |
SQL/error logger. |
NowFunc |
func() time.Time |
time.Now().Local() |
Source of "now" for timestamps — override for UTC or test clocks. |
DryRun |
bool |
false |
Build SQL without executing it. |
PrepareStmt |
bool |
false |
Cache and reuse prepared statements (LRU + TTL). |
PrepareStmtMaxSize |
int |
unbounded | Max cached prepared statements. |
PrepareStmtTTL |
time.Duration |
1h-ish default |
TTL for cached prepared statements. |
DisableAutomaticPing |
bool |
false |
Skip the connectivity Ping() after Open. |
DisableForeignKeyConstraintWhenMigrating |
bool |
false |
Don't emit FK constraints during AutoMigrate. |
IgnoreRelationshipsWhenMigrating |
bool |
false |
Don't auto-create related tables during migration. |
DisableNestedTransaction |
bool |
false |
Don't use save points for nested Transaction calls. |
AllowGlobalUpdate |
bool |
false |
Permit updates/deletes with no WHERE clause. |
QueryFields |
bool |
false |
SELECT explicit column names instead of *. |
CreateBatchSize |
int |
0 |
Auto-batch Create on large slices. |
TranslateError |
bool |
false |
Map driver errors to GORM sentinels (ErrDuplicatedKey, etc.). |
PropagateUnscoped |
bool |
false |
Propagate Unscoped into nested statements. |
ClauseBuilders |
map[string]clause.ClauseBuilder |
empty | Override how named clauses are built (advanced). |
ConnPool |
ConnPool |
from dialector | Inject a custom connection pool. |
Dialector |
Dialector |
from Open arg |
The database driver. |
Plugins |
map[string]Plugin |
empty | Registered plugins. |
Tip
For a production HTTP service, a solid baseline is:
PrepareStmt: true,TranslateError: true, aLoggerconfigured toWarnwith aSlowThreshold, andNowFuncreturning UTC. KeepSkipDefaultTransactionat its default unless profiling shows the per-write transaction is a real bottleneck.
4.2 Naming strategy
schema.NamingStrategy (schema/naming.go) fields:
| Field | Effect |
|---|---|
TablePrefix |
Prepended to every table name (e.g. "public."). |
SingularTable |
If true, table names are singular (user, not users). |
NoLowerCase |
Disable snake_case lowercasing. |
NameReplacer |
A strings.Replacer-like hook to rewrite identifiers (e.g. "CID" → "Cid"). |
IdentifierMaxLength |
Truncate generated identifiers (default 64). |
db, _ := gorm.Open(dialector, &gorm.Config{
NamingStrategy: schema.NamingStrategy{
TablePrefix: "app_",
SingularTable: false,
},
})
4.3 Logger configuration
logger.Config (logger/logger.go):
| Field | Purpose |
|---|---|
LogLevel |
One of Silent (1), Error (2), Warn (3), Info (4). |
SlowThreshold |
Queries slower than this are logged at Warn as SLOW SQL. Default 200ms on logger.Default. |
Colorful |
ANSI color output. |
IgnoreRecordNotFoundError |
Suppress logging of ErrRecordNotFound. |
ParameterizedQueries |
If true, the logger redacts bind values (logs ?/$n instead of data) — useful for not leaking PII into logs. |
newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags),
logger.Config{
SlowThreshold: 200 * time.Millisecond,
LogLevel: logger.Warn,
IgnoreRecordNotFoundError: true,
ParameterizedQueries: true,
Colorful: false,
},
)
db, _ := gorm.Open(dialector, &gorm.Config{Logger: newLogger})
On Go 1.21+, logger.NewSlogLogger(slog.Default(), cfg) routes GORM logs through structured log/slog, emitting duration, sql, and rows attributes.
Caution
The logger interpolates bind variables into the SQL string for human readability (
logger.ExplainSQL). That string is for display only — never feed it back to the database, and preferParameterizedQueries: truein environments where query logs may contain sensitive values.
5. Security
GORM is a library, so its security posture is mostly about how you use it. The relevant facts from the source:
5.1 SQL injection
- Placeholders are safe.
Where,Raw,Exec, etc. parameterize?and named (@name) arguments through the driver's bind-variable mechanism (Dialector.BindVarTo). User input passed as an argument is never concatenated into SQL. - Identifiers and raw fragments are NOT escaped. The first string argument to
Where("..."),Order(...),Select(...),Group(...),Table(...),Raw(...), andJoins(...)is treated as SQL. Never build these from untrusted input.
Warning
db.Order(userInput)anddb.Where(fmt.Sprintf("name = '%s'", userInput))are injection vectors. Always pass user data as a bind argument:db.Where("name = ?", userInput). For dynamic column/sort names, validate against an allow-list before interpolating.
5.2 The ExplainSQL warning
logger.ExplainSQL and db.ToSQL produce interpolated SQL strings explicitly documented as unsafe to execute (injection risk). They exist for logging/inspection only.
5.3 Global write protection
GORM blocks Update/Delete without a WHERE clause or primary key (ErrMissingWhereClause), preventing accidental full-table mutation. This guard is bypassed only by an explicit AllowGlobalUpdate session — keep that opt-in tightly scoped.
5.4 Read/write field permissions
Field tags -> (read-only), <- (write-only/creatable), and - (ignored) let you keep sensitive or computed columns out of inserts/updates at the mapping layer, reducing mass-assignment risk.
5.5 Soft deletes and data retention
Models with gorm.DeletedAt are never physically removed by Delete; rows persist with deleted_at set. For data-retention/GDPR-style hard deletes, you must use db.Unscoped().Delete(...).
5.6 Logging parameters
Enable ParameterizedQueries: true to keep bind values (which may be credentials, tokens, or PII) out of SQL logs.
6. Operations
GORM has no process to start, stop, or monitor. "Operations" means managing the database connection it owns and observing query behavior.
6.1 Connection pool management
The pool is the standard library's. Obtain it with db.DB() and tune:
sqlDB, err := db.DB()
sqlDB.SetMaxOpenConns(100)
sqlDB.SetMaxIdleConns(10)
sqlDB.SetConnMaxLifetime(time.Hour)
sqlDB.SetConnMaxIdleTime(30 * time.Minute)
| Knob | Guidance |
|---|---|
SetMaxOpenConns |
Cap to what your database can handle; too high causes contention server-side. |
SetMaxIdleConns |
Keep ≥ your steady-state concurrency to avoid churn; ≤ MaxOpenConns. |
SetConnMaxLifetime |
Set below the database's/load-balancer's idle timeout to avoid "broken pipe" errors. |
6.2 Health checks
sqlDB, _ := db.DB()
if err := sqlDB.PingContext(ctx); err != nil { /* unhealthy */ }
PreparedStmtDB and PreparedStmtTX also implement Ping(), delegating to the underlying *sql.DB.
6.3 Prepared-statement cache
With PrepareStmt: true, GORM wraps the pool in a PreparedStmtDB (prepare_stmt.go) backed by an LRU stmt_store.Store (internal/stmt_store/). Statements are keyed by SQL text, guarded by an sync.RWMutex, and evicted on driver.ErrBadConn or TTL expiry. Call (*PreparedStmtDB).Close() to clear all cached statements (e.g. on reconnect).
Note
Open issue #7788 reports lock contention and a potential goroutine leak in the prepared-statement cache / LRU under high concurrency. If you run
PrepareStmt: trueat scale, monitor goroutine counts and benchmark against your workload.
6.4 Observability
The logger's Trace is the built-in observability hook: it logs every statement with elapsed time and rows affected, flags slow queries above SlowThreshold, and surfaces errors (other than ignored ErrRecordNotFound). Route it through slog for structured ingestion, or implement logger.Interface to push metrics/traces to your APM.
6.5 Migrations
db.AutoMigrate(&Models...) creates missing tables, adds missing columns and indexes, and updates column types — but never drops columns or tables (non-destructive). For full DDL control use the Migrator() interface:
m := db.Migrator()
m.CreateTable(&User{})
m.HasTable(&User{})
m.AddColumn(&User{}, "Age")
m.CreateIndex(&User{}, "idx_users_name")
m.DropColumn(&User{}, "Obsolete") // explicit, destructive — your call
The full Migrator interface (migrator.go) covers databases, tables, columns, views, constraints, and indexes; each driver provides a concrete implementation built on the base migrator.Migrator (migrator/migrator.go).
Warning
AutoMigrateis convenient for development and additive production changes, but it will not remove or rename columns and offers no down-migrations or version history. For destructive or auditable schema changes in production, drive DDL through the explicitMigratormethods (or a dedicated migration tool) inside a reviewed change process.
7. Extending GORM
7.1 Plugins
A plugin implements Plugin (Name() string, Initialize(*DB) error) and is registered with db.Use(plugin). Use rejects duplicates by name with ErrRegistered and calls Initialize immediately. Plugins typically register or replace callbacks and/or clause builders.
type MetricsPlugin struct{}
func (MetricsPlugin) Name() string { return "metrics" }
func (p MetricsPlugin) Initialize(db *gorm.DB) error {
return db.Callback().Query().After("gorm:query").Register("metrics:query", recordQuery)
}
db.Use(MetricsPlugin{})
7.2 Custom callbacks
Insert, replace, or remove behavior anywhere in a processor's pipeline:
db.Callback().Create().Before("gorm:create").Register("audit:stamp", stampActor)
db.Callback().Update().Replace("gorm:update", customUpdate)
db.Callback().Delete().Remove("gorm:before_delete")
7.3 Custom data types (Valuer / Scanner)
Any type implementing driver.Valuer + sql.Scanner works as a column. For SQL-expression-valued types, implement GORM's Valuer interface (GormValue(ctx, *DB) clause.Expr) to inject expressions like ST_GeomFromText(?).
7.4 Serializers
The schema.SerializerInterface (with built-ins JSONSerializer, GobSerializer, UnixSecondSerializer in schema/serializer.go) transforms a Go field to/from its stored representation via the serializer tag (gorm:"serializer:json").
7.5 Writing a dialector
To support a new database, implement the Dialector interface (Name, Initialize, Migrator, DataTypeOf, DefaultValueOf, BindVarTo, QuoteTo, Explain) plus a migrator.Migrator subtype. The existing gorm.io/driver/* packages are the reference implementations.
8. Repository Structure
| Path | Contents |
|---|---|
gorm.go |
DB, Config, Session, Open, sessions, plugins, ToSQL. |
chainable_api.go |
Chain builders: Model, Where, Select, Joins, Order, Limit, Preload, … |
finisher_api.go |
Executors: Create, First, Find, Save, Update(s), Delete, Count, Transaction, Exec, … |
generics.go |
The type-safe gorm.G[T] API and WithResult. |
association.go |
Association mode (Append/Replace/Delete/Clear/Count). |
statement.go |
The Statement builder and condition parsing. |
model.go |
The gorm.Model convenience struct. |
soft_delete.go |
DeletedAt and the soft-delete query/update/delete clauses. |
prepare_stmt.go |
PreparedStmtDB / PreparedStmtTX caches. |
errors.go |
Exported sentinel errors. |
interfaces.go |
Dialector, Plugin, ConnPool, Tx, Valuer, ErrorTranslator, … |
migrator.go |
Migrator, ColumnType, Index, TableType interfaces; AutoMigrate. |
scan.go |
Row-scanning logic (into structs, maps, primitives, with join nesting). |
callbacks/ |
The default pipeline + Create/Query/Update/Delete/Preload/Row/Raw callbacks. |
clause/ |
Every SQL clause type. |
schema/ |
Model parsing: fields, relationships, indexes, constraints, naming, serializers. |
migrator/ |
Base migrator + ColumnType/Index/TableType concrete types. |
logger/ |
Logger interface, default logger, slog logger, SQL explainer. |
utils/ |
Helpers (FileWithLineNum, string utilities). |
internal/lru, internal/stmt_store |
LRU cache and prepared-statement store. |
tests/ |
Cross-driver integration suite (separate module; uses Docker). |
9. Testing Strategy
GORM splits tests into two scopes:
- In-package unit tests sit beside the code they cover (
clause/*_test.go,schema/*_test.go,logger/*_test.go, etc.) and exercise SQL generation, schema parsing, naming, and serialization without a live database. - Integration tests live in
tests/as a separate Go module (gorm.io/gorm/tests,go 1.24.0) that depends on the real drivers (gorm.io/driver/mysql,postgres,sqlite,sqlserver,gaussdb). They run against actual databases.
Running the integration suite (from tests/README.md):
cd tests
docker-compose up # provision MySQL/Postgres/SQL Server/etc.
./tests_all.sh # run the suite across all dialects
Tip
When contributing or validating a driver, run
tests/against every supported database — behavior (quoting, returning clauses, on-conflict semantics, type mapping) legitimately differs per dialect, and the suite is where those differences are pinned down.
Lint configuration lives in .golangci.yml (golangci-lint v2): enabled linters include cyclop, gocritic, gosec, ineffassign, prealloc, unconvert, unparam, plus gofumpt/goimports formatting.
10. Performance Tuning
| Lever | Effect |
|---|---|
PrepareStmt: true |
Reuse prepared statements; biggest win for repeated query shapes. |
SkipDefaultTransaction: true |
Removes the implicit per-write transaction; measurable on write-heavy loops. |
CreateBatchSize / CreateInBatches |
Collapse many inserts into batched multi-row statements. |
FindInBatches |
Bound memory for large reads by processing in chunks. |
Select / QueryFields |
Fetch only needed columns; QueryFields avoids SELECT *. |
Preload vs Joins |
Preload issues separate queries (avoids row multiplication); Joins is one query (better for filtering). Choose per access pattern. |
| Connection pool sizing | Match MaxOpenConns/MaxIdleConns to real concurrency (Section 6.1). |
The community has filed a coordinated set of performance issues against the analyzed version — useful as a tuning/awareness checklist:
| Issue | Area |
|---|---|
| #7786 | Hot-path overhead when logger is Silent (Trace closure, time.Now, context). |
| #7792 | Preload efficiency — O(n²) assignment, concurrent loading, batch splitting. |
| #7791 | Reflection and fmt.Sprintf allocations in SQL-building paths. |
| #7787 | singleflight for schema Parse; cache ParseIndexes. |
| #7789 | sync.Pool for Statement and scan buffers to cut per-query allocations. |
| #7788 | Lock contention / goroutine leak in the prepared-statement cache / LRU. |
| #7790 | rows.Close() not deferred in delete/update returning paths — connection leak on panic. |
Caution
Issue #7790 (un-deferred
rows.Close()on the returning path) and #7788 (prepared-statement cache leak) describe potential connection/goroutine leaks under specific failure modes. If you run at high throughput withReturningclauses orPrepareStmt, monitor open connections and goroutine counts, and confirm against your pinned GORM version whether these are resolved.
11. Known Issues & Behavioral Gotchas
Sourced from errors.go, the code paths, and the project's tracked issues:
- Zero-value struct conditions/updates are skipped. Passing a struct to
Where/Updatesignores zero fields; use maps orSelect. (The single most reported source of confusion.) - Global writes are blocked. Unconditioned
Update/DeletereturnErrMissingWhereClauseunlessAllowGlobalUpdateis set. - Error sentinels require
TranslateError.ErrDuplicatedKey/ErrForeignKeyViolated/ErrCheckConstraintViolatedonly surface whenTranslateError: trueand the driver implementsErrorTranslator; GORM warns at startup if it doesn't. AutoMigrateis additive only. It never drops columns/tables and has no down-migrations.- Soft-deleted rows linger.
gorm.DeletedAtmodels needUnscoped().Delete()for true removal. Joinsby association requires a defined relationship (issue #7745); otherwise use a rawJOIN ... ON.SavePoint/RollbackToneed driver support (SavePointerDialectorInterface); otherwiseErrUnsupportedDriver. Prepared statements are temporarily detached around save points because some databases (e.g. MySQL 8) don't support them with prepared statements.ColumnType.DecimalSizecan panic on partially-populated driver wrappers under concurrentDROP COLUMN(issue #7764) — a driver-interaction edge case.
12. Quick Reference
12.1 Setup
db, err := gorm.Open(driver.Open(dsn), &gorm.Config{
PrepareStmt: true,
TranslateError: true,
Logger: logger.Default.LogMode(logger.Warn),
NowFunc: func() time.Time { return time.Now().UTC() },
})
12.2 Sentinel errors
ErrRecordNotFound, ErrInvalidTransaction, ErrNotImplemented, ErrMissingWhereClause, ErrUnsupportedRelation, ErrPrimaryKeyRequired, ErrModelValueRequired, ErrModelAccessibleFieldsRequired, ErrSubQueryRequired, ErrInvalidData, ErrUnsupportedDriver, ErrRegistered, ErrInvalidField, ErrEmptySlice, ErrDryRunModeUnsupported, ErrInvalidDB, ErrInvalidValue, ErrInvalidValueOfLength, ErrPreloadNotAllowed, ErrDuplicatedKey, ErrForeignKeyViolated, ErrCheckConstraintViolated.
12.3 Callback management
db.Callback().Create().Before("gorm:create").Register("name", fn)
db.Callback().Query().After("gorm:query").Register("name", fn)
db.Callback().Update().Replace("gorm:update", fn)
db.Callback().Delete().Remove("name")
12.4 Migrator
db.AutoMigrate(&A{}, &B{})
m := db.Migrator()
m.HasTable(&A{}); m.CreateTable(&A{}); m.DropTable(&A{})
m.AddColumn(&A{}, "Field"); m.HasColumn(&A{}, "Field"); m.DropColumn(&A{}, "Field")
m.CreateIndex(&A{}, "idx"); m.HasIndex(&A{}, "idx"); m.DropIndex(&A{}, "idx")
m.CreateConstraint(&A{}, "fk_x"); m.HasConstraint(&A{}, "fk_x")
12.5 Session overrides
db.Session(&gorm.Session{
NewDB: true, // fresh, condition-free handle
PrepareStmt: true,
SkipDefaultTransaction: true,
FullSaveAssociations: true,
AllowGlobalUpdate: true,
DryRun: true,
QueryFields: true,
SkipHooks: true,
Context: ctx,
})