• 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

74.11
/connection.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
        "context"
13
        "database/sql"
14
        "database/sql/driver"
15
        "encoding/json"
16
        "fmt"
17
        "io"
18
        "net"
19
        "runtime"
20
        "strconv"
21
        "strings"
22
        "sync/atomic"
23
        "time"
24
)
25

26
type mysqlConn struct {
27
        buf              buffer
28
        netConn          net.Conn
29
        rawConn          net.Conn    // underlying connection when netConn is TLS connection.
30
        result           mysqlResult // managed by clearResult() and handleOkPacket().
31
        compIO           *compIO
32
        cfg              *Config
33
        connector        *connector
34
        maxAllowedPacket int
35
        maxWriteSize     int
36
        capabilities     capabilityFlag
37
        extCapabilities  extendedCapabilityFlag
38
        status           statusFlag
39
        sequence         uint8
40
        compressSequence uint8
41
        parseTime        bool
42
        compress         bool
43

44
        // for context support (Go 1.8+)
45
        watching bool
46
        watcher  chan<- context.Context
47
        closech  chan struct{}
48
        finished chan<- struct{}
49
        canceled atomicError // set non-nil if conn is canceled
50
        closed   atomic.Bool // set when conn is closed, before closech is closed
51
}
52

53
// Helper function to call per-connection logger.
54
func (mc *mysqlConn) log(v ...any) {
271✔
55
        _, filename, lineno, ok := runtime.Caller(1)
271✔
56
        if ok {
542✔
57
                pos := strings.LastIndexByte(filename, '/')
271✔
58
                if pos != -1 {
542✔
59
                        filename = filename[pos+1:]
271✔
60
                }
271✔
61
                prefix := fmt.Sprintf("%s:%d ", filename, lineno)
271✔
62
                v = append([]any{prefix}, v...)
271✔
63
        }
64

65
        mc.cfg.Logger.Print(v...)
271✔
66
}
67

68
func (mc *mysqlConn) readWithTimeout(b []byte) (int, error) {
104,315✔
69
        to := mc.cfg.ReadTimeout
104,315✔
70
        if to > 0 {
104,315✔
71
                if err := mc.netConn.SetReadDeadline(time.Now().Add(to)); err != nil {
×
72
                        return 0, err
×
73
                }
×
74
        }
75
        return mc.netConn.Read(b)
104,315✔
76
}
77

78
func (mc *mysqlConn) writeWithTimeout(b []byte) (int, error) {
103,050✔
79
        to := mc.cfg.WriteTimeout
103,050✔
80
        if to > 0 {
103,050✔
81
                if err := mc.netConn.SetWriteDeadline(time.Now().Add(to)); err != nil {
×
82
                        return 0, err
×
83
                }
×
84
        }
85
        return mc.netConn.Write(b)
103,050✔
86
}
87

88
func (mc *mysqlConn) resetSequence() {
81,090✔
89
        mc.sequence = 0
81,090✔
90
        mc.compressSequence = 0
81,090✔
91
}
81,090✔
92

93
// syncSequence must be called when finished writing some packet and before start reading.
94
func (mc *mysqlConn) syncSequence() {
59,758✔
95
        // Syncs compressionSequence to sequence.
59,758✔
96
        // This is not documented but done in `net_flush()` in MySQL and MariaDB.
59,758✔
97
        // https://github.com/mariadb-corporation/mariadb-connector-c/blob/8228164f850b12353da24df1b93a1e53cc5e85e9/libmariadb/ma_net.c#L170-L171
59,758✔
98
        // https://github.com/mysql/mysql-server/blob/824e2b4064053f7daf17d7f3f84b7a3ed92e5fb4/sql-common/net_serv.cc#L293
59,758✔
99
        if mc.compress {
77,406✔
100
                mc.sequence = mc.compressSequence
17,648✔
101
                mc.compIO.reset()
17,648✔
102
        }
17,648✔
103
}
104

