From a3f2c66ceda6c3329c4112e791513b64e374eb2e Mon Sep 17 00:00:00 2001 From: Dmitry Zenovich Date: Tue, 26 Aug 2025 01:47:55 +0300 Subject: [PATCH 01/10] introduce a new method mysqlConn.sendNoArgsCommandWithResultOK() based on the implementation of mysqlConn.Ping(); replace the implementation of mysqlConn.Ping() with "return mc.sendNoArgsCommandWithResultOK(ctx, comPing)" --- connection.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/connection.go b/connection.go index 5648e47d..872826c9 100644 --- a/connection.go +++ b/connection.go @@ -503,6 +503,10 @@ func (mc *mysqlConn) finish() { // Ping implements driver.Pinger interface func (mc *mysqlConn) Ping(ctx context.Context) (err error) { + return mc.sendNoArgsCommandWithResultOK(ctx, comPing) +} + +func (mc *mysqlConn) sendNoArgsCommandWithResultOK(ctx context.Context, cmd byte) (err error) { if mc.closed.Load() { return driver.ErrBadConn } @@ -513,7 +517,7 @@ func (mc *mysqlConn) Ping(ctx context.Context) (err error) { defer mc.finish() handleOk := mc.clearResult() - if err = mc.writeCommandPacket(comPing); err != nil { + if err = mc.writeCommandPacket(cmd); err != nil { return mc.markBadConn(err) } From d6400de0d687b3fc8225b473cc17d691b37b11e5 Mon Sep 17 00:00:00 2001 From: Dmitry Zenovich Date: Tue, 26 Aug 2025 01:55:45 +0300 Subject: [PATCH 02/10] introduce a new method mysqlConn.Reset() allowing to reset the mysql connection (implemented as "return mc.sendNoArgsCommandWithResultOK(ctx, comConnReset)" where comConnReset=31); add tests --- connection.go | 5 ++ connection_test.go | 137 ++++++++++++++++++++++++++++----------------- const.go | 1 + driver_test.go | 114 ++++++++++++++++++++++++++++++------- 4 files changed, 185 insertions(+), 72 deletions(-) diff --git a/connection.go b/connection.go index 872826c9..df2164df 100644 --- a/connection.go +++ b/connection.go @@ -685,6 +685,11 @@ func (mc *mysqlConn) startWatcher() { }() } +// Reset resets the MySQL connection. +func (mc *mysqlConn) Reset(ctx context.Context) (err error) { + return mc.sendNoArgsCommandWithResultOK(ctx, comConnReset) +} + func (mc *mysqlConn) CheckNamedValue(nv *driver.NamedValue) (err error) { nv.Value, err = converter{}.ConvertValue(nv.Value) return diff --git a/connection_test.go b/connection_test.go index 440ecbff..64ecd588 100644 --- a/connection_test.go +++ b/connection_test.go @@ -129,65 +129,98 @@ func TestCheckNamedValue(t *testing.T) { } } -// TestCleanCancel tests passed context is cancelled at start. +// TestNoArgsCommandCleanCancel tests passed context is cancelled at start. // No packet should be sent. Connection should keep current status. -func TestCleanCancel(t *testing.T) { - mc := &mysqlConn{ - closech: make(chan struct{}), - } - mc.startWatcher() - defer mc.cleanup() - - ctx, cancel := context.WithCancel(context.Background()) - cancel() - - for range 3 { // Repeat same behavior - err := mc.Ping(ctx) - if err != context.Canceled { - t.Errorf("expected context.Canceled, got %#v", err) - } - - if mc.closed.Load() { - t.Error("expected mc is not closed, closed actually") - } - - if mc.watching { - t.Error("expected watching is false, but true") - } +func TestNoArgsCommandCleanCancel(t *testing.T) { + for _, test := range []struct { + name string + funcToCall func(ctx context.Context, mc *mysqlConn) error + } { + {name: "Ping", funcToCall: func(ctx context.Context, mc *mysqlConn) error { return mc.Ping(ctx) }}, + {name: "Reset", funcToCall: func(ctx context.Context, mc *mysqlConn) error { return mc.Reset(ctx) }}, + } { + test := test + t.Run(test.name, func(t *testing.T) { + mc := &mysqlConn{ + closech: make(chan struct{}), + } + mc.startWatcher() + defer mc.cleanup() + + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + for range 3 { // Repeat same behavior + err := test.funcToCall(ctx, mc) + if err != context.Canceled { + t.Errorf("expected context.Canceled, got %#v", err) + } + + if mc.closed.Load() { + t.Error("expected mc is not closed, closed actually") + } + + if mc.watching { + t.Error("expected watching is false, but true") + } + } + }) } } -func TestPingMarkBadConnection(t *testing.T) { - nc := badConnection{err: errors.New("boom")} - mc := &mysqlConn{ - netConn: nc, - buf: newBuffer(), - maxAllowedPacket: defaultMaxAllowedPacket, - closech: make(chan struct{}), - cfg: NewConfig(), - } - - err := mc.Ping(context.Background()) - - if err != driver.ErrBadConn { - t.Errorf("expected driver.ErrBadConn, got %#v", err) +func TestNoArgsCommandMarkBadConnection(t *testing.T) { + for _, test := range []struct { + name string + funcToCall func(mc *mysqlConn) error + } { + {name: "Ping", funcToCall: func(mc *mysqlConn) error { return mc.Ping(context.Background()) }}, + {name: "Reset", funcToCall: func(mc *mysqlConn) error { return mc.Reset(context.Background()) }}, + } { + test := test + t.Run(test.name, func(t *testing.T) { + nc := badConnection{err: errors.New("boom")} + mc := &mysqlConn{ + netConn: nc, + buf: newBuffer(), + maxAllowedPacket: defaultMaxAllowedPacket, + closech: make(chan struct{}), + cfg: NewConfig(), + } + + err := test.funcToCall(mc) + + if err != driver.ErrBadConn { + t.Errorf("expected driver.ErrBadConn, got %#v", err) + } + }) } } -func TestPingErrInvalidConn(t *testing.T) { - nc := badConnection{err: errors.New("failed to write"), n: 10} - mc := &mysqlConn{ - netConn: nc, - buf: newBuffer(), - maxAllowedPacket: defaultMaxAllowedPacket, - closech: make(chan struct{}), - cfg: NewConfig(), - } - - err := mc.Ping(context.Background()) - - if err != nc.err { - t.Errorf("expected %#v, got %#v", nc.err, err) +func TestNoArgsCommandErrInvalidConn(t *testing.T) { + for _, test := range []struct { + name string + funcToCall func(mc *mysqlConn) error + } { + {name: "Ping", funcToCall: func(mc *mysqlConn) error { return mc.Ping(context.Background()) }}, + {name: "Reset", funcToCall: func(mc *mysqlConn) error { return mc.Reset(context.Background()) }}, + } { + test := test + t.Run(test.name, func(t *testing.T) { + nc := badConnection{err: errors.New("failed to write"), n: 10} + mc := &mysqlConn{ + netConn: nc, + buf: newBuffer(), + maxAllowedPacket: defaultMaxAllowedPacket, + closech: make(chan struct{}), + cfg: NewConfig(), + } + + err := test.funcToCall(mc) + + if err != nc.err { + t.Errorf("expected %#v, got %#v", nc.err, err) + } + }) } } diff --git a/const.go b/const.go index 6f0cdf30..8c56069a 100644 --- a/const.go +++ b/const.go @@ -115,6 +115,7 @@ const ( comStmtReset comSetOption comStmtFetch + comConnReset = 31 ) // https://dev.mysql.com/doc/internals/en/com-query-response.html#packet-Protocol::ColumnType diff --git a/driver_test.go b/driver_test.go index ec0f2877..79a7ca81 100644 --- a/driver_test.go +++ b/driver_test.go @@ -2322,13 +2322,69 @@ func TestRejectReadOnly(t *testing.T) { } func TestPing(t *testing.T) { - ctx := context.Background() runTests(t, dsn, func(dbt *DBTest) { if err := dbt.db.Ping(); err != nil { dbt.fail("Ping", "Ping", err) } }) +} + +func TestNoArgsCommand(t *testing.T) { + ctx := context.Background() + for _, test := range []struct{ + method string + query string + funcToCall func(ctx context.Context, mc *mysqlConn) error + } { + {method: "Ping", query: "Ping", funcToCall: func(ctx context.Context, mc *mysqlConn) error {return mc.Ping(ctx)}}, + {method: "Conn", query: "Reset", funcToCall: func(ctx context.Context, mc *mysqlConn) error {return mc.Reset(ctx)}}, + } { + test := test + t.Run(test.method+"_"+test.query, func(t *testing.T) { + runTests(t, dsn, func(dbt *DBTest) { + conn, err := dbt.db.Conn(ctx) + if err != nil { + dbt.fail("db", "Conn", err) + } + + // Check that affectedRows and insertIds are cleared after each call. + conn.Raw(func(conn any) error { + c := conn.(*mysqlConn) + + // Issue a query that sets affectedRows and insertIds. + q, err := c.Query(`SELECT 1`, nil) + if err != nil { + dbt.fail("Conn", "Query", err) + } + if got, want := c.result.affectedRows, []int64{0}; !reflect.DeepEqual(got, want) { + dbt.Fatalf("bad affectedRows: got %v, want=%v", got, want) + } + if got, want := c.result.insertIds, []int64{0}; !reflect.DeepEqual(got, want) { + dbt.Fatalf("bad insertIds: got %v, want=%v", got, want) + } + q.Close() + + // Verify that Ping()/Reset() clears both fields. + for range 2 { + if err := test.funcToCall(ctx, c); err != nil { + dbt.fail(test.method, test.query, err) + } + if got, want := c.result.affectedRows, []int64(nil); !reflect.DeepEqual(got, want) { + t.Errorf("bad affectedRows: got %v, want=%v", got, want) + } + if got, want := c.result.insertIds, []int64(nil); !reflect.DeepEqual(got, want) { + t.Errorf("bad affectedRows: got %v, want=%v", got, want) + } + } + return nil + }) + }) + }) + } +} +func TestReset(t *testing.T) { + ctx := context.Background() runTests(t, dsn, func(dbt *DBTest) { conn, err := dbt.db.Conn(ctx) if err != nil { @@ -2339,31 +2395,49 @@ func TestPing(t *testing.T) { conn.Raw(func(conn any) error { c := conn.(*mysqlConn) - // Issue a query that sets affectedRows and insertIds. - q, err := c.Query(`SELECT 1`, nil) + _, err = c.ExecContext(ctx, "SET @a := 1", nil) + if err != nil { + dbt.fail("Conn", "ExecContext", err) + } + var rows driver.Rows + rows, err = c.QueryContext(ctx, "SELECT @a", nil) if err != nil { - dbt.fail("Conn", "Query", err) + dbt.fail("Conn", "QueryContext", err) } - if got, want := c.result.affectedRows, []int64{0}; !reflect.DeepEqual(got, want) { - dbt.Fatalf("bad affectedRows: got %v, want=%v", got, want) + result := []driver.Value{0} + err = rows.Next(result) + if err != nil { + dbt.fail("Rows", "Next", err) } - if got, want := c.result.insertIds, []int64{0}; !reflect.DeepEqual(got, want) { - dbt.Fatalf("bad insertIds: got %v, want=%v", got, want) + err = rows.Close() + if err != nil { + dbt.fail("Rows", "Close", err) + } + if !reflect.DeepEqual([]driver.Value{int64(1)}, result) { + dbt.Fatalf("failed to set @a to 1 with SET: got %v, want=%v", result, []driver.Value{int64(1)}) } - q.Close() - // Verify that Ping() clears both fields. - for range 2 { - if err := c.Ping(ctx); err != nil { - dbt.fail("Pinger", "Ping", err) - } - if got, want := c.result.affectedRows, []int64(nil); !reflect.DeepEqual(got, want) { - t.Errorf("bad affectedRows: got %v, want=%v", got, want) - } - if got, want := c.result.insertIds, []int64(nil); !reflect.DeepEqual(got, want) { - t.Errorf("bad affectedRows: got %v, want=%v", got, want) - } + err = c.Reset(ctx) + if err != nil { + dbt.fail("Conn", "Reset", err) + } + + rows, err = c.QueryContext(ctx, "SELECT @a", nil) + if err != nil { + dbt.fail("Conn", "QueryContext", err) + } + err = rows.Next(result) + if err != nil { + dbt.fail("Rows", "Next", err) + } + err = rows.Close() + if err != nil { + dbt.fail("Rows", "Close", err) } + if !reflect.DeepEqual([]driver.Value{nil}, result) { + dbt.Fatalf("Reset did not reset the session (@a is still set): got %v, want=%v", result, []driver.Value{nil}) + } + return nil }) }) From 43cdc228f1e85e2ba34cae8710b97638bc534b76 Mon Sep 17 00:00:00 2001 From: Dmitry Zenovich Date: Tue, 26 Aug 2025 02:02:13 +0300 Subject: [PATCH 03/10] add Dmitry Zenovich into the list of authors --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 05e71df4..514736d1 100644 --- a/AUTHORS +++ b/AUTHORS @@ -42,6 +42,7 @@ Demouth Diego Dupin Dirkjan Bussink DisposaBoy +Dmitry Zenovich Egor Smolyakov Erwan Martin Evan Elias From 8630716859e32cd546111022a3db457fd0fa1024 Mon Sep 17 00:00:00 2001 From: Dmitry Zenovich Date: Tue, 26 Aug 2025 14:56:20 +0300 Subject: [PATCH 04/10] rename constant comConnReset to comResetConnection, add preceding constants (comDaemon and comBinlogDumpGTID) to make their values auto-generated --- connection.go | 2 +- const.go | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/connection.go b/connection.go index df2164df..4b73ed49 100644 --- a/connection.go +++ b/connection.go @@ -687,7 +687,7 @@ func (mc *mysqlConn) startWatcher() { // Reset resets the MySQL connection. func (mc *mysqlConn) Reset(ctx context.Context) (err error) { - return mc.sendNoArgsCommandWithResultOK(ctx, comConnReset) + return mc.sendNoArgsCommandWithResultOK(ctx, comResetConnection) } func (mc *mysqlConn) CheckNamedValue(nv *driver.NamedValue) (err error) { diff --git a/const.go b/const.go index 8c56069a..85a27112 100644 --- a/const.go +++ b/const.go @@ -115,7 +115,9 @@ const ( comStmtReset comSetOption comStmtFetch - comConnReset = 31 + comDaemon + comBinlogDumpGTID + comResetConnection ) // https://dev.mysql.com/doc/internals/en/com-query-response.html#packet-Protocol::ColumnType From 527b0ab4d3807f7aec78635c5f410341248c59c7 Mon Sep 17 00:00:00 2001 From: Dmitry Zenovich Date: Tue, 26 Aug 2025 15:03:17 +0300 Subject: [PATCH 05/10] rename method mysqlConn.sendNoArgsCommandWithResultOK() to mysqlConn.sendSimpleCommandOK(), rename related tests accordingly --- connection.go | 6 +++--- connection_test.go | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/connection.go b/connection.go index 4b73ed49..9663eb96 100644 --- a/connection.go +++ b/connection.go @@ -503,10 +503,10 @@ func (mc *mysqlConn) finish() { // Ping implements driver.Pinger interface func (mc *mysqlConn) Ping(ctx context.Context) (err error) { - return mc.sendNoArgsCommandWithResultOK(ctx, comPing) + return mc.sendSimpleCommandOK(ctx, comPing) } -func (mc *mysqlConn) sendNoArgsCommandWithResultOK(ctx context.Context, cmd byte) (err error) { +func (mc *mysqlConn) sendSimpleCommandOK(ctx context.Context, cmd byte) (err error) { if mc.closed.Load() { return driver.ErrBadConn } @@ -687,7 +687,7 @@ func (mc *mysqlConn) startWatcher() { // Reset resets the MySQL connection. func (mc *mysqlConn) Reset(ctx context.Context) (err error) { - return mc.sendNoArgsCommandWithResultOK(ctx, comResetConnection) + return mc.sendSimpleCommandOK(ctx, comResetConnection) } func (mc *mysqlConn) CheckNamedValue(nv *driver.NamedValue) (err error) { diff --git a/connection_test.go b/connection_test.go index 64ecd588..6de595e9 100644 --- a/connection_test.go +++ b/connection_test.go @@ -129,9 +129,9 @@ func TestCheckNamedValue(t *testing.T) { } } -// TestNoArgsCommandCleanCancel tests passed context is cancelled at start. +// TestSimpleCommandOKCleanCancel tests passed context is cancelled at start. // No packet should be sent. Connection should keep current status. -func TestNoArgsCommandCleanCancel(t *testing.T) { +func TestSimpleCommandOKCleanCancel(t *testing.T) { for _, test := range []struct { name string funcToCall func(ctx context.Context, mc *mysqlConn) error @@ -168,7 +168,7 @@ func TestNoArgsCommandCleanCancel(t *testing.T) { } } -func TestNoArgsCommandMarkBadConnection(t *testing.T) { +func TestSimpleCommandOKMarkBadConnection(t *testing.T) { for _, test := range []struct { name string funcToCall func(mc *mysqlConn) error @@ -196,7 +196,7 @@ func TestNoArgsCommandMarkBadConnection(t *testing.T) { } } -func TestNoArgsCommandErrInvalidConn(t *testing.T) { +func TestSimpleCommandOKErrInvalidConn(t *testing.T) { for _, test := range []struct { name string funcToCall func(mc *mysqlConn) error From 3d484fca987600b51bcff37ac5a1901f5bfbaca3 Mon Sep 17 00:00:00 2001 From: Dmitry Zenovich Date: Tue, 26 Aug 2025 15:06:21 +0300 Subject: [PATCH 06/10] improve the comment of mysqlConn.Reset() --- connection.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/connection.go b/connection.go index 9663eb96..55c09bd2 100644 --- a/connection.go +++ b/connection.go @@ -685,7 +685,16 @@ func (mc *mysqlConn) startWatcher() { }() } -// Reset resets the MySQL connection. +// Reset resets the server-side session state using COM_RESET_CONNECTION. +// It clears most per-session state (e.g., user variables, prepared statements) +// without re-authenticating. +// Usage hint: call via database/sql.Conn.Raw using a method assertion: +// conn.Raw(func(c any) error { +// if r, ok := c.(interface{ Reset(context.Context) error }); ok { +// return r.Reset(ctx) +// } +// return nil +// }) func (mc *mysqlConn) Reset(ctx context.Context) (err error) { return mc.sendSimpleCommandOK(ctx, comResetConnection) } From 272fe8157fcbe90463c5f56586e7d684d8730f1d Mon Sep 17 00:00:00 2001 From: Dmitry Zenovich Date: Tue, 26 Aug 2025 15:15:50 +0300 Subject: [PATCH 07/10] fix issues in tests TestNoArgsCommand (+ rename it to TestSimpleCommandOK) and TestReset: fix messages and comments, close DB connections --- driver_test.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/driver_test.go b/driver_test.go index 79a7ca81..0e6be5b3 100644 --- a/driver_test.go +++ b/driver_test.go @@ -2329,14 +2329,14 @@ func TestPing(t *testing.T) { }) } -func TestNoArgsCommand(t *testing.T) { +func TestSimpleCommandOK(t *testing.T) { ctx := context.Background() for _, test := range []struct{ method string query string funcToCall func(ctx context.Context, mc *mysqlConn) error } { - {method: "Ping", query: "Ping", funcToCall: func(ctx context.Context, mc *mysqlConn) error {return mc.Ping(ctx)}}, + {method: "Pinger", query: "Ping", funcToCall: func(ctx context.Context, mc *mysqlConn) error {return mc.Ping(ctx)}}, {method: "Conn", query: "Reset", funcToCall: func(ctx context.Context, mc *mysqlConn) error {return mc.Reset(ctx)}}, } { test := test @@ -2346,6 +2346,7 @@ func TestNoArgsCommand(t *testing.T) { if err != nil { dbt.fail("db", "Conn", err) } + defer conn.Close() // Check that affectedRows and insertIds are cleared after each call. conn.Raw(func(conn any) error { @@ -2373,7 +2374,7 @@ func TestNoArgsCommand(t *testing.T) { t.Errorf("bad affectedRows: got %v, want=%v", got, want) } if got, want := c.result.insertIds, []int64(nil); !reflect.DeepEqual(got, want) { - t.Errorf("bad affectedRows: got %v, want=%v", got, want) + t.Errorf("bad insertIds: got %v, want=%v", got, want) } } return nil @@ -2390,8 +2391,9 @@ func TestReset(t *testing.T) { if err != nil { dbt.fail("db", "Conn", err) } + defer conn.Close() - // Check that affectedRows and insertIds are cleared after each call. + // Verify that COM_RESET_CONNECTION clears session state (e.g., user variables). conn.Raw(func(conn any) error { c := conn.(*mysqlConn) From 9c308f10164ca6d68a4bca8192d1f381456d85d6 Mon Sep 17 00:00:00 2001 From: Dmitry Zenovich Date: Tue, 26 Aug 2025 16:00:17 +0300 Subject: [PATCH 08/10] allow skipping TestReset and TestSimpleCommandOK_Reset when COM_RESET_CONNECTION is not supported by the DB, support the text protocol when using driver.Rows() in TestReset() --- driver_test.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/driver_test.go b/driver_test.go index 0e6be5b3..e9860412 100644 --- a/driver_test.go +++ b/driver_test.go @@ -2368,6 +2368,11 @@ func TestSimpleCommandOK(t *testing.T) { // Verify that Ping()/Reset() clears both fields. for range 2 { if err := test.funcToCall(ctx, c); err != nil { + // Skip Reset on servers lacking COM_RESET_CONNECTION support. + if test.query == "Reset" { + maybeSkip(t, err, 1047) // ER_UNKNOWN_COM_ERROR + maybeSkip(t, err, 1235) // ER_NOT_SUPPORTED_YET + } dbt.fail(test.method, test.query, err) } if got, want := c.result.affectedRows, []int64(nil); !reflect.DeepEqual(got, want) { @@ -2406,7 +2411,7 @@ func TestReset(t *testing.T) { if err != nil { dbt.fail("Conn", "QueryContext", err) } - result := []driver.Value{0} + result := []driver.Value{nil} err = rows.Next(result) if err != nil { dbt.fail("Rows", "Next", err) @@ -2415,12 +2420,16 @@ func TestReset(t *testing.T) { if err != nil { dbt.fail("Rows", "Close", err) } - if !reflect.DeepEqual([]driver.Value{int64(1)}, result) { - dbt.Fatalf("failed to set @a to 1 with SET: got %v, want=%v", result, []driver.Value{int64(1)}) + if !(reflect.DeepEqual([]driver.Value{int64(1)}, result) || + reflect.DeepEqual([]driver.Value{[]byte("1")}, result)) { + dbt.Fatalf("failed to set @a to 1 with SET: got %v, want int64(1) or []byte(\"1\")", result) } err = c.Reset(ctx) if err != nil { + // Allow skipping on unsupported COM_RESET_CONNECTION + maybeSkip(t, err, 1047) // ER_UNKNOWN_COM_ERROR + maybeSkip(t, err, 1235) // ER_NOT_SUPPORTED_YET dbt.fail("Conn", "Reset", err) } From 203cac4decba3cd53a4c0d585fb71eee7318ed68 Mon Sep 17 00:00:00 2001 From: Dmitry Zenovich Date: Tue, 26 Aug 2025 16:20:35 +0300 Subject: [PATCH 09/10] use QueryContext instead of Query in TestSimpleCommandOK and verify result of rows closing there, add a comment explaining why we do not clear the destination before calling rows.Next() in TestReset --- driver_test.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/driver_test.go b/driver_test.go index e9860412..9a718a60 100644 --- a/driver_test.go +++ b/driver_test.go @@ -2353,9 +2353,9 @@ func TestSimpleCommandOK(t *testing.T) { c := conn.(*mysqlConn) // Issue a query that sets affectedRows and insertIds. - q, err := c.Query(`SELECT 1`, nil) + q, err := c.QueryContext(ctx, `SELECT 1`, nil) if err != nil { - dbt.fail("Conn", "Query", err) + dbt.fail("Conn", "QueryContext", err) } if got, want := c.result.affectedRows, []int64{0}; !reflect.DeepEqual(got, want) { dbt.Fatalf("bad affectedRows: got %v, want=%v", got, want) @@ -2363,7 +2363,9 @@ func TestSimpleCommandOK(t *testing.T) { if got, want := c.result.insertIds, []int64{0}; !reflect.DeepEqual(got, want) { dbt.Fatalf("bad insertIds: got %v, want=%v", got, want) } - q.Close() + if err := q.Close(); err != nil { + dbt.fail("Rows", "Close", err) + } // Verify that Ping()/Reset() clears both fields. for range 2 { @@ -2437,6 +2439,8 @@ func TestReset(t *testing.T) { if err != nil { dbt.fail("Conn", "QueryContext", err) } + // We intentionally do not clear the destination before calling Next() + // to make sure the SELECT really returns nil err = rows.Next(result) if err != nil { dbt.fail("Rows", "Next", err) From 02afe1ddae6c3029be51afa104433f89c2758515 Mon Sep 17 00:00:00 2001 From: Dmitry Zenovich Date: Tue, 26 Aug 2025 16:35:29 +0300 Subject: [PATCH 10/10] seed the result with a sentinel explicitly in TestReset --- driver_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/driver_test.go b/driver_test.go index 9a718a60..77accbd7 100644 --- a/driver_test.go +++ b/driver_test.go @@ -2439,8 +2439,8 @@ func TestReset(t *testing.T) { if err != nil { dbt.fail("Conn", "QueryContext", err) } - // We intentionally do not clear the destination before calling Next() - // to make sure the SELECT really returns nil + // Seed with a sentinel to ensure Rows.Next overwrites it with nil. + result = []driver.Value{"sentinel-non-nil"} err = rows.Next(result) if err != nil { dbt.fail("Rows", "Next", err)