Skip to content

Commit d055d0f

Browse files
author
Vincent Landgraf
committed
implement prepared stmt recovery mechanism
1 parent a80c27b commit d055d0f

File tree

2 files changed

+42
-2
lines changed

2 files changed

+42
-2
lines changed

sqlite3.go

+11-1
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ type SQLiteStmt struct {
111111
t string
112112
closed bool
113113
cls bool
114+
query string
114115
}
115116

116117
// Result struct.
@@ -328,7 +329,7 @@ func (c *SQLiteConn) Prepare(query string) (driver.Stmt, error) {
328329
if tail != nil && C.strlen(tail) > 0 {
329330
t = strings.TrimSpace(C.GoString(tail))
330331
}
331-
return &SQLiteStmt{c: c, s: s, t: t}, nil
332+
return &SQLiteStmt{c: c, s: s, t: t, query: query}, nil
332333
}
333334

334335
// Close the statement.
@@ -401,6 +402,7 @@ func (s *SQLiteStmt) bind(args []driver.Value) error {
401402
// Query the statement with arguments. Return records.
402403
func (s *SQLiteStmt) Query(args []driver.Value) (driver.Rows, error) {
403404
if err := s.bind(args); err != nil {
405+
defer s.reprepare()
404406
return nil, err
405407
}
406408
return &SQLiteRows{s, int(C.sqlite3_column_count(s.s)), nil, nil, s.cls}, nil
@@ -416,13 +418,21 @@ func (r *SQLiteResult) RowsAffected() (int64, error) {
416418
return r.changes, nil
417419
}
418420

421+
// Reprepares the statement in case of errors
422+
func (s *SQLiteStmt) reprepare() {
423+
stmt, _ := s.c.Prepare(s.query)
424+
s.s = stmt.(*SQLiteStmt).s
425+
}
426+
419427
// Execute the statement with arguments. Return result object.
420428
func (s *SQLiteStmt) Exec(args []driver.Value) (driver.Result, error) {
421429
if err := s.bind(args); err != nil {
430+
defer s.reprepare()
422431
return nil, err
423432
}
424433
rv := C.sqlite3_step(s.s)
425434
if rv != C.SQLITE_ROW && rv != C.SQLITE_OK && rv != C.SQLITE_DONE {
435+
defer s.reprepare()
426436
return nil, s.c.lastError()
427437
}
428438

sqlite3_test/sqltest.go

+31-1
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,14 @@ type DB struct {
2929
var db *DB
3030

3131
// the following tables will be created and dropped during the test
32-
var testTables = []string{"foo", "bar", "t", "bench"}
32+
var testTables = []string{"foo", "bar", "t", "bench", "users"}
3333

3434
var tests = []testing.InternalTest{
3535
{"TestBlobs", TestBlobs},
3636
{"TestManyQueryRow", TestManyQueryRow},
3737
{"TestTxQuery", TestTxQuery},
3838
{"TestPreparedStmt", TestPreparedStmt},
39+
{"TestPreparedStmtRecovery", TestPreparedStmtRecovery},
3940
}
4041

4142
var benchmarks = []testing.InternalBenchmark{
@@ -299,6 +300,35 @@ func TestPreparedStmt(t *testing.T) {
299300
}
300301
}
301302

303+
func TestPreparedStmtRecovery(t *testing.T) {
304+
db.tearDown()
305+
db.Exec(`create table if not exists users (
306+
uid integer primary key autoincrement,
307+
name text not null,
308+
constraint uniqname unique (name) on conflict rollback
309+
);`)
310+
311+
stmt, err := db.Prepare(`insert into users (name) values (?);`)
312+
if err != nil {
313+
t.Fatalf("recovery 1: %v", err)
314+
}
315+
316+
_, e1 := stmt.Exec("test")
317+
if e1 != nil {
318+
t.Fatalf("recovery 2: %v", e1)
319+
}
320+
321+
_, e2 := stmt.Exec("test")
322+
if e2 == nil {
323+
t.Fatalf("recovery 3: %v", e2)
324+
}
325+
326+
_, e3 := stmt.Exec("test1")
327+
if e3 != nil {
328+
t.Fatalf("recovery 4: %v", e3)
329+
}
330+
}
331+
302332
// Benchmarks need to use panic() since b.Error errors are lost when
303333
// running via testing.Benchmark() I would like to run these via go
304334
// test -bench but calling Benchmark() from a benchmark test

0 commit comments

Comments
 (0)