Files
mysql/docs/adr/ADR-001-database-sql-native-types.md

38 lines
2.7 KiB
Markdown
Raw Normal View History

# ADR-001: database/sql Native Types
**Status:** Accepted
**Date:** 2026-03-18
## Context
Go's standard `database/sql` package was designed as a database-agnostic abstraction. Using it for MySQL is the conventional approach: the `go-sql-driver/mysql` package registers itself as a `database/sql` driver, and all query operations are expressed through `*sql.DB`, `*sql.Tx`, `*sql.Rows`, `*sql.Row`, and `sql.Result`.
An alternative would be to use a MySQL-specific client library analogous to pgx for PostgreSQL. However, there is no dominant MySQL-specific client with the same level of ecosystem adoption and capability that pgx has for PostgreSQL. Using `database/sql` with a standard driver is the practical and broadly understood choice.
The `postgres` module deliberately chose pgx native types. MySQL is the counterpart — it uses `database/sql` native types. The two modules are not interchangeable, which is by design.
## Decision
The `mysql` module's `Executor` interface uses `database/sql` types throughout:
```go
type Executor interface {
ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
}
```
Method names follow the `database/sql` convention (`ExecContext`, `QueryContext`, `QueryRowContext`) rather than the pgx convention (`Exec`, `Query`, `QueryRow`). This matches the underlying `*sql.DB` and `*sql.Tx` method signatures exactly, meaning `*sql.DB` satisfies `Executor` structurally without any wrapping.
The `mysqlComponent` embeds `*sql.DB` and delegates directly. `mysqlTx` wraps `*sql.Tx` and delegates to it.
## Consequences
- **Positive**: Familiarity — `database/sql` is Go's standard library API, known to most Go developers.
- **Positive**: `*sql.DB` and `*sql.Tx` natively satisfy the `Executor` interface, reducing the need for wrapper code.
- **Positive**: Any `database/sql`-compatible driver (MariaDB, TiDB, etc.) could be substituted by changing the driver registration without touching the interface.
- **Negative**: `sql.Result` is less informative than `pgconn.CommandTag` (no command string, only `LastInsertId` and `RowsAffected`). Applications that need PostgreSQL-style command metadata must use the `postgres` module.
- **Negative**: `*sql.Rows` must be closed after use (`defer rows.Close()`). Forgetting this leaks connections back to the pool.
- **Note**: The method naming difference from `postgres` (`ExecContext` vs `Exec`) is intentional and honest — it matches the actual API of the underlying library rather than artificially unifying the two modules.