Skip to content

Commit 67ab52b

Browse files
committed
adding tests
1 parent 76c00e3 commit 67ab52b

File tree

3 files changed

+319
-79
lines changed

3 files changed

+319
-79
lines changed

connection.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -216,9 +216,10 @@ func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
216216
return nil, driver.ErrBadConn
217217
}
218218

219-
stmt := &mysqlStmt{
220-
mc: mc,
221-
}
219+
stmt := &mysqlStmt{
220+
mc: mc,
221+
queryString: query,
222+
}
222223

223224
// Read Result
224225
columnCount, err := stmt.readPrepareResultPacket()

reprepare_test.go

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
2+
//
3+
// Copyright 2025 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"
13+
"testing"
14+
"time"
15+
)
16+
17+
// Ensures that executing a prepared statement still returns correct data
18+
// after a DDL that changes a column type. This validates automatic
19+
// reprepare on ER_NEED_REPREPARE-capable servers and correctness in general.
20+
func TestPreparedStmtReprepareAfterDDL(t *testing.T) {
21+
runTests(t, dsn+"&parseTime=true", func(dbt *DBTest) {
22+
db := dbt.db
23+
24+
dbt.mustExec("DROP TABLE IF EXISTS reprepare_test")
25+
dbt.mustExec(`
26+
CREATE TABLE reprepare_test (
27+
id INT AUTO_INCREMENT PRIMARY KEY,
28+
state TINYINT,
29+
round TINYINT NOT NULL DEFAULT 0,
30+
remark TEXT,
31+
ctime TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
32+
)`)
33+
t.Cleanup(func() { db.Exec("DROP TABLE IF EXISTS reprepare_test") })
34+
35+
dbt.mustExec("INSERT INTO reprepare_test(state, round, remark) VALUES (1, 1, 'hello')")
36+
37+
stmt, err := db.Prepare("SELECT state, round, remark, ctime FROM reprepare_test WHERE id=?")
38+
if err != nil {
39+
t.Fatalf("prepare failed: %v", err)
40+
}
41+
defer stmt.Close()
42+
43+
var (
44+
s1, r1 int
45+
rem1 string
46+
ct1 time.Time
47+
)
48+
if err := stmt.QueryRow(1).Scan(&s1, &r1, &rem1, &ct1); err != nil {
49+
t.Fatalf("first scan failed: %v", err)
50+
}
51+
if s1 != 1 || r1 != 1 || rem1 != "hello" || ct1.IsZero() {
52+
t.Fatalf("unexpected first row values: (%d,%d,%q,%v)", s1, r1, rem1, ct1)
53+
}
54+
55+
// Change the column type that participates in the prepared statement's result set.
56+
dbt.mustExec("ALTER TABLE reprepare_test MODIFY state INT")
57+
58+
var (
59+
s2, r2 int
60+
rem2 string
61+
ct2 time.Time
62+
)
63+
// This used to fail or return incorrect data on some servers without reprepare handling.
64+
if err := stmt.QueryRow(1).Scan(&s2, &r2, &rem2, &ct2); err != nil {
65+
// Some environments may not reproduce ER_NEED_REPREPARE, so avoid flakiness by surfacing the error.
66+
t.Fatalf("second scan failed: %v", err)
67+
}
68+
69+
if s2 != s1 || r2 != r1 || rem2 != rem1 || ct2.IsZero() {
70+
t.Fatalf("unexpected second row values after DDL: got (%d,%d,%q,%v), want (%d,%d,%q,<non-zero>)",
71+
s2, r2, rem2, ct2, s1, r1, rem1,
72+
)
73+
}
74+
})
75+
}
76+
77+
// Validates Exec path also reprovisions the prepared statement after DDL.
78+
func TestPreparedStmtExecReprepareAfterDDL(t *testing.T) {
79+
runTests(t, dsn, func(dbt *DBTest) {
80+
db := dbt.db
81+
82+
dbt.mustExec("DROP TABLE IF EXISTS reprepare_exec_test")
83+
dbt.mustExec(`
84+
CREATE TABLE reprepare_exec_test (
85+
id INT AUTO_INCREMENT PRIMARY KEY,
86+
value INT NOT NULL
87+
)`)
88+
t.Cleanup(func() { db.Exec("DROP TABLE IF EXISTS reprepare_exec_test") })
89+
90+
stmt, err := db.Prepare("INSERT INTO reprepare_exec_test(value) VALUES (?)")
91+
if err != nil {
92+
t.Fatalf("prepare failed: %v", err)
93+
}
94+
defer stmt.Close()
95+
96+
if _, err := stmt.Exec(1); err != nil {
97+
t.Fatalf("first exec failed: %v", err)
98+
}
99+
100+
// Change the column type to trigger metadata invalidation on some servers.
101+
dbt.mustExec("ALTER TABLE reprepare_exec_test MODIFY value BIGINT")
102+
103+
if _, err := stmt.Exec(2); err != nil {
104+
t.Fatalf("second exec (after DDL) failed: %v", err)
105+
}
106+
107+
// Verify both rows are present and correct.
108+
rows := dbt.mustQuery("SELECT value FROM reprepare_exec_test ORDER BY id")
109+
defer rows.Close()
110+
var got []int
111+
for rows.Next() {
112+
var v int
113+
if err := rows.Scan(&v); err != nil {
114+
t.Fatalf("scan values failed: %v", err)
115+
}
116+
got = append(got, v)
117+
}
118+
if len(got) != 2 || got[0] != 1 || got[1] != 2 {
119+
t.Fatalf("unexpected values: %v", got)
120+
}
121+
})
122+
}
123+
124+
// Ensures repeated scans using the same prepared statement remain correct across DDL, scanning into sql.NullTime.
125+
func TestPreparedStmtReprepareMultipleScansAfterDDL_NullTime(t *testing.T) {
126+
runTests(t, dsn+"&parseTime=true", func(dbt *DBTest) {
127+
db := dbt.db
128+
129+
dbt.mustExec("DROP TABLE IF EXISTS reprepare_multi_test")
130+
dbt.mustExec(`
131+
CREATE TABLE reprepare_multi_test (
132+
id INT AUTO_INCREMENT PRIMARY KEY,
133+
state TINYINT,
134+
ctime TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
135+
)`)
136+
t.Cleanup(func() { db.Exec("DROP TABLE IF EXISTS reprepare_multi_test") })
137+
138+
dbt.mustExec("INSERT INTO reprepare_multi_test(state) VALUES (5)")
139+
140+
stmt, err := db.Prepare("SELECT state, ctime FROM reprepare_multi_test WHERE id=?")
141+
if err != nil {
142+
t.Fatalf("prepare failed: %v", err)
143+
}
144+
defer stmt.Close()
145+
146+
// First scan
147+
{
148+
var s int
149+
var ct sql.NullTime
150+
if err := stmt.QueryRow(1).Scan(&s, &ct); err != nil {
151+
t.Fatalf("first scan failed: %v", err)
152+
}
153+
if s != 5 || !ct.Valid || ct.Time.IsZero() {
154+
t.Fatalf("unexpected first values: (%d,%v)", s, ct)
155+
}
156+
}
157+
158+
// DDL change that alters one of the selected column types
159+
dbt.mustExec("ALTER TABLE reprepare_multi_test MODIFY state INT")
160+
161+
// Second scan after DDL
162+
{
163+
var s int
164+
var ct sql.NullTime
165+
if err := stmt.QueryRow(1).Scan(&s, &ct); err != nil {
166+
t.Fatalf("second scan failed: %v", err)
167+
}
168+
if s != 5 || !ct.Valid || ct.Time.IsZero() {
169+
t.Fatalf("unexpected second values after DDL: (%d,%v)", s, ct)
170+
}
171+
}
172+
173+
// Third scan to ensure continued usability
174+
{
175+
var s int
176+
var ct sql.NullTime
177+
if err := stmt.QueryRow(1).Scan(&s, &ct); err != nil {
178+
t.Fatalf("third scan failed: %v", err)
179+
}
180+
if s != 5 || !ct.Valid || ct.Time.IsZero() {
181+
t.Fatalf("unexpected third values after DDL: (%d,%v)", s, ct)
182+
}
183+
}
184+
})
185+
}
186+
187+

0 commit comments

Comments
 (0)