refactor: split dbx package into domain files

Reorganize monolithic dbx.go and utility.go into focused files by
concern, add package doc.go, and gitignore coverage.out.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-29 17:46:54 -07:00
parent 14686a83cb
commit f9befa1b0a
16 changed files with 652 additions and 583 deletions
+138
View File
@@ -0,0 +1,138 @@
package dbx
import (
"database/sql"
"errors"
"gitea.auvem.com/go-toolkit/app"
"gitea.auvem.com/go-toolkit/dbx/internal/dbxshared"
"github.com/go-jet/jet/v2/qrm"
)
const (
// ModuleDBName is the name of the database module.
ModuleDBName = "database"
// DialectPostgres is the PostgreSQL dialect.
DialectPostgres Dialect = "postgres"
// DialectMySQL is the MySQL dialect.
DialectMySQL Dialect = "mysql"
)
// Dialect is the SQL dialect used by the database connection.
type Dialect string
// String implements the Stringer interface for Dialect.
func (d Dialect) String() string {
return string(d)
}
// dbState stores package-level state for the database connection.
type dbState struct {
sqlDB *sql.DB
config *DBConfig
dialect Dialect
debugLog bool
}
var (
// ErrNoRows is returned when a query returns no rows.
ErrNoRows = qrm.ErrNoRows
// ErrValueIsZero is returned when an expected value is missing.
ErrValueIsZero = errors.New("value is zero-value for type")
state = dbState{}
)
// SQLO returns the current SQL database handle.
func SQLO() *sql.DB {
dbxshared.DBModule.RequireLoaded("dbx.SQLO requires database module")
if state.sqlDB == nil {
panic("SQL database not initialized")
}
return state.sqlDB
}
// ModuleDB returns the database module with the provided configuration.
// dialect specifies the SQL dialect to use (e.g., DialectPostgres, DialectMySQL).
// config specifies the database connection configuration.
// forceDebugLog forces debug logging to be enabled regardless of the config setting.
func ModuleDB(dialect Dialect, config *DBConfig, forceDebugLog bool) *app.Module {
if dbxshared.DBModule != nil {
panic("ModuleDB initialized multiple times")
}
if config == nil {
panic("ModuleDB requires a non-nil DBConfig")
}
state.config = config
state.dialect = dialect
state.debugLog = config.DebugLog || forceDebugLog
dbxshared.DBModule = app.NewModule(ModuleDBName, app.ModuleOpts{
Setup: setupDB,
Teardown: teardownDB,
})
return dbxshared.DBModule
}
func setupDB(m *app.Module) error {
if state.sqlDB != nil && state.sqlDB.Ping() == nil {
m.Logger().Warn("Database connection already established")
return nil
}
logArgs := []any{
"user", state.config.User,
"name", state.config.Name,
"uri", state.config.URI,
"dialect", state.dialect,
}
var err error
state.sqlDB, err = sql.Open(string(state.dialect), state.config.ConnectionString(state.dialect))
if err != nil {
logArgs = append(logArgs, "err", err)
m.Logger().Error("Couldn't open SQL database", logArgs...)
return err
}
if err := state.sqlDB.Ping(); err != nil {
logArgs = append(logArgs, "err", err)
m.Logger().Error("Couldn't ping SQL database", logArgs...)
return err
}
state.sqlDB.SetMaxOpenConns(state.config.MaxConn)
stats := state.sqlDB.Stats()
m.Logger().Info(
"Connected to SQL database",
"user", state.config.User,
"name", state.config.Name,
"uri", state.config.URI,
"maxConnections", stats.MaxOpenConnections,
"currConnections", stats.OpenConnections,
)
if state.debugLog {
dbxshared.InitLogger(state.dialect)
}
return nil
}
func teardownDB(m *app.Module) error {
if state.sqlDB == nil {
return nil
}
if err := state.sqlDB.Close(); err != nil {
m.Logger().Error("Couldn't close database", "err", err)
return err
}
m.Logger().Info("Closed database connection")
return nil
}