105
// Handles parameters set in DSN after the connection is established
106
func (mc *mysqlConn) handleParams() (err error) {
12,716✔
107
        var cmdSet strings.Builder
12,716✔
108

12,716✔
109
        for param, val := range mc.cfg.Params {
13,100✔
110
                if cmdSet.Len() == 0 {
768✔
111
                        // Heuristic: 29 chars for each other key=value to reduce reallocations
384✔
112
                        cmdSet.Grow(4 + len(param) + 3 + len(val) + 30*(len(mc.cfg.Params)-1))
384✔
113
                        cmdSet.WriteString("SET ")
384✔
114
                } else {
384✔
115
                        cmdSet.WriteString(", ")
×
116
                }
×
117
                cmdSet.WriteString(param)
384✔
118
                cmdSet.WriteString(" = ")
384✔
119
                cmdSet.WriteString(val)
384✔
120
        }
121

122
        if cmdSet.Len() > 0 {
13,100✔
123
                err = mc.exec(cmdSet.String())
384✔
124
        }
384✔
125

126
        return
12,716✔
127
}
128

129
// markBadConn replaces errBadConnNoWrite with driver.ErrBadConn.
130
// This function is used to return driver.ErrBadConn only when safe to retry.
131
func (mc *mysqlConn) markBadConn(err error) error {
531✔
132
        if err == errBadConnNoWrite {
563✔
133
                return driver.ErrBadConn
32✔
134
        }
32✔
135
        return err
499✔
136
}
137

138
func (mc *mysqlConn) Begin() (driver.Tx, error) {
×
139
        return mc.begin(false)
×
140
}
×
141

142
func (mc *mysqlConn) begin(readOnly bool) (driver.Tx, error) {
4,835✔
143
        if mc.closed.Load() {
4,835✔
144
                return nil, driver.ErrBadConn
×
145
        }
×
146
        var q string
4,835✔
147
        if readOnly {
4,867✔
148
                q = "START TRANSACTION READ ONLY"
32✔
149
        } else {
4,835✔
150
                q = "START TRANSACTION"
4,803✔
151
        }
4,803✔
152
        err := mc.exec(q)
4,835✔
153
        if err == nil {
9,670✔
154
                return &mysqlTx{mc}, err
4,835✔
155
        }
4,835✔
156
        return nil, mc.markBadConn(err)
×
157
}
158

159
func (mc *mysqlConn) Close() (err error) {
12,684✔
160
        // Makes Close idempotent
12,684✔
161
        if !mc.closed.Load() {
23,015✔
162
                err = mc.writeCommandPacket(comQuit)
10,331✔
163
        }
10,331✔
164
        mc.close()
12,684✔
165
        return
12,684✔
166
}
167

168
// close closes the network connection and clear results without sending COM_QUIT.
169
func (mc *mysqlConn) close() {
14,987✔
170
        mc.cleanup()
14,987✔
171
        mc.clearResult()
14,987✔
172
}
14,987✔
173

174
// Closes the network connection and unsets internal variables. Do not call this
175
// function after successfully authentication, call Close instead. This function
176
// is called before auth or on auth failure because MySQL will have already
177
// closed the network connection.
178
func (mc *mysqlConn) cleanup() {
25,883✔
179
        if mc.closed.Swap(true) {
34,607✔
180
                return
8,724✔
181
        }
8,724✔
182

183
        // Makes cleanup idempotent
184
        close(mc.closech)
17,159✔
185
        conn := mc.rawConn
17,159✔
186
        if conn == nil {
17,351✔
187
                return
192✔
188
        }
192✔
189
        if err := conn.Close(); err != nil {
16,967✔
190
                mc.log("closing connection:", err)
×
191
        }
×
192
        // This function can be called from multiple goroutines.
193
        // So we can not mc.clearResult() here.
194
        // Caller should do it if they are in safe goroutine.
195
}
196

197
func (mc *mysqlConn) error() error {
45,024✔
198
        if mc.closed.Load() {
46,988✔
199
                if err := mc.canceled.Value(); err != nil {
3,928✔
200
                        return err
1,964✔
201
                }
1,964✔
202
                return ErrInvalidConn
×
203
        }
204
        return nil
43,060✔
205
}
206

