# 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): `Executor` uses `sql.Result`, `*sql.Rows`, and `*sql.Row`. Method names follow `database/sql` convention: `ExecContext`, `QueryContext`, `QueryRowContext`. The `postgres` module uses pgx types; these two are intentionally incompatible. - **No ctx on Tx.Commit/Rollback** (ADR-002): `database/sql` does not support per-call context on `Commit` or `Rollback`. The `mysql.Tx` interface honestly omits `ctx` from these methods rather than accepting and ignoring it. `UnitOfWork.Do` calls `tx.Commit()` and `tx.Rollback()` without context. - **Driver import alias** (ADR-003): The `go-sql-driver/mysql` driver package name collides with the package name `mysql`. In `errors.go` it is imported as `mysqldrv` to disambiguate. In `mysql.go` it is imported with `_` for side-effect registration only. - **Error mapping via MySQLError.Number**: `HandleError` type-asserts to `*mysqldrv.MySQLError` and 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 `postgres` module — active `*sql.Tx` is stored under `ctxTxKey{}` in the context. `GetExecutor(ctx)` returns the transaction if present, otherwise `*sql.DB`. ## Patterns Lifecycle registration: ```go db := mysql.New(logger, cfg) lc.Append(db) r.Get("/health", health.NewHandler(logger, db)) ``` Unit of Work: ```go 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: ```go 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 is `database/sql`; the two are distinct. - Do not add a `ctx` parameter to `Tx.Commit()` or `Tx.Rollback()`. `database/sql` does not support it; accepting and silently ignoring a context would be misleading. - Do not import `github.com/go-sql-driver/mysql` directly in application code to type-assert on `*mysql.MySQLError`. Use `db.HandleError(err)` instead — it maps driver errors to portable `xerrors` codes. - Do not match MySQL errors by message string. Use `HandleError` which switches on `mysqldrv.MySQLError.Number`. - Do not add package-level `*sql.DB` variables. `mysqlComponent` is the unit of construction; use dependency injection. - Do not forget `defer rows.Close()` after `QueryContext` — unclosed `*sql.Rows` hold connections from the pool. ## Testing Notes - `compliance_test.go` asserts at compile time that `mysql.New(...)` satisfies `mysql.Component`. - Integration tests (real queries, transaction rollback) require a live MySQL or MariaDB instance, typically provided by a CI service container. - `HandleError` can be unit-tested by constructing `*mysqldrv.MySQLError{Number: 1062}` directly — no database connection needed. - Pool initialization happens in `OnInit`, not `New`. Mocking the `Client` interface bypasses pool setup entirely, making unit tests straightforward.