From 3bce691e38b77726b86671170492b3c85c61b3af Mon Sep 17 00:00:00 2001 From: Alberto Carretero Date: Mon, 23 Oct 2023 15:28:47 +0200 Subject: [PATCH] bugfix: after executing statement table lock was not cleared When we execute a statement, we step once and return. If the query returned more than one row, we would not have consumed everything so the underlying table would still be locked even when the execution had finished. This change resets the statement after execution so that the locks can be cleared. --- sqlite3.go | 6 ++++++ sqlite3_test.go | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/sqlite3.go b/sqlite3.go index 5e4e2ff5..86387c24 100644 --- a/sqlite3.go +++ b/sqlite3.go @@ -2083,6 +2083,12 @@ func (s *SQLiteStmt) execSync(args []driver.NamedValue) (driver.Result, error) { return nil, err } + rv = C.sqlite3_reset(s.s) + if rv != C.SQLITE_OK { + err := s.c.lastError() + C.sqlite3_clear_bindings(s.s) + return nil, err + } return &SQLiteResult{id: int64(rowid), changes: int64(changes)}, nil } diff --git a/sqlite3_test.go b/sqlite3_test.go index 326361ec..30d61564 100644 --- a/sqlite3_test.go +++ b/sqlite3_test.go @@ -2101,6 +2101,7 @@ var tests = []testing.InternalTest{ {Name: "TestManyQueryRow", F: testManyQueryRow}, {Name: "TestTxQuery", F: testTxQuery}, {Name: "TestPreparedStmt", F: testPreparedStmt}, + {Name: "TestExecStmtDoesNotBlockTable", F: testExecStmtDoesNotBlockTable}, {Name: "TestExecEmptyQuery", F: testExecEmptyQuery}, } @@ -2432,6 +2433,40 @@ func testPreparedStmt(t *testing.T) { wg.Wait() } +// testExecStmtDoesNotBlockTable tests that the tables' locks are cleared after +// executing a statement. +func testExecStmtDoesNotBlockTable(t *testing.T) { + db.tearDown() + db.mustExec("CREATE TABLE t (count INT)") + sel, err := db.Prepare("SELECT count FROM t") + if err != nil { + t.Fatalf("prepare 1: %v", err) + } + ins, err := db.Prepare(db.q("INSERT INTO t (count) VALUES (?)")) + if err != nil { + t.Fatalf("prepare 2: %v", err) + } + drop, err := db.Prepare(db.q("DROP TABLE t")) + if err != nil { + t.Fatalf("prepare 3: %v", err) + } + + for n := 1; n <= 3; n++ { + if _, err := ins.Exec(n); err != nil { + t.Fatalf("insert(%d) = %v", n, err) + } + } + + _, err = sel.Exec() + if err != nil { + t.Fatalf("exec 1: %v", err) + } + _, err = drop.Exec() + if err != nil { + t.Fatalf("exec 2: %v", err) + } +} + // testEmptyQuery is test for validating the API in case of empty query func testExecEmptyQuery(t *testing.T) { db.tearDown()