207
func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
8,464✔
208
        if mc.closed.Load() {
8,464✔
209
                return nil, driver.ErrBadConn
×
210
        }
×
211
        // Send command
212
        err := mc.writeCommandPacketStr(comStmtPrepare, query)
8,464✔
213
        if err != nil {
8,464✔
214
                // STMT_PREPARE is safe to retry.  So we can return ErrBadConn here.
×
215
                mc.log(err)
×
216
                return nil, driver.ErrBadConn
×
217
        }
×
218

219
        stmt := &mysqlStmt{
8,464✔
220
                mc: mc,
8,464✔
221
        }
8,464✔
222

8,464✔
223
        // Read Result
8,464✔
224
        columnCount, err := stmt.readPrepareResultPacket()
8,464✔
225
        if err == nil {
16,928✔
226
                if stmt.paramCount > 0 {
16,608✔
227
                        if err = mc.skipColumns(stmt.paramCount); err != nil {
8,144✔
228
                                return nil, err
×
229
                        }
×
230
                }
231

232
                if columnCount > 0 {
14,944✔
233
                        if mc.extCapabilities&clientCacheMetadata != 0 {
9,780✔
234
                                if stmt.columns, err = mc.readColumns(int(columnCount)); err != nil {
3,300✔
NEW
235
                                        return nil, err
×
NEW
236
                                }
×
237
                        } else {
3,180✔
238
                                if err = mc.skipColumns(int(columnCount)); err != nil {
3,180✔
NEW
239
                                        return nil, err
×
NEW
240
                                }
×
241
                        }
242
                }
243
        }
244

245
        return stmt, err
8,464✔
246
}
247

248
func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (string, error) {
3,592✔
249
        // Number of ? should be same to len(args)
3,592✔
250
        if strings.Count(query, "?") != len(args) {
3,656✔
251
                return "", driver.ErrSkip
64✔
252
        }
64✔
253

254
        buf, err := mc.buf.takeCompleteBuffer()
3,528✔
255
        if err != nil {
3,528✔
256
                // can not take the buffer. Something must be wrong with the connection
×
257
                mc.cleanup()
×
258
                // interpolateParams would be called before sending any query.
×
259
                // So its safe to retry.
×
260
                return "", driver.ErrBadConn
×
261
        }
×
262
        buf = buf[:0]
3,528✔
263
        argPos := 0
3,528✔
264

3,528✔
265
        for i := 0; i < len(query); i++ {
10,488✔
266
                q := strings.IndexByte(query[i:], '?')
6,960✔
267
                if q == -1 {
10,232✔
268
                        buf = append(buf, query[i:]...)
3,272✔
269
                        break
3,272✔
270
                }
271
                buf = append(buf, query[i:i+q]...)
3,688✔
272
                i += q
3,688✔
273

3,688✔
274
                arg := args[argPos]
3,688✔
275
                argPos++
3,688✔
276

3,688✔
277
                if arg == nil {
3,688✔
278
                        buf = append(buf, "NULL"...)
×
279
                        continue
×
280
                }
281

282
                switch v := arg.(type) {
3,688✔
283
                case int64:
320✔
284
                        buf = strconv.AppendInt(buf, v, 10)
320✔
285
                case uint64:
32✔
286
                        // Handle uint64 explicitly because our custom ConvertValue emits unsigned values
32✔
287
                        buf = strconv.AppendUint(buf, v, 10)
32✔
288
                case float64:
×
289
                        buf = strconv.AppendFloat(buf, v, 'g', -1, 64)
×
290
                case bool:
×
291
                        if v {
×
292
                                buf = append(buf, '1')
×
293
                        } else {
×
294
                                buf = append(buf, '0')
×
295
                        }
×
296
                case time.Time:
1,140✔
297
                        if v.IsZero() {
1,576✔
298
                                buf = append(buf, "'0000-00-00'"...)
436✔
299
                        } else {
1,140✔
300
                                buf = append(buf, '\'')
704✔
301
                                buf, err = appendDateTime(buf, v.In(mc.cfg.Loc), mc.cfg.timeTruncate)
704✔
302
                                if err != nil {
704✔
303
                                        return "", err
×
304
                                }
×
305
                                buf = append(buf, '\'')
704✔
306
                        }
307
                case json.RawMessage:
32✔
308
                        buf = append(buf, '\'')
32✔
309
                        if mc.status&statusNoBackslashEscapes == 0 {
64✔
310
                                buf = escapeBytesBackslash(buf, v)
32✔
311
                        } else {
32✔
312
                                buf = escapeBytesQuotes(buf, v)
×
313
                        }
×
314
                        buf = append(buf, '\'')
32✔
315
                case []byte:
×
316
                        if v == nil {
×
317
                                buf = append(buf, "NULL"...)
×
318
                        } else {
×
319
                                buf = append(buf, "_binary'"...)
×
320
                                if mc.status&statusNoBackslashEscapes == 0 {
×
321
                                        buf = escapeBytesBackslash(buf, v)
×
322
                                } else {
×
323
                                        buf = escapeBytesQuotes(buf, v)
×
324
                                }
×
325
                                buf = append(buf, '\'')
×
326
                        }
327
                case string:
2,164✔
328
                        buf = append(buf, '\'')
2,164✔
329
                        if mc.status&statusNoBackslashEscapes == 0 {
4,232✔
330
                                buf = escapeStringBackslash(buf, v)
2,068✔
331
                        } else {
2,164✔
332
                                buf = escapeStringQuotes(buf, v)
96✔
333
                        }
96✔
334
                        buf = append(buf, '\'')
2,164✔
335
                default:
×
336
                        return "", driver.ErrSkip
×
337
                }
338

339
                if len(buf)+4 > mc.maxAllowedPacket {
3,688✔
340
                        return "", driver.ErrSkip
×
341
                }
×
342
        }
343
        if argPos != len(args) {
3,528✔
344
                return "", driver.ErrSkip
×
345
        }
×
346
        return string(buf), nil
3,528✔
347
}
348

