Skip to content

Commit fb68849

Browse files
committed
Merge branch 'development'
2 parents a5a1be2 + c2bddde commit fb68849

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1198
-797
lines changed

CHANGELOG.md

Lines changed: 11 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.6.x` Releases - [7.6.0](#760)
1011
- `7.5.x` Releases - [7.5.0](#750)
1112
- `7.4.x` Releases - [7.4.0](#740) - [7.4.1](#741)
1213
- `7.3.x` Releases - [7.3.0](#730)
@@ -137,6 +138,16 @@ GRDB adheres to [Semantic Versioning](https://semver.org/), with one exception:
137138

138139
---
139140

141+
## 7.6.0
142+
143+
Released July 20, 2025
144+
145+
- **New**: All closures that accept DatabaseComponents can throw by [@groue](https://github.com/groue) in [#1768](https://github.com/groue/GRDB.swift/pull/1768)
146+
- **New**: Add Sendable conformance to DatabasePreUpdateEvent.Kind, matching DatabaseEvent.Kind by [@Jason-Abbott](https://github.com/Jason-Abbott) in [#1773](https://github.com/groue/GRDB.swift/pull/1773)
147+
- **New**: Add custom build instruction for hardened runtime by [@Jason-Abbott](https://github.com/Jason-Abbott) in [#1776](https://github.com/groue/GRDB.swift/pull/1776)
148+
- **New**: Access task locals from asynchronous database accesses by [@groue](https://github.com/groue) in [#1794](https://github.com/groue/GRDB.swift/pull/1794)
149+
- **New**: Throwing row accessors by [@groue](https://github.com/groue) in [#1796](https://github.com/groue/GRDB.swift/pull/1796)
150+
140151
## 7.5.0
141152

142153
Released May 11, 2025

Documentation/AssociationsBasics.md

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2186,26 +2186,25 @@ struct BookInfo: FetchableRecord {
21862186

21872187
init(row: Row) throws {
21882188
book = try Book(row: row)
2189-
author = row["author"]
2190-
country = row["country"]
2191-
coverImage = row["coverImage"]
2189+
author = try row.decode(forKey: "author")
2190+
country = try row.decodeIfPresent(forKey: "country")
2191+
coverImage = try row.decodeIfPresent(forKey: "coverImage")
21922192
}
21932193
}
21942194

21952195
let bookInfos: [BookInfo] = try BookInfo.fetchAll(db, request)
21962196
```
21972197

