Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type mysqlConn struct {
capabilities capabilityFlag
extCapabilities extendedCapabilityFlag
status statusFlag
warnings uint16 // managed by resetSequence() and the OK/EOF readers; see Warnings().
sequence uint8
compressSequence uint8
parseTime bool
Expand Down Expand Up @@ -88,6 +89,12 @@ func (mc *mysqlConn) writeWithTimeout(b []byte) (int, error) {
func (mc *mysqlConn) resetSequence() {
mc.sequence = 0
mc.compressSequence = 0
// Sending a command is also what ends the previous command's diagnostics,
// so this is where the warning count resets. Deliberately not clearResult():
// that also runs from (*mysqlRows).Close, which happens after the packet
// carrying the count has been read and would throw the count away before
// the caller could ask for it.
mc.warnings = 0
}

// syncSequence must be called when finished writing some packet and before start reading.
Expand Down
73 changes: 40 additions & 33 deletions packets.go
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,37 @@ func readStatus(b []byte) statusFlag {
return statusFlag(b[0]) | statusFlag(b[1])<<8
}

// readResultsetTerminator records the status flags and warning count carried by
// the packet that ends a resultset: a classic EOF packet, or an OK packet sent
// with an 0xFE header once CLIENT_DEPRECATE_EOF is negotiated. The two layouts
// order those two fields differently, which is why reading them lives in one
// place. data is the whole packet, starting at the 0xFE header.
func (mc *mysqlConn) readResultsetTerminator(data []byte) {
if mc.capabilities&clientDeprecateEOF == 0 {
// Deprecated EOF packet: header, warning count, status flags.
// https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_eof_packet.html
mc.warnings = readWarnings(data[1:])
mc.status = readStatus(data[3:])
return
}
// OK packet with an 0xFE header: status flags precede the warning count.
_, _, n := readLengthEncodedInteger(data[1:]) // affected_rows
_, _, m := readLengthEncodedInteger(data[1+n:]) // last_insert_id
mc.status = readStatus(data[1+n+m:])
mc.warnings = readWarnings(data[1+n+m+2:])
}

// readWarnings reads the two-byte warning count that OK and EOF packets carry
// under CLIENT_PROTOCOL_41. The driver always negotiates that capability, so a
// buffer too short to hold the field means a malformed packet rather than an
// older server; report no warnings rather than panicking on a slice bound.
func readWarnings(b []byte) uint16 {
if len(b) < 2 {
return 0
}
return uint16(b[0]) | uint16(b[1])<<8
}

// Returns an instance of okHandler for codepaths where mysqlConn.result doesn't
// need to be cleared first (e.g. during authentication, or while additional
// resultsets are being fetched.)
Expand All @@ -656,8 +687,7 @@ func (mc *okHandler) conn() *mysqlConn {
return (*mysqlConn)(mc)
}

// clearResult clears the connection's stored affectedRows and insertIds
// fields.
// clearResult clears the connection's stored affectedRows and insertIds.
//
// It returns a handler that can process OK responses.
func (mc *mysqlConn) clearResult() *okHandler {
Expand Down Expand Up @@ -690,11 +720,13 @@ func (mc *okHandler) handleOkPacket(data []byte) error {

// server_status [2 bytes]
mc.status = readStatus(data[1+n+m : 1+n+m+2])
if mc.status&statusMoreResultsExists != 0 {
return nil
}

// warning count [2 bytes]
//
// Recorded unconditionally, including when statusMoreResultsExists is set:
// every statement of a multi-statement gets an OK packet of its own, and
// each should leave its own count behind rather than only the last.
mc.warnings = readWarnings(data[1+n+m+2:])

return nil
}
Expand Down Expand Up @@ -823,16 +855,7 @@ func (rows *textRows) readRow(dest []driver.Value) error {
// In such case, 0xFE can mean string larger than 0xffffff.
// https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_integers.html#sect_protocol_basic_dt_int_le
if data[0] == iEOF && len(data) <= 0xffffff {
if mc.capabilities&clientDeprecateEOF == 0 {
// Deprecated EOF packet
// https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_eof_packet.html
mc.status = readStatus(data[3:])
} else {
// Ok Packet with an 0xFE header
_, _, n := readLengthEncodedInteger(data[1:]) // affected_rows
_, _, m := readLengthEncodedInteger(data[1+n:]) // last_insert_id
mc.status = readStatus(data[1+n+m:])
}
mc.readResultsetTerminator(data)
rows.rs.done = true
if !rows.HasNextResultSet() {
rows.mc = nil
Expand Down Expand Up @@ -955,15 +978,7 @@ func (mc *mysqlConn) skipRows() error {
// text row packets may starts with LengthEncodedString.
// In such case, 0xFE can mean string larger than 0xffffff.
if len(data) <= 0xffffff {
if mc.capabilities&clientDeprecateEOF == 0 {
// EOF packet
mc.status = readStatus(data[3:])
} else {
// OK packet with an 0xFE header
_, _, n := readLengthEncodedInteger(data[1:]) // affected_rows
_, _, m := readLengthEncodedInteger(data[1+n:]) // last_insert_id
mc.status = readStatus(data[1+n+m:])
}
mc.readResultsetTerminator(data)
return nil
}
}
Expand Down Expand Up @@ -1281,15 +1296,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
if data[0] != iOK {
// EOF/OK Packet
if data[0] == iEOF {
if rows.mc.capabilities&clientDeprecateEOF == 0 {
// EOF packet
rows.mc.status = readStatus(data[3:])
} else {
// OK Packet with an 0xFE header
_, _, n := readLengthEncodedInteger(data[1:])
_, _, m := readLengthEncodedInteger(data[1+n:])
rows.mc.status = readStatus(data[1+n+m:])
}
rows.mc.readResultsetTerminator(data)
rows.rs.done = true
if !rows.HasNextResultSet() {
rows.mc = nil
Expand Down
46 changes: 46 additions & 0 deletions warnings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2026 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.

package mysql

// Warnings reports the warning count the server sent in the packet that
// terminated the last statement executed on this connection — the OK packet
// for a statement without a resultset, the EOF (or 0xFE-headered OK) packet
// that ends the rows otherwise. It is the same number MySQL's own client
// prints as "N warnings" and exposes as @@warning_count.
//
// The count is what makes reading warnings affordable. MySQL keeps the
// diagnostics themselves in per-connection state that only SHOW WARNINGS can
// read, so a caller that wants them has to spend a round trip; the count says
// whether there is anything to spend it on. Clients that surface warnings —
// Connector/J among them — use it exactly that way.
//
// It is valid from the moment the statement's response is complete until the
// next statement starts on this connection, which resets it to zero. For a
// resultset that means after the rows have been fully read (or Close called):
// the terminating packet carrying the count has not arrived before then.
//
// An error packet carries no count of its own, so a statement that fails
// reports zero — the value the statement's own start left behind. Inside a
// multi-statement, where each statement gets its own OK packet, a failure
// instead leaves the last successful statement's count in place.
//
// Reach it through (*sql.Conn).Raw and an interface assertion:
//
// err := conn.Raw(func(dc any) error {
// if wc, ok := dc.(interface{ Warnings() uint16 }); ok && wc.Warnings() > 0 {
// // SHOW WARNINGS on this same connection
// }
// return nil
// })
//
// Note that database/sql may hand the connection to another caller as soon as
// it is released, so the count must be read before that happens.
func (mc *mysqlConn) Warnings() uint16 {
return mc.warnings
}
Loading
Loading