From be57666e25b27e3a2137ab38e408d930f4560898 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= Date: Tue, 14 Oct 2025 19:17:10 +0000 Subject: [PATCH 1/8] fix parsing prepare response https://dev.mysql.com/doc/dev/mysql-server/9.3.0/page_protocol_com_stmt_prepare.html warnings is optional from server, & is preceded by a reserved byte --- client/stmt.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/client/stmt.go b/client/stmt.go index 30d878b55..bab79c7ba 100644 --- a/client/stmt.go +++ b/client/stmt.go @@ -265,9 +265,14 @@ func (c *Conn) Prepare(query string) (*Stmt, error) { s.params = int(binary.LittleEndian.Uint16(data[pos:])) pos += 2 - // warnings - s.warnings = int(binary.LittleEndian.Uint16(data[pos:])) - // pos += 2 + // reserved + pos += 1 + + if len(data) >= 12 { + // warnings + s.warnings = int(binary.LittleEndian.Uint16(data[pos:])) + // pos += 2 + } if s.params > 0 { if err := s.conn.readUntilEOF(); err != nil { From a86fb49f790f4d242383b5434c9ecdcf58b778f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= Date: Tue, 14 Oct 2025 20:16:16 +0000 Subject: [PATCH 2/8] wip --- client/auth.go | 2 +- client/resp.go | 57 ++++++++++++++++++++++++++++++-------------------- 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/client/auth.go b/client/auth.go index 950d67505..ddfa2a8f1 100644 --- a/client/auth.go +++ b/client/auth.go @@ -220,7 +220,7 @@ func (c *Conn) writeAuthHandshake() error { c.ccaps&mysql.CLIENT_MULTI_STATEMENTS | c.ccaps&mysql.CLIENT_MULTI_RESULTS | c.ccaps&mysql.CLIENT_PS_MULTI_RESULTS | c.ccaps&mysql.CLIENT_CONNECT_ATTRS | c.ccaps&mysql.CLIENT_COMPRESS | c.ccaps&mysql.CLIENT_ZSTD_COMPRESSION_ALGORITHM | - c.ccaps&mysql.CLIENT_LOCAL_FILES + c.ccaps&mysql.CLIENT_LOCAL_FILES | c.ccaps&mysql.CLIENT_DEPRECATE_EOF capability &^= c.clientExplicitOffCaps diff --git a/client/resp.go b/client/resp.go index 8d77b480e..98f12e30a 100644 --- a/client/resp.go +++ b/client/resp.go @@ -336,10 +336,9 @@ func (c *Conn) readResultsetStreaming(data []byte, binary bool, result *mysql.Re } func (c *Conn) readResultColumns(result *mysql.Result) (err error) { - i := 0 var data []byte - for { + for i := range len(result.Fields) { rawPkgLen := len(result.RawPkg) result.RawPkg, err = c.ReadPacketReuseMem(result.RawPkg) if err != nil { @@ -347,22 +346,6 @@ func (c *Conn) readResultColumns(result *mysql.Result) (err error) { } data = result.RawPkg[rawPkgLen:] - // EOF Packet - if c.isEOFPacket(data) { - if c.capability&mysql.CLIENT_PROTOCOL_41 > 0 { - result.Warnings = binary.LittleEndian.Uint16(data[1:]) - // todo add strict_mode, warning will be treat as error - result.Status = binary.LittleEndian.Uint16(data[3:]) - c.status = result.Status - } - - if i != len(result.Fields) { - err = mysql.ErrMalformPacket - } - - return err - } - if result.Fields[i] == nil { result.Fields[i] = &mysql.Field{} } @@ -372,8 +355,30 @@ func (c *Conn) readResultColumns(result *mysql.Result) (err error) { } result.FieldNames[utils.ByteSliceToString(result.Fields[i].Name)] = i + } + + if !c.HasCapability(mysql.CLIENT_DEPRECATE_EOF) { + // EOF Packet + rawPkgLen := len(result.RawPkg) + result.RawPkg, err = c.ReadPacketReuseMem(result.RawPkg) + if err != nil { + return err + } + data = result.RawPkg[rawPkgLen:] - i++ + if c.isEOFPacket(data) { + if c.capability&mysql.CLIENT_PROTOCOL_41 > 0 { + result.Warnings = binary.LittleEndian.Uint16(data[1:]) + // todo add strict_mode, warning will be treat as error + result.Status = binary.LittleEndian.Uint16(data[3:]) + c.status = result.Status + } + return nil + } else { + return mysql.ErrMalformPacket + } + } else { + return nil } } @@ -388,15 +393,21 @@ func (c *Conn) readResultRows(result *mysql.Result, isBinary bool) (err error) { } data = result.RawPkg[rawPkgLen:] - // EOF Packet - if c.isEOFPacket(data) { - if c.capability&mysql.CLIENT_PROTOCOL_41 > 0 { + if data[0] == mysql.EOF_HEADER && len(data) <= 0xffffff { + if c.HasCapability(mysql.CLIENT_DEPRECATE_EOF) { + // Treat like OK + affectedRows, _, n := mysql.LengthEncodedInt(data[1:]) + insertId, _, m := mysql.LengthEncodedInt(data[1+n:]) + result.Status = binary.LittleEndian.Uint16(data[1+n+m:]) + result.AffectedRows = affectedRows + result.InsertId = insertId + c.status = result.Status + } else if c.capability&mysql.CLIENT_PROTOCOL_41 > 0 { result.Warnings = binary.LittleEndian.Uint16(data[1:]) // todo add strict_mode, warning will be treat as error result.Status = binary.LittleEndian.Uint16(data[3:]) c.status = result.Status } - break } From 9e363a6b2275e5414fbd183954556f382292146a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= Date: Tue, 14 Oct 2025 23:31:02 +0000 Subject: [PATCH 3/8] make it work --- client/auth.go | 9 ++++----- client/conn.go | 2 +- client/resp.go | 33 ++++++++++++--------------------- client/stmt.go | 27 +++++++++++++++++++++++---- 4 files changed, 40 insertions(+), 31 deletions(-) diff --git a/client/auth.go b/client/auth.go index ddfa2a8f1..f0f8ccc4f 100644 --- a/client/auth.go +++ b/client/auth.go @@ -92,7 +92,7 @@ func (c *Conn) readInitialHandshake() error { pos += 2 // The upper 2 bytes of the Capabilities Flags - c.capability = uint32(binary.LittleEndian.Uint16(data[pos:pos+2]))<<16 | c.capability + c.capability |= uint32(binary.LittleEndian.Uint16(data[pos:pos+2])) << 16 pos += 2 // length of the combined auth_plugin_data (scramble), if auth_plugin_data_len is > 0 @@ -209,10 +209,8 @@ func (c *Conn) writeAuthHandshake() error { // Set default client capabilities that reflect the abilities of this library capability := mysql.CLIENT_PROTOCOL_41 | mysql.CLIENT_SECURE_CONNECTION | - mysql.CLIENT_LONG_PASSWORD | mysql.CLIENT_TRANSACTIONS | mysql.CLIENT_PLUGIN_AUTH - // Adjust client capability flags based on server support - capability |= c.capability & mysql.CLIENT_LONG_FLAG - capability |= c.capability & mysql.CLIENT_QUERY_ATTRIBUTES + mysql.CLIENT_LONG_PASSWORD | mysql.CLIENT_TRANSACTIONS | mysql.CLIENT_PLUGIN_AUTH | + mysql.CLIENT_LONG_FLAG | mysql.CLIENT_QUERY_ATTRIBUTES // Adjust client capability flags on specific client requests // Only flags that would make any sense setting and aren't handled elsewhere // in the library are supported here @@ -275,6 +273,7 @@ func (c *Conn) writeAuthHandshake() error { data := make([]byte, length+4) // capability [32 bit] + c.capability &= capability data[4] = byte(capability) data[5] = byte(capability >> 8) data[6] = byte(capability >> 16) diff --git a/client/conn.go b/client/conn.go index 572fe2b09..dd81e5ebd 100644 --- a/client/conn.go +++ b/client/conn.go @@ -252,7 +252,7 @@ func (c *Conn) UnsetCapability(cap uint32) { // HasCapability returns true if the connection has the specific capability func (c *Conn) HasCapability(cap uint32) bool { - return c.ccaps&cap > 0 + return c.ccaps&cap != 0 } // UseSSL: use default SSL diff --git a/client/resp.go b/client/resp.go index 98f12e30a..f89edce14 100644 --- a/client/resp.go +++ b/client/resp.go @@ -14,22 +14,6 @@ import ( "github.com/go-mysql-org/go-mysql/utils" ) -func (c *Conn) readUntilEOF() (err error) { - var data []byte - - for { - data, err = c.ReadPacket() - if err != nil { - return err - } - - // EOF Packet - if c.isEOFPacket(data) { - return err - } - } -} - func (c *Conn) isEOFPacket(data []byte) bool { return data[0] == mysql.EOF_HEADER && len(data) <= 5 } @@ -357,7 +341,7 @@ func (c *Conn) readResultColumns(result *mysql.Result) (err error) { result.FieldNames[utils.ByteSliceToString(result.Fields[i].Name)] = i } - if !c.HasCapability(mysql.CLIENT_DEPRECATE_EOF) { + if c.capability&mysql.CLIENT_DEPRECATE_EOF == 0 { // EOF Packet rawPkgLen := len(result.RawPkg) result.RawPkg, err = c.ReadPacketReuseMem(result.RawPkg) @@ -394,7 +378,7 @@ func (c *Conn) readResultRows(result *mysql.Result, isBinary bool) (err error) { data = result.RawPkg[rawPkgLen:] if data[0] == mysql.EOF_HEADER && len(data) <= 0xffffff { - if c.HasCapability(mysql.CLIENT_DEPRECATE_EOF) { + if c.capability&mysql.CLIENT_DEPRECATE_EOF != 0 { // Treat like OK affectedRows, _, n := mysql.LengthEncodedInt(data[1:]) insertId, _, m := mysql.LengthEncodedInt(data[1+n:]) @@ -446,9 +430,16 @@ func (c *Conn) readResultRowsStreaming(result *mysql.Result, isBinary bool, perR return err } - // EOF Packet - if c.isEOFPacket(data) { - if c.capability&mysql.CLIENT_PROTOCOL_41 > 0 { + if data[0] == mysql.EOF_HEADER && len(data) <= 0xffffff { + if c.capability&mysql.CLIENT_DEPRECATE_EOF != 0 { + // Treat like OK + affectedRows, _, n := mysql.LengthEncodedInt(data[1:]) + insertId, _, m := mysql.LengthEncodedInt(data[1+n:]) + result.Status = binary.LittleEndian.Uint16(data[1+n+m:]) + result.AffectedRows = affectedRows + result.InsertId = insertId + c.status = result.Status + } else if c.capability&mysql.CLIENT_PROTOCOL_41 > 0 { result.Warnings = binary.LittleEndian.Uint16(data[1:]) // todo add strict_mode, warning will be treat as error result.Status = binary.LittleEndian.Uint16(data[3:]) diff --git a/client/stmt.go b/client/stmt.go index bab79c7ba..2014d182c 100644 --- a/client/stmt.go +++ b/client/stmt.go @@ -275,14 +275,33 @@ func (c *Conn) Prepare(query string) (*Stmt, error) { } if s.params > 0 { - if err := s.conn.readUntilEOF(); err != nil { - return nil, errors.Trace(err) + for range s.params { + if _, err := s.conn.ReadPacket(); err != nil { + return nil, errors.Trace(err) + } + } + if s.conn.capability&mysql.CLIENT_DEPRECATE_EOF == 0 { + if packet, err := s.conn.ReadPacket(); err != nil { + return nil, errors.Trace(err) + } else if c.isEOFPacket(packet) { + return nil, mysql.ErrMalformPacket + } } } if s.columns > 0 { - if err := s.conn.readUntilEOF(); err != nil { - return nil, errors.Trace(err) + // TODO process when CLIENT_CACHE_METADATA enabled + for range s.columns { + if _, err := s.conn.ReadPacket(); err != nil { + return nil, errors.Trace(err) + } + } + if s.conn.capability&mysql.CLIENT_DEPRECATE_EOF == 0 { + if packet, err := s.conn.ReadPacket(); err != nil { + return nil, errors.Trace(err) + } else if c.isEOFPacket(packet) { + return nil, mysql.ErrMalformPacket + } } } From 19e6e0ee3642c937a6f65ce4bce85d5f3e6e1727 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= Date: Fri, 17 Oct 2025 00:03:44 +0000 Subject: [PATCH 4/8] oops --- client/stmt.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/stmt.go b/client/stmt.go index 2014d182c..106e176de 100644 --- a/client/stmt.go +++ b/client/stmt.go @@ -283,7 +283,7 @@ func (c *Conn) Prepare(query string) (*Stmt, error) { if s.conn.capability&mysql.CLIENT_DEPRECATE_EOF == 0 { if packet, err := s.conn.ReadPacket(); err != nil { return nil, errors.Trace(err) - } else if c.isEOFPacket(packet) { + } else if !c.isEOFPacket(packet) { return nil, mysql.ErrMalformPacket } } @@ -299,7 +299,7 @@ func (c *Conn) Prepare(query string) (*Stmt, error) { if s.conn.capability&mysql.CLIENT_DEPRECATE_EOF == 0 { if packet, err := s.conn.ReadPacket(); err != nil { return nil, errors.Trace(err) - } else if c.isEOFPacket(packet) { + } else if !c.isEOFPacket(packet) { return nil, mysql.ErrMalformPacket } } From 76ee08feed98812a0b8c34750ac2d2cba436b947 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= Date: Fri, 17 Oct 2025 00:15:17 +0000 Subject: [PATCH 5/8] now that tests passed, turn on by default, test with it off --- client/auth.go | 4 ++-- client/client_test.go | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/client/auth.go b/client/auth.go index f0f8ccc4f..1d2e45768 100644 --- a/client/auth.go +++ b/client/auth.go @@ -210,7 +210,7 @@ func (c *Conn) writeAuthHandshake() error { // Set default client capabilities that reflect the abilities of this library capability := mysql.CLIENT_PROTOCOL_41 | mysql.CLIENT_SECURE_CONNECTION | mysql.CLIENT_LONG_PASSWORD | mysql.CLIENT_TRANSACTIONS | mysql.CLIENT_PLUGIN_AUTH | - mysql.CLIENT_LONG_FLAG | mysql.CLIENT_QUERY_ATTRIBUTES + mysql.CLIENT_LONG_FLAG | mysql.CLIENT_QUERY_ATTRIBUTES | mysql.CLIENT_DEPRECATE_EOF // Adjust client capability flags on specific client requests // Only flags that would make any sense setting and aren't handled elsewhere // in the library are supported here @@ -218,7 +218,7 @@ func (c *Conn) writeAuthHandshake() error { c.ccaps&mysql.CLIENT_MULTI_STATEMENTS | c.ccaps&mysql.CLIENT_MULTI_RESULTS | c.ccaps&mysql.CLIENT_PS_MULTI_RESULTS | c.ccaps&mysql.CLIENT_CONNECT_ATTRS | c.ccaps&mysql.CLIENT_COMPRESS | c.ccaps&mysql.CLIENT_ZSTD_COMPRESSION_ALGORITHM | - c.ccaps&mysql.CLIENT_LOCAL_FILES | c.ccaps&mysql.CLIENT_DEPRECATE_EOF + c.ccaps&mysql.CLIENT_LOCAL_FILES capability &^= c.clientExplicitOffCaps diff --git a/client/client_test.go b/client/client_test.go index 0d39bbc3b..c312b50d7 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -101,6 +101,18 @@ func (s *clientTestSuite) TestConn_Compress() { require.NoError(s.T(), err) } +func (s *clientTestSuite) TestConn_NoDeprecateEOF() { + addr := fmt.Sprintf("%s:%s", *test_util.MysqlHost, s.port) + conn, err := Connect(addr, *testUser, *testPassword, "", func(conn *Conn) error { + conn.UnsetCapability(mysql.CLIENT_DEPRECATE_EOF) + return nil + }) + require.NoError(s.T(), err) + + _, err = conn.Execute("SELECT VERSION()") + require.NoError(s.T(), err) +} + func (s *clientTestSuite) TestConn_SetCapability() { caps := []uint32{ mysql.CLIENT_LONG_PASSWORD, @@ -125,6 +137,7 @@ func (s *clientTestSuite) TestConn_SetCapability() { mysql.CLIENT_PLUGIN_AUTH, mysql.CLIENT_CONNECT_ATTRS, mysql.CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA, + mysql.CLIENT_DEPRECATE_EOF, } for _, capI := range caps { From 7f9025c2f77f217e6da9c7726596c1e61c79a028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= Date: Fri, 17 Oct 2025 00:34:47 +0000 Subject: [PATCH 6/8] fix EOF handling for COM_LIST_FIELDS --- client/conn.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/conn.go b/client/conn.go index dd81e5ebd..3e73e6bb9 100644 --- a/client/conn.go +++ b/client/conn.go @@ -466,7 +466,7 @@ func (c *Conn) FieldList(table string, wildcard string) ([]*mysql.Field, error) } // EOF Packet - if c.isEOFPacket(data) { + if data[0] == mysql.EOF_HEADER && len(data) <= 0xffffff { return fs, nil } From 76a031e9edf1a0266397bcc3e9236c1992bae814 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= Date: Fri, 17 Oct 2025 00:46:51 +0000 Subject: [PATCH 7/8] comment warning --- client/resp.go | 1 + 1 file changed, 1 insertion(+) diff --git a/client/resp.go b/client/resp.go index f89edce14..6657704b5 100644 --- a/client/resp.go +++ b/client/resp.go @@ -14,6 +14,7 @@ import ( "github.com/go-mysql-org/go-mysql/utils" ) +// this should only be called when CLIENT_DEPRECATE_EOF not enabled func (c *Conn) isEOFPacket(data []byte) bool { return data[0] == mysql.EOF_HEADER && len(data) <= 5 } From 1a6753e976ca72e13f1410494d82eadc4828a721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= Date: Sun, 19 Oct 2025 10:44:26 +0000 Subject: [PATCH 8/8] Update client/resp.go Co-authored-by: lance6716 --- client/resp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/resp.go b/client/resp.go index 6657704b5..0bac69ac8 100644 --- a/client/resp.go +++ b/client/resp.go @@ -323,7 +323,7 @@ func (c *Conn) readResultsetStreaming(data []byte, binary bool, result *mysql.Re func (c *Conn) readResultColumns(result *mysql.Result) (err error) { var data []byte - for i := range len(result.Fields) { + for i := range result.Fields { rawPkgLen := len(result.RawPkg) result.RawPkg, err = c.ReadPacketReuseMem(result.RawPkg) if err != nil {