conn: expose the OK/EOF packet warning count - #2
Merged
Merged
Conversation
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.
Coverage Report for CI Build 33120091003Coverage increased (+0.05%) to 82.659%Details
Uncovered ChangesNo uncovered changes found. Coverage Regressions4 previously-covered lines in 1 file lost coverage.
Coverage Stats
💛 - Coveralls |
There was a problem hiding this comment.
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.warningsstorage and a(*mysqlConn).Warnings() uint16accessor, resetting the value at statement start viaclearResult(). - 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.
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>
eeSeeGee
approved these changes
Aug 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 inhandleOkPacketwith 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 WARNINGScan 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 WARNINGSwhen 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 onereadResultsetTerminatormethod. They were already byte-for-byte identical; the warning count made keeping them identical harder, because an EOF packet orders warnings before status and an0xFE-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 thatclearResult()resets it so one statement's count cannot be read as the next one's.TestHandleOkPacketWarningsWithMoreResults— the count is recorded before thestatusMoreResultsExistsearly 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 EXISTSon a missing table (note 1051, via the OK packet), a truncatingCAST(via the resultset terminator), and a clean SELECT (0).Full suite passes against MySQL 8.
TestLoadDatafails on my local server only because it haslocal_infile=OFF; it fails identically onmaster.