Skip to content

Commit

Permalink
Merge branch 'development'
Browse files Browse the repository at this point in the history
  • Loading branch information
groue committed Apr 21, 2024
2 parents 77b85be + 1c5017a commit 639fa91
Show file tree
Hide file tree
Showing 19 changed files with 796 additions and 154 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Expand Up @@ -7,6 +7,7 @@ GRDB adheres to [Semantic Versioning](https://semver.org/), with one exception:

#### 6.x Releases

- `6.27.x` Releases - [6.27.0](#6270)
- `6.26.x` Releases - [6.26.0](#6260)
- `6.25.x` Releases - [6.25.0](#6250)
- `6.24.x` Releases - [6.24.0](#6240) - [6.24.1](#6241) - [6.24.2](#6242)
Expand Down Expand Up @@ -124,6 +125,14 @@ GRDB adheres to [Semantic Versioning](https://semver.org/), with one exception:

---

## 6.27.0

Released April 21, 2024

- **Fixed**: [#1533](https://github.com/groue/GRDB.swift/pull/1533) by [@groue](https://github.com/groue): Fix a bug in Decodable support
- **Documentation Update**: [#1534](https://github.com/groue/GRDB.swift/pull/1534) The [Single-Row Tables](Documentation/SingleRowTables.md) guide was updated with guidance about default configuration values.
- **Documentation Update**: [#1535](https://github.com/groue/GRDB.swift/pull/1535) The [ValueObservation Scheduling](https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/valueobservation#ValueObservation-Scheduling) documentation chapter explains the default behavior of `ValueObservation` fetches, and explains how to make sure they are never performed on the main thread.

## 6.26.0

Released March 23, 2024
Expand Down
3 changes: 2 additions & 1 deletion Documentation/DemoApps/GRDBAsyncDemo/README.md
Expand Up @@ -13,8 +13,9 @@ The topics covered in this demo are:

- How to setup a database in an iOS app.
- How to define a simple [Codable Record](../../../README.md#codable-records).
- How to track database changes and animate a SwiftUI List with an async sequence built from [ValueObservation](https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/valueobservation).
- How to track database changes and animate a SwiftUI List with [ValueObservation](https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/valueobservation) Combine publishers.
- How to apply the recommendations of [Recommended Practices for Designing Record Types](https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/recordrecommendedpractices).
- How to perform `async` database accesses.
- How to feed SwiftUI previews with a transient database.

**Files of interest:**
Expand Down
2 changes: 1 addition & 1 deletion GRDB.swift.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'GRDB.swift'
s.version = '6.26.0'
s.version = '6.27.0'

s.license = { :type => 'MIT', :file => 'LICENSE' }
s.summary = 'A toolkit for SQLite databases, with a focus on application development.'
Expand Down
4 changes: 4 additions & 0 deletions GRDB.xcodeproj/project.pbxproj
Expand Up @@ -276,6 +276,7 @@
56AFEF2F29969F6E00CA1E51 /* TransactionClock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AFEF2E29969F6E00CA1E51 /* TransactionClock.swift */; };
56AFEF372996B9DC00CA1E51 /* TransactionDateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AFEF362996B9DC00CA1E51 /* TransactionDateTests.swift */; };
56B021C91D8C0D3900B239BB /* MutablePersistableRecordPersistenceConflictPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B021C81D8C0D3900B239BB /* MutablePersistableRecordPersistenceConflictPolicyTests.swift */; };
56B6AB062BD3DCAC009A0B71 /* SingletonUserDefaultsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B6AB052BD3DCAC009A0B71 /* SingletonUserDefaultsTest.swift */; };
56B6EF56208CB4E3002F0ACB /* ColumnExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B6EF55208CB4E3002F0ACB /* ColumnExpressionTests.swift */; };
56B7EE832863781300C0525F /* WALSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B7EE822863781300C0525F /* WALSnapshot.swift */; };
56B7F43A1BEB42D500E39BBF /* Migration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B7F4391BEB42D500E39BBF /* Migration.swift */; };
Expand Down Expand Up @@ -770,6 +771,7 @@
56AFEF362996B9DC00CA1E51 /* TransactionDateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionDateTests.swift; sourceTree = "<group>"; };
56B021C81D8C0D3900B239BB /* MutablePersistableRecordPersistenceConflictPolicyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MutablePersistableRecordPersistenceConflictPolicyTests.swift; sourceTree = "<group>"; };
56B14E7E1D4DAE54000BF4A3 /* RowFromDictionaryLiteralTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RowFromDictionaryLiteralTests.swift; sourceTree = "<group>"; };
56B6AB052BD3DCAC009A0B71 /* SingletonUserDefaultsTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingletonUserDefaultsTest.swift; sourceTree = "<group>"; };
56B6EF55208CB4E3002F0ACB /* ColumnExpressionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColumnExpressionTests.swift; sourceTree = "<group>"; };
56B7EE822863781300C0525F /* WALSnapshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WALSnapshot.swift; sourceTree = "<group>"; };
56B7F4291BE14A1900E39BBF /* CGFloatTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGFloatTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1552,6 +1554,7 @@
children = (
564E73DE203D50B9000C443C /* JoinSupportTests.swift */,
5616B4FA28B5F5220052017E /* SingletonRecordTest.swift */,
56B6AB052BD3DCAC009A0B71 /* SingletonUserDefaultsTest.swift */,
5674A7251F30A8EF0095F066 /* FetchableRecord */,
560B3FA41C19DFF800C58EC7 /* PersistableRecord */,
56176C9E1EACEDF9000F3F2B /* Record */,
Expand Down Expand Up @@ -2086,6 +2089,7 @@
562393181DECC02000A6B01F /* RowFetchTests.swift in Sources */,
56677C0D241CD0D00050755D /* ValueObservationRecorder.swift in Sources */,
5653EADA20944B4F00F46237 /* AssociationRowScopeSearchTests.swift in Sources */,
56B6AB062BD3DCAC009A0B71 /* SingletonUserDefaultsTest.swift in Sources */,
563B5336267E2F90009549B5 /* TableTests.swift in Sources */,
56D4965A1D81304E008276D7 /* FoundationNSDataTests.swift in Sources */,
56D496791D81309E008276D7 /* RecordWithColumnNameManglingTests.swift in Sources */,
Expand Down
14 changes: 8 additions & 6 deletions GRDB/Core/Database.swift
Expand Up @@ -820,6 +820,11 @@ public final class Database: CustomStringConvertible, CustomDebugStringConvertib

/// Reports the database region to ``ValueObservation``.
///
/// Calling this method does not fetch any database values. It just
/// helps optimizing `ValueObservation`. See
/// ``ValueObservation/trackingConstantRegion(_:)`` for more
/// information, and some examples of usage.
///
/// For example:
///
/// ```swift
Expand All @@ -831,12 +836,9 @@ public final class Database: CustomStringConvertible, CustomDebugStringConvertib
/// }
/// ```
///
/// See ``ValueObservation/trackingConstantRegion(_:)`` for some examples
/// of region reporting.
///
/// This method has no effect on a ``ValueObservation`` created with an
/// explicit list of tracked regions. In the example below, only the
/// `player` table is tracked:
/// This method has no effect on a `ValueObservation` created with
/// ``ValueObservation/tracking(regions:fetch:)``. In the example below,
/// only the `player` table is tracked:
///
/// ```swift
/// // Observes the 'player' table only
Expand Down
47 changes: 39 additions & 8 deletions GRDB/Documentation.docc/Extension/ValueObservation.md
Expand Up @@ -80,6 +80,8 @@ By default, `ValueObservation` notifies a fresh value whenever any component of

By default, `ValueObservation` notifies the initial value, as well as eventual changes and errors, on the main dispatch queue, asynchronously. This can be configured: see <doc:ValueObservation#ValueObservation-Scheduling>.

By default, `ValueObservation` fetches a fresh value immediately after a change is committed in the database. In particular, modifying the database on the main thread triggers a fetch on the main thread as well. This behavior can be configured: see <doc:ValueObservation#ValueObservation-Scheduling>.

`ValueObservation` may coalesce subsequent changes into a single notification.

`ValueObservation` may notify consecutive identical values. You can filter out the undesired duplicates with the ``removeDuplicates()`` method.
Expand Down Expand Up @@ -117,16 +119,45 @@ It is very useful in graphic applications, because you can configure views right
The `immediate` scheduling requires that the observation starts from the main dispatch queue (a fatal error is raised otherwise):

```swift
let cancellable = observation.start(in: dbQueue, scheduling: .immediate) { error in
// Called on the main dispatch queue
} onChange: { value in
// Called on the main dispatch queue
print("Fresh value", value)
}
// Immediate scheduling notifies
// the initial value right on subscription.
let cancellable = observation
.start(in: dbQueue, scheduling: .immediate) { error in
// Called on the main dispatch queue
} onChange: { value in
// Called on the main dispatch queue
print("Fresh value", value)
}
// <- Here "Fresh value" has already been printed.
```

The other built-in scheduler ``ValueObservationScheduler/async(onQueue:)`` asynchronously schedules values and errors on the dispatch queue of your choice.
The other built-in scheduler ``ValueObservationScheduler/async(onQueue:)`` asynchronously schedules values and errors on the dispatch queue of your choice. Make sure you provide a serial queue, because a concurrent one such as `DispachQueue.global(qos: .default)` would mess with the ordering of fresh value notifications:

```swift
// Async scheduling notifies all values
// on the specified dispatch queue.
let myQueue: DispatchQueue
let cancellable = observation
.start(in: dbQueue, scheduling: .async(myQueue)) { error in
// Called asynchronously on myQueue
} onChange: { value in
// Called asynchronously on myQueue
print("Fresh value", value)
}
```

As described above, the `scheduling` argument controls the execution of the change and error callbacks. You also have some control on the execution of the database fetch:

- With the `.immediate` scheduling, the initial fetch is always performed synchronously, on the main thread, when the observation starts, so that the initial value can be notified immediately.

- With the default `.async` scheduling, the initial fetch is always performed asynchronouly. It never blocks the main thread.

- By default, fresh values are fetched immediately after the database was changed. In particular, modifying the database on the main thread triggers a fetch on the main thread as well.

To change this behavior, and guarantee that fresh values are never fetched from the main thread, you need a ``DatabasePool`` and an optimized observation created with the ``tracking(regions:fetch:)`` or ``trackingConstantRegion(_:)`` methods. Make sure you read the documentation of those methods, or you might write an observation that misses some database changes.

It is possible to use a ``DatabasePool`` in the application, and an in-memory ``DatabaseQueue`` in tests and Xcode previews, with the common protocol ``DatabaseWriter``.


## ValueObservation Sharing

Expand Down Expand Up @@ -237,7 +268,7 @@ When needed, you can help GRDB optimize observations and reduce database content
>
> The `map` operator performs its job without blocking database accesses, and without blocking the main thread.
> Tip: When the observation tracks a constant database region, create an optimized observation with the ``trackingConstantRegion(_:)`` method. See the documentation of this method for more information about what constitutes a "constant region", and the nature of the optimization.
> Tip: When the observation tracks a constant database region, create an optimized observation with the ``tracking(regions:fetch:)`` or ``trackingConstantRegion(_:)`` methods. Make sure you read the documentation of those methods, or you might write an observation that misses some database changes.
**Truncating WAL checkpoints impact ValueObservation.** Such checkpoints are performed with ``Database/checkpoint(_:on:)`` or [`PRAGMA wal_checkpoint`](https://www.sqlite.org/pragma.html#pragma_wal_checkpoint). When an observation is started on a ``DatabasePool``, from a database that has a missing or empty [wal file](https://www.sqlite.org/tempfiles.html#write_ahead_log_wal_files), the observation will always notify two values when it starts, even if the database content is not changed. This is a consequence of the impossibility to create the [wal snapshot](https://www.sqlite.org/c3ref/snapshot_get.html) needed for detecting that no changes were performed during the observation startup. If your application performs truncating checkpoints, you will avoid this behavior if you recreate a non-empty wal file before starting observations. To do so, perform any kind of no-op transaction (such a creating and dropping a dummy table).

Expand Down

0 comments on commit 639fa91

Please sign in to comment.