Skip to content

Commit c396fc2

Browse files
committed
Merge branch 'development'
2 parents d6b50b2 + 73083c4 commit c396fc2

18 files changed

+913
-465
lines changed

CHANGELOG.md

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

88
#### 7.x Releases
99

10-
- `7.0.0` Betas - [7.0.0-beta](#700-beta) - [7.0.0-beta.2](#700-beta2) - [7.0.0-beta.3](#700-beta3) - [7.0.0-beta.4](#700-beta4)
10+
- `7.0.0` Betas - [7.0.0-beta](#700-beta) - [7.0.0-beta.2](#700-beta2) - [7.0.0-beta.3](#700-beta3) - [7.0.0-beta.4](#700-beta4) - [7.0.0-beta.5](#700-beta5)
1111

1212
#### 6.x Releases
1313

@@ -131,11 +131,18 @@ GRDB adheres to [Semantic Versioning](https://semver.org/), with one exception:
131131

132132
---
133133

134+
## 7.0.0-beta.5
135+
136+
Released October 13, 2024
137+
138+
- **Fixed**: Fix DatabaseMigrator.hasSchemaChanges failing for readonly connections by [@Jnosh](https://github.com/Jnosh) in [#1653](https://github.com/groue/GRDB.swift/pull/1653)
139+
- **Documentation Update**: Enhance the database schema documentation by [@groue](https://github.com/groue) in [#1652](https://github.com/groue/GRDB.swift/pull/1652)
140+
134141
## 7.0.0-beta.4
135142

136143
Released October 12, 2024
137144

138-
- **New**: Allow applications to handle DatabaseMigrator schema changes [@groue](https://github.com/groue) in [#1651](https://github.com/groue/GRDB.swift/pull/1651)
145+
- **New**: Allow applications to handle DatabaseMigrator schema changes by [@groue](https://github.com/groue) in [#1651](https://github.com/groue/GRDB.swift/pull/1651)
139146

140147
## 7.0.0-beta.3
141148

@@ -159,7 +166,7 @@ Released September 29, 2024
159166

160167
[Migrating From GRDB 6 to GRDB 7](Documentation/GRDB7MigrationGuide.md) describes in detail how to bump the GRDB version in your application.
161168

162-
The new [Swift Concurrency and GRDB](https://swiftpackageindex.com/groue/grdb.swift/v7.0.0-beta.4/documentation/grdb/swiftconcurrency) guide explains how to best integrate GRDB and Swift Concurrency.
169+
The new [Swift Concurrency and GRDB](https://swiftpackageindex.com/groue/grdb.swift/v7.0.0-beta.5/documentation/grdb/swiftconcurrency) guide explains how to best integrate GRDB and Swift Concurrency.
163170

164171
The [demo app](Documentation/DemoApps/) was rewritten from scratch in a brand new Xcode 16 project.
165172

Documentation/GRDB7MigrationGuide.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ Do not miss [Swift Concurrency and GRDB], for more recommendations regarding non
228228
- 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.
229229

230230
[Migrating to Swift 6]: https://www.swift.org/migration/documentation/migrationguide
231-
[Sharing a Database]: https://swiftpackageindex.com/groue/grdb.swift/v7.0.0-beta.4/documentation/grdb/databasesharing
232-
[Transaction Kinds]: https://swiftpackageindex.com/groue/grdb.swift/v7.0.0-beta.4/documentation/grdb/transactions#Transaction-Kinds
233-
[Swift Concurrency and GRDB]: https://swiftpackageindex.com/groue/grdb.swift/v7.0.0-beta.4/documentation/grdb/swiftconcurrency
234-
[Record]: https://swiftpackageindex.com/groue/grdb.swift/v7.0.0-beta.4/documentation/grdb/record
231+
[Sharing a Database]: https://swiftpackageindex.com/groue/grdb.swift/v7.0.0-beta.5/documentation/grdb/databasesharing
232+
[Transaction Kinds]: https://swiftpackageindex.com/groue/grdb.swift/v7.0.0-beta.5/documentation/grdb/transactions#Transaction-Kinds
233+
[Swift Concurrency and GRDB]: https://swiftpackageindex.com/groue/grdb.swift/v7.0.0-beta.5/documentation/grdb/swiftconcurrency
234+
[Record]: https://swiftpackageindex.com/groue/grdb.swift/v7.0.0-beta.5/documentation/grdb/record

GRDB.swift.podspec

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

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

GRDB/Core/Database+Schema.swift

Lines changed: 138 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,15 @@ extension Database {
9696

9797
// MARK: - Database Schema
9898

99-
/// Returns the current schema version.
99+
/// Returns the current schema version (`PRAGMA schema_version`).
100+
///
101+
/// For example:
102+
///
103+
/// ```swift
104+
/// let version = try dbQueue.read { db in
105+
/// try db.schemaVersion()
106+
/// }
107+
/// ```
100108
///
101109
/// Related SQLite documentation: <https://www.sqlite.org/pragma.html#pragma_schema_version>
102110
public func schemaVersion() throws -> Int32 {
@@ -234,8 +242,19 @@ extension Database {
234242

235243
/// Returns whether a table exists
236244
///
237-
/// When `schemaName` is not specified, known schemas are iterated in
238-
/// SQLite resolution order and the first matching result is returned.
245+
/// When `schemaName` is not specified, the result is true if any known
246+
/// schema contains the table.
247+
///
248+
/// For example:
249+
///
250+
/// ```swift
251+
/// try dbQueue.read { db in
252+
/// if try db.tableExists("player") { ... }
253+
/// if try db.tableExists("player", in: "main") { ... }
254+
/// if try db.tableExists("player", in: "temp") { ... }
255+
/// if try db.tableExists("player", in: "attached") { ... }
256+
/// }
257+
/// ```
239258
///
240259
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs, or
241260
/// if the specified schema does not exist
@@ -278,8 +297,19 @@ extension Database {
278297
/// Returns whether a view exists, in the main or temp schema, or in an
279298
/// attached database.
280299
///
281-
/// When `schemaName` is not specified, known schemas are iterated in
282-
/// SQLite resolution order and the first matching result is returned.
300+
/// When `schemaName` is not specified, the result is true if any known
301+
/// schema contains the table.
302+
///
303+
/// For example:
304+
///
305+
/// ```swift
306+
/// try dbQueue.read { db in
307+
/// if try db.viewExists("player") { ... }
308+
/// if try db.viewExists("player", in: "main") { ... }
309+
/// if try db.viewExists("player", in: "temp") { ... }
310+
/// if try db.viewExists("player", in: "attached") { ... }
311+
/// }
312+
/// ```
283313
///
284314
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs, or
285315
/// if the specified schema does not exist
@@ -296,8 +326,19 @@ extension Database {
296326
/// Returns whether a trigger exists, in the main or temp schema, or in an
297327
/// attached database.
298328
///
299-
/// When `schemaName` is not specified, known schemas are iterated in
300-
/// SQLite resolution order and the first matching result is returned.
329+
/// When `schemaName` is not specified, the result is true if any known
330+
/// schema contains the table.
331+
///
332+
/// For example:
333+
///
334+
/// ```swift
335+
/// try dbQueue.read { db in
336+
/// if try db.triggerExists("on_player_update") { ... }
337+
/// if try db.triggerExists("on_player_update", in: "main") { ... }
338+
/// if try db.triggerExists("on_player_update", in: "temp") { ... }
339+
/// if try db.triggerExists("on_player_update", in: "attached") { ... }
340+
/// }
341+
/// ```
301342
///
302343
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs, or
303344
/// if the specified schema does not exist
@@ -340,6 +381,15 @@ extension Database {
340381
/// table has no explicit primary key, the result is the hidden
341382
/// "rowid" column.
342383
///
384+
/// For example:
385+
///
386+
/// ```swift
387+
/// try dbQueue.read { db in
388+
/// let primaryKey = try db.primaryKey("player")
389+
/// print(primaryKey.columns)
390+
/// }
391+
/// ```
392+
///
343393
/// When `schemaName` is not specified, known schemas are iterated in
344394
/// SQLite resolution order and the first matching result is returned.
345395
///
@@ -359,6 +409,36 @@ extension Database {
359409
throw DatabaseError.noSuchTable(tableName)
360410
}
361411

412+
/// Returns the name of the single-column primary key.
413+
///
414+
/// A fatal error is raised if the primary key has several columns, or
415+
/// if `tableName` is the name of a database view.
416+
func filteringPrimaryKeyColumn(_ tableName: String) throws -> String {
417+
do {
418+
let primaryKey = try primaryKey(tableName)
419+
GRDBPrecondition(
420+
primaryKey.columns.count == 1,
421+
"Filtering by primary key requires a single-column primary key in the table '\(tableName)'")
422+
return primaryKey.columns[0]
423+
} catch let error as DatabaseError {
424+
// Maybe the user tries to filter a view by primary key,
425+
// as in <https://github.com/groue/GRDB.swift/issues/1648>.
426+
// In this case, raise a fatalError because this is a
427+
// programmer error which is very likely to be detected
428+
// during development.
429+
if case .SQLITE_ERROR = error.resultCode,
430+
(try? viewExists(tableName)) == true
431+
{
432+
fatalError("""
433+
Filtering by primary key is not available on the database view '\(tableName)'. \
434+
Use `filter(Column("...") == value)` instead.
435+
""")
436+
} else {
437+
throw error
438+
}
439+
}
440+
}
441+
362442
/// Returns nil if table does not exist
363443
private func primaryKey(_ table: TableIdentifier) throws -> PrimaryKeyInfo? {
364444
SchedulingWatchdog.preconditionValidQueue(self)
@@ -499,14 +579,26 @@ extension Database {
499579

500580
/// The indexes on table named `tableName`.
501581
///
582+
/// For example:
583+
///
584+
/// ```swift
585+
/// try dbQueue.read { db in
586+
/// let indexes = db.indexes(in: "player")
587+
/// for index in indexes {
588+
/// print(index.columns)
589+
/// }
590+
/// }
591+
/// ```
592+
///
502593
/// Only indexes on columns are returned. Indexes on expressions are
503594
/// not returned.
504595
///
505-
/// SQLite does not define any index for INTEGER PRIMARY KEY columns: this
506-
/// method does not return any index that represents the primary key.
596+
/// SQLite does not define any index for INTEGER PRIMARY KEY columns:
597+
/// this method does not return any index that represents the
598+
/// primary key.
507599
///
508-
/// If you want to know if a set of columns uniquely identifies a row, because
509-
/// the columns contain the primary key or a unique index, use
600+
/// If you want to know if a set of columns uniquely identifies a row,
601+
/// because the columns contain the primary key or a unique index, use
510602
/// ``table(_:hasUniqueKey:)``.
511603
///
512604
/// When `schemaName` is not specified, known schemas are iterated in
@@ -587,16 +679,19 @@ extension Database {
587679
/// For example:
588680
///
589681
/// ```swift
590-
/// // One table with one primary key (id), and a unique index (a, b):
591-
/// //
592-
/// // > CREATE TABLE t(id INTEGER PRIMARY KEY, a, b, c);
593-
/// // > CREATE UNIQUE INDEX i ON t(a, b);
594-
/// try db.table("t", hasUniqueKey: ["id"]) // true
595-
/// try db.table("t", hasUniqueKey: ["a", "b"]) // true
596-
/// try db.table("t", hasUniqueKey: ["b", "a"]) // true
597-
/// try db.table("t", hasUniqueKey: ["c"]) // false
598-
/// try db.table("t", hasUniqueKey: ["id", "a"]) // true
599-
/// try db.table("t", hasUniqueKey: ["id", "a", "b", "c"]) // true
682+
/// try dbQueue.read { db in
683+
/// // One table with one primary key (id)
684+
/// // and a unique index (a, b):
685+
/// //
686+
/// // > CREATE TABLE t(id INTEGER PRIMARY KEY, a, b, c);
687+
/// // > CREATE UNIQUE INDEX i ON t(a, b);
688+
/// try db.table("t", hasUniqueKey: ["id"]) // true
689+
/// try db.table("t", hasUniqueKey: ["a", "b"]) // true
690+
/// try db.table("t", hasUniqueKey: ["b", "a"]) // true
691+
/// try db.table("t", hasUniqueKey: ["c"]) // false
692+
/// try db.table("t", hasUniqueKey: ["id", "a"]) // true
693+
/// try db.table("t", hasUniqueKey: ["id", "a", "b", "c"]) // true
694+
/// }
600695
/// ```
601696
public func table(
602697
_ tableName: String,
@@ -610,6 +705,17 @@ extension Database {
610705
/// When `schemaName` is not specified, known schemas are iterated in
611706
/// SQLite resolution order and the first matching result is returned.
612707
///
708+
/// For example:
709+
///
710+
/// ```swift
711+
/// try dbQueue.read { db in
712+
/// let foreignKeys = try db.foreignKeys(in: "player")
713+
/// for foreignKey in foreignKeys {
714+
/// print(foreignKey.destinationTable)
715+
/// }
716+
/// }
717+
/// ```
718+
///
613719
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs, if
614720
/// the specified schema does not exist, or if no such table or view
615721
/// with this name exists in the main or temp schema, or in an attached
@@ -831,6 +937,17 @@ extension Database {
831937
/// When `schemaName` is not specified, known schemas are iterated in
832938
/// SQLite resolution order and the first matching result is returned.
833939
///
940+
/// For example:
941+
///
942+
/// ```swift
943+
/// try dbQueue.read { db in
944+
/// let columns = try db.columns(in: "player")
945+
/// for column in columns {
946+
/// print(column.name)
947+
/// }
948+
/// }
949+
/// ```
950+
///
834951
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs, if
835952
/// the specified schema does not exist,or if no such table or view
836953
/// with this name exists in the main or temp schema, or in an attached

0 commit comments

Comments
 (0)