2198-
You are already familiar with row subscripts to decode [database values](../README.md#column-values):
2198+
When you extract a record from a row, GRDB looks up the tree of **association keys**:
21992199

22002200
```swift
2201-
let name: String = row["name"]
2201+
let author = try row.decode(Author.self, forKey: "author")
22022202
```
22032203

2204-
When you extract a record instead of a value from a row, GRDB looks up the tree of **association keys**. If the key is not found, or only associated with columns that all contain NULL values, an optional record is decoded as nil:
2204+
If the key is not found, or only associated with columns that are all NULL, an optional record is decoded as nil:
22052205

22062206
```swift
2207-
let author: Author = row["author"]
2208-
let country: Country? = row["country"]
2207+
let country = try row.decodeIfPresent(Country.self, forKey: "country")
22092208
```
22102209

22112210
You can also perform custom navigation in the tree by using *row scopes*. See [Row Adapters] for more information.
@@ -2221,7 +2220,7 @@ struct AuthorInfo: FetchableRecord {
22212220

22222221
init(row: Row) throws {
22232222
author = try Author(row: row)
2224-
books = row["books"]
2223+
books = try row.decode(forKey: "books")
22252224
}
22262225
}
22272226

Documentation/CustomSQLiteBuilds.md

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,47 +12,47 @@ GRDB builds SQLite with [swiftlyfalling/SQLiteLib](https://github.com/swiftlyfal
1212
**To install GRDB with a custom SQLite build:**
1313

1414
1. Clone the GRDB git repository, checkout the latest tagged version:
15-
15+
1616
```sh
1717
cd [GRDB directory]
1818
git checkout [latest tag]
1919
git submodule update --init SQLiteCustom/src
2020
```
21-
21+
2222
2. Choose your [extra compilation options](https://www.sqlite.org/compile.html). For example, `SQLITE_ENABLE_FTS5`, `SQLITE_ENABLE_PREUPDATE_HOOK`.
23-
23+
2424
It is recommended that you enable the `SQLITE_ENABLE_SNAPSHOT` option. It allows GRDB to optimize [ValueObservation](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/valueobservation) when you use a [Database Pool](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/databasepool).
2525

2626
3. Create a folder named `GRDBCustomSQLite` somewhere in your project directory.
2727

2828
4. Create four files in the `GRDBCustomSQLite` folder:
2929

3030
- `SQLiteLib-USER.xcconfig`: this file sets the extra SQLite compilation flags.
31-
31+
3232
```xcconfig
3333
// As many -D options as there are custom SQLite compilation options
3434
// Note: there is no space between -D and the option name.
3535
CUSTOM_SQLLIBRARY_CFLAGS = -DSQLITE_ENABLE_SNAPSHOT -DSQLITE_ENABLE_FTS5
3636
```
37-
37+
3838
- `GRDBCustomSQLite-USER.xcconfig`: this file lets GRDB know about extra compilation flags, and enables extra GRDB APIs.
39-
39+
4040
```xcconfig
4141
// As many -D options as there are custom SQLite compilation options
4242
// Note: there is one space between -D and the option name.
4343
CUSTOM_OTHER_SWIFT_FLAGS = -D SQLITE_ENABLE_SNAPSHOT -D SQLITE_ENABLE_FTS5
4444
```
45-
45+
4646
- `GRDBCustomSQLite-USER.h`: this file lets your application know about extra compilation flags.
47-
47+
4848
```c
4949
// As many #define as there are custom SQLite compilation options
5050
#define SQLITE_ENABLE_SNAPSHOT
5151
#define SQLITE_ENABLE_FTS5
5252
```
53-
53+
5454
- `GRDBCustomSQLite-INSTALL.sh`: this file installs the three other files.
55-
55+
5656
```sh
5757
# License: MIT License
5858
# https://github.com/swiftlyfalling/SQLiteLib/blob/master/LICENSE
@@ -61,52 +61,52 @@ GRDB builds SQLite with [swiftlyfalling/SQLiteLib](https://github.com/swiftlyfal
6161
# PROJECT PATHS
6262
# !! MODIFY THESE TO MATCH YOUR PROJECT HIERARCHY !!
6363
#######################################################
64-
64+
6565
# The path to the folder containing GRDBCustom.xcodeproj:
6666
GRDB_SOURCE_PATH="${PROJECT_DIR}/GRDB"
67-
67+
6868
# The path to your custom "SQLiteLib-USER.xcconfig":
6969
SQLITELIB_XCCONFIG_USER_PATH="${PROJECT_DIR}/GRDBCustomSQLite/SQLiteLib-USER.xcconfig"
70-
70+
7171
# The path to your custom "GRDBCustomSQLite-USER.xcconfig":
7272
CUSTOMSQLITE_XCCONFIG_USER_PATH="${PROJECT_DIR}/GRDBCustomSQLite/GRDBCustomSQLite-USER.xcconfig"
73-
73+
7474
# The path to your custom "GRDBCustomSQLite-USER.h":
7575
CUSTOMSQLITE_H_USER_PATH="${PROJECT_DIR}/GRDBCustomSQLite/GRDBCustomSQLite-USER.h"
76-
76+
7777
#######################################################
7878
#
7979
#######################################################
80-
81-
80+
81+
8282
if [ ! -d "$GRDB_SOURCE_PATH" ];
8383
then
8484
echo "error: Path to GRDB source (GRDB_SOURCE_PATH) missing/incorrect: $GRDB_SOURCE_PATH"
8585
exit 1
8686
fi
87-
87+
8888
SyncFileChanges () {
8989
SOURCE=$1
9090
DESTINATIONPATH=$2
9191
DESTINATIONFILENAME=$3
9292
DESTINATION="${DESTINATIONPATH}/${DESTINATIONFILENAME}"
93-
93+
9494
if [ ! -f "$SOURCE" ];
9595
then
9696
echo "error: Source file missing: $SOURCE"
9797
exit 1
9898
fi
99-
99+
100100
rsync -a "$SOURCE" "$DESTINATION"
101101
}
102-
102+
103103
SyncFileChanges $SQLITELIB_XCCONFIG_USER_PATH "${GRDB_SOURCE_PATH}/SQLiteCustom/src" "SQLiteLib-USER.xcconfig"
104104
SyncFileChanges $CUSTOMSQLITE_XCCONFIG_USER_PATH "${GRDB_SOURCE_PATH}/SQLiteCustom" "GRDBCustomSQLite-USER.xcconfig"
105105
SyncFileChanges $CUSTOMSQLITE_H_USER_PATH "${GRDB_SOURCE_PATH}/SQLiteCustom" "GRDBCustomSQLite-USER.h"
106-
106+
107107
echo "Finished syncing"
108108
```
109-
109+
110110
Modify the top of `GRDBCustomSQLite-INSTALL.sh` file so that it contains correct paths.
111111

112112
5. Embed the `GRDBCustom.xcodeproj` project in your own project.
@@ -116,17 +116,21 @@ GRDB builds SQLite with [swiftlyfalling/SQLiteLib](https://github.com/swiftlyfal
116116
7. Add the `GRDBCustom.framework` from the targeted platform to the **Embedded Binaries** section of the **General** tab of your **application target**.
117117

118118
8. Add a Run Script phase for your target in the **Pre-actions** section of the **Build** tab of your **application scheme**:
119-
119+
120120
```sh
121121
source "${PROJECT_DIR}/GRDBCustomSQLite/GRDBCustomSQLite-INSTALL.sh"
122122
```
123-
123+
124124
The path should be the path to your `GRDBCustomSQLite-INSTALL.sh` file.
125-
125+
126126
Select your application target in the "Provide build settings from" menu.
127127

128128
9. Check the "Shared" checkbox of your application scheme (this lets you commit the pre-action in your Version Control System).
129129

130+
10. If you have enabled "Hardened Runtime" for your target (**Build Settings**/**Signing**) then you may need to check **Disable Library Validation** under the **Hardened Runtime** section of the **Signing & Capabilities** tab.
131+
132+
(The build error without this exception is "Library not loaded ... different Team IDs")
133+
130134
Now you can use GRDB with your custom SQLite build:
131135

132136
```swift

Documentation/DemoApps/GRDBDemo/GRDBDemo/Database/AppDatabase.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import os.log
1212
/// let dbQueue = try DatabaseQueue(configuration: config)
1313
/// let appDatabase = try AppDatabase(dbQueue)
1414
/// ```
15-
final class AppDatabase: Sendable {
15+
struct AppDatabase: Sendable {
1616
/// Access to the database.
1717
///
1818
/// See <https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/databaseconnections>
@@ -130,7 +130,7 @@ extension AppDatabase {
130130

131131
/// Refresh all players (by performing some random changes, for demo purpose).
132132
func refreshPlayers() async throws {
133-
try await dbWriter.write { [self] db in
133+
try await dbWriter.write { db in
134134
if try Player.all().isEmpty(db) {
135135
// When database is empty, insert new random players
136136
try createRandomPlayers(db)

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.5.0'
3+
s.version = '7.6.0'
44

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

GRDB.xcodeproj/project.pbxproj

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@
273273
56A5EF0F1EF7F20B00F03071 /* ForeignKeyInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A5EF0E1EF7F20B00F03071 /* ForeignKeyInfoTests.swift */; };
274274
56A8C2301D1914540096E9D4 /* UUID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A8C22F1D1914540096E9D4 /* UUID.swift */; };
275275
56AACAA822ACED7100A40F2A /* Fetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AACAA722ACED7100A40F2A /* Fetch.swift */; };
276+
56AC93DF2E291C4700DB6C74 /* DispatchQueueActor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AC93DE2E291C4700DB6C74 /* DispatchQueueActor.swift */; };
276277
56AE64122229A53700AD1B0B /* HasOneThroughAssociation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AE64112229A53700AD1B0B /* HasOneThroughAssociation.swift */; };
277278
56AE6424222AAC9500AD1B0B /* AssociationHasOneThroughSQLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AE6423222AAC9500AD1B0B /* AssociationHasOneThroughSQLTests.swift */; };
278279
56AFEF2F29969F6E00CA1E51 /* TransactionClock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AFEF2E29969F6E00CA1E51 /* TransactionClock.swift */; };
@@ -769,6 +770,7 @@
769770
56A8C22F1D1914540096E9D4 /* UUID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UUID.swift; sourceTree = "<group>"; };
770771
56A8C2361D1914790096E9D4 /* FoundationNSUUIDTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationNSUUIDTests.swift; sourceTree = "<group>"; };
771772
56AACAA722ACED7100A40F2A /* Fetch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fetch.swift; sourceTree = "<group>"; };
773+
56AC93DE2E291C4700DB6C74 /* DispatchQueueActor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DispatchQueueActor.swift; sourceTree = "<group>"; };
772774
56AE64112229A53700AD1B0B /* HasOneThroughAssociation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HasOneThroughAssociation.swift; sourceTree = "<group>"; };
773775
56AE6423222AAC9500AD1B0B /* AssociationHasOneThroughSQLTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssociationHasOneThroughSQLTests.swift; sourceTree = "<group>"; };
774776
56AF746A1D41FB9C005E9FF3 /* DatabaseValueConvertibleEscapingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseValueConvertibleEscapingTests.swift; sourceTree = "<group>"; };
@@ -1595,13 +1597,13 @@
15951597
56A238751B9C75030082EB20 /* DatabaseValue.swift */,
15961598
560D923E1C672C3E00F4F92B /* DatabaseValueConvertible.swift */,
15971599
563363C31C942C37000BE133 /* DatabaseWriter.swift */,
1600+
56AC93DE2E291C4700DB6C74 /* DispatchQueueActor.swift */,
15981601
5636E9BB1D22574100B9B05F /* FetchRequest.swift */,
15991602
56A238761B9C75030082EB20 /* Row.swift */,
16001603
567404871CEF84C8003ED5CC /* RowAdapter.swift */,
16011604
566B9C1F25C6CC24004542CF /* RowDecodingError.swift */,
16021605
56BB6EA81D3009B100A1CA52 /* SchedulingWatchdog.swift */,
16031606
560A37A61C8FF6E500949E71 /* SerializedDatabase.swift */,
1604-
56D3331F29C38D6700430680 /* WALSnapshotTransaction.swift */,
16051607
56E9FAD7221053DC00C703A8 /* SQL.swift */,
16061608
569D6DDD220EF9E100A058A9 /* SQLInterpolation.swift */,
16071609
56FBFED82210731A00945324 /* SQLRequest.swift */,
@@ -1611,6 +1613,7 @@
16111613
56AFEF2E29969F6E00CA1E51 /* TransactionClock.swift */,
16121614
566B91321FA4D3810012D5B0 /* TransactionObserver.swift */,
16131615
56B7EE822863781300C0525F /* WALSnapshot.swift */,
1616+
56D3331F29C38D6700430680 /* WALSnapshotTransaction.swift */,
16141617
5605F1471C672E4000235C62 /* Support */,
16151618
);
16161619
path = Core;
@@ -2180,6 +2183,7 @@
21802183
563B8FC524A1D3B9007A48C9 /* OnDemandFuture.swift in Sources */,
21812184
5656A8B02295BFD7001FF3FF /* TableRecord+QueryInterfaceRequest.swift in Sources */,
21822185
5613ED4421A95B2C00DC7A68 /* ValueReducer.swift in Sources */,
2186+
56AC93DF2E291C4700DB6C74 /* DispatchQueueActor.swift in Sources */,
21832187
5636E9BC1D22574100B9B05F /* FetchRequest.swift in Sources */,
21842188
566DDE0D288D763C0000DCFB /* Fixits.swift in Sources */,
21852189
56BB6EA91D3009B100A1CA52 /* SchedulingWatchdog.swift in Sources */,