349
func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) {
18,160✔
350
        if mc.closed.Load() {
18,160✔
351
                return nil, driver.ErrBadConn
×
352
        }
×
353
        if len(args) != 0 {
20,432✔
354
                if !mc.cfg.InterpolateParams {
4,064✔
355
                        return nil, driver.ErrSkip
1,792✔
356
                }
1,792✔
357
                // try to interpolate the parameters to save extra roundtrips for preparing and closing a statement
358
                prepared, err := mc.interpolateParams(query, args)
480✔
359
                if err != nil {
480✔
360
                        return nil, err
×
361
                }
×
362
                query = prepared
480✔
363
        }
364

365
        err := mc.exec(query)
16,368✔
366
        if err == nil {
32,276✔
367
                copied := mc.result
15,908✔
368
                return &copied, err
15,908✔
369
        }
15,908✔
370
        return nil, mc.markBadConn(err)
460✔
371
}
372

373
// Internal function to execute commands
374
func (mc *mysqlConn) exec(query string) error {
27,018✔
375
        handleOk := mc.clearResult()
27,018✔
376
        // Send command
27,018✔
377
        if err := mc.writeCommandPacketStr(comQuery, query); err != nil {
27,018✔
378
                return mc.markBadConn(err)
×
379
        }
×
380

381
        // Read Result
382
        resLen, _, err := handleOk.readResultSetHeaderPacket()
27,018✔
383
        if err != nil {
27,638✔
384
                return err
620✔
385
        }
620✔
386

387
        if resLen > 0 {
26,446✔
388
                // columns
48✔
389
                if err := mc.skipColumns(resLen); err != nil {
48✔
390
                        return err
×
391
                }
×
392

393
                // rows
394
                if err := mc.skipRows(); err != nil {
48✔
395
                        return err
×
396
                }
×
397
        }
398

399
        return handleOk.discardResults()
26,398✔
400
}
401

402
func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, error) {
160✔
403
        return mc.query(query, args)
160✔
404
}
160✔
405

