Skip to content

Commit ea6d8e0

Browse files
committed
Merge branch 'development'
2 parents 287c4be + 073209f commit ea6d8e0

File tree

9 files changed

+83
-3
lines changed

9 files changed

+83
-3
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ GRDB adheres to [Semantic Versioning](https://semver.org/), with one exception:
77

88
#### 7.x Releases
99

10+
- `7.1.x` Releases - [7.1.0](#710)
1011
- `7.0.x` Releases - [7.0.0](#700)
1112
- `7.0.0` Betas - [7.0.0-beta](#700-beta) - [7.0.0-beta.2](#700-beta2) - [7.0.0-beta.3](#700-beta3) - [7.0.0-beta.4](#700-beta4) - [7.0.0-beta.5](#700-beta5) - [7.0.0-beta.6](#700-beta6) - [7.0.0-beta.7](#700-beta7)
1213

@@ -132,6 +133,14 @@ GRDB adheres to [Semantic Versioning](https://semver.org/), with one exception:
132133

133134
---
134135

136+
## 7.1.0
137+
138+
Released February 5, 2025
139+
140+
- **New**: Expose `sqlite3_libversion_number` as `Database.sqliteLibVersionNumber` by [@groue](https://github.com/groue) in [#1713](https://github.com/groue/GRDB.swift/pull/1713)
141+
- **Fixed**: DatabaseQueue restores its read/write abilities when an async read-only database access is cancelled by [@groue](https://github.com/groue) in [#1716](https://github.com/groue/GRDB.swift/pull/1716)
142+
- **New**: Allow RxGRDB to compile without warning by [@groue](https://github.com/groue) in [#1717](https://github.com/groue/GRDB.swift/pull/1717)
143+
135144
## 7.0.0
136145

137146
Released January 26, 2025

GRDB.swift.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = 'GRDB.swift'
3-
s.version = '7.0.0'
3+
s.version = '7.1.0'
44

55
s.license = { :type => 'MIT', :file => 'LICENSE' }
66
s.summary = 'A toolkit for SQLite databases, with a focus on application development.'

GRDB/Core/Database.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1288,6 +1288,12 @@ public final class Database: CustomStringConvertible, CustomDebugStringConvertib
12881288
return
12891289
}
12901290

1291+
// Suspension should not prevent adjusting the read-only mode.
1292+
// See <https://github.com/groue/GRDB.swift/issues/1715>.
1293+
if statement.isQueryOnlyPragma {
1294+
return
1295+
}
1296+
12911297
// How should we interrupt the statement?
12921298
enum Interrupt {
12931299
case abort // Rollback and throw SQLITE_ABORT

GRDB/Core/DatabaseRegionObservation.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ extension DatabaseRegionObservation {
8383
/// - parameter onChange: The closure to execute when a transaction has
8484
/// modified the observed region.
8585
/// - returns: A DatabaseCancellable that can stop the observation.
86+
@preconcurrency // For RxGRDB, see <https://github.com/RxSwiftCommunity/RxGRDB/issues/72#issuecomment-2631125658>
8687
public func start(
8788
in writer: any DatabaseWriter,
8889
onError: @escaping @Sendable (Error) -> Void,

GRDB/Core/Statement.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ public final class Statement {
9292
/// The effects on the database (reported by `sqlite3_set_authorizer`).
9393
private(set) var authorizerEventKinds: [DatabaseEventKind] = []
9494

95+
/// If true, the statement executes is a `PRAGMA QUERY_ONLY` statement.
96+
private(set) var isQueryOnlyPragma = false
97+
9598
/// A boolean value indicating if the prepared statement makes no direct
9699
/// changes to the content of the database file.
97100
///
@@ -160,6 +163,7 @@ public final class Statement {
160163
self.invalidatesDatabaseSchemaCache = authorizer.invalidatesDatabaseSchemaCache
161164
self.transactionEffect = authorizer.transactionEffect
162165
self.authorizerEventKinds = authorizer.databaseEventKinds
166+
self.isQueryOnlyPragma = authorizer.isQueryOnlyPragma
163167
}
164168

165169
deinit {

GRDB/Core/StatementAuthorizer.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ final class StatementAuthorizer {
4141
/// savepoint statement.
4242
var transactionEffect: Statement.TransactionEffect?
4343

44+
/// If true, the statement executes is a `PRAGMA QUERY_ONLY` statement.
45+
var isQueryOnlyPragma = false
46+
4447
private var isDropStatement = false
4548

4649
init(_ database: Database) {
@@ -67,6 +70,7 @@ final class StatementAuthorizer {
6770
databaseEventKinds = []
6871
invalidatesDatabaseSchemaCache = false
6972
transactionEffect = nil
73+
isQueryOnlyPragma = false
7074
isDropStatement = false
7175
}
7276

@@ -192,6 +196,11 @@ final class StatementAuthorizer {
192196
}
193197
return SQLITE_OK
194198

199+
case SQLITE_PRAGMA:
200+
if let cString1 {
201+
isQueryOnlyPragma = sqlite3_stricmp(cString1, "query_only") == 0
202+
}
203+
return SQLITE_OK
195204
default:
196205
return SQLITE_OK
197206
}

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<a href="https://github.com/groue/GRDB.swift/actions/workflows/CI.yml"><img alt="CI Status" src="https://github.com/groue/GRDB.swift/actions/workflows/CI.yml/badge.svg?branch=master"></a>
1616
</p>
1717

18-
**Latest release**: January 26, 2025 • [version 7.0.0](https://github.com/groue/GRDB.swift/tree/v7.0.0) • [CHANGELOG](CHANGELOG.md) • [Migrating From GRDB 6 to GRDB 7](Documentation/GRDB7MigrationGuide.md)
18+
**Latest release**: February 5, 2025 • [version 7.1.0](https://github.com/groue/GRDB.swift/tree/v7.1.0) • [CHANGELOG](CHANGELOG.md) • [Migrating From GRDB 6 to GRDB 7](Documentation/GRDB7MigrationGuide.md)
1919

2020
**Requirements**: iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 7.0+ &bull; SQLite 3.20.0+ &bull; Swift 6+ / Xcode 16+
2121

Support/Info.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<key>CFBundlePackageType</key>
1616
<string>FMWK</string>
1717
<key>CFBundleShortVersionString</key>
18-
<string>7.0.0</string>
18+
<string>7.1.0</string>
1919
<key>CFBundleSignature</key>
2020
<string>????</string>
2121
<key>CFBundleVersion</key>

Tests/GRDBTests/DatabaseWriterTests.swift

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,57 @@ class DatabaseWriterTests : GRDBTestCase {
415415

416416
// MARK: - Task Cancellation
417417

418+
// Regression test for <https://github.com/groue/GRDB.swift/issues/1715>.
419+
func test_write_is_possible_after_read_cancelled_after_database_access() async throws {
420+
// When a read access is cancelled, DatabaseQueue needs to execute
421+
// `PRAGMA query_only=0` in order to restore the read/write access.
422+
//
423+
// Here we test that this pragma can run from a cancelled read.
424+
//
425+
// Small difficulty: some SQLite versions (seen with 3.43.2) execute
426+
// the `query_only` pragma at compile time, not only at execution
427+
// time (yeah, that's an SQLite bug). The problem of this bug is
428+
// that even if the `PRAGMA query_only=0` is not executed due to
429+
// Task cancellation, its side effect is still executed when it is
430+
// compiled, unintentionally. A cancelled `PRAGMA query_only=0`
431+
// still works!
432+
//
433+
// To avoid this SQLite bug from messing with our test, we perform
434+
// two reads: one that compiles and cache `PRAGMA query_only`
435+
// statements, and a second read that we cancel. This time the
436+
// `PRAGMA query_only=0` triggers its side effect if and only if it
437+
// is actually executed (the behavior we are testing).
438+
func test(_ dbWriter: some DatabaseWriter) async throws {
439+
let semaphore = AsyncSemaphore(value: 0)
440+
let cancelledTaskMutex = Mutex<Task<Void, any Error>?>(nil)
441+
let task = Task {
442+
await semaphore.wait()
443+
444+
// First read, not cancelled, so that all `query_only`
445+
// pragma statements are compiled (see above).
446+
try await dbWriter.read { db in }
447+
448+
// Second read, cancelled.
449+
try await dbWriter.read { db in
450+
try XCTUnwrap(cancelledTaskMutex.load()).cancel()
451+
}
452+
}
453+
cancelledTaskMutex.store(task)
454+
semaphore.signal()
455+
// Wait until reads are completed
456+
try? await task.value
457+
458+
// Write access is restored after read cancellation (no error is thrown)
459+
try await dbWriter.write { db in
460+
try db.execute(sql: "CREATE TABLE test(a)")
461+
}
462+
}
463+
464+
try await test(makeDatabaseQueue())
465+
try await test(makeDatabasePool())
466+
try await test(AnyDatabaseWriter(makeDatabaseQueue()))
467+
}
468+
418469
func test_writeWithoutTransaction_is_cancelled_by_Task_cancellation_performed_before_database_access() async throws {
419470
func test(_ dbWriter: some DatabaseWriter) async throws {
420471
let semaphore = AsyncSemaphore(value: 0)

0 commit comments

Comments
 (0)