database

package
v1.3.16 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 11, 2026 License: MIT Imports: 16 Imported by: 0

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

View Source
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

func ApplyFullTextSearch(query *gorm.DB, columns []string, searchTerm string) *gorm.DB

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

func EnableDBTracing(conn *gorm.DB, tr tracing.Tracer) error

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

func EscapeBackticks(s string) string

EscapeBackticks escapes backticks in a column name for safe use in SQL identifiers.

func EscapeLike

func EscapeLike(s string) string

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

func GetNextOrderNumber[T any](db *gorm.DB, parentColumn string, parentID int) (int, error)

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

func MustPrepareTestDB(models ...interface{}) (*gorm.DB, func())

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

func PrepareTestDB(models ...interface{}) (*gorm.DB, func() error, error)

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

func QuoteColumn(s string) string

QuoteColumn returns the column name wrapped in backticks, escaping any backticks inside.

func SQLTxFromContext

func SQLTxFromContext(ctx context.Context) (*sql.Tx, bool)

SQLTxFromContext retrieves the underlying *sql.Tx stored in the context by the Transactor.

func SafeMigrate

func SafeMigrate(db *gorm.DB, models ...interface{}) error

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

func SimulateConnectionLoss(reg *Registry, name string) func()

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

func TxFromContext

func TxFromContext(ctx context.Context) (*gorm.DB, bool)

TxFromContext retrieves the transaction from the context if it exists.

Types

type BaseRepository

type BaseRepository struct {
	Conn *gorm.DB
}

func (*BaseRepository) DB

func (b *BaseRepository) DB(ctx context.Context) *gorm.DB

DB returns the transaction from context if present, otherwise the base connection. Embed this in every repository to get transaction propagation for free.

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

type ErrConnectionFailed struct {
	Name string
	Err  error
}

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

type ErrInvalidConfig struct {
	Name   string
	Reason string
}

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

func NewTestRegistry(names ...string) (*Registry, func() error)

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

func (r *Registry) AddConnection(name string, conn *gorm.DB)

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

func (r *Registry) CloseAll() error

CloseAll closes all registered connection pools. Call during graceful shutdown.

func (*Registry) Get

func (r *Registry) Get(name string) (*gorm.DB, error)

Get returns the *gorm.DB pool for the given name. Returns ErrConnectionNotFound if the name was never registered.

func (*Registry) Has

func (r *Registry) Has(name string) bool

Has returns true if a connection with the given name is registered.

func (*Registry) MustGet

func (r *Registry) MustGet(name string) *gorm.DB

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) Names

func (r *Registry) Names() []string

Names returns all registered connection names.

func (*Registry) Primary

func (r *Registry) Primary() *gorm.DB

Primary returns the default database connection. Panics if no connections have been registered.

func (*Registry) PrimaryName

func (r *Registry) PrimaryName() string

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.

Directories

Path Synopsis
Package sqlc is a scaffold for integrating SQLC-generated query code with the go-core database infrastructure.
Package sqlc is a scaffold for integrating SQLC-generated query code with the go-core database infrastructure.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL