Skip to content

Commit c94aca2

Browse files
authored
Merge pull request #1145 from museapphq/feature/reset-statement
Adding public method to reset a prepared statement
2 parents dd3e813 + d970a11 commit c94aca2

File tree

3 files changed

+52
-2
lines changed

3 files changed

+52
-2
lines changed

Documentation/Index.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -828,7 +828,7 @@ let query = try db.prepare(users)
828828
for user in query {
829829
// 💥 can throw an error here
830830
}
831-
````
831+
```
832832

833833
#### Failable iteration
834834

@@ -1872,6 +1872,14 @@ let stmt = try db.prepare("SELECT * FROM attachments WHERE typeConformsTo(UTI, ?
18721872
for row in stmt.bind(kUTTypeImage) { /* ... */ }
18731873
```
18741874

1875+
> _Note:_ Prepared queries can be reused, and long lived prepared queries should be `reset()` after each use. Otherwise, the transaction (either [implicit or explicit](https://www.sqlite.org/lang_transaction.html#implicit_versus_explicit_transactions)) will be held open until the query is reset or finalized. This can affect performance. Statements are reset automatically during `deinit`.
1876+
>
1877+
> ```swift
1878+
> someObj.statement = try db.prepare("SELECT * FROM attachments WHERE typeConformsTo(UTI, ?)")
1879+
> for row in someObj.statement.bind(kUTTypeImage) { /* ... */ }
1880+
> someObj.statement.reset()
1881+
> ```
1882+
18751883
[UTTypeConformsTo]: https://developer.apple.com/documentation/coreservices/1444079-uttypeconformsto
18761884

18771885
## Custom Aggregations

Sources/SQLite/Core/Statement.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,11 @@ public final class Statement {
185185
try connection.sync { try connection.check(sqlite3_step(handle)) == SQLITE_ROW }
186186
}
187187

188-
fileprivate func reset(clearBindings shouldClear: Bool = true) {
188+
public func reset() {
189+
reset(clearBindings: true)
190+
}
191+
192+
fileprivate func reset(clearBindings shouldClear: Bool) {
189193
sqlite3_reset(handle)
190194
if shouldClear { sqlite3_clear_bindings(handle) }
191195
}

Tests/SQLiteTests/StatementTests.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
import XCTest
22
import SQLite
33

4+
#if SQLITE_SWIFT_STANDALONE
5+
import sqlite3
6+
#elseif SQLITE_SWIFT_SQLCIPHER
7+
import SQLCipher
8+
#elseif os(Linux)
9+
import CSQLite
10+
#else
11+
import SQLite3
12+
#endif
13+
414
class StatementTests: SQLiteTestCase {
515
override func setUpWithError() throws {
616
try super.setUpWithError()
@@ -34,4 +44,32 @@ class StatementTests: SQLiteTestCase {
3444

3545
XCTAssertEqual(names.map({ "\($0)@example.com" }), emails.sorted())
3646
}
47+
48+
/// Check that a statement reset will close the implicit transaction, allowing wal file to checkpoint
49+
func test_reset_statement() throws {
50+
// insert single row
51+
try insertUsers("bob")
52+
53+
// prepare a statement and read a single row. This will increment the cursor which
54+
// prevents the implicit transaction from closing.
55+
// https://www.sqlite.org/lang_transaction.html#implicit_versus_explicit_transactions
56+
let statement = try db.prepare("SELECT email FROM users")
57+
_ = try statement.step()
58+
59+
// verify implicit transaction is not closed, and the users table is still locked
60+
XCTAssertThrowsError(try db.run("DROP TABLE users")) { error in
61+
if case let Result.error(_, code, _) = error {
62+
XCTAssertEqual(code, SQLITE_LOCKED)
63+
} else {
64+
XCTFail("unexpected error")
65+
}
66+
}
67+
68+
// reset the prepared statement, unlocking the table and allowing the implicit transaction to close
69+
statement.reset()
70+
71+
// truncate succeeds
72+
try db.run("DROP TABLE users")
73+
}
74+
3775
}

0 commit comments

Comments
 (0)