MySQL
Read a config or secret value from a MySQL / MariaDB table.
| Scheme | mysql:// |
| Module | github.com/xavidop/mamori/providers/mysql |
| Sensitive | no (opt-in with WithSensitive) |
| Watch | poll |
| Auth | DSN (WithDSN / MYSQL_DSN) |
Install
go get github.com/xavidop/mamori/providers/mysql
import _ "github.com/xavidop/mamori/providers/mysql"
Using the ref
A mysql:// ref points at one row of a MySQL or MariaDB table (a key/value lookup), optionally selecting a field from a JSON value.
mysql://<table>/<key>[#json-field][?key_col=<c>&val_col=<c>]
| Part | Required | What it means |
|---|---|---|
<table> | yes | The table to read. Validated against a strict identifier allowlist, then backtick-quoted. |
<key> | yes | The row key, bound as a ? placeholder (never interpolated) and matched with WHERE <key_col> = ?. |
#json-field | no | Parse the value column as a JSON object and return one field (via mamori.SelectKey). |
?key_col=<c> | no | Override the key column name (default key). |
?val_col=<c> | no | Override the value column name (default value). |
Examples
mysql://settings/greeting- runsSELECT value FROM settings WHERE key = ?with the key bound togreeting.mysql://settings/workers?val_col=int_value- reads the same row but from theint_valuecolumn instead ofvalue.mysql://config/db#host- reads the JSON object at keydband returns itshostfield.mysql://settings/feature_x?key_col=name&val_col=data- runsSELECT data FROM settings WHERE name = ?.
type Config struct {
Greeting string `source:"mysql://settings/greeting"`
Workers int `source:"mysql://settings/workers?val_col=int_value"`
}
The row key is always a bound ? placeholder; the table and column names are validated against a strict identifier allowlist (^[A-Za-z_][A-Za-z0-9_]*$) and backtick-quoted before any query runs, so a ref can never inject SQL. Value.Version is a content hash of the value by default, or a native revision column via WithVersionColumn. Values are non-sensitive unless you set WithSensitive(true) or wrap the field in secret.String.
Watch
MySQL has no built-in change notification, so mamori polls (WithPollInterval + jitter).
Configuration
import mysqlprov "github.com/xavidop/mamori/providers/mysql"
mamori.WithProvider(mysqlprov.New(mysqlprov.WithDSN("user:pass@tcp(mysql:3306)/appdb")))
Close() releases a pool this provider opened itself; a pool injected with WithDB is owned by the caller and stays open. Either way Close is terminal, not a no-op: after it returns, every Resolve on that provider fails with errors.Is(err, mamori.ErrUnavailable). A subsequent Load fails for the fields it feeds, and under Watch the default onfail policy (keeplast) freezes them at their last known-good value and reports the error to your error handler.
A default: tag does not cover this since it only applies to genuine absence (ErrNotFound), never to an error; falling back on error requires the explicit per-field opt-in onfail:"default".
Error classification
Beyond the not-found case, query failures are classified by the driver’s numeric server error code so mamori.ErrorKind can distinguish them:
| Number | mamori kind |
|---|---|
1044 (ER_DBACCESS_DENIED_ERROR), 1142 (ER_TABLEACCESS_DENIED_ERROR) | permission_denied |
1045 (ER_ACCESS_DENIED_ERROR) | unauthenticated |
1040 (ER_CON_COUNT_ERROR), 1203 (ER_TOO_MANY_USER_CONNECTIONS) | unavailable |
| anything else | unknown |
MySQL has no rate-limit error class, so nothing maps to rate_limited, and the syntax-error code is unreachable through this provider’s fixed query template, so nothing maps to invalid either. Numbers not listed above report unknown rather than being guessed at, and the original *mysqldriver.MySQLError stays reachable with errors.As.
A refused TCP connection (client errors 2002/2003, a fully down database) is not a *MySQLError - it is a net-level error whose exact wrapped type is not stable enough to match reliably, so it deliberately reports unknown rather than being type-guessed. A connection-limit rejection (1040/1203, a reachable but overloaded database) still reports unavailable.
Verified with an in-memory fake (including an identifier-allowlist rejection test); live behavior is covered by //go:build integration tests.