GRDB/Core/Database.swift

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1149,7 +1149,9 @@ public final class Database: CustomStringConvertible, CustomDebugStringConvertib
11491149

11501150
// See <https://www.sqlite.org/c3ref/interrupt.html>
11511151
func interrupt() {
1152-
sqlite3_interrupt(sqliteConnection)
1152+
if let sqliteConnection {
1153+
sqlite3_interrupt(sqliteConnection)
1154+
}
11531155
}
11541156

11551157
// MARK: - Database Suspension
@@ -1223,22 +1225,31 @@ public final class Database: CustomStringConvertible, CustomDebugStringConvertib
12231225
}
12241226
}
12251227

1226-
/// Cancels the current database access. All statements but ROLLBACK
1227-
/// will throw `CancellationError`, until `uncancel()` is called.
1228-
///
1229-
/// This method can be called from any thread.
1230-
func cancel() {
1231-
let needsInterrupt = suspensionMutex.withLock { suspension in
1232-
if suspension.isCancelled {
1233-
return false
1234-
}
1235-
1236-
suspension.isCancelled = true
1237-
return suspension.interruptsWhenCancelled
1228+
/// Returns a closure that cancels the current database access.
1229+
/// Most statements will throw `CancellationError`, until `uncancel()`
1230+
/// is called.
1231+
var cancel: @Sendable () -> Void {
1232+
// Workaround the fact that SQLiteConnection is not Sendable.
1233+
struct Connection: @unchecked Sendable {
1234+
var sqliteConnection: SQLiteConnection?
12381235
}
1236+
let connection = Connection(sqliteConnection: sqliteConnection)
12391237

1240-
if needsInterrupt {
1241-
interrupt()
1238+
return { [suspensionMutex, connection] in
1239+
let needsInterrupt = suspensionMutex.withLock { suspension in
1240+
if suspension.isCancelled {
1241+
return false
1242+
}
1243+
1244+
suspension.isCancelled = true
1245+
return suspension.interruptsWhenCancelled
1246+
}
1247+
1248+
if needsInterrupt {
1249+
if let sqliteConnection = connection.sqliteConnection {
1250+
sqlite3_interrupt(sqliteConnection)
1251+
}
1252+
}
12421253
}
12431254
}
12441255

@@ -1318,6 +1329,11 @@ public final class Database: CustomStringConvertible, CustomDebugStringConvertib
13181329
return
13191330
}
13201331

1332+
// Commits when read-only are just like rollbacks above.
1333+
if statement.transactionEffect == .commitTransaction && isReadOnly {
1334+
return
1335+
}
1336+
13211337
// Suspension should not prevent adjusting the read-only mode.
13221338
// See <https://github.com/groue/GRDB.swift/issues/1715>.
13231339
if statement.isQueryOnlyPragma {

0 commit comments

Comments
 (0)