406
func (mc *mysqlConn) query(query string, args []driver.Value) (*textRows, error) {
21,044✔
407
        handleOk := mc.clearResult()
21,044✔
408

21,044✔
409
        if mc.closed.Load() {
21,044✔
410
                return nil, driver.ErrBadConn
×
411
        }
×
412
        if len(args) != 0 {
30,220✔
413
                if !mc.cfg.InterpolateParams {
15,400✔
414
                        return nil, driver.ErrSkip
6,224✔
415
                }
6,224✔
416
                // try client-side prepare to reduce roundtrip
417
                prepared, err := mc.interpolateParams(query, args)
2,952✔
418
                if err != nil {
2,952✔
419
                        return nil, err
×
420
                }
×
421
                query = prepared
2,952✔
422
        }
423
        // Send command
424
        err := mc.writeCommandPacketStr(comQuery, query)
14,820✔
425
        if err != nil {
14,820✔
426
                return nil, mc.markBadConn(err)
×
427
        }
×
428

429
        // Read Result
430
        var resLen int
14,820✔
431
        resLen, _, err = handleOk.readResultSetHeaderPacket()
14,820✔
432
        if err != nil {
14,918✔
433
                return nil, err
98✔
434
        }
98✔
435

436
        rows := new(textRows)
14,722✔
437
        rows.mc = mc
14,722✔
438

14,722✔
439
        if resLen == 0 {
14,930✔
440
                rows.rs.done = true
208✔
441

208✔
442
                switch err := rows.NextResultSet(); err {
208✔
443
                case nil, io.EOF:
208✔
444
                        return rows, nil
208✔
445
                default:
×
446
                        return nil, err
×
447
                }
448
        }
449

450
        // Columns
451
        rows.rs.columns, err = mc.readColumns(resLen)
14,514✔
452
        return rows, err
14,514✔
453
}
454

455
// Gets the value of the given MySQL System Variable
456
// The returned byte slice is only valid until the next read
457
func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) {
96✔
458
        // Send command
96✔
459
        handleOk := mc.clearResult()
96✔
460
        if err := mc.writeCommandPacketStr(comQuery, "SELECT @@"+name); err != nil {
96✔
461
                return nil, err
×
462
        }
×
463

464
        // Read Result
465
        resLen, _, err := handleOk.readResultSetHeaderPacket()
96✔
466
        if err == nil {
192✔
467
                rows := new(textRows)
96✔
468
                rows.mc = mc
96✔
469
                rows.rs.columns = []mysqlField{{fieldType: fieldTypeVarChar}}
96✔
470

96✔
471
                if resLen > 0 {
192✔
472
                        // Columns
96✔
473
                        if err := mc.skipColumns(resLen); err != nil {
96✔
474
                                return nil, err
×
475
                        }
×
476
                }
477

478
                dest := make([]driver.Value, resLen)
96✔
479
                if err = rows.readRow(dest); err == nil {
192✔
480
                        return dest[0].([]byte), mc.skipRows()
96✔
481
                }
96✔
482
        }
483
        return nil, err
×
484
}
485

486
// cancel is called when the query has canceled.
487
func (mc *mysqlConn) cancel(err error) {
6,342✔
488
        mc.canceled.Set(err)
6,342✔
489
        mc.cleanup()
6,342✔
490
}
6,342✔
491

492
// finish is called when the query has succeeded.
493
func (mc *mysqlConn) finish() {
80,594✔
494
        if !mc.watching || mc.finished == nil {
151,775✔
495
                return
71,181✔
496
        }
71,181✔
497
        select {
9,413✔
498
        case mc.finished <- struct{}{}:
3,071✔
499
                mc.watching = false
3,071✔
500
        case <-mc.closech:
6,342✔
501
        }
502
}
503

504
// Ping implements driver.Pinger interface
505
func (mc *mysqlConn) Ping(ctx context.Context) (err error) {
2,587✔
506
        if mc.closed.Load() {
2,630✔
507
                return driver.ErrBadConn
43✔
508
        }
43✔
509

510
        if err = mc.watchCancel(ctx); err != nil {
2,647✔
511
                return
103✔
512
        }
103✔
513
        defer mc.finish()
2,441✔
514

2,441✔
515
        handleOk := mc.clearResult()
2,441✔
516
        if err = mc.writeCommandPacket(comPing); err != nil {
2,512✔
517
                return mc.markBadConn(err)
71✔
518
        }
71✔
519

520
        return handleOk.readResultOK()
2,370✔
521
}
522

