Documentation
¶
Overview ¶
Package database provides GORM-based database access, connection registry, repository helpers, transaction management, and schema migration utilities.
Services register named connections at startup through the Registry:
database.Register("primary", gormDB)
Repositories retrieve the primary connection from the registry:
db := bootstrap.MustResolve[*database.Registry](c).Primary()
Transactions are managed through the Transactor interface so that callers do not depend on GORM directly:
err := tx.WithinTransaction(ctx, func(ctx context.Context) error { ... })
Index ¶
- Constants
- func ApplyFullTextSearch(query *gorm.DB, columns []string, searchTerm string) *gorm.DB
- func ApplyPoolSettings(s poolSetter, cfg ConnectionConfig)
- func EnableDBTracing(conn *gorm.DB, tr tracing.Tracer) error
- func EscapeBackticks(s string) string
- func EscapeLike(s string) string
- func GetNextDocumentNumberAndYear[T any](db *gorm.DB, numberColumn string, yearColumn string) (documentNumber int, year int, err error)
- func GetNextOrderNumber[T any](db *gorm.DB, parentColumn string, parentID int) (int, error)
- func MustPrepareTestDB(models ...interface{}) (*gorm.DB, func())
- func PrepareTestDB(models ...interface{}) (*gorm.DB, func() error, error)
- func QuoteColumn(s string) string
- func SQLTxFromContext(ctx context.Context) (*sql.Tx, bool)
- func SafeMigrate(db *gorm.DB, models ...interface{}) error
- func SimulateConnectionLoss(reg *Registry, name string) func()
- func TxFromContext(ctx context.Context) (*gorm.DB, bool)
- type BaseRepository
- type ConnectionConfig
- type ErrConnectionFailed
- type ErrConnectionNotFound
- type ErrInvalidConfig
- type Registry
- func (r *Registry) AddConnection(name string, conn *gorm.DB)
- func (r *Registry) CloseAll() error
- func (r *Registry) Get(name string) (*gorm.DB, error)
- func (r *Registry) Has(name string) bool
- func (r *Registry) MustGet(name string) *gorm.DB
- func (r *Registry) MustRegister(cfg ConnectionConfig)
- func (r *Registry) Names() []string
- func (r *Registry) Primary() *gorm.DB
- func (r *Registry) PrimaryName() string
- func (r *Registry) Register(cfg ConnectionConfig) error
- type RegistryConfig
- type Transactor
Constants ¶
const ( Null = "null" DriverSQLite = "sqlite" DriverMySQL = "mysql" SQLiteInt = "integer" MySQLInt = "int" SQLiteString = "text" MySQLString = "varchar" SQLiteFloat = "decimal(10,2)" MySQLFloat = "decimal(10,2) unsigned" SQLiteDate = "date" MySQLDate = "date" SQLiteDateTime = "datetime" MySQLDateTime = "datetime" SQLiteBool = "boolean" MySQLBool = "tinyint(1) unsigned" SQLiteJSON = "json" MySQLJSON = "json" )
DB type constants — used by custom GORM types to return the correct SQL type for the target database engine.
Variables ¶
This section is empty.
Functions ¶
func ApplyFullTextSearch ¶
ApplyFullTextSearch adds WHERE clauses for a "search all columns" feature. It splits the search term by spaces and checks if ANY column matches ANY part. Note: This uses LIKE %...% which is slow for large datasets.
func ApplyPoolSettings ¶
func ApplyPoolSettings(s poolSetter, cfg ConnectionConfig)
ApplyPoolSettings applies the provided ConnectionConfig pool settings to the given poolSetter. It expects cfg to have sensible defaults applied (e.g. via ConnectionConfig.withDefaults()).
func EnableDBTracing ¶
EnableDBTracing registers GORM callbacks to start and finish tracing spans for common DB operation types (query, create, update, delete, row, raw). It is intentionally minimal: it stores the finish func in the current GORM statement instance and calls it in the corresponding after-callback.
func EscapeBackticks ¶
EscapeBackticks escapes backticks in a column name for safe use in SQL identifiers.
func EscapeLike ¶
func GetNextDocumentNumberAndYear ¶
func GetNextDocumentNumberAndYear[T any](db *gorm.DB, numberColumn string, yearColumn string) (documentNumber int, year int, err error)
GetNextDocumentNumberAndYear returns the next document number for the current calendar year. The read is wrapped in SELECT … FOR UPDATE to prevent two concurrent requests from receiving the same number.
Call this inside a transaction so the lock is held until the INSERT completes.
func GetNextOrderNumber ¶
GetNextOrderNumber returns the next available order_number for the given table, optionally scoped to a parent record. The read is wrapped in a serialisable SELECT … FOR UPDATE to prevent duplicate numbers under concurrent inserts.
The caller must call this within an open transaction — the lock is only held for the duration of that transaction. Passing a non-transactional *gorm.DB is safe but provides no isolation guarantee.
func MustPrepareTestDB ¶
MustPrepareTestDB is like PrepareTestDB but panics on error. It returns a cleanup function that does not return an error (panics on close failure).
func PrepareTestDB ¶
PrepareTestDB opens an in-memory SQLite database, runs AutoMigrate on the provided models, and returns the *gorm.DB, a cleanup function and an error. The cleanup function closes the underlying sql.DB connection.
func QuoteColumn ¶
QuoteColumn returns the column name wrapped in backticks, escaping any backticks inside.
func SQLTxFromContext ¶
SQLTxFromContext retrieves the underlying *sql.Tx stored in the context by the Transactor.
func SafeMigrate ¶
SafeMigrate runs AutoMigrate with a lightweight migration lock to avoid concurrent migrations. Retries up to 5 times (2 s apart) to handle fast container restarts where the previous process's lock hasn't expired yet. Stale locks (held longer than 30 s) are cleared automatically.
func SimulateConnectionLoss ¶
SimulateConnectionLoss temporarily removes a named connection from the registry and returns a restore function. Use in tests to verify error handling when the database is unavailable.
Example:
restore := database.SimulateConnectionLoss(reg, "local") defer restore() // code under test should now receive ErrConnectionNotFound
Types ¶
type BaseRepository ¶
type ConnectionConfig ¶
type ConnectionConfig struct {
// Name is the key used to retrieve this connection later.
// e.g. "local", "shared", "etx_hr"
Name string
// Driver is the database driver name (currently "mysql" or "sqlite").
Driver string
Host string
Port string
Database string
Username string
Password string
// SQLMode overrides the MySQL sql_mode for every session opened by this pool.
// Useful when the application must connect to servers with different default
// modes (e.g. MySQL 5.1 in production vs MySQL 8 locally).
//
// Provide a comma-separated list of mode names, or an empty string to use the
// server's default. Common values:
//
// "" — use server default (production MySQL 5)
// "ALLOW_INVALID_DATES,NO_ENGINE_SUBSTITUTION" — permit zero dates (local MySQL 8)
//
// See https://dev.mysql.com/doc/refman/8.0/en/sql-mode.html for the full list.
SQLMode string
// Pool settings — zero values use the defaults below.
MaxIdleConns int // default: 5
MaxOpenConns int // default: 75
ConnMaxLifetime int // minutes, default: 5
// Debug enables GORM query logging.
Debug bool
}
ConnectionConfig holds everything needed to open one database connection pool. Applications build these at startup and register them with the Registry.
database.ConnectionConfig{
Name: "local",
Host: os.Getenv("LOCAL_DB_HOST"),
Port: os.Getenv("LOCAL_DB_PORT"),
Database: os.Getenv("LOCAL_DB_DATABASE"),
Username: os.Getenv("LOCAL_DB_USERNAME"),
Password: os.Getenv("LOCAL_DB_PASSWORD"),
}
type ErrConnectionFailed ¶
ErrConnectionFailed is returned when the database driver fails to open.
func (ErrConnectionFailed) Error ¶
func (e ErrConnectionFailed) Error() string
func (ErrConnectionFailed) Unwrap ¶
func (e ErrConnectionFailed) Unwrap() error
type ErrConnectionNotFound ¶
type ErrConnectionNotFound struct {
Name string
}
ErrConnectionNotFound is returned when a named connection has not been registered.
func (ErrConnectionNotFound) Error ¶
func (e ErrConnectionNotFound) Error() string
type ErrInvalidConfig ¶
ErrInvalidConfig is returned when a ConnectionConfig is missing required fields.
func (ErrInvalidConfig) Error ¶
func (e ErrInvalidConfig) Error() string
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry manages a set of named database connection pools. It replaces the package-level global connection map from the original code.
Create one Registry per application at startup, register all connections, then pass it to your router/handlers via dependency injection.
Applications with one database:
reg := database.NewRegistry(cfg)
reg.MustRegister(database.ConnectionConfig{Name: "local", ...})
Applications with multiple databases:
reg.MustRegister(database.ConnectionConfig{Name: "local", ...})
reg.MustRegister(database.ConnectionConfig{Name: "shared", ...})
reg.MustRegister(database.ConnectionConfig{Name: "etx_hr", ...})
func NewRegistry ¶
func NewRegistry(log *slog.Logger, cfg RegistryConfig) *Registry
NewRegistry creates a new empty Registry with the given options.
func NewRegistryFromConfigs ¶
func NewRegistryFromConfigs(log *slog.Logger, rcfg RegistryConfig, conns []ConnectionConfig) *Registry
NewRegistryFromConfigs creates a new Registry with the given options and pre-registered connections.
func NewTestRegistry ¶
NewTestRegistry creates a Registry pre-populated with SQLite in-memory connections for the given names. Use this in tests instead of PrepareTestDatabase.
Each name gets its own isolated in-memory SQLite database. The returned cleanup function closes all connections.
Example:
reg, cleanup := database.NewTestRegistry(t, "local", "shared")
defer cleanup()
// Migrate your test schemas
conn := reg.MustGet("local")
conn.AutoMigrate(&entities.Customer{})
func (*Registry) AddConnection ¶
AddConnection lets you inject a pre-built *gorm.DB directly. Useful in tests where you want to inject a SQLite in-memory connection without going through the MySQL driver.
Example:
conn, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
reg.AddConnection("local", conn)
func (*Registry) CloseAll ¶
CloseAll closes all registered connection pools. Call during graceful shutdown.
func (*Registry) Get ¶
Get returns the *gorm.DB pool for the given name. Returns ErrConnectionNotFound if the name was never registered.
func (*Registry) MustGet ¶
MustGet is like Get but panics on error. Use in middleware where a missing connection is a programming error, not a runtime condition.
func (*Registry) MustRegister ¶
func (r *Registry) MustRegister(cfg ConnectionConfig)
MustRegister is like Register but panics on error. Use at application startup where a missing DB connection is unrecoverable.
func (*Registry) Primary ¶
Primary returns the default database connection. Panics if no connections have been registered.
func (*Registry) PrimaryName ¶
func (*Registry) Register ¶
func (r *Registry) Register(cfg ConnectionConfig) error
Register opens a connection pool for the given config and stores it under cfg.Name. Returns an error if the config is invalid or the connection cannot be opened. Safe to call concurrently.
type RegistryConfig ¶
type RegistryConfig struct {
// LogLevel controls GORM query logging.
// "silent", "error", "warn", "info" — defaults to "error".
LogLevel string
// SlowQueryThreshold is the duration above which a query is considered slow.
// Default: 1 second.
SlowQueryThreshold time.Duration
}
RegistryConfig holds the options used when creating a Registry.
type Transactor ¶
type Transactor interface {
WithinTransaction(ctx context.Context, fn func(ctx context.Context) error) error
}
Transactor defines the interface for executing operations within a transaction.
func NewTransactor ¶
func NewTransactor(conn *gorm.DB) Transactor
NewTransactor creates a new Transactor instance.
func NewTransactorFromRegistry ¶
func NewTransactorFromRegistry(reg *Registry, dbName string) (Transactor, error)
NewTransactorFromRegistry creates a Transactor for the named connection in reg. Pass an empty string for dbName to use the primary connection.