diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000000..d82189eac3
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,8 @@
+[*.swift]
+indent_style = space
+indent_size = 4
+tab_width = 4
+end_of_line = lf
+insert_final_newline = true
+max_line_length = 76
+trim_trailing_whitespace = true
diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
index 6a9f3ec808..54a73ddacb 100644
--- a/.github/workflows/CI.yml
+++ b/.github/workflows/CI.yml
@@ -1,4 +1,4 @@
-# https://github.com/actions/runner-images/blob/main/images/macos/macos-12-Readme.md
+# https://github.com/actions/runner-images/blob/main/images/macos/macos-14-Readme.md
name: "GRDB CI"
@@ -40,42 +40,18 @@ jobs:
fail-fast: false
matrix:
include:
- - xcode: "Xcode_14.3.1.app"
- runsOn: macOS-13
+ - xcode: "Xcode_16.1.app"
+ runsOn: macOS-14
destination: "platform=macOS"
name: "macOS"
- - xcode: "Xcode_14.3.1.app"
- runsOn: macOS-13
- destination: "OS=16.4,name=iPhone 14 Pro"
+ - xcode: "Xcode_16.1.app"
+ runsOn: macOS-14
+ destination: "OS=18.1,name=iPhone 15 Pro"
name: "iOS"
- - xcode: "Xcode_14.3.1.app"
- runsOn: macOS-13
- destination: "OS=16.4,name=Apple TV"
+ - xcode: "Xcode_16.1.app"
+ runsOn: macOS-14
+ destination: "OS=18.0,name=Apple TV"
name: "tvOS"
- - xcode: "Xcode_14.2.app"
- runsOn: macOS-13
- destination: "platform=macOS"
- name: "macOS"
- - xcode: "Xcode_14.2.app"
- runsOn: macOS-13
- destination: "OS=16.2,name=iPhone 14"
- name: "iOS"
- - xcode: "Xcode_14.1.app"
- runsOn: macOS-13
- destination: "platform=macOS"
- name: "macOS"
- - xcode: "Xcode_14.1.app"
- runsOn: macOS-13
- destination: "OS=16.1,name=iPhone 14"
- name: "iOS"
- - xcode: "Xcode_14.0.1.app"
- runsOn: macOS-12
- destination: "platform=macOS"
- name: "macOS"
- - xcode: "Xcode_14.0.1.app"
- runsOn: macOS-12
- destination: "OS=16.0,name=iPhone 14"
- name: "iOS"
steps:
- uses: actions/checkout@v4
- name: ${{ matrix.name }}
@@ -90,18 +66,9 @@ jobs:
fail-fast: false
matrix:
include:
- - xcode: "Xcode_14.3.1.app"
- runsOn: macOS-13
- name: "Xcode 14.3.1"
- - xcode: "Xcode_14.2.app"
- runsOn: macOS-13
- name: "Xcode 14.2"
- - xcode: "Xcode_14.1.app"
- runsOn: macOS-13
- name: "Xcode 14.1"
- - xcode: "Xcode_14.0.1.app"
- runsOn: macOS-12
- name: "Xcode 14.0.1"
+ - xcode: "Xcode_16.1.app"
+ runsOn: macOS-14
+ name: "Xcode 16.1"
steps:
- uses: actions/checkout@v4
- name: ${{ matrix.name }}
@@ -116,12 +83,9 @@ jobs:
fail-fast: false
matrix:
include:
- - xcode: "Xcode_14.3.1.app"
- runsOn: macOS-13
- name: "Xcode 14.3.1"
- - xcode: "Xcode_14.0.1.app"
- runsOn: macOS-12
- name: "Xcode 14.0.1"
+ - xcode: "Xcode_16.1.app"
+ runsOn: macOS-14
+ name: "Xcode 16.1"
steps:
- uses: actions/checkout@v4
- name: ${{ matrix.name }}
@@ -136,12 +100,9 @@ jobs:
fail-fast: false
matrix:
include:
- - xcode: "Xcode_14.3.1.app"
- runsOn: macOS-13
- name: "Xcode 14.3.1"
- - xcode: "Xcode_14.0.1.app"
- runsOn: macOS-12
- name: "Xcode 14.0.1"
+ - xcode: "Xcode_16.1.app"
+ runsOn: macOS-14
+ name: "Xcode 16.1"
steps:
- uses: actions/checkout@v4
- name: ${{ matrix.name }}
@@ -156,12 +117,9 @@ jobs:
fail-fast: false
matrix:
include:
- - xcode: "Xcode_14.3.1.app"
- runsOn: macOS-13
- name: "Xcode 14.3.1"
- - xcode: "Xcode_14.0.1.app"
- runsOn: macOS-12
- name: "Xcode 14.0.1"
+ - xcode: "Xcode_16.1.app"
+ runsOn: macOS-14
+ name: "Xcode 16.1"
steps:
- uses: actions/checkout@v4
- name: ${{ matrix.name }}
@@ -176,12 +134,9 @@ jobs:
fail-fast: false
matrix:
include:
- - xcode: "Xcode_14.3.1.app"
- runsOn: macOS-13
- name: "Xcode 14.3.1"
- - xcode: "Xcode_14.0.1.app"
- runsOn: macOS-12
- name: "Xcode 14.0.1"
+ - xcode: "Xcode_16.1.app"
+ runsOn: macOS-14
+ name: "Xcode 16.1"
steps:
- uses: actions/checkout@v4
- name: ${{ matrix.name }}
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/GRDB-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/GRDB-Package.xcscheme
deleted file mode 100644
index b60345c202..0000000000
--- a/.swiftpm/xcode/xcshareddata/xcschemes/GRDB-Package.xcscheme
+++ /dev/null
@@ -1,134 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 68f846842e..099b355ded 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -127,6 +127,25 @@ GRDB adheres to [Semantic Versioning](https://semver.org/), with one exception:
---
+## Next Release
+
+- **Breaking Change**: Bump requirements by [@groue](https://github.com/groue) in [#1598](https://github.com/groue/GRDB.swift/pull/1598) and [#1634](https://github.com/groue/GRDB.swift/pull/1634)
+- **Breaking Change**: Enhance ergonomics of record methods that insert/save/upsert and fetch by [@groue](https://github.com/groue) in [#1599](https://github.com/groue/GRDB.swift/pull/1599)
+- **Breaking Change**: Rename the CSQLite module to GRDBSQLite, and stop exporting the C SQLite functions by [@groue](https://github.com/groue) in [#1600](https://github.com/groue/GRDB.swift/pull/1600)
+- **Breaking Change**: Perform all writes with immediate transactions by default by [@groue](https://github.com/groue) in [#1602](https://github.com/groue/GRDB.swift/pull/1602)
+- **Breaking Change**: Remove DatabasePool.concurrentRead by [@groue](https://github.com/groue) in [#1603](https://github.com/groue/GRDB.swift/pull/1603)
+- **Breaking Change**: Coding strategies depend on the column by [@groue](https://github.com/groue) in [#1606](https://github.com/groue/GRDB.swift/pull/1606)
+- **Breaking Change**: Add missing Sendable conformances by [@groue](https://github.com/groue) in [#1607](https://github.com/groue/GRDB.swift/pull/1607) and [#1639](https://github.com/groue/GRDB.swift/pull/1639)
+- **Breaking Change**: Async database accesses honor Task cancellation by [@groue](https://github.com/groue) in [#1610](https://github.com/groue/GRDB.swift/pull/1610)
+- **Breaking Change**: Prefer Collection over Sequence for filter(keys:) and related APIs by [@groue](https://github.com/groue) in [#1617](https://github.com/groue/GRDB.swift/pull/1617)
+- **Breaking Change**: MainActor ValueObservation scheduling by [@groue](https://github.com/groue) in [#1633](https://github.com/groue/GRDB.swift/pull/1633)
+- **Breaking Change**: Prefer any DatabaseReader and DatabaseWriter by [@groue](https://github.com/groue) in [#1635](https://github.com/groue/GRDB.swift/pull/1635)
+- **New**: Sendable database accesses by [@groue](https://github.com/groue) in [#1618](https://github.com/groue/GRDB.swift/pull/1618)
+- **New**: DatabaseCursor has a primary associated type by [@groue](https://github.com/groue) in [#1605](https://github.com/groue/GRDB.swift/pull/1605)
+- **Documentation Update**: [Migrating From GRDB 6 to GRDB 7](Documentation/GRDB7MigrationGuide.md) describes in detail how to bump the GRDB version in your application.
+- **Documentation Update**: The new [Swift Concurrency and GRDB](https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/swiftconcurrency) guide explains how to best integrate GRDB and Swift Concurrency.
+- **Documentation Update**: The [demo app](Documentation/DemoApps/) was rewritten from scratch in a brand new Xcode 16 project.
+
## 6.29.3
Released September 7, 2024
diff --git a/Documentation/AssociationsBasics.md b/Documentation/AssociationsBasics.md
index 50e241674f..e37565ce75 100644
--- a/Documentation/AssociationsBasics.md
+++ b/Documentation/AssociationsBasics.md
@@ -137,9 +137,7 @@ Before we dive in, please remember that associations can not generate all possib
**Associations are available on types that adopt the necessary supporting protocols.**
-When your record type is a subclass of the [Record class], all necessary protocols are already setup and ready: you can skip this chapter.
-
-Generally speaking, associations use the [TableRecord], [FetchableRecord], and [EncodableRecord] protocols:
+Associations are based on the [TableRecord], [FetchableRecord], and [EncodableRecord] protocols:
- **[TableRecord]** is the protocol that lets you declare associations between record types:
@@ -3005,7 +3003,6 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
[TableRecord]: ../README.md#tablerecord-protocol
[Recommended Practices for Designing Record Types]: https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/recordrecommendedpractices
[regular aggregating methods]: ../README.md#fetching-aggregated-values
-[Record class]: ../README.md#record-class
[EncodableRecord]: ../README.md#persistablerecord-protocol
[PersistableRecord]: ../README.md#persistablerecord-protocol
[Codable Records]: ../README.md#codable-records
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo.xcodeproj/project.pbxproj b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo.xcodeproj/project.pbxproj
deleted file mode 100644
index 7b04e5bebc..0000000000
--- a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo.xcodeproj/project.pbxproj
+++ /dev/null
@@ -1,611 +0,0 @@
-// !$*UTF8*$!
-{
- archiveVersion = 1;
- classes = {
- };
- objectVersion = 54;
- objects = {
-
-/* Begin PBXBuildFile section */
- 56026CAC25B8A7EF00D1DF3F /* PlayerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56026CAA25B8A7EF00D1DF3F /* PlayerTests.swift */; };
- 56026CAD25B8A7EF00D1DF3F /* AppDatabaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56026CAB25B8A7EF00D1DF3F /* AppDatabaseTests.swift */; };
- 56519DCA274FC8E900ED16D8 /* GRDBQuery in Frameworks */ = {isa = PBXBuildFile; productRef = 56519DC9274FC8E900ED16D8 /* GRDBQuery */; };
- 5671723A261B23C800423B6F /* PlayerList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56717239261B23C800423B6F /* PlayerList.swift */; };
- 56717252261B334D00423B6F /* PlayerRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56717251261B334D00423B6F /* PlayerRequestTests.swift */; };
- 567C3E1A2520B6DE0011F6E9 /* GRDBAsyncDemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567C3E192520B6DE0011F6E9 /* GRDBAsyncDemoApp.swift */; };
- 567C3E1E2520B6DF0011F6E9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 567C3E1D2520B6DF0011F6E9 /* Assets.xcassets */; };
- 567C3E212520B6DF0011F6E9 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 567C3E202520B6DF0011F6E9 /* Preview Assets.xcassets */; };
- 567C3E5D2520B75C0011F6E9 /* Player.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567C3E532520B75C0011F6E9 /* Player.swift */; };
- 567C3E5E2520B75C0011F6E9 /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567C3E542520B75C0011F6E9 /* Persistence.swift */; };
- 567C3E612520B75D0011F6E9 /* PlayerFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567C3E592520B75C0011F6E9 /* PlayerFormView.swift */; };
- 567C3E622520B75D0011F6E9 /* AppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567C3E5A2520B75C0011F6E9 /* AppView.swift */; };
- 567C3E632520B75D0011F6E9 /* PlayerCreationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567C3E5B2520B75C0011F6E9 /* PlayerCreationView.swift */; };
- 567C3E642520B75D0011F6E9 /* PlayerEditionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567C3E5C2520B75C0011F6E9 /* PlayerEditionView.swift */; };
- 567C3E662520B7880011F6E9 /* AppDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567C3E652520B7880011F6E9 /* AppDatabase.swift */; };
- 567C3E792520BB650011F6E9 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 567C3E752520BB650011F6E9 /* Localizable.stringsdict */; };
- 567C3E7A2520BB650011F6E9 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 567C3E772520BB650011F6E9 /* LaunchScreen.storyboard */; };
- 56B6D1092619EC1B003CC455 /* PlayerRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B6D1082619EC1B003CC455 /* PlayerRequest.swift */; };
- 56F8A13527359A5A0011ACBE /* GRDB in Frameworks */ = {isa = PBXBuildFile; productRef = 56F8A13427359A5A0011ACBE /* GRDB */; };
-/* End PBXBuildFile section */
-
-/* Begin PBXContainerItemProxy section */
- 56026C9D25B8A7D000D1DF3F /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 567C3E0E2520B6DE0011F6E9 /* Project object */;
- proxyType = 1;
- remoteGlobalIDString = 567C3E152520B6DE0011F6E9;
- remoteInfo = GRDBAsyncDemo;
- };
-/* End PBXContainerItemProxy section */
-
-/* Begin PBXCopyFilesBuildPhase section */
- 567C3E502520B70E0011F6E9 /* Embed Frameworks */ = {
- isa = PBXCopyFilesBuildPhase;
- buildActionMask = 2147483647;
- dstPath = "";
- dstSubfolderSpec = 10;
- files = (
- );
- name = "Embed Frameworks";
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXCopyFilesBuildPhase section */
-
-/* Begin PBXFileReference section */
- 56026C9825B8A7D000D1DF3F /* GRDBAsyncDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GRDBAsyncDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
- 56026C9C25B8A7D000D1DF3F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- 56026CAA25B8A7EF00D1DF3F /* PlayerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerTests.swift; sourceTree = ""; };
- 56026CAB25B8A7EF00D1DF3F /* AppDatabaseTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDatabaseTests.swift; sourceTree = ""; };
- 56717239261B23C800423B6F /* PlayerList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerList.swift; sourceTree = ""; };
- 56717251261B334D00423B6F /* PlayerRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerRequestTests.swift; sourceTree = ""; };
- 567C3E162520B6DE0011F6E9 /* GRDBAsyncDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GRDBAsyncDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
- 567C3E192520B6DE0011F6E9 /* GRDBAsyncDemoApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GRDBAsyncDemoApp.swift; sourceTree = ""; };
- 567C3E1D2520B6DF0011F6E9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
- 567C3E202520B6DF0011F6E9 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
- 567C3E222520B6DF0011F6E9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- 567C3E532520B75C0011F6E9 /* Player.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Player.swift; sourceTree = ""; };
- 567C3E542520B75C0011F6E9 /* Persistence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = ""; };
- 567C3E592520B75C0011F6E9 /* PlayerFormView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerFormView.swift; sourceTree = ""; };
- 567C3E5A2520B75C0011F6E9 /* AppView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppView.swift; sourceTree = ""; };
- 567C3E5B2520B75C0011F6E9 /* PlayerCreationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerCreationView.swift; sourceTree = ""; };
- 567C3E5C2520B75C0011F6E9 /* PlayerEditionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerEditionView.swift; sourceTree = ""; };
- 567C3E652520B7880011F6E9 /* AppDatabase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDatabase.swift; sourceTree = ""; };
- 567C3E762520BB650011F6E9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; };
- 567C3E782520BB650011F6E9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
- 56B6D1082619EC1B003CC455 /* PlayerRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerRequest.swift; sourceTree = ""; };
- 56F8A12E27359A350011ACBE /* GRDB.swift */ = {isa = PBXFileReference; lastKnownFileType = folder; name = GRDB.swift; path = ../../..; sourceTree = ""; };
-/* End PBXFileReference section */
-
-/* Begin PBXFrameworksBuildPhase section */
- 56026C9525B8A7D000D1DF3F /* Frameworks */ = {
- isa = PBXFrameworksBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 567C3E132520B6DE0011F6E9 /* Frameworks */ = {
- isa = PBXFrameworksBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 56519DCA274FC8E900ED16D8 /* GRDBQuery in Frameworks */,
- 56F8A13527359A5A0011ACBE /* GRDB in Frameworks */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXFrameworksBuildPhase section */
-
-/* Begin PBXGroup section */
- 56026C9925B8A7D000D1DF3F /* GRDBAsyncDemoTests */ = {
- isa = PBXGroup;
- children = (
- 56026C9C25B8A7D000D1DF3F /* Info.plist */,
- 56026CAB25B8A7EF00D1DF3F /* AppDatabaseTests.swift */,
- 56717251261B334D00423B6F /* PlayerRequestTests.swift */,
- 56026CAA25B8A7EF00D1DF3F /* PlayerTests.swift */,
- );
- path = GRDBAsyncDemoTests;
- sourceTree = "";
- };
- 56185BC125B8047D00B9C30F /* Resources */ = {
- isa = PBXGroup;
- children = (
- 567C3E1D2520B6DF0011F6E9 /* Assets.xcassets */,
- 567C3E772520BB650011F6E9 /* LaunchScreen.storyboard */,
- 567C3E752520BB650011F6E9 /* Localizable.stringsdict */,
- );
- path = Resources;
- sourceTree = "";
- };
- 567C3E0D2520B6DE0011F6E9 = {
- isa = PBXGroup;
- children = (
- 567C3E182520B6DE0011F6E9 /* GRDBAsyncDemo */,
- 56026C9925B8A7D000D1DF3F /* GRDBAsyncDemoTests */,
- 567C3E172520B6DE0011F6E9 /* Products */,
- 567C3E4D2520B70E0011F6E9 /* Frameworks */,
- 56F8A12E27359A350011ACBE /* GRDB.swift */,
- );
- sourceTree = "";
- };
- 567C3E172520B6DE0011F6E9 /* Products */ = {
- isa = PBXGroup;
- children = (
- 567C3E162520B6DE0011F6E9 /* GRDBAsyncDemo.app */,
- 56026C9825B8A7D000D1DF3F /* GRDBAsyncDemoTests.xctest */,
- );
- name = Products;
- sourceTree = "";
- };
- 567C3E182520B6DE0011F6E9 /* GRDBAsyncDemo */ = {
- isa = PBXGroup;
- children = (
- 567C3E222520B6DF0011F6E9 /* Info.plist */,
- 567C3E652520B7880011F6E9 /* AppDatabase.swift */,
- 567C3E192520B6DE0011F6E9 /* GRDBAsyncDemoApp.swift */,
- 567C3E542520B75C0011F6E9 /* Persistence.swift */,
- 567C3E532520B75C0011F6E9 /* Player.swift */,
- 56B6D1082619EC1B003CC455 /* PlayerRequest.swift */,
- 567C3E1F2520B6DF0011F6E9 /* Preview Content */,
- 56185BC125B8047D00B9C30F /* Resources */,
- 567C3E582520B75C0011F6E9 /* Views */,
- );
- path = GRDBAsyncDemo;
- sourceTree = "";
- };
- 567C3E1F2520B6DF0011F6E9 /* Preview Content */ = {
- isa = PBXGroup;
- children = (
- 567C3E202520B6DF0011F6E9 /* Preview Assets.xcassets */,
- );
- path = "Preview Content";
- sourceTree = "";
- };
- 567C3E4D2520B70E0011F6E9 /* Frameworks */ = {
- isa = PBXGroup;
- children = (
- );
- name = Frameworks;
- sourceTree = "";
- };
- 567C3E582520B75C0011F6E9 /* Views */ = {
- isa = PBXGroup;
- children = (
- 567C3E5A2520B75C0011F6E9 /* AppView.swift */,
- 567C3E5B2520B75C0011F6E9 /* PlayerCreationView.swift */,
- 567C3E5C2520B75C0011F6E9 /* PlayerEditionView.swift */,
- 567C3E592520B75C0011F6E9 /* PlayerFormView.swift */,
- 56717239261B23C800423B6F /* PlayerList.swift */,
- );
- path = Views;
- sourceTree = "";
- };
-/* End PBXGroup section */
-
-/* Begin PBXNativeTarget section */
- 56026C9725B8A7D000D1DF3F /* GRDBAsyncDemoTests */ = {
- isa = PBXNativeTarget;
- buildConfigurationList = 56026CA825B8A7D000D1DF3F /* Build configuration list for PBXNativeTarget "GRDBAsyncDemoTests" */;
- buildPhases = (
- 56026C9425B8A7D000D1DF3F /* Sources */,
- 56026C9525B8A7D000D1DF3F /* Frameworks */,
- 56026C9625B8A7D000D1DF3F /* Resources */,
- );
- buildRules = (
- );
- dependencies = (
- 56026C9E25B8A7D000D1DF3F /* PBXTargetDependency */,
- );
- name = GRDBAsyncDemoTests;
- productName = GRDBAsyncDemoTests;
- productReference = 56026C9825B8A7D000D1DF3F /* GRDBAsyncDemoTests.xctest */;
- productType = "com.apple.product-type.bundle.unit-test";
- };
- 567C3E152520B6DE0011F6E9 /* GRDBAsyncDemo */ = {
- isa = PBXNativeTarget;
- buildConfigurationList = 567C3E252520B6DF0011F6E9 /* Build configuration list for PBXNativeTarget "GRDBAsyncDemo" */;
- buildPhases = (
- 567C3E122520B6DE0011F6E9 /* Sources */,
- 567C3E132520B6DE0011F6E9 /* Frameworks */,
- 567C3E142520B6DE0011F6E9 /* Resources */,
- 567C3E502520B70E0011F6E9 /* Embed Frameworks */,
- );
- buildRules = (
- );
- dependencies = (
- 56F8A13127359A540011ACBE /* PBXTargetDependency */,
- );
- name = GRDBAsyncDemo;
- packageProductDependencies = (
- 56F8A13427359A5A0011ACBE /* GRDB */,
- 56519DC9274FC8E900ED16D8 /* GRDBQuery */,
- );
- productName = GRBCombineDemo;
- productReference = 567C3E162520B6DE0011F6E9 /* GRDBAsyncDemo.app */;
- productType = "com.apple.product-type.application";
- };
-/* End PBXNativeTarget section */
-
-/* Begin PBXProject section */
- 567C3E0E2520B6DE0011F6E9 /* Project object */ = {
- isa = PBXProject;
- attributes = {
- BuildIndependentTargetsInParallel = YES;
- LastSwiftUpdateCheck = 1250;
- LastUpgradeCheck = 1200;
- TargetAttributes = {
- 56026C9725B8A7D000D1DF3F = {
- CreatedOnToolsVersion = 12.3;
- TestTargetID = 567C3E152520B6DE0011F6E9;
- };
- 567C3E152520B6DE0011F6E9 = {
- CreatedOnToolsVersion = 12.0;
- };
- };
- };
- buildConfigurationList = 567C3E112520B6DE0011F6E9 /* Build configuration list for PBXProject "GRDBAsyncDemo" */;
- compatibilityVersion = "Xcode 12.0";
- developmentRegion = en;
- hasScannedForEncodings = 0;
- knownRegions = (
- en,
- Base,
- );
- mainGroup = 567C3E0D2520B6DE0011F6E9;
- packageReferences = (
- 56519DC8274FC8E900ED16D8 /* XCRemoteSwiftPackageReference "GRDBQuery" */,
- );
- productRefGroup = 567C3E172520B6DE0011F6E9 /* Products */;
- projectDirPath = "";
- projectRoot = "";
- targets = (
- 567C3E152520B6DE0011F6E9 /* GRDBAsyncDemo */,
- 56026C9725B8A7D000D1DF3F /* GRDBAsyncDemoTests */,
- );
- };
-/* End PBXProject section */
-
-/* Begin PBXResourcesBuildPhase section */
- 56026C9625B8A7D000D1DF3F /* Resources */ = {
- isa = PBXResourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 567C3E142520B6DE0011F6E9 /* Resources */ = {
- isa = PBXResourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 567C3E212520B6DF0011F6E9 /* Preview Assets.xcassets in Resources */,
- 567C3E7A2520BB650011F6E9 /* LaunchScreen.storyboard in Resources */,
- 567C3E1E2520B6DF0011F6E9 /* Assets.xcassets in Resources */,
- 567C3E792520BB650011F6E9 /* Localizable.stringsdict in Resources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXResourcesBuildPhase section */
-
-/* Begin PBXSourcesBuildPhase section */
- 56026C9425B8A7D000D1DF3F /* Sources */ = {
- isa = PBXSourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 56026CAC25B8A7EF00D1DF3F /* PlayerTests.swift in Sources */,
- 56026CAD25B8A7EF00D1DF3F /* AppDatabaseTests.swift in Sources */,
- 56717252261B334D00423B6F /* PlayerRequestTests.swift in Sources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 567C3E122520B6DE0011F6E9 /* Sources */ = {
- isa = PBXSourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 567C3E5E2520B75C0011F6E9 /* Persistence.swift in Sources */,
- 567C3E5D2520B75C0011F6E9 /* Player.swift in Sources */,
- 56B6D1092619EC1B003CC455 /* PlayerRequest.swift in Sources */,
- 5671723A261B23C800423B6F /* PlayerList.swift in Sources */,
- 567C3E612520B75D0011F6E9 /* PlayerFormView.swift in Sources */,
- 567C3E632520B75D0011F6E9 /* PlayerCreationView.swift in Sources */,
- 567C3E662520B7880011F6E9 /* AppDatabase.swift in Sources */,
- 567C3E622520B75D0011F6E9 /* AppView.swift in Sources */,
- 567C3E642520B75D0011F6E9 /* PlayerEditionView.swift in Sources */,
- 567C3E1A2520B6DE0011F6E9 /* GRDBAsyncDemoApp.swift in Sources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXSourcesBuildPhase section */
-
-/* Begin PBXTargetDependency section */
- 56026C9E25B8A7D000D1DF3F /* PBXTargetDependency */ = {
- isa = PBXTargetDependency;
- target = 567C3E152520B6DE0011F6E9 /* GRDBAsyncDemo */;
- targetProxy = 56026C9D25B8A7D000D1DF3F /* PBXContainerItemProxy */;
- };
- 56F8A13127359A540011ACBE /* PBXTargetDependency */ = {
- isa = PBXTargetDependency;
- productRef = 56F8A13027359A540011ACBE /* GRDB */;
- };
-/* End PBXTargetDependency section */
-
-/* Begin PBXVariantGroup section */
- 567C3E752520BB650011F6E9 /* Localizable.stringsdict */ = {
- isa = PBXVariantGroup;
- children = (
- 567C3E762520BB650011F6E9 /* en */,
- );
- name = Localizable.stringsdict;
- sourceTree = "";
- };
- 567C3E772520BB650011F6E9 /* LaunchScreen.storyboard */ = {
- isa = PBXVariantGroup;
- children = (
- 567C3E782520BB650011F6E9 /* Base */,
- );
- name = LaunchScreen.storyboard;
- sourceTree = "";
- };
-/* End PBXVariantGroup section */
-
-/* Begin XCBuildConfiguration section */
- 56026C9F25B8A7D000D1DF3F /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- BUNDLE_LOADER = "$(TEST_HOST)";
- CODE_SIGN_STYLE = Automatic;
- INFOPLIST_FILE = GRDBAsyncDemoTests/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 17.0;
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@executable_path/Frameworks",
- "@loader_path/Frameworks",
- );
- PRODUCT_BUNDLE_IDENTIFIER = com.github.groue.GRDBAsyncDemoTests;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = "1,2";
- TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GRDBAsyncDemo.app/GRDBAsyncDemo";
- };
- name = Debug;
- };
- 56026CA025B8A7D000D1DF3F /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- BUNDLE_LOADER = "$(TEST_HOST)";
- CODE_SIGN_STYLE = Automatic;
- INFOPLIST_FILE = GRDBAsyncDemoTests/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 17.0;
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@executable_path/Frameworks",
- "@loader_path/Frameworks",
- );
- PRODUCT_BUNDLE_IDENTIFIER = com.github.groue.GRDBAsyncDemoTests;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = "1,2";
- TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GRDBAsyncDemo.app/GRDBAsyncDemo";
- };
- name = Release;
- };
- 567C3E232520B6DF0011F6E9 /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
- CLANG_ANALYZER_NONNULL = YES;
- CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
- CLANG_CXX_LIBRARY = "libc++";
- CLANG_ENABLE_MODULES = YES;
- CLANG_ENABLE_OBJC_ARC = YES;
- CLANG_ENABLE_OBJC_WEAK = YES;
- CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
- CLANG_WARN_BOOL_CONVERSION = YES;
- CLANG_WARN_COMMA = YES;
- CLANG_WARN_CONSTANT_CONVERSION = YES;
- CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
- CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
- CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
- CLANG_WARN_EMPTY_BODY = YES;
- CLANG_WARN_ENUM_CONVERSION = YES;
- CLANG_WARN_INFINITE_RECURSION = YES;
- CLANG_WARN_INT_CONVERSION = YES;
- CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
- CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
- CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
- CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
- CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
- CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
- CLANG_WARN_STRICT_PROTOTYPES = YES;
- CLANG_WARN_SUSPICIOUS_MOVE = YES;
- CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
- CLANG_WARN_UNREACHABLE_CODE = YES;
- CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- COPY_PHASE_STRIP = NO;
- DEBUG_INFORMATION_FORMAT = dwarf;
- ENABLE_STRICT_OBJC_MSGSEND = YES;
- ENABLE_TESTABILITY = YES;
- GCC_C_LANGUAGE_STANDARD = gnu11;
- GCC_DYNAMIC_NO_PIC = NO;
- GCC_NO_COMMON_BLOCKS = YES;
- GCC_OPTIMIZATION_LEVEL = 0;
- GCC_PREPROCESSOR_DEFINITIONS = (
- "DEBUG=1",
- "$(inherited)",
- );
- GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
- GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
- GCC_WARN_UNDECLARED_SELECTOR = YES;
- GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
- GCC_WARN_UNUSED_FUNCTION = YES;
- GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 15.0;
- MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
- MTL_FAST_MATH = YES;
- ONLY_ACTIVE_ARCH = YES;
- SDKROOT = iphoneos;
- SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
- SWIFT_OPTIMIZATION_LEVEL = "-Onone";
- };
- name = Debug;
- };
- 567C3E242520B6DF0011F6E9 /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
- CLANG_ANALYZER_NONNULL = YES;
- CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
- CLANG_CXX_LIBRARY = "libc++";
- CLANG_ENABLE_MODULES = YES;
- CLANG_ENABLE_OBJC_ARC = YES;
- CLANG_ENABLE_OBJC_WEAK = YES;
- CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
- CLANG_WARN_BOOL_CONVERSION = YES;
- CLANG_WARN_COMMA = YES;
- CLANG_WARN_CONSTANT_CONVERSION = YES;
- CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
- CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
- CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
- CLANG_WARN_EMPTY_BODY = YES;
- CLANG_WARN_ENUM_CONVERSION = YES;
- CLANG_WARN_INFINITE_RECURSION = YES;
- CLANG_WARN_INT_CONVERSION = YES;
- CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
- CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
- CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
- CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
- CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
- CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
- CLANG_WARN_STRICT_PROTOTYPES = YES;
- CLANG_WARN_SUSPICIOUS_MOVE = YES;
- CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
- CLANG_WARN_UNREACHABLE_CODE = YES;
- CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- COPY_PHASE_STRIP = NO;
- DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
- ENABLE_NS_ASSERTIONS = NO;
- ENABLE_STRICT_OBJC_MSGSEND = YES;
- GCC_C_LANGUAGE_STANDARD = gnu11;
- GCC_NO_COMMON_BLOCKS = YES;
- GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
- GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
- GCC_WARN_UNDECLARED_SELECTOR = YES;
- GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
- GCC_WARN_UNUSED_FUNCTION = YES;
- GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 15.0;
- MTL_ENABLE_DEBUG_INFO = NO;
- MTL_FAST_MATH = YES;
- SDKROOT = iphoneos;
- SWIFT_COMPILATION_MODE = wholemodule;
- SWIFT_OPTIMIZATION_LEVEL = "-O";
- VALIDATE_PRODUCT = YES;
- };
- name = Release;
- };
- 567C3E262520B6DF0011F6E9 /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
- ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
- CODE_SIGN_STYLE = Automatic;
- DEVELOPMENT_ASSET_PATHS = "\"GRDBAsyncDemo/Preview Content\"";
- DEVELOPMENT_TEAM = "";
- ENABLE_PREVIEWS = YES;
- INFOPLIST_FILE = GRDBAsyncDemo/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 17.0;
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@executable_path/Frameworks",
- );
- PRODUCT_BUNDLE_IDENTIFIER = com.github.groue.GRDBAsyncDemo;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = 1;
- };
- name = Debug;
- };
- 567C3E272520B6DF0011F6E9 /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
- ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
- CODE_SIGN_STYLE = Automatic;
- DEVELOPMENT_ASSET_PATHS = "\"GRDBAsyncDemo/Preview Content\"";
- DEVELOPMENT_TEAM = "";
- ENABLE_PREVIEWS = YES;
- INFOPLIST_FILE = GRDBAsyncDemo/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 17.0;
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@executable_path/Frameworks",
- );
- PRODUCT_BUNDLE_IDENTIFIER = com.github.groue.GRDBAsyncDemo;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = 1;
- };
- name = Release;
- };
-/* End XCBuildConfiguration section */
-
-/* Begin XCConfigurationList section */
- 56026CA825B8A7D000D1DF3F /* Build configuration list for PBXNativeTarget "GRDBAsyncDemoTests" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 56026C9F25B8A7D000D1DF3F /* Debug */,
- 56026CA025B8A7D000D1DF3F /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
- 567C3E112520B6DE0011F6E9 /* Build configuration list for PBXProject "GRDBAsyncDemo" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 567C3E232520B6DF0011F6E9 /* Debug */,
- 567C3E242520B6DF0011F6E9 /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
- 567C3E252520B6DF0011F6E9 /* Build configuration list for PBXNativeTarget "GRDBAsyncDemo" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 567C3E262520B6DF0011F6E9 /* Debug */,
- 567C3E272520B6DF0011F6E9 /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
-/* End XCConfigurationList section */
-
-/* Begin XCRemoteSwiftPackageReference section */
- 56519DC8274FC8E900ED16D8 /* XCRemoteSwiftPackageReference "GRDBQuery" */ = {
- isa = XCRemoteSwiftPackageReference;
- repositoryURL = "https://github.com/groue/GRDBQuery";
- requirement = {
- kind = upToNextMajorVersion;
- minimumVersion = 0.6.0;
- };
- };
-/* End XCRemoteSwiftPackageReference section */
-
-/* Begin XCSwiftPackageProductDependency section */
- 56519DC9274FC8E900ED16D8 /* GRDBQuery */ = {
- isa = XCSwiftPackageProductDependency;
- package = 56519DC8274FC8E900ED16D8 /* XCRemoteSwiftPackageReference "GRDBQuery" */;
- productName = GRDBQuery;
- };
- 56F8A13027359A540011ACBE /* GRDB */ = {
- isa = XCSwiftPackageProductDependency;
- productName = GRDB;
- };
- 56F8A13427359A5A0011ACBE /* GRDB */ = {
- isa = XCSwiftPackageProductDependency;
- productName = GRDB;
- };
-/* End XCSwiftPackageProductDependency section */
- };
- rootObject = 567C3E0E2520B6DE0011F6E9 /* Project object */;
-}
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
deleted file mode 100644
index 18d981003d..0000000000
--- a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
- IDEDidComputeMac32BitWarning
-
-
-
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
deleted file mode 100644
index 216295601d..0000000000
--- a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "pins" : [
- {
- "identity" : "grdbquery",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/groue/GRDBQuery",
- "state" : {
- "revision" : "a6c46dd38ecf11a5c37732870dc03a384d582fba",
- "version" : "0.9.0"
- }
- }
- ],
- "version" : 2
-}
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo.xcodeproj/xcshareddata/xcschemes/GRDBAsyncDemo.xcscheme b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo.xcodeproj/xcshareddata/xcschemes/GRDBAsyncDemo.xcscheme
deleted file mode 100644
index aeb65e487a..0000000000
--- a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo.xcodeproj/xcshareddata/xcschemes/GRDBAsyncDemo.xcscheme
+++ /dev/null
@@ -1,96 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/AppDatabase.swift b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/AppDatabase.swift
deleted file mode 100644
index e19cc7af7a..0000000000
--- a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/AppDatabase.swift
+++ /dev/null
@@ -1,235 +0,0 @@
-import Foundation
-import GRDB
-import os.log
-
-/// A database of players.
-///
-/// You create an `AppDatabase` with a connection to an SQLite database
-/// (see ).
-///
-/// Create those connections with a configuration returned from
-/// `AppDatabase/makeConfiguration(_:)`.
-///
-/// For example:
-///
-/// ```swift
-/// // Create an in-memory AppDatabase
-/// let config = AppDatabase.makeConfiguration()
-/// let dbQueue = try DatabaseQueue(configuration: config)
-/// let appDatabase = try AppDatabase(dbQueue)
-/// ```
-struct AppDatabase {
- /// Creates an `AppDatabase`, and makes sure the database schema
- /// is ready.
- ///
- /// - important: Create the `DatabaseWriter` with a configuration
- /// returned by ``makeConfiguration(_:)``.
- init(_ dbWriter: any DatabaseWriter) throws {
- self.dbWriter = dbWriter
- try migrator.migrate(dbWriter)
- }
-
- /// Provides access to the database.
- ///
- /// Application can use a `DatabasePool`, while SwiftUI previews and tests
- /// can use a fast in-memory `DatabaseQueue`.
- ///
- /// See
- private let dbWriter: any DatabaseWriter
-}
-
-// MARK: - Database Configuration
-
-extension AppDatabase {
- private static let sqlLogger = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "SQL")
-
- /// Returns a database configuration suited for `PlayerRepository`.
- ///
- /// SQL statements are logged if the `SQL_TRACE` environment variable
- /// is set.
- ///
- /// - parameter base: A base configuration.
- public static func makeConfiguration(_ base: Configuration = Configuration()) -> Configuration {
- var config = base
-
- // An opportunity to add required custom SQL functions or
- // collations, if needed:
- // config.prepareDatabase { db in
- // db.add(function: ...)
- // }
-
- // Log SQL statements if the `SQL_TRACE` environment variable is set.
- // See
- if ProcessInfo.processInfo.environment["SQL_TRACE"] != nil {
- config.prepareDatabase { db in
- db.trace {
- // It's ok to log statements publicly. Sensitive
- // information (statement arguments) are not logged
- // unless config.publicStatementArguments is set
- // (see below).
- os_log("%{public}@", log: sqlLogger, type: .debug, String(describing: $0))
- }
- }
- }
-
-#if DEBUG
- // Protect sensitive information by enabling verbose debugging in
- // DEBUG builds only.
- // See
- config.publicStatementArguments = true
-#endif
-
- return config
- }
-}
-
-// MARK: - Database Migrations
-
-extension AppDatabase {
- /// The DatabaseMigrator that defines the database schema.
- ///
- /// See
- private var migrator: DatabaseMigrator {
- var migrator = DatabaseMigrator()
-
-#if DEBUG
- // Speed up development by nuking the database when migrations change
- // See
- migrator.eraseDatabaseOnSchemaChange = true
-#endif
-
- migrator.registerMigration("createPlayer") { db in
- // Create a table
- // See
- try db.create(table: "player") { t in
- t.autoIncrementedPrimaryKey("id")
- t.column("name", .text).notNull()
- t.column("score", .integer).notNull()
- }
- }
-
- // Migrations for future application versions will be inserted here:
- // migrator.registerMigration(...) { db in
- // ...
- // }
-
- return migrator
- }
-}
-
-// MARK: - Database Access: Writes
-// The write methods execute invariant-preserving database transactions.
-
-extension AppDatabase {
- /// A validation error that prevents some players from being saved into
- /// the database.
- enum ValidationError: LocalizedError {
- case missingName
-
- var errorDescription: String? {
- switch self {
- case .missingName:
- return "Please provide a name"
- }
- }
- }
-
- /// Saves (inserts or updates) a player. When the method returns, the
- /// player is present in the database, and its id is not nil.
- func savePlayer(_ player: inout Player) async throws {
- if player.name.isEmpty {
- throw ValidationError.missingName
- }
- player = try await dbWriter.write { [player] db in
- try player.saved(db)
- }
- }
-
- /// Delete the specified players
- func deletePlayers(ids: [Int64]) async throws {
- try await dbWriter.write { db in
- _ = try Player.deleteAll(db, ids: ids)
- }
- }
-
- /// Delete all players
- func deleteAllPlayers() async throws {
- try await dbWriter.write { db in
- _ = try Player.deleteAll(db)
- }
- }
-
- /// Refresh all players (by performing some random changes, for demo purpose).
- func refreshPlayers() async throws {
- try await dbWriter.write { db in
- if try Player.all().isEmpty(db) {
- // When database is empty, insert new random players
- try createRandomPlayers(db)
- } else {
- // Insert a player
- if Bool.random() {
- _ = try Player.makeRandom().inserted(db) // insert but ignore inserted id
- }
-
- // Delete a random player
- if Bool.random() {
- try Player.order(sql: "RANDOM()").limit(1).deleteAll(db)
- }
-
- // Update some players
- for var player in try Player.fetchAll(db) where Bool.random() {
- try player.updateChanges(db) {
- $0.score = Player.randomScore()
- }
- }
- }
- }
- }
-
- /// Create random players if the database is empty.
- func createRandomPlayersIfEmpty() throws {
- try dbWriter.write { db in
- if try Player.all().isEmpty(db) {
- try createRandomPlayers(db)
- }
- }
- }
-
- private static let uiTestPlayers = [
- Player(id: nil, name: "Arthur", score: 5),
- Player(id: nil, name: "Barbara", score: 6),
- Player(id: nil, name: "Craig", score: 8),
- Player(id: nil, name: "David", score: 4),
- Player(id: nil, name: "Elena", score: 1),
- Player(id: nil, name: "Frederik", score: 2),
- Player(id: nil, name: "Gilbert", score: 7),
- Player(id: nil, name: "Henriette", score: 3)]
-
- func createPlayersForUITests() throws {
- try dbWriter.write { db in
- try AppDatabase.uiTestPlayers.forEach { player in
- _ = try player.inserted(db) // insert but ignore inserted id
- }
- }
- }
-
- /// Support for `createRandomPlayersIfEmpty()` and `refreshPlayers()`.
- private func createRandomPlayers(_ db: Database) throws {
- for _ in 0..<8 {
- _ = try Player.makeRandom().inserted(db) // insert but ignore inserted id
- }
- }
-}
-
-// MARK: - Database Access: Reads
-
-// This demo app does not provide any specific reading method, and instead
-// gives an unrestricted read-only access to the rest of the application.
-// In your app, you are free to choose another path, and define focused
-// reading methods.
-extension AppDatabase {
- /// Provides a read-only access to the database
- var reader: DatabaseReader {
- dbWriter
- }
-}
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/GRDBAsyncDemoApp.swift b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/GRDBAsyncDemoApp.swift
deleted file mode 100644
index 748b10a900..0000000000
--- a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/GRDBAsyncDemoApp.swift
+++ /dev/null
@@ -1,32 +0,0 @@
-import GRDBQuery
-import SwiftUI
-
-@main
-struct GRDBAsyncDemoApp: App {
- var body: some Scene {
- WindowGroup {
- AppView().appDatabase(.shared)
- }
- }
-}
-
-// MARK: - Give SwiftUI access to the database
-
-private struct AppDatabaseKey: EnvironmentKey {
- static var defaultValue: AppDatabase { .empty() }
-}
-
-extension EnvironmentValues {
- var appDatabase: AppDatabase {
- get { self[AppDatabaseKey.self] }
- set { self[AppDatabaseKey.self] = newValue }
- }
-}
-
-extension View {
- func appDatabase(_ appDatabase: AppDatabase) -> some View {
- self
- .environment(\.appDatabase, appDatabase)
- .databaseContext(.readOnly { appDatabase.reader })
- }
-}
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Info.plist b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Info.plist
deleted file mode 100644
index 4754bb8682..0000000000
--- a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Info.plist
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
- CFBundleDevelopmentRegion
- $(DEVELOPMENT_LANGUAGE)
- CFBundleExecutable
- $(EXECUTABLE_NAME)
- CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- $(PRODUCT_NAME)
- CFBundlePackageType
- $(PRODUCT_BUNDLE_PACKAGE_TYPE)
- CFBundleShortVersionString
- 1.0
- CFBundleVersion
- 1
- LSRequiresIPhoneOS
-
- UIApplicationSceneManifest
-
- UIApplicationSupportsMultipleScenes
-
-
- UIApplicationSupportsIndirectInputEvents
-
- UILaunchScreen
-
- UILaunchStoryboardName
- LaunchScreen
- UIRequiredDeviceCapabilities
-
- armv7
-
- UISupportedInterfaceOrientations
-
- UIInterfaceOrientationPortrait
-
- UISupportedInterfaceOrientations~ipad
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
-
-
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/PlayerRequest.swift b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/PlayerRequest.swift
deleted file mode 100644
index 4d1d924605..0000000000
--- a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/PlayerRequest.swift
+++ /dev/null
@@ -1,35 +0,0 @@
-import GRDB
-import GRDBQuery
-
-/// A player request can be used with the `@Query` property wrapper in order to
-/// feed a view with a list of players.
-///
-/// For example:
-///
-/// struct MyView: View {
-/// @Query(PlayerRequest(ordering: .byName)) private var players: [Player]
-///
-/// var body: some View {
-/// List(players) { player in ... )
-/// }
-/// }
-struct PlayerRequest: ValueObservationQueryable {
- enum Ordering {
- case byScore
- case byName
- }
-
- static var defaultValue: [Player] { [] }
-
- /// The ordering used by the player request.
- var ordering: Ordering
-
- func fetch(_ db: Database) throws -> [Player] {
- switch ordering {
- case .byScore:
- return try Player.all().orderedByScore().fetchAll(db)
- case .byName:
- return try Player.all().orderedByName().fetchAll(db)
- }
- }
-}
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
deleted file mode 100644
index 29d91251df..0000000000
--- a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
+++ /dev/null
@@ -1,116 +0,0 @@
-{
- "images" : [
- {
- "size" : "20x20",
- "idiom" : "iphone",
- "filename" : "icon_20pt@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "20x20",
- "idiom" : "iphone",
- "filename" : "icon_20pt@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "29x29",
- "idiom" : "iphone",
- "filename" : "icon_29pt@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "29x29",
- "idiom" : "iphone",
- "filename" : "icon_29pt@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "40x40",
- "idiom" : "iphone",
- "filename" : "icon_40pt@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "40x40",
- "idiom" : "iphone",
- "filename" : "icon_40pt@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "60x60",
- "idiom" : "iphone",
- "filename" : "icon_60pt@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "60x60",
- "idiom" : "iphone",
- "filename" : "icon_60pt@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "20x20",
- "idiom" : "ipad",
- "filename" : "icon_20pt.png",
- "scale" : "1x"
- },
- {
- "size" : "20x20",
- "idiom" : "ipad",
- "filename" : "icon_20pt@2x-1.png",
- "scale" : "2x"
- },
- {
- "size" : "29x29",
- "idiom" : "ipad",
- "filename" : "icon_29pt.png",
- "scale" : "1x"
- },
- {
- "size" : "29x29",
- "idiom" : "ipad",
- "filename" : "icon_29pt@2x-1.png",
- "scale" : "2x"
- },
- {
- "size" : "40x40",
- "idiom" : "ipad",
- "filename" : "icon_40pt.png",
- "scale" : "1x"
- },
- {
- "size" : "40x40",
- "idiom" : "ipad",
- "filename" : "icon_40pt@2x-1.png",
- "scale" : "2x"
- },
- {
- "size" : "76x76",
- "idiom" : "ipad",
- "filename" : "icon_76pt.png",
- "scale" : "1x"
- },
- {
- "size" : "76x76",
- "idiom" : "ipad",
- "filename" : "icon_76pt@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "83.5x83.5",
- "idiom" : "ipad",
- "filename" : "icon_83.5@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "1024x1024",
- "idiom" : "ios-marketing",
- "filename" : "Icon.png",
- "scale" : "1x"
- }
- ],
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
\ No newline at end of file
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/Icon.png b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/Icon.png
deleted file mode 100644
index 66b1931a14..0000000000
Binary files a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/Icon.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt.png b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt.png
deleted file mode 100644
index 90648b3f40..0000000000
Binary files a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x-1.png b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x-1.png
deleted file mode 100644
index a077a6f490..0000000000
Binary files a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x-1.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x.png b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x.png
deleted file mode 100644
index a077a6f490..0000000000
Binary files a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt@3x.png b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt@3x.png
deleted file mode 100644
index 600bdbd9cd..0000000000
Binary files a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt@3x.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt.png b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt.png
deleted file mode 100644
index 8e04af0dd8..0000000000
Binary files a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x-1.png b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x-1.png
deleted file mode 100644
index 686e8d99e2..0000000000
Binary files a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x-1.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x.png b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x.png
deleted file mode 100644
index 686e8d99e2..0000000000
Binary files a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt@3x.png b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt@3x.png
deleted file mode 100644
index 1d013c3d33..0000000000
Binary files a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt@3x.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt.png b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt.png
deleted file mode 100644
index a077a6f490..0000000000
Binary files a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x-1.png b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x-1.png
deleted file mode 100644
index da66b9ba82..0000000000
Binary files a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x-1.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x.png b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x.png
deleted file mode 100644
index da66b9ba82..0000000000
Binary files a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt@3x.png b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt@3x.png
deleted file mode 100644
index 59346ef4b6..0000000000
Binary files a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt@3x.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x.png b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x.png
deleted file mode 100644
index 59346ef4b6..0000000000
Binary files a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x.png b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x.png
deleted file mode 100644
index d4640afc9a..0000000000
Binary files a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_76pt.png b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_76pt.png
deleted file mode 100644
index e3a04522bf..0000000000
Binary files a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_76pt.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_76pt@2x.png b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_76pt@2x.png
deleted file mode 100644
index 593ebd783d..0000000000
Binary files a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_76pt@2x.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x.png b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x.png
deleted file mode 100644
index ca02cd03bc..0000000000
Binary files a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/LaunchIcon.imageset/Contents.json b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/LaunchIcon.imageset/Contents.json
deleted file mode 100644
index 2cbe59d5ec..0000000000
--- a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/LaunchIcon.imageset/Contents.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "images" : [
- {
- "filename" : "LaunchIcon.pdf",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- }
-}
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Base.lproj/LaunchScreen.storyboard b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Base.lproj/LaunchScreen.storyboard
deleted file mode 100644
index 79f85a1dd9..0000000000
--- a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Base.lproj/LaunchScreen.storyboard
+++ /dev/null
@@ -1,42 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/en.lproj/Localizable.stringsdict b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/en.lproj/Localizable.stringsdict
deleted file mode 100644
index 2d9aa217c4..0000000000
--- a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/en.lproj/Localizable.stringsdict
+++ /dev/null
@@ -1,42 +0,0 @@
-
-
-
-
- %lld Players
-
- NSStringLocalizedFormatKey
- %#@VARIABLE@
- VARIABLE
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- lld
- zero
- No Player
- one
- 1 Player
- other
- %lld Players
-
-
- %lld points
-
- NSStringLocalizedFormatKey
- %#@VARIABLE@
- VARIABLE
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- lld
- zero
- 0 point
- one
- 1 point
- other
- %lld points
-
-
-
-
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Views/AppView.swift b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Views/AppView.swift
deleted file mode 100644
index 562b4aee8a..0000000000
--- a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Views/AppView.swift
+++ /dev/null
@@ -1,154 +0,0 @@
-import GRDBQuery
-import SwiftUI
-
-/// The main application view
-struct AppView: View {
- /// Write access to the database
- @Environment(\.appDatabase) private var appDatabase
-
- /// The `players` property is automatically updated when the database changes
- @Query(PlayerRequest(ordering: .byScore)) private var players: [Player]
-
- /// We'll need to leave edit mode in several occasions.
- @State private var editMode = EditMode.inactive
-
- /// Tracks the presentation of the player creation sheet.
- @State private var newPlayerIsPresented = false
-
- // If you want to define the query on initialization, you will prefer:
- //
- // @Query private var players: [Player]
- //
- // init(initialOrdering: PlayerRequest.Ordering) {
- // _players = Query(PlayerRequest(ordering: initialOrdering))
- // }
-
- var body: some View {
- NavigationView {
- PlayerList(players: players)
- .navigationBarTitle(Text("\(players.count) Players"))
- .navigationBarItems(
- leading: HStack {
- EditButton()
- newPlayerButton
- },
- trailing: ToggleOrderingButton(
- ordering: $players.ordering,
- willChange: {
- // onChange(of: $players.wrappedValue.ordering)
- // is not able to leave the editing mode during
- // the animation of the list content.
- // Workaround: stop editing before the ordering
- // is changed, and the list content is updated.
- stopEditing()
- }))
- .toolbar { toolbarContent }
- .onChange(of: players) {
- if players.isEmpty {
- stopEditing()
- }
- }
- .environment(\.editMode, $editMode)
- }
- }
-
- private var toolbarContent: some ToolbarContent {
- ToolbarItemGroup(placement: .bottomBar) {
- Button {
- // Don't stopEditing() here because this is
- // performed `onChange(of: players)`
- Task {
- try? await appDatabase.deleteAllPlayers()
- }
- } label: {
- Image(systemName: "trash").imageScale(.large)
- }
-
- Spacer()
-
- Button {
- stopEditing()
- Task {
- try? await appDatabase.refreshPlayers()
- }
- } label: {
- Image(systemName: "arrow.clockwise").imageScale(.large)
- }
-
- Spacer()
-
- Button {
- stopEditing()
- // Perform 50 refreshes in parallel
- Task {
- try? await withThrowingTaskGroup(of: Void.self) { group in
- for _ in 0..<50 {
- _ = group.addTaskUnlessCancelled {
- try await appDatabase.refreshPlayers()
- }
- }
- try await group.waitForAll()
- }
- }
- } label: {
- Image(systemName: "tornado").imageScale(.large)
- }
- }
- }
-
- /// The button that presents the player creation sheet.
- private var newPlayerButton: some View {
- Button {
- stopEditing()
- newPlayerIsPresented = true
- } label: {
- Image(systemName: "plus")
- }
- .accessibility(label: Text("New Player"))
- .sheet(isPresented: $newPlayerIsPresented) {
- PlayerCreationView()
- }
- }
-
- private func stopEditing() {
- withAnimation {
- editMode = .inactive
- }
- }
-}
-
-private struct ToggleOrderingButton: View {
- @Binding var ordering: PlayerRequest.Ordering
- let willChange: () -> Void
-
- var body: some View {
- switch ordering {
- case .byName:
- Button {
- willChange()
- ordering = .byScore
- } label: {
- Label("Name", systemImage: "arrowtriangle.up.fill").labelStyle(.titleAndIcon)
- }
- case .byScore:
- Button {
- willChange()
- ordering = .byName
- } label: {
- Label("Score", systemImage: "arrowtriangle.down.fill").labelStyle(.titleAndIcon)
- }
- }
- }
-}
-
-// MARK: - Previews
-
-#Preview("Empty") {
- // Preview the default, empty database
- AppView()
-}
-
-#Preview("Populated") {
- // Preview a database of random players
- AppView().appDatabase(.random())
-}
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Views/PlayerCreationView.swift b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Views/PlayerCreationView.swift
deleted file mode 100644
index a47fdd04df..0000000000
--- a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Views/PlayerCreationView.swift
+++ /dev/null
@@ -1,50 +0,0 @@
-import SwiftUI
-
-/// The view that creates a new player.
-struct PlayerCreationView: View {
- /// Write access to the database
- @Environment(\.appDatabase) private var appDatabase
- @Environment(\.dismiss) private var dismiss
- @State private var form = PlayerForm(name: "", score: "")
- @State private var errorAlertIsPresented = false
- @State private var errorAlertTitle = ""
-
- var body: some View {
- NavigationView {
- PlayerFormView(form: $form)
- .alert(
- isPresented: $errorAlertIsPresented,
- content: { Alert(title: Text(errorAlertTitle)) })
- .navigationBarTitle("New Player")
- .navigationBarItems(
- leading: Button(role: .cancel) {
- dismiss()
- } label: {
- Text("Cancel")
- },
- trailing: Button {
- Task { await save() }
- } label: {
- Text("Save")
- })
- }
- }
-
- private func save() async {
- do {
- var player = Player(id: nil, name: "", score: 0)
- form.apply(to: &player)
- try await appDatabase.savePlayer(&player)
- dismiss()
- } catch {
- errorAlertTitle = (error as? LocalizedError)?.errorDescription ?? "An error occurred"
- errorAlertIsPresented = true
- }
- }
-}
-
-// MARK: - Previews
-
-#Preview {
- PlayerCreationView()
-}
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Views/PlayerEditionView.swift b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Views/PlayerEditionView.swift
deleted file mode 100644
index 1ab97677dc..0000000000
--- a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Views/PlayerEditionView.swift
+++ /dev/null
@@ -1,40 +0,0 @@
-import SwiftUI
-
-/// The view that edits an existing player.
-struct PlayerEditionView: View {
- /// Write access to the database
- @Environment(\.appDatabase) private var appDatabase
- @Environment(\.isPresented) private var isPresented
- private let player: Player
- @State private var form: PlayerForm
-
- init(player: Player) {
- self.player = player
- self.form = PlayerForm(player)
- }
-
- var body: some View {
- PlayerFormView(form: $form)
- .onChange(of: isPresented) {
- // Save when back button is pressed
- if !isPresented {
- Task {
- var savedPlayer = player
- form.apply(to: &savedPlayer)
- // Ignore error because I don't know how to cancel the
- // back button and present the error
- try? await appDatabase.savePlayer(&savedPlayer)
- }
- }
- }
- }
-}
-
-// MARK: - Previews
-
-#Preview {
- NavigationView {
- PlayerEditionView(player: Player.makeRandom())
- .navigationBarTitle("Player Edition")
- }
-}
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Views/PlayerFormView.swift b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Views/PlayerFormView.swift
deleted file mode 100644
index 590506d465..0000000000
--- a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Views/PlayerFormView.swift
+++ /dev/null
@@ -1,48 +0,0 @@
-import SwiftUI
-
-/// The Player editing form, embedded in both
-/// `PlayerCreationView` and `PlayerEditionView`.
-struct PlayerFormView: View {
- @Binding var form: PlayerForm
-
- var body: some View {
- List {
- TextField("Name", text: $form.name)
- .accessibility(label: Text("Player Name"))
- TextField("Score", text: $form.score).keyboardType(.numberPad)
- .accessibility(label: Text("Player Score"))
- }
- .listStyle(InsetGroupedListStyle())
- }
-}
-
-struct PlayerForm {
- var name: String
- var score: String
-}
-
-extension PlayerForm {
- init(_ player: Player) {
- self.name = player.name
- self.score = "\(player.score)"
- }
-
- func apply(to player: inout Player) {
- player.name = name
- player.score = Int(score) ?? 0
- }
-}
-
-// MARK: - Previews
-
-#Preview("Empty") {
- PlayerFormView(form: .constant(PlayerForm(
- name: "",
- score: "")))
-}
-
-#Preview("Prefilled") {
- PlayerFormView(form: .constant(PlayerForm(
- name: Player.randomName(),
- score: "\(Player.randomScore())")))
-}
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Views/PlayerList.swift b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Views/PlayerList.swift
deleted file mode 100644
index f156f786c0..0000000000
--- a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Views/PlayerList.swift
+++ /dev/null
@@ -1,59 +0,0 @@
-import SwiftUI
-
-struct PlayerList: View {
- /// Write access to the database
- @Environment(\.appDatabase) private var appDatabase
-
- /// The players in the list
- var players: [Player]
-
- var body: some View {
- List {
- ForEach(players) { player in
- NavigationLink(destination: editionView(for: player)) {
- PlayerRow(player: player)
- // Don't animate player update
- .animation(nil, value: player)
- }
- }
- .onDelete { offsets in
- let playerIds = offsets.compactMap { players[$0].id }
- Task {
- try? await appDatabase.deletePlayers(ids: playerIds)
- }
- }
- }
- // Animate list updates
- .animation(.default, value: players)
- .listStyle(.plain)
- }
-
- /// The view that edits a player in the list.
- private func editionView(for player: Player) -> some View {
- PlayerEditionView(player: player).navigationBarTitle(player.name)
- }
-}
-
-private struct PlayerRow: View {
- var player: Player
-
- var body: some View {
- HStack {
- Text(player.name)
- Spacer()
- Text("\(player.score) points").foregroundColor(.gray)
- }
- }
-}
-
-// MARK: - Previews
-
-#Preview {
- NavigationView {
- PlayerList(players: [
- Player(id: 1, name: "Arthur", score: 100),
- Player(id: 2, name: "Barbara", score: 1000),
- ])
- .navigationTitle("Preview")
- }
-}
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemoTests/AppDatabaseTests.swift b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemoTests/AppDatabaseTests.swift
deleted file mode 100644
index b736e90774..0000000000
--- a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemoTests/AppDatabaseTests.swift
+++ /dev/null
@@ -1,144 +0,0 @@
-import XCTest
-import GRDB
-@testable import GRDBAsyncDemo
-
-class AppDatabaseTests: XCTestCase {
- func test_database_schema() throws {
- // Given an empty database
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
-
- // When we instantiate an AppDatabase
- _ = try AppDatabase(dbQueue)
-
- // Then the player table exists, with id, name & score columns
- try dbQueue.read { db in
- try XCTAssert(db.tableExists("player"))
- let columns = try db.columns(in: "player")
- let columnNames = Set(columns.map { $0.name })
- XCTAssertEqual(columnNames, ["id", "name", "score"])
- }
- }
-
- func test_savePlayer_inserts() async throws {
- // Given an empty players database
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- let appDatabase = try AppDatabase(dbQueue)
-
- // When we save a new player
- var player = Player(id: nil, name: "Arthur", score: 100)
- try await appDatabase.savePlayer(&player)
-
- // Then the player exists in the database
- let playerExists = try await dbQueue.read { [player] in try player.exists($0) }
- XCTAssertTrue(playerExists)
- }
-
- func test_savePlayer_updates() async throws {
- // Given a players database that contains a player
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- let appDatabase = try AppDatabase(dbQueue)
- var player = try await dbQueue.write { db in
- try Player(id: nil, name: "Arthur", score: 100).inserted(db)
- }
-
- // When we modify and save the player
- player.name = "Barbara"
- player.score = 1000
- try await appDatabase.savePlayer(&player)
-
- // Then the player has been updated in the database
- let fetchedPlayer = try await dbQueue.read { [player] db in
- try XCTUnwrap(Player.fetchOne(db, key: player.id))
- }
- XCTAssertEqual(fetchedPlayer, player)
- }
-
- func test_deletePlayers() async throws {
- // Given a players database that contains four players
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- let appDatabase = try AppDatabase(dbQueue)
- let playerIds: [Int64] = try await dbQueue.write { db in
- _ = try Player(id: nil, name: "Arthur", score: 100).inserted(db)
- _ = try Player(id: nil, name: "Barbara", score: 200).inserted(db)
- _ = try Player(id: nil, name: "Craig", score: 150).inserted(db)
- _ = try Player(id: nil, name: "David", score: 120).inserted(db)
- return try Player.selectPrimaryKey().fetchAll(db)
- }
-
- // When we delete two players
- let deletedId1 = playerIds[0]
- let deletedId2 = playerIds[2]
- try await appDatabase.deletePlayers(ids: [deletedId1, deletedId2])
-
- // Then the deleted players no longer exist
- try await dbQueue.read { db in
- try XCTAssertFalse(Player.exists(db, id: deletedId1))
- try XCTAssertFalse(Player.exists(db, id: deletedId2))
- }
-
- // Then the database still contains two players
- let count = try await dbQueue.read { try Player.fetchCount($0) }
- XCTAssertEqual(count, 2)
- }
-
- func test_deleteAllPlayers() async throws {
- // Given a players database that contains players
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- let appDatabase = try AppDatabase(dbQueue)
- try await dbQueue.write { db in
- _ = try Player(id: nil, name: "Arthur", score: 100).inserted(db)
- _ = try Player(id: nil, name: "Barbara", score: 200).inserted(db)
- _ = try Player(id: nil, name: "Craig", score: 150).inserted(db)
- _ = try Player(id: nil, name: "David", score: 120).inserted(db)
- }
-
- // When we delete all players
- try await appDatabase.deleteAllPlayers()
-
- // Then the database does not contain any player
- let count = try await dbQueue.read { try Player.fetchCount($0) }
- XCTAssertEqual(count, 0)
- }
-
- func test_refreshPlayers_populates_an_empty_database() async throws {
- // Given an empty players database
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- let appDatabase = try AppDatabase(dbQueue)
-
- // When we refresh players
- try await appDatabase.refreshPlayers()
-
- // Then the database is not empty
- let count = try await dbQueue.read { try Player.fetchCount($0) }
- XCTAssert(count > 0)
- }
-
- func test_createRandomPlayersIfEmpty_populates_an_empty_database() throws {
- // Given an empty players database
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- let appDatabase = try AppDatabase(dbQueue)
-
- // When we create random players
- try appDatabase.createRandomPlayersIfEmpty()
-
- // Then the database is not empty
- try XCTAssert(dbQueue.read(Player.fetchCount) > 0)
- }
-
- func test_createRandomPlayersIfEmpty_does_not_modify_a_non_empty_database() throws {
- // Given a players database that contains one player
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- let appDatabase = try AppDatabase(dbQueue)
- var player = Player(id: nil, name: "Arthur", score: 100)
- try dbQueue.write { db in
- try player.insert(db)
- }
-
- // When we create random players
- try appDatabase.createRandomPlayersIfEmpty()
-
- // Then the database still only contains the original player
- let players = try dbQueue.read(Player.fetchAll)
- XCTAssertEqual(players, [player])
- }
-}
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemoTests/Info.plist b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemoTests/Info.plist
deleted file mode 100644
index 64d65ca495..0000000000
--- a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemoTests/Info.plist
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
- CFBundleDevelopmentRegion
- $(DEVELOPMENT_LANGUAGE)
- CFBundleExecutable
- $(EXECUTABLE_NAME)
- CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- $(PRODUCT_NAME)
- CFBundlePackageType
- $(PRODUCT_BUNDLE_PACKAGE_TYPE)
- CFBundleShortVersionString
- 1.0
- CFBundleVersion
- 1
-
-
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemoTests/PlayerRequestTests.swift b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemoTests/PlayerRequestTests.swift
deleted file mode 100644
index 71d82ab409..0000000000
--- a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemoTests/PlayerRequestTests.swift
+++ /dev/null
@@ -1,43 +0,0 @@
-import XCTest
-import GRDB
-@testable import GRDBAsyncDemo
-
-class PlayerRequestTests: XCTestCase {
- func test_PlayerRequest_byName_fetches_well_ordered_players() throws {
- // Given a players database that contains two players
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- _ = try AppDatabase(dbQueue)
- var player1 = Player(id: nil, name: "Arthur", score: 100)
- var player2 = Player(id: nil, name: "Barbara", score: 1000)
- try dbQueue.write { db in
- try player1.insert(db)
- try player2.insert(db)
- }
-
- // When we fetch players ordered by name
- let playerRequest = PlayerRequest(ordering: .byName)
- let players = try dbQueue.read(playerRequest.fetch)
-
- // Then the players are the two players ordered by name
- XCTAssertEqual(players, [player1, player2])
- }
-
- func test_PlayerRequest_byScore_fetches_well_ordered_players() throws {
- // Given a players database that contains two players
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- _ = try AppDatabase(dbQueue)
- var player1 = Player(id: nil, name: "Arthur", score: 100)
- var player2 = Player(id: nil, name: "Barbara", score: 1000)
- try dbQueue.write { db in
- try player1.insert(db)
- try player2.insert(db)
- }
-
- // When we fetch players ordered by score
- let playerRequest = PlayerRequest(ordering: .byScore)
- let players = try dbQueue.read(playerRequest.fetch)
-
- // Then the players are the two players ordered by score descending
- XCTAssertEqual(players, [player2, player1])
- }
-}
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemoTests/PlayerTests.swift b/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemoTests/PlayerTests.swift
deleted file mode 100644
index edd8726845..0000000000
--- a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemoTests/PlayerTests.swift
+++ /dev/null
@@ -1,108 +0,0 @@
-import XCTest
-import GRDB
-@testable import GRDBAsyncDemo
-
-class PlayerTests: XCTestCase {
- // MARK: - CRUD
- // Test that our Player type properly talks to GRDB.
-
- func testInsert() throws {
- // Given an empty players database
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- _ = try AppDatabase(dbQueue)
-
- // When we insert a player
- var player = Player(id: nil, name: "Arthur", score: 100)
- try dbQueue.write { db in
- try player.insert(db)
- }
-
- // Then the player gets a non-nil id
- XCTAssertNotNil(player.id)
- }
-
- func testRoundtrip() throws {
- // Given an empty players database
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- _ = try AppDatabase(dbQueue)
-
- // When we insert a player and fetch the player with the same id
- var insertedPlayer = Player(id: nil, name: "Arthur", score: 100)
- let fetchedPlayer: Player? = try dbQueue.write { db in
- try insertedPlayer.insert(db)
- return try Player.fetchOne(db, key: insertedPlayer.id)
- }
-
- // Then the fetched player is equal to the inserted player
- XCTAssertEqual(insertedPlayer, fetchedPlayer)
- }
-
- // MARK: - Requests
- // Test that requests defined on the Player type behave as expected.
-
- func testOrderedByScore() throws {
- // Given a players database that contains players with distinct scores
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- _ = try AppDatabase(dbQueue)
- var player1 = Player(id: 1, name: "Arthur", score: 100)
- var player2 = Player(id: 2, name: "Barbara", score: 200)
- var player3 = Player(id: 3, name: "Craig", score: 150)
- var player4 = Player(id: 4, name: "David", score: 120)
- try dbQueue.write { db in
- try player1.insert(db)
- try player2.insert(db)
- try player3.insert(db)
- try player4.insert(db)
- }
-
- // When we fetch players ordered by score
- let players = try dbQueue.read(Player.all().orderedByScore().fetchAll)
-
- // Then fetched players are ordered by score descending
- XCTAssertEqual(players, [player2, player3, player4, player1])
- }
-
- func testOrderedByScoreSortsIdenticalScoresByName() throws {
- // Given a players database that contains players with common scores
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- _ = try AppDatabase(dbQueue)
- var player1 = Player(id: 1, name: "Arthur", score: 100)
- var player2 = Player(id: 2, name: "Barbara", score: 200)
- var player3 = Player(id: 3, name: "Craig", score: 200)
- var player4 = Player(id: 4, name: "David", score: 200)
- try dbQueue.write { db in
- try player1.insert(db)
- try player2.insert(db)
- try player3.insert(db)
- try player4.insert(db)
- }
-
- // When we fetch players ordered by score
- let players = try dbQueue.read(Player.all().orderedByScore().fetchAll)
-
- // Then fetched players are ordered by score descending and by name
- XCTAssertEqual(players, [player2, player3, player4, player1])
- }
-
- func testOrderedByName() throws {
- // Given a players database that contains players with distinct names
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- _ = try AppDatabase(dbQueue)
- var player1 = Player(id: 1, name: "Arthur", score: 100)
- var player2 = Player(id: 2, name: "Barbara", score: 200)
- var player3 = Player(id: 3, name: "Craig", score: 150)
- var player4 = Player(id: 4, name: "David", score: 120)
- try dbQueue.write { db in
- try player1.insert(db)
- try player2.insert(db)
- try player3.insert(db)
- try player4.insert(db)
- }
-
- // When we fetch players ordered by name
- let players = try dbQueue.read(Player.all().orderedByName().fetchAll)
-
- // Then fetched players are ordered by name
- XCTAssertEqual(players, [player1, player2, player3, player4])
- }
-}
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/README.md b/Documentation/DemoApps/GRDBAsyncDemo/README.md
deleted file mode 100644
index 79389fdb21..0000000000
--- a/Documentation/DemoApps/GRDBAsyncDemo/README.md
+++ /dev/null
@@ -1,52 +0,0 @@
-Async/Await + SwiftUI Demo Application
-======================================
-
-
-
-**This demo application is an Async/Await + SwiftUI application.** For a demo application that uses UIKit, see [GRDBDemoiOS](../GRDBDemoiOS/README.md), and for Combine + SwiftUI, see [GRDBCombineDemo](../GRDBCombineDemo/README.md).
-
-**Requirements**: iOS 15.0+ / Xcode 13.1+
-
-> **Note**: This demo app is not a project template. Do not copy it as a starting point for your application. Instead, create a new project, choose a GRDB [installation method](../../../README.md#installation), and use the demo as an inspiration.
-
-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 [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:**
-
-- [GRDBAsyncDemoApp.swift](GRDBAsyncDemo/GRDBAsyncDemoApp.swift)
-
- `GRDBAsyncDemoApp` feeds the app views with a database, through the SwiftUI environment.
-
-- [AppDatabase.swift](GRDBAsyncDemo/AppDatabase.swift)
-
- `AppDatabase` is the type that grants database access. It uses [DatabaseMigrator](https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databasemigrator) in order to setup the database schema.
-
-- [Persistence.swift](GRDBAsyncDemo/Persistence.swift)
-
- This file instantiates various `AppDatabase` for the various projects needs: one database on disk for the application, and in-memory databases for SwiftUI previews.
-
-- [Player.swift](GRDBAsyncDemo/Player.swift)
-
- `Player` is a [Record](../../../README.md#records) type, able to read and write in the database. It conforms to the standard Codable protocol in order to gain all advantages of [Codable Records](../../../README.md#codable-records).
-
-- [PlayerRequest.swift](GRDBAsyncDemo/PlayerRequest.swift), [AppView.swift](GRDBAsyncDemo/Views/AppView.swift)
-
- `PlayerRequest` defines the player requests used by the app (sorted by score, or by name).
-
- `PlayerRequest` feeds the `@Query` property wrapper (`@Query`, defined in [GRDBQuery](https://github.com/groue/GRDBQuery), allows SwiftUI views to display up-to-date database content).
-
- `AppView` is the SwiftUI view that uses `@Query` in order to feed its player list.
-
-- [GRDBAsyncDemoTests](GRDBAsyncDemoTests)
-
- - Test the database schema
- - Test the `Player` record and its requests
- - Test the `PlayerRequest` methods that feed the list of players.
- - Test the `AppDatabase` methods that let the app access the database.
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/Screenshot.png b/Documentation/DemoApps/GRDBAsyncDemo/Screenshot.png
deleted file mode 100644
index 0536821854..0000000000
Binary files a/Documentation/DemoApps/GRDBAsyncDemo/Screenshot.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo.xcodeproj/project.pbxproj b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo.xcodeproj/project.pbxproj
deleted file mode 100644
index f1892a540c..0000000000
--- a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo.xcodeproj/project.pbxproj
+++ /dev/null
@@ -1,611 +0,0 @@
-// !$*UTF8*$!
-{
- archiveVersion = 1;
- classes = {
- };
- objectVersion = 54;
- objects = {
-
-/* Begin PBXBuildFile section */
- 56026CAC25B8A7EF00D1DF3F /* PlayerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56026CAA25B8A7EF00D1DF3F /* PlayerTests.swift */; };
- 56026CAD25B8A7EF00D1DF3F /* AppDatabaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56026CAB25B8A7EF00D1DF3F /* AppDatabaseTests.swift */; };
- 56519DC7274FC85600ED16D8 /* GRDBQuery in Frameworks */ = {isa = PBXBuildFile; productRef = 56519DC6274FC85600ED16D8 /* GRDBQuery */; };
- 5671723A261B23C800423B6F /* PlayerList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56717239261B23C800423B6F /* PlayerList.swift */; };
- 56717252261B334D00423B6F /* PlayerRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56717251261B334D00423B6F /* PlayerRequestTests.swift */; };
- 567C3E1A2520B6DE0011F6E9 /* GRDBCombineDemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567C3E192520B6DE0011F6E9 /* GRDBCombineDemoApp.swift */; };
- 567C3E1E2520B6DF0011F6E9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 567C3E1D2520B6DF0011F6E9 /* Assets.xcassets */; };
- 567C3E212520B6DF0011F6E9 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 567C3E202520B6DF0011F6E9 /* Preview Assets.xcassets */; };
- 567C3E5D2520B75C0011F6E9 /* Player.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567C3E532520B75C0011F6E9 /* Player.swift */; };
- 567C3E5E2520B75C0011F6E9 /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567C3E542520B75C0011F6E9 /* Persistence.swift */; };
- 567C3E612520B75D0011F6E9 /* PlayerFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567C3E592520B75C0011F6E9 /* PlayerFormView.swift */; };
- 567C3E622520B75D0011F6E9 /* AppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567C3E5A2520B75C0011F6E9 /* AppView.swift */; };
- 567C3E632520B75D0011F6E9 /* PlayerCreationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567C3E5B2520B75C0011F6E9 /* PlayerCreationView.swift */; };
- 567C3E642520B75D0011F6E9 /* PlayerEditionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567C3E5C2520B75C0011F6E9 /* PlayerEditionView.swift */; };
- 567C3E662520B7880011F6E9 /* AppDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567C3E652520B7880011F6E9 /* AppDatabase.swift */; };
- 567C3E792520BB650011F6E9 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 567C3E752520BB650011F6E9 /* Localizable.stringsdict */; };
- 567C3E7A2520BB650011F6E9 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 567C3E772520BB650011F6E9 /* LaunchScreen.storyboard */; };
- 56B6D1092619EC1B003CC455 /* PlayerRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B6D1082619EC1B003CC455 /* PlayerRequest.swift */; };
- 56F8A1202735989D0011ACBE /* GRDB in Frameworks */ = {isa = PBXBuildFile; productRef = 56F8A11F2735989D0011ACBE /* GRDB */; };
-/* End PBXBuildFile section */
-
-/* Begin PBXContainerItemProxy section */
- 56026C9D25B8A7D000D1DF3F /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 567C3E0E2520B6DE0011F6E9 /* Project object */;
- proxyType = 1;
- remoteGlobalIDString = 567C3E152520B6DE0011F6E9;
- remoteInfo = GRDBCombineDemo;
- };
-/* End PBXContainerItemProxy section */
-
-/* Begin PBXCopyFilesBuildPhase section */
- 567C3E502520B70E0011F6E9 /* Embed Frameworks */ = {
- isa = PBXCopyFilesBuildPhase;
- buildActionMask = 2147483647;
- dstPath = "";
- dstSubfolderSpec = 10;
- files = (
- );
- name = "Embed Frameworks";
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXCopyFilesBuildPhase section */
-
-/* Begin PBXFileReference section */
- 56026C9825B8A7D000D1DF3F /* GRDBCombineDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GRDBCombineDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
- 56026C9C25B8A7D000D1DF3F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- 56026CAA25B8A7EF00D1DF3F /* PlayerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerTests.swift; sourceTree = ""; };
- 56026CAB25B8A7EF00D1DF3F /* AppDatabaseTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDatabaseTests.swift; sourceTree = ""; };
- 56717239261B23C800423B6F /* PlayerList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerList.swift; sourceTree = ""; };
- 56717251261B334D00423B6F /* PlayerRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerRequestTests.swift; sourceTree = ""; };
- 567C3E162520B6DE0011F6E9 /* GRDBCombineDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GRDBCombineDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
- 567C3E192520B6DE0011F6E9 /* GRDBCombineDemoApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GRDBCombineDemoApp.swift; sourceTree = ""; };
- 567C3E1D2520B6DF0011F6E9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
- 567C3E202520B6DF0011F6E9 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
- 567C3E222520B6DF0011F6E9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- 567C3E532520B75C0011F6E9 /* Player.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Player.swift; sourceTree = ""; };
- 567C3E542520B75C0011F6E9 /* Persistence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = ""; };
- 567C3E592520B75C0011F6E9 /* PlayerFormView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerFormView.swift; sourceTree = ""; };
- 567C3E5A2520B75C0011F6E9 /* AppView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppView.swift; sourceTree = ""; };
- 567C3E5B2520B75C0011F6E9 /* PlayerCreationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerCreationView.swift; sourceTree = ""; };
- 567C3E5C2520B75C0011F6E9 /* PlayerEditionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerEditionView.swift; sourceTree = ""; };
- 567C3E652520B7880011F6E9 /* AppDatabase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDatabase.swift; sourceTree = ""; };
- 567C3E762520BB650011F6E9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; };
- 567C3E782520BB650011F6E9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
- 56B6D1082619EC1B003CC455 /* PlayerRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerRequest.swift; sourceTree = ""; };
- 56F8A11C2735988F0011ACBE /* GRDB.swift */ = {isa = PBXFileReference; lastKnownFileType = folder; name = GRDB.swift; path = ../../..; sourceTree = ""; };
-/* End PBXFileReference section */
-
-/* Begin PBXFrameworksBuildPhase section */
- 56026C9525B8A7D000D1DF3F /* Frameworks */ = {
- isa = PBXFrameworksBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 567C3E132520B6DE0011F6E9 /* Frameworks */ = {
- isa = PBXFrameworksBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 56519DC7274FC85600ED16D8 /* GRDBQuery in Frameworks */,
- 56F8A1202735989D0011ACBE /* GRDB in Frameworks */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXFrameworksBuildPhase section */
-
-/* Begin PBXGroup section */
- 56026C9925B8A7D000D1DF3F /* GRDBCombineDemoTests */ = {
- isa = PBXGroup;
- children = (
- 56026C9C25B8A7D000D1DF3F /* Info.plist */,
- 56026CAB25B8A7EF00D1DF3F /* AppDatabaseTests.swift */,
- 56717251261B334D00423B6F /* PlayerRequestTests.swift */,
- 56026CAA25B8A7EF00D1DF3F /* PlayerTests.swift */,
- );
- path = GRDBCombineDemoTests;
- sourceTree = "";
- };
- 56185BC125B8047D00B9C30F /* Resources */ = {
- isa = PBXGroup;
- children = (
- 567C3E1D2520B6DF0011F6E9 /* Assets.xcassets */,
- 567C3E772520BB650011F6E9 /* LaunchScreen.storyboard */,
- 567C3E752520BB650011F6E9 /* Localizable.stringsdict */,
- );
- path = Resources;
- sourceTree = "";
- };
- 567C3E0D2520B6DE0011F6E9 = {
- isa = PBXGroup;
- children = (
- 567C3E182520B6DE0011F6E9 /* GRDBCombineDemo */,
- 56026C9925B8A7D000D1DF3F /* GRDBCombineDemoTests */,
- 567C3E172520B6DE0011F6E9 /* Products */,
- 567C3E4D2520B70E0011F6E9 /* Frameworks */,
- 56F8A11C2735988F0011ACBE /* GRDB.swift */,
- );
- sourceTree = "";
- };
- 567C3E172520B6DE0011F6E9 /* Products */ = {
- isa = PBXGroup;
- children = (
- 567C3E162520B6DE0011F6E9 /* GRDBCombineDemo.app */,
- 56026C9825B8A7D000D1DF3F /* GRDBCombineDemoTests.xctest */,
- );
- name = Products;
- sourceTree = "";
- };
- 567C3E182520B6DE0011F6E9 /* GRDBCombineDemo */ = {
- isa = PBXGroup;
- children = (
- 567C3E222520B6DF0011F6E9 /* Info.plist */,
- 567C3E652520B7880011F6E9 /* AppDatabase.swift */,
- 567C3E192520B6DE0011F6E9 /* GRDBCombineDemoApp.swift */,
- 567C3E542520B75C0011F6E9 /* Persistence.swift */,
- 567C3E532520B75C0011F6E9 /* Player.swift */,
- 56B6D1082619EC1B003CC455 /* PlayerRequest.swift */,
- 567C3E1F2520B6DF0011F6E9 /* Preview Content */,
- 56185BC125B8047D00B9C30F /* Resources */,
- 567C3E582520B75C0011F6E9 /* Views */,
- );
- path = GRDBCombineDemo;
- sourceTree = "";
- };
- 567C3E1F2520B6DF0011F6E9 /* Preview Content */ = {
- isa = PBXGroup;
- children = (
- 567C3E202520B6DF0011F6E9 /* Preview Assets.xcassets */,
- );
- path = "Preview Content";
- sourceTree = "";
- };
- 567C3E4D2520B70E0011F6E9 /* Frameworks */ = {
- isa = PBXGroup;
- children = (
- );
- name = Frameworks;
- sourceTree = "";
- };
- 567C3E582520B75C0011F6E9 /* Views */ = {
- isa = PBXGroup;
- children = (
- 567C3E5A2520B75C0011F6E9 /* AppView.swift */,
- 567C3E5B2520B75C0011F6E9 /* PlayerCreationView.swift */,
- 567C3E5C2520B75C0011F6E9 /* PlayerEditionView.swift */,
- 567C3E592520B75C0011F6E9 /* PlayerFormView.swift */,
- 56717239261B23C800423B6F /* PlayerList.swift */,
- );
- path = Views;
- sourceTree = "";
- };
-/* End PBXGroup section */
-
-/* Begin PBXNativeTarget section */
- 56026C9725B8A7D000D1DF3F /* GRDBCombineDemoTests */ = {
- isa = PBXNativeTarget;
- buildConfigurationList = 56026CA825B8A7D000D1DF3F /* Build configuration list for PBXNativeTarget "GRDBCombineDemoTests" */;
- buildPhases = (
- 56026C9425B8A7D000D1DF3F /* Sources */,
- 56026C9525B8A7D000D1DF3F /* Frameworks */,
- 56026C9625B8A7D000D1DF3F /* Resources */,
- );
- buildRules = (
- );
- dependencies = (
- 56026C9E25B8A7D000D1DF3F /* PBXTargetDependency */,
- );
- name = GRDBCombineDemoTests;
- productName = GRDBCombineDemoTests;
- productReference = 56026C9825B8A7D000D1DF3F /* GRDBCombineDemoTests.xctest */;
- productType = "com.apple.product-type.bundle.unit-test";
- };
- 567C3E152520B6DE0011F6E9 /* GRDBCombineDemo */ = {
- isa = PBXNativeTarget;
- buildConfigurationList = 567C3E252520B6DF0011F6E9 /* Build configuration list for PBXNativeTarget "GRDBCombineDemo" */;
- buildPhases = (
- 567C3E122520B6DE0011F6E9 /* Sources */,
- 567C3E132520B6DE0011F6E9 /* Frameworks */,
- 567C3E142520B6DE0011F6E9 /* Resources */,
- 567C3E502520B70E0011F6E9 /* Embed Frameworks */,
- );
- buildRules = (
- );
- dependencies = (
- 56F8A11E273598960011ACBE /* PBXTargetDependency */,
- );
- name = GRDBCombineDemo;
- packageProductDependencies = (
- 56F8A11F2735989D0011ACBE /* GRDB */,
- 56519DC6274FC85600ED16D8 /* GRDBQuery */,
- );
- productName = GRBCombineDemo;
- productReference = 567C3E162520B6DE0011F6E9 /* GRDBCombineDemo.app */;
- productType = "com.apple.product-type.application";
- };
-/* End PBXNativeTarget section */
-
-/* Begin PBXProject section */
- 567C3E0E2520B6DE0011F6E9 /* Project object */ = {
- isa = PBXProject;
- attributes = {
- BuildIndependentTargetsInParallel = YES;
- LastSwiftUpdateCheck = 1250;
- LastUpgradeCheck = 1200;
- TargetAttributes = {
- 56026C9725B8A7D000D1DF3F = {
- CreatedOnToolsVersion = 12.3;
- TestTargetID = 567C3E152520B6DE0011F6E9;
- };
- 567C3E152520B6DE0011F6E9 = {
- CreatedOnToolsVersion = 12.0;
- };
- };
- };
- buildConfigurationList = 567C3E112520B6DE0011F6E9 /* Build configuration list for PBXProject "GRDBCombineDemo" */;
- compatibilityVersion = "Xcode 12.0";
- developmentRegion = en;
- hasScannedForEncodings = 0;
- knownRegions = (
- en,
- Base,
- );
- mainGroup = 567C3E0D2520B6DE0011F6E9;
- packageReferences = (
- 56519DC5274FC85600ED16D8 /* XCRemoteSwiftPackageReference "GRDBQuery" */,
- );
- productRefGroup = 567C3E172520B6DE0011F6E9 /* Products */;
- projectDirPath = "";
- projectRoot = "";
- targets = (
- 567C3E152520B6DE0011F6E9 /* GRDBCombineDemo */,
- 56026C9725B8A7D000D1DF3F /* GRDBCombineDemoTests */,
- );
- };
-/* End PBXProject section */
-
-/* Begin PBXResourcesBuildPhase section */
- 56026C9625B8A7D000D1DF3F /* Resources */ = {
- isa = PBXResourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 567C3E142520B6DE0011F6E9 /* Resources */ = {
- isa = PBXResourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 567C3E212520B6DF0011F6E9 /* Preview Assets.xcassets in Resources */,
- 567C3E7A2520BB650011F6E9 /* LaunchScreen.storyboard in Resources */,
- 567C3E1E2520B6DF0011F6E9 /* Assets.xcassets in Resources */,
- 567C3E792520BB650011F6E9 /* Localizable.stringsdict in Resources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXResourcesBuildPhase section */
-
-/* Begin PBXSourcesBuildPhase section */
- 56026C9425B8A7D000D1DF3F /* Sources */ = {
- isa = PBXSourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 56026CAC25B8A7EF00D1DF3F /* PlayerTests.swift in Sources */,
- 56026CAD25B8A7EF00D1DF3F /* AppDatabaseTests.swift in Sources */,
- 56717252261B334D00423B6F /* PlayerRequestTests.swift in Sources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 567C3E122520B6DE0011F6E9 /* Sources */ = {
- isa = PBXSourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 567C3E5E2520B75C0011F6E9 /* Persistence.swift in Sources */,
- 567C3E5D2520B75C0011F6E9 /* Player.swift in Sources */,
- 56B6D1092619EC1B003CC455 /* PlayerRequest.swift in Sources */,
- 5671723A261B23C800423B6F /* PlayerList.swift in Sources */,
- 567C3E612520B75D0011F6E9 /* PlayerFormView.swift in Sources */,
- 567C3E632520B75D0011F6E9 /* PlayerCreationView.swift in Sources */,
- 567C3E662520B7880011F6E9 /* AppDatabase.swift in Sources */,
- 567C3E622520B75D0011F6E9 /* AppView.swift in Sources */,
- 567C3E642520B75D0011F6E9 /* PlayerEditionView.swift in Sources */,
- 567C3E1A2520B6DE0011F6E9 /* GRDBCombineDemoApp.swift in Sources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXSourcesBuildPhase section */
-
-/* Begin PBXTargetDependency section */
- 56026C9E25B8A7D000D1DF3F /* PBXTargetDependency */ = {
- isa = PBXTargetDependency;
- target = 567C3E152520B6DE0011F6E9 /* GRDBCombineDemo */;
- targetProxy = 56026C9D25B8A7D000D1DF3F /* PBXContainerItemProxy */;
- };
- 56F8A11E273598960011ACBE /* PBXTargetDependency */ = {
- isa = PBXTargetDependency;
- productRef = 56F8A11D273598960011ACBE /* GRDB */;
- };
-/* End PBXTargetDependency section */
-
-/* Begin PBXVariantGroup section */
- 567C3E752520BB650011F6E9 /* Localizable.stringsdict */ = {
- isa = PBXVariantGroup;
- children = (
- 567C3E762520BB650011F6E9 /* en */,
- );
- name = Localizable.stringsdict;
- sourceTree = "";
- };
- 567C3E772520BB650011F6E9 /* LaunchScreen.storyboard */ = {
- isa = PBXVariantGroup;
- children = (
- 567C3E782520BB650011F6E9 /* Base */,
- );
- name = LaunchScreen.storyboard;
- sourceTree = "";
- };
-/* End PBXVariantGroup section */
-
-/* Begin XCBuildConfiguration section */
- 56026C9F25B8A7D000D1DF3F /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- BUNDLE_LOADER = "$(TEST_HOST)";
- CODE_SIGN_STYLE = Automatic;
- INFOPLIST_FILE = GRDBCombineDemoTests/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 17.0;
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@executable_path/Frameworks",
- "@loader_path/Frameworks",
- );
- PRODUCT_BUNDLE_IDENTIFIER = com.github.groue.GRDBCombineDemoTests;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = "1,2";
- TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GRDBCombineDemo.app/GRDBCombineDemo";
- };
- name = Debug;
- };
- 56026CA025B8A7D000D1DF3F /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- BUNDLE_LOADER = "$(TEST_HOST)";
- CODE_SIGN_STYLE = Automatic;
- INFOPLIST_FILE = GRDBCombineDemoTests/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 17.0;
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@executable_path/Frameworks",
- "@loader_path/Frameworks",
- );
- PRODUCT_BUNDLE_IDENTIFIER = com.github.groue.GRDBCombineDemoTests;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = "1,2";
- TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GRDBCombineDemo.app/GRDBCombineDemo";
- };
- name = Release;
- };
- 567C3E232520B6DF0011F6E9 /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
- CLANG_ANALYZER_NONNULL = YES;
- CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
- CLANG_CXX_LIBRARY = "libc++";
- CLANG_ENABLE_MODULES = YES;
- CLANG_ENABLE_OBJC_ARC = YES;
- CLANG_ENABLE_OBJC_WEAK = YES;
- CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
- CLANG_WARN_BOOL_CONVERSION = YES;
- CLANG_WARN_COMMA = YES;
- CLANG_WARN_CONSTANT_CONVERSION = YES;
- CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
- CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
- CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
- CLANG_WARN_EMPTY_BODY = YES;
- CLANG_WARN_ENUM_CONVERSION = YES;
- CLANG_WARN_INFINITE_RECURSION = YES;
- CLANG_WARN_INT_CONVERSION = YES;
- CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
- CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
- CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
- CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
- CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
- CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
- CLANG_WARN_STRICT_PROTOTYPES = YES;
- CLANG_WARN_SUSPICIOUS_MOVE = YES;
- CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
- CLANG_WARN_UNREACHABLE_CODE = YES;
- CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- COPY_PHASE_STRIP = NO;
- DEBUG_INFORMATION_FORMAT = dwarf;
- ENABLE_STRICT_OBJC_MSGSEND = YES;
- ENABLE_TESTABILITY = YES;
- GCC_C_LANGUAGE_STANDARD = gnu11;
- GCC_DYNAMIC_NO_PIC = NO;
- GCC_NO_COMMON_BLOCKS = YES;
- GCC_OPTIMIZATION_LEVEL = 0;
- GCC_PREPROCESSOR_DEFINITIONS = (
- "DEBUG=1",
- "$(inherited)",
- );
- GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
- GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
- GCC_WARN_UNDECLARED_SELECTOR = YES;
- GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
- GCC_WARN_UNUSED_FUNCTION = YES;
- GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 15.0;
- MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
- MTL_FAST_MATH = YES;
- ONLY_ACTIVE_ARCH = YES;
- SDKROOT = iphoneos;
- SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
- SWIFT_OPTIMIZATION_LEVEL = "-Onone";
- };
- name = Debug;
- };
- 567C3E242520B6DF0011F6E9 /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
- CLANG_ANALYZER_NONNULL = YES;
- CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
- CLANG_CXX_LIBRARY = "libc++";
- CLANG_ENABLE_MODULES = YES;
- CLANG_ENABLE_OBJC_ARC = YES;
- CLANG_ENABLE_OBJC_WEAK = YES;
- CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
- CLANG_WARN_BOOL_CONVERSION = YES;
- CLANG_WARN_COMMA = YES;
- CLANG_WARN_CONSTANT_CONVERSION = YES;
- CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
- CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
- CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
- CLANG_WARN_EMPTY_BODY = YES;
- CLANG_WARN_ENUM_CONVERSION = YES;
- CLANG_WARN_INFINITE_RECURSION = YES;
- CLANG_WARN_INT_CONVERSION = YES;
- CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
- CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
- CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
- CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
- CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
- CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
- CLANG_WARN_STRICT_PROTOTYPES = YES;
- CLANG_WARN_SUSPICIOUS_MOVE = YES;
- CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
- CLANG_WARN_UNREACHABLE_CODE = YES;
- CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- COPY_PHASE_STRIP = NO;
- DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
- ENABLE_NS_ASSERTIONS = NO;
- ENABLE_STRICT_OBJC_MSGSEND = YES;
- GCC_C_LANGUAGE_STANDARD = gnu11;
- GCC_NO_COMMON_BLOCKS = YES;
- GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
- GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
- GCC_WARN_UNDECLARED_SELECTOR = YES;
- GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
- GCC_WARN_UNUSED_FUNCTION = YES;
- GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 15.0;
- MTL_ENABLE_DEBUG_INFO = NO;
- MTL_FAST_MATH = YES;
- SDKROOT = iphoneos;
- SWIFT_COMPILATION_MODE = wholemodule;
- SWIFT_OPTIMIZATION_LEVEL = "-O";
- VALIDATE_PRODUCT = YES;
- };
- name = Release;
- };
- 567C3E262520B6DF0011F6E9 /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
- ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
- CODE_SIGN_STYLE = Automatic;
- DEVELOPMENT_ASSET_PATHS = "\"GRDBCombineDemo/Preview Content\"";
- DEVELOPMENT_TEAM = "";
- ENABLE_PREVIEWS = YES;
- INFOPLIST_FILE = GRDBCombineDemo/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 17.0;
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@executable_path/Frameworks",
- );
- PRODUCT_BUNDLE_IDENTIFIER = com.github.groue.GRDBCombineDemo;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = 1;
- };
- name = Debug;
- };
- 567C3E272520B6DF0011F6E9 /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
- ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
- CODE_SIGN_STYLE = Automatic;
- DEVELOPMENT_ASSET_PATHS = "\"GRDBCombineDemo/Preview Content\"";
- DEVELOPMENT_TEAM = "";
- ENABLE_PREVIEWS = YES;
- INFOPLIST_FILE = GRDBCombineDemo/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 17.0;
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@executable_path/Frameworks",
- );
- PRODUCT_BUNDLE_IDENTIFIER = com.github.groue.GRDBCombineDemo;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = 1;
- };
- name = Release;
- };
-/* End XCBuildConfiguration section */
-
-/* Begin XCConfigurationList section */
- 56026CA825B8A7D000D1DF3F /* Build configuration list for PBXNativeTarget "GRDBCombineDemoTests" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 56026C9F25B8A7D000D1DF3F /* Debug */,
- 56026CA025B8A7D000D1DF3F /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
- 567C3E112520B6DE0011F6E9 /* Build configuration list for PBXProject "GRDBCombineDemo" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 567C3E232520B6DF0011F6E9 /* Debug */,
- 567C3E242520B6DF0011F6E9 /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
- 567C3E252520B6DF0011F6E9 /* Build configuration list for PBXNativeTarget "GRDBCombineDemo" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 567C3E262520B6DF0011F6E9 /* Debug */,
- 567C3E272520B6DF0011F6E9 /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
-/* End XCConfigurationList section */
-
-/* Begin XCRemoteSwiftPackageReference section */
- 56519DC5274FC85600ED16D8 /* XCRemoteSwiftPackageReference "GRDBQuery" */ = {
- isa = XCRemoteSwiftPackageReference;
- repositoryURL = "https://github.com/groue/GRDBQuery";
- requirement = {
- kind = upToNextMajorVersion;
- minimumVersion = 0.6.0;
- };
- };
-/* End XCRemoteSwiftPackageReference section */
-
-/* Begin XCSwiftPackageProductDependency section */
- 56519DC6274FC85600ED16D8 /* GRDBQuery */ = {
- isa = XCSwiftPackageProductDependency;
- package = 56519DC5274FC85600ED16D8 /* XCRemoteSwiftPackageReference "GRDBQuery" */;
- productName = GRDBQuery;
- };
- 56F8A11D273598960011ACBE /* GRDB */ = {
- isa = XCSwiftPackageProductDependency;
- productName = GRDB;
- };
- 56F8A11F2735989D0011ACBE /* GRDB */ = {
- isa = XCSwiftPackageProductDependency;
- productName = GRDB;
- };
-/* End XCSwiftPackageProductDependency section */
- };
- rootObject = 567C3E0E2520B6DE0011F6E9 /* Project object */;
-}
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
deleted file mode 100644
index 216295601d..0000000000
--- a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "pins" : [
- {
- "identity" : "grdbquery",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/groue/GRDBQuery",
- "state" : {
- "revision" : "a6c46dd38ecf11a5c37732870dc03a384d582fba",
- "version" : "0.9.0"
- }
- }
- ],
- "version" : 2
-}
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo.xcodeproj/xcshareddata/xcschemes/GRDBCombineDemo.xcscheme b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo.xcodeproj/xcshareddata/xcschemes/GRDBCombineDemo.xcscheme
deleted file mode 100644
index 0cbf7bf5c1..0000000000
--- a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo.xcodeproj/xcshareddata/xcschemes/GRDBCombineDemo.xcscheme
+++ /dev/null
@@ -1,96 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/AppDatabase.swift b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/AppDatabase.swift
deleted file mode 100644
index c4b5a5e920..0000000000
--- a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/AppDatabase.swift
+++ /dev/null
@@ -1,235 +0,0 @@
-import Foundation
-import GRDB
-import os.log
-
-/// A database of players.
-///
-/// You create an `AppDatabase` with a connection to an SQLite database
-/// (see ).
-///
-/// Create those connections with a configuration returned from
-/// `AppDatabase/makeConfiguration(_:)`.
-///
-/// For example:
-///
-/// ```swift
-/// // Create an in-memory AppDatabase
-/// let config = AppDatabase.makeConfiguration()
-/// let dbQueue = try DatabaseQueue(configuration: config)
-/// let appDatabase = try AppDatabase(dbQueue)
-/// ```
-struct AppDatabase {
- /// Creates an `AppDatabase`, and makes sure the database schema
- /// is ready.
- ///
- /// - important: Create the `DatabaseWriter` with a configuration
- /// returned by ``makeConfiguration(_:)``.
- init(_ dbWriter: any DatabaseWriter) throws {
- self.dbWriter = dbWriter
- try migrator.migrate(dbWriter)
- }
-
- /// Provides access to the database.
- ///
- /// Application can use a `DatabasePool`, while SwiftUI previews and tests
- /// can use a fast in-memory `DatabaseQueue`.
- ///
- /// See
- private let dbWriter: any DatabaseWriter
-}
-
-// MARK: - Database Configuration
-
-extension AppDatabase {
- private static let sqlLogger = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "SQL")
-
- /// Returns a database configuration suited for `PlayerRepository`.
- ///
- /// SQL statements are logged if the `SQL_TRACE` environment variable
- /// is set.
- ///
- /// - parameter base: A base configuration.
- public static func makeConfiguration(_ base: Configuration = Configuration()) -> Configuration {
- var config = base
-
- // An opportunity to add required custom SQL functions or
- // collations, if needed:
- // config.prepareDatabase { db in
- // db.add(function: ...)
- // }
-
- // Log SQL statements if the `SQL_TRACE` environment variable is set.
- // See
- if ProcessInfo.processInfo.environment["SQL_TRACE"] != nil {
- config.prepareDatabase { db in
- db.trace {
- // It's ok to log statements publicly. Sensitive
- // information (statement arguments) are not logged
- // unless config.publicStatementArguments is set
- // (see below).
- os_log("%{public}@", log: sqlLogger, type: .debug, String(describing: $0))
- }
- }
- }
-
-#if DEBUG
- // Protect sensitive information by enabling verbose debugging in
- // DEBUG builds only.
- // See
- config.publicStatementArguments = true
-#endif
-
- return config
- }
-}
-
-// MARK: - Database Migrations
-
-extension AppDatabase {
- /// The DatabaseMigrator that defines the database schema.
- ///
- /// See
- private var migrator: DatabaseMigrator {
- var migrator = DatabaseMigrator()
-
-#if DEBUG
- // Speed up development by nuking the database when migrations change
- // See
- migrator.eraseDatabaseOnSchemaChange = true
-#endif
-
- migrator.registerMigration("createPlayer") { db in
- // Create a table
- // See
- try db.create(table: "player") { t in
- t.autoIncrementedPrimaryKey("id")
- t.column("name", .text).notNull()
- t.column("score", .integer).notNull()
- }
- }
-
- // Migrations for future application versions will be inserted here:
- // migrator.registerMigration(...) { db in
- // ...
- // }
-
- return migrator
- }
-}
-
-// MARK: - Database Access: Writes
-// The write methods execute invariant-preserving database transactions.
-
-extension AppDatabase {
- /// A validation error that prevents some players from being saved into
- /// the database.
- enum ValidationError: LocalizedError {
- case missingName
-
- var errorDescription: String? {
- switch self {
- case .missingName:
- return "Please provide a name"
- }
- }
- }
-
- /// Saves (inserts or updates) a player. When the method returns, the
- /// player is present in the database, and its id is not nil.
- func savePlayer(_ player: inout Player) throws {
- if player.name.isEmpty {
- throw ValidationError.missingName
- }
- try dbWriter.write { db in
- try player.save(db)
- }
- }
-
- /// Delete the specified players
- func deletePlayers(ids: [Int64]) throws {
- try dbWriter.write { db in
- _ = try Player.deleteAll(db, ids: ids)
- }
- }
-
- /// Delete all players
- func deleteAllPlayers() throws {
- try dbWriter.write { db in
- _ = try Player.deleteAll(db)
- }
- }
-
- /// Refresh all players (by performing some random changes, for demo purpose).
- func refreshPlayers() throws {
- try dbWriter.write { db in
- if try Player.all().isEmpty(db) {
- // When database is empty, insert new random players
- try createRandomPlayers(db)
- } else {
- // Insert a player
- if Bool.random() {
- _ = try Player.makeRandom().inserted(db) // insert but ignore inserted id
- }
-
- // Delete a random player
- if Bool.random() {
- try Player.order(sql: "RANDOM()").limit(1).deleteAll(db)
- }
-
- // Update some players
- for var player in try Player.fetchAll(db) where Bool.random() {
- try player.updateChanges(db) {
- $0.score = Player.randomScore()
- }
- }
- }
- }
- }
-
- /// Create random players if the database is empty.
- func createRandomPlayersIfEmpty() throws {
- try dbWriter.write { db in
- if try Player.all().isEmpty(db) {
- try createRandomPlayers(db)
- }
- }
- }
-
- private static let uiTestPlayers = [
- Player(id: nil, name: "Arthur", score: 5),
- Player(id: nil, name: "Barbara", score: 6),
- Player(id: nil, name: "Craig", score: 8),
- Player(id: nil, name: "David", score: 4),
- Player(id: nil, name: "Elena", score: 1),
- Player(id: nil, name: "Frederik", score: 2),
- Player(id: nil, name: "Gilbert", score: 7),
- Player(id: nil, name: "Henriette", score: 3)]
-
- func createPlayersForUITests() throws {
- try dbWriter.write { db in
- try AppDatabase.uiTestPlayers.forEach { player in
- _ = try player.inserted(db) // insert but ignore inserted id
- }
- }
- }
-
- /// Support for `createRandomPlayersIfEmpty()` and `refreshPlayers()`.
- private func createRandomPlayers(_ db: Database) throws {
- for _ in 0..<8 {
- _ = try Player.makeRandom().inserted(db) // insert but ignore inserted id
- }
- }
-}
-
-// MARK: - Database Access: Reads
-
-// This demo app does not provide any specific reading method, and instead
-// gives an unrestricted read-only access to the rest of the application.
-// In your app, you are free to choose another path, and define focused
-// reading methods.
-extension AppDatabase {
- /// Provides a read-only access to the database
- var reader: DatabaseReader {
- dbWriter
- }
-}
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/GRDBCombineDemoApp.swift b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/GRDBCombineDemoApp.swift
deleted file mode 100644
index 27db2c5ea4..0000000000
--- a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/GRDBCombineDemoApp.swift
+++ /dev/null
@@ -1,32 +0,0 @@
-import GRDBQuery
-import SwiftUI
-
-@main
-struct GRDBCombineDemoApp: App {
- var body: some Scene {
- WindowGroup {
- AppView().appDatabase(.shared)
- }
- }
-}
-
-// MARK: - Give SwiftUI access to the database
-
-private struct AppDatabaseKey: EnvironmentKey {
- static var defaultValue: AppDatabase { .empty() }
-}
-
-extension EnvironmentValues {
- var appDatabase: AppDatabase {
- get { self[AppDatabaseKey.self] }
- set { self[AppDatabaseKey.self] = newValue }
- }
-}
-
-extension View {
- func appDatabase(_ appDatabase: AppDatabase) -> some View {
- self
- .environment(\.appDatabase, appDatabase)
- .databaseContext(.readOnly { appDatabase.reader })
- }
-}
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Info.plist b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Info.plist
deleted file mode 100644
index 4754bb8682..0000000000
--- a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Info.plist
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
- CFBundleDevelopmentRegion
- $(DEVELOPMENT_LANGUAGE)
- CFBundleExecutable
- $(EXECUTABLE_NAME)
- CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- $(PRODUCT_NAME)
- CFBundlePackageType
- $(PRODUCT_BUNDLE_PACKAGE_TYPE)
- CFBundleShortVersionString
- 1.0
- CFBundleVersion
- 1
- LSRequiresIPhoneOS
-
- UIApplicationSceneManifest
-
- UIApplicationSupportsMultipleScenes
-
-
- UIApplicationSupportsIndirectInputEvents
-
- UILaunchScreen
-
- UILaunchStoryboardName
- LaunchScreen
- UIRequiredDeviceCapabilities
-
- armv7
-
- UISupportedInterfaceOrientations
-
- UIInterfaceOrientationPortrait
-
- UISupportedInterfaceOrientations~ipad
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
-
-
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Persistence.swift b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Persistence.swift
deleted file mode 100644
index 05b0115789..0000000000
--- a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Persistence.swift
+++ /dev/null
@@ -1,77 +0,0 @@
-import Foundation
-import GRDB
-
-extension AppDatabase {
- /// The database for the application
- static let shared = makeShared()
-
- private static func makeShared() -> AppDatabase {
- do {
- // Apply recommendations from
- //
- //
- // Create the "Application Support/Database" directory if needed
- let fileManager = FileManager.default
- let appSupportURL = try fileManager.url(
- for: .applicationSupportDirectory, in: .userDomainMask,
- appropriateFor: nil, create: true)
- let directoryURL = appSupportURL.appendingPathComponent("Database", isDirectory: true)
-
- // Support for tests: delete the database if requested
- if CommandLine.arguments.contains("-reset") {
- try? fileManager.removeItem(at: directoryURL)
- }
-
- // Create the database folder if needed
- try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true)
-
- // Open or create the database
- let databaseURL = directoryURL.appendingPathComponent("db.sqlite")
- NSLog("Database stored at \(databaseURL.path)")
- let dbPool = try DatabasePool(
- path: databaseURL.path,
- // Use default AppDatabase configuration
- configuration: AppDatabase.makeConfiguration())
-
- // Create the AppDatabase
- let appDatabase = try AppDatabase(dbPool)
-
- // Prepare the database with test fixtures if requested
- if CommandLine.arguments.contains("-fixedTestData") {
- try appDatabase.createPlayersForUITests()
- } else {
- // Otherwise, populate the database if it is empty, for better
- // demo purpose.
- try appDatabase.createRandomPlayersIfEmpty()
- }
-
- return appDatabase
- } catch {
- // Replace this implementation with code to handle the error appropriately.
- // fatalError() causes the application to generate a crash log and terminate.
- //
- // Typical reasons for an error here include:
- // * The parent directory cannot be created, or disallows writing.
- // * The database is not accessible, due to permissions or data protection when the device is locked.
- // * The device is out of space.
- // * The database could not be migrated to its latest schema version.
- // Check the error message to determine what the actual problem was.
- fatalError("Unresolved error \(error)")
- }
- }
-
- /// Creates an empty database for SwiftUI previews
- static func empty() -> AppDatabase {
- // Connect to an in-memory database
- // See https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databaseconnections
- let dbQueue = try! DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- return try! AppDatabase(dbQueue)
- }
-
- /// Creates a database full of random players for SwiftUI previews
- static func random() -> AppDatabase {
- let appDatabase = empty()
- try! appDatabase.createRandomPlayersIfEmpty()
- return appDatabase
- }
-}
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Player.swift b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Player.swift
deleted file mode 100644
index 7876b049df..0000000000
--- a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Player.swift
+++ /dev/null
@@ -1,105 +0,0 @@
-import GRDB
-
-/// The Player struct.
-///
-/// Identifiable conformance supports SwiftUI list animations, and type-safe
-/// GRDB primary key methods.
-/// Equatable conformance supports tests.
-struct Player: Identifiable, Equatable {
- /// The player id.
- ///
- /// Int64 is the recommended type for auto-incremented database ids.
- /// Use nil for players that are not inserted yet in the database.
- var id: Int64?
- var name: String
- var score: Int
-}
-
-extension Player {
- private static let names = [
- "Arthur", "Anita", "Barbara", "Bernard", "Craig", "Chiara", "David",
- "Dean", "Éric", "Elena", "Fatima", "Frederik", "Gilbert", "Georgette",
- "Henriette", "Hassan", "Ignacio", "Irene", "Julie", "Jack", "Karl",
- "Kristel", "Louis", "Liz", "Masashi", "Mary", "Noam", "Nicole",
- "Ophelie", "Oleg", "Pascal", "Patricia", "Quentin", "Quinn", "Raoul",
- "Rachel", "Stephan", "Susie", "Tristan", "Tatiana", "Ursule", "Urbain",
- "Victor", "Violette", "Wilfried", "Wilhelmina", "Yvon", "Yann",
- "Zazie", "Zoé"]
-
- /// Creates a new player with empty name and zero score
- static func new() -> Player {
- Player(id: nil, name: "", score: 0)
- }
-
- /// Creates a new player with random name and random score
- static func makeRandom() -> Player {
- Player(id: nil, name: randomName(), score: randomScore())
- }
-
- /// Returns a random name
- static func randomName() -> String {
- names.randomElement()!
- }
-
- /// Returns a random score
- static func randomScore() -> Int {
- 10 * Int.random(in: 0...100)
- }
-}
-
-// MARK: - Persistence
-
-/// Make Player a Codable Record.
-///
-/// See
-extension Player: Codable, FetchableRecord, MutablePersistableRecord {
- // Define database columns from CodingKeys
- fileprivate enum Columns {
- static let name = Column(CodingKeys.name)
- static let score = Column(CodingKeys.score)
- }
-
- /// Updates a player id after it has been inserted in the database.
- mutating func didInsert(_ inserted: InsertionSuccess) {
- id = inserted.rowID
- }
-}
-
-// MARK: - Player Database Requests
-
-/// Define some player requests used by the application.
-///
-/// See
-extension DerivableRequest {
- /// A request of players ordered by name.
- ///
- /// For example:
- ///
- /// let players: [Player] = try dbWriter.read { db in
- /// try Player.all().orderedByName().fetchAll(db)
- /// }
- func orderedByName() -> Self {
- // Sort by name in a localized case insensitive fashion
- // See https://github.com/groue/GRDB.swift/blob/master/README.md#string-comparison
- order(Player.Columns.name.collating(.localizedCaseInsensitiveCompare))
- }
-
- /// A request of players ordered by score.
- ///
- /// For example:
- ///
- /// let players: [Player] = try dbWriter.read { db in
- /// try Player.all().orderedByScore().fetchAll(db)
- /// }
- /// let bestPlayer: Player? = try dbWriter.read { db in
- /// try Player.all().orderedByScore().fetchOne(db)
- /// }
- func orderedByScore() -> Self {
- // Sort by descending score, and then by name, in a
- // localized case insensitive fashion
- // See https://github.com/groue/GRDB.swift/blob/master/README.md#string-comparison
- order(
- Player.Columns.score.desc,
- Player.Columns.name.collating(.localizedCaseInsensitiveCompare))
- }
-}
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/PlayerRequest.swift b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/PlayerRequest.swift
deleted file mode 100644
index 4d1d924605..0000000000
--- a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/PlayerRequest.swift
+++ /dev/null
@@ -1,35 +0,0 @@
-import GRDB
-import GRDBQuery
-
-/// A player request can be used with the `@Query` property wrapper in order to
-/// feed a view with a list of players.
-///
-/// For example:
-///
-/// struct MyView: View {
-/// @Query(PlayerRequest(ordering: .byName)) private var players: [Player]
-///
-/// var body: some View {
-/// List(players) { player in ... )
-/// }
-/// }
-struct PlayerRequest: ValueObservationQueryable {
- enum Ordering {
- case byScore
- case byName
- }
-
- static var defaultValue: [Player] { [] }
-
- /// The ordering used by the player request.
- var ordering: Ordering
-
- func fetch(_ db: Database) throws -> [Player] {
- switch ordering {
- case .byScore:
- return try Player.all().orderedByScore().fetchAll(db)
- case .byName:
- return try Player.all().orderedByName().fetchAll(db)
- }
- }
-}
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
deleted file mode 100644
index 29d91251df..0000000000
--- a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
+++ /dev/null
@@ -1,116 +0,0 @@
-{
- "images" : [
- {
- "size" : "20x20",
- "idiom" : "iphone",
- "filename" : "icon_20pt@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "20x20",
- "idiom" : "iphone",
- "filename" : "icon_20pt@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "29x29",
- "idiom" : "iphone",
- "filename" : "icon_29pt@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "29x29",
- "idiom" : "iphone",
- "filename" : "icon_29pt@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "40x40",
- "idiom" : "iphone",
- "filename" : "icon_40pt@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "40x40",
- "idiom" : "iphone",
- "filename" : "icon_40pt@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "60x60",
- "idiom" : "iphone",
- "filename" : "icon_60pt@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "60x60",
- "idiom" : "iphone",
- "filename" : "icon_60pt@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "20x20",
- "idiom" : "ipad",
- "filename" : "icon_20pt.png",
- "scale" : "1x"
- },
- {
- "size" : "20x20",
- "idiom" : "ipad",
- "filename" : "icon_20pt@2x-1.png",
- "scale" : "2x"
- },
- {
- "size" : "29x29",
- "idiom" : "ipad",
- "filename" : "icon_29pt.png",
- "scale" : "1x"
- },
- {
- "size" : "29x29",
- "idiom" : "ipad",
- "filename" : "icon_29pt@2x-1.png",
- "scale" : "2x"
- },
- {
- "size" : "40x40",
- "idiom" : "ipad",
- "filename" : "icon_40pt.png",
- "scale" : "1x"
- },
- {
- "size" : "40x40",
- "idiom" : "ipad",
- "filename" : "icon_40pt@2x-1.png",
- "scale" : "2x"
- },
- {
- "size" : "76x76",
- "idiom" : "ipad",
- "filename" : "icon_76pt.png",
- "scale" : "1x"
- },
- {
- "size" : "76x76",
- "idiom" : "ipad",
- "filename" : "icon_76pt@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "83.5x83.5",
- "idiom" : "ipad",
- "filename" : "icon_83.5@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "1024x1024",
- "idiom" : "ios-marketing",
- "filename" : "Icon.png",
- "scale" : "1x"
- }
- ],
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
\ No newline at end of file
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/Icon.png b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/Icon.png
deleted file mode 100644
index 66b1931a14..0000000000
Binary files a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/Icon.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt.png b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt.png
deleted file mode 100644
index 90648b3f40..0000000000
Binary files a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x-1.png b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x-1.png
deleted file mode 100644
index a077a6f490..0000000000
Binary files a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x-1.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x.png b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x.png
deleted file mode 100644
index a077a6f490..0000000000
Binary files a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt@3x.png b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt@3x.png
deleted file mode 100644
index 600bdbd9cd..0000000000
Binary files a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt@3x.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt.png b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt.png
deleted file mode 100644
index 8e04af0dd8..0000000000
Binary files a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x-1.png b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x-1.png
deleted file mode 100644
index 686e8d99e2..0000000000
Binary files a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x-1.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x.png b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x.png
deleted file mode 100644
index 686e8d99e2..0000000000
Binary files a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt@3x.png b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt@3x.png
deleted file mode 100644
index 1d013c3d33..0000000000
Binary files a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt@3x.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt.png b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt.png
deleted file mode 100644
index a077a6f490..0000000000
Binary files a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x-1.png b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x-1.png
deleted file mode 100644
index da66b9ba82..0000000000
Binary files a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x-1.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x.png b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x.png
deleted file mode 100644
index da66b9ba82..0000000000
Binary files a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt@3x.png b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt@3x.png
deleted file mode 100644
index 59346ef4b6..0000000000
Binary files a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt@3x.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x.png b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x.png
deleted file mode 100644
index 59346ef4b6..0000000000
Binary files a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x.png b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x.png
deleted file mode 100644
index d4640afc9a..0000000000
Binary files a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_76pt.png b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_76pt.png
deleted file mode 100644
index e3a04522bf..0000000000
Binary files a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_76pt.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_76pt@2x.png b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_76pt@2x.png
deleted file mode 100644
index 593ebd783d..0000000000
Binary files a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_76pt@2x.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x.png b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x.png
deleted file mode 100644
index ca02cd03bc..0000000000
Binary files a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/LaunchIcon.imageset/Contents.json b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/LaunchIcon.imageset/Contents.json
deleted file mode 100644
index 2cbe59d5ec..0000000000
--- a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/LaunchIcon.imageset/Contents.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "images" : [
- {
- "filename" : "LaunchIcon.pdf",
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- }
-}
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/LaunchIcon.imageset/LaunchIcon.pdf b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/LaunchIcon.imageset/LaunchIcon.pdf
deleted file mode 100644
index 2660891492..0000000000
Binary files a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Assets.xcassets/LaunchIcon.imageset/LaunchIcon.pdf and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Base.lproj/LaunchScreen.storyboard b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Base.lproj/LaunchScreen.storyboard
deleted file mode 100644
index 79f85a1dd9..0000000000
--- a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/Base.lproj/LaunchScreen.storyboard
+++ /dev/null
@@ -1,42 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/en.lproj/Localizable.stringsdict b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/en.lproj/Localizable.stringsdict
deleted file mode 100644
index 2d9aa217c4..0000000000
--- a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Resources/en.lproj/Localizable.stringsdict
+++ /dev/null
@@ -1,42 +0,0 @@
-
-
-
-
- %lld Players
-
- NSStringLocalizedFormatKey
- %#@VARIABLE@
- VARIABLE
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- lld
- zero
- No Player
- one
- 1 Player
- other
- %lld Players
-
-
- %lld points
-
- NSStringLocalizedFormatKey
- %#@VARIABLE@
- VARIABLE
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- lld
- zero
- 0 point
- one
- 1 point
- other
- %lld points
-
-
-
-
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Views/AppView.swift b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Views/AppView.swift
deleted file mode 100644
index 7c93e15cb8..0000000000
--- a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Views/AppView.swift
+++ /dev/null
@@ -1,145 +0,0 @@
-import GRDBQuery
-import SwiftUI
-
-/// The main application view
-struct AppView: View {
- /// Write access to the database
- @Environment(\.appDatabase) private var appDatabase
-
- /// The `players` property is automatically updated when the database changes
- @Query(PlayerRequest(ordering: .byScore)) private var players: [Player]
-
- /// We'll need to leave edit mode in several occasions.
- @State private var editMode = EditMode.inactive
-
- /// Tracks the presentation of the player creation sheet.
- @State private var newPlayerIsPresented = false
-
- // If you want to define the query on initialization, you will prefer:
- //
- // @Query private var players: [Player]
- //
- // init(initialOrdering: PlayerRequest.Ordering) {
- // _players = Query(PlayerRequest(ordering: initialOrdering))
- // }
-
- var body: some View {
- NavigationView {
- PlayerList(players: players)
- .navigationBarTitle(Text("\(players.count) Players"))
- .navigationBarItems(
- leading: HStack {
- EditButton()
- newPlayerButton
- },
- trailing: ToggleOrderingButton(
- ordering: $players.ordering,
- willChange: {
- // onChange(of: $players.wrappedValue.ordering)
- // is not able to leave the editing mode during
- // the animation of the list content.
- // Workaround: stop editing before the ordering
- // is changed, and the list content is updated.
- stopEditing()
- }))
- .toolbar { toolbarContent }
- .onChange(of: players) {
- if players.isEmpty {
- stopEditing()
- }
- }
- .environment(\.editMode, $editMode)
- }
- }
-
- private var toolbarContent: some ToolbarContent {
- ToolbarItemGroup(placement: .bottomBar) {
- Button {
- // Don't stopEditing() here because this is
- // performed `onChange(of: players)`
- try! appDatabase.deleteAllPlayers()
- } label: {
- Image(systemName: "trash").imageScale(.large)
- }
-
- Spacer()
-
- Button {
- stopEditing()
- try! appDatabase.refreshPlayers()
- } label: {
- Image(systemName: "arrow.clockwise").imageScale(.large)
- }
-
- Spacer()
-
- Button {
- stopEditing()
- // Perform 50 refreshes in parallel
- for _ in 0..<50 {
- DispatchQueue.global().async {
- try! AppDatabase.shared.refreshPlayers()
- }
- }
- } label: {
- Image(systemName: "tornado").imageScale(.large)
- }
- }
- }
-
- /// The button that presents the player creation sheet.
- private var newPlayerButton: some View {
- Button {
- stopEditing()
- newPlayerIsPresented = true
- } label: {
- Image(systemName: "plus")
- }
- .accessibility(label: Text("New Player"))
- .sheet(isPresented: $newPlayerIsPresented) {
- PlayerCreationView()
- }
- }
-
- private func stopEditing() {
- withAnimation {
- editMode = .inactive
- }
- }
-}
-
-private struct ToggleOrderingButton: View {
- @Binding var ordering: PlayerRequest.Ordering
- let willChange: () -> Void
-
- var body: some View {
- switch ordering {
- case .byName:
- Button {
- willChange()
- ordering = .byScore
- } label: {
- Label("Name", systemImage: "arrowtriangle.up.fill").labelStyle(.titleAndIcon)
- }
- case .byScore:
- Button {
- willChange()
- ordering = .byName
- } label: {
- Label("Score", systemImage: "arrowtriangle.down.fill").labelStyle(.titleAndIcon)
- }
- }
- }
-}
-
-// MARK: - Previews
-
-#Preview("Empty") {
- // Preview the default, empty database
- AppView()
-}
-
-#Preview("Populated") {
- // Preview a database of random players
- AppView().appDatabase(.random())
-}
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Views/PlayerCreationView.swift b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Views/PlayerCreationView.swift
deleted file mode 100644
index f86876fd9f..0000000000
--- a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Views/PlayerCreationView.swift
+++ /dev/null
@@ -1,50 +0,0 @@
-import SwiftUI
-
-/// The view that creates a new player.
-struct PlayerCreationView: View {
- /// Write access to the database
- @Environment(\.appDatabase) private var appDatabase
- @Environment(\.dismiss) private var dismiss
- @State private var form = PlayerForm(name: "", score: "")
- @State private var errorAlertIsPresented = false
- @State private var errorAlertTitle = ""
-
- var body: some View {
- NavigationView {
- PlayerFormView(form: $form)
- .alert(
- isPresented: $errorAlertIsPresented,
- content: { Alert(title: Text(errorAlertTitle)) })
- .navigationBarTitle("New Player")
- .navigationBarItems(
- leading: Button(role: .cancel) {
- dismiss()
- } label: {
- Text("Cancel")
- },
- trailing: Button {
- save()
- } label: {
- Text("Save")
- })
- }
- }
-
- private func save() {
- do {
- var player = Player(id: nil, name: "", score: 0)
- form.apply(to: &player)
- try appDatabase.savePlayer(&player)
- dismiss()
- } catch {
- errorAlertTitle = (error as? LocalizedError)?.errorDescription ?? "An error occurred"
- errorAlertIsPresented = true
- }
- }
-}
-
-// MARK: - Previews
-
-#Preview {
- PlayerCreationView()
-}
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Views/PlayerEditionView.swift b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Views/PlayerEditionView.swift
deleted file mode 100644
index a3c559a1af..0000000000
--- a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Views/PlayerEditionView.swift
+++ /dev/null
@@ -1,38 +0,0 @@
-import SwiftUI
-
-/// The view that edits an existing player.
-struct PlayerEditionView: View {
- /// Write access to the database
- @Environment(\.appDatabase) private var appDatabase
- @Environment(\.isPresented) private var isPresented
- private let player: Player
- @State private var form: PlayerForm
-
- init(player: Player) {
- self.player = player
- self.form = PlayerForm(player)
- }
-
- var body: some View {
- PlayerFormView(form: $form)
- .onChange(of: isPresented) {
- // Save when back button is pressed
- if !isPresented {
- var savedPlayer = player
- form.apply(to: &savedPlayer)
- // Ignore error because I don't know how to cancel the
- // back button and present the error
- try? appDatabase.savePlayer(&savedPlayer)
- }
- }
- }
-}
-
-// MARK: - Previews
-
-#Preview {
- NavigationView {
- PlayerEditionView(player: Player.makeRandom())
- .navigationBarTitle("Player Edition")
- }
-}
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Views/PlayerFormView.swift b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Views/PlayerFormView.swift
deleted file mode 100644
index 590506d465..0000000000
--- a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Views/PlayerFormView.swift
+++ /dev/null
@@ -1,48 +0,0 @@
-import SwiftUI
-
-/// The Player editing form, embedded in both
-/// `PlayerCreationView` and `PlayerEditionView`.
-struct PlayerFormView: View {
- @Binding var form: PlayerForm
-
- var body: some View {
- List {
- TextField("Name", text: $form.name)
- .accessibility(label: Text("Player Name"))
- TextField("Score", text: $form.score).keyboardType(.numberPad)
- .accessibility(label: Text("Player Score"))
- }
- .listStyle(InsetGroupedListStyle())
- }
-}
-
-struct PlayerForm {
- var name: String
- var score: String
-}
-
-extension PlayerForm {
- init(_ player: Player) {
- self.name = player.name
- self.score = "\(player.score)"
- }
-
- func apply(to player: inout Player) {
- player.name = name
- player.score = Int(score) ?? 0
- }
-}
-
-// MARK: - Previews
-
-#Preview("Empty") {
- PlayerFormView(form: .constant(PlayerForm(
- name: "",
- score: "")))
-}
-
-#Preview("Prefilled") {
- PlayerFormView(form: .constant(PlayerForm(
- name: Player.randomName(),
- score: "\(Player.randomScore())")))
-}
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Views/PlayerList.swift b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Views/PlayerList.swift
deleted file mode 100644
index 7913c73c60..0000000000
--- a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Views/PlayerList.swift
+++ /dev/null
@@ -1,57 +0,0 @@
-import SwiftUI
-
-struct PlayerList: View {
- /// Write access to the database
- @Environment(\.appDatabase) private var appDatabase
-
- /// The players in the list
- var players: [Player]
-
- var body: some View {
- List {
- ForEach(players) { player in
- NavigationLink(destination: editionView(for: player)) {
- PlayerRow(player: player)
- // Don't animate player update
- .animation(nil, value: player)
- }
- }
- .onDelete { offsets in
- let playerIds = offsets.compactMap { players[$0].id }
- try? appDatabase.deletePlayers(ids: playerIds)
- }
- }
- // Animate list updates
- .animation(.default, value: players)
- .listStyle(.plain)
- }
-
- /// The view that edits a player in the list.
- private func editionView(for player: Player) -> some View {
- PlayerEditionView(player: player).navigationBarTitle(player.name)
- }
-}
-
-private struct PlayerRow: View {
- var player: Player
-
- var body: some View {
- HStack {
- Text(player.name)
- Spacer()
- Text("\(player.score) points").foregroundColor(.gray)
- }
- }
-}
-
-// MARK: - Previews
-
-#Preview {
- NavigationView {
- PlayerList(players: [
- Player(id: 1, name: "Arthur", score: 100),
- Player(id: 2, name: "Barbara", score: 1000),
- ])
- .navigationTitle("Preview")
- }
-}
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemoTests/AppDatabaseTests.swift b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemoTests/AppDatabaseTests.swift
deleted file mode 100644
index f9343671e1..0000000000
--- a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemoTests/AppDatabaseTests.swift
+++ /dev/null
@@ -1,146 +0,0 @@
-import XCTest
-import GRDB
-@testable import GRDBCombineDemo
-
-class AppDatabaseTests: XCTestCase {
- func test_database_schema() throws {
- // Given an empty database
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
-
- // When we instantiate an AppDatabase
- _ = try AppDatabase(dbQueue)
-
- // Then the player table exists, with id, name & score columns
- try dbQueue.read { db in
- try XCTAssert(db.tableExists("player"))
- let columns = try db.columns(in: "player")
- let columnNames = Set(columns.map { $0.name })
- XCTAssertEqual(columnNames, ["id", "name", "score"])
- }
- }
-
- func test_savePlayer_inserts() throws {
- // Given an empty players database
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- let appDatabase = try AppDatabase(dbQueue)
-
- // When we save a new player
- var player = Player(id: nil, name: "Arthur", score: 100)
- try appDatabase.savePlayer(&player)
-
- // Then the player exists in the database
- try XCTAssertTrue(dbQueue.read(player.exists))
- }
-
- func test_savePlayer_updates() throws {
- // Given a players database that contains a player
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- let appDatabase = try AppDatabase(dbQueue)
- var player = Player(id: nil, name: "Arthur", score: 100)
- try dbQueue.write { db in
- try player.insert(db)
- }
-
- // When we modify and save the player
- player.name = "Barbara"
- player.score = 1000
- try appDatabase.savePlayer(&player)
-
- // Then the player has been updated in the database
- let fetchedPlayer = try dbQueue.read { db in
- try XCTUnwrap(Player.fetchOne(db, key: player.id))
- }
- XCTAssertEqual(fetchedPlayer, player)
- }
-
- func test_deletePlayers() throws {
- // Given a players database that contains four players
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- let appDatabase = try AppDatabase(dbQueue)
- var player1 = Player(id: nil, name: "Arthur", score: 100)
- var player2 = Player(id: nil, name: "Barbara", score: 200)
- var player3 = Player(id: nil, name: "Craig", score: 150)
- var player4 = Player(id: nil, name: "David", score: 120)
- try dbQueue.write { db in
- try player1.insert(db)
- try player2.insert(db)
- try player3.insert(db)
- try player4.insert(db)
- }
-
- // When we delete two players
- try appDatabase.deletePlayers(ids: [player1.id!, player3.id!])
-
- // Then the deleted players no longer exist
- try dbQueue.read { db in
- try XCTAssertFalse(player1.exists(db))
- try XCTAssertFalse(player3.exists(db))
- }
-
- // Then the database still contains two players
- try XCTAssertEqual(dbQueue.read(Player.fetchCount), 2)
- }
-
- func test_deleteAllPlayers() throws {
- // Given a players database that contains players
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- let appDatabase = try AppDatabase(dbQueue)
- var player1 = Player(id: nil, name: "Arthur", score: 100)
- var player2 = Player(id: nil, name: "Barbara", score: 200)
- var player3 = Player(id: nil, name: "Craig", score: 150)
- var player4 = Player(id: nil, name: "David", score: 120)
- try dbQueue.write { db in
- try player1.insert(db)
- try player2.insert(db)
- try player3.insert(db)
- try player4.insert(db)
- }
-
- // When we delete all players
- try appDatabase.deleteAllPlayers()
-
- // Then the database does not contain any player
- try XCTAssertEqual(dbQueue.read(Player.fetchCount), 0)
- }
-
- func test_refreshPlayers_populates_an_empty_database() throws {
- // Given an empty players database
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- let appDatabase = try AppDatabase(dbQueue)
-
- // When we refresh players
- try appDatabase.refreshPlayers()
-
- // Then the database is not empty
- try XCTAssert(dbQueue.read(Player.fetchCount) > 0)
- }
-
- func test_createRandomPlayersIfEmpty_populates_an_empty_database() throws {
- // Given an empty players database
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- let appDatabase = try AppDatabase(dbQueue)
-
- // When we create random players
- try appDatabase.createRandomPlayersIfEmpty()
-
- // Then the database is not empty
- try XCTAssert(dbQueue.read(Player.fetchCount) > 0)
- }
-
- func test_createRandomPlayersIfEmpty_does_not_modify_a_non_empty_database() throws {
- // Given a players database that contains one player
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- let appDatabase = try AppDatabase(dbQueue)
- var player = Player(id: nil, name: "Arthur", score: 100)
- try dbQueue.write { db in
- try player.insert(db)
- }
-
- // When we create random players
- try appDatabase.createRandomPlayersIfEmpty()
-
- // Then the database still only contains the original player
- let players = try dbQueue.read(Player.fetchAll)
- XCTAssertEqual(players, [player])
- }
-}
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemoTests/Info.plist b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemoTests/Info.plist
deleted file mode 100644
index 64d65ca495..0000000000
--- a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemoTests/Info.plist
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
- CFBundleDevelopmentRegion
- $(DEVELOPMENT_LANGUAGE)
- CFBundleExecutable
- $(EXECUTABLE_NAME)
- CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- $(PRODUCT_NAME)
- CFBundlePackageType
- $(PRODUCT_BUNDLE_PACKAGE_TYPE)
- CFBundleShortVersionString
- 1.0
- CFBundleVersion
- 1
-
-
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemoTests/PlayerRequestTests.swift b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemoTests/PlayerRequestTests.swift
deleted file mode 100644
index c9e58da1ed..0000000000
--- a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemoTests/PlayerRequestTests.swift
+++ /dev/null
@@ -1,43 +0,0 @@
-import XCTest
-import GRDB
-@testable import GRDBCombineDemo
-
-class PlayerRequestTests: XCTestCase {
- func test_PlayerRequest_byName_fetches_well_ordered_players() throws {
- // Given a players database that contains two players
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- _ = try AppDatabase(dbQueue)
- var player1 = Player(id: nil, name: "Arthur", score: 100)
- var player2 = Player(id: nil, name: "Barbara", score: 1000)
- try dbQueue.write { db in
- try player1.insert(db)
- try player2.insert(db)
- }
-
- // When we fetch players ordered by name
- let playerRequest = PlayerRequest(ordering: .byName)
- let players = try dbQueue.read(playerRequest.fetch)
-
- // Then the players are the two players ordered by name
- XCTAssertEqual(players, [player1, player2])
- }
-
- func test_PlayerRequest_byScore_fetches_well_ordered_players() throws {
- // Given a players database that contains two players
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- _ = try AppDatabase(dbQueue)
- var player1 = Player(id: nil, name: "Arthur", score: 100)
- var player2 = Player(id: nil, name: "Barbara", score: 1000)
- try dbQueue.write { db in
- try player1.insert(db)
- try player2.insert(db)
- }
-
- // When we fetch players ordered by score
- let playerRequest = PlayerRequest(ordering: .byScore)
- let players = try dbQueue.read(playerRequest.fetch)
-
- // Then the players are the two players ordered by score descending
- XCTAssertEqual(players, [player2, player1])
- }
-}
diff --git a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemoTests/PlayerTests.swift b/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemoTests/PlayerTests.swift
deleted file mode 100644
index 4262d80235..0000000000
--- a/Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemoTests/PlayerTests.swift
+++ /dev/null
@@ -1,108 +0,0 @@
-import XCTest
-import GRDB
-@testable import GRDBCombineDemo
-
-class PlayerTests: XCTestCase {
- // MARK: - CRUD
- // Test that our Player type properly talks to GRDB.
-
- func testInsert() throws {
- // Given an empty players database
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- _ = try AppDatabase(dbQueue)
-
- // When we insert a player
- var player = Player(id: nil, name: "Arthur", score: 100)
- try dbQueue.write { db in
- try player.insert(db)
- }
-
- // Then the player gets a non-nil id
- XCTAssertNotNil(player.id)
- }
-
- func testRoundtrip() throws {
- // Given an empty players database
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- _ = try AppDatabase(dbQueue)
-
- // When we insert a player and fetch the player with the same id
- var insertedPlayer = Player(id: nil, name: "Arthur", score: 100)
- let fetchedPlayer: Player? = try dbQueue.write { db in
- try insertedPlayer.insert(db)
- return try Player.fetchOne(db, key: insertedPlayer.id)
- }
-
- // Then the fetched player is equal to the inserted player
- XCTAssertEqual(insertedPlayer, fetchedPlayer)
- }
-
- // MARK: - Requests
- // Test that requests defined on the Player type behave as expected.
-
- func testOrderedByScore() throws {
- // Given a players database that contains players with distinct scores
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- _ = try AppDatabase(dbQueue)
- var player1 = Player(id: 1, name: "Arthur", score: 100)
- var player2 = Player(id: 2, name: "Barbara", score: 200)
- var player3 = Player(id: 3, name: "Craig", score: 150)
- var player4 = Player(id: 4, name: "David", score: 120)
- try dbQueue.write { db in
- try player1.insert(db)
- try player2.insert(db)
- try player3.insert(db)
- try player4.insert(db)
- }
-
- // When we fetch players ordered by score
- let players = try dbQueue.read(Player.all().orderedByScore().fetchAll)
-
- // Then fetched players are ordered by score descending
- XCTAssertEqual(players, [player2, player3, player4, player1])
- }
-
- func testOrderedByScoreSortsIdenticalScoresByName() throws {
- // Given a players database that contains players with common scores
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- _ = try AppDatabase(dbQueue)
- var player1 = Player(id: 1, name: "Arthur", score: 100)
- var player2 = Player(id: 2, name: "Barbara", score: 200)
- var player3 = Player(id: 3, name: "Craig", score: 200)
- var player4 = Player(id: 4, name: "David", score: 200)
- try dbQueue.write { db in
- try player1.insert(db)
- try player2.insert(db)
- try player3.insert(db)
- try player4.insert(db)
- }
-
- // When we fetch players ordered by score
- let players = try dbQueue.read(Player.all().orderedByScore().fetchAll)
-
- // Then fetched players are ordered by score descending and by name
- XCTAssertEqual(players, [player2, player3, player4, player1])
- }
-
- func testOrderedByName() throws {
- // Given a players database that contains players with distinct names
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- _ = try AppDatabase(dbQueue)
- var player1 = Player(id: 1, name: "Arthur", score: 100)
- var player2 = Player(id: 2, name: "Barbara", score: 200)
- var player3 = Player(id: 3, name: "Craig", score: 150)
- var player4 = Player(id: 4, name: "David", score: 120)
- try dbQueue.write { db in
- try player1.insert(db)
- try player2.insert(db)
- try player3.insert(db)
- try player4.insert(db)
- }
-
- // When we fetch players ordered by name
- let players = try dbQueue.read(Player.all().orderedByName().fetchAll)
-
- // Then fetched players are ordered by name
- XCTAssertEqual(players, [player1, player2, player3, player4])
- }
-}
diff --git a/Documentation/DemoApps/GRDBCombineDemo/README.md b/Documentation/DemoApps/GRDBCombineDemo/README.md
deleted file mode 100644
index 6b2ccec0f7..0000000000
--- a/Documentation/DemoApps/GRDBCombineDemo/README.md
+++ /dev/null
@@ -1,51 +0,0 @@
-Combine + SwiftUI Demo Application
-==================================
-
-
-
-**This demo application is a Combine + SwiftUI application.** For a demo application that uses UIKit, see [GRDBDemoiOS](../GRDBDemoiOS/README.md), and for Async/Await + SwiftUI, see [GRDBAsyncDemo](../GRDBAsyncDemo/README.md).
-
-**Requirements**: iOS 15.0+ / Xcode 12+
-
-> **Note**: This demo app is not a project template. Do not copy it as a starting point for your application. Instead, create a new project, choose a GRDB [installation method](../../../README.md#installation), and use the demo as an inspiration.
-
-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 [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 feed SwiftUI previews with a transient database.
-
-**Files of interest:**
-
-- [GRDBCombineDemoApp.swift](GRDBCombineDemo/GRDBCombineDemoApp.swift)
-
- `GRDBCombineDemoApp` feeds the app views with a database, through the SwiftUI environment.
-
-- [AppDatabase.swift](GRDBCombineDemo/AppDatabase.swift)
-
- `AppDatabase` is the type that grants database access. It uses [DatabaseMigrator](https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databasemigrator) in order to setup the database schema.
-
-- [Persistence.swift](GRDBCombineDemo/Persistence.swift)
-
- This file instantiates various `AppDatabase` for the various projects needs: one database on disk for the application, and in-memory databases for SwiftUI previews.
-
-- [Player.swift](GRDBCombineDemo/Player.swift)
-
- `Player` is a [Record](../../../README.md#records) type, able to read and write in the database. It conforms to the standard Codable protocol in order to gain all advantages of [Codable Records](../../../README.md#codable-records).
-
-- [PlayerRequest.swift](GRDBCombineDemo/PlayerRequest.swift), [AppView.swift](GRDBCombineDemo/Views/AppView.swift)
-
- `PlayerRequest` defines the player requests used by the app (sorted by score, or by name).
-
- `PlayerRequest` feeds the `@Query` property wrapper (`@Query`, defined in [GRDBQuery](https://github.com/groue/GRDBQuery), allows SwiftUI views to display up-to-date database content).
-
- `AppView` is the SwiftUI view that uses `@Query` in order to feed its player list.
-
-- [GRDBCombineDemoTests](GRDBCombineDemoTests)
-
- - Test the database schema
- - Test the `Player` record and its requests
- - Test the `PlayerRequest` methods that feed the list of players.
- - Test the `AppDatabase` methods that let the app access the database.
diff --git a/Documentation/DemoApps/GRDBCombineDemo/Screenshot.png b/Documentation/DemoApps/GRDBCombineDemo/Screenshot.png
deleted file mode 100644
index 0536821854..0000000000
Binary files a/Documentation/DemoApps/GRDBCombineDemo/Screenshot.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBDemo/GRDBDemo.xcodeproj/project.pbxproj b/Documentation/DemoApps/GRDBDemo/GRDBDemo.xcodeproj/project.pbxproj
new file mode 100644
index 0000000000..47782e0625
--- /dev/null
+++ b/Documentation/DemoApps/GRDBDemo/GRDBDemo.xcodeproj/project.pbxproj
@@ -0,0 +1,503 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 77;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 56CFC6772C9F1E1B000B5023 /* GRDB in Frameworks */ = {isa = PBXBuildFile; productRef = 56CFC6762C9F1E1B000B5023 /* GRDB */; };
+ 56CFC6E42C9F5AEA000B5023 /* GRDB in Frameworks */ = {isa = PBXBuildFile; productRef = 56CFC6E32C9F5AEA000B5023 /* GRDB */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ 56CFC6552C9F1DCA000B5023 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 56CFC63C2C9F1DC9000B5023 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 56CFC6432C9F1DC9000B5023;
+ remoteInfo = GRDBDemo;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXFileReference section */
+ 56CFC6442C9F1DC9000B5023 /* GRDBDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GRDBDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 56CFC6542C9F1DCA000B5023 /* GRDBDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GRDBDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 56CFC6B82C9F544D000B5023 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
+ 56CFC6AA2C9F3ADB000B5023 /* Exceptions for "GRDBDemo" folder in "GRDBDemo" target */ = {
+ isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
+ membershipExceptions = (
+ Info.plist,
+ );
+ target = 56CFC6432C9F1DC9000B5023 /* GRDBDemo */;
+ };
+/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
+
+/* Begin PBXFileSystemSynchronizedRootGroup section */
+ 56CFC6462C9F1DC9000B5023 /* GRDBDemo */ = {
+ isa = PBXFileSystemSynchronizedRootGroup;
+ exceptions = (
+ 56CFC6AA2C9F3ADB000B5023 /* Exceptions for "GRDBDemo" folder in "GRDBDemo" target */,
+ );
+ path = GRDBDemo;
+ sourceTree = "";
+ };
+ 56CFC6572C9F1DCA000B5023 /* GRDBDemoTests */ = {
+ isa = PBXFileSystemSynchronizedRootGroup;
+ path = GRDBDemoTests;
+ sourceTree = "";
+ };
+/* End PBXFileSystemSynchronizedRootGroup section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 56CFC6412C9F1DC9000B5023 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 56CFC6772C9F1E1B000B5023 /* GRDB in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 56CFC6512C9F1DCA000B5023 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 56CFC6E42C9F5AEA000B5023 /* GRDB in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 56CFC63B2C9F1DC9000B5023 = {
+ isa = PBXGroup;
+ children = (
+ 56CFC6B82C9F544D000B5023 /* README.md */,
+ 56CFC6462C9F1DC9000B5023 /* GRDBDemo */,
+ 56CFC6572C9F1DCA000B5023 /* GRDBDemoTests */,
+ 56CFC6E22C9F5AEA000B5023 /* Frameworks */,
+ 56CFC6452C9F1DC9000B5023 /* Products */,
+ );
+ sourceTree = "";
+ };
+ 56CFC6452C9F1DC9000B5023 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 56CFC6442C9F1DC9000B5023 /* GRDBDemo.app */,
+ 56CFC6542C9F1DCA000B5023 /* GRDBDemoTests.xctest */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 56CFC6E22C9F5AEA000B5023 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 56CFC6432C9F1DC9000B5023 /* GRDBDemo */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 56CFC6682C9F1DCB000B5023 /* Build configuration list for PBXNativeTarget "GRDBDemo" */;
+ buildPhases = (
+ 56CFC6402C9F1DC9000B5023 /* Sources */,
+ 56CFC6412C9F1DC9000B5023 /* Frameworks */,
+ 56CFC6422C9F1DC9000B5023 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ fileSystemSynchronizedGroups = (
+ 56CFC6462C9F1DC9000B5023 /* GRDBDemo */,
+ );
+ name = GRDBDemo;
+ packageProductDependencies = (
+ 56CFC6762C9F1E1B000B5023 /* GRDB */,
+ );
+ productName = GRDBDemo;
+ productReference = 56CFC6442C9F1DC9000B5023 /* GRDBDemo.app */;
+ productType = "com.apple.product-type.application";
+ };
+ 56CFC6532C9F1DCA000B5023 /* GRDBDemoTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 56CFC66B2C9F1DCB000B5023 /* Build configuration list for PBXNativeTarget "GRDBDemoTests" */;
+ buildPhases = (
+ 56CFC6502C9F1DCA000B5023 /* Sources */,
+ 56CFC6512C9F1DCA000B5023 /* Frameworks */,
+ 56CFC6522C9F1DCA000B5023 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 56CFC6562C9F1DCA000B5023 /* PBXTargetDependency */,
+ );
+ fileSystemSynchronizedGroups = (
+ 56CFC6572C9F1DCA000B5023 /* GRDBDemoTests */,
+ );
+ name = GRDBDemoTests;
+ packageProductDependencies = (
+ 56CFC6E32C9F5AEA000B5023 /* GRDB */,
+ );
+ productName = GRDBDemoTests;
+ productReference = 56CFC6542C9F1DCA000B5023 /* GRDBDemoTests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 56CFC63C2C9F1DC9000B5023 /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ BuildIndependentTargetsInParallel = 1;
+ LastSwiftUpdateCheck = 1600;
+ LastUpgradeCheck = 1600;
+ TargetAttributes = {
+ 56CFC6432C9F1DC9000B5023 = {
+ CreatedOnToolsVersion = 16.0;
+ };
+ 56CFC6532C9F1DCA000B5023 = {
+ CreatedOnToolsVersion = 16.0;
+ TestTargetID = 56CFC6432C9F1DC9000B5023;
+ };
+ };
+ };
+ buildConfigurationList = 56CFC63F2C9F1DC9000B5023 /* Build configuration list for PBXProject "GRDBDemo" */;
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 56CFC63B2C9F1DC9000B5023;
+ minimizedProjectReferenceProxies = 1;
+ packageReferences = (
+ 56CFC6752C9F1E1B000B5023 /* XCLocalSwiftPackageReference "../../../../GRDB.swift" */,
+ );
+ preferredProjectObjectVersion = 77;
+ productRefGroup = 56CFC6452C9F1DC9000B5023 /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 56CFC6432C9F1DC9000B5023 /* GRDBDemo */,
+ 56CFC6532C9F1DCA000B5023 /* GRDBDemoTests */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 56CFC6422C9F1DC9000B5023 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 56CFC6522C9F1DCA000B5023 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 56CFC6402C9F1DC9000B5023 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 56CFC6502C9F1DCA000B5023 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ 56CFC6562C9F1DCA000B5023 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 56CFC6432C9F1DC9000B5023 /* GRDBDemo */;
+ targetProxy = 56CFC6552C9F1DCA000B5023 /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin XCBuildConfiguration section */
+ 56CFC6662C9F1DCB000B5023 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 17.6;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ };
+ name = Debug;
+ };
+ 56CFC6672C9F1DCB000B5023 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 17.6;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 56CFC6692C9F1DCB000B5023 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_ASSET_PATHS = "\"GRDBDemo/Preview Content\"";
+ ENABLE_PREVIEWS = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = GRDBDemo/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = "GRDB Demo";
+ INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
+ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.github.groue.GRDBDemo;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 6.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 56CFC66A2C9F1DCB000B5023 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_ASSET_PATHS = "\"GRDBDemo/Preview Content\"";
+ ENABLE_PREVIEWS = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = GRDBDemo/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = "GRDB Demo";
+ INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
+ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.github.groue.GRDBDemo;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 6.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
+ 56CFC66C2C9F1DCB000B5023 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ GENERATE_INFOPLIST_FILE = YES;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.github.groue.GRDBDemoTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_VERSION = 6.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GRDBDemo.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/GRDBDemo";
+ };
+ name = Debug;
+ };
+ 56CFC66D2C9F1DCB000B5023 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ GENERATE_INFOPLIST_FILE = YES;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.github.groue.GRDBDemoTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_EMIT_LOC_STRINGS = NO;
+ SWIFT_VERSION = 6.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GRDBDemo.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/GRDBDemo";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 56CFC63F2C9F1DC9000B5023 /* Build configuration list for PBXProject "GRDBDemo" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 56CFC6662C9F1DCB000B5023 /* Debug */,
+ 56CFC6672C9F1DCB000B5023 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 56CFC6682C9F1DCB000B5023 /* Build configuration list for PBXNativeTarget "GRDBDemo" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 56CFC6692C9F1DCB000B5023 /* Debug */,
+ 56CFC66A2C9F1DCB000B5023 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 56CFC66B2C9F1DCB000B5023 /* Build configuration list for PBXNativeTarget "GRDBDemoTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 56CFC66C2C9F1DCB000B5023 /* Debug */,
+ 56CFC66D2C9F1DCB000B5023 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+
+/* Begin XCLocalSwiftPackageReference section */
+ 56CFC6752C9F1E1B000B5023 /* XCLocalSwiftPackageReference "../../../../GRDB.swift" */ = {
+ isa = XCLocalSwiftPackageReference;
+ relativePath = ../../../../GRDB.swift;
+ };
+/* End XCLocalSwiftPackageReference section */
+
+/* Begin XCSwiftPackageProductDependency section */
+ 56CFC6762C9F1E1B000B5023 /* GRDB */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = GRDB;
+ };
+ 56CFC6E32C9F5AEA000B5023 /* GRDB */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 56CFC6752C9F1E1B000B5023 /* XCLocalSwiftPackageReference "../../../../GRDB.swift" */;
+ productName = GRDB;
+ };
+/* End XCSwiftPackageProductDependency section */
+ };
+ rootObject = 56CFC63C2C9F1DC9000B5023 /* Project object */;
+}
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Documentation/DemoApps/GRDBDemo/GRDBDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata
similarity index 100%
rename from Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata
rename to Documentation/DemoApps/GRDBDemo/GRDBDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS.xcodeproj/xcshareddata/xcschemes/GRDBDemoiOS.xcscheme b/Documentation/DemoApps/GRDBDemo/GRDBDemo.xcodeproj/xcshareddata/xcschemes/GRDBDemo.xcscheme
similarity index 70%
rename from Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS.xcodeproj/xcshareddata/xcschemes/GRDBDemoiOS.xcscheme
rename to Documentation/DemoApps/GRDBDemo/GRDBDemo.xcodeproj/xcshareddata/xcschemes/GRDBDemo.xcscheme
index 20c4615d25..386a9790f8 100644
--- a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS.xcodeproj/xcshareddata/xcschemes/GRDBDemoiOS.xcscheme
+++ b/Documentation/DemoApps/GRDBDemo/GRDBDemo.xcodeproj/xcshareddata/xcschemes/GRDBDemo.xcscheme
@@ -1,10 +1,11 @@
+ LastUpgradeVersion = "1600"
+ version = "1.7">
+ buildImplicitDependencies = "YES"
+ buildArchitectures = "Automatic">
+ BlueprintIdentifier = "56CFC6432C9F1DC9000B5023"
+ BuildableName = "GRDBDemo.app"
+ BlueprintName = "GRDBDemo"
+ ReferencedContainer = "container:GRDBDemo.xcodeproj">
@@ -26,17 +27,18 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
- shouldUseLaunchSchemeArgsEnv = "YES">
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ shouldAutocreateTestPlan = "YES">
+ BlueprintIdentifier = "56CFC6532C9F1DCA000B5023"
+ BuildableName = "GRDBDemoTests.xctest"
+ BlueprintName = "GRDBDemoTests"
+ ReferencedContainer = "container:GRDBDemo.xcodeproj">
@@ -55,10 +57,10 @@
runnableDebuggingMode = "0">
+ BlueprintIdentifier = "56CFC6432C9F1DC9000B5023"
+ BuildableName = "GRDBDemo.app"
+ BlueprintName = "GRDBDemo"
+ ReferencedContainer = "container:GRDBDemo.xcodeproj">
@@ -79,10 +81,10 @@
runnableDebuggingMode = "0">
+ BlueprintIdentifier = "56CFC6432C9F1DC9000B5023"
+ BuildableName = "GRDBDemo.app"
+ BlueprintName = "GRDBDemo"
+ ReferencedContainer = "container:GRDBDemo.xcodeproj">
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/AppDatabase.swift b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Database/AppDatabase.swift
similarity index 67%
rename from Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/AppDatabase.swift
rename to Documentation/DemoApps/GRDBDemo/GRDBDemo/Database/AppDatabase.swift
index 8c38cd2a77..fca3988424 100644
--- a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/AppDatabase.swift
+++ b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Database/AppDatabase.swift
@@ -1,90 +1,33 @@
+import Foundation
import GRDB
import os.log
-/// `AppDatabase` lets the application access the database.
-///
-/// You create an `AppDatabase` with a connection to an SQLite database
-/// (see ).
-///
-/// Create those connections with a configuration returned from
-/// `AppDatabase/makeConfiguration(_:)`.
+/// The type that provides access to the application database.
///
/// For example:
///
/// ```swift
-/// // Create an in-memory AppDatabase
+/// // Create an empty, in-memory, AppDatabase
/// let config = AppDatabase.makeConfiguration()
/// let dbQueue = try DatabaseQueue(configuration: config)
/// let appDatabase = try AppDatabase(dbQueue)
/// ```
-struct AppDatabase {
- /// Creates an `AppDatabase`, and makes sure the database schema
+final class AppDatabase: Sendable {
+ /// Access to the database.
+ ///
+ /// See
+ private let dbWriter: any DatabaseWriter
+
+ /// Creates a `AppDatabase`, and makes sure the database schema
/// is ready.
///
/// - important: Create the `DatabaseWriter` with a configuration
/// returned by ``makeConfiguration(_:)``.
- init(_ dbWriter: any DatabaseWriter) throws {
+ init(_ dbWriter: any GRDB.DatabaseWriter) throws {
self.dbWriter = dbWriter
try migrator.migrate(dbWriter)
}
- /// Provides access to the database.
- ///
- /// Application can use a `DatabasePool`, and tests can use a fast
- /// in-memory `DatabaseQueue`.
- ///
- /// See
- private let dbWriter: any DatabaseWriter
-}
-
-// MARK: - Database Configuration
-
-extension AppDatabase {
- private static let sqlLogger = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "SQL")
-
- /// Returns a database configuration suited for `PlayerRepository`.
- ///
- /// SQL statements are logged if the `SQL_TRACE` environment variable
- /// is set.
- ///
- /// - parameter base: A base configuration.
- public static func makeConfiguration(_ base: Configuration = Configuration()) -> Configuration {
- var config = base
-
- // An opportunity to add required custom SQL functions or
- // collations, if needed:
- // config.prepareDatabase { db in
- // db.add(function: ...)
- // }
-
- // Log SQL statements if the `SQL_TRACE` environment variable is set.
- // See
- if ProcessInfo.processInfo.environment["SQL_TRACE"] != nil {
- config.prepareDatabase { db in
- db.trace {
- // It's ok to log statements publicly. Sensitive
- // information (statement arguments) are not logged
- // unless config.publicStatementArguments is set
- // (see below).
- os_log("%{public}@", log: sqlLogger, type: .debug, String(describing: $0))
- }
- }
- }
-
-#if DEBUG
- // Protect sensitive information by enabling verbose debugging in
- // DEBUG builds only.
- // See
- config.publicStatementArguments = true
-#endif
-
- return config
- }
-}
-
-// MARK: - Database Migrations
-
-extension AppDatabase {
/// The DatabaseMigrator that defines the database schema.
///
/// See
@@ -93,11 +36,11 @@ extension AppDatabase {
#if DEBUG
// Speed up development by nuking the database when migrations change
- // See
+ // See
migrator.eraseDatabaseOnSchemaChange = true
#endif
- migrator.registerMigration("createPlayer") { db in
+ migrator.registerMigration("v1") { db in
// Create a table
// See
try db.create(table: "player") { t in
@@ -116,8 +59,54 @@ extension AppDatabase {
}
}
+// MARK: - Database Configuration
+
+extension AppDatabase {
+ // Uncomment for enabling SQL logging
+ // private static let sqlLogger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "SQL")
+
+ /// Returns a database configuration suited for `AppDatabase`.
+ ///
+ /// SQL statements are logged if the `SQL_TRACE` environment variable
+ /// is set.
+ ///
+ /// - parameter base: A base configuration.
+ static func makeConfiguration(_ base: Configuration = Configuration()) -> Configuration {
+ var config = base
+
+ // Add custom SQL functions or collations, if needed:
+ // config.prepareDatabase { db in
+ // db.add(function: ...)
+ // }
+
+ // Uncomment for enabling SQL logging if the `SQL_TRACE` environment variable is set.
+ // See
+ // if ProcessInfo.processInfo.environment["SQL_TRACE"] != nil {
+ // config.prepareDatabase { db in
+ // let dbName = db.description
+ // db.trace { event in
+ // // Sensitive information (statement arguments) is not
+ // // logged unless config.publicStatementArguments is set
+ // // (see below).
+ // sqlLogger.debug("\(dbName): \(event)")
+ // }
+ // }
+ // }
+ //
+ // #if DEBUG
+ // // Protect sensitive information by enabling verbose debugging in
+ // // DEBUG builds only.
+ // // See
+ // config.publicStatementArguments = true
+ // #endif
+
+ return config
+ }
+}
+
// MARK: - Database Access: Writes
// The write methods execute invariant-preserving database transactions.
+// In this demo repository, they are pretty simple.
extension AppDatabase {
/// Saves (inserts or updates) a player. When the method returns, the
@@ -131,7 +120,7 @@ extension AppDatabase {
/// Delete the specified players
func deletePlayers(ids: [Int64]) throws {
try dbWriter.write { db in
- _ = try Player.deleteAll(db, ids: ids)
+ _ = try Player.deleteAll(db, keys: ids)
}
}
@@ -143,8 +132,8 @@ extension AppDatabase {
}
/// Refresh all players (by performing some random changes, for demo purpose).
- func refreshPlayers() throws {
- try dbWriter.write { db in
+ func refreshPlayers() async throws {
+ try await dbWriter.write { [self] db in
if try Player.all().isEmpty(db) {
// When database is empty, insert new random players
try createRandomPlayers(db)
@@ -193,8 +182,8 @@ extension AppDatabase {
// In your app, you are free to choose another path, and define focused
// reading methods.
extension AppDatabase {
- /// Provides a read-only access to the database
- var reader: DatabaseReader {
+ /// Provides a read-only access to the database.
+ var reader: any GRDB.DatabaseReader {
dbWriter
}
}
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Player.swift b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Database/Models/Player.swift
similarity index 90%
rename from Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Player.swift
rename to Documentation/DemoApps/GRDBDemo/GRDBDemo/Database/Models/Player.swift
index 7876b049df..0c63186f95 100644
--- a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Player.swift
+++ b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Database/Models/Player.swift
@@ -5,7 +5,7 @@ import GRDB
/// Identifiable conformance supports SwiftUI list animations, and type-safe
/// GRDB primary key methods.
/// Equatable conformance supports tests.
-struct Player: Identifiable, Equatable {
+struct Player: Equatable {
/// The player id.
///
/// Int64 is the recommended type for auto-incremented database ids.
@@ -47,14 +47,14 @@ extension Player {
}
}
-// MARK: - Persistence
+// MARK: - Database
/// Make Player a Codable Record.
///
/// See
extension Player: Codable, FetchableRecord, MutablePersistableRecord {
// Define database columns from CodingKeys
- fileprivate enum Columns {
+ enum Columns {
static let name = Column(CodingKeys.name)
static let score = Column(CodingKeys.score)
}
@@ -65,6 +65,9 @@ extension Player: Codable, FetchableRecord, MutablePersistableRecord {
}
}
+// Convenience access to player columns in this file
+private typealias Columns = Player.Columns
+
// MARK: - Player Database Requests
/// Define some player requests used by the application.
@@ -81,7 +84,7 @@ extension DerivableRequest {
func orderedByName() -> Self {
// Sort by name in a localized case insensitive fashion
// See https://github.com/groue/GRDB.swift/blob/master/README.md#string-comparison
- order(Player.Columns.name.collating(.localizedCaseInsensitiveCompare))
+ order(Columns.name.collating(.localizedCaseInsensitiveCompare))
}
/// A request of players ordered by score.
@@ -99,7 +102,7 @@ extension DerivableRequest {
// localized case insensitive fashion
// See https://github.com/groue/GRDB.swift/blob/master/README.md#string-comparison
order(
- Player.Columns.score.desc,
- Player.Columns.name.collating(.localizedCaseInsensitiveCompare))
+ Columns.score.desc,
+ Columns.name.collating(.localizedCaseInsensitiveCompare))
}
}
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Persistence.swift b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Database/Persistence.swift
similarity index 72%
rename from Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Persistence.swift
rename to Documentation/DemoApps/GRDBDemo/GRDBDemo/Database/Persistence.swift
index 05b0115789..94ab0ae79d 100644
--- a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Persistence.swift
+++ b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Database/Persistence.swift
@@ -9,41 +9,25 @@ extension AppDatabase {
do {
// Apply recommendations from
//
- //
+
// Create the "Application Support/Database" directory if needed
let fileManager = FileManager.default
let appSupportURL = try fileManager.url(
for: .applicationSupportDirectory, in: .userDomainMask,
appropriateFor: nil, create: true)
let directoryURL = appSupportURL.appendingPathComponent("Database", isDirectory: true)
-
- // Support for tests: delete the database if requested
- if CommandLine.arguments.contains("-reset") {
- try? fileManager.removeItem(at: directoryURL)
- }
-
- // Create the database folder if needed
try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true)
// Open or create the database
let databaseURL = directoryURL.appendingPathComponent("db.sqlite")
- NSLog("Database stored at \(databaseURL.path)")
- let dbPool = try DatabasePool(
- path: databaseURL.path,
- // Use default AppDatabase configuration
- configuration: AppDatabase.makeConfiguration())
+ let config = AppDatabase.makeConfiguration()
+ let dbPool = try DatabasePool(path: databaseURL.path, configuration: config)
// Create the AppDatabase
let appDatabase = try AppDatabase(dbPool)
- // Prepare the database with test fixtures if requested
- if CommandLine.arguments.contains("-fixedTestData") {
- try appDatabase.createPlayersForUITests()
- } else {
- // Otherwise, populate the database if it is empty, for better
- // demo purpose.
- try appDatabase.createRandomPlayersIfEmpty()
- }
+ // Populate the database if it is empty, for better demo purpose.
+ try appDatabase.createRandomPlayersIfEmpty()
return appDatabase
} catch {
diff --git a/Documentation/DemoApps/GRDBDemo/GRDBDemo/GRDBDemoApp.swift b/Documentation/DemoApps/GRDBDemo/GRDBDemo/GRDBDemoApp.swift
new file mode 100644
index 0000000000..a08232a76e
--- /dev/null
+++ b/Documentation/DemoApps/GRDBDemo/GRDBDemo/GRDBDemoApp.swift
@@ -0,0 +1,22 @@
+import SwiftUI
+
+@main
+struct GRDBDemoApp: App {
+ var body: some Scene {
+ WindowGroup {
+ PlayersNavigationView().appDatabase(.shared)
+ }
+ }
+}
+
+// MARK: - Give SwiftUI access to the database
+
+extension EnvironmentValues {
+ @Entry var appDatabase = AppDatabase.empty()
+}
+
+extension View {
+ func appDatabase(_ appDatabase: AppDatabase) -> some View {
+ self.environment(\.appDatabase, appDatabase)
+ }
+}
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Info.plist
similarity index 65%
rename from Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
rename to Documentation/DemoApps/GRDBDemo/GRDBDemo/Info.plist
index 18d981003d..0427f524a1 100644
--- a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
+++ b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Info.plist
@@ -2,7 +2,10 @@
- IDEDidComputeMac32BitWarning
-
+ UILaunchScreen
+
+ UIImageName
+ LaunchScreen
+
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Preview Content/Preview Assets.xcassets/Contents.json b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Preview Content/Preview Assets.xcassets/Contents.json
similarity index 100%
rename from Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Preview Content/Preview Assets.xcassets/Contents.json
rename to Documentation/DemoApps/GRDBDemo/GRDBDemo/Preview Content/Preview Assets.xcassets/Contents.json
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AccentColor.colorset/Contents.json b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Resources/Assets.xcassets/AccentColor.colorset/Contents.json
similarity index 100%
rename from Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/AccentColor.colorset/Contents.json
rename to Documentation/DemoApps/GRDBDemo/GRDBDemo/Resources/Assets.xcassets/AccentColor.colorset/Contents.json
diff --git a/Documentation/DemoApps/GRDBDemo/GRDBDemo/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000000..3395dad77a
--- /dev/null
+++ b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,37 @@
+{
+ "images" : [
+ {
+ "filename" : "Icon-Light-1024×1024.png",
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "filename" : "Icon-Dark-1024×1024.png",
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "tinted"
+ }
+ ],
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git "a/Documentation/DemoApps/GRDBDemo/GRDBDemo/Resources/Assets.xcassets/AppIcon.appiconset/Icon-Dark-1024\303\2271024.png" "b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Resources/Assets.xcassets/AppIcon.appiconset/Icon-Dark-1024\303\2271024.png"
new file mode 100644
index 0000000000..d20dbec5c8
Binary files /dev/null and "b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Resources/Assets.xcassets/AppIcon.appiconset/Icon-Dark-1024\303\2271024.png" differ
diff --git "a/Documentation/DemoApps/GRDBDemo/GRDBDemo/Resources/Assets.xcassets/AppIcon.appiconset/Icon-Light-1024\303\2271024.png" "b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Resources/Assets.xcassets/AppIcon.appiconset/Icon-Light-1024\303\2271024.png"
new file mode 100644
index 0000000000..f456cd22f7
Binary files /dev/null and "b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Resources/Assets.xcassets/AppIcon.appiconset/Icon-Light-1024\303\2271024.png" differ
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/Contents.json b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Resources/Assets.xcassets/Contents.json
similarity index 100%
rename from Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/Contents.json
rename to Documentation/DemoApps/GRDBDemo/GRDBDemo/Resources/Assets.xcassets/Contents.json
diff --git a/Documentation/DemoApps/GRDBDemo/GRDBDemo/Resources/Assets.xcassets/LaunchScreen.imageset/Contents.json b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Resources/Assets.xcassets/LaunchScreen.imageset/Contents.json
new file mode 100644
index 0000000000..8988614712
--- /dev/null
+++ b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Resources/Assets.xcassets/LaunchScreen.imageset/Contents.json
@@ -0,0 +1,22 @@
+{
+ "images" : [
+ {
+ "filename" : "LaunchIcon.pdf",
+ "idiom" : "universal"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "filename" : "LaunchIcon~Dark.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/LaunchIcon.imageset/LaunchIcon.pdf b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Resources/Assets.xcassets/LaunchScreen.imageset/LaunchIcon.pdf
similarity index 100%
rename from Documentation/DemoApps/GRDBAsyncDemo/GRDBAsyncDemo/Resources/Assets.xcassets/LaunchIcon.imageset/LaunchIcon.pdf
rename to Documentation/DemoApps/GRDBDemo/GRDBDemo/Resources/Assets.xcassets/LaunchScreen.imageset/LaunchIcon.pdf
diff --git a/Documentation/DemoApps/GRDBDemo/GRDBDemo/Resources/Assets.xcassets/LaunchScreen.imageset/LaunchIcon~Dark.pdf b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Resources/Assets.xcassets/LaunchScreen.imageset/LaunchIcon~Dark.pdf
new file mode 100644
index 0000000000..cc67d97963
Binary files /dev/null and b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Resources/Assets.xcassets/LaunchScreen.imageset/LaunchIcon~Dark.pdf differ
diff --git a/Documentation/DemoApps/GRDBDemo/GRDBDemo/Resources/Localizable.xcstrings b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Resources/Localizable.xcstrings
new file mode 100644
index 0000000000..8d20dbef02
--- /dev/null
+++ b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Resources/Localizable.xcstrings
@@ -0,0 +1,89 @@
+{
+ "sourceLanguage" : "en",
+ "strings" : {
+ "" : {
+
+ },
+ "%lld Players" : {
+ "localizations" : {
+ "en" : {
+ "variations" : {
+ "plural" : {
+ "one" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "1 Player"
+ }
+ },
+ "other" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "%lld Players"
+ }
+ },
+ "zero" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "No Player"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "%lld points" : {
+ "localizations" : {
+ "en" : {
+ "variations" : {
+ "plural" : {
+ "one" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "1 point"
+ }
+ },
+ "other" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "%lld points"
+ }
+ },
+ "zero" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "0 point"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "Add Player" : {
+
+ },
+ "Anonymous" : {
+
+ },
+ "Cancel" : {
+
+ },
+ "Name" : {
+
+ },
+ "New Player" : {
+
+ },
+ "Save" : {
+
+ },
+ "Score" : {
+
+ },
+ "The team is empty!" : {
+
+ }
+ },
+ "version" : "1.0"
+}
\ No newline at end of file
diff --git a/Documentation/DemoApps/GRDBDemo/GRDBDemo/Views/PlayerCreationSheet.swift b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Views/PlayerCreationSheet.swift
new file mode 100644
index 0000000000..c526348041
--- /dev/null
+++ b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Views/PlayerCreationSheet.swift
@@ -0,0 +1,39 @@
+import SwiftUI
+
+/// A view that creates a `Player`. Display it as a sheet.
+struct PlayerCreationSheet: View {
+ @Environment(\.appDatabase) var appDatabase
+ @Environment(\.dismiss) var dismiss
+ @State var form = PlayerForm(name: "", score: nil)
+
+ var body: some View {
+ NavigationStack {
+ Form {
+ PlayerFormView(form: $form)
+ }
+ .navigationTitle("New Player")
+ .toolbar {
+ ToolbarItem(placement: .cancellationAction) {
+ Button("Cancel") { dismiss() }
+ }
+ ToolbarItem(placement: .confirmationAction) {
+ Button("Save") {
+ save()
+ }
+ }
+ }
+ }
+ }
+
+ private func save() {
+ var player = Player(name: form.name, score: form.score ?? 0)
+ try? appDatabase.savePlayer(&player)
+ dismiss()
+ }
+}
+
+// MARK: - Previews
+
+#Preview {
+ PlayerCreationSheet()
+}
diff --git a/Documentation/DemoApps/GRDBDemo/GRDBDemo/Views/PlayerEditionView.swift b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Views/PlayerEditionView.swift
new file mode 100644
index 0000000000..0bbe76aa76
--- /dev/null
+++ b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Views/PlayerEditionView.swift
@@ -0,0 +1,41 @@
+import SwiftUI
+
+struct PlayerEditionView: View {
+ @Environment(\.isPresented) var isPresented
+ @Environment(\.appDatabase) var appDatabase
+ @State var form: PlayerForm
+ var player: Player
+
+ init(player: Player) {
+ self.player = player
+ self._form = State(initialValue: PlayerForm(name: player.name, score: player.score))
+ }
+
+ var body: some View {
+ Form {
+ PlayerFormView(form: $form)
+ }
+ .navigationTitle(player.name)
+ .onChange(of: isPresented) {
+ if !isPresented {
+ // Back button was pressed
+ save()
+ }
+ }
+ }
+
+ private func save() {
+ var player = player
+ player.name = form.name
+ player.score = form.score ?? 0
+ try? appDatabase.savePlayer(&player)
+ }
+}
+
+// MARK: - Previews
+
+#Preview {
+ NavigationStack {
+ PlayerEditionView(player: .makeRandom())
+ }
+}
diff --git a/Documentation/DemoApps/GRDBDemo/GRDBDemo/Views/PlayerFormView.swift b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Views/PlayerFormView.swift
new file mode 100644
index 0000000000..32172b020e
--- /dev/null
+++ b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Views/PlayerFormView.swift
@@ -0,0 +1,64 @@
+import SwiftUI
+
+/// A view that edits a `PlayerForm`.
+struct PlayerFormView: View {
+ @Binding var form: PlayerForm
+
+ private enum FocusElement {
+ case name
+ case score
+ }
+ @FocusState private var focusedElement: FocusElement?
+
+ var body: some View {
+ Group {
+ LabeledContent {
+ TextField(text: $form.name) { EmptyView() }
+ .textInputAutocapitalization(.words)
+ .autocorrectionDisabled()
+ .submitLabel(.next)
+ .focused($focusedElement, equals: .name)
+ .labelsHidden()
+ .onSubmit {
+ focusedElement = .score
+ }
+ } label: {
+ Text("Name").foregroundStyle(.secondary)
+ }
+
+ LabeledContent {
+ TextField(value: $form.score, format: .number) { EmptyView() }
+ .keyboardType(.numberPad)
+ .focused($focusedElement, equals: .score)
+ .labelsHidden()
+ } label: {
+ Text("Score").foregroundStyle(.secondary)
+ }
+ }
+ .onAppear { focusedElement = .name }
+ }
+}
+
+/// The model edited by `PlayerFormView`.
+struct PlayerForm {
+ var name: String
+ var score: Int?
+}
+
+// MARK: - Previews
+
+#Preview("Prefilled") {
+ @Previewable @State var form = PlayerForm(name: "John", score: 100)
+
+ Form {
+ PlayerFormView(form: $form)
+ }
+}
+
+#Preview("Empty") {
+ @Previewable @State var form = PlayerForm(name: "", score: nil)
+
+ Form {
+ PlayerFormView(form: $form)
+ }
+}
diff --git a/Documentation/DemoApps/GRDBDemo/GRDBDemo/Views/PlayerListModel.swift b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Views/PlayerListModel.swift
new file mode 100644
index 0000000000..74dc501ece
--- /dev/null
+++ b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Views/PlayerListModel.swift
@@ -0,0 +1,88 @@
+import Foundation
+import Observation
+import GRDB
+
+/// The observable model that drives the main navigation view.
+///
+/// It observes the database in order to always display an up-to-date list
+/// of players.
+///
+/// This class is testable. See `PlayerListModelTests.swift`.
+@Observable @MainActor final class PlayerListModel {
+ /// A player ordering
+ enum Ordering {
+ case byName
+ case byScore
+ }
+
+ /// The player ordering
+ var ordering = Ordering.byScore {
+ didSet { observePlayers() }
+ }
+
+ /// The players.
+ ///
+ /// The array remains empty until `observePlayers()` is called.
+ var players: [Player] = []
+
+ private let appDatabase: AppDatabase
+ @ObservationIgnored private var cancellable: AnyDatabaseCancellable?
+
+ // MARK: - Initialization
+
+ /// Creates a `PlayerListModel`.
+ init(appDatabase: AppDatabase) {
+ self.appDatabase = appDatabase
+ }
+
+ /// Start observing the database.
+ func observePlayers() {
+ // We observe all players, sorted according to `ordering`.
+ let observation = ValueObservation.tracking { [ordering] db in
+ switch ordering {
+ case .byName:
+ try Player.all().orderedByName().fetchAll(db)
+ case .byScore:
+ try Player.all().orderedByScore().fetchAll(db)
+ }
+ }
+
+ // Start observing the database.
+ // Previous observation, if any, is cancelled.
+ cancellable = observation.start(in: appDatabase.reader) { error in
+ // Handle error
+ } onChange: { [unowned self] players in
+ self.players = players
+ }
+ }
+
+ // MARK: - Actions
+
+ /// Delete players at specified indexes in `self.players`.
+ func deletePlayers(at offsets: IndexSet) throws {
+ let playerIds = offsets.compactMap { players[$0].id }
+ try appDatabase.deletePlayers(ids: playerIds)
+ }
+
+ /// Delete all players.
+ func deleteAllPlayers() throws {
+ try appDatabase.deleteAllPlayers()
+ }
+
+ /// Refresh all players (by performing some random changes, for demo purpose).
+ func refreshPlayers() async throws {
+ try await appDatabase.refreshPlayers()
+ }
+
+ /// Perform 50 refreshes in parallel, for demo purpose.
+ func refreshPlayersManyTimes() async throws {
+ try await withThrowingTaskGroup(of: Void.self) { group in
+ for _ in 0..<50 {
+ group.addTask {
+ try await AppDatabase.shared.refreshPlayers()
+ }
+ }
+ for try await _ in group { }
+ }
+ }
+}
diff --git a/Documentation/DemoApps/GRDBDemo/GRDBDemo/Views/PlayerListView.swift b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Views/PlayerListView.swift
new file mode 100644
index 0000000000..0f3316512c
--- /dev/null
+++ b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Views/PlayerListView.swift
@@ -0,0 +1,76 @@
+import SwiftUI
+
+/// A view that displays a list of players.
+struct PlayerListView: View {
+ @Bindable var model: PlayerListModel
+
+ var body: some View {
+ List {
+ ForEach(model.players, id: \.id) { player in
+ NavigationLink {
+ PlayerEditionView(player: player)
+ } label: {
+ PlayerRow(player: player)
+ }
+ }
+ .onDelete { offsets in
+ try? model.deletePlayers(at: offsets)
+ }
+ }
+ .animation(.default, value: model.players)
+ .listStyle(.plain)
+ .navigationTitle("\(model.players.count) Players")
+ }
+}
+
+struct PlayerRow: View {
+ var player: Player
+
+ var body: some View {
+ HStack {
+ Group {
+ if player.name.isEmpty {
+ Text("Anonymous").italic()
+ } else {
+ Text(player.name)
+ }
+ }
+ .frame(maxWidth: .infinity, alignment: .leading)
+
+ Text("\(player.score) points")
+ .monospacedDigit()
+ .foregroundStyle(.secondary)
+ }
+ }
+}
+
+// MARK: - Previews
+
+#Preview {
+ struct Preview: View {
+ @Environment(\.appDatabase) var appDatabase
+
+ var body: some View {
+ // This technique makes it possible to create an observable object
+ // (PlayerListModel) from the SwiftUI environment.
+ ContentView(appDatabase: appDatabase)
+ }
+ }
+
+ struct ContentView: View {
+ @State var model: PlayerListModel
+
+ init(appDatabase: AppDatabase) {
+ _model = State(initialValue: PlayerListModel(appDatabase: appDatabase))
+ }
+
+ var body: some View {
+ NavigationStack {
+ PlayerListView(model: model)
+ }
+ .onAppear { model.observePlayers() }
+ }
+ }
+
+ return Preview().appDatabase(.random())
+}
diff --git a/Documentation/DemoApps/GRDBDemo/GRDBDemo/Views/PlayersNavigationView.swift b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Views/PlayersNavigationView.swift
new file mode 100644
index 0000000000..b6e96ac2d6
--- /dev/null
+++ b/Documentation/DemoApps/GRDBDemo/GRDBDemo/Views/PlayersNavigationView.swift
@@ -0,0 +1,181 @@
+import SwiftUI
+
+/// The main navigation view.
+struct PlayersNavigationView: View {
+ @Environment(\.appDatabase) var appDatabase
+
+ var body: some View {
+ // This technique makes it possible to create an observable object
+ // (PlayerListModel) from the SwiftUI environment.
+ ContentView(appDatabase: appDatabase)
+ }
+}
+
+private struct ContentView: View {
+ /// The model for the player list.
+ @State var model: PlayerListModel
+
+ /// Tracks the edit mode of the player list.
+ @State var editMode = EditMode.inactive
+
+ /// Tracks the presentation of the player creation sheet.
+ @State var presentsCreationSheet = false
+
+ init(appDatabase: AppDatabase) {
+ _model = State(initialValue: PlayerListModel(appDatabase: appDatabase))
+ }
+
+ var body: some View {
+ NavigationStack {
+ contentView
+ .toolbar { bottomBarContent }
+ .environment(\.editMode, $editMode)
+ }
+ .onAppear {
+ model.observePlayers()
+ }
+ .onChange(of: model.ordering) {
+ // Stop editing when ordering is modified
+ stopEditing()
+ }
+ .onChange(of: model.players.isEmpty) {
+ // Stop editing when the last player is deleted.
+ if model.players.isEmpty {
+ stopEditing()
+ }
+ }
+ .sheet(isPresented: $presentsCreationSheet) {
+ PlayerCreationSheet()
+ }
+ }
+
+ @ViewBuilder
+ private var contentView: some View {
+ if model.players.isEmpty {
+ emptyPlayersView
+ } else {
+ PlayerListView(model: model)
+ .toolbar {
+ ToolbarItemGroup(placement: .topBarLeading) {
+ presentCreationSheetButton
+ EditButton()
+ }
+ ToolbarItem(placement: .topBarTrailing) {
+ ToggleOrderingButton(ordering: $model.ordering)
+ }
+ }
+ }
+ }
+
+ private var emptyPlayersView: some View {
+ ContentUnavailableView {
+ Label("The team is empty!", systemImage: "person.slash")
+ } actions: {
+ Button("Add Player") {
+ presentsCreationSheet = true
+ }
+ .buttonStyle(.borderedProminent)
+ }
+ // Hide the title, but set a string anyway in order to avoid
+ // an odd relayout when player list becomes empty during
+ // the tornado.
+ .navigationTitle("")
+ }
+
+ @ToolbarContentBuilder
+ private var bottomBarContent: some ToolbarContent {
+ ToolbarItemGroup(placement: .bottomBar) {
+ deleteAllButton
+ Spacer()
+ refreshButton
+ Spacer()
+ tornadoButton
+ }
+ }
+
+ private var presentCreationSheetButton: some View {
+ Button {
+ stopEditing()
+ presentsCreationSheet = true
+ } label: {
+ Image(systemName: "plus")
+ }
+ }
+
+ private var deleteAllButton: some View {
+ Button {
+ try? model.deleteAllPlayers()
+ } label: {
+ Image(systemName: "trash")
+ }
+ }
+
+ private var refreshButton: some View {
+ Button {
+ Task {
+ stopEditing()
+ try? await model.refreshPlayers()
+ }
+ } label: {
+ Image(systemName: "arrow.clockwise")
+ }
+ }
+
+ private var tornadoButton: some View {
+ Button {
+ Task {
+ stopEditing()
+ try? await model.refreshPlayersManyTimes()
+ }
+ } label: {
+ Image(systemName: "tornado")
+ }
+ }
+
+ private func stopEditing() {
+ withAnimation {
+ editMode = .inactive
+ }
+ }
+}
+
+private struct ToggleOrderingButton: View {
+ @Binding var ordering: PlayerListModel.Ordering
+
+ var body: some View {
+ switch ordering {
+ case .byName:
+ Button {
+ ordering = .byScore
+ } label: {
+ buttonLabel("Name", systemImage: "arrowtriangle.up.fill")
+ }
+ case .byScore:
+ Button {
+ ordering = .byName
+ } label: {
+ buttonLabel("Score", systemImage: "arrowtriangle.down.fill")
+ }
+ }
+ }
+
+ private func buttonLabel(_ title: LocalizedStringKey, systemImage: String) -> some View {
+ HStack {
+ Text(title)
+ Image(systemName: systemImage)
+ .imageScale(.medium)
+ }
+ }
+}
+
+// MARK: - Previews
+
+#Preview("Populated") {
+ PlayersNavigationView()
+ .appDatabase(.random())
+}
+
+#Preview("Empty") {
+ PlayersNavigationView()
+ .appDatabase(.empty())
+}
diff --git a/Documentation/DemoApps/GRDBDemo/GRDBDemoTests/AppDatabaseTests.swift b/Documentation/DemoApps/GRDBDemo/GRDBDemoTests/AppDatabaseTests.swift
new file mode 100644
index 0000000000..29b22ad86b
--- /dev/null
+++ b/Documentation/DemoApps/GRDBDemo/GRDBDemoTests/AppDatabaseTests.swift
@@ -0,0 +1,58 @@
+import Testing
+import GRDB
+@testable import GRDBDemo
+
+struct AppDatabaseTests {
+ @Test func insert() throws {
+ // Given an empty database
+ let appDatabase = try makeEmptyTestDatabase()
+
+ // When we insert a player
+ var insertedPlayer = Player(name: "Arthur", score: 1000)
+ try appDatabase.savePlayer(&insertedPlayer)
+
+ // Then the inserted player has an id
+ #expect(insertedPlayer.id != nil)
+
+ // Then the inserted player exists in the database
+ let fetchedPlayer = try appDatabase.reader.read(Player.fetchOne)
+ #expect(fetchedPlayer == insertedPlayer)
+ }
+
+ @Test func update() throws {
+ // Given a database that contains a player
+ let appDatabase = try makeEmptyTestDatabase()
+ var insertedPlayer = Player(name: "Arthur", score: 1000)
+ try appDatabase.savePlayer(&insertedPlayer)
+
+ // When we update a player
+ var updatedPlayer = insertedPlayer
+ updatedPlayer.name = "Barbara"
+ updatedPlayer.score = 0
+ try appDatabase.savePlayer(&updatedPlayer)
+
+ // Then the player is updated
+ let fetchedPlayer = try appDatabase.reader.read(Player.fetchOne)
+ #expect(fetchedPlayer == updatedPlayer)
+ }
+
+ @Test func deleteAll() throws {
+ // Given a database that contains a player
+ let appDatabase = try makeEmptyTestDatabase()
+ var player = Player(name: "Arthur", score: 1000)
+ try appDatabase.savePlayer(&player)
+
+ // When we delete all players
+ try appDatabase.deleteAllPlayers()
+
+ // Then no player exists
+ let count = try appDatabase.reader.read(Player.fetchCount(_:))
+ #expect(count == 0)
+ }
+
+ /// Return an empty, in-memory, `AppDatabase`.
+ private func makeEmptyTestDatabase() throws -> AppDatabase {
+ let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
+ return try AppDatabase(dbQueue)
+ }
+}
diff --git a/Documentation/DemoApps/GRDBDemo/GRDBDemoTests/PlayerListModelTests.swift b/Documentation/DemoApps/GRDBDemo/GRDBDemoTests/PlayerListModelTests.swift
new file mode 100644
index 0000000000..18c386a44e
--- /dev/null
+++ b/Documentation/DemoApps/GRDBDemo/GRDBDemoTests/PlayerListModelTests.swift
@@ -0,0 +1,83 @@
+import Testing
+import GRDB
+@testable import GRDBDemo
+
+struct PlayerListModelTests {
+ // MARK: - PlayerListModel.observePlayers tests
+
+ @Test(.timeLimit(.minutes(1)))
+ @MainActor func observation_started_after_player_creation() async throws {
+ // Given a PlayerListModel on a database that contains a player
+ let appDatabase = try makeEmptyTestDatabase()
+ var player = Player(name: "Arthur", score: 1000)
+ try appDatabase.savePlayer(&player)
+ let model = PlayerListModel(appDatabase: appDatabase)
+
+ // When we start observing the database
+ model.observePlayers()
+
+ // Then the model eventually fetches the player.
+ // We poll because we do not know when the model will update its players.
+ await pollUntil {
+ model.players.isEmpty == false
+ }
+ #expect(model.players == [player])
+ }
+
+ @Test(.timeLimit(.minutes(1)))
+ @MainActor func observation_started_before_player_creation() async throws {
+ // Given a PlayerListModel that observes a empty database
+ let appDatabase = try makeEmptyTestDatabase()
+ let model = PlayerListModel(appDatabase: appDatabase)
+ model.observePlayers()
+
+ // When we insert a player
+ var player = Player(name: "Arthur", score: 1000)
+ try appDatabase.savePlayer(&player)
+
+ // Then the model eventually fetches the player.
+ // We poll because we do not know when the model will update its players.
+ await pollUntil {
+ model.players.isEmpty == false
+ }
+ #expect(model.players == [player])
+ }
+
+ @Test
+ @MainActor func test_deleteAllPlayers_deletes_players_in_the_database() async throws {
+ // Given a PlayerListModel on a database that contains a player
+ let appDatabase = try makeEmptyTestDatabase()
+ var player = Player(name: "Arthur", score: 1000)
+ try appDatabase.savePlayer(&player)
+ let model = PlayerListModel(appDatabase: appDatabase)
+
+ // When we delete all players
+ try model.deleteAllPlayers()
+
+ // Then the database is empty.
+ let playerCount = try await appDatabase.reader.read { db in
+ try Player.fetchCount(db)
+ }
+ #expect(playerCount == 0)
+ }
+
+ /// Return an empty, in-memory, `AppDatabase`.
+ private func makeEmptyTestDatabase() throws -> AppDatabase {
+ let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
+ return try AppDatabase(dbQueue)
+ }
+
+ /// Convenience method that loops until a condition is met.
+ private func pollUntil(condition: @escaping @MainActor () async -> Bool) async {
+ await confirmation { confirmation in
+ while true {
+ if await condition() {
+ confirmation()
+ return
+ } else {
+ await Task.yield()
+ }
+ }
+ }
+ }
+}
diff --git a/Documentation/DemoApps/GRDBDemo/README.md b/Documentation/DemoApps/GRDBDemo/README.md
new file mode 100644
index 0000000000..24ecf51a2c
--- /dev/null
+++ b/Documentation/DemoApps/GRDBDemo/README.md
@@ -0,0 +1,36 @@
+GRDBDemo Application
+====================
+
+
+
+**GRDBDemo demonstrates how GRDB can fuel a SwiftUI application.**
+
+> **Note**: This demo app is not a project template. Do not copy it as a starting point for your application. Instead, create a new project, choose a GRDB [installation method](../../../README.md#installation), and use the demo as an inspiration.
+
+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 [ValueObservation](https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/valueobservation).
+- How to apply the recommendations of [Recommended Practices for Designing Record Types](https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/recordrecommendedpractices).
+- How to feed SwiftUI previews with a transient database.
+
+**Files of interest:**
+
+- [GRDBDemoApp.swift](GRDBDemo/GRDBDemoApp.swift)
+
+ `GRDBDemoApp` feeds the SwiftUI app with a database, through the SwiftUI environment.
+
+- [AppDatabase.swift](GRDBDemo/Database/AppDatabase.swift)
+
+ `AppDatabase` is the type that grants database access. It uses [DatabaseMigrator](https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databasemigrator) in order to setup the database schema, and provides methods that read and write.
+
+- [Persistence.swift](GRDBDemo/Database/Persistence.swift)
+
+ This file instantiates various `AppDatabase` for the various projects needs: one database on disk for the application, and in-memory databases for SwiftUI previews.
+
+- [Player.swift](GRDBDemo/Database/Models/Player.swift)
+
+ `Player` is a [Record](../../../README.md#records) type, able to read and write in the database. It conforms to the standard Codable protocol in order to gain all advantages of [Codable Records](../../../README.md#codable-records).
+
+- [GRDBDemoTests](GRDBDemoTests)
diff --git a/Documentation/DemoApps/GRDBDemo/Screenshot.png b/Documentation/DemoApps/GRDBDemo/Screenshot.png
new file mode 100644
index 0000000000..ded3f50acd
Binary files /dev/null and b/Documentation/DemoApps/GRDBDemo/Screenshot.png differ
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS Extension/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS Extension/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json
deleted file mode 100644
index 9be9adbf7d..0000000000
--- a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS Extension/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "images" : [
- {
- "idiom" : "watch",
- "screenWidth" : "{130,145}",
- "scale" : "2x"
- },
- {
- "idiom" : "watch",
- "screenWidth" : "{146,165}",
- "scale" : "2x"
- }
- ],
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
\ No newline at end of file
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS Extension/Assets.xcassets/Complication.complicationset/Contents.json b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS Extension/Assets.xcassets/Complication.complicationset/Contents.json
deleted file mode 100644
index 2eca9a1f46..0000000000
--- a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS Extension/Assets.xcassets/Complication.complicationset/Contents.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{
- "assets" : [
- {
- "idiom" : "watch",
- "filename" : "Circular.imageset",
- "role" : "circular"
- },
- {
- "idiom" : "watch",
- "filename" : "Extra Large.imageset",
- "role" : "extra-large"
- },
- {
- "idiom" : "watch",
- "filename" : "Modular.imageset",
- "role" : "modular"
- },
- {
- "idiom" : "watch",
- "filename" : "Utilitarian.imageset",
- "role" : "utilitarian"
- }
- ],
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS Extension/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS Extension/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json
deleted file mode 100644
index 9be9adbf7d..0000000000
--- a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS Extension/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "images" : [
- {
- "idiom" : "watch",
- "screenWidth" : "{130,145}",
- "scale" : "2x"
- },
- {
- "idiom" : "watch",
- "screenWidth" : "{146,165}",
- "scale" : "2x"
- }
- ],
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
\ No newline at end of file
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS Extension/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS Extension/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json
deleted file mode 100644
index 9be9adbf7d..0000000000
--- a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS Extension/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "images" : [
- {
- "idiom" : "watch",
- "screenWidth" : "{130,145}",
- "scale" : "2x"
- },
- {
- "idiom" : "watch",
- "screenWidth" : "{146,165}",
- "scale" : "2x"
- }
- ],
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
\ No newline at end of file
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS Extension/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS Extension/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json
deleted file mode 100644
index 9be9adbf7d..0000000000
--- a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS Extension/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "images" : [
- {
- "idiom" : "watch",
- "screenWidth" : "{130,145}",
- "scale" : "2x"
- },
- {
- "idiom" : "watch",
- "screenWidth" : "{146,165}",
- "scale" : "2x"
- }
- ],
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
\ No newline at end of file
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS Extension/ExtensionDelegate.swift b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS Extension/ExtensionDelegate.swift
deleted file mode 100644
index 320f84a6b2..0000000000
--- a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS Extension/ExtensionDelegate.swift
+++ /dev/null
@@ -1,48 +0,0 @@
-import WatchKit
-
-class ExtensionDelegate: NSObject, WKExtensionDelegate {
-
- func applicationDidFinishLaunching() {
- // Perform any final initialization of your application.
- }
-
- func applicationDidBecomeActive() {
- // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
- }
-
- func applicationWillResignActive() {
- // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
- // Use this method to pause ongoing tasks, disable timers, etc.
- }
-
- func handle(_ backgroundTasks: Set) {
- // Sent when the system needs to launch the application in the background to process tasks. Tasks arrive in a set, so loop through and process each one.
- for task in backgroundTasks {
- // Use a switch statement to check the task type
- switch task {
- case let backgroundTask as WKApplicationRefreshBackgroundTask:
- // Be sure to complete the background task once you’re done.
- backgroundTask.setTaskCompletedWithSnapshot(false)
- case let snapshotTask as WKSnapshotRefreshBackgroundTask:
- // Snapshot tasks have a unique completion call, make sure to set your expiration date
- snapshotTask.setTaskCompleted(restoredDefaultState: true, estimatedSnapshotExpiration: Date.distantFuture, userInfo: nil)
- case let connectivityTask as WKWatchConnectivityRefreshBackgroundTask:
- // Be sure to complete the connectivity task once you’re done.
- connectivityTask.setTaskCompletedWithSnapshot(false)
- case let urlSessionTask as WKURLSessionRefreshBackgroundTask:
- // Be sure to complete the URL session task once you’re done.
- urlSessionTask.setTaskCompletedWithSnapshot(false)
- case let relevantShortcutTask as WKRelevantShortcutRefreshBackgroundTask:
- // Be sure to complete the relevant-shortcut task once you're done.
- relevantShortcutTask.setTaskCompletedWithSnapshot(false)
- case let intentDidRunTask as WKIntentDidRunRefreshBackgroundTask:
- // Be sure to complete the intent-did-run task once you're done.
- intentDidRunTask.setTaskCompletedWithSnapshot(false)
- default:
- // make sure to complete unhandled task types
- task.setTaskCompletedWithSnapshot(false)
- }
- }
- }
-
-}
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS Extension/Info.plist b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS Extension/Info.plist
deleted file mode 100644
index 0881b8b30e..0000000000
--- a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS Extension/Info.plist
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
- CFBundleDevelopmentRegion
- en
- CFBundleDisplayName
- GRDBDemoWatchOS Extension
- CFBundleExecutable
- $(EXECUTABLE_NAME)
- CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- $(PRODUCT_NAME)
- CFBundlePackageType
- XPC!
- CFBundleShortVersionString
- 1.0
- CFBundleVersion
- 1
- NSExtension
-
- NSExtensionAttributes
-
- WKAppBundleIdentifier
- com.github.groue.GRDBDemoiOS2.watchkitapp
-
- NSExtensionPointIdentifier
- com.apple.watchkit
-
- WKExtensionDelegateClassName
- $(PRODUCT_MODULE_NAME).ExtensionDelegate
-
-
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS Extension/InterfaceController.swift b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS Extension/InterfaceController.swift
deleted file mode 100644
index 71d59c7948..0000000000
--- a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS Extension/InterfaceController.swift
+++ /dev/null
@@ -1,15 +0,0 @@
-import WatchKit
-import Foundation
-import GRDB
-
-class InterfaceController: WKInterfaceController {
-
- @IBOutlet var versionLabel: WKInterfaceLabel!
-
- override func awake(withContext context: Any?) {
- super.awake(withContext: context)
-
- let sqliteVersion = String(cString: sqlite3_libversion(), encoding: .utf8)
- versionLabel.setText(sqliteVersion)
- }
-}
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS/Assets.xcassets/AppIcon.appiconset/Contents.json
deleted file mode 100644
index dd221ba54d..0000000000
--- a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS/Assets.xcassets/AppIcon.appiconset/Contents.json
+++ /dev/null
@@ -1,55 +0,0 @@
-{
- "images" : [
- {
- "size" : "24x24",
- "idiom" : "watch",
- "scale" : "2x",
- "role" : "notificationCenter",
- "subtype" : "38mm"
- },
- {
- "size" : "27.5x27.5",
- "idiom" : "watch",
- "scale" : "2x",
- "role" : "notificationCenter",
- "subtype" : "42mm"
- },
- {
- "size" : "29x29",
- "idiom" : "watch",
- "role" : "companionSettings",
- "scale" : "2x"
- },
- {
- "size" : "29x29",
- "idiom" : "watch",
- "role" : "companionSettings",
- "scale" : "3x"
- },
- {
- "size" : "40x40",
- "idiom" : "watch",
- "scale" : "2x",
- "role" : "appLauncher",
- "subtype" : "38mm"
- },
- {
- "size" : "86x86",
- "idiom" : "watch",
- "scale" : "2x",
- "role" : "quickLook",
- "subtype" : "38mm"
- },
- {
- "size" : "98x98",
- "idiom" : "watch",
- "scale" : "2x",
- "role" : "quickLook",
- "subtype" : "42mm"
- }
- ],
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS/Base.lproj/Interface.storyboard b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS/Base.lproj/Interface.storyboard
deleted file mode 100644
index 4988b4b688..0000000000
--- a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS/Base.lproj/Interface.storyboard
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS/Info.plist b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS/Info.plist
deleted file mode 100644
index 0478fa15f8..0000000000
--- a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoWatchOS/Info.plist
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
-
- CFBundleDevelopmentRegion
- en
- CFBundleDisplayName
- GRDBDemoiOS
- CFBundleExecutable
- $(EXECUTABLE_NAME)
- CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- $(PRODUCT_NAME)
- CFBundlePackageType
- APPL
- CFBundleShortVersionString
- 1.0
- CFBundleVersion
- 1
- UISupportedInterfaceOrientations
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationPortraitUpsideDown
-
- WKCompanionAppBundleIdentifier
- com.github.groue.GRDBDemoiOS2
- WKWatchKitApp
-
-
-
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS.xcodeproj/project.pbxproj b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS.xcodeproj/project.pbxproj
deleted file mode 100644
index 827e5c560c..0000000000
--- a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS.xcodeproj/project.pbxproj
+++ /dev/null
@@ -1,912 +0,0 @@
-// !$*UTF8*$!
-{
- archiveVersion = 1;
- classes = {
- };
- objectVersion = 54;
- objects = {
-
-/* Begin PBXBuildFile section */
- 56185BBF25B8036100B9C30F /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56185BBE25B8036100B9C30F /* Persistence.swift */; };
- 56185BF325B80B8900B9C30F /* AppDatabaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56185BF225B80B8900B9C30F /* AppDatabaseTests.swift */; };
- 56185C0725B80CEC00B9C30F /* PlayerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56185C0625B80CEC00B9C30F /* PlayerTests.swift */; };
- 567940C528BA33D2004A0298 /* GRDB.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 56606D0A2355931F00185962 /* GRDB.framework */; };
- 567940C628BA33D2004A0298 /* GRDB.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 56606D0A2355931F00185962 /* GRDB.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
- 567940C728BA33DE004A0298 /* GRDB.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 56606D0A2355931F00185962 /* GRDB.framework */; };
- 567940C828BA33DE004A0298 /* GRDB.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 56606D0A2355931F00185962 /* GRDB.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
- 568E5FC31E926430002582E0 /* Interface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 568E5FC11E926430002582E0 /* Interface.storyboard */; };
- 568E5FC51E926430002582E0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 568E5FC41E926430002582E0 /* Assets.xcassets */; };
- 568E5FCC1E926430002582E0 /* GRDBDemoWatchOS Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 568E5FCB1E926430002582E0 /* GRDBDemoWatchOS Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
- 568E5FD11E926430002582E0 /* InterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 568E5FD01E926430002582E0 /* InterfaceController.swift */; };
- 568E5FD31E926430002582E0 /* ExtensionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 568E5FD21E926430002582E0 /* ExtensionDelegate.swift */; };
- 568E5FD51E926430002582E0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 568E5FD41E926430002582E0 /* Assets.xcassets */; };
- 568E5FD91E926430002582E0 /* GRDBDemoWatchOS.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 568E5FBF1E926430002582E0 /* GRDBDemoWatchOS.app */; };
- 56B036071E8D9EBE003B6DA4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B036061E8D9EBE003B6DA4 /* AppDelegate.swift */; };
- 56B0360C1E8D9EBE003B6DA4 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 56B0360A1E8D9EBE003B6DA4 /* Main.storyboard */; };
- 56B0360E1E8D9EBE003B6DA4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 56B0360D1E8D9EBE003B6DA4 /* Assets.xcassets */; };
- 56B036111E8D9EBE003B6DA4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 56B0360F1E8D9EBE003B6DA4 /* LaunchScreen.storyboard */; };
- 56B0361B1E8D9F38003B6DA4 /* AppDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B036191E8D9F38003B6DA4 /* AppDatabase.swift */; };
- 56B0361C1E8D9F38003B6DA4 /* Player.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B0361A1E8D9F38003B6DA4 /* Player.swift */; };
- 56B036231E8D9F4C003B6DA4 /* PlayerEditionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B0361F1E8D9F4C003B6DA4 /* PlayerEditionViewController.swift */; };
- 56B036241E8D9F4C003B6DA4 /* PlayerListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B036201E8D9F4C003B6DA4 /* PlayerListViewController.swift */; };
- 56FE6F2624A90CE400711EDF /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FE6F2524A90CE400711EDF /* SceneDelegate.swift */; };
-/* End PBXBuildFile section */
-
-/* Begin PBXContainerItemProxy section */
- 56185BF525B80B8900B9C30F /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 56B035FB1E8D9EBE003B6DA4 /* Project object */;
- proxyType = 1;
- remoteGlobalIDString = 56B036021E8D9EBE003B6DA4;
- remoteInfo = GRDBDemoiOS;
- };
- 5642252F29A2390800D714BF /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 56B036261E8D9F79003B6DA4 /* GRDB.xcodeproj */;
- proxyType = 1;
- remoteGlobalIDString = DC3773F219C8CBB3004FCF85;
- remoteInfo = GRDB;
- };
- 5642253329A2391900D714BF /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 56B036261E8D9F79003B6DA4 /* GRDB.xcodeproj */;
- proxyType = 1;
- remoteGlobalIDString = DC3773F219C8CBB3004FCF85;
- remoteInfo = GRDB;
- };
- 56606D092355931F00185962 /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 56B036261E8D9F79003B6DA4 /* GRDB.xcodeproj */;
- proxyType = 2;
- remoteGlobalIDString = DC3773F319C8CBB3004FCF85;
- remoteInfo = GRDBOSX;
- };
- 56606D0B2355931F00185962 /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 56B036261E8D9F79003B6DA4 /* GRDB.xcodeproj */;
- proxyType = 2;
- remoteGlobalIDString = 56E5D7F91B4D422D00430942;
- remoteInfo = GRDBOSXTests;
- };
- 568E5FCD1E926430002582E0 /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 56B035FB1E8D9EBE003B6DA4 /* Project object */;
- proxyType = 1;
- remoteGlobalIDString = 568E5FCA1E926430002582E0;
- remoteInfo = "GRDBDemoWatchOS Extension";
- };
- 568E5FD71E926430002582E0 /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 56B035FB1E8D9EBE003B6DA4 /* Project object */;
- proxyType = 1;
- remoteGlobalIDString = 568E5FBE1E926430002582E0;
- remoteInfo = GRDBDemoWatchOS;
- };
-/* End PBXContainerItemProxy section */
-
-/* Begin PBXCopyFilesBuildPhase section */
- 568E5FDD1E926430002582E0 /* Embed Foundation Extensions */ = {
- isa = PBXCopyFilesBuildPhase;
- buildActionMask = 2147483647;
- dstPath = "";
- dstSubfolderSpec = 13;
- files = (
- 568E5FCC1E926430002582E0 /* GRDBDemoWatchOS Extension.appex in Embed Foundation Extensions */,
- );
- name = "Embed Foundation Extensions";
- runOnlyForDeploymentPostprocessing = 0;
- };
- 568E5FE11E926430002582E0 /* Embed Watch Content */ = {
- isa = PBXCopyFilesBuildPhase;
- buildActionMask = 2147483647;
- dstPath = "$(CONTENTS_FOLDER_PATH)/Watch";
- dstSubfolderSpec = 16;
- files = (
- 568E5FD91E926430002582E0 /* GRDBDemoWatchOS.app in Embed Watch Content */,
- );
- name = "Embed Watch Content";
- runOnlyForDeploymentPostprocessing = 0;
- };
- 568E5FE81E926547002582E0 /* Embed Frameworks */ = {
- isa = PBXCopyFilesBuildPhase;
- buildActionMask = 2147483647;
- dstPath = "";
- dstSubfolderSpec = 10;
- files = (
- 567940C828BA33DE004A0298 /* GRDB.framework in Embed Frameworks */,
- );
- name = "Embed Frameworks";
- runOnlyForDeploymentPostprocessing = 0;
- };
- 56B036621E8D9FB1003B6DA4 /* Embed Frameworks */ = {
- isa = PBXCopyFilesBuildPhase;
- buildActionMask = 2147483647;
- dstPath = "";
- dstSubfolderSpec = 10;
- files = (
- 567940C628BA33D2004A0298 /* GRDB.framework in Embed Frameworks */,
- );
- name = "Embed Frameworks";
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXCopyFilesBuildPhase section */
-
-/* Begin PBXFileReference section */
- 56185BBE25B8036100B9C30F /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = ""; };
- 56185BF025B80B8900B9C30F /* GRDBDemoiOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GRDBDemoiOSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
- 56185BF225B80B8900B9C30F /* AppDatabaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDatabaseTests.swift; sourceTree = ""; };
- 56185BF425B80B8900B9C30F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- 56185C0625B80CEC00B9C30F /* PlayerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerTests.swift; sourceTree = ""; };
- 568E5FBF1E926430002582E0 /* GRDBDemoWatchOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GRDBDemoWatchOS.app; sourceTree = BUILT_PRODUCTS_DIR; };
- 568E5FC21E926430002582E0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Interface.storyboard; sourceTree = ""; };
- 568E5FC41E926430002582E0 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
- 568E5FC61E926430002582E0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- 568E5FCB1E926430002582E0 /* GRDBDemoWatchOS Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "GRDBDemoWatchOS Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
- 568E5FD01E926430002582E0 /* InterfaceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterfaceController.swift; sourceTree = ""; };
- 568E5FD21E926430002582E0 /* ExtensionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionDelegate.swift; sourceTree = ""; };
- 568E5FD41E926430002582E0 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
- 568E5FD61E926430002582E0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- 56B036031E8D9EBE003B6DA4 /* GRDBDemoiOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GRDBDemoiOS.app; sourceTree = BUILT_PRODUCTS_DIR; };
- 56B036061E8D9EBE003B6DA4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
- 56B0360B1E8D9EBE003B6DA4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
- 56B0360D1E8D9EBE003B6DA4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
- 56B036101E8D9EBE003B6DA4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
- 56B036121E8D9EBE003B6DA4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- 56B036191E8D9F38003B6DA4 /* AppDatabase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDatabase.swift; sourceTree = ""; };
- 56B0361A1E8D9F38003B6DA4 /* Player.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Player.swift; sourceTree = ""; };
- 56B0361F1E8D9F4C003B6DA4 /* PlayerEditionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerEditionViewController.swift; sourceTree = ""; };
- 56B036201E8D9F4C003B6DA4 /* PlayerListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerListViewController.swift; sourceTree = ""; };
- 56B036261E8D9F79003B6DA4 /* GRDB.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = GRDB.xcodeproj; path = ../../../GRDB.xcodeproj; sourceTree = ""; };
- 56FE6F2524A90CE400711EDF /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
-/* End PBXFileReference section */
-
-/* Begin PBXFrameworksBuildPhase section */
- 56185BED25B80B8900B9C30F /* Frameworks */ = {
- isa = PBXFrameworksBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 568E5FC81E926430002582E0 /* Frameworks */ = {
- isa = PBXFrameworksBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 567940C728BA33DE004A0298 /* GRDB.framework in Frameworks */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 56B036001E8D9EBE003B6DA4 /* Frameworks */ = {
- isa = PBXFrameworksBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 567940C528BA33D2004A0298 /* GRDB.framework in Frameworks */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXFrameworksBuildPhase section */
-
-/* Begin PBXGroup section */
- 56185BF125B80B8900B9C30F /* GRDBDemoiOSTests */ = {
- isa = PBXGroup;
- children = (
- 56185BF425B80B8900B9C30F /* Info.plist */,
- 56185BF225B80B8900B9C30F /* AppDatabaseTests.swift */,
- 56185C0625B80CEC00B9C30F /* PlayerTests.swift */,
- );
- path = GRDBDemoiOSTests;
- sourceTree = "";
- };
- 56606CFD2355931F00185962 /* Products */ = {
- isa = PBXGroup;
- children = (
- 56606D0A2355931F00185962 /* GRDB.framework */,
- 56606D0C2355931F00185962 /* GRDBTests.xctest */,
- );
- name = Products;
- sourceTree = "";
- };
- 56606D1F2355938A00185962 /* Frameworks */ = {
- isa = PBXGroup;
- children = (
- );
- name = Frameworks;
- sourceTree = "";
- };
- 568E5FC01E926430002582E0 /* GRDBDemoWatchOS */ = {
- isa = PBXGroup;
- children = (
- 568E5FC11E926430002582E0 /* Interface.storyboard */,
- 568E5FC41E926430002582E0 /* Assets.xcassets */,
- 568E5FC61E926430002582E0 /* Info.plist */,
- );
- path = GRDBDemoWatchOS;
- sourceTree = "";
- };
- 568E5FCF1E926430002582E0 /* GRDBDemoWatchOS Extension */ = {
- isa = PBXGroup;
- children = (
- 568E5FD01E926430002582E0 /* InterfaceController.swift */,
- 568E5FD21E926430002582E0 /* ExtensionDelegate.swift */,
- 568E5FD41E926430002582E0 /* Assets.xcassets */,
- 568E5FD61E926430002582E0 /* Info.plist */,
- );
- path = "GRDBDemoWatchOS Extension";
- sourceTree = "";
- };
- 56B035FA1E8D9EBE003B6DA4 = {
- isa = PBXGroup;
- children = (
- 56B036261E8D9F79003B6DA4 /* GRDB.xcodeproj */,
- 56B036051E8D9EBE003B6DA4 /* GRDBDemoiOS */,
- 568E5FC01E926430002582E0 /* GRDBDemoWatchOS */,
- 568E5FCF1E926430002582E0 /* GRDBDemoWatchOS Extension */,
- 56185BF125B80B8900B9C30F /* GRDBDemoiOSTests */,
- 56B036041E8D9EBE003B6DA4 /* Products */,
- 56606D1F2355938A00185962 /* Frameworks */,
- );
- sourceTree = "";
- };
- 56B036041E8D9EBE003B6DA4 /* Products */ = {
- isa = PBXGroup;
- children = (
- 56B036031E8D9EBE003B6DA4 /* GRDBDemoiOS.app */,
- 568E5FBF1E926430002582E0 /* GRDBDemoWatchOS.app */,
- 568E5FCB1E926430002582E0 /* GRDBDemoWatchOS Extension.appex */,
- 56185BF025B80B8900B9C30F /* GRDBDemoiOSTests.xctest */,
- );
- name = Products;
- sourceTree = "";
- };
- 56B036051E8D9EBE003B6DA4 /* GRDBDemoiOS */ = {
- isa = PBXGroup;
- children = (
- 56B036121E8D9EBE003B6DA4 /* Info.plist */,
- 56B036191E8D9F38003B6DA4 /* AppDatabase.swift */,
- 56B036061E8D9EBE003B6DA4 /* AppDelegate.swift */,
- 56185BBE25B8036100B9C30F /* Persistence.swift */,
- 56B0361A1E8D9F38003B6DA4 /* Player.swift */,
- 56FE6F2524A90CE400711EDF /* SceneDelegate.swift */,
- 56FE6F4E24A9112900711EDF /* Resources */,
- 56FE6F4F24A9114200711EDF /* ViewControllers */,
- );
- path = GRDBDemoiOS;
- sourceTree = "";
- };
- 56FE6F4E24A9112900711EDF /* Resources */ = {
- isa = PBXGroup;
- children = (
- 56B0360D1E8D9EBE003B6DA4 /* Assets.xcassets */,
- 56B0360F1E8D9EBE003B6DA4 /* LaunchScreen.storyboard */,
- 56B0360A1E8D9EBE003B6DA4 /* Main.storyboard */,
- );
- path = Resources;
- sourceTree = "";
- };
- 56FE6F4F24A9114200711EDF /* ViewControllers */ = {
- isa = PBXGroup;
- children = (
- 56B0361F1E8D9F4C003B6DA4 /* PlayerEditionViewController.swift */,
- 56B036201E8D9F4C003B6DA4 /* PlayerListViewController.swift */,
- );
- path = ViewControllers;
- sourceTree = "";
- };
-/* End PBXGroup section */
-
-/* Begin PBXNativeTarget section */
- 56185BEF25B80B8900B9C30F /* GRDBDemoiOSTests */ = {
- isa = PBXNativeTarget;
- buildConfigurationList = 56185BFF25B80B8900B9C30F /* Build configuration list for PBXNativeTarget "GRDBDemoiOSTests" */;
- buildPhases = (
- 56185BEC25B80B8900B9C30F /* Sources */,
- 56185BED25B80B8900B9C30F /* Frameworks */,
- 56185BEE25B80B8900B9C30F /* Resources */,
- );
- buildRules = (
- );
- dependencies = (
- 56185BF625B80B8900B9C30F /* PBXTargetDependency */,
- );
- name = GRDBDemoiOSTests;
- productName = GRDBDemoiOSTests;
- productReference = 56185BF025B80B8900B9C30F /* GRDBDemoiOSTests.xctest */;
- productType = "com.apple.product-type.bundle.unit-test";
- };
- 568E5FBE1E926430002582E0 /* GRDBDemoWatchOS */ = {
- isa = PBXNativeTarget;
- buildConfigurationList = 568E5FDE1E926430002582E0 /* Build configuration list for PBXNativeTarget "GRDBDemoWatchOS" */;
- buildPhases = (
- 568E5FBD1E926430002582E0 /* Resources */,
- 568E5FDD1E926430002582E0 /* Embed Foundation Extensions */,
- );
- buildRules = (
- );
- dependencies = (
- 568E5FCE1E926430002582E0 /* PBXTargetDependency */,
- );
- name = GRDBDemoWatchOS;
- productName = GRDBDemoWatchOS;
- productReference = 568E5FBF1E926430002582E0 /* GRDBDemoWatchOS.app */;
- productType = "com.apple.product-type.application.watchapp2";
- };
- 568E5FCA1E926430002582E0 /* GRDBDemoWatchOS Extension */ = {
- isa = PBXNativeTarget;
- buildConfigurationList = 568E5FDA1E926430002582E0 /* Build configuration list for PBXNativeTarget "GRDBDemoWatchOS Extension" */;
- buildPhases = (
- 568E5FC71E926430002582E0 /* Sources */,
- 568E5FC81E926430002582E0 /* Frameworks */,
- 568E5FC91E926430002582E0 /* Resources */,
- 568E5FE81E926547002582E0 /* Embed Frameworks */,
- );
- buildRules = (
- );
- dependencies = (
- 5642253429A2391900D714BF /* PBXTargetDependency */,
- );
- name = "GRDBDemoWatchOS Extension";
- productName = "GRDBDemoWatchOS Extension";
- productReference = 568E5FCB1E926430002582E0 /* GRDBDemoWatchOS Extension.appex */;
- productType = "com.apple.product-type.watchkit2-extension";
- };
- 56B036021E8D9EBE003B6DA4 /* GRDBDemoiOS */ = {
- isa = PBXNativeTarget;
- buildConfigurationList = 56B036151E8D9EBE003B6DA4 /* Build configuration list for PBXNativeTarget "GRDBDemoiOS" */;
- buildPhases = (
- 56B035FF1E8D9EBE003B6DA4 /* Sources */,
- 56B036001E8D9EBE003B6DA4 /* Frameworks */,
- 56B036011E8D9EBE003B6DA4 /* Resources */,
- 56B036621E8D9FB1003B6DA4 /* Embed Frameworks */,
- 568E5FE11E926430002582E0 /* Embed Watch Content */,
- );
- buildRules = (
- );
- dependencies = (
- 5642253029A2390800D714BF /* PBXTargetDependency */,
- 568E5FD81E926430002582E0 /* PBXTargetDependency */,
- );
- name = GRDBDemoiOS;
- productName = GRDBDemoiOS;
- productReference = 56B036031E8D9EBE003B6DA4 /* GRDBDemoiOS.app */;
- productType = "com.apple.product-type.application";
- };
-/* End PBXNativeTarget section */
-
-/* Begin PBXProject section */
- 56B035FB1E8D9EBE003B6DA4 /* Project object */ = {
- isa = PBXProject;
- attributes = {
- LastSwiftUpdateCheck = 1230;
- LastUpgradeCheck = 1400;
- ORGANIZATIONNAME = "Gwendal Roué";
- TargetAttributes = {
- 56185BEF25B80B8900B9C30F = {
- CreatedOnToolsVersion = 12.3;
- DevelopmentTeam = AMD8W895CT;
- ProvisioningStyle = Automatic;
- TestTargetID = 56B036021E8D9EBE003B6DA4;
- };
- 568E5FBE1E926430002582E0 = {
- CreatedOnToolsVersion = 8.3;
- DevelopmentTeam = AMD8W895CT;
- ProvisioningStyle = Automatic;
- };
- 568E5FCA1E926430002582E0 = {
- CreatedOnToolsVersion = 8.3;
- DevelopmentTeam = AMD8W895CT;
- LastSwiftMigration = 1020;
- ProvisioningStyle = Automatic;
- };
- 56B036021E8D9EBE003B6DA4 = {
- CreatedOnToolsVersion = 8.3;
- DevelopmentTeam = AMD8W895CT;
- LastSwiftMigration = 1020;
- ProvisioningStyle = Automatic;
- };
- };
- };
- buildConfigurationList = 56B035FE1E8D9EBE003B6DA4 /* Build configuration list for PBXProject "GRDBDemoiOS" */;
- compatibilityVersion = "Xcode 12.0";
- developmentRegion = en;
- hasScannedForEncodings = 0;
- knownRegions = (
- en,
- Base,
- );
- mainGroup = 56B035FA1E8D9EBE003B6DA4;
- productRefGroup = 56B036041E8D9EBE003B6DA4 /* Products */;
- projectDirPath = "";
- projectReferences = (
- {
- ProductGroup = 56606CFD2355931F00185962 /* Products */;
- ProjectRef = 56B036261E8D9F79003B6DA4 /* GRDB.xcodeproj */;
- },
- );
- projectRoot = "";
- targets = (
- 56B036021E8D9EBE003B6DA4 /* GRDBDemoiOS */,
- 568E5FBE1E926430002582E0 /* GRDBDemoWatchOS */,
- 568E5FCA1E926430002582E0 /* GRDBDemoWatchOS Extension */,
- 56185BEF25B80B8900B9C30F /* GRDBDemoiOSTests */,
- );
- };
-/* End PBXProject section */
-
-/* Begin PBXReferenceProxy section */
- 56606D0A2355931F00185962 /* GRDB.framework */ = {
- isa = PBXReferenceProxy;
- fileType = wrapper.framework;
- path = GRDB.framework;
- remoteRef = 56606D092355931F00185962 /* PBXContainerItemProxy */;
- sourceTree = BUILT_PRODUCTS_DIR;
- };
- 56606D0C2355931F00185962 /* GRDBTests.xctest */ = {
- isa = PBXReferenceProxy;
- fileType = wrapper.cfbundle;
- path = GRDBTests.xctest;
- remoteRef = 56606D0B2355931F00185962 /* PBXContainerItemProxy */;
- sourceTree = BUILT_PRODUCTS_DIR;
- };
-/* End PBXReferenceProxy section */
-
-/* Begin PBXResourcesBuildPhase section */
- 56185BEE25B80B8900B9C30F /* Resources */ = {
- isa = PBXResourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 568E5FBD1E926430002582E0 /* Resources */ = {
- isa = PBXResourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 568E5FC51E926430002582E0 /* Assets.xcassets in Resources */,
- 568E5FC31E926430002582E0 /* Interface.storyboard in Resources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 568E5FC91E926430002582E0 /* Resources */ = {
- isa = PBXResourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 568E5FD51E926430002582E0 /* Assets.xcassets in Resources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 56B036011E8D9EBE003B6DA4 /* Resources */ = {
- isa = PBXResourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 56B036111E8D9EBE003B6DA4 /* LaunchScreen.storyboard in Resources */,
- 56B0360E1E8D9EBE003B6DA4 /* Assets.xcassets in Resources */,
- 56B0360C1E8D9EBE003B6DA4 /* Main.storyboard in Resources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXResourcesBuildPhase section */
-
-/* Begin PBXSourcesBuildPhase section */
- 56185BEC25B80B8900B9C30F /* Sources */ = {
- isa = PBXSourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 56185C0725B80CEC00B9C30F /* PlayerTests.swift in Sources */,
- 56185BF325B80B8900B9C30F /* AppDatabaseTests.swift in Sources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 568E5FC71E926430002582E0 /* Sources */ = {
- isa = PBXSourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 568E5FD31E926430002582E0 /* ExtensionDelegate.swift in Sources */,
- 568E5FD11E926430002582E0 /* InterfaceController.swift in Sources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 56B035FF1E8D9EBE003B6DA4 /* Sources */ = {
- isa = PBXSourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 56FE6F2624A90CE400711EDF /* SceneDelegate.swift in Sources */,
- 56B0361C1E8D9F38003B6DA4 /* Player.swift in Sources */,
- 56B0361B1E8D9F38003B6DA4 /* AppDatabase.swift in Sources */,
- 56185BBF25B8036100B9C30F /* Persistence.swift in Sources */,
- 56B036071E8D9EBE003B6DA4 /* AppDelegate.swift in Sources */,
- 56B036241E8D9F4C003B6DA4 /* PlayerListViewController.swift in Sources */,
- 56B036231E8D9F4C003B6DA4 /* PlayerEditionViewController.swift in Sources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXSourcesBuildPhase section */
-
-/* Begin PBXTargetDependency section */
- 56185BF625B80B8900B9C30F /* PBXTargetDependency */ = {
- isa = PBXTargetDependency;
- target = 56B036021E8D9EBE003B6DA4 /* GRDBDemoiOS */;
- targetProxy = 56185BF525B80B8900B9C30F /* PBXContainerItemProxy */;
- };
- 5642253029A2390800D714BF /* PBXTargetDependency */ = {
- isa = PBXTargetDependency;
- name = GRDB;
- targetProxy = 5642252F29A2390800D714BF /* PBXContainerItemProxy */;
- };
- 5642253429A2391900D714BF /* PBXTargetDependency */ = {
- isa = PBXTargetDependency;
- name = GRDB;
- targetProxy = 5642253329A2391900D714BF /* PBXContainerItemProxy */;
- };
- 568E5FCE1E926430002582E0 /* PBXTargetDependency */ = {
- isa = PBXTargetDependency;
- target = 568E5FCA1E926430002582E0 /* GRDBDemoWatchOS Extension */;
- targetProxy = 568E5FCD1E926430002582E0 /* PBXContainerItemProxy */;
- };
- 568E5FD81E926430002582E0 /* PBXTargetDependency */ = {
- isa = PBXTargetDependency;
- target = 568E5FBE1E926430002582E0 /* GRDBDemoWatchOS */;
- targetProxy = 568E5FD71E926430002582E0 /* PBXContainerItemProxy */;
- };
-/* End PBXTargetDependency section */
-
-/* Begin PBXVariantGroup section */
- 568E5FC11E926430002582E0 /* Interface.storyboard */ = {
- isa = PBXVariantGroup;
- children = (
- 568E5FC21E926430002582E0 /* Base */,
- );
- name = Interface.storyboard;
- sourceTree = "";
- };
- 56B0360A1E8D9EBE003B6DA4 /* Main.storyboard */ = {
- isa = PBXVariantGroup;
- children = (
- 56B0360B1E8D9EBE003B6DA4 /* Base */,
- );
- name = Main.storyboard;
- sourceTree = "";
- };
- 56B0360F1E8D9EBE003B6DA4 /* LaunchScreen.storyboard */ = {
- isa = PBXVariantGroup;
- children = (
- 56B036101E8D9EBE003B6DA4 /* Base */,
- );
- name = LaunchScreen.storyboard;
- sourceTree = "";
- };
-/* End PBXVariantGroup section */
-
-/* Begin XCBuildConfiguration section */
- 56185BF725B80B8900B9C30F /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- BUNDLE_LOADER = "$(TEST_HOST)";
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
- CLANG_ENABLE_OBJC_WEAK = YES;
- CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
- CODE_SIGN_STYLE = Automatic;
- DEVELOPMENT_TEAM = AMD8W895CT;
- GCC_C_LANGUAGE_STANDARD = gnu11;
- INFOPLIST_FILE = GRDBDemoiOSTests/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 17.0;
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@executable_path/Frameworks",
- "@loader_path/Frameworks",
- );
- MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
- MTL_FAST_MATH = YES;
- PRODUCT_BUNDLE_IDENTIFIER = com.github.groue.GRDBDemoiOSTests;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = "1,2";
- TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GRDBDemoiOS.app/GRDBDemoiOS";
- };
- name = Debug;
- };
- 56185BF825B80B8900B9C30F /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- BUNDLE_LOADER = "$(TEST_HOST)";
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
- CLANG_ENABLE_OBJC_WEAK = YES;
- CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
- CODE_SIGN_STYLE = Automatic;
- DEVELOPMENT_TEAM = AMD8W895CT;
- GCC_C_LANGUAGE_STANDARD = gnu11;
- INFOPLIST_FILE = GRDBDemoiOSTests/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 17.0;
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@executable_path/Frameworks",
- "@loader_path/Frameworks",
- );
- MTL_FAST_MATH = YES;
- PRODUCT_BUNDLE_IDENTIFIER = com.github.groue.GRDBDemoiOSTests;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = "1,2";
- TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GRDBDemoiOS.app/GRDBDemoiOS";
- };
- name = Release;
- };
- 568E5FDB1E926430002582E0 /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ASSETCATALOG_COMPILER_COMPLICATION_NAME = Complication;
- CLANG_ENABLE_OBJC_WEAK = YES;
- DEVELOPMENT_TEAM = AMD8W895CT;
- INFOPLIST_FILE = "GRDBDemoWatchOS Extension/Info.plist";
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@executable_path/Frameworks",
- "@executable_path/../../Frameworks",
- );
- PRODUCT_BUNDLE_IDENTIFIER = com.github.groue.GRDBDemoiOS2.watchkitapp.watchkitextension;
- PRODUCT_NAME = "${TARGET_NAME}";
- SDKROOT = watchos;
- SKIP_INSTALL = YES;
- TARGETED_DEVICE_FAMILY = 4;
- };
- name = Debug;
- };
- 568E5FDC1E926430002582E0 /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ASSETCATALOG_COMPILER_COMPLICATION_NAME = Complication;
- CLANG_ENABLE_OBJC_WEAK = YES;
- DEVELOPMENT_TEAM = AMD8W895CT;
- INFOPLIST_FILE = "GRDBDemoWatchOS Extension/Info.plist";
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@executable_path/Frameworks",
- "@executable_path/../../Frameworks",
- );
- PRODUCT_BUNDLE_IDENTIFIER = com.github.groue.GRDBDemoiOS2.watchkitapp.watchkitextension;
- PRODUCT_NAME = "${TARGET_NAME}";
- SDKROOT = watchos;
- SKIP_INSTALL = YES;
- TARGETED_DEVICE_FAMILY = 4;
- };
- name = Release;
- };
- 568E5FDF1E926430002582E0 /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
- CLANG_ENABLE_OBJC_WEAK = YES;
- DEVELOPMENT_TEAM = AMD8W895CT;
- IBSC_MODULE = GRDBDemoWatchOS_Extension;
- INFOPLIST_FILE = GRDBDemoWatchOS/Info.plist;
- PRODUCT_BUNDLE_IDENTIFIER = com.github.groue.GRDBDemoiOS2.watchkitapp;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SDKROOT = watchos;
- SKIP_INSTALL = YES;
- TARGETED_DEVICE_FAMILY = 4;
- };
- name = Debug;
- };
- 568E5FE01E926430002582E0 /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
- CLANG_ENABLE_OBJC_WEAK = YES;
- DEVELOPMENT_TEAM = AMD8W895CT;
- IBSC_MODULE = GRDBDemoWatchOS_Extension;
- INFOPLIST_FILE = GRDBDemoWatchOS/Info.plist;
- PRODUCT_BUNDLE_IDENTIFIER = com.github.groue.GRDBDemoiOS2.watchkitapp;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SDKROOT = watchos;
- SKIP_INSTALL = YES;
- TARGETED_DEVICE_FAMILY = 4;
- };
- name = Release;
- };
- 56B036131E8D9EBE003B6DA4 /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
- CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
- CLANG_ANALYZER_NONNULL = YES;
- CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
- CLANG_CXX_LIBRARY = "libc++";
- CLANG_ENABLE_MODULES = YES;
- CLANG_ENABLE_OBJC_ARC = YES;
- CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
- CLANG_WARN_BOOL_CONVERSION = YES;
- CLANG_WARN_COMMA = YES;
- CLANG_WARN_CONSTANT_CONVERSION = YES;
- CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
- CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
- CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
- CLANG_WARN_EMPTY_BODY = YES;
- CLANG_WARN_ENUM_CONVERSION = YES;
- CLANG_WARN_INFINITE_RECURSION = YES;
- CLANG_WARN_INT_CONVERSION = YES;
- CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
- CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
- CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
- CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
- CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
- CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
- CLANG_WARN_STRICT_PROTOTYPES = YES;
- CLANG_WARN_SUSPICIOUS_MOVE = YES;
- CLANG_WARN_UNREACHABLE_CODE = YES;
- CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
- COPY_PHASE_STRIP = NO;
- DEBUG_INFORMATION_FORMAT = dwarf;
- ENABLE_STRICT_OBJC_MSGSEND = YES;
- ENABLE_TESTABILITY = YES;
- GCC_C_LANGUAGE_STANDARD = gnu99;
- GCC_DYNAMIC_NO_PIC = NO;
- GCC_NO_COMMON_BLOCKS = YES;
- GCC_OPTIMIZATION_LEVEL = 0;
- GCC_PREPROCESSOR_DEFINITIONS = (
- "DEBUG=1",
- "$(inherited)",
- );
- GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
- GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
- GCC_WARN_UNDECLARED_SELECTOR = YES;
- GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
- GCC_WARN_UNUSED_FUNCTION = YES;
- GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 15.0;
- MTL_ENABLE_DEBUG_INFO = YES;
- ONLY_ACTIVE_ARCH = YES;
- SDKROOT = iphoneos;
- SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
- SWIFT_OPTIMIZATION_LEVEL = "-Onone";
- SWIFT_VERSION = 5.0;
- WATCHOS_DEPLOYMENT_TARGET = 8.0;
- };
- name = Debug;
- };
- 56B036141E8D9EBE003B6DA4 /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
- CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
- CLANG_ANALYZER_NONNULL = YES;
- CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
- CLANG_CXX_LIBRARY = "libc++";
- CLANG_ENABLE_MODULES = YES;
- CLANG_ENABLE_OBJC_ARC = YES;
- CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
- CLANG_WARN_BOOL_CONVERSION = YES;
- CLANG_WARN_COMMA = YES;
- CLANG_WARN_CONSTANT_CONVERSION = YES;
- CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
- CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
- CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
- CLANG_WARN_EMPTY_BODY = YES;
- CLANG_WARN_ENUM_CONVERSION = YES;
- CLANG_WARN_INFINITE_RECURSION = YES;
- CLANG_WARN_INT_CONVERSION = YES;
- CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
- CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
- CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
- CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
- CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
- CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
- CLANG_WARN_STRICT_PROTOTYPES = YES;
- CLANG_WARN_SUSPICIOUS_MOVE = YES;
- CLANG_WARN_UNREACHABLE_CODE = YES;
- CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
- COPY_PHASE_STRIP = NO;
- DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
- ENABLE_NS_ASSERTIONS = NO;
- ENABLE_STRICT_OBJC_MSGSEND = YES;
- GCC_C_LANGUAGE_STANDARD = gnu99;
- GCC_NO_COMMON_BLOCKS = YES;
- GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
- GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
- GCC_WARN_UNDECLARED_SELECTOR = YES;
- GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
- GCC_WARN_UNUSED_FUNCTION = YES;
- GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 15.0;
- MTL_ENABLE_DEBUG_INFO = NO;
- SDKROOT = iphoneos;
- SWIFT_COMPILATION_MODE = wholemodule;
- SWIFT_OPTIMIZATION_LEVEL = "-O";
- SWIFT_VERSION = 5.0;
- VALIDATE_PRODUCT = YES;
- WATCHOS_DEPLOYMENT_TARGET = 8.0;
- };
- name = Release;
- };
- 56B036161E8D9EBE003B6DA4 /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
- CLANG_ENABLE_OBJC_WEAK = YES;
- DEVELOPMENT_TEAM = AMD8W895CT;
- INFOPLIST_FILE = GRDBDemoiOS/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 17.0;
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@executable_path/Frameworks",
- );
- PRODUCT_BUNDLE_IDENTIFIER = com.github.groue.GRDBDemoiOS2;
- PRODUCT_NAME = "$(TARGET_NAME)";
- };
- name = Debug;
- };
- 56B036171E8D9EBE003B6DA4 /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
- CLANG_ENABLE_OBJC_WEAK = YES;
- DEVELOPMENT_TEAM = AMD8W895CT;
- INFOPLIST_FILE = GRDBDemoiOS/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 17.0;
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@executable_path/Frameworks",
- );
- PRODUCT_BUNDLE_IDENTIFIER = com.github.groue.GRDBDemoiOS2;
- PRODUCT_NAME = "$(TARGET_NAME)";
- };
- name = Release;
- };
-/* End XCBuildConfiguration section */
-
-/* Begin XCConfigurationList section */
- 56185BFF25B80B8900B9C30F /* Build configuration list for PBXNativeTarget "GRDBDemoiOSTests" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 56185BF725B80B8900B9C30F /* Debug */,
- 56185BF825B80B8900B9C30F /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
- 568E5FDA1E926430002582E0 /* Build configuration list for PBXNativeTarget "GRDBDemoWatchOS Extension" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 568E5FDB1E926430002582E0 /* Debug */,
- 568E5FDC1E926430002582E0 /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
- 568E5FDE1E926430002582E0 /* Build configuration list for PBXNativeTarget "GRDBDemoWatchOS" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 568E5FDF1E926430002582E0 /* Debug */,
- 568E5FE01E926430002582E0 /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
- 56B035FE1E8D9EBE003B6DA4 /* Build configuration list for PBXProject "GRDBDemoiOS" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 56B036131E8D9EBE003B6DA4 /* Debug */,
- 56B036141E8D9EBE003B6DA4 /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
- 56B036151E8D9EBE003B6DA4 /* Build configuration list for PBXNativeTarget "GRDBDemoiOS" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 56B036161E8D9EBE003B6DA4 /* Debug */,
- 56B036171E8D9EBE003B6DA4 /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
-/* End XCConfigurationList section */
- };
- rootObject = 56B035FB1E8D9EBE003B6DA4 /* Project object */;
-}
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS.xcodeproj/xcshareddata/xcschemes/GRDBDemoWatchOS.xcscheme b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS.xcodeproj/xcshareddata/xcschemes/GRDBDemoWatchOS.xcscheme
deleted file mode 100644
index bd03393c0d..0000000000
--- a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS.xcodeproj/xcshareddata/xcschemes/GRDBDemoWatchOS.xcscheme
+++ /dev/null
@@ -1,116 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/AppDelegate.swift b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/AppDelegate.swift
deleted file mode 100644
index 07edceeed8..0000000000
--- a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/AppDelegate.swift
+++ /dev/null
@@ -1,22 +0,0 @@
-import UIKit
-
-@UIApplicationMain
-class AppDelegate: UIResponder, UIApplicationDelegate {
- func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
- return true
- }
-
- // MARK: UISceneSession Lifecycle
-
- func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
- // Called when a new scene session is being created.
- // Use this method to select a configuration to create the new scene with.
- return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
- }
-
- func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
- // Called when the user discards a scene session.
- // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
- // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
- }
-}
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Info.plist b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Info.plist
deleted file mode 100644
index b5f9c0796b..0000000000
--- a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Info.plist
+++ /dev/null
@@ -1,62 +0,0 @@
-
-
-
-
- CFBundleDevelopmentRegion
- $(DEVELOPMENT_LANGUAGE)
- CFBundleExecutable
- $(EXECUTABLE_NAME)
- CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- $(PRODUCT_NAME)
- CFBundlePackageType
- $(PRODUCT_BUNDLE_PACKAGE_TYPE)
- CFBundleShortVersionString
- 1.0
- CFBundleVersion
- 1
- LSRequiresIPhoneOS
-
- UIApplicationSceneManifest
-
- UIApplicationSupportsMultipleScenes
-
- UISceneConfigurations
-
- UIWindowSceneSessionRoleApplication
-
-
- UISceneConfigurationName
- Default Configuration
- UISceneDelegateClassName
- $(PRODUCT_MODULE_NAME).SceneDelegate
- UISceneStoryboardFile
- Main
-
-
-
-
- UILaunchStoryboardName
- LaunchScreen
- UIMainStoryboardFile
- Main
- UIRequiredDeviceCapabilities
-
- armv7
-
- UISupportedInterfaceOrientations
-
- UIInterfaceOrientationPortrait
-
- UISupportedInterfaceOrientations~ipad
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
-
-
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Persistence.swift b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Persistence.swift
deleted file mode 100644
index 96267e6846..0000000000
--- a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Persistence.swift
+++ /dev/null
@@ -1,49 +0,0 @@
-import Foundation
-import GRDB
-
-extension AppDatabase {
- /// The database for the application
- static let shared = makeShared()
-
- private static func makeShared() -> AppDatabase {
- do {
- // Apply recommendations from
- //
- //
- // Create the "Application Support/Database" directory if needed
- let fileManager = FileManager.default
- let appSupportURL = try fileManager.url(
- for: .applicationSupportDirectory, in: .userDomainMask,
- appropriateFor: nil, create: true)
- let directoryURL = appSupportURL.appendingPathComponent("Database", isDirectory: true)
- try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true)
-
- // Open or create the database
- let databaseURL = directoryURL.appendingPathComponent("db.sqlite")
- NSLog("Database stored at \(databaseURL.path)")
- let dbPool = try DatabasePool(
- path: databaseURL.path,
- // Use default AppDatabase configuration
- configuration: AppDatabase.makeConfiguration())
-
- // Create the AppDatabase
- let appDatabase = try AppDatabase(dbPool)
-
- // Populate the database if it is empty, for better demo purpose.
- try appDatabase.createRandomPlayersIfEmpty()
-
- return appDatabase
- } catch {
- // Replace this implementation with code to handle the error appropriately.
- // fatalError() causes the application to generate a crash log and terminate.
- //
- // Typical reasons for an error here include:
- // * The parent directory cannot be created, or disallows writing.
- // * The database is not accessible, due to permissions or data protection when the device is locked.
- // * The device is out of space.
- // * The database could not be migrated to its latest schema version.
- // Check the error message to determine what the actual problem was.
- fatalError("Unresolved error \(error)")
- }
- }
-}
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Player.swift b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Player.swift
deleted file mode 100644
index 82204b62c4..0000000000
--- a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Player.swift
+++ /dev/null
@@ -1,104 +0,0 @@
-import GRDB
-
-/// The Player struct.
-///
-/// Identifiable conformance supports type-safe GRDB primary key methods.
-/// Hashable conformance supports table view updates
-struct Player: Identifiable, Hashable {
- /// The player id.
- ///
- /// Int64 is the recommended type for auto-incremented database ids.
- /// Use nil for players that are not inserted yet in the database.
- var id: Int64?
- var name: String
- var score: Int
-}
-
-extension Player {
- private static let names = [
- "Arthur", "Anita", "Barbara", "Bernard", "Craig", "Chiara", "David",
- "Dean", "Éric", "Elena", "Fatima", "Frederik", "Gilbert", "Georgette",
- "Henriette", "Hassan", "Ignacio", "Irene", "Julie", "Jack", "Karl",
- "Kristel", "Louis", "Liz", "Masashi", "Mary", "Noam", "Nicole",
- "Ophelie", "Oleg", "Pascal", "Patricia", "Quentin", "Quinn", "Raoul",
- "Rachel", "Stephan", "Susie", "Tristan", "Tatiana", "Ursule", "Urbain",
- "Victor", "Violette", "Wilfried", "Wilhelmina", "Yvon", "Yann",
- "Zazie", "Zoé"]
-
- /// Creates a new player with empty name and zero score
- static func new() -> Player {
- Player(id: nil, name: "", score: 0)
- }
-
- /// Creates a new player with random name and random score
- static func makeRandom() -> Player {
- Player(id: nil, name: randomName(), score: randomScore())
- }
-
- /// Returns a random name
- static func randomName() -> String {
- names.randomElement()!
- }
-
- /// Returns a random score
- static func randomScore() -> Int {
- 10 * Int.random(in: 0...100)
- }
-}
-
-// MARK: - Persistence
-
-/// Make Player a Codable Record.
-///
-/// See
-extension Player: Codable, FetchableRecord, MutablePersistableRecord {
- // Define database columns from CodingKeys
- fileprivate enum Columns {
- static let name = Column(CodingKeys.name)
- static let score = Column(CodingKeys.score)
- }
-
- /// Updates a player id after it has been inserted in the database.
- mutating func didInsert(_ inserted: InsertionSuccess) {
- id = inserted.rowID
- }
-}
-
-// MARK: - Player Database Requests
-
-/// Define some player requests used by the application.
-///
-/// See
-extension DerivableRequest {
- /// A request of players ordered by name.
- ///
- /// For example:
- ///
- /// let players: [Player] = try dbWriter.read { db in
- /// try Player.all().orderedByName().fetchAll(db)
- /// }
- func orderedByName() -> Self {
- // Sort by name in a localized case insensitive fashion
- // See https://github.com/groue/GRDB.swift/blob/master/README.md#string-comparison
- order(Player.Columns.name.collating(.localizedCaseInsensitiveCompare))
- }
-
- /// A request of players ordered by score.
- ///
- /// For example:
- ///
- /// let players: [Player] = try dbWriter.read { db in
- /// try Player.all().orderedByScore().fetchAll(db)
- /// }
- /// let bestPlayer: Player? = try dbWriter.read { db in
- /// try Player.all().orderedByScore().fetchOne(db)
- /// }
- func orderedByScore() -> Self {
- // Sort by descending score, and then by name, in a
- // localized case insensitive fashion
- // See https://github.com/groue/GRDB.swift/blob/master/README.md#string-comparison
- order(
- Player.Columns.score.desc,
- Player.Columns.name.collating(.localizedCaseInsensitiveCompare))
- }
-}
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
deleted file mode 100644
index 29d91251df..0000000000
--- a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
+++ /dev/null
@@ -1,116 +0,0 @@
-{
- "images" : [
- {
- "size" : "20x20",
- "idiom" : "iphone",
- "filename" : "icon_20pt@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "20x20",
- "idiom" : "iphone",
- "filename" : "icon_20pt@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "29x29",
- "idiom" : "iphone",
- "filename" : "icon_29pt@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "29x29",
- "idiom" : "iphone",
- "filename" : "icon_29pt@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "40x40",
- "idiom" : "iphone",
- "filename" : "icon_40pt@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "40x40",
- "idiom" : "iphone",
- "filename" : "icon_40pt@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "60x60",
- "idiom" : "iphone",
- "filename" : "icon_60pt@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "60x60",
- "idiom" : "iphone",
- "filename" : "icon_60pt@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "20x20",
- "idiom" : "ipad",
- "filename" : "icon_20pt.png",
- "scale" : "1x"
- },
- {
- "size" : "20x20",
- "idiom" : "ipad",
- "filename" : "icon_20pt@2x-1.png",
- "scale" : "2x"
- },
- {
- "size" : "29x29",
- "idiom" : "ipad",
- "filename" : "icon_29pt.png",
- "scale" : "1x"
- },
- {
- "size" : "29x29",
- "idiom" : "ipad",
- "filename" : "icon_29pt@2x-1.png",
- "scale" : "2x"
- },
- {
- "size" : "40x40",
- "idiom" : "ipad",
- "filename" : "icon_40pt.png",
- "scale" : "1x"
- },
- {
- "size" : "40x40",
- "idiom" : "ipad",
- "filename" : "icon_40pt@2x-1.png",
- "scale" : "2x"
- },
- {
- "size" : "76x76",
- "idiom" : "ipad",
- "filename" : "icon_76pt.png",
- "scale" : "1x"
- },
- {
- "size" : "76x76",
- "idiom" : "ipad",
- "filename" : "icon_76pt@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "83.5x83.5",
- "idiom" : "ipad",
- "filename" : "icon_83.5@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "1024x1024",
- "idiom" : "ios-marketing",
- "filename" : "Icon.png",
- "scale" : "1x"
- }
- ],
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
\ No newline at end of file
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon.png b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon.png
deleted file mode 100644
index 66b1931a14..0000000000
Binary files a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt.png b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt.png
deleted file mode 100644
index 90648b3f40..0000000000
Binary files a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x-1.png b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x-1.png
deleted file mode 100644
index a077a6f490..0000000000
Binary files a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x-1.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x.png b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x.png
deleted file mode 100644
index a077a6f490..0000000000
Binary files a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt@3x.png b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt@3x.png
deleted file mode 100644
index 600bdbd9cd..0000000000
Binary files a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_20pt@3x.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt.png b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt.png
deleted file mode 100644
index 8e04af0dd8..0000000000
Binary files a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x-1.png b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x-1.png
deleted file mode 100644
index 686e8d99e2..0000000000
Binary files a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x-1.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x.png b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x.png
deleted file mode 100644
index 686e8d99e2..0000000000
Binary files a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt@3x.png b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt@3x.png
deleted file mode 100644
index 1d013c3d33..0000000000
Binary files a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_29pt@3x.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt.png b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt.png
deleted file mode 100644
index a077a6f490..0000000000
Binary files a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x-1.png b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x-1.png
deleted file mode 100644
index da66b9ba82..0000000000
Binary files a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x-1.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x.png b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x.png
deleted file mode 100644
index da66b9ba82..0000000000
Binary files a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt@3x.png b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt@3x.png
deleted file mode 100644
index 59346ef4b6..0000000000
Binary files a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_40pt@3x.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x.png b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x.png
deleted file mode 100644
index 59346ef4b6..0000000000
Binary files a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x.png b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x.png
deleted file mode 100644
index d4640afc9a..0000000000
Binary files a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_76pt.png b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_76pt.png
deleted file mode 100644
index e3a04522bf..0000000000
Binary files a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_76pt.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_76pt@2x.png b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_76pt@2x.png
deleted file mode 100644
index 593ebd783d..0000000000
Binary files a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_76pt@2x.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x.png b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x.png
deleted file mode 100644
index ca02cd03bc..0000000000
Binary files a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x.png and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/LaunchIcon.imageset/LaunchIcon.pdf b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/LaunchIcon.imageset/LaunchIcon.pdf
deleted file mode 100644
index 2660891492..0000000000
Binary files a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Assets.xcassets/LaunchIcon.imageset/LaunchIcon.pdf and /dev/null differ
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Base.lproj/LaunchScreen.storyboard b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Base.lproj/LaunchScreen.storyboard
deleted file mode 100644
index 307d45b11d..0000000000
--- a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Base.lproj/LaunchScreen.storyboard
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Base.lproj/Main.storyboard b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Base.lproj/Main.storyboard
deleted file mode 100644
index 0f36128da4..0000000000
--- a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Resources/Base.lproj/Main.storyboard
+++ /dev/null
@@ -1,213 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/SceneDelegate.swift b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/SceneDelegate.swift
deleted file mode 100644
index b484ed11dc..0000000000
--- a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/SceneDelegate.swift
+++ /dev/null
@@ -1,40 +0,0 @@
-import UIKit
-
-class SceneDelegate: UIResponder, UIWindowSceneDelegate {
- var window: UIWindow?
-
- func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
- // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
- // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
- // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
- guard let _ = (scene as? UIWindowScene) else { return }
- }
-
- func sceneDidDisconnect(_ scene: UIScene) {
- // Called as the scene is being released by the system.
- // This occurs shortly after the scene enters the background, or when its session is discarded.
- // Release any resources associated with this scene that can be re-created the next time the scene connects.
- // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
- }
-
- func sceneDidBecomeActive(_ scene: UIScene) {
- // Called when the scene has moved from an inactive state to an active state.
- // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
- }
-
- func sceneWillResignActive(_ scene: UIScene) {
- // Called when the scene will move from an active state to an inactive state.
- // This may occur due to temporary interruptions (ex. an incoming phone call).
- }
-
- func sceneWillEnterForeground(_ scene: UIScene) {
- // Called as the scene transitions from the background to the foreground.
- // Use this method to undo the changes made on entering the background.
- }
-
- func sceneDidEnterBackground(_ scene: UIScene) {
- // Called as the scene transitions from the foreground to the background.
- // Use this method to save data, release shared resources, and store enough scene-specific state information
- // to restore the scene back to its current state.
- }
-}
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/ViewControllers/PlayerEditionViewController.swift b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/ViewControllers/PlayerEditionViewController.swift
deleted file mode 100644
index e9988462ae..0000000000
--- a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/ViewControllers/PlayerEditionViewController.swift
+++ /dev/null
@@ -1,128 +0,0 @@
-import UIKit
-
-class PlayerEditionViewController: UITableViewController {
- enum Mode {
- /// Edition ends with the "Commit" unwind segue.
- case creation
-
- /// Edition ends when user hits the back button.
- case edition
- }
-
- /// The edited player
- private(set) var player: Player
-
- /// The presentation mode
- let mode: Mode
-
- @IBOutlet private weak var cancelButtonItem: UIBarButtonItem!
- @IBOutlet private weak var saveButtonItem: UIBarButtonItem!
- @IBOutlet private weak var nameCell: UITableViewCell!
- @IBOutlet private weak var nameTextField: UITextField!
- @IBOutlet private weak var scoreCell: UITableViewCell!
- @IBOutlet private weak var scoreTextField: UITextField!
-
- init?(_ coder: NSCoder, mode: Mode, player: Player) {
- self.mode = mode
- self.player = player
- super.init(coder: coder)
- }
-
- @available(*, unavailable)
- required init?(coder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- override func viewDidLoad() {
- super.viewDidLoad()
- configureNavigationItem()
- configureForm()
- }
-}
-
-// MARK: - Navigation
-
-extension PlayerEditionViewController {
- override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
- // Force keyboard to dismiss early
- view.endEditing(true)
- return true
- }
-
- override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
- if segue.identifier == "Commit" {
- saveChanges()
- }
- }
-
- override func willMove(toParent parent: UIViewController?) {
- super.willMove(toParent: parent)
-
- if mode == .edition, parent == nil {
- // Self is popping from its navigation controller
- saveChanges()
- }
- }
-
- private func configureNavigationItem() {
- switch mode {
- case .creation:
- navigationItem.title = "New Player"
- navigationItem.leftBarButtonItem = cancelButtonItem
- navigationItem.rightBarButtonItem = saveButtonItem
- case .edition:
- navigationItem.title = player.name
- navigationItem.leftBarButtonItem = nil
- navigationItem.rightBarButtonItem = nil
- }
- }
-}
-
-// MARK: - Form
-
-extension PlayerEditionViewController: UITextFieldDelegate {
- override func viewWillAppear(_ animated: Bool) {
- super.viewWillAppear(animated)
- nameTextField.becomeFirstResponder()
- }
-
- override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
- tableView.deselectRow(at: indexPath, animated: false)
- let cell = tableView.cellForRow(at: indexPath)
- if cell === nameCell {
- nameTextField.becomeFirstResponder()
- } else if cell === scoreCell {
- scoreTextField.becomeFirstResponder()
- }
- }
-
- func textFieldShouldReturn(_ textField: UITextField) -> Bool {
- if textField == nameTextField {
- scoreTextField.becomeFirstResponder()
- }
- return false
- }
-
- @IBAction func textFieldDidChange(_ textField: UITextField) {
- // User has edited the player: prevent interactive dismissal
- isModalInPresentation = true
- }
-
- private func configureForm() {
- nameTextField.text = player.name
-
- if player.score == 0 && player.id == nil {
- scoreTextField.text = ""
- } else {
- scoreTextField.text = "\(player.score)"
- }
- }
-
- private func saveChanges() {
- var player = self.player
- player.name = nameTextField.text ?? ""
- player.score = scoreTextField.text.flatMap { Int($0) } ?? 0
- try! AppDatabase.shared.savePlayer(&player)
- self.player = player
- }
-}
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/ViewControllers/PlayerListViewController.swift b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/ViewControllers/PlayerListViewController.swift
deleted file mode 100644
index 0ef0c52d4b..0000000000
--- a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/ViewControllers/PlayerListViewController.swift
+++ /dev/null
@@ -1,194 +0,0 @@
-import UIKit
-import GRDB
-
-/// PlayerListViewController displays the list of players.
-class PlayerListViewController: UITableViewController {
- private enum PlayerOrdering {
- case byName
- case byScore
- }
-
- @IBOutlet private weak var newPlayerButtonItem: UIBarButtonItem!
- private var dataSource: PlayerDataSource!
- private var playersCancellable: DatabaseCancellable?
- private var playerOrdering: PlayerOrdering = .byScore {
- didSet {
- configureOrderingBarButtonItem()
- observePlayers()
- }
- }
-
- override func viewDidLoad() {
- super.viewDidLoad()
- configureToolbar()
- configureNavigationItem()
- configureDataSource()
- observePlayers()
- }
-
- override func viewWillAppear(_ animated: Bool) {
- super.viewWillAppear(animated)
- navigationController?.isToolbarHidden = false
- }
-
- private func configureToolbar() {
- toolbarItems = [
- UIBarButtonItem(systemItem: .trash, primaryAction: UIAction { [unowned self] _ in
- setEditing(false, animated: true)
- try! AppDatabase.shared.deleteAllPlayers()
- }),
- UIBarButtonItem(systemItem: .flexibleSpace),
- UIBarButtonItem(systemItem: .refresh, primaryAction: UIAction { [unowned self] _ in
- setEditing(false, animated: true)
- try! AppDatabase.shared.refreshPlayers()
- }),
- UIBarButtonItem(systemItem: .flexibleSpace),
- UIBarButtonItem(image: UIImage(systemName: "tornado"), primaryAction: UIAction { [unowned self] _ in
- setEditing(false, animated: true)
- for _ in 0..<50 {
- DispatchQueue.global().async {
- try! AppDatabase.shared.refreshPlayers()
- }
- }
- }),
- ]
- }
-
- private func configureNavigationItem() {
- navigationItem.backBarButtonItem = UIBarButtonItem(title: "Players")
- navigationItem.leftBarButtonItems = [editButtonItem, newPlayerButtonItem]
- configureOrderingBarButtonItem()
- }
-
- private func configureOrderingBarButtonItem() {
- switch playerOrdering {
- case .byScore:
- navigationItem.rightBarButtonItem = UIBarButtonItem(
- title: "Score ▼",
- primaryAction: UIAction { [unowned self] _ in
- setEditing(false, animated: true)
- playerOrdering = .byName
- })
- case .byName:
- navigationItem.rightBarButtonItem = UIBarButtonItem(
- title: "Name ▲",
- primaryAction: UIAction { [unowned self] _ in
- setEditing(false, animated: true)
- playerOrdering = .byScore
- })
- }
- }
-
- private func configureDataSource() {
- dataSource = PlayerDataSource(tableView: tableView) { (tableView, indexPath, player) in
- let cell = tableView.dequeueReusableCell(withIdentifier: "Player", for: indexPath)
- if player.name.isEmpty {
- cell.textLabel?.text = "(anonymous)"
- } else {
- cell.textLabel?.text = player.name
- }
- cell.detailTextLabel?.text = abs(player.score) > 1 ? "\(player.score) points" : "0 point"
- return cell
- }
- dataSource.defaultRowAnimation = .fade
- tableView.dataSource = dataSource
- }
-
- private func configureTitle(from players: [Player]) {
- switch players.count {
- case 0:
- navigationItem.title = "No Player"
- case 1:
- navigationItem.title = "1 Player"
- case let count:
- navigationItem.title = "\(count) Players"
- }
- }
-
- private func configureDataSource(from players: [Player]) {
- var snapshot = NSDiffableDataSourceSnapshot()
- snapshot.appendSections([0])
- snapshot.appendItems(players, toSection: 0)
-
- // Remember selection
- let selectedPlayerId = tableView.indexPathForSelectedRow.flatMap {
- dataSource.itemIdentifier(for: $0)?.id
- }
-
- // Avoid a UIKit warning; don't animate when popping from edition
- let animated = view.window != nil
-
- dataSource.apply(snapshot, animatingDifferences: animated, completion: {
- // Restore selection
- if let index = players.firstIndex(where: { $0.id == selectedPlayerId }) {
- self.tableView.selectRow(at: IndexPath(row: index, section: 0), animated: false, scrollPosition: .none)
- }
- })
- }
-
- private func observePlayers() {
- let request: QueryInterfaceRequest
- switch playerOrdering {
- case .byName:
- request = Player.all().orderedByName()
- case .byScore:
- request = Player.all().orderedByScore()
- }
-
- playersCancellable = ValueObservation
- .tracking(request.fetchAll(_:))
- .start(
- in: AppDatabase.shared.reader,
- // Immediate scheduling feeds the data source right on subscription,
- // and avoids an undesired animation when the application starts.
- scheduling: .immediate,
- onError: { error in fatalError("Unexpected error: \(error)") },
- onChange: { [weak self] players in
- guard let self else { return }
- self.configureTitle(from: players)
- self.configureDataSource(from: players)
- })
- }
-}
-
-
-// MARK: - Navigation
-
-extension PlayerListViewController {
- @IBSegueAction func makePlayerEditionViewController(_ coder: NSCoder) -> PlayerEditionViewController? {
- guard let indexPath = tableView.indexPathForSelectedRow,
- let player = dataSource.itemIdentifier(for: indexPath)
- else { return nil }
- return PlayerEditionViewController(coder, mode: .edition, player: player)
- }
-
- @IBSegueAction func makePlayerCreationViewController(_ coder: NSCoder) -> PlayerEditionViewController? {
- let player = Player(id: nil, name: "", score: 0)
- return PlayerEditionViewController(coder, mode: .creation, player: player)
- }
-
- @IBAction func cancelPlayerEdition(_ segue: UIStoryboardSegue) {
- // Player creation cancelled
- }
-
- @IBAction func commitPlayerEdition(_ segue: UIStoryboardSegue) {
- // Player creation committed
- }
-}
-
-
-// MARK: - UITableViewDataSource
-
-/// Subclass of UITableViewDiffableDataSource that supports row deletion
-private class PlayerDataSource: UITableViewDiffableDataSource {
- override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
- true
- }
-
- override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
- // Delete the player
- if let player = itemIdentifier(for: indexPath), let id = player.id {
- try! AppDatabase.shared.deletePlayers(ids: [id])
- }
- }
-}
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOSTests/AppDatabaseTests.swift b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOSTests/AppDatabaseTests.swift
deleted file mode 100644
index 2c359a8988..0000000000
--- a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOSTests/AppDatabaseTests.swift
+++ /dev/null
@@ -1,146 +0,0 @@
-import XCTest
-import GRDB
-@testable import GRDBDemoiOS
-
-class AppDatabaseTests: XCTestCase {
- func test_database_schema() throws {
- // Given an empty database
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
-
- // When we instantiate an AppDatabase
- _ = try AppDatabase(dbQueue)
-
- // Then the player table exists, with id, name & score columns
- try dbQueue.read { db in
- try XCTAssert(db.tableExists("player"))
- let columns = try db.columns(in: "player")
- let columnNames = Set(columns.map { $0.name })
- XCTAssertEqual(columnNames, ["id", "name", "score"])
- }
- }
-
- func test_savePlayer_inserts() throws {
- // Given an empty players database
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- let appDatabase = try AppDatabase(dbQueue)
-
- // When we save a new player
- var player = Player(id: nil, name: "Arthur", score: 100)
- try appDatabase.savePlayer(&player)
-
- // Then the player exists in the database
- try XCTAssertTrue(dbQueue.read(player.exists))
- }
-
- func test_savePlayer_updates() throws {
- // Given a players database that contains a player
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- let appDatabase = try AppDatabase(dbQueue)
- var player = Player(id: nil, name: "Arthur", score: 100)
- try dbQueue.write { db in
- try player.insert(db)
- }
-
- // When we modify and save the player
- player.name = "Barbara"
- player.score = 1000
- try appDatabase.savePlayer(&player)
-
- // Then the player has been updated in the database
- let fetchedPlayer = try dbQueue.read { db in
- try XCTUnwrap(Player.fetchOne(db, key: player.id))
- }
- XCTAssertEqual(fetchedPlayer, player)
- }
-
- func test_deletePlayers() throws {
- // Given a players database that contains four players
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- let appDatabase = try AppDatabase(dbQueue)
- var player1 = Player(id: nil, name: "Arthur", score: 100)
- var player2 = Player(id: nil, name: "Barbara", score: 200)
- var player3 = Player(id: nil, name: "Craig", score: 150)
- var player4 = Player(id: nil, name: "David", score: 120)
- try dbQueue.write { db in
- try player1.insert(db)
- try player2.insert(db)
- try player3.insert(db)
- try player4.insert(db)
- }
-
- // When we delete two players
- try appDatabase.deletePlayers(ids: [player1.id!, player3.id!])
-
- // Then the deleted players no longer exist
- try dbQueue.read { db in
- try XCTAssertFalse(player1.exists(db))
- try XCTAssertFalse(player3.exists(db))
- }
-
- // Then the database still contains two players
- try XCTAssertEqual(dbQueue.read(Player.fetchCount), 2)
- }
-
- func test_deleteAllPlayers() throws {
- // Given a players database that contains players
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- let appDatabase = try AppDatabase(dbQueue)
- var player1 = Player(id: nil, name: "Arthur", score: 100)
- var player2 = Player(id: nil, name: "Barbara", score: 200)
- var player3 = Player(id: nil, name: "Craig", score: 150)
- var player4 = Player(id: nil, name: "David", score: 120)
- try dbQueue.write { db in
- try player1.insert(db)
- try player2.insert(db)
- try player3.insert(db)
- try player4.insert(db)
- }
-
- // When we delete all players
- try appDatabase.deleteAllPlayers()
-
- // Then the database does not contain any player
- try XCTAssertEqual(dbQueue.read(Player.fetchCount), 0)
- }
-
- func test_refreshPlayers_populates_an_empty_database() throws {
- // Given an empty players database
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- let appDatabase = try AppDatabase(dbQueue)
-
- // When we refresh players
- try appDatabase.refreshPlayers()
-
- // Then the database is not empty
- try XCTAssert(dbQueue.read(Player.fetchCount) > 0)
- }
-
- func test_createRandomPlayersIfEmpty_populates_an_empty_database() throws {
- // Given an empty players database
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- let appDatabase = try AppDatabase(dbQueue)
-
- // When we create random players
- try appDatabase.createRandomPlayersIfEmpty()
-
- // Then the database is not empty
- try XCTAssert(dbQueue.read(Player.fetchCount) > 0)
- }
-
- func test_createRandomPlayersIfEmpty_does_not_modify_a_non_empty_database() throws {
- // Given a players database that contains one player
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- let appDatabase = try AppDatabase(dbQueue)
- var player = Player(id: nil, name: "Arthur", score: 100)
- try dbQueue.write { db in
- try player.insert(db)
- }
-
- // When we create random players
- try appDatabase.createRandomPlayersIfEmpty()
-
- // Then the database still only contains the original player
- let players = try dbQueue.read(Player.fetchAll)
- XCTAssertEqual(players, [player])
- }
-}
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOSTests/Info.plist b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOSTests/Info.plist
deleted file mode 100644
index 64d65ca495..0000000000
--- a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOSTests/Info.plist
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
- CFBundleDevelopmentRegion
- $(DEVELOPMENT_LANGUAGE)
- CFBundleExecutable
- $(EXECUTABLE_NAME)
- CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- $(PRODUCT_NAME)
- CFBundlePackageType
- $(PRODUCT_BUNDLE_PACKAGE_TYPE)
- CFBundleShortVersionString
- 1.0
- CFBundleVersion
- 1
-
-
diff --git a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOSTests/PlayerTests.swift b/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOSTests/PlayerTests.swift
deleted file mode 100644
index 64d1c1d2b3..0000000000
--- a/Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOSTests/PlayerTests.swift
+++ /dev/null
@@ -1,108 +0,0 @@
-import XCTest
-import GRDB
-@testable import GRDBDemoiOS
-
-class PlayerTests: XCTestCase {
- // MARK: - CRUD
- // Test that our Player type properly talks to GRDB.
-
- func testInsert() throws {
- // Given an empty players database
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- _ = try AppDatabase(dbQueue)
-
- // When we insert a player
- var player = Player(id: nil, name: "Arthur", score: 100)
- try dbQueue.write { db in
- try player.insert(db)
- }
-
- // Then the player gets a non-nil id
- XCTAssertNotNil(player.id)
- }
-
- func testRoundtrip() throws {
- // Given an empty players database
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- _ = try AppDatabase(dbQueue)
-
- // When we insert a player and fetch the player with the same id
- var insertedPlayer = Player(id: nil, name: "Arthur", score: 100)
- let fetchedPlayer: Player? = try dbQueue.write { db in
- try insertedPlayer.insert(db)
- return try Player.fetchOne(db, key: insertedPlayer.id)
- }
-
- // Then the fetched player is equal to the inserted player
- XCTAssertEqual(insertedPlayer, fetchedPlayer)
- }
-
- // MARK: - Requests
- // Test that requests defined on the Player type behave as expected.
-
- func testOrderedByScore() throws {
- // Given a players database that contains players with distinct scores
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- _ = try AppDatabase(dbQueue)
- var player1 = Player(id: 1, name: "Arthur", score: 100)
- var player2 = Player(id: 2, name: "Barbara", score: 200)
- var player3 = Player(id: 3, name: "Craig", score: 150)
- var player4 = Player(id: 4, name: "David", score: 120)
- try dbQueue.write { db in
- try player1.insert(db)
- try player2.insert(db)
- try player3.insert(db)
- try player4.insert(db)
- }
-
- // When we fetch players ordered by score
- let players = try dbQueue.read(Player.all().orderedByScore().fetchAll)
-
- // Then fetched players are ordered by score descending
- XCTAssertEqual(players, [player2, player3, player4, player1])
- }
-
- func testOrderedByScoreSortsIdenticalScoresByName() throws {
- // Given a players database that contains players with common scores
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- _ = try AppDatabase(dbQueue)
- var player1 = Player(id: 1, name: "Arthur", score: 100)
- var player2 = Player(id: 2, name: "Barbara", score: 200)
- var player3 = Player(id: 3, name: "Craig", score: 200)
- var player4 = Player(id: 4, name: "David", score: 200)
- try dbQueue.write { db in
- try player1.insert(db)
- try player2.insert(db)
- try player3.insert(db)
- try player4.insert(db)
- }
-
- // When we fetch players ordered by score
- let players = try dbQueue.read(Player.all().orderedByScore().fetchAll)
-
- // Then fetched players are ordered by score descending and by name
- XCTAssertEqual(players, [player2, player3, player4, player1])
- }
-
- func testOrderedByName() throws {
- // Given a players database that contains players with distinct names
- let dbQueue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration())
- _ = try AppDatabase(dbQueue)
- var player1 = Player(id: 1, name: "Arthur", score: 100)
- var player2 = Player(id: 2, name: "Barbara", score: 200)
- var player3 = Player(id: 3, name: "Craig", score: 150)
- var player4 = Player(id: 4, name: "David", score: 120)
- try dbQueue.write { db in
- try player1.insert(db)
- try player2.insert(db)
- try player3.insert(db)
- try player4.insert(db)
- }
-
- // When we fetch players ordered by name
- let players = try dbQueue.read(Player.all().orderedByName().fetchAll)
-
- // Then fetched players are ordered by name
- XCTAssertEqual(players, [player1, player2, player3, player4])
- }
-}
diff --git a/Documentation/DemoApps/GRDBDemoiOS/README.md b/Documentation/DemoApps/GRDBDemoiOS/README.md
deleted file mode 100644
index ab854697c7..0000000000
--- a/Documentation/DemoApps/GRDBDemoiOS/README.md
+++ /dev/null
@@ -1,45 +0,0 @@
-UIKit Demo Application
-======================
-
-
-
-**This demo application is a storyboard-based UIKit application.** For a demo application that uses Combine + SwiftUI, see [GRDBCombineDemo](../GRDBCombineDemo/README.md), and for Async/Await + SwiftUI, see [GRDBAsyncDemo](../GRDBAsyncDemo/README.md).
-
-**Requirements**: iOS 15.0+ / Xcode 12+
-
-> **Note**: This demo app is not a project template. Do not copy it as a starting point for your application. Instead, create a new project, choose a GRDB [installation method](../../../README.md#installation), and use the demo as an inspiration.
-
-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 table view with [ValueObservation](https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/valueobservation).
-- How to apply the recommendations of [Recommended Practices for Designing Record Types](https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/recordrecommendedpractices).
-
-**Files of interest:**
-
-- [AppDatabase.swift](GRDBDemoiOS/AppDatabase.swift)
-
- `AppDatabase` is the type that grants database access. It uses [DatabaseMigrator](https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databasemigrator) in order to setup the database schema, and [ValueObservation](https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/valueobservation) in order to let the application observe database changes.
-
-- [Persistence.swift](GRDBDemoiOS/Persistence.swift)
-
- This file defines the `AppDatabase` instance used by the application.
-
-- [Player.swift](GRDBDemoiOS/Player.swift)
-
- `Player` is a [Record](../../../README.md#records) type, able to read and write in the database. It conforms to the standard Codable protocol in order to gain all advantages of [Codable Records](../../../README.md#codable-records). It defines the database requests used by the application.
-
-- [PlayerListViewController.swift](GRDBDemoiOS/ViewControllers/PlayerListViewController.swift)
-
- `PlayerListViewController` displays the list of players.
-
-- [PlayerEditionViewController.swift](GRDBDemoiOS/ViewControllers/PlayerEditionViewController.swift)
-
- `PlayerEditionViewController` can create or edit a player, and save it in the database.
-
-- [GRDBDemoiOSTests](GRDBDemoiOSTests)
-
- - Test the database schema
- - Test the `Player` record and its requests
- - Test the `AppDatabase` methods that let the app access the database.
diff --git a/Documentation/DemoApps/GRDBDemoiOS/Screenshot.png b/Documentation/DemoApps/GRDBDemoiOS/Screenshot.png
deleted file mode 100644
index 7a0fccd710..0000000000
Binary files a/Documentation/DemoApps/GRDBDemoiOS/Screenshot.png and /dev/null differ
diff --git a/Documentation/DemoApps/README.md b/Documentation/DemoApps/README.md
index 136665c684..8c6ce177f6 100644
--- a/Documentation/DemoApps/README.md
+++ b/Documentation/DemoApps/README.md
@@ -1,13 +1,9 @@
Demo Applications
=================
-- [GRDBDemoiOS]: a storyboard-based UIKit application.
-- [GRDBCombineDemo]: a Combine + SwiftUI application.
-- [GRDBAsyncDemo]: a Async/Await + SwiftUI application.
+[GRDBDemo] demonstrates how GRDB can fuel a SwiftUI application.
-[GRDBCombineDemo] and [GRDBAsyncDemo] use the same `@Query` property wrapper, that lets SwiftUI views automatically update their content when the database changes. It is defined in the [GRDBQuery] package.
+See also the demo apps of the [GRDBQuery] package: they use the `@Query` property wrapper that helps SwiftUI views automatically update their content when the database changes.
-[GRDBDemoiOS]: GRDBDemoiOS
-[GRDBCombineDemo]: GRDBCombineDemo
-[GRDBAsyncDemo]: GRDBAsyncDemo
+[GRDBDemo]: GRDBDemo
[GRDBQuery]: https://github.com/groue/GRDBQuery
diff --git a/Documentation/GRDB7MigrationGuide.md b/Documentation/GRDB7MigrationGuide.md
new file mode 100644
index 0000000000..4fbf5df773
--- /dev/null
+++ b/Documentation/GRDB7MigrationGuide.md
@@ -0,0 +1,188 @@
+Migrating From GRDB 6 to GRDB 7
+===============================
+
+**This guide helps you upgrade your applications from GRDB 6 to GRDB 7.**
+
+- [Preparing the Migration to GRDB 7](#preparing-the-migration-to-grdb-7)
+- [New requirements](#new-requirements)
+- [The Record Base Class is Discouraged](#the-record-base-class-is-discouraged)
+- [Column Coding Strategies](#column-coding-strategies)
+- [Cancellable Async Database Accesses](#cancellable-async-database-accesses)
+- [Default Transaction Kind](#default-transaction-kind)
+- [Access to SQLite C functions](#access-to-sqlite-c-functions)
+- [Recommendations Regarding Swift Concurrency](#recommendations-regarding-swift-concurrency)
+- [Other Changes](#other-changes)
+
+## Preparing the Migration to GRDB 7
+
+Before upgrading, ensure you are using the [latest GRDB 6 release](https://github.com/groue/GRDB.swift/tags) and address any deprecation warnings. Once this is done, proceed with upgrading to GRDB 7. Due to breaking changes, your application may no longer compile. Follow the fix-it suggestions for simple syntax updates, and review the specific modifications described below.
+
+## New requirements
+
+GRDB requirements have been bumped:
+
+- **Swift Compiler 6+** (was Swift 5.7+). Both Swift 5 and Swift 6 language modes are supported. For more information, see the [Migrating to Swift 6] Apple guide.
+- **Xcode 16.0+** (was Xcode 14.0+)
+- **iOS 13+** (was iOS 11+)
+- **macOS 10.15+** (was macOS 10.13+)
+- **tvOS 13+** (was tvOS 11+)
+- **watchOS 7.0+** (was watchOS 4+)
+- **SQLite 3.20.0+** (was SQLite 3.19.3+)
+
+## The Record Base Class is Discouraged
+
+The usage of the [Record] base class is **discouraged** in GRDB 7. Present in GRDB 1.0, in 2017, it has served its purpose.
+
+It is not recommended to define any new type that subclass `Record`.
+
+It is recommended to refactor `Record` subclasses into Swift structs, before you enable the strict concurrency checkings or the Swift 6 language mode. See [Migrating to Swift 6] for more information about Swift 6 language modes.
+
+For example:
+
+```swift
+// GRDB 6
+class Player: Record {
+ var id: UUID
+ var name: String
+ var score: Int
+
+ override class var databaseTableName: String { "player" }
+
+ init(id: UUID, name: String, score: Int) { ... }
+ required init(row: Row) throws { ... }
+ override func encode(to container: inout PersistenceContainer) throws { ...}
+}
+
+// GRDB 7
+struct Player: Codable {
+ var id: UUID
+ var name: String
+ var score: Int
+}
+
+extension Player: FetchableRecord, PersistableRecord { }
+```
+
+Do not miss [Swift Concurrency and GRDB], for more recommendations regarding non-Sendable record types in GRDB.
+
+## Column Coding Strategies
+
+In GRDB 6, Codable record types can specify how `Data`, `Date`, and `UUID` properties are stored in the database:
+
+```swift
+// GRDB 6
+struct Player {
+ static let databaseDataDecodingStrategy = ...
+ static let databaseDateDecodingStrategy = ...
+ static let databaseDataEncodingStrategy = ...
+ static let databaseDateEncodingStrategy = ...
+ static let databaseUUIDEncodingStrategy = ...
+}
+```
+
+These properties have been removed in GRDB 7. You must now define methods that accept a column argument:
+
+```swift
+// GRDB 7
+struct Player {
+ static func databaseDataDecodingStrategy(for column: String) -> DatabaseDataDecodingStrategy { ... }
+ static func databaseDateDecodingStrategy(for column: String) -> DatabaseDateDecodingStrategy { ...}
+ static func databaseDataEncodingStrategy(for column: String) -> DatabaseDataEncodingStrategy { ... }
+ static func databaseDateEncodingStrategy(for column: String) -> DatabaseDateEncodingStrategy { ... }
+ static func databaseUUIDEncodingStrategy(for column: String) -> DatabaseUUIDEncodingStrategy { ... }
+}
+```
+
+## Cancellable Async Database Accesses
+
+In GRDB 6, asynchronous database accesses such as `try await read { ... }` or `try await write { ... }` complete even if the wrapper Task is cancelled.
+
+In GRDB 7, asynchronous database accesses respect Task cancellation. If a Task is cancelled, reads and writes throw a `CancellationError`, pending transactions are rolled back, and the database is not modified. The only SQL statement that can execute in a cancelled database access is `ROLLBACK`.
+
+The effect of this change on your application depends on how it uses tasks. For example, take care of database jobs initiated frop the [`task`](https://developer.apple.com/documentation/swiftui/view/task(priority:_:)) SwiftUI modifier.
+
+If you want an asynchronous database access to always complete, regardless of Task cancellation, wrap it in an unstructured Task:
+
+```swift
+// Create a new Task in order to ignore
+// cancellation of the current task, and
+// make sure database changes are always
+// committed to disk.
+let task = Task {
+ try await writer.write { ... }
+}
+// If needed, wait for the database job to complete:
+try await task.value
+```
+
+Other asynchronous database accesses, such as methods accepting a completion blocks (`asyncRead`, etc.), Combine publishers, RxSwift observables, do not handle cancellation and will proceed to completion by default.
+
+## Default Transaction Kind
+
+Some applications specify a default transaction kind, which was previously recommended in the [Sharing a Database] guide:
+
+```swift
+// GRDB 6
+var config = Configuration()
+config.defaultTransactionKind = .immediate
+```
+
+In GRDB 7, `Configuration` no longer has a `defaultTransactionKind` property, because transactions are automatically managed. Reads use DEFERRED transactions, and writes use IMMEDIATE transactions.
+
+You can still specify a transaction kind explicitly when necessary. See [Transaction Kinds] for details.
+
+## Access to SQLite C functions
+
+In GRDB 6, the underlying C SQLite library is implicitly available:
+
+```swift
+// GRDB 6
+import GRDB
+
+let sqliteVersion = sqlite3_libversion_number()
+```
+
+In GRDB 7, you may need an additional import, depending on how GRDB is integrated:
+
+- If your app uses the GRDB Swift Package Manager (SPM) package:
+
+ ```swift
+ import SQLite3
+
+ let sqliteVersion = sqlite3_libversion_number()
+ ```
+
+ The GRDB 6 SPM package included a product named "CSQLite." In GRDB 7, this product has been renamed "GRDBSQLite." Update your dependencies accordingly. It is unclear at the time of writing whether some projects can remove this dependency.
+
+- If your app uses SQLCipher:
+
+ ```swift
+ import SQLCipher
+
+ let sqliteVersion = sqlite3_libversion_number()
+ ```
+
+- In other cases, no additional import is needed.
+
+## Recommendations Regarding Swift Concurrency
+
+GRDB 7 requires Xcode 16+ and a Swift 6 compiler.
+
+Depending of the language mode and level of concurrency checkings used by your application (see [Migrating to Swift 6]), you may see warnings or errors. We address those issues, and provide general guidance, in [Swift Concurrency and GRDB].
+
+
+## Other Changes
+
+- `ValueObservation` must be started from the Main Actor by default. Use an explicit `async(onQueue: .main)` scheduling in order to remove this constraint.
+
+- `DatabasePool.concurrentRead` has been removed. Use [`asyncConcurrentRead`](https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databasepool/asyncconcurrentread(_:)) instead.
+
+- The `PersistenceContainer` subscript no longer guarantees that the value returned is the same as what was previously set. It only guarantees that both values are encoded identically in the database.
+
+- The async sequence returned by [`ValueObservation.values`](https://swiftpackageindex.com/groue/grdb.swiftdocumentation/grdb/valueobservation/values(in:scheduling:bufferingpolicy:)) now iterates on the cooperative thread pool by default. Use .mainActor as the scheduler if you need the previous behavior.
+
+[Migrating to Swift 6]: https://www.swift.org/migration/documentation/migrationguide
+[Sharing a Database]: https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databasesharing
+[Transaction Kinds]: https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/transactions#Transaction-Kinds
+[Swift Concurrency and GRDB]: https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/swiftconcurrency
+[Record]: https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/record
diff --git a/Documentation/ReleaseProcess.md b/Documentation/ReleaseProcess.md
index 08210d7404..07749a5b3a 100644
--- a/Documentation/ReleaseProcess.md
+++ b/Documentation/ReleaseProcess.md
@@ -7,8 +7,7 @@ To release a new GRDB version:
- Tests
- `make distclean test`
- - Build and run GRDBDemoiOS in Release configuration on a device
- - Archive GRDBDemoiOS
+ - Build and run GRDBDemo
- Check for performance regression with GRDBOSXPerformanceTests
- On https://github.com/groue/sqlcipher.git upgrade, update SQLCipher version in README.md
- On https://github.com/swiftlyfalling/SQLiteLib upgrade, update SQLite version in Documentation/CustomSQLiteBuilds.md
diff --git a/Documentation/SQLInterpolation.md b/Documentation/SQLInterpolation.md
index 2af59d2fb4..38666a8b9a 100644
--- a/Documentation/SQLInterpolation.md
+++ b/Documentation/SQLInterpolation.md
@@ -395,7 +395,9 @@ This chapter lists all kinds of supported interpolations.
struct AltPlayer: TableRecord {
static let databaseTableName = "player"
- static let databaseSelection: [any SQLSelectable] = [Column("id"), Column("name")]
+ static var databaseSelection: [any SQLSelectable] {
+ [Column("id"), Column("name")]
+ }
}
// SELECT player.id, player.name FROM player
diff --git a/GRDB.swift.podspec b/GRDB.swift.podspec
index 9ebb0c099d..dcfc881e78 100644
--- a/GRDB.swift.podspec
+++ b/GRDB.swift.podspec
@@ -9,11 +9,11 @@ Pod::Spec.new do |s|
s.source = { :git => 'https://github.com/groue/GRDB.swift.git', :tag => "v#{s.version}" }
s.module_name = 'GRDB'
- s.swift_versions = ['5.7']
- s.ios.deployment_target = '11.0'
- s.osx.deployment_target = '10.13'
- s.watchos.deployment_target = '4.0'
- s.tvos.deployment_target = '11.0'
+ s.swift_versions = ['5.10']
+ s.ios.deployment_target = '13.0'
+ s.osx.deployment_target = '10.15'
+ s.watchos.deployment_target = '7.0'
+ s.tvos.deployment_target = '13.0'
s.default_subspec = 'standard'
s.subspec 'standard' do |ss|
diff --git a/GRDB.xcodeproj/project.pbxproj b/GRDB.xcodeproj/project.pbxproj
index 9c13f9eb07..bf103ccf18 100755
--- a/GRDB.xcodeproj/project.pbxproj
+++ b/GRDB.xcodeproj/project.pbxproj
@@ -88,6 +88,7 @@
563363C01C942C04000BE133 /* DatabaseReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 563363BF1C942C04000BE133 /* DatabaseReader.swift */; };
563363C41C942C37000BE133 /* DatabaseWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 563363C31C942C37000BE133 /* DatabaseWriter.swift */; };
5636E9BC1D22574100B9B05F /* FetchRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5636E9BB1D22574100B9B05F /* FetchRequest.swift */; };
+ 563866CB2C847659004C515A /* AsyncSemaphore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 563866CA2C847654004C515A /* AsyncSemaphore.swift */; };
563B06AB217EF0CC00B38F35 /* ValueObservation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 563B06AA217EF0CC00B38F35 /* ValueObservation.swift */; };
563B06BD2185CCD300B38F35 /* ValueObservationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 563B06BC2185CCD300B38F35 /* ValueObservationTests.swift */; };
563B06C72185D29F00B38F35 /* ValueObservationReadonlyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 563B06C22185D29F00B38F35 /* ValueObservationReadonlyTests.swift */; };
@@ -105,6 +106,7 @@
563C67B324628BEA00E94EDC /* DatabasePoolTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 563C67B224628BEA00E94EDC /* DatabasePoolTests.swift */; };
563CBBE12A595131008905CE /* SQLIndexGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 563CBBE02A595131008905CE /* SQLIndexGenerator.swift */; };
563DE4F3231A91E2005081B7 /* DatabaseConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 563DE4EC231A91E2005081B7 /* DatabaseConfigurationTests.swift */; };
+ 563EA3E12C7B3A22001BE0D4 /* Mutex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 563EA3E02C7B3A22001BE0D4 /* Mutex.swift */; };
563EF415215F87EB007DAACD /* OrderedDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 563EF414215F87EB007DAACD /* OrderedDictionary.swift */; };
563EF42D2161180D007DAACD /* AssociationAggregate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 563EF42C2161180D007DAACD /* AssociationAggregate.swift */; };
563EF43F216131D1007DAACD /* AssociationAggregateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 563EF43E216131D1007DAACD /* AssociationAggregateTests.swift */; };
@@ -165,7 +167,7 @@
5657AAB91D107001006283EF /* NSData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5657AAB81D107001006283EF /* NSData.swift */; };
5657AB0F1D10899D006283EF /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5657AB0E1D10899D006283EF /* URL.swift */; };
5659F4881EA8D94E004A4992 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5659F4871EA8D94E004A4992 /* Utils.swift */; };
- 5659F4901EA8D964004A4992 /* ReadWriteBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5659F48F1EA8D964004A4992 /* ReadWriteBox.swift */; };
+ 5659F4901EA8D964004A4992 /* ReadWriteLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5659F48F1EA8D964004A4992 /* ReadWriteLock.swift */; };
5659F4981EA8D989004A4992 /* Pool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5659F4971EA8D989004A4992 /* Pool.swift */; };
5664759A1D97D8A000FF74B8 /* SQLCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566475991D97D8A000FF74B8 /* SQLCollection.swift */; };
566475CC1D981D5E00FF74B8 /* SQLFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566475CA1D981D5E00FF74B8 /* SQLFunctions.swift */; };
@@ -186,7 +188,7 @@
566B912B1FA4D0CC0012D5B0 /* StatementAuthorizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566B912A1FA4D0CC0012D5B0 /* StatementAuthorizer.swift */; };
566B91331FA4D3810012D5B0 /* TransactionObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566B91321FA4D3810012D5B0 /* TransactionObserver.swift */; };
566B9C2025C6CC24004542CF /* RowDecodingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566B9C1F25C6CC24004542CF /* RowDecodingError.swift */; };
- 566BE71E2342542F00A8254B /* LockedBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566BE7172342542F00A8254B /* LockedBox.swift */; };
+ 566BE71E2342542F00A8254B /* Mutex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566BE7172342542F00A8254B /* Mutex.swift */; };
566DDE0D288D763C0000DCFB /* Fixits.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566DDE0C288D763C0000DCFB /* Fixits.swift */; };
56703297212B5450007D270F /* DatabaseUUIDEncodingStrategyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56703290212B544F007D270F /* DatabaseUUIDEncodingStrategyTests.swift */; };
56713FDD2691F409006153C3 /* JSONRequiredEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56713FDC2691F409006153C3 /* JSONRequiredEncoder.swift */; };
@@ -267,7 +269,6 @@
56A2388B1B9C75030082EB20 /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A238781B9C75030082EB20 /* Statement.swift */; };
56A238931B9C750B0082EB20 /* DatabaseMigrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A238921B9C750B0082EB20 /* DatabaseMigrator.swift */; };
56A238A41B9C753B0082EB20 /* Record.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A238A11B9C753B0082EB20 /* Record.swift */; };
- 56A2FA3624424D2A00E97D23 /* Export.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A2FA3524424D2A00E97D23 /* Export.swift */; };
56A5EF0F1EF7F20B00F03071 /* ForeignKeyInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A5EF0E1EF7F20B00F03071 /* ForeignKeyInfoTests.swift */; };
56A8C2301D1914540096E9D4 /* UUID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A8C22F1D1914540096E9D4 /* UUID.swift */; };
56AACAA822ACED7100A40F2A /* Fetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AACAA722ACED7100A40F2A /* Fetch.swift */; };
@@ -518,6 +519,7 @@
563363D41C94484E000BE133 /* DatabaseQueueReleaseMemoryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseQueueReleaseMemoryTests.swift; sourceTree = ""; };
5634B1061CF9B970005360B9 /* TransactionObserverSavepointsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionObserverSavepointsTests.swift; sourceTree = ""; };
5636E9BB1D22574100B9B05F /* FetchRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchRequest.swift; sourceTree = ""; };
+ 563866CA2C847654004C515A /* AsyncSemaphore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncSemaphore.swift; sourceTree = ""; };
563B06AA217EF0CC00B38F35 /* ValueObservation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValueObservation.swift; sourceTree = ""; };
563B06BC2185CCD300B38F35 /* ValueObservationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValueObservationTests.swift; sourceTree = ""; };
563B06C22185D29F00B38F35 /* ValueObservationReadonlyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueObservationReadonlyTests.swift; sourceTree = ""; };
@@ -535,6 +537,7 @@
563C67B224628BEA00E94EDC /* DatabasePoolTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabasePoolTests.swift; sourceTree = ""; };
563CBBE02A595131008905CE /* SQLIndexGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SQLIndexGenerator.swift; sourceTree = ""; };
563DE4EC231A91E2005081B7 /* DatabaseConfigurationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseConfigurationTests.swift; sourceTree = ""; };
+ 563EA3E02C7B3A22001BE0D4 /* Mutex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mutex.swift; sourceTree = ""; };
563EF414215F87EB007DAACD /* OrderedDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderedDictionary.swift; sourceTree = ""; };
563EF42C2161180D007DAACD /* AssociationAggregate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssociationAggregate.swift; sourceTree = ""; };
563EF43E216131D1007DAACD /* AssociationAggregateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssociationAggregateTests.swift; sourceTree = ""; };
@@ -602,7 +605,7 @@
5657AB341D108BA9006283EF /* FoundationNSURLTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationNSURLTests.swift; sourceTree = ""; };
5657AB351D108BA9006283EF /* FoundationURLTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationURLTests.swift; sourceTree = ""; };
5659F4871EA8D94E004A4992 /* Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; };
- 5659F48F1EA8D964004A4992 /* ReadWriteBox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadWriteBox.swift; sourceTree = ""; };
+ 5659F48F1EA8D964004A4992 /* ReadWriteLock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadWriteLock.swift; sourceTree = ""; };
5659F4971EA8D989004A4992 /* Pool.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Pool.swift; sourceTree = ""; };
565B0FEE1BBC7D980098DE03 /* FetchableRecordTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchableRecordTests.swift; sourceTree = ""; };
565D5D701BBC694D00DC9BD4 /* Row+FoundationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Row+FoundationTests.swift"; sourceTree = ""; };
@@ -629,7 +632,7 @@
566B912A1FA4D0CC0012D5B0 /* StatementAuthorizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatementAuthorizer.swift; sourceTree = ""; };
566B91321FA4D3810012D5B0 /* TransactionObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionObserver.swift; sourceTree = ""; };
566B9C1F25C6CC24004542CF /* RowDecodingError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RowDecodingError.swift; sourceTree = ""; };
- 566BE7172342542F00A8254B /* LockedBox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LockedBox.swift; sourceTree = ""; };
+ 566BE7172342542F00A8254B /* Mutex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Mutex.swift; sourceTree = ""; };
566DDE0C288D763C0000DCFB /* Fixits.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fixits.swift; sourceTree = ""; };
56703290212B544F007D270F /* DatabaseUUIDEncodingStrategyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseUUIDEncodingStrategyTests.swift; sourceTree = ""; };
56713FDC2691F409006153C3 /* JSONRequiredEncoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONRequiredEncoder.swift; sourceTree = ""; };
@@ -717,6 +720,7 @@
5698AD151DAAD16F0056AF8C /* FTS5Tokenizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5Tokenizer.swift; sourceTree = ""; };
5698AD201DABAEFA0056AF8C /* FTS5WrapperTokenizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5WrapperTokenizer.swift; sourceTree = ""; };
5698AD341DABAF4A0056AF8C /* FTS5CustomTokenizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS5CustomTokenizer.swift; sourceTree = ""; };
+ 5698C1C32CA844F4001C0EB0 /* GRDBTests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = GRDBTests.xcconfig; sourceTree = ""; };
569BBA20228DE51800478429 /* AssociationPrefetchingFetchableRecordTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssociationPrefetchingFetchableRecordTests.swift; sourceTree = ""; };
569BBA3522905FFA00478429 /* InflectionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InflectionsTests.swift; sourceTree = ""; };
569BBA4522906A8200478429 /* InflectionsTests.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = InflectionsTests.json; sourceTree = ""; };
@@ -756,7 +760,6 @@
56A238921B9C750B0082EB20 /* DatabaseMigrator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseMigrator.swift; sourceTree = ""; };
56A238A11B9C753B0082EB20 /* Record.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Record.swift; sourceTree = ""; };
56A238B51B9CA2590082EB20 /* DatabaseTimestampTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseTimestampTests.swift; sourceTree = ""; };
- 56A2FA3524424D2A00E97D23 /* Export.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Export.swift; sourceTree = ""; };
56A4CDAF1D4234B200B1A9B9 /* SQLExpressionLiteralTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLExpressionLiteralTests.swift; sourceTree = ""; };
56A5E4081BA2BCF900707640 /* RecordWithColumnNameManglingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordWithColumnNameManglingTests.swift; sourceTree = ""; };
56A5EF0E1EF7F20B00F03071 /* ForeignKeyInfoTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ForeignKeyInfoTests.swift; sourceTree = ""; };
@@ -1010,8 +1013,10 @@
56176C581EACC2D8000F3F2B /* GRDBTests */ = {
isa = PBXGroup;
children = (
+ 563866CA2C847654004C515A /* AsyncSemaphore.swift */,
56677C14241D14450050755D /* FailureTestCase.swift */,
5623E0901B4AFACC00B20B7F /* GRDBTestCase.swift */,
+ 563EA3E02C7B3A22001BE0D4 /* Mutex.swift */,
562EA81E1F17B26F00FA528C /* Compilation */,
56A238111B9C74A90082EB20 /* Core */,
567B5BDA2AD3281B00629622 /* Dump */,
@@ -1320,11 +1325,11 @@
56717270261C68E900423B6F /* CaseInsensitiveIdentifier.swift */,
563EF4492161F179007DAACD /* Inflections.swift */,
569BBA482291707D00478429 /* Inflections+English.swift */,
- 566BE7172342542F00A8254B /* LockedBox.swift */,
+ 566BE7172342542F00A8254B /* Mutex.swift */,
563B8FC424A1D3B9007A48C9 /* OnDemandFuture.swift */,
563EF414215F87EB007DAACD /* OrderedDictionary.swift */,
5659F4971EA8D989004A4992 /* Pool.swift */,
- 5659F48F1EA8D964004A4992 /* ReadWriteBox.swift */,
+ 5659F48F1EA8D964004A4992 /* ReadWriteLock.swift */,
563B8FB424A1D029007A48C9 /* ReceiveValuesOn.swift */,
56781B0A243F86E600650A83 /* Refinable.swift */,
5659F4871EA8D94E004A4992 /* Utils.swift */,
@@ -1740,7 +1745,6 @@
DC37742D19C8CC90004FCF85 /* GRDB */ = {
isa = PBXGroup;
children = (
- 56A2FA3524424D2A00E97D23 /* Export.swift */,
566DDE0C288D763C0000DCFB /* Fixits.swift */,
648704AD2B7E66390036480B /* PrivacyInfo.xcprivacy */,
56A2386F1B9C75030082EB20 /* Core */,
@@ -1766,6 +1770,7 @@
56C48E731C9A9923005DF1D9 /* module.modulemap */,
DC3773F719C8CBB3004FCF85 /* Info.plist */,
56B8F49A1B4E2F3600C24296 /* GRDB.xcconfig */,
+ 5698C1C32CA844F4001C0EB0 /* GRDBTests.xcconfig */,
56C494401ED7255500CC72AF /* GRDBDeploymentTarget.xcconfig */,
);
name = "Supporting Files";
@@ -2061,6 +2066,7 @@
5615B275222B107900061C1C /* AssociationHasOneThroughFetchableRecordTests.swift in Sources */,
56D4966F1D81309E008276D7 /* RecordPrimaryKeyNoneTests.swift in Sources */,
56D496621D81304E008276D7 /* StatementArguments+FoundationTests.swift in Sources */,
+ 563EA3E12C7B3A22001BE0D4 /* Mutex.swift in Sources */,
56176C5D1EACCCC7000F3F2B /* FTS5TokenizerTests.swift in Sources */,
56D496B21D8133CE008276D7 /* DatabaseQueueInMemoryTests.swift in Sources */,
562393601DEE06D300A6B01F /* CursorTests.swift in Sources */,
@@ -2114,6 +2120,7 @@
56D496681D813086008276D7 /* FetchableRecord+QueryInterfaceRequestTests.swift in Sources */,
5623934E1DEDFEFB00A6B01F /* EnumeratedCursorTests.swift in Sources */,
568068311EBBA26100EFB8AA /* SQLRequestTests.swift in Sources */,
+ 563866CB2C847659004C515A /* AsyncSemaphore.swift in Sources */,
56703297212B5450007D270F /* DatabaseUUIDEncodingStrategyTests.swift in Sources */,
5676FBA622F5CAD9004717D9 /* ValueObservationRegionRecordingTests.swift in Sources */,
56D496B41D8133F8008276D7 /* DatabaseTests.swift in Sources */,
@@ -2180,7 +2187,7 @@
563B06AB217EF0CC00B38F35 /* ValueObservation.swift in Sources */,
56D110FA28AFC97E00E64463 /* MutablePersistableRecord+DAO.swift in Sources */,
56CEB5111EAA324B00BFAF62 /* FTS3+QueryInterface.swift in Sources */,
- 5659F4901EA8D964004A4992 /* ReadWriteBox.swift in Sources */,
+ 5659F4901EA8D964004A4992 /* ReadWriteLock.swift in Sources */,
566A841A2041146100E50BFD /* DatabaseSnapshot.swift in Sources */,
569EF0E2200D2D8400A9FA45 /* DatabaseRegion.swift in Sources */,
56CEB4F11EAA2EFA00BFAF62 /* FetchableRecord.swift in Sources */,
@@ -2226,7 +2233,6 @@
5617294E223533F40006E219 /* EncodableRecord.swift in Sources */,
56B7EE832863781300C0525F /* WALSnapshot.swift in Sources */,
5698AD181DAAD17A0056AF8C /* FTS5Tokenizer.swift in Sources */,
- 56A2FA3624424D2A00E97D23 /* Export.swift in Sources */,
56B964B11DA51D010002DA19 /* FTS5TokenizerDescriptor.swift in Sources */,
4E13D2F32769B87F0037588C /* DatabaseBackupProgress.swift in Sources */,
560A37A71C8FF6E500949E71 /* SerializedDatabase.swift in Sources */,
@@ -2269,7 +2275,7 @@
5690C3401D23E82A00E59934 /* Data.swift in Sources */,
5659F4881EA8D94E004A4992 /* Utils.swift in Sources */,
567B5BE82AD3284100629622 /* DumpFormat.swift in Sources */,
- 566BE71E2342542F00A8254B /* LockedBox.swift in Sources */,
+ 566BE71E2342542F00A8254B /* Mutex.swift in Sources */,
56A238931B9C750B0082EB20 /* DatabaseMigrator.swift in Sources */,
5603CEBB2AC862EC00CF097D /* SQLJSONExpressible.swift in Sources */,
56F89DF72A57EAA9002FE2AA /* ColumnDefinition.swift in Sources */,
@@ -2340,7 +2346,7 @@
/* Begin XCBuildConfiguration section */
56E5D8021B4D422E00430942 /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 56C494401ED7255500CC72AF /* GRDBDeploymentTarget.xcconfig */;
+ baseConfigurationReference = 5698C1C32CA844F4001C0EB0 /* GRDBTests.xcconfig */;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
@@ -2370,7 +2376,7 @@
};
56E5D8031B4D422E00430942 /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 56C494401ED7255500CC72AF /* GRDBDeploymentTarget.xcconfig */;
+ baseConfigurationReference = 5698C1C32CA844F4001C0EB0 /* GRDBTests.xcconfig */;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
diff --git a/GRDB.xcworkspace/contents.xcworkspacedata b/GRDB.xcworkspace/contents.xcworkspacedata
index 3258ed7a7b..26f0eae470 100644
--- a/GRDB.xcworkspace/contents.xcworkspacedata
+++ b/GRDB.xcworkspace/contents.xcworkspacedata
@@ -45,12 +45,6 @@
location = "group:Tests/Performance/GRDBProfiling/GRDBProfiling.xcodeproj">
-
-
-
-
+ location = "group:Documentation/DemoApps/GRDBDemo/GRDBDemo.xcodeproj">
diff --git a/GRDB/Core/Configuration.swift b/GRDB/Core/Configuration.swift
index 6b261902b6..059bbb86f9 100644
--- a/GRDB/Core/Configuration.swift
+++ b/GRDB/Core/Configuration.swift
@@ -1,7 +1,16 @@
+// Import C SQLite functions
+#if SWIFT_PACKAGE
+import GRDBSQLite
+#elseif GRDBCIPHER
+import SQLCipher
+#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER
+import SQLite3
+#endif
+
import Dispatch
import Foundation
-public struct Configuration {
+public struct Configuration: Sendable {
// MARK: - Misc options
@@ -186,7 +195,7 @@ public struct Configuration {
// MARK: - Managing SQLite Connections
- private var setups: [(Database) throws -> Void] = []
+ private var setups: [@Sendable (Database) throws -> Void] = []
/// Defines a function to run whenever an SQLite connection is opened.
///
@@ -223,34 +232,12 @@ public struct Configuration {
///
/// On newly created databases files, ``DatabasePool`` activates the WAL
/// mode after the preparation functions have run.
- public mutating func prepareDatabase(_ setup: @escaping (Database) throws -> Void) {
+ public mutating func prepareDatabase(_ setup: @escaping @Sendable (Database) throws -> Void) {
setups.append(setup)
}
// MARK: - Transactions
- /// The default kind of write transactions.
- ///
- /// The default is ``Database/TransactionKind/deferred``.
- ///
- /// You can change the default transaction kind. For example, you can force
- /// all write transactions to be `IMMEDIATE`:
- ///
- /// ```swift
- /// var config = Configuration()
- /// config.defaultTransactionKind = .immediate
- /// let dbQueue = try DatabaseQueue(configuration: config)
- ///
- /// // BEGIN IMMEDIATE TRANSACTION; ...; COMMIT TRANSACTION;
- /// try dbQueue.write { db in ... }
- /// ```
- ///
- /// This property is ignored for read-only transactions. Those always open
- /// `DEFERRED` SQLite transactions.
- ///
- /// Related SQLite documentation:
- public var defaultTransactionKind: Database.TransactionKind = .deferred
-
/// A boolean value indicating whether it is valid to leave a transaction
/// opened at the end of a database access method.
///
@@ -445,9 +432,25 @@ public struct Configuration {
/// through a `SerializedDatabase`.
var threadingMode = Database.ThreadingMode.default
- var SQLiteConnectionDidOpen: (() -> Void)?
- var SQLiteConnectionWillClose: ((SQLiteConnection) -> Void)?
- var SQLiteConnectionDidClose: (() -> Void)?
+ private(set) var SQLiteConnectionDidOpen: (@Sendable () -> Void)?
+ private(set) var SQLiteConnectionWillClose: (@Sendable (SQLiteConnection) -> Void)?
+ private(set) var SQLiteConnectionDidClose: (@Sendable () -> Void)?
+
+ // Workaround https://github.com/apple/swift/issues/72727
+ mutating func onConnectionDidOpen(_ callback: @escaping @Sendable () -> Void) {
+ SQLiteConnectionDidOpen = callback
+ }
+
+ // Workaround https://github.com/apple/swift/issues/72727
+ mutating func onConnectionWillClose(_ callback: @escaping @Sendable (SQLiteConnection) -> Void) {
+ SQLiteConnectionWillClose = callback
+ }
+
+ // Workaround https://github.com/apple/swift/issues/72727
+ mutating func onConnectionDidClose(_ callback: @escaping @Sendable () -> Void) {
+ SQLiteConnectionDidClose = callback
+ }
+
var SQLiteOpenFlags: CInt {
var flags = readonly ? SQLITE_OPEN_READONLY : (SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE)
if sqlite3_libversion_number() >= 3037000 {
diff --git a/GRDB/Core/Cursor.swift b/GRDB/Core/Cursor.swift
index af874297c1..68baeab44d 100644
--- a/GRDB/Core/Cursor.swift
+++ b/GRDB/Core/Cursor.swift
@@ -670,9 +670,6 @@ extension Cursor where Element: Equatable {
extension Cursor where Element: Comparable {
/// Returns the maximum element in the cursor.
///
- /// - Parameter areInIncreasingOrder: A predicate that returns `true`
- /// if its first argument should be ordered before its second
- /// argument; otherwise, `false`.
/// - Returns: The cursor's maximum element, according to
/// `areInIncreasingOrder`. If the cursor has no elements, returns
/// `nil`.
@@ -682,9 +679,6 @@ extension Cursor where Element: Comparable {
/// Returns the minimum element in the cursor.
///
- /// - Parameter areInIncreasingOrder: A predicate that returns `true`
- /// if its first argument should be ordered before its second
- /// argument; otherwise, `false`.
/// - Returns: The cursor's minimum element, according to
/// `areInIncreasingOrder`. If the cursor has no elements, returns
/// `nil`.
@@ -754,17 +748,13 @@ public final class AnyCursor: Cursor {
}
/// Creates a new cursor whose elements are elements of `iterator`.
- public convenience init(iterator: I)
- where I: IteratorProtocol, I.Element == Element
- {
+ public convenience init(iterator: some IteratorProtocol) {
var iterator = iterator
self.init { iterator.next() }
}
/// Creates a new cursor whose elements are elements of `sequence`.
- public convenience init(_ sequence: S)
- where S: Sequence, S.Element == Element
- {
+ public convenience init(_ sequence: some Sequence) {
self.init(iterator: sequence.makeIterator())
}
@@ -915,6 +905,10 @@ public final class FilterCursor {
}
}
+// Explicit non-conformance to Sendable.
+@available(*, unavailable)
+extension FilterCursor: Sendable { }
+
extension FilterCursor: Cursor {
public func next() throws -> Base.Element? {
while let element = try base.next() {
diff --git a/GRDB/Core/Database+Schema.swift b/GRDB/Core/Database+Schema.swift
index 878cbe72bf..beb6712771 100644
--- a/GRDB/Core/Database+Schema.swift
+++ b/GRDB/Core/Database+Schema.swift
@@ -1,3 +1,12 @@
+// Import C SQLite functions
+#if SWIFT_PACKAGE
+import GRDBSQLite
+#elseif GRDBCIPHER
+import SQLCipher
+#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER
+import SQLite3
+#endif
+
extension Database {
/// A cache for the available database schemas.
struct SchemaCache {
@@ -589,13 +598,11 @@ extension Database {
/// try db.table("t", hasUniqueKey: ["id", "a"]) // true
/// try db.table("t", hasUniqueKey: ["id", "a", "b", "c"]) // true
/// ```
- public func table(
+ public func table(
_ tableName: String,
- hasUniqueKey columns: Columns)
- throws -> Bool
- where Columns: Sequence, Columns.Element == String
- {
- try columnsForUniqueKey(Array(columns), in: tableName) != nil
+ hasUniqueKey columns: some Collection
+ ) throws -> Bool {
+ try columnsForUniqueKey(columns, in: tableName) != nil
}
/// Returns the foreign keys defined on table named `tableName`.
@@ -920,12 +927,10 @@ extension Database {
/// returns the columns of the unique key, ordered as the matching index (or
/// primary key). The case of returned columns is not guaranteed to match
/// the case of input columns.
- func columnsForUniqueKey(
- _ columns: Columns,
- in tableName: String)
- throws -> [String]?
- where Columns: Sequence, Columns.Element == String
- {
+ func columnsForUniqueKey(
+ _ columns: some Collection,
+ in tableName: String
+ ) throws -> [String]? {
let lowercasedColumns = Set(columns.map { $0.lowercased() })
if lowercasedColumns.isEmpty {
// Don't hit the database for trivial case
diff --git a/GRDB/Core/Database+Statements.swift b/GRDB/Core/Database+Statements.swift
index 061c5b3646..46038a83b5 100644
--- a/GRDB/Core/Database+Statements.swift
+++ b/GRDB/Core/Database+Statements.swift
@@ -1,3 +1,12 @@
+// Import C SQLite functions
+#if SWIFT_PACKAGE
+import GRDBSQLite
+#elseif GRDBCIPHER
+import SQLCipher
+#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER
+import SQLite3
+#endif
+
import Foundation
extension Database {
@@ -430,6 +439,7 @@ extension Database {
// documentation of this method for more information).
try checkForAbortedTransaction(sql: statement.sql, arguments: statement.arguments)
+ // Cancelled database accesses must not execute.
// Suspended databases must not execute statements that create the risk
// of `0xdead10cc` exception (see the documentation of this method for
// more information).
@@ -482,6 +492,17 @@ extension Database {
// and throws the user-provided cancelled commit error.
try observationBroker?.statementDidFail(statement)
+ switch ResultCode(rawValue: resultCode) {
+ case .SQLITE_INTERRUPT, .SQLITE_ABORT:
+ if suspensionMutex.load().isCancelled {
+ // The only error that a user sees when a Task is cancelled
+ // is CancellationError.
+ throw CancellationError()
+ }
+ default:
+ break
+ }
+
// Throw statement failure
throw DatabaseError(
resultCode: resultCode,
@@ -531,19 +552,7 @@ struct StatementCache {
// > time and probably reused many times.
//
// This looks like a perfect match for cached statements.
- //
- // However SQLITE_PREPARE_PERSISTENT was only introduced in
- // SQLite 3.20.0 http://www.sqlite.org/changes.html#version_3_20
- #if GRDBCUSTOMSQLITE || GRDBCIPHER
let statement = try db.makeStatement(sql: sql, prepFlags: CUnsignedInt(SQLITE_PREPARE_PERSISTENT))
- #else
- let statement: Statement
- if #available(iOS 12, macOS 10.14, watchOS 5, *) { // SQLite 3.20+
- statement = try db.makeStatement(sql: sql, prepFlags: CUnsignedInt(SQLITE_PREPARE_PERSISTENT))
- } else {
- statement = try db.makeStatement(sql: sql)
- }
- #endif
statements[sql] = statement
return statement
}
diff --git a/GRDB/Core/Database.swift b/GRDB/Core/Database.swift
index 9d5a13b7bd..61354847b1 100644
--- a/GRDB/Core/Database.swift
+++ b/GRDB/Core/Database.swift
@@ -1,3 +1,12 @@
+// Import C SQLite functions
+#if SWIFT_PACKAGE
+import GRDBSQLite
+#elseif GRDBCIPHER
+import SQLCipher
+#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER
+import SQLite3
+#endif
+
import Foundation
/// A raw SQLite connection, suitable for the SQLite C API.
@@ -113,8 +122,14 @@ let SQLITE_TRANSIENT = unsafeBitCast(OpaquePointer(bitPattern: -1), to: sqlite3_
/// - ``logError``
/// - ``releaseMemory()``
/// - ``trace(options:_:)``
+///
+/// ### Supporting Types
+///
+/// - ``BusyCallback``
+/// - ``BusyMode``
/// - ``CheckpointMode``
/// - ``DatabaseBackupProgress``
+/// - ``LogErrorFunction``
/// - ``StorageClass``
/// - ``TraceEvent``
/// - ``TracingOptions``
@@ -134,8 +149,26 @@ public final class Database: CustomStringConvertible, CustomDebugStringConvertib
/// The error logging function.
///
+ /// SQLite can be configured to invoke a callback function containing
+ /// an error code and a terse error message whenever anomalies occur.
+ ///
+ /// This global error callback must be configured early in the lifetime
+ /// of your application:
+ ///
+ /// ```swift
+ /// Database.logError = { (resultCode, message) in
+ /// NSLog("%@", "SQLite error \(resultCode): \(message)")
+ /// }
+ /// ```
+ ///
+ /// - warning: Database.logError must be set before any database
+ /// connection is opened. This includes the connections that your
+ /// application opens with GRDB, but also connections opened by
+ /// other tools, such as third-party libraries. Setting it after a
+ /// connection has been opened is an SQLite misuse, and has no effect.
+ ///
/// Related SQLite documentation:
- public static var logError: LogErrorFunction? = nil {
+ nonisolated(unsafe) public static var logError: LogErrorFunction? = nil {
didSet {
if logError != nil {
_registerErrorLogCallback { (_, code, message) in
@@ -279,18 +312,29 @@ public final class Database: CustomStringConvertible, CustomDebugStringConvertib
/// `isRecordingSelectedRegion` is true.
var selectedRegion = DatabaseRegion()
- /// Support for `checkForAbortedTransaction()`
- var isInsideTransactionBlock = false
-
- /// Support for `checkForSuspensionViolation(from:)`
- @LockedBox var isSuspended = false
-
/// Support for `checkForSuspensionViolation(from:)`
/// This cache is never cleared: we assume journal mode never changes.
var journalModeCache: String?
+ // MARK: - Suspension
+
+ struct Suspension {
+ /// If true, the database is suspended and should not acquire any
+ /// write lock in order to avoid the 0xDEAD10CC exception.
+ var isSuspended: Bool
+
+ /// If true, the database access has been cancelled.
+ var isCancelled: Bool
+ }
+
+ /// Support for `checkForSuspensionViolation(from:)`
+ let suspensionMutex = Mutex(Suspension(isSuspended: false, isCancelled: false))
+
// MARK: - Transaction Date
+ /// Support for `checkForAbortedTransaction()`
+ var isInsideTransactionBlock = false
+
enum AutocommitState {
case off
case on
@@ -352,10 +396,10 @@ public final class Database: CustomStringConvertible, CustomDebugStringConvertib
private var trace: ((TraceEvent) -> Void)?
/// The registered custom SQL functions.
- private var functions = Set()
+ private var functions: [DatabaseFunction.ID: DatabaseFunction] = [:]
/// The registered custom SQL collations.
- private var collations = Set()
+ private var collations: [DatabaseCollation.ID: DatabaseCollation] = [:]
/// Support for `beginReadOnly()` and `endReadOnly()`.
private var readOnlyDepth = 0
@@ -602,7 +646,7 @@ public final class Database: CustomStringConvertible, CustomDebugStringConvertib
guard code == SQLITE_OK else {
// So there remain some unfinalized prepared statement somewhere.
if let log = Self.logError {
- if code == SQLITE_BUSY {
+ if ResultCode(rawValue: code).primaryResultCode == .SQLITE_BUSY {
// Let the user know about unfinalized statements that did
// prevent the connection from closing properly.
var stmt: SQLiteStatement? = sqlite3_next_stmt(sqliteConnection, nil)
@@ -698,13 +742,13 @@ public final class Database: CustomStringConvertible, CustomDebugStringConvertib
/// let dbPool = try DatabasePool(path: ..., configuration: config)
/// ```
public func add(function: DatabaseFunction) {
- functions.update(with: function)
+ functions[function.id] = function
function.install(in: self)
}
/// Removes a custom SQL function.
public func remove(function: DatabaseFunction) {
- functions.remove(function)
+ functions.removeValue(forKey: function.id)
function.uninstall(in: self)
}
@@ -725,7 +769,7 @@ public final class Database: CustomStringConvertible, CustomDebugStringConvertib
/// let dbPool = try DatabasePool(path: ..., configuration: config)
/// ```
public func add(collation: DatabaseCollation) {
- collations.update(with: collation)
+ collations[collation.id] = collation
let collationPointer = Unmanaged.passUnretained(collation).toOpaque()
let code = sqlite3_create_collation_v2(
sqliteConnection,
@@ -744,7 +788,7 @@ public final class Database: CustomStringConvertible, CustomDebugStringConvertib
/// Removes a collation.
public func remove(collation: DatabaseCollation) {
- collations.remove(collation)
+ collations.removeValue(forKey: collation.id)
sqlite3_create_collation_v2(
sqliteConnection,
collation.name,
@@ -1116,22 +1160,25 @@ public final class Database: CustomStringConvertible, CustomDebugStringConvertib
///
/// Suspension ends with `resume()`.
func suspend() {
- $isSuspended.update { isSuspended in
- if isSuspended {
- return
+ let needsInterrupt = suspensionMutex.withLock { suspension in
+ if suspension.isSuspended {
+ return false
}
- // Prevent future lock acquisition
- isSuspended = true
-
- // Interrupt the database because this may trigger an
- // SQLITE_INTERRUPT error which may itself abort a transaction and
- // release a lock. See
+ suspension.isSuspended = true
+ return true
+ }
+
+ if needsInterrupt {
+ // Interrupting the database can trigger an SQLITE_INTERRUPT
+ // error which may itself abort a transaction and
+ // release a database lock, which is our goal.
+ // See
+ //
+ // Maybe interrupt will not release any lock. To address this,
+ // we'll issue a rollback on next database access which requires
+ // a lock. See `checkForSuspensionViolation(from:).`
interrupt()
-
- // Now what about the eventual remaining lock? We'll issue a
- // rollback on next database access which requires a lock, in
- // checkForSuspensionViolation(from:).
}
}
@@ -1143,7 +1190,35 @@ public final class Database: CustomStringConvertible, CustomDebugStringConvertib
///
/// See suspend().
func resume() {
- isSuspended = false
+ suspensionMutex.withLock {
+ $0.isSuspended = false
+ }
+ }
+
+ /// Cancels the current database access. All statements but ROLLBACK
+ /// will throw `CancellationError`, until `uncancel()` is called.
+ ///
+ /// This method can be called from any thread.
+ func cancel() {
+ let needsInterrupt = suspensionMutex.withLock { suspension in
+ if suspension.isCancelled {
+ return false
+ }
+
+ suspension.isCancelled = true
+ return true
+ }
+
+ if needsInterrupt {
+ interrupt()
+ }
+ }
+
+ /// Undo `cancel()`.
+ func uncancel() {
+ suspensionMutex.withLock {
+ $0.isCancelled = false
+ }
}
/// Support for `checkForSuspensionViolation(from:)`
@@ -1167,15 +1242,51 @@ public final class Database: CustomStringConvertible, CustomDebugStringConvertib
return journalMode
}
- /// If the database is suspended, and executing the statement would lock the
- /// database in a way that may trigger the [`0xdead10cc` exception](https://developer.apple.com/documentation/xcode/understanding-the-exception-types-in-a-crash-report),
- /// this method rollbacks the current transaction and throws `SQLITE_ABORT`.
+ /// Prevents a statement from running, if the database is suspended, or
+ /// if the current database access is cancelled by Task cancellation.
+ ///
+ /// Transaction rollbacks are always allowed. For other statements:
+ ///
+ /// - When database access is cancelled, this method
+ /// throws `CancellationError`.
///
- /// See `suspend()` and ``Configuration/observesSuspensionNotifications``.
+ /// - When database is suspensed, and if the statement would lock the
+ /// database in a way that may trigger the 0xDEAD10CC exception, this
+ /// method rollbacks the current transaction and throws `SQLITE_ABORT`.
+ ///
+ /// See `cancel()`, `suspend()` and
+ /// ``Configuration/observesSuspensionNotifications``.
func checkForSuspensionViolation(from statement: Statement) throws {
- try $isSuspended.read { isSuspended in
- guard isSuspended else {
- return
+ // No reason for suspension should prevent rollbacks:
+ //
+ // - A rollback releases the write lock when the database
+ // is interrupted, when preventing 0xDEAD10CC.
+ //
+ // - A rollback properly closes a transaction that fails because
+ // it runs in a Task that was cancelled.
+ //
+ // Finally, a rollback must be run by GRDB, not by a direct call
+ // to `sqlite3_exec`, so that transaction observers are
+ // properly notified.
+ if statement.transactionEffect == .rollbackTransaction {
+ return
+ }
+
+ // How should we interrupt the statement?
+ enum Interrupt {
+ case abort // Rollback and throw SQLITE_ABORT
+ case cancel // Throw CancellationError
+ }
+
+ let interrupt: Interrupt? = try suspensionMutex.withLock { suspension in
+ // Check for cancellation first, so that the only error that
+ // a user sees when a Task is cancelled is CancellationError.
+ if suspension.isCancelled {
+ return .cancel
+ }
+
+ guard suspension.isSuspended else {
+ return nil
}
if try journalMode() == "wal" && statement.isReadonly {
@@ -1186,7 +1297,7 @@ public final class Database: CustomStringConvertible, CustomDebugStringConvertib
// Those are not read-only:
// - INSERT ...
// - BEGIN IMMEDIATE TRANSACTION
- return
+ return nil
}
if statement.releasesDatabaseLock {
@@ -1195,15 +1306,24 @@ public final class Database: CustomStringConvertible, CustomDebugStringConvertib
// - ROLLBACK
// - ROLLBACK TRANSACTION TO SAVEPOINT
// - RELEASE SAVEPOINT
- return
+ return nil
}
+ // Assume statement can acquire a write lock: abort.
+ return .abort
+ }
+
+ switch interrupt {
+ case nil:
+ break
+
+ case .cancel:
+ throw CancellationError()
+
+ case .abort:
// Attempt at releasing an eventual lock with ROLLBACk,
// as explained in Database.suspend().
- //
- // Use sqlite3_exec instead of `try? rollback()` in order to avoid
- // an infinite loop in checkForSuspensionViolation(from:)
- _ = sqlite3_exec(sqliteConnection, "ROLLBACK", nil, nil, nil)
+ try? rollback()
throw DatabaseError(
resultCode: .SQLITE_ABORT,
@@ -1273,14 +1393,10 @@ public final class Database: CustomStringConvertible, CustomDebugStringConvertib
/// Use ``inSavepoint(_:)`` instead.
///
/// - parameters:
- /// - kind: The transaction type (default nil).
- ///
- /// If nil, and the database connection is read-only, the transaction
- /// kind is ``TransactionKind/deferred``.
+ /// - kind: The transaction type.
///
- /// If nil, and the database connection is not read-only, the
- /// transaction kind is the ``Configuration/defaultTransactionKind``
- /// of the ``configuration``.
+ /// If nil, the transaction kind is DEFERRED when the current
+ /// database access is read-only, and IMMEDIATE otherwise.
/// - operations: A function that executes SQL statements and returns
/// either ``TransactionCompletion/commit`` or ``TransactionCompletion/rollback``.
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs, or the
@@ -1404,8 +1520,7 @@ public final class Database: CustomStringConvertible, CustomDebugStringConvertib
// By default, top level SQLite savepoints open a
// deferred transaction.
//
- // But GRDB database configuration mandates a default transaction
- // kind that we have to honor.
+ // But GRDB prefers immediate transactions for writes.
//
// Besides, starting some (?) SQLCipher/SQLite version, SQLite has a
// bug. Returning 1 from `sqlite3_commit_hook` does not leave the
@@ -1493,18 +1608,22 @@ public final class Database: CustomStringConvertible, CustomDebugStringConvertib
/// Related SQLite documentation:
///
/// - parameters:
- /// - kind: The transaction type (default nil).
+ /// - kind: The transaction type.
///
- /// If nil, and the database connection is read-only, the transaction
- /// kind is ``TransactionKind/deferred``.
- ///
- /// If nil, and the database connection is not read-only, the
- /// transaction kind is the ``Configuration/defaultTransactionKind``
- /// of the ``configuration``.
+ /// If nil, the transaction kind is DEFERRED when the current
+ /// database access is read-only, and IMMEDIATE otherwise.
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs.
public func beginTransaction(_ kind: TransactionKind? = nil) throws {
// SQLite throws an error for non-deferred transactions when read-only.
- let kind = kind ?? (isReadOnly ? .deferred : configuration.defaultTransactionKind)
+ // We prefer immediate transactions for writes, so that write
+ // transactions can not overlap. This reduces the opportunity for
+ // SQLITE_BUSY, which is immediately thrown whenever a transaction
+ // is upgraded after an initial read and a concurrent processes
+ // has acquired the write lock beforehand. This SQLITE_BUSY error
+ // can not be avoided with a busy timeout.
+ //
+ // See .
+ let kind = kind ?? (isReadOnly ? .deferred : .immediate)
try execute(sql: "BEGIN \(kind.rawValue) TRANSACTION")
assert(sqlite3_get_autocommit(sqliteConnection) == 0)
}
@@ -1816,8 +1935,8 @@ extension Database {
// MARK: - Database-Related Types
- /// See BusyMode and
- public typealias BusyCallback = (_ numberOfTries: Int) -> Bool
+ /// See ``BusyMode`` and
+ public typealias BusyCallback = @Sendable (_ numberOfTries: Int) -> Bool
/// When there are several connections to a database, a connection may try
/// to access the database while it is locked by another connection.
@@ -1845,7 +1964,7 @@ extension Database {
/// -
/// -
/// -
- public enum BusyMode {
+ public enum BusyMode: Sendable {
/// The `SQLITE_BUSY` error is immediately returned to the connection
/// that tries to access the locked database.
case immediateError
@@ -2005,7 +2124,7 @@ extension Database {
}
/// An error log function that takes an error code and message.
- public typealias LogErrorFunction = (_ resultCode: ResultCode, _ message: String) -> Void
+ public typealias LogErrorFunction = @Sendable (_ resultCode: ResultCode, _ message: String) -> Void
/// An SQLite storage class.
///
diff --git a/GRDB/Core/DatabaseCollation.swift b/GRDB/Core/DatabaseCollation.swift
index 2f76223c56..6748874c8b 100644
--- a/GRDB/Core/DatabaseCollation.swift
+++ b/GRDB/Core/DatabaseCollation.swift
@@ -1,3 +1,12 @@
+// Import C SQLite functions
+#if SWIFT_PACKAGE
+import GRDBSQLite
+#elseif GRDBCIPHER
+import SQLCipher
+#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER
+import SQLite3
+#endif
+
import Foundation
/// `DatabaseCollation` is a custom string comparison function used by SQLite.
@@ -20,10 +29,36 @@ import Foundation
/// - ``localizedCompare``
/// - ``localizedStandardCompare``
/// - ``unicodeCompare``
-public final class DatabaseCollation {
+public final class DatabaseCollation: Identifiable, Sendable {
+ /// The identifier of an SQLite collation.
+ ///
+ /// SQLite identifies collations by their name (case insensitive).
+ public struct ID: Hashable {
+ var name: String
+
+ // Collation equality is based on the sqlite3_strnicmp SQLite function.
+ // (see https://www.sqlite.org/c3ref/create_collation.html). Computing
+ // a hash value that honors the Swift Hashable contract (value equality
+ // implies hash equality) is thus non trivial. But it's not that
+ // important, since this hashValue is only used when one adds
+ // or removes a collation from a database connection.
+ public func hash(into hasher: inout Hasher) {
+ hasher.combine(0)
+ }
+
+ /// Two collations are equal if they share the same name (case insensitive)
+ public static func == (lhs: Self, rhs: Self) -> Bool {
+ // See
+ return sqlite3_stricmp(lhs.name, rhs.name) == 0
+ }
+ }
+
+ /// The identifier of the collation.
+ public var id: ID { ID(name: name) }
+
/// The name of the collation.
public let name: String
- let function: (CInt, UnsafeRawPointer?, CInt, UnsafeRawPointer?) -> ComparisonResult
+ let function: @Sendable (CInt, UnsafeRawPointer?, CInt, UnsafeRawPointer?) -> ComparisonResult
/// Creates a collation.
///
@@ -40,7 +75,7 @@ public final class DatabaseCollation {
/// - parameters:
/// - name: The collation name.
/// - function: A function that compares two strings.
- public init(_ name: String, function: @escaping (String, String) -> ComparisonResult) {
+ public init(_ name: String, function: @escaping @Sendable (String, String) -> ComparisonResult) {
self.name = name
self.function = { (length1, buffer1, length2, buffer2) in
// Buffers are not C strings: they do not end with \0.
@@ -58,21 +93,3 @@ public final class DatabaseCollation {
}
}
}
-
-extension DatabaseCollation: Hashable {
- // Collation equality is based on the sqlite3_strnicmp SQLite function.
- // (see https://www.sqlite.org/c3ref/create_collation.html). Computing
- // a hash value that honors the Swift Hashable contract (value equality
- // implies hash equality) is thus non trivial. But it's not that
- // important, since this hashValue is only used when one adds
- // or removes a collation from a database connection.
- public func hash(into hasher: inout Hasher) {
- hasher.combine(0)
- }
-
- /// Two collations are equal if they share the same name (case insensitive)
- public static func == (lhs: DatabaseCollation, rhs: DatabaseCollation) -> Bool {
- // See
- return sqlite3_stricmp(lhs.name, rhs.name) == 0
- }
-}
diff --git a/GRDB/Core/DatabaseError.swift b/GRDB/Core/DatabaseError.swift
index 53eb009b37..bf808dbaa5 100644
--- a/GRDB/Core/DatabaseError.swift
+++ b/GRDB/Core/DatabaseError.swift
@@ -1,3 +1,12 @@
+// Import C SQLite functions
+#if SWIFT_PACKAGE
+import GRDBSQLite
+#elseif GRDBCIPHER
+import SQLCipher
+#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER
+import SQLite3
+#endif
+
import Foundation
/// An SQLite result code.
@@ -406,6 +415,10 @@ extension DatabaseError {
static func connectionIsClosed() -> Self {
DatabaseError(resultCode: .SQLITE_MISUSE, message: "Connection is closed")
}
+
+ static func snapshotIsLost() -> Self {
+ DatabaseError(resultCode: .SQLITE_ABORT, message: "Snapshot is lost.")
+ }
}
// Support for `catch DatabaseError.SQLITE_XXX`
diff --git a/GRDB/Core/DatabaseFunction.swift b/GRDB/Core/DatabaseFunction.swift
index 81fe825748..9e2c03ed2f 100644
--- a/GRDB/Core/DatabaseFunction.swift
+++ b/GRDB/Core/DatabaseFunction.swift
@@ -1,3 +1,12 @@
+// Import C SQLite functions
+#if SWIFT_PACKAGE
+import GRDBSQLite
+#elseif GRDBCIPHER
+import SQLCipher
+#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER
+import SQLite3
+#endif
+
/// A custom SQL function or aggregate.
///
/// ## Topics
@@ -20,16 +29,20 @@
/// - ``localizedUppercase``
/// - ``lowercase``
/// - ``uppercase``
-public final class DatabaseFunction: Hashable {
- // SQLite identifies functions by (name + argument count)
- private struct Identity: Hashable {
+public final class DatabaseFunction: Identifiable, Sendable {
+ /// The identifier of an SQLite function.
+ ///
+ /// SQLite identifies functions by their name and argument count.
+ public struct ID: Hashable, Sendable {
let name: String
let nArg: CInt // -1 for variadic functions
}
- /// The name of the SQL function
- public var name: String { identity.name }
- private let identity: Identity
+ /// The name of the SQL function.
+ public var name: String { id.name }
+
+ /// The identifier of the SQL function.
+ public let id: ID
let isPure: Bool
private let kind: Kind
private var eTextRep: CInt { (SQLITE_UTF8 | (isPure ? SQLITE_DETERMINISTIC : 0)) }
@@ -73,11 +86,11 @@ public final class DatabaseFunction: Hashable {
_ name: String,
argumentCount: Int? = nil,
pure: Bool = false,
- function: @escaping ([DatabaseValue]) throws -> (any DatabaseValueConvertible)?)
+ function: @escaping @Sendable ([DatabaseValue]) throws -> (any DatabaseValueConvertible)?)
{
- self.identity = Identity(name: name, nArg: argumentCount.map(CInt.init) ?? -1)
+ self.id = ID(name: name, nArg: argumentCount.map(CInt.init) ?? -1)
self.isPure = pure
- self.kind = .function{ (argc, argv) in
+ self.kind = .function { (argc, argv) in
let arguments = (0..
- private enum Kind {
+ private enum Kind: Sendable {
/// A regular function: SELECT f(1)
- case function((CInt, UnsafeMutablePointer?) throws -> (any DatabaseValueConvertible)?)
+ case function(@Sendable (CInt, UnsafeMutablePointer?) throws -> (any DatabaseValueConvertible)?)
/// An aggregate: SELECT f(foo) FROM bar GROUP BY baz
- case aggregate(() -> any DatabaseAggregate)
+ case aggregate(@Sendable () -> any DatabaseAggregate)
/// Feeds the `pApp` parameter of sqlite3_create_function_v2
///
@@ -415,17 +428,6 @@ public final class DatabaseFunction: Hashable {
}
}
-extension DatabaseFunction {
- public func hash(into hasher: inout Hasher) {
- hasher.combine(identity)
- }
-
- /// Two functions are equal if they share the same name and arity.
- public static func == (lhs: DatabaseFunction, rhs: DatabaseFunction) -> Bool {
- lhs.identity == rhs.identity
- }
-}
-
/// The protocol for custom SQLite aggregates.
///
/// For example:
diff --git a/GRDB/Core/DatabasePool.swift b/GRDB/Core/DatabasePool.swift
index 7dd0525fc2..ce1bae833e 100644
--- a/GRDB/Core/DatabasePool.swift
+++ b/GRDB/Core/DatabasePool.swift
@@ -11,7 +11,7 @@ public final class DatabasePool {
/// It is constant, until close() sets it to nil.
private var readerPool: Pool?
- @LockedBox var databaseSnapshotCount = 0
+ let databaseSnapshotCountMutex = Mutex(0)
/// If Database Suspension is enabled, this array contains the necessary `NotificationCenter` observers.
private var suspensionObservers: [NSObjectProtocol] = []
@@ -62,17 +62,15 @@ public final class DatabasePool {
// an opened transaction.
readerConfiguration.allowsUnsafeTransactions = false
- var readerCount = 0
readerPool = Pool(
maximumCount: configuration.maximumReaderCount,
qos: configuration.readQoS,
- makeElement: {
- readerCount += 1 // protected by Pool (TODO: document this protection behavior)
+ makeElement: { [readerConfiguration] index in
return try SerializedDatabase(
path: path,
configuration: readerConfiguration,
defaultLabel: "GRDB.DatabasePool",
- purpose: "reader.\(readerCount)")
+ purpose: "reader.\(index)")
})
// Set up journal mode unless readonly
@@ -118,10 +116,6 @@ public final class DatabasePool {
configuration.readonly = true
- // Readers use deferred transactions by default.
- // Other transaction kinds are forbidden by SQLite in read-only connections.
- configuration.defaultTransactionKind = .deferred
-
//
// > But there are some obscure cases where a query against a WAL-mode
// > database can return SQLITE_BUSY, so applications should be prepared
@@ -148,7 +142,7 @@ public final class DatabasePool {
}
}
-// @unchecked because of databaseSnapshotCount, readerPool and suspensionObservers
+// @unchecked because of readerPool and suspensionObservers
extension DatabasePool: @unchecked Sendable { }
extension DatabasePool {
@@ -357,7 +351,47 @@ extension DatabasePool: DatabaseReader {
}
}
- public func asyncRead(_ value: @escaping (Result) -> Void) {
+ public func read(
+ _ value: @escaping @Sendable (Database) throws -> T
+ ) async throws -> T {
+ GRDBPrecondition(currentReader == nil, "Database methods are not reentrant.")
+ guard let readerPool else {
+ throw DatabaseError.connectionIsClosed()
+ }
+
+ let dbAccess = CancellableDatabaseAccess()
+ return try await dbAccess.withCancellableContinuation { continuation in
+ readerPool.asyncGet { result in
+ do {
+ let (reader, releaseReader) = try result.get()
+ // Second async jump because that's how `Pool.async` has to be used.
+ reader.async { db in
+ defer {
+ try? db.commit() // Ignore commit error
+ releaseReader(.reuse)
+ }
+ do {
+ let result = try dbAccess.inDatabase(db) {
+ // The block isolation comes from the DEFERRED transaction.
+ try db.beginTransaction(.deferred)
+ try db.clearSchemaCacheIfNeeded()
+ return try value(db)
+ }
+ continuation.resume(returning: result)
+ } catch {
+ continuation.resume(throwing: error)
+ }
+ }
+ } catch {
+ continuation.resume(throwing: error)
+ }
+ }
+ }
+ }
+
+ public func asyncRead(
+ _ value: @escaping @Sendable (Result) -> Void
+ ) {
guard let readerPool else {
value(.failure(DatabaseError.connectionIsClosed()))
return
@@ -401,7 +435,43 @@ extension DatabasePool: DatabaseReader {
}
}
- public func asyncUnsafeRead(_ value: @escaping (Result) -> Void) {
+ public func unsafeRead(
+ _ value: @escaping @Sendable (Database) throws -> T
+ ) async throws -> T {
+ guard let readerPool else {
+ throw DatabaseError.connectionIsClosed()
+ }
+
+ let dbAccess = CancellableDatabaseAccess()
+ return try await dbAccess.withCancellableContinuation { continuation in
+ readerPool.asyncGet { result in
+ do {
+ let (reader, releaseReader) = try result.get()
+ // Second async jump because that's how `Pool.async` has to be used.
+ reader.async { db in
+ defer {
+ releaseReader(.reuse)
+ }
+ do {
+ let result = try dbAccess.inDatabase(db) {
+ try db.clearSchemaCacheIfNeeded()
+ return try value(db)
+ }
+ continuation.resume(returning: result)
+ } catch {
+ continuation.resume(throwing: error)
+ }
+ }
+ } catch {
+ continuation.resume(throwing: error)
+ }
+ }
+ }
+ }
+
+ public func asyncUnsafeRead(
+ _ value: @escaping @Sendable (Result) -> Void
+ ) {
guard let readerPool else {
value(.failure(DatabaseError.connectionIsClosed()))
return
@@ -446,25 +516,9 @@ extension DatabasePool: DatabaseReader {
}
}
- public func concurrentRead(_ value: @escaping (Database) throws -> T) -> DatabaseFuture {
- // The semaphore that blocks until futureResult is defined:
- let futureSemaphore = DispatchSemaphore(value: 0)
- var futureResult: Result? = nil
-
- asyncConcurrentRead { dbResult in
- // Fetch and release the future
- futureResult = dbResult.flatMap { db in Result { try value(db) } }
- futureSemaphore.signal()
- }
-
- return DatabaseFuture {
- // Block the future until results are fetched
- _ = futureSemaphore.wait(timeout: .distantFuture)
- return try futureResult!.get()
- }
- }
-
- public func spawnConcurrentRead(_ value: @escaping (Result) -> Void) {
+ public func spawnConcurrentRead(
+ _ value: @escaping @Sendable (Result) -> Void
+ ) {
asyncConcurrentRead(value)
}
@@ -505,7 +559,9 @@ extension DatabasePool: DatabaseReader {
/// ```
///
/// - parameter value: A function that accesses the database.
- public func asyncConcurrentRead(_ value: @escaping (Result) -> Void) {
+ public func asyncConcurrentRead(
+ _ value: @escaping @Sendable (Result) -> Void
+ ) {
// Check that we're on the writer queue...
writer.execute { db in
// ... and that no transaction is opened.
@@ -644,8 +700,7 @@ extension DatabasePool: DatabaseReader {
// MARK: - WAL Snapshot Transactions
- // swiftlint:disable:next line_length
-#if SQLITE_ENABLE_SNAPSHOT || (!GRDBCUSTOMSQLITE && !GRDBCIPHER && (compiler(>=5.7.1) || !(os(macOS) || targetEnvironment(macCatalyst))))
+#if SQLITE_ENABLE_SNAPSHOT || (!GRDBCUSTOMSQLITE && !GRDBCIPHER)
/// Returns a long-lived WAL snapshot transaction on a reader connection.
func walSnapshotTransaction() throws -> WALSnapshotTransaction {
guard let readerPool else {
@@ -665,7 +720,9 @@ extension DatabasePool: DatabaseReader {
///
/// - important: The `completion` argument is executed in a serial
/// dispatch queue, so make sure you use the transaction asynchronously.
- func asyncWALSnapshotTransaction(_ completion: @escaping (Result) -> Void) {
+ func asyncWALSnapshotTransaction(
+ _ completion: @escaping @Sendable (Result) -> Void
+ ) {
guard let readerPool else {
completion(.failure(DatabaseError.connectionIsClosed()))
return
@@ -691,9 +748,8 @@ extension DatabasePool: DatabaseReader {
public func _add(
observation: ValueObservation,
scheduling scheduler: some ValueObservationScheduler,
- onChange: @escaping (Reducer.Value) -> Void)
- -> AnyDatabaseCancellable
- {
+ onChange: @escaping @Sendable (Reducer.Value) -> Void
+ ) -> AnyDatabaseCancellable {
if configuration.readonly {
// The easy case: the database does not change
return _addReadOnly(
@@ -722,9 +778,8 @@ extension DatabasePool: DatabaseReader {
private func _addConcurrent(
observation: ValueObservation,
scheduling scheduler: some ValueObservationScheduler,
- onChange: @escaping (Reducer.Value) -> Void)
- -> AnyDatabaseCancellable
- {
+ onChange: @escaping @Sendable (Reducer.Value) -> Void
+ ) -> AnyDatabaseCancellable {
assert(!configuration.readonly, "Use _addReadOnly(observation:) instead")
assert(!observation.requiresWriteAccess, "Use _addWriteOnly(observation:) instead")
let observer = ValueConcurrentObserver(
@@ -746,6 +801,12 @@ extension DatabasePool: DatabaseWriter {
try writer.sync(updates)
}
+ public func writeWithoutTransaction(
+ _ updates: @escaping @Sendable (Database) throws -> T
+ ) async throws -> T {
+ try await writer.execute(updates)
+ }
+
@_disfavoredOverload // SR-15150 Async overloading in protocol implementation fails
public func barrierWriteWithoutTransaction(_ updates: (Database) throws -> T) throws -> T {
guard let readerPool else {
@@ -756,7 +817,29 @@ extension DatabasePool: DatabaseWriter {
}
}
- public func asyncBarrierWriteWithoutTransaction(_ updates: @escaping (Result) -> Void) {
+ public func barrierWriteWithoutTransaction(
+ _ updates: @escaping @Sendable (Database) throws -> T
+ ) async throws -> T {
+ let dbAccess = CancellableDatabaseAccess()
+ return try await dbAccess.withCancellableContinuation { continuation in
+ asyncBarrierWriteWithoutTransaction { dbResult in
+ do {
+ try dbAccess.checkCancellation()
+ let db = try dbResult.get()
+ let result = try dbAccess.inDatabase(db) {
+ try updates(db)
+ }
+ continuation.resume(returning: result)
+ } catch {
+ continuation.resume(throwing: error)
+ }
+ }
+ }
+ }
+
+ public func asyncBarrierWriteWithoutTransaction(
+ _ updates: @escaping @Sendable (Result) -> Void
+ ) {
guard let readerPool else {
updates(.failure(DatabaseError.connectionIsClosed()))
return
@@ -788,9 +871,10 @@ extension DatabasePool: DatabaseWriter {
///
/// - precondition: This method is not reentrant.
/// - parameters:
- /// - kind: The transaction type (default nil). If nil, the transaction
- /// type is the ``Configuration/defaultTransactionKind`` of the
- /// the ``configuration``.
+ /// - kind: The transaction type.
+ ///
+ /// If nil, the transaction kind is DEFERRED when the database
+ /// connection is read-only, and IMMEDIATE otherwise.
/// - updates: A function that updates the database.
/// - throws: The error thrown by `updates`, or by the wrapping transaction.
public func writeInTransaction(
@@ -809,7 +893,9 @@ extension DatabasePool: DatabaseWriter {
try writer.reentrantSync(updates)
}
- public func asyncWriteWithoutTransaction(_ updates: @escaping (Database) -> Void) {
+ public func asyncWriteWithoutTransaction(
+ _ updates: @escaping @Sendable (Database) -> Void
+ ) {
writer.async(updates)
}
}
@@ -867,11 +953,10 @@ extension DatabasePool {
path: path,
configuration: DatabasePool.readerConfiguration(writer.configuration),
defaultLabel: "GRDB.DatabasePool",
- purpose: "snapshot.\($databaseSnapshotCount.increment())")
+ purpose: "snapshot.\(databaseSnapshotCountMutex.increment())")
}
- // swiftlint:disable:next line_length
-#if SQLITE_ENABLE_SNAPSHOT || (!GRDBCUSTOMSQLITE && !GRDBCIPHER && (compiler(>=5.7.1) || !(os(macOS) || targetEnvironment(macCatalyst))))
+#if SQLITE_ENABLE_SNAPSHOT || (!GRDBCUSTOMSQLITE && !GRDBCIPHER)
/// Creates a database snapshot that allows concurrent accesses to an
/// unchanging database content, as it exists at the moment the snapshot
/// is created.
diff --git a/GRDB/Core/DatabasePublishers.swift b/GRDB/Core/DatabasePublishers.swift
index 8f8054953b..c7e7a603f1 100644
--- a/GRDB/Core/DatabasePublishers.swift
+++ b/GRDB/Core/DatabasePublishers.swift
@@ -1,5 +1,4 @@
#if canImport(Combine)
/// A namespace for database Combine publishers.
-@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *)
public enum DatabasePublishers { }
#endif
diff --git a/GRDB/Core/DatabaseQueue.swift b/GRDB/Core/DatabaseQueue.swift
index ae763f694c..37b62fde5b 100644
--- a/GRDB/Core/DatabaseQueue.swift
+++ b/GRDB/Core/DatabaseQueue.swift
@@ -233,7 +233,19 @@ extension DatabaseQueue: DatabaseReader {
}
}
- public func asyncRead(_ value: @escaping (Result) -> Void) {
+ public func read(
+ _ value: @escaping @Sendable (Database) throws -> T
+ ) async throws -> T {
+ try await writer.execute { db in
+ try db.isolated(readOnly: true) {
+ try value(db)
+ }
+ }
+ }
+
+ public func asyncRead(
+ _ value: @escaping @Sendable (Result) -> Void
+ ) {
writer.async { db in
defer {
// Ignore error because we can not notify it.
@@ -254,11 +266,20 @@ extension DatabaseQueue: DatabaseReader {
}
}
+ @_disfavoredOverload // SR-15150 Async overloading in protocol implementation fails
public func unsafeRead(_ value: (Database) throws -> T) rethrows -> T {
try writer.sync(value)
}
- public func asyncUnsafeRead(_ value: @escaping (Result) -> Void) {
+ public func unsafeRead(
+ _ value: @escaping @Sendable (Database) throws -> T
+ ) async throws -> T {
+ try await writer.execute(value)
+ }
+
+ public func asyncUnsafeRead(
+ _ value: @escaping @Sendable (Result) -> Void
+ ) {
writer.async { value(.success($0)) }
}
@@ -266,20 +287,9 @@ extension DatabaseQueue: DatabaseReader {
try writer.reentrantSync(value)
}
- public func concurrentRead