• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

go-sql-driver / mysql / 14675550227

26 Apr 2025 12:08AM UTC coverage: 82.41% (-0.6%) from 82.961%
14675550227

push

github

web-flow
MariaDB Metadata skipping and DEPRECATE_EOF (#1708)

[MariaDB metadata skipping](https://mariadb.com/kb/en/mariadb-protocol-differences-with-mysql/#prepare-statement-skipping-metadata).

With this change, MariaDB server won't send metadata when they have not changed, saving client parsing metadata and network.

This feature rely on these changes:
* extended capabilities support 
* EOF packet deprecation makes current implementation to be revised

A benchmark BenchmarkReceiveMetadata has been added to show the difference.

155 of 194 new or added lines in 5 files covered. (79.9%)

6 existing lines in 2 files now uncovered.

3303 of 4008 relevant lines covered (82.41%)

2391188.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

72.67
/statement.go
1
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
2
//
3
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
4
//
5
// This Source Code Form is subject to the terms of the Mozilla Public
6
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
7
// You can obtain one at http://mozilla.org/MPL/2.0/.
8

9
package mysql
10

11
import (
12
        "database/sql/driver"
13
        "encoding/json"
14
        "fmt"
15
        "io"
16
        "reflect"
17
)
18

19
type mysqlStmt struct {
20
        mc         *mysqlConn
21
        id         uint32
22
        paramCount int
23
        columns    []mysqlField
24
}
25

26
func (stmt *mysqlStmt) Close() error {
8,400✔
27
        if stmt.mc == nil || stmt.mc.closed.Load() {
8,464✔
28
                // driver.Stmt.Close could be called more than once, thus this function
64✔
29
                // had to be idempotent. See also Issue #450 and golang/go#16019.
64✔
30
                // This bug has been fixed in Go 1.8.
64✔
31
                // https://github.com/golang/go/commit/90b8a0ca2d0b565c7c7199ffcf77b15ea6b6db3a
64✔
32
                // But we keep this function idempotent because it is safer.
64✔
33
                return nil
64✔
34
        }
64✔
35

36
        err := stmt.mc.writeCommandPacketUint32(comStmtClose, stmt.id)
8,336✔
37
        stmt.mc = nil
8,336✔
38
        return err
8,336✔
39
}
40

41
func (stmt *mysqlStmt) NumInput() int {
8,848✔
42
        return stmt.paramCount
8,848✔
43
}
8,848✔
44

45
func (stmt *mysqlStmt) ColumnConverter(idx int) driver.ValueConverter {
×
46
        return converter{}
×
47
}
×
48

49
func (stmt *mysqlStmt) CheckNamedValue(nv *driver.NamedValue) (err error) {
12,591,440✔
50
        nv.Value, err = converter{}.ConvertValue(nv.Value)
12,591,440✔
51
        return
12,591,440✔
52
}
12,591,440✔
53

54
func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
1,888✔
55
        if stmt.mc.closed.Load() {
1,920✔
56
                return nil, driver.ErrBadConn
32✔
57
        }
32✔
58
        // Send command
59
        err := stmt.writeExecutePacket(args)
1,856✔
60
        if err != nil {
1,856✔
61
                return nil, stmt.mc.markBadConn(err)
×
62
        }
×
63

64
        mc := stmt.mc
1,856✔
65
        handleOk := stmt.mc.clearResult()
1,856✔
66

1,856✔
67
        // Read Result
1,856✔
68
        resLen, metadataFollows, err := handleOk.readResultSetHeaderPacket()
1,856✔
69
        if err != nil {
1,888✔
70
                return nil, err
32✔
71
        }
32✔
72

73
        if resLen > 0 {
1,824✔
74
                // Columns
×
NEW
75
                if metadataFollows && stmt.mc.extCapabilities&clientCacheMetadata != 0 {
×
NEW
76
                        // we can not skip column metadata because next stmt.Query() may use it.
×
NEW
77
                        if stmt.columns, err = mc.readColumns(resLen); err != nil {
×
NEW
78
                                return nil, err
×
NEW
79
                        }
×
NEW
80
                } else {
×
NEW
81
                        if err = mc.skipColumns(resLen); err != nil {
×
NEW
82
                                return nil, err
×
NEW
83
                        }
×
84
                }
85

86
                // Rows
NEW
87
                if err = mc.skipRows(); err != nil {
×
88
                        return nil, err
×
89
                }
×
90
        }
91

92
        if err := handleOk.discardResults(); err != nil {
1,824✔
93
                return nil, err
×
94
        }
×
95

96
        copied := mc.result
1,824✔
97
        return &copied, nil
1,824✔
98
}
99

100
func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
×
101
        return stmt.query(args)
×
102
}
×
103

