mysqlproxy prepared target sessions
2026-09-25: account modes and replication command forwarding
MySQL accounts select readonly, readwrite (safe/default), or unrestricted (ordinary read-write). Existing readwrite configurations keep WHERE checks; only explicit unrestricted skips SQL parsing and parser-mode initialization/reset. Gateway-wide readonly still overrides account settings. No storage migration is required.
Client initialization such as SET character_set_results = NULL, DEFAULT, or a
static known charset is accepted in safe and readonly modes because it only
selects result encoding and cannot change how later SQL is parsed. Other
parser-affecting charset and SQL-mode changes remain restricted.
The command loop now relays STATISTICS, PROCESS INFO, SET OPTION, replication registration, BINLOG DUMP/GTID and common administrative commands (the latter remain denied in readonly mode). Dump packets use a dedicated streaming relay, preserving event payloads and sequence wrap until EOF/ERR/disconnect. Closing the client or cancelling its context closes an idle upstream; dump connections close when streaming ends. This supports asynchronous replication, not semi-sync ACK. Registration audit excludes credentials. Unsupported interactive protocols such as CHANGE USER and server cursors remain rejected.
Local TCP protocol fixtures cover streaming and command response shapes; they do not use a live MySQL server. See account modes and change rationale for actual validation.
2026-09-25: safe-mode WHERE checks
Safe/read-only MySQL query and binary prepare requests pass CheckWriteSafety before forwarding. UPDATE/DELETE must have their own outer WHERE, including safe read-write accounts. Dynamic SQL text and unverifiable SQL are rejected. Connection setup and reset establish a fixed parser-compatible SQL mode and configured charset; prepared standby sessions inherit the same setup. Client attempts to change the parser environment are restricted. There is no schema migration or SQL rewrite. Compatibility and fixture validation are documented in MySQL write safety and change rationale.
2026-09-22: protocol-authenticated idle standby
Reason and behavior
Previously target protocol connections were created only after a downstream client needed them. Handler.PrepareSession now eagerly opens and authenticates one real single-node target session without a downstream client. It clears PreparedTarget on the setup snapshot to prevent recursive borrowing. The parent gateway pool owns publishing, periodic heartbeats, replacement, rejection backoff and shutdown.
MySQL uses COM_PING. The prepared initial database is p.Database; a nonempty client database override different from p.Database never checks out the default-database standby. Existing TLS, compression, collation and read-only initialization remain in open.
Checkout checks current target credential validity before accessing the pool. A prepared value is checked against target identity/account, database/authSource, outbound route, TLS, compression and session options. The request context is used for a fresh heartbeat before handing the session to protocol serving code. A failed heartbeat closes the standby and falls back to the existing new-connection path; a canceled request returns its context error. No business command is replayed. Checked-out connections are never returned to this standby pool: transactions, cursors and subscriptions cannot be shared with another client.
Preparation is bounded by the configured connect timeout (15 seconds by default). Heartbeats have a five-second upper bound and respect earlier context cancellation. Their cancellation callback is stopped, or joined if already running, before returning ownership. Canceling a completed setup or heartbeat context cannot close a successfully handed-off session.
Setup failures preserve only typed authentication, permission and host-identity rejection categories using outbound.FailureKind. Unknown errors use a generic message. Original server-controlled error text is not retained in the returned error chain, and there is no string-based password-error guessing. The parent pool can stop reconnecting explicit rejections instead of repeatedly attempting login.
Scope and compatibility
Only this protocol directory is changed by this adapter task. No schema migration, production data change, new explicit transaction or business write is introduced. Disabled persistence and clustered targets continue through the original open path. Existing protocol serving/audit behavior remains in place. Shared pool wiring and project-wide change-rationale documentation belong to the parent integration task.
Actual validation
Go 1.25.0, GOFLAGS=-p=2, GOTMPDIR=artifacts/portable-validation/build-work and its cache were used for:
- go test ./internal/protocol/mysqlproxy ./internal/protocol/pgproxy ./internal/protocol/mongoproxy ./internal/protocol/redisproxy -count=1 -timeout=120s — passed.
- go vet ./internal/protocol/mysqlproxy ./internal/protocol/pgproxy ./internal/protocol/mongoproxy ./internal/protocol/redisproxy — passed.
- Prepared fixture coverage includes eager authentication, Ping, same-connection checkout with a second heartbeat, failed-heartbeat replacement, cancellation, blocked setup/heartbeat deadlines, credential validation, identity/database mismatches, explicit typed authentication rejection and credential-safe errors.
- MySQL/PostgreSQL also verify database overrides leave the default standby available; MongoDB verifies pre-auth hello isolation and custom AuthSource on authentication, idle Ping and checkout Ping; Redis checks buffering/deadlines.
- The attempted targeted race run was unavailable because this environment has CGO disabled (go: -race requires cgo). No race-pass claim is made.
These are local protocol fixtures, not live database or production-server tests. No frontend build, embedded asset build, deployment or commit was performed here.