523
// BeginTx implements driver.ConnBeginTx interface
524
func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
4,847✔
525
        if mc.closed.Load() {
4,859✔
526
                return nil, driver.ErrBadConn
12✔
527
        }
12✔
528

529
        if err := mc.watchCancel(ctx); err != nil {
4,835✔
530
                return nil, err
×
531
        }
×
532
        defer mc.finish()
4,835✔
533

4,835✔
534
        if sql.IsolationLevel(opts.Isolation) != sql.LevelDefault {
4,899✔
535
                level, err := mapIsolationLevel(opts.Isolation)
64✔
536
                if err != nil {
64✔
537
                        return nil, err
×
538
                }
×
539
                err = mc.exec("SET TRANSACTION ISOLATION LEVEL " + level)
64✔
540
                if err != nil {
64✔
541
                        return nil, err
×
542
                }
×
543
        }
544

545
        return mc.begin(opts.ReadOnly)
4,835✔
546
}
547

548
func (mc *mysqlConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
20,884✔
549
        dargs, err := namedValueToValue(args)
20,884✔
550
        if err != nil {
20,884✔
551
                return nil, err
×
552
        }
×
553

554
        if err := mc.watchCancel(ctx); err != nil {
20,884✔
555
                return nil, err
×
556
        }
×
557

558
        rows, err := mc.query(query, dargs)
20,884✔
559
        if err != nil {
27,206✔
560
                mc.finish()
6,322✔
561
                return nil, err
6,322✔
562
        }
6,322✔
563
        rows.finish = mc.finish
14,562✔
564
        return rows, err
14,562✔
565
}
566

567
func (mc *mysqlConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
18,128✔
568
        dargs, err := namedValueToValue(args)
18,128✔
569
        if err != nil {
18,128✔
570
                return nil, err
×
571
        }
×
572

573
        if err := mc.watchCancel(ctx); err != nil {
18,128✔
574
                return nil, err
×
575
        }
×
576
        defer mc.finish()
18,128✔
577

18,128✔
578
        return mc.Exec(query, dargs)
18,128✔
579
}
580

581
func (mc *mysqlConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
8,432✔
582
        if err := mc.watchCancel(ctx); err != nil {
8,432✔
583
                return nil, err
×
584
        }
×
585

586
        stmt, err := mc.Prepare(query)
8,432✔
587
        mc.finish()
8,432✔
588
        if err != nil {
8,432✔
589
                return nil, err
×
590
        }
×
591

592
        select {
8,432✔
593
        default:
8,432✔
594
        case <-ctx.Done():
×
595
                stmt.Close()
×
596
                return nil, ctx.Err()
×
597
        }
598
        return stmt, nil
8,432✔
599
}
600

601
func (stmt *mysqlStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
7,024✔
602
        dargs, err := namedValueToValue(args)
7,024✔
603
        if err != nil {
7,024✔
604
                return nil, err
×
605
        }
×
606

607
        if err := stmt.mc.watchCancel(ctx); err != nil {
7,024✔
608
                return nil, err
×
609
        }
×
610

611
        rows, err := stmt.query(dargs)
7,024✔
612
        if err != nil {
7,056✔
613
                stmt.mc.finish()
32✔
614
                return nil, err
32✔
615
        }
32✔
616
        rows.finish = stmt.mc.finish
6,992✔
617
        return rows, err
6,992✔
618
}
619

620
func (stmt *mysqlStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
1,824✔
621
        dargs, err := namedValueToValue(args)
1,824✔
622
        if err != nil {
1,824✔
623
                return nil, err
×
624
        }
×
625

626
        if err := stmt.mc.watchCancel(ctx); err != nil {
1,824✔
627
                return nil, err
×
628
        }
×
629
        defer stmt.mc.finish()
1,824✔
630

1,824✔
631
        return stmt.Exec(dargs)
1,824✔
632
}
633

