Skip to content

conn: expose the OK/EOF packet warning count - #2

Merged
morgo merged 3 commits into
masterfrom
warning-count
Aug 28, 2026
Merged

conn: expose the OK/EOF packet warning count#2
morgo merged 3 commits into
masterfrom
warning-count

Conversation

@morgo

@morgo morgo commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

What

MySQL reports how many warnings a statement raised in the packet that terminates its response. The driver dropped the field on the floor — a literal // warning count [2 bytes] comment in handleOkPacket with no parse behind it, and three copies of the resultset-terminator reader that skipped straight past it to the status flags.

This records the count on the connection, clears it in clearResult(), and exposes it as (*mysqlConn).Warnings() for callers reaching the connection through (*sql.Conn).Raw.

Why

Without the count, a caller that wants to surface warnings has only bad options. The warnings themselves live in per-connection state that only SHOW WARNINGS can read, so reading them costs a round trip; with no count to gate on, that round trip has to be spent after every statement, warnings or not.

The count is what makes it affordable — and for a proxy it is also what has to go into its own OK packet, since clients such as Connector/J only issue SHOW WARNINGS when the count they were handed is non-zero.

Concretely: strata (a Vitess-compatible MySQL proxy) has no backend connection affinity outside transactions, so it must read a statement's warnings eagerly, while it still holds the connection. Gating that on the count keeps the cost at one integer comparison for the overwhelming majority of statements, which raise nothing.

The terminator refactor

The three inlined resultset-terminator readers (textRows.readRow, binaryRows.readRow, skipRows) collapse into one readResultsetTerminator method. They were already byte-for-byte identical; the warning count made keeping them identical harder, because an EOF packet orders warnings before status and an 0xFE-headered OK packet orders them after — so each copy would have had to get that backwards independently.

Validity contract

Documented on the accessor: valid from the moment the statement's response is complete until the next statement starts on that connection. For a resultset that means after the rows are drained or Closed — the terminating packet carrying the count has not arrived before then. An error packet carries no count, so a failed statement leaves the previous one's value in place.

Tests

  • TestReadWarnings — little-endian decode and the short-buffer guard.
  • TestHandleOkPacketWarnings — OK-packet parse, and that clearResult() resets it so one statement's count cannot be read as the next one's.
  • TestHandleOkPacketWarningsWithMoreResults — the count is recorded before the statusMoreResultsExists early return, so each statement of a multi-statement leaves its own behind.
  • TestReadResultsetTerminatorWarnings — both packet layouts, pinning the field order that differs between them.
  • TestWarnings — integration: clean INSERT (0), DROP TABLE IF EXISTS on a missing table (note 1051, via the OK packet), a truncating CAST (via the resultset terminator), and a clean SELECT (0).

Full suite passes against MySQL 8. TestLoadData fails on my local server only because it has local_infile=OFF; it fails identically on master.

MySQL reports how many warnings a statement raised in the packet that
terminates its response, but the driver dropped the field on the floor: a
literal `// warning count [2 bytes]` comment in handleOkPacket with no
parse behind it, and three copies of the resultset terminator reader that
skipped straight past it to the status flags.

Without the count, a caller that wants to surface warnings has only bad
options. The diagnostics themselves live in per-connection state that
only SHOW WARNINGS can read, so reading them costs a round trip; with no
count to gate on, that round trip has to be spent after every statement,
warnings or not. The count is what makes it affordable — and it is also
what a proxy needs to put in its own OK packet, since clients such as
Connector/J only ask for warnings when the count they were given is
non-zero.

Record it on the connection alongside the status flags, clear it in
clearResult() so one statement's count cannot be read as the next one's,
and expose it as (*mysqlConn).Warnings() for callers reaching the
connection through (*sql.Conn).Raw.

The three inlined resultset terminator readers collapse into one
readResultsetTerminator method. They were already identical; the warning
count made keeping them identical harder, because an EOF packet orders
warnings before status and an 0xFE-headered OK packet orders them after,
so each copy would have had to get that backwards independently.
@coveralls

coveralls commented Aug 27, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 33120091003

Coverage increased (+0.05%) to 82.659%

Details

  • Coverage increased (+0.05%) from the base build.
  • Patch coverage: 34 of 34 lines across 3 files are fully covered (100%).
  • 4 coverage regressions across 1 file.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

4 previously-covered lines in 1 file lost coverage.

File Lines Losing Coverage Coverage
compress.go 4 78.42%

Coverage Stats

Coverage Status
Relevant Lines: 4175
Covered Lines: 3451
Line Coverage: 82.66%
Coverage Strength: 2569384.31 hits per line

💛 - Coveralls

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds support for tracking and exposing the MySQL protocol warning-count field (from OK / EOF / 0xFE-OK packets) on a per-connection basis, so callers using (*sql.Conn).Raw can cheaply decide whether to issue SHOW WARNINGS.

Changes:

  • Add mysqlConn.warnings storage and a (*mysqlConn).Warnings() uint16 accessor, resetting the value at statement start via clearResult().
  • Parse and record warning counts from OK packets and from resultset terminator packets, refactoring duplicated terminator parsing into readResultsetTerminator.
  • Add unit and integration tests covering little-endian decoding, OK/EOF layouts, multi-statement behavior, and end-to-end warning counts.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
warnings.go Documents and exposes (*mysqlConn).Warnings() for consumers via (*sql.Conn).Raw.
warnings_test.go Adds unit/integration tests validating warning-count parsing and lifecycle behavior.
packets.go Implements warning-count parsing, adds readResultsetTerminator, and resets warnings in clearResult().
connection.go Adds warnings uint16 to mysqlConn state.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread warnings.go Outdated
Comment thread packets.go Outdated
morgo and others added 2 commits August 27, 2026 15:43
Both comments on the PR were right, and both were about wording that
described an earlier draft rather than the code.

handleOkPacket no longer has a statusMoreResultsExists early return, so
"recorded before the more-results check" described a check that is not
there. Say what the code now does and why: the count is recorded
unconditionally so each statement of a multi-statement leaves its own
behind.

Warnings() claimed a failed statement leaves the *previous* count in
place. It does not: clearResult() zeroes the count when the failing
statement starts, and an error packet carries no count, so a standalone
failure reports zero. The previous-count case is real only inside a
multi-statement, where the last successful statement's OK packet already
recorded one. Both cases are now stated, and the standalone one is
pinned by a test.
clearResult() was the wrong reset point. It runs at the start of every
statement, but it also runs from (*mysqlRows).Close — after skipRows has
already read the packet that carries the count. A caller that closes a
resultset without draining it (a proxy forwarding rows elsewhere) had the
count zeroed out from under it before it could read it.

Reset in resetSequence() instead: sending a command is what ends the
previous statement's diagnostics, which is the semantic the count is
supposed to follow anyway.

The existing integration coverage missed this because draining to EOF
nils out rows.mc, so Close returns early and never reaches clearResult.
Cover the undrained path too, plus a unit test pinning that clearResult
specifically does not touch the count.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@morgo
morgo merged commit 0208bfc into master Aug 28, 2026
70 checks passed
@morgo
morgo deleted the warning-count branch August 28, 2026 15:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants