Skip to content

Commit cceb24a

Browse files
committed
Merge branch 'development'
2 parents 3733648 + bea454a commit cceb24a

File tree

78 files changed

+3506
-3549
lines changed

Some content is hidden

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

78 files changed

+3506
-3549
lines changed

BRAG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ Here is a list of people who were happy to meet GRDB :-)
44

55
---
66

7+
https://twitter.com/swiftkarim/status/1354753451377483781
8+
9+
> I cannot recommend GRDB enough. It works well with Swift and eliminates whole classes of potential programming errors that can be made with Core Data. Also being able to use raw SQL queries if needed can be extremely useful!
10+
11+
---
12+
713
https://twitter.com/peres/status/1352219836508729346
814

915
> Just the occasional reminder that GRDB and its documentation is the gold standard. That's it.

CHANGELOG.md

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

88
#### 5.x Releases
99

10+
- `5.4.x` Releases - [5.4.0](#540)
1011
- `5.3.x` Releases - [5.3.0](#530)
1112
- `5.2.x` Releases - [5.2.0](#520)
1213
- `5.1.x` Releases - [5.1.0](#510)
@@ -68,6 +69,38 @@ GRDB adheres to [Semantic Versioning](https://semver.org/), with one exception:
6869

6970
- [0.110.0](#01100), ...
7071

72+
## 5.4.0
73+
74+
Released February 15, 2021 • [diff](https://github.com/groue/GRDB.swift/compare/v5.3.0...v5.4.0)
75+
76+
- **Breaking Change**: The query interface was refactored and some types such as `SQLExpression` are no longer a protocol.
77+
78+
This may break some applications that rely on Swift type inference, such as in the following example:
79+
80+
```swift
81+
// No longer compiles
82+
let values = [Column("score"), Column("score") + Column("bonus")]
83+
```
84+
85+
The fix is to add an explicit declaration of the desired type:
86+
87+
```swift
88+
// A possible fix
89+
let values: [SQLExpressible] = [Column("score"), Column("score") + Column("bonus")]
90+
```
91+
92+
Occurrences of such code breakage should be very rare.
93+
94+
- **New**: [SQL Interpolation](Documentation/SQLInterpolation.md) supports embedding collations into SQL literals:
95+
96+
```swift
97+
let request: SQLRequest<Player> = "SELECT * FROM player ORDER BY email COLLATION \(.nocase)"
98+
let request: SQLRequest<Player> = "SELECT * FROM player ORDER BY name COLLATION \(.localizedCompare)"
99+
```
100+
101+
- **Documentation update**: [Adding support for missing SQL functions or operators](README.md#adding-support-for-missing-sql-functions-or-operators) explains how to extend the query interface when needed.
102+
103+
- **Documentation update**: The [Demo Applications](Documentation/DemoApps/) now provide tests for the database access layer.
71104

72105
## 5.3.0
73106

Documentation/AssociationsBasics.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -941,7 +941,21 @@ The pattern is always the same: you start from a base request, that you extend w
941941

942942
Another way to describe the difference is that `required` filters the fetched results in order to discard missing associated records, when `optional` does not filter anything, and lets missing values pass through.
943943

944-
Finally, readers who speak SQL may compare `optional` with left joins, and `required` with inner joins.
944+
Finally, readers who speak SQL may compare `optional` with left joins, and `required` with inner joins:
945+
946+
```swift
947+
// SELECT book.* FROM book LEFT JOIN author ON author.id = book.authorID
948+
Book.joining(optional: Book.author)
949+
950+
// SELECT book.* FROM book JOIN author ON author.id = book.authorID
951+
Book.joining(required: Book.author)
952+
953+
// SELECT book.*, author.* FROM book LEFT JOIN author ON author.id = book.authorID
954+
Book.including(optional: Book.author)
955+
956+
// SELECT book.*, author.* FROM book JOIN author ON author.id = book.authorID
957+
Book.including(required: Book.author)
958+
```
945959

946960

947961
## Combining Associations

Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/AppDatabase.swift

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,6 @@ struct AppDatabase {
3838
try db.create(table: "player") { t in
3939
t.autoIncrementedPrimaryKey("id")
4040
t.column("name", .text).notNull()
41-
// Sort player names in a localized case insensitive fashion by default
42-
// See https://github.com/groue/GRDB.swift/blob/master/README.md#unicode
43-
.collate(.localizedCaseInsensitiveCompare)
4441
t.column("score", .integer).notNull()
4542
}
4643
}
@@ -58,7 +55,7 @@ struct AppDatabase {
5855

5956
extension AppDatabase {
6057
/// Saves (inserts or updates) a player. When the method returns, the
61-
/// player id is not nil.
58+
/// player is present in the database, and its id is not nil.
6259
func savePlayer(_ player: inout Player) throws {
6360
try dbWriter.write { db in
6461
try player.save(db)
@@ -83,7 +80,7 @@ extension AppDatabase {
8380
func refreshPlayers() throws {
8481
try dbWriter.write { db in
8582
if try Player.fetchCount(db) == 0 {
86-
// Insert new random players
83+
// When database is empty, insert new random players
8784
try createRandomPlayers(db)
8885
} else {
8986
// Insert a player

Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Persistence.swift

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,21 @@ extension AppDatabase {
66

77
private static func makeShared() -> AppDatabase {
88
do {
9+
// Create a folder for storing the SQLite database, as well as
10+
// the various temporary files created during normal database
11+
// operations (https://sqlite.org/tempfiles.html).
12+
let fileManager = FileManager()
13+
let folderURL = try fileManager
14+
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
15+
.appendingPathComponent("database", isDirectory: true)
16+
try fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true)
17+
918
// Connect to a database on disk
1019
// See https://github.com/groue/GRDB.swift/blob/master/README.md#database-connections
11-
let url: URL = try FileManager.default
12-
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
13-
.appendingPathComponent("db.sqlite")
14-
let dbPool = try DatabasePool(path: url.path)
20+
let dbURL = folderURL.appendingPathComponent("db.sqlite")
21+
let dbPool = try DatabasePool(path: dbURL.path)
22+
23+
// Create the AppDatabase
1524
let appDatabase = try AppDatabase(dbPool)
1625

1726
// Populate the database if it is empty, for better demo purpose.

Documentation/DemoApps/GRDBCombineDemo/GRDBCombineDemo/Player.swift

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,25 +71,35 @@ extension Player: Codable, FetchableRecord, MutablePersistableRecord {
7171
/// See https://github.com/groue/GRDB.swift/blob/master/README.md#requests
7272
/// See https://github.com/groue/GRDB.swift/blob/master/Documentation/GoodPracticesForDesigningRecordTypes.md
7373
extension DerivableRequest where RowDecoder == Player {
74-
/// A request of players ordered by name
74+
/// A request of players ordered by name.
7575
///
7676
/// For example:
7777
///
78-
/// let players = try dbQueue.read { db in
78+
/// let players: [Player] = try dbWriter.read { db in
7979
/// try Player.all().orderedByName().fetchAll(db)
8080
/// }
8181
func orderedByName() -> Self {
82-
order(Player.Columns.name)
82+
// Sort by name in a localized case insensitive fashion
83+
// See https://github.com/groue/GRDB.swift/blob/master/README.md#string-comparison
84+
order(Player.Columns.name.collating(.localizedCaseInsensitiveCompare))
8385
}
8486

85-
/// A request of players ordered by score
87+
/// A request of players ordered by score.
8688
///
8789
/// For example:
8890
///
89-
/// let players = try dbQueue.read { db in
91+
/// let players: [Player] = try dbWriter.read { db in
9092
/// try Player.all().orderedByScore().fetchAll(db)
9193
/// }
94+
/// let bestPlayer: Player? = try dbWriter.read { db in
95+
/// try Player.all().orderedByScore().fetchOne(db)
96+
/// }
9297
func orderedByScore() -> Self {
93-
order(Player.Columns.score.desc, Player.Columns.name)
98+
// Sort by descending score, and then by name, in a
99+
// localized case insensitive fashion
100+
// See https://github.com/groue/GRDB.swift/blob/master/README.md#string-comparison
101+
order(
102+
Player.Columns.score.desc,
103+
Player.Columns.name.collating(.localizedCaseInsensitiveCompare))
94104
}
95105
}

Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/AppDatabase.swift

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,6 @@ final class AppDatabase {
3737
try db.create(table: "player") { t in
3838
t.autoIncrementedPrimaryKey("id")
3939
t.column("name", .text).notNull()
40-
// Sort player names in a localized case insensitive fashion by default
41-
// See https://github.com/groue/GRDB.swift/blob/master/README.md#unicode
42-
.collate(.localizedCaseInsensitiveCompare)
4340
t.column("score", .integer).notNull()
4441
}
4542
}
@@ -57,7 +54,7 @@ final class AppDatabase {
5754

5855
extension AppDatabase {
5956
/// Saves (inserts or updates) a player. When the method returns, the
60-
/// player id is not nil.
57+
/// player is present in the database, and its id is not nil.
6158
func savePlayer(_ player: inout Player) throws {
6259
try dbWriter.write { db in
6360
try player.save(db)
@@ -82,7 +79,7 @@ extension AppDatabase {
8279
func refreshPlayers() throws {
8380
try dbWriter.write { db in
8481
if try Player.fetchCount(db) == 0 {
85-
// Insert new random players
82+
// When database is empty, insert new random players
8683
try createRandomPlayers(db)
8784
} else {
8885
// Insert a player
@@ -127,20 +124,6 @@ extension AppDatabase {
127124
// MARK: - Database Access: Reads
128125

129126
extension AppDatabase {
130-
/// Tracks changes in the number of players
131-
func observePlayerCount(
132-
onError: @escaping (Error) -> Void,
133-
onChange: @escaping (Int) -> Void)
134-
-> DatabaseCancellable
135-
{
136-
ValueObservation
137-
.tracking(Player.fetchCount)
138-
.start(
139-
in: dbWriter,
140-
onError: onError,
141-
onChange: onChange)
142-
}
143-
144127
/// Tracks changes in players ordered by name
145128
func observePlayersOrderedByName(
146129
onError: @escaping (Error) -> Void,

Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Persistence.swift

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,26 @@
11
import GRDB
22

33
extension AppDatabase {
4-
/// The shared `PlayerDatabase`
4+
/// The database for the application
55
static let shared = makeShared()
66

77
private static func makeShared() -> AppDatabase {
88
do {
9+
// Create a folder for storing the SQLite database, as well as
10+
// the various temporary files created during normal database
11+
// operations (https://sqlite.org/tempfiles.html).
12+
let fileManager = FileManager()
13+
let folderURL = try fileManager
14+
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
15+
.appendingPathComponent("database", isDirectory: true)
16+
try fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true)
17+
918
// Connect to a database on disk
1019
// See https://github.com/groue/GRDB.swift/blob/master/README.md#database-connections
11-
let url: URL = try FileManager.default
12-
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
13-
.appendingPathComponent("db.sqlite")
14-
let dbPool = try DatabasePool(path: url.path)
20+
let dbURL = folderURL.appendingPathComponent("db.sqlite")
21+
let dbPool = try DatabasePool(path: dbURL.path)
22+
23+
// Create the AppDatabase
1524
let appDatabase = try AppDatabase(dbPool)
1625

1726
// Populate the database if it is empty, for better demo purpose.

Documentation/DemoApps/GRDBDemoiOS/GRDBDemoiOS/Player.swift

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,25 +70,35 @@ extension Player: Codable, FetchableRecord, MutablePersistableRecord {
7070
/// See https://github.com/groue/GRDB.swift/blob/master/README.md#requests
7171
/// See https://github.com/groue/GRDB.swift/blob/master/Documentation/GoodPracticesForDesigningRecordTypes.md
7272
extension DerivableRequest where RowDecoder == Player {
73-
/// A request of players ordered by name
73+
/// A request of players ordered by name.
7474
///
7575
/// For example:
7676
///
77-
/// let players = try dbQueue.read { db in
77+
/// let players: [Player] = try dbWriter.read { db in
7878
/// try Player.all().orderedByName().fetchAll(db)
7979
/// }
8080
func orderedByName() -> Self {
81-
order(Player.Columns.name)
81+
// Sort by name in a localized case insensitive fashion
82+
// See https://github.com/groue/GRDB.swift/blob/master/README.md#string-comparison
83+
order(Player.Columns.name.collating(.localizedCaseInsensitiveCompare))
8284
}
8385

84-
/// A request of players ordered by score
86+
/// A request of players ordered by score.
8587
///
8688
/// For example:
8789
///
88-
/// let players = try dbQueue.read { db in
90+
/// let players: [Player] = try dbWriter.read { db in
8991
/// try Player.all().orderedByScore().fetchAll(db)
9092
/// }
93+
/// let bestPlayer: Player? = try dbWriter.read { db in
94+
/// try Player.all().orderedByScore().fetchOne(db)
95+
/// }
9196
func orderedByScore() -> Self {
92-
order(Player.Columns.score.desc, Player.Columns.name)
97+
// Sort by descending score, and then by name, in a
98+
// localized case insensitive fashion
99+
// See https://github.com/groue/GRDB.swift/blob/master/README.md#string-comparison
100+
order(
101+
Player.Columns.score.desc,
102+
Player.Columns.name.collating(.localizedCaseInsensitiveCompare))
93103
}
94104
}

0 commit comments

Comments
 (0)