104
func (stmt *mysqlStmt) query(args []driver.Value) (*binaryRows, error) {
7,024✔
105
        if stmt.mc.closed.Load() {
7,024✔
106
                return nil, driver.ErrBadConn
×
107
        }
×
108
        // Send command
109
        err := stmt.writeExecutePacket(args)
7,024✔
110
        if err != nil {
7,024✔
111
                return nil, stmt.mc.markBadConn(err)
×
112
        }
×
113

114
        mc := stmt.mc
7,024✔
115

7,024✔
116
        // Read Result
7,024✔
117
        handleOk := stmt.mc.clearResult()
7,024✔
118
        resLen, metadataFollows, err := handleOk.readResultSetHeaderPacket()
7,024✔
119
        if err != nil {
7,056✔
120
                return nil, err
32✔
121
        }
32✔
122

123
        rows := new(binaryRows)
6,992✔
124

6,992✔
125
        if resLen > 0 {
13,984✔
126
                rows.mc = mc
6,992✔
127
                if metadataFollows {
10,624✔
128
                        if rows.rs.columns, err = mc.readColumns(resLen); err != nil {
3,632✔
NEW
129
                                return nil, err
×
NEW
130
                        }
×
131
                        stmt.columns = rows.rs.columns
3,632✔
132
                } else {
3,360✔
133
                        if err = mc.skipEof(); err != nil {
3,360✔
NEW
134
                                return nil, err
×
NEW
135
                        }
×
136
                        rows.rs.columns = stmt.columns
3,360✔
137
                }
138
        } else {
×
139
                rows.rs.done = true
×
140

×
141
                switch err := rows.NextResultSet(); err {
×
142
                case nil, io.EOF:
×
143
                        return rows, nil
×
144
                default:
×
145
                        return nil, err
×
146
                }
147
        }
148

149
        return rows, err
6,992✔
150
}
151

152
var jsonType = reflect.TypeOf(json.RawMessage{})
153

154
type converter struct{}
155

156
// ConvertValue mirrors the reference/default converter in database/sql/driver
157
// with _one_ exception.  We support uint64 with their high bit and the default
158
// implementation does not.  This function should be kept in sync with
159
// database/sql/driver defaultConverter.ConvertValue() except for that
160
// deliberate difference.
161
func (c converter) ConvertValue(v any) (driver.Value, error) {
12,604,168✔
162
        if driver.IsValue(v) {
25,205,168✔
163
                return v, nil
12,601,000✔
164
        }
12,601,000✔
165

166
        if vr, ok := v.(driver.Valuer); ok {
3,424✔
167
                sv, err := callValuerValue(vr)
256✔
168
                if err != nil {
288✔
169
                        return nil, err
32✔
170
                }
32✔
171
                if driver.IsValue(sv) {
416✔
172
                        return sv, nil
192✔
173
                }
192✔
174
                // A value returned from the Valuer interface can be "a type handled by
175
                // a database driver's NamedValueChecker interface" so we should accept
176
                // uint64 here as well.
177
                if u, ok := sv.(uint64); ok {
64✔
178
                        return u, nil
32✔
179
                }
32✔
180
                return nil, fmt.Errorf("non-Value type %T returned from Value", sv)
×
181
        }
182
        rv := reflect.ValueOf(v)
2,912✔
183
        switch rv.Kind() {
2,912✔
184
        case reflect.Ptr:
32✔
185
                // indirect pointers
32✔
186
                if rv.IsNil() {
32✔
187
                        return nil, nil
×
188
                } else {
32✔
189
                        return c.ConvertValue(rv.Elem().Interface())
32✔
190
                }
32✔
191
        case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
2,080✔
192
                return rv.Int(), nil
2,080✔
193
        case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
352✔
194
                return rv.Uint(), nil
352✔
195
        case reflect.Float32, reflect.Float64:
128✔
196
                return rv.Float(), nil
128✔
197
        case reflect.Bool:
32✔
198
                return rv.Bool(), nil
32✔
199
        case reflect.Slice:
224✔
200
                switch t := rv.Type(); {
224✔
201
                case t == jsonType:
160✔
202
                        return v, nil
160✔
203
                case t.Elem().Kind() == reflect.Uint8:
32✔
204
                        return rv.Bytes(), nil
32✔
205
                default:
32✔
206
                        return nil, fmt.Errorf("unsupported type %T, a slice of %s", v, t.Elem().Kind())
32✔
207
                }
208
        case reflect.String:
32✔
209
                return rv.String(), nil
32✔
210
        }
211
        return nil, fmt.Errorf("unsupported type %T, a %s", v, rv.Kind())
32✔
212
}
213

214
var valuerReflectType = reflect.TypeOf((*driver.Valuer)(nil)).Elem()
215

216
// callValuerValue returns vr.Value(), with one exception:
217
// If vr.Value is an auto-generated method on a pointer type and the
218
// pointer is nil, it would panic at runtime in the panicwrap
219
// method. Treat it like nil instead.
220
//
221
// This is so people can implement driver.Value on value types and
222
// still use nil pointers to those types to mean nil/NULL, just like
223
// string/*string.
224
//
225
// This is an exact copy of the same-named unexported function from the
226
// database/sql package.
227
func callValuerValue(vr driver.Valuer) (v driver.Value, err error) {
256✔
228
        if rv := reflect.ValueOf(vr); rv.Kind() == reflect.Ptr &&
256✔
229
                rv.IsNil() &&
256✔
230
                rv.Type().Elem().Implements(valuerReflectType) {
320✔
231
                return nil, nil
64✔
232
        }
64✔
233
        return vr.Value()
192✔
234
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc