-
Notifications
You must be signed in to change notification settings - Fork 18k
database/sql: Tx.ExecContext() no longer calls Stmt.CheckNamedValue() #23820
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Confirmed with master 755b36a |
What database driver are you using? |
If your driver has a ExecContext method on your Conn, your I've reviewed the Feel free to post further questions to golang-nuts or https://groups.google.com/forum/#!forum/golang-sql groups. |
The ExecContext receiver is on the Conn and the Stmt. |
You say "looks like it will work as intended." Did you actually test? |
You said |
Thanks. I'll try today. Worked in 1.9 but not 1.10. |
@kardianos @andybons, Thank you for looking at this and your help. I created a go test that can be dropped into database/sql that shows how go 1.9 calls Stmt.CheckNamedValue() and 1.10 does not. I created a simple test driver that implements the bare minimum. Can you help me understand why driver.Conn must implement CheckNamedValue()? Here is how I am thinking about this:
go 1.9 output
go 1.10 output
Drop m_test.go under database/sql. Run m_test.gopackage sql
import (
"context"
"database/sql/driver"
"io"
"runtime"
"testing"
)
type Driver struct {
T *testing.T
}
func (o *Driver) Open(dsn string) (driver.Conn, error) {
return &TCon{t: o.T}, nil
}
type TCon struct {
t *testing.T
}
func (o *TCon) Prepare(query string) (driver.Stmt, error) {
o.t.Log("TCon.Prepare")
return &TStmt{con: o}, nil
}
// func (o *TCon) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
// o.t.Log("TCon.ExecContext")
// return nil, driver.ErrSkip
// }
func (o *TCon) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
o.t.Log("TCon.QueryContext")
return &TRows{&TStmt{o}}, nil
}
func (o *TCon) Close() error {
return nil
}
func (o *TCon) Begin() (driver.Tx, error) {
return o, nil
}
func (o *TCon) Commit() error {
return nil
}
func (o *TCon) Rollback() error {
return nil
}
type TStmt struct {
con *TCon
}
func (o TStmt) Close() error {
return nil
}
func (o *TStmt) NumInput() int {
return -1
}
// Docs are incorrect. Exec must be implemented by Stmt for driver.Stmt
// http://godoc.org/database/sql/driver#Stmt
//
func (o *TStmt) Exec(args []driver.Value) (driver.Result, error) {
o.con.t.Log("Stmt.Exec")
return nil, driver.ErrSkip
}
// Docs are incorrect. Exec must be implemented by Stmt for driver.Stmt
// http://godoc.org/database/sql/driver#Stmt
//
func (o *TStmt) Query(args []driver.Value) (driver.Rows, error) {
o.con.t.Log("Stmt.Query")
return nil, driver.ErrSkip
}
func (o *TStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
o.con.t.Log("Stmt.ExecContext")
return nil, driver.ErrSkip
}
func (o *TStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
o.con.t.Log("Stmt.QueryContext")
return &TRows{st: o}, nil
}
// http://gd/database/sql/driver#NamedValueChecker
//
func (o *TStmt) CheckNamedValue(nv *driver.NamedValue) error {
o.con.t.Log("Stmt.CheckNamedValue", nv.Name, nv.Value)
return nil
}
type TRows struct {
st *TStmt
}
func (o *TRows) Columns() []string {
return []string{}
}
func (o *TRows) Close() error {
return nil
}
func (o *TRows) Next(dest []driver.Value) error {
return io.EOF
}
func TestNamedValue(t *testing.T) {
Register("testdb", &Driver{t})
db, err := Open("testdb", "")
if err != nil {
t.Fatal(err)
}
defer db.Close()
ctx := context.Background()
con, err := db.Conn(ctx)
if err != nil {
t.Fatal(err)
}
defer con.Close()
t.Log("go version:", runtime.Version())
t.Log("before QueryContext called")
rows, err := con.QueryContext(ctx, "select ...", Named("", 7), Named("", 8))
if err != nil {
t.Fatal(err)
return
}
t.Log("after QueryContext called, Stmt.CheckNamedValue called?")
defer rows.Close()
for rows.Next() {
if err := rows.Scan(); err != nil {
t.Fatal(err)
return
}
}
if err := rows.Err(); err != nil {
t.Fatal(err)
return
}
} |
@aletheia7 Great write-up. I'll respond in detail: Here is the docs for
In otherwords, if Execer and ExecerContext is defined on driver.Conn, the execution flow bypasses the creation of a driver.Stmt. If there is no driver.Stmt, it does not know if CheckNamedValue is defined on Stmt, because it has been provided no Stmt. Thus it just looks for ExecerContext. This is the logical reason why it doesn't work in Go1.10. Go1.9 first checks for the non-context driver variants for Queryer and Execer, then it checks for the context versions but only if the non-context versions exist (Queryer must exist for QueryerContext to be used in Go1.9). As documented in the Go1.10 release notes here https://golang.org/doc/go1.10#database/sql/driver
Your reference driver as listed above (which is great! Thanks for documenting your case so clearly!) only implements QueryerContext, not Queryer. You can also tell from the helpful trace you included that the Go1.9 version does NOT call the QueryContext method but is forced to create a Stmt. However in Go1.10 the QueryContext method is checked for independent of the Query method, so it no longer creates a Stmt. No Stmt, no CheckNamedValue (as defined in your system). To resolve this issue, you should either remove QueryContext from TCon, or add CheckNamedValue to TCon. |
What version of Go are you using (
go version
)?go version go1.10rc2 linux/amd64
Does this issue reproduce with the latest release?
What operating system and processor architecture are you using (
go env
)?Debian Linux Testing/Buster
What did you do?
My internal app calls my sql.Driver. The sql.Tx.ExecContext() calls Stmt.CheckNamedValue() for each argument with 1.9.4. This no longer happens with 1.10rc2.
I wrote the sql.Driver and it's easy for me to insert a debug call into the Stmt.CheckNamedValue().
I have the Debian golang-1.9 and golang-1.10 (sid) installed side-by-side. I can switch to either one by changing a link under /usr/lib/go -> go-1.9 or go-1.10.
What did you expect to see?
Stmt.CheckedNamedValue() should be called for each argument.
What did you see instead?
CheckedNameValue() is not called.
The text was updated successfully, but these errors were encountered: