- add Config.Charset (MYSQL_CHARSET, default "utf8mb4"): connection character set sent as SET NAMES during handshake; previously hardcoded - add Config.Loc (MYSQL_LOC, default "UTC"): IANA timezone for time.Time ↔ DATE/DATETIME conversion; previously hardcoded - add Config.ParseTime (MYSQL_PARSE_TIME, default "true"): driver-level DATE/DATETIME → time.Time mapping; valid values "true"/"false"; previously hardcoded - update DSN() to derive parameters from Config fields with empty-means-default semantics; existing Config literals produce identical DSN output (backward compatible) - remove unused url.URL construction from DSN(); params now built directly via url.Values - document collation DSN limitation in Config godoc, CLAUDE.md, RELEASE.md, CHANGELOG.md: go-sql-driver v1.8.x uses 1-byte handshake collation IDs (max 255); MariaDB 11.4+ collations such as utf8mb4_uca1400_as_cs exceed that range — set collation at the database/table level in schema migrations instead
4.9 KiB
4.9 KiB
mysql
database/sql-backed MySQL client with launcher lifecycle, health check integration, and unit-of-work transaction management.
Purpose
Provides a Component that manages a *sql.DB connection pool, satisfies the launcher.Component lifecycle hooks (OnInit, OnStart, OnStop), and implements health.Checkable (priority: critical). Also provides NewUnitOfWork for wrapping multiple repository operations in a single transaction via context injection.
Tier & Dependencies
Tier 3 (infrastructure) — depends on:
code.nochebuena.dev/go/health(Tier 1)code.nochebuena.dev/go/launcher(Tier 1)code.nochebuena.dev/go/logz(Tier 0)code.nochebuena.dev/go/xerrors(Tier 0)github.com/go-sql-driver/mysql(external driver)
Key Design Decisions
- database/sql native types (ADR-001):
Executorusessql.Result,*sql.Rows, and*sql.Row. Method names followdatabase/sqlconvention:ExecContext,QueryContext,QueryRowContext. Thepostgresmodule uses pgx types; these two are intentionally incompatible. - No ctx on Tx.Commit/Rollback (ADR-002):
database/sqldoes not support per-call context onCommitorRollback. Themysql.Txinterface honestly omitsctxfrom these methods rather than accepting and ignoring it.UnitOfWork.Docallstx.Commit()andtx.Rollback()without context. - Driver import alias (ADR-003): The
go-sql-driver/mysqldriver package name collides with the package namemysql. Inerrors.goit is imported asmysqldrvto disambiguate. Inmysql.goit is imported with_for side-effect registration only. - Error mapping via MySQLError.Number:
HandleErrortype-asserts to*mysqldrv.MySQLErrorand switches on.Number. Error codes 1062 (duplicate key) →ErrAlreadyExists; 1216, 1217, 1451, 1452 (foreign key violations) →ErrInvalidInput.sql.ErrNoRows→ErrNotFound. - UnitOfWork via context injection: Same pattern as the
postgresmodule — active*sql.Txis stored underctxTxKey{}in the context.GetExecutor(ctx)returns the transaction if present, otherwise*sql.DB. - Configurable DSN parameters (v0.9.1):
Config.Charset,Config.Loc, andConfig.ParseTimeare optional string fields that control the corresponding DSN parameters. Empty value means "use the safe default" (utf8mb4,UTC,true). ExistingConfigliterals that do not set these fields behave identically to v0.9.0. - Collation DSN limitation:
go-sql-driverv1.8.x negotiates the connection collation via a 1-byte handshake ID (max 255). MariaDB 11.4+ collations such asutf8mb4_uca1400_as_csexceed that range and cannot be specified in the DSN. Set collation at the schema level (database/table DDL) instead.
Patterns
Lifecycle registration:
db := mysql.New(logger, cfg)
lc.Append(db)
r.Get("/health", health.NewHandler(logger, db))
Unit of Work:
uow := mysql.NewUnitOfWork(logger, db)
err := uow.Do(ctx, func(ctx context.Context) error {
exec := db.GetExecutor(ctx) // returns active *sql.Tx
_, err := exec.ExecContext(ctx, "INSERT INTO ...")
return err
})
Error handling in repository code:
rows, err := db.GetExecutor(ctx).QueryContext(ctx, "SELECT ...")
if err != nil {
return db.HandleError(err)
}
defer rows.Close()
What to Avoid
- Do not use pgx types (
pgx.Rows,pgconn.CommandTag, etc.) in code that depends on this module. This isdatabase/sql; the two are distinct. - Do not add a
ctxparameter toTx.Commit()orTx.Rollback().database/sqldoes not support it; accepting and silently ignoring a context would be misleading. - Do not import
github.com/go-sql-driver/mysqldirectly in application code to type-assert on*mysql.MySQLError. Usedb.HandleError(err)instead — it maps driver errors to portablexerrorscodes. - Do not match MySQL errors by message string. Use
HandleErrorwhich switches onmysqldrv.MySQLError.Number. - Do not add package-level
*sql.DBvariables.mysqlComponentis the unit of construction; use dependency injection. - Do not forget
defer rows.Close()afterQueryContext— unclosed*sql.Rowshold connections from the pool. - Do not pass a MariaDB 11.4+ collation (e.g.
utf8mb4_uca1400_as_cs) asConfig.Collationor any DSN parameter — the driver will fail at connect time with "unknown collation". Set collation in schema migrations at the database/table level instead.
Testing Notes
compliance_test.goasserts at compile time thatmysql.New(...)satisfiesmysql.Component.- Integration tests (real queries, transaction rollback) require a live MySQL or MariaDB instance, typically provided by a CI service container.
HandleErrorcan be unit-tested by constructing*mysqldrv.MySQLError{Number: 1062}directly — no database connection needed.- Pool initialization happens in
OnInit, notNew. Mocking theClientinterface bypasses pool setup entirely, making unit tests straightforward.