634
func (mc *mysqlConn) watchCancel(ctx context.Context) error {
80,830✔
635
        if mc.watching {
80,830✔
636
                // Reach here if canceled,
×
637
                // so the connection is already invalid
×
638
                mc.cleanup()
×
639
                return nil
×
640
        }
×
641
        // When ctx is already cancelled, don't watch it.
642
        if err := ctx.Err(); err != nil {
81,034✔
643
                return err
204✔
644
        }
204✔
645
        // When ctx is not cancellable, don't watch it.
646
        if ctx.Done() == nil {
151,839✔
647
                return nil
71,213✔
648
        }
71,213✔
649
        // When watcher is not alive, can't watch it.
650
        if mc.watcher == nil {
9,413✔
651
                return nil
×
652
        }
×
653

654
        mc.watching = true
9,413✔
655
        mc.watcher <- ctx
9,413✔
656
        return nil
9,413✔
657
}
658

659
func (mc *mysqlConn) startWatcher() {
17,191✔
660
        watcher := make(chan context.Context, 1)
17,191✔
661
        mc.watcher = watcher
17,191✔
662
        finished := make(chan struct{})
17,191✔
663
        mc.finished = finished
17,191✔
664
        go func() {
34,382✔
665
                for {
43,795✔
666
                        var ctx context.Context
26,604✔
667
                        select {
26,604✔
668
                        case ctx = <-watcher:
9,413✔
669
                        case <-mc.closech:
16,999✔
670
                                return
16,999✔
671
                        }
672

673
                        select {
9,413✔
674
                        case <-ctx.Done():
6,342✔
675
                                mc.cancel(ctx.Err())
6,342✔
676
                        case <-finished:
3,071✔
677
                        case <-mc.closech:
×
678
                                return
×
679
                        }
680
                }
681
        }()
682
}
683

684
func (mc *mysqlConn) CheckNamedValue(nv *driver.NamedValue) (err error) {
12,120✔
685
        nv.Value, err = converter{}.ConvertValue(nv.Value)
12,120✔
686
        return
12,120✔
687
}
12,120✔
688

689
// ResetSession implements driver.SessionResetter.
690
// (From Go 1.10)
691
func (mc *mysqlConn) ResetSession(ctx context.Context) error {
30,029✔
692
        if mc.closed.Load() || mc.buf.busy() {
30,029✔
693
                return driver.ErrBadConn
×
694
        }
×
695

696
        // Perform a stale connection check. We only perform this check for
697
        // the first query on a connection that has been checked out of the
698
        // connection pool: a fresh connection from the pool is more likely
699
        // to be stale, and it has not performed any previous writes that
700
        // could cause data corruption, so it's safe to return ErrBadConn
701
        // if the check fails.
702
        if mc.cfg.CheckConnLiveness {
60,058✔
703
                conn := mc.netConn
30,029✔
704
                if mc.rawConn != nil {
60,058✔
705
                        conn = mc.rawConn
30,029✔
706
                }
30,029✔
707
                var err error
30,029✔
708
                if mc.cfg.ReadTimeout != 0 {
30,029✔
709
                        err = conn.SetReadDeadline(time.Now().Add(mc.cfg.ReadTimeout))
×
710
                }
×
711
                if err == nil {
60,058✔
712
                        err = connCheck(conn)
30,029✔
713
                }
30,029✔
714
                if err != nil {
30,096✔
715
                        mc.log("closing bad idle connection: ", err)
67✔
716
                        return driver.ErrBadConn
67✔
717
                }
67✔
718
        }
719

720
        return nil
29,962✔
721
}
722

723
// IsValid implements driver.Validator interface
724
// (From Go 1.15)
725
func (mc *mysqlConn) IsValid() bool {
42,367✔
726
        return !mc.closed.Load() && !mc.buf.busy()
42,367✔
727
}
42,367✔
728

729
var _ driver.SessionResetter = &mysqlConn{}
730
var _ driver.Validator = &mysqlConn{}
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

© 2025 Coveralls, Inc