diff --git a/sqlite3.go b/sqlite3.go index dd849be..ef54c20 100644 --- a/sqlite3.go +++ b/sqlite3.go @@ -2294,6 +2294,17 @@ func stmtArgs(args []driver.NamedValue, start, na int) []driver.NamedValue { return stmtArgs } +// bindError converts a non-OK return code from bindValue into an error. +// The synthetic SQLITE_MISUSE returned for unsupported Go types is never +// recorded in the database handle, so lastError may report no error; fall +// back to an explicit message instead of silently ignoring the failure. +func (s *SQLiteStmt) bindError(v driver.Value) error { + if err := s.c.lastError(); err != nil { + return err + } + return fmt.Errorf("sqlite3: unsupported bind type %T", v) +} + func (s *SQLiteStmt) bind(args []driver.NamedValue) error { rv := C._sqlite3_reset_clear(s.s) if rv != C.SQLITE_ROW && rv != C.SQLITE_OK && rv != C.SQLITE_DONE { @@ -2313,7 +2324,7 @@ func (s *SQLiteStmt) bind(args []driver.NamedValue) error { n := C.int(arg.Ordinal) rv = bindValue(s.s, n, arg.Value) if rv != C.SQLITE_OK { - return s.c.lastError() + return s.bindError(arg.Value) } } return nil @@ -2323,7 +2334,7 @@ func (s *SQLiteStmt) bind(args []driver.NamedValue) error { if arg.Name == "" { rv = bindValue(s.s, C.int(arg.Ordinal), arg.Value) if rv != C.SQLITE_OK { - return s.c.lastError() + return s.bindError(arg.Value) } continue } @@ -2334,7 +2345,7 @@ func (s *SQLiteStmt) bind(args []driver.NamedValue) error { } rv = bindValue(s.s, C.int(idx), arg.Value) if rv != C.SQLITE_OK { - return s.c.lastError() + return s.bindError(arg.Value) } } } diff --git a/sqlite3_test.go b/sqlite3_test.go index d8a535b..10ecf7b 100644 --- a/sqlite3_test.go +++ b/sqlite3_test.go @@ -1324,6 +1324,50 @@ func TestDateTimeNow(t *testing.T) { } } +func TestBindErrorPaths(t *testing.T) { + d := &SQLiteDriver{} + conn, err := d.Open(":memory:") + if err != nil { + t.Fatal("Failed to open database:", err) + } + defer conn.Close() + c := conn.(*SQLiteConn) + + if _, err := c.Exec("CREATE TABLE t (v)", nil); err != nil { + t.Fatal("Failed to create table:", err) + } + + // An unsupported Go type must report an explicit error instead of + // silently binding NULL: positional parameter. + _, err = c.Exec("INSERT INTO t VALUES (?)", []driver.Value{int32(1)}) + if err == nil || !strings.Contains(err.Error(), "unsupported bind type int32") { + t.Errorf("positional bind of unsupported type: got %v, want unsupported bind type error", err) + } + + // The same for a named parameter. + stmt, err := c.Prepare("INSERT INTO t VALUES (:x)") + if err != nil { + t.Fatal("Failed to prepare:", err) + } + err = stmt.(*SQLiteStmt).bind([]driver.NamedValue{{Name: "x", Ordinal: 1, Value: int32(1)}}) + if err == nil || !strings.Contains(err.Error(), "unsupported bind type int32") { + t.Errorf("named bind of unsupported type: got %v, want unsupported bind type error", err) + } + stmt.Close() + + // A genuine SQLite bind failure must preserve the recorded error. + stmt, err = c.Prepare("INSERT INTO t VALUES (?)") + if err != nil { + t.Fatal("Failed to prepare:", err) + } + err = stmt.(*SQLiteStmt).bind([]driver.NamedValue{{Ordinal: 2, Value: int64(1)}}) + var serr Error + if !errors.As(err, &serr) || serr.Code != ErrRange { + t.Errorf("out-of-range bind: got %v, want SQLITE_RANGE error", err) + } + stmt.Close() +} + func TestFunctionRegistration(t *testing.T) { addi8_16_32 := func(a int8, b int16) int32 { return int32(a) + int32(b) } addi64 := func(a, b int64) int64 { return a + b }