1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
| | From c59db66f9ac754b40463c6788ab9bad4f045cc92 Mon Sep 17 00:00:00 2001
From: Caleb Ristvedt <caleb.ristvedt@cune.org>
Date: Sun, 7 Jun 2020 18:27:44 -0500
Subject: [PATCH] Reset statement when sqlite-finalize is called on cached
statement
Automatically-started transactions only end when a statement finishes, which is
normally either when sqlite-reset or sqlite-finalize is called for that
statement. Consequently, transactions automatically started by cached
statements won't end until the statement is next reused by sqlite-prepare or
sqlite-reset is called on it. This changes sqlite-finalize so that it preserves
the statement-finishing (and thus transaction-finishing) behavior of
sqlite_finalize.
---
sqlite3.scm.in | 43 ++++++++++++++++++++++++++++++++++---------
1 file changed, 34 insertions(+), 9 deletions(-)
diff --git a/sqlite3.scm.in b/sqlite3.scm.in
index 77b5032..19241dc 100644
--- a/sqlite3.scm.in
+++ b/sqlite3.scm.in
@@ -274,15 +274,40 @@ statements, into DB. The result is unspecified."
(dynamic-func "sqlite3_finalize" libsqlite3)
(list '*))))
(lambda (stmt)
- ;; Note: When STMT is cached, this is a no-op. This ensures caching
- ;; actually works while still separating concerns: users can turn
- ;; caching on and off without having to change the rest of their code.
- (when (and (stmt-live? stmt)
- (not (stmt-cached? stmt)))
- (let ((p (stmt-pointer stmt)))
- (sqlite-remove-statement! (stmt->db stmt) stmt)
- (set-stmt-live?! stmt #f)
- (f p))))))
+ ;; Note: When STMT is cached, this merely resets. This ensures caching
+ ;; actually works while still separating concerns: users can turn caching
+ ;; on and off without having to change the rest of their code.
+ (if (and (stmt-live? stmt)
+ (not (stmt-cached? stmt)))
+ (let ((p (stmt-pointer stmt)))
+ (sqlite-remove-statement! (stmt->db stmt) stmt)
+ (set-stmt-live?! stmt #f)
+ (f p))
+ ;; It's necessary to reset cached statements due to the following:
+ ;;
+ ;; "An implicit transaction (a transaction that is started
+ ;; automatically, not a transaction started by BEGIN) is committed
+ ;; automatically when the last active statement finishes. A statement
+ ;; finishes when its last cursor closes, which is guaranteed to happen
+ ;; when the prepared statement is reset or finalized. Some statements
+ ;; might "finish" for the purpose of transaction control prior to
+ ;; being reset or finalized, but there is no guarantee of this."
+ ;;
+ ;; (see https://www.sqlite.org/lang_transaction.html)
+ ;;
+ ;; Thus, it's possible for an implicitly-started transaction to hang
+ ;; around until sqlite-reset is called when the cached statement is
+ ;; next used. Because the transaction is committed automatically only
+ ;; when the *last active statement* finishes, the implicitly-started
+ ;; transaction may later be upgraded to a write transaction (!) and
+ ;; this non-reset statement will still be keeping the transaction from
+ ;; committing until it is next used or the database connection is
+ ;; closed. This has the potential to make (exclusive) write access to
+ ;; the database necessary for much longer than it should be.
+ ;;
+ ;; So it's necessary to preserve the statement-finishing behavior of
+ ;; sqlite_finalize here, which we do by calling sqlite-reset.
+ (sqlite-reset stmt)))))
(define *stmt-map* (make-weak-key-hash-table))
(define (stmt->db stmt)
--
2.26.2
|