Skip to content

Commit c7205b1

Browse files
committed
Merge branch 'development'
2 parents ea6d8e0 + 49d14e6 commit c7205b1

14 files changed

+205
-15
lines changed

.github/workflows/CI.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ jobs:
4646
name: "macOS"
4747
- xcode: "Xcode_16.1.app"
4848
runsOn: macOS-14
49-
destination: "OS=18.1,name=iPhone 15 Pro"
49+
destination: "OS=18.1,name=iPhone 16 Pro"
5050
name: "iOS"
5151
- xcode: "Xcode_16.1.app"
5252
runsOn: macOS-14

CHANGELOG.md

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

88
#### 7.x Releases
99

10+
- `7.2.x` Releases - [7.2.0](#720)
1011
- `7.1.x` Releases - [7.1.0](#710)
1112
- `7.0.x` Releases - [7.0.0](#700)
1213
- `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) - [7.0.0-beta.6](#700-beta6) - [7.0.0-beta.7](#700-beta7)
@@ -133,6 +134,12 @@ GRDB adheres to [Semantic Versioning](https://semver.org/), with one exception:
133134

134135
---
135136

137+
## 7.2.0
138+
139+
Released February 12, 2025
140+
141+
- **New**: Select ids with selectID() by [@groue](https://github.com/groue) in [#1719](https://github.com/groue/GRDB.swift/pull/1719)
142+
136143
## 7.1.0
137144

138145
Released February 5, 2025

GRDB.swift.podspec

+1-1
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.1.0'
3+
s.version = '7.2.0'
44

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

GRDB/Documentation.docc/RecordRecommendedPractices.md

+6-6
Original file line numberDiff line numberDiff line change
@@ -393,16 +393,16 @@ Extensions to the `DerivableRequest` protocol can not change the type of request
393393
394394
```swift
395395
extension QueryInterfaceRequest<Author> {
396-
// Selects author ids
397-
func selectId() -> QueryInterfaceRequest<Int64> {
398-
selectPrimaryKey(as: Int64.self)
396+
// Selects authors' name
397+
func selectName() -> QueryInterfaceRequest<String> {
398+
select(Author.Columns.name)
399399
}
400400
}
401401
402-
// The ids of Japanese authors
403-
let ids: Set<Int64> = try Author.all()
402+
// The names of Japanese authors
403+
let names: Set<String> = try Author.all()
404404
.filter(countryCode: "JP")
405-
.selectId()
405+
.selectName()
406406
.fetchSet(db)
407407
```
408408

GRDB/QueryInterface/Request/QueryInterfaceRequest.swift

+42-1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
/// - ``select(_:as:)-3o8qw``
5757
/// - ``select(literal:as:)``
5858
/// - ``select(sql:arguments:as:)``
59+
/// - ``selectID()``
5960
/// - ``selectPrimaryKey(as:)``
6061
///
6162
/// ### Batch Delete
@@ -237,7 +238,7 @@ extension QueryInterfaceRequest: SelectionRequest {
237238
select(sqlLiteral, as: type)
238239
}
239240

240-
/// Selects the primary key.
241+
/// Returns a request that selects the primary key.
241242
///
242243
/// All primary keys are supported:
243244
///
@@ -281,6 +282,46 @@ extension QueryInterfaceRequest: SelectionRequest {
281282
.asRequest(of: PrimaryKey.self)
282283
}
283284

285+
/// Returns a request that selects the primary key.
286+
///
287+
/// For example:
288+
///
289+
/// ```swift
290+
/// // SELECT id FROM player WHERE ...
291+
/// let request = try Player.filter(...).selectID()
292+
/// ```
293+
///
294+
/// **Important**: if the record type has an `ID` type that is an
295+
/// optional, such as `Int64?`, it is recommended to prefer
296+
/// ``selectPrimaryKey(as:)`` instead:
297+
///
298+
/// ```swift
299+
/// struct Player: Identifiable {
300+
/// var id: Int64?
301+
/// }
302+
///
303+
/// // NOT RECOMMENDED: Set<Int64?>
304+
/// let ids = try Player.filter(...)
305+
/// .selectID()
306+
/// .fetchSet(db)
307+
///
308+
/// // BETTER: Set<Int64>
309+
/// let ids = try Player.filter(...)
310+
/// .selectPrimaryKey(as: Int64.self)
311+
/// .fetchSet(db)
312+
/// ```
313+
public func selectID() -> QueryInterfaceRequest<RowDecoder.ID>
314+
where RowDecoder: Identifiable
315+
{
316+
selectWhenConnected { db in
317+
let primaryKey = try db.primaryKey(self.databaseTableName)
318+
GRDBPrecondition(
319+
primaryKey.columns.count == 1,
320+
"selectID requires a single-column primary key in the table \(self.databaseTableName)")
321+
return [Column(primaryKey.columns[0])]
322+
}.asRequest(of: RowDecoder.ID.self)
323+
}
324+
284325
public func annotatedWhenConnected(
285326
with selection: @escaping @Sendable (Database) throws -> [any SQLSelectable])
286327
-> Self

GRDB/QueryInterface/SQL/Table.swift

+35
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494
/// - ``select(literal:as:)``
9595
/// - ``select(sql:arguments:)``
9696
/// - ``select(sql:arguments:as:)``
97+
/// - ``selectID()``
9798
/// - ``selectPrimaryKey(as:)``
9899
/// - ``with(_:)``
99100
///
@@ -424,6 +425,40 @@ extension Table {
424425
all().selectPrimaryKey(as: type)
425426
}
426427

428+
/// Returns a request that selects the primary key.
429+
///
430+
/// For example:
431+
///
432+
/// ```swift
433+
/// let table = Table<Player>("player")
434+
///
435+
/// // SELECT id FROM player
436+
/// let request = try table.selectID()
437+
/// ```
438+
///
439+
/// **Important**: if the record type has an `ID` type that is an
440+
/// optional, such as `Int64?`, it is recommended to prefer
441+
/// ``selectPrimaryKey(as:)`` instead:
442+
///
443+
/// ```swift
444+
/// struct Player: Identifiable {
445+
/// var id: Int64?
446+
/// }
447+
///
448+
/// let table = Table<Player>("player")
449+
///
450+
/// // NOT RECOMMENDED: Set<Int64?>
451+
/// let ids = try table.selectID().fetchSet(db)
452+
///
453+
/// // BETTER: Set<Int64>
454+
/// let ids = try table.selectPrimaryKey(as: Int64.self).fetchSet(db)
455+
/// ```
456+
public func selectID() -> QueryInterfaceRequest<RowDecoder.ID>
457+
where RowDecoder: Identifiable
458+
{
459+
all().selectID()
460+
}
461+
427462
/// Returns a request with the provided result columns appended to the
428463
/// table columns.
429464
///

GRDB/QueryInterface/TableRecord+QueryInterfaceRequest.swift

+30
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,36 @@ extension TableRecord {
251251
all().selectPrimaryKey(as: type)
252252
}
253253

254+
/// Returns a request that selects the primary key.
255+
///
256+
/// For example:
257+
///
258+
/// ```swift
259+
/// // SELECT id FROM player
260+
/// let request = try Player.selectID()
261+
/// ```
262+
///
263+
/// **Important**: if the record type has an `ID` type that is an
264+
/// optional, such as `Int64?`, it is recommended to prefer
265+
/// ``selectPrimaryKey(as:)`` instead:
266+
///
267+
/// ```swift
268+
/// struct Player: Identifiable {
269+
/// var id: Int64?
270+
/// }
271+
///
272+
/// // NOT RECOMMENDED: Set<Int64?>
273+
/// let ids = try Player.selectID().fetchSet(db)
274+
///
275+
/// // BETTER: Set<Int64>
276+
/// let ids = try Player.selectPrimaryKey(as: Int64.self).fetchSet(db)
277+
/// ```
278+
public static func selectID() -> QueryInterfaceRequest<Self.ID>
279+
where Self: Identifiable
280+
{
281+
all().selectID()
282+
}
283+
254284
/// Returns a request with the provided result columns appended to the
255285
/// record selection.
256286
///

GRDB/Record/TableRecord.swift

+1
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ import Foundation
9898
/// - ``select(literal:as:)``
9999
/// - ``select(sql:arguments:)``
100100
/// - ``select(sql:arguments:as:)``
101+
/// - ``selectID()``
101102
/// - ``selectPrimaryKey(as:)``
102103
/// - ``with(_:)``
103104
///

README.md

+11-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<a href="https://github.com/groue/GRDB.swift/actions/workflows/CI.yml"><img alt="CI Status" src="https://github.com/groue/GRDB.swift/actions/workflows/CI.yml/badge.svg?branch=master"></a>
1616
</p>
1717

18-
**Latest release**: February 5, 2025 • [version 7.1.0](https://github.com/groue/GRDB.swift/tree/v7.1.0) • [CHANGELOG](CHANGELOG.md) • [Migrating From GRDB 6 to GRDB 7](Documentation/GRDB7MigrationGuide.md)
18+
**Latest release**: February 12, 2025 • [version 7.2.0](https://github.com/groue/GRDB.swift/tree/v7.2.0) • [CHANGELOG](CHANGELOG.md) • [Migrating From GRDB 6 to GRDB 7](Documentation/GRDB7MigrationGuide.md)
1919

2020
**Requirements**: iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 7.0+ &bull; SQLite 3.20.0+ &bull; Swift 6+ / Xcode 16+
2121

@@ -3341,6 +3341,16 @@ You can now build requests with the following methods: `all`, `none`, `select`,
33413341
Player.select(nameColumn, as: String.self)
33423342
```
33433343

3344+
- [`selectID()`](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/queryinterfacerequest/selectID()) is available on [Identifiable Records]. It supports all tables that have a single-column primary key:
3345+
3346+
```swift
3347+
// SELECT id FROM player
3348+
Player.selectID()
3349+
3350+
// SELECT id FROM player WHERE name IS NOT NULL
3351+
Player.filter(nameColumn != nil).selectID()
3352+
```
3353+
33443354
- [`annotated(with: expression...)`](https://swiftpackageindex.com/groue/GRDB.swift/documentation/grdb/selectionrequest/annotated(with:)-6ehs4) extends the selection.
33453355

33463356
```swift

Support/Info.plist

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<key>CFBundlePackageType</key>
1616
<string>FMWK</string>
1717
<key>CFBundleShortVersionString</key>
18-
<string>7.1.0</string>
18+
<string>7.2.0</string>
1919
<key>CFBundleSignature</key>
2020
<string>????</string>
2121
<key>CFBundleVersion</key>

Tests/GRDBTests/RecordMinimalNonOptionalPrimaryKeySingleTests.swift

+20
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,16 @@ class RecordMinimalNonOptionalPrimaryKeySingleTests: GRDBTestCase {
800800
}
801801
}
802802

803+
func test_static_selectID() throws {
804+
let dbQueue = try makeDatabaseQueue()
805+
try dbQueue.inDatabase { db in
806+
let record = MinimalNonOptionalPrimaryKeySingle(id: "theUUID")
807+
try record.insert(db)
808+
let ids: [String] = try MinimalSingle.selectID().fetchAll(db)
809+
XCTAssertEqual(ids, ["theUUID"])
810+
}
811+
}
812+
803813
func test_request_selectPrimaryKey() throws {
804814
let dbQueue = try makeDatabaseQueue()
805815
try dbQueue.inDatabase { db in
@@ -811,4 +821,14 @@ class RecordMinimalNonOptionalPrimaryKeySingleTests: GRDBTestCase {
811821
XCTAssertEqual(rows, [["id": "theUUID"]])
812822
}
813823
}
824+
825+
func test_request_selectID() throws {
826+
let dbQueue = try makeDatabaseQueue()
827+
try dbQueue.inDatabase { db in
828+
let record = MinimalNonOptionalPrimaryKeySingle(id: "theUUID")
829+
try record.insert(db)
830+
let ids: [String] = try MinimalSingle.all().selectID().fetchAll(db)
831+
XCTAssertEqual(ids, ["theUUID"])
832+
}
833+
}
814834
}

Tests/GRDBTests/RecordMinimalPrimaryKeyRowIDTests.swift

+20
Original file line numberDiff line numberDiff line change
@@ -844,6 +844,16 @@ class RecordMinimalPrimaryKeyRowIDTests : GRDBTestCase {
844844
}
845845
}
846846

847+
func test_static_selectID() throws {
848+
let dbQueue = try makeDatabaseQueue()
849+
try dbQueue.inDatabase { db in
850+
let record = MinimalRowID()
851+
try record.insert(db)
852+
let ids: [Int64?] = try MinimalRowID.selectID().fetchAll(db)
853+
XCTAssertEqual(ids, [1])
854+
}
855+
}
856+
847857
func test_request_selectPrimaryKey() throws {
848858
let dbQueue = try makeDatabaseQueue()
849859
try dbQueue.inDatabase { db in
@@ -855,4 +865,14 @@ class RecordMinimalPrimaryKeyRowIDTests : GRDBTestCase {
855865
XCTAssertEqual(rows, [["id": 1]])
856866
}
857867
}
868+
869+
func test_request_selectID() throws {
870+
let dbQueue = try makeDatabaseQueue()
871+
try dbQueue.inDatabase { db in
872+
let record = MinimalRowID()
873+
try record.insert(db)
874+
let ids: [Int64?] = try MinimalRowID.all().selectID().fetchAll(db)
875+
XCTAssertEqual(ids, [1])
876+
}
877+
}
858878
}

Tests/GRDBTests/RecordMinimalPrimaryKeySingleTests.swift

+26-4
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ class RecordMinimalPrimaryKeySingleTests: GRDBTestCase {
375375
XCTAssertEqual(lastSQLQuery, "SELECT * FROM \"minimalSingles\" WHERE \"UUID\" = '\(record.UUID!)'")
376376
}
377377
}
378-
378+
379379

380380
// MARK: - Fetch With Key Request
381381

@@ -661,7 +661,7 @@ class RecordMinimalPrimaryKeySingleTests: GRDBTestCase {
661661
_ = try MinimalSingle.find(db, key: id)
662662
XCTFail("Expected RecordError")
663663
} catch RecordError.recordNotFound(databaseTableName: "minimalSingles", key: ["UUID": .null]) { }
664-
664+
665665
do {
666666
let fetchedRecord = try MinimalSingle.find(db, key: record.UUID)
667667
XCTAssertTrue(fetchedRecord.UUID == record.UUID)
@@ -680,7 +680,7 @@ class RecordMinimalPrimaryKeySingleTests: GRDBTestCase {
680680
}
681681
}
682682
}
683-
683+
684684

685685
// MARK: - Fetch With Primary Key Request
686686

@@ -861,7 +861,7 @@ class RecordMinimalPrimaryKeySingleTests: GRDBTestCase {
861861
}
862862

863863
// MARK: Select PrimaryKey
864-
864+
865865
func test_static_selectPrimaryKey() throws {
866866
let dbQueue = try makeDatabaseQueue()
867867
try dbQueue.inDatabase { db in
@@ -875,6 +875,17 @@ class RecordMinimalPrimaryKeySingleTests: GRDBTestCase {
875875
}
876876
}
877877

878+
func test_static_selectID() throws {
879+
let dbQueue = try makeDatabaseQueue()
880+
try dbQueue.inDatabase { db in
881+
let record = MinimalSingle()
882+
record.UUID = "theUUID"
883+
try record.insert(db)
884+
let ids: [String] = try MinimalSingle.selectID().fetchAll(db)
885+
XCTAssertEqual(ids, ["theUUID"])
886+
}
887+
}
888+
878889
func test_request_selectPrimaryKey() throws {
879890
let dbQueue = try makeDatabaseQueue()
880891
try dbQueue.inDatabase { db in
@@ -887,4 +898,15 @@ class RecordMinimalPrimaryKeySingleTests: GRDBTestCase {
887898
XCTAssertEqual(rows, [["UUID": "theUUID"]])
888899
}
889900
}
901+
902+
func test_request_selectID() throws {
903+
let dbQueue = try makeDatabaseQueue()
904+
try dbQueue.inDatabase { db in
905+
let record = MinimalSingle()
906+
record.UUID = "theUUID"
907+
try record.insert(db)
908+
let ids: [String] = try MinimalSingle.all().selectID().fetchAll(db)
909+
XCTAssertEqual(ids, ["theUUID"])
910+
}
911+
}
890912
}

0 commit comments

Comments
 (0)