Skip to content

Commit 97b0f96

Browse files
authored
Add SyncMetadata.ID (#271)
* Introduce `SyncMetadata.ID` * wip * wip
1 parent 8874760 commit 97b0f96

12 files changed

Lines changed: 517 additions & 337 deletions

File tree

Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 1 addition & 19 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Examples/Reminders/RemindersLists.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class RemindersListsModel {
1313
.group(by: \.id)
1414
.order(by: \.position)
1515
.leftJoin(Reminder.all) { $0.id.eq($1.remindersListID) && !$1.isCompleted }
16-
.leftJoin(SyncMetadata.all) { $0.hasMetadata(in: $2) }
16+
.leftJoin(SyncMetadata.all) { $0.syncMetadataID.eq($2.id) }
1717
.select {
1818
ReminderListState.Columns(
1919
remindersCount: $1.id.count(),

Sources/SQLiteData/CloudKit/SyncMetadata.swift

Lines changed: 86 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,25 @@
1111
/// See <doc:CloudKit#Accessing-CloudKit-metadata> for more info.
1212
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
1313
@Table("sqlitedata_icloud_metadata")
14-
public struct SyncMetadata: Hashable, Sendable {
14+
public struct SyncMetadata: Hashable, Identifiable, Sendable {
15+
/// A selection of columns representing a synchronized record's unique identifier and type.
16+
@Selection
17+
public struct ID: Hashable, Sendable {
18+
/// The unique identifier of the record synchronized.
19+
public var recordPrimaryKey: String
20+
21+
/// The type of the record synchronized, _i.e._ its table name.
22+
public var recordType: String
23+
}
24+
25+
/// The unique identifier and type of the record synchronized.
26+
public let id: ID
27+
1528
/// The unique identifier of the record synchronized.
16-
public var recordPrimaryKey: String
29+
public var recordPrimaryKey: String { id.recordPrimaryKey }
1730

1831
/// The type of the record synchronized, _i.e._ its table name.
19-
public var recordType: String
32+
public var recordType: String { id.recordType }
2033

2134
/// The record zone name.
2235
public var zoneName: String
@@ -35,11 +48,25 @@
3548
@Column(generated: .virtual)
3649
public let recordName: String
3750

51+
/// A selection of columns representing a synchronized parent record's unique identifier and
52+
/// type.
53+
@Selection
54+
public struct ParentID: Hashable, Sendable {
55+
/// The unique identifier of the parent record synchronized.
56+
public var parentRecordPrimaryKey: String
57+
58+
/// The type of the parent record synchronized, _i.e._ its table name.
59+
public var parentRecordType: String
60+
}
61+
62+
/// The identifier and type of this record's parent, if any.
63+
public var parentRecordID: ParentID?
64+
3865
/// The unique identifier of this record's parent, if any.
39-
public var parentRecordPrimaryKey: String?
66+
public var parentRecordPrimaryKey: String? { parentRecordID?.parentRecordPrimaryKey }
4067

4168
/// The type of this record's parent, _i.e._ its table name, if any.
42-
public var parentRecordType: String?
69+
public var parentRecordType: String? { parentRecordID?.parentRecordType }
4370

4471
/// The name of this record's parent, if any.
4572
///
@@ -85,6 +112,25 @@
85112
public var userModificationTime: Int64
86113
}
87114

115+
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
116+
extension SyncMetadata.TableColumns {
117+
public var recordPrimaryKey: TableColumn<SyncMetadata, String> {
118+
id.recordPrimaryKey
119+
}
120+
121+
public var recordType: TableColumn<SyncMetadata, String> {
122+
id.recordType
123+
}
124+
125+
public var parentRecordPrimaryKey: TableColumn<SyncMetadata, String?> {
126+
parentRecordID.parentRecordPrimaryKey
127+
}
128+
129+
public var parentRecordType: TableColumn<SyncMetadata, String?> {
130+
parentRecordID.parentRecordType
131+
}
132+
}
133+
88134
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
89135
extension SyncMetadata {
90136
package init(
@@ -99,16 +145,18 @@
99145
share: CKShare? = nil,
100146
userModificationTime: Int64
101147
) {
102-
self.recordPrimaryKey = recordPrimaryKey
103-
self.recordType = recordType
148+
self.id = ID(recordPrimaryKey: recordPrimaryKey, recordType: recordType)
104149
self.recordName = "\(recordPrimaryKey):\(recordType)"
105150
self.zoneName = zoneName
106151
self.ownerName = ownerName
107-
self.parentRecordPrimaryKey = parentRecordPrimaryKey
108-
self.parentRecordType = parentRecordType
109152
if let parentRecordPrimaryKey, let parentRecordType {
153+
self.parentRecordID = ParentID(
154+
parentRecordPrimaryKey: parentRecordPrimaryKey,
155+
parentRecordType: parentRecordType
156+
)
110157
self.parentRecordName = "\(parentRecordPrimaryKey):\(parentRecordType)"
111158
} else {
159+
self.parentRecordID = nil
112160
self.parentRecordName = nil
113161
}
114162
self.lastKnownServerRecord = lastKnownServerRecord
@@ -139,10 +187,11 @@
139187
}
140188

141189
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
142-
extension PrimaryKeyedTable where PrimaryKey: IdentifierStringConvertible {
190+
extension PrimaryKeyedTable where PrimaryKey.QueryOutput: IdentifierStringConvertible {
143191
/// A query for finding the metadata associated with a record.
144192
///
145193
/// - Parameter primaryKey: The primary key of the record whose metadata to look up.
194+
@available(*, deprecated, message: "Use 'SyncMetadata.find(record.syncMetadataID)', instead")
146195
public static func metadata(for primaryKey: PrimaryKey.QueryOutput) -> Where<SyncMetadata> {
147196
SyncMetadata.where {
148197
#sql(
@@ -153,13 +202,15 @@
153202
)
154203
}
155204
}
156-
}
157205

158-
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
159-
extension PrimaryKeyedTable where PrimaryKey.QueryOutput: IdentifierStringConvertible {
160-
/// Constructs a ``SyncMetadata/RecordName-swift.struct`` for a primary keyed table give an ID.
161-
///
162-
/// - Parameter id: The ID of the record.
206+
/// An identifier representing any associated synchronization metadata.
207+
public var syncMetadataID: SyncMetadata.ID {
208+
SyncMetadata.ID(
209+
recordPrimaryKey: primaryKey.rawIdentifier,
210+
recordType: Self.tableName
211+
)
212+
}
213+
163214
package static func recordName(for id: PrimaryKey.QueryOutput) -> String {
164215
"\(id.rawIdentifier):\(tableName)"
165216
}
@@ -179,10 +230,29 @@
179230
/// RemindersList
180231
/// .leftJoin(SyncMetadata.all) { $0.hasMetadata.in($1) }
181232
/// ```
233+
@available(
234+
*,
235+
deprecated,
236+
message: """
237+
Join the 'SyncMetadata' table using 'SyncMetadata.id' and 'Table.syncMetadataID', instead.
238+
"""
239+
)
182240
public func hasMetadata(in metadata: SyncMetadata.TableColumns) -> some QueryExpression<Bool> {
183241
metadata.recordType.eq(QueryValue.tableName)
184242
&& #sql("\(primaryKey)").eq(metadata.recordPrimaryKey)
185243
}
244+
245+
/// An identifier representing any associated synchronization metadata.
246+
///
247+
/// This helper can be useful when joining your tables to the ``SyncMetadata`` table:
248+
///
249+
/// ```swift
250+
/// RemindersList
251+
/// .leftJoin(SyncMetadata.all) { $0.syncMetadataID.eq($1.id) }
252+
/// ```
253+
public var syncMetadataID: some QueryExpression<SyncMetadata.ID> {
254+
#sql("\(primaryKey), \(bind: QueryValue.tableName)")
255+
}
186256
}
187257

188258
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)

Sources/SQLiteData/Documentation.docc/Articles/CloudKit.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -541,17 +541,17 @@ to attach the metadatabase to your database connection. This can be done with th
541541
``GRDB/Database/attachMetadatabase(containerIdentifier:)`` method defined on `Database`. See
542542
<doc:CloudKit#Setting-up-a-SyncEngine> for more information on how to do this.
543543

544-
With that done you can use the ``StructuredQueriesCore/PrimaryKeyedTable/metadata(for:)`` method
545-
to construct a SQL query for fetching the meta data associated with one of your records.
544+
With that done you can use the ``StructuredQueriesCore/PrimaryKeyedTable/syncMetadataID`` property
545+
to construct a SQL query for fetching the metadata associated with one of your records.
546546

547547
For example, if you want to retrieve the `CKRecord` that is associated with a particular row in
548548
one of your tables, say a reminder, then you can use ``SyncMetadata/lastKnownServerRecord`` to
549549
retrieve the `CKRecord` and then invoke a CloudKit database function to retrieve all of the details:
550550

551551
```swift
552552
let lastKnownServerRecord = try database.read { db in
553-
try RemindersList
554-
.metadata(for: remindersListID)
553+
try SyncMetadata
554+
.find(remindersList.syncMetadataID)
555555
.select(\.lastKnownServerRecord)
556556
.fetchOne(db)
557557
?? nil
@@ -578,7 +578,7 @@ will give you access to the most current list of participants and permissions fo
578578
```swift
579579
let share = try database.read { db in
580580
try RemindersList
581-
.metadata(for: remindersListID)
581+
.find(remindersList.syncMetadataID)
582582
.select(\.share)
583583
.fetchOne(db)
584584
}
@@ -606,7 +606,7 @@ following:
606606

607607
@FetchAll(
608608
RemindersList
609-
.leftJoin(SyncMetadata.all) { $0.hasMetadata(in: $1) }
609+
.leftJoin(SyncMetadata.all) { $0.syncMetadataID.eq($1.id) }
610610
.select {
611611
Row.Columns(
612612
remindersList: $0,
@@ -617,7 +617,7 @@ following:
617617
var rows
618618
```
619619

620-
Here we have used the ``StructuredQueriesCore/PrimaryKeyedTableDefinition/hasMetadata(in:)`` helper
620+
Here we have used the ``StructuredQueriesCore/PrimaryKeyedTableDefinition/syncMetadataID`` helper
621621
that is defined on all primary key tables so that we can join ``SyncMetadata`` to `RemindersList`.
622622

623623
<!--

Sources/SQLiteData/Documentation.docc/Articles/CloudKitSharing.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -393,8 +393,8 @@ select the ``SyncMetadata/share`` value:
393393

394394
```swift
395395
let share = try await database.read { db in
396-
RemindersList
397-
.metadata(for: id)
396+
SyncMetadata
397+
.find(remindersList.syncMetadataID)
398398
.select(\.share)
399399
.fetchOne(db)
400400
?? nil

Sources/SQLiteData/Documentation.docc/SQLiteData.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,5 +317,6 @@ with SQLite to take full advantage of GRDB and SQLiteData.
317317
- ``Dependencies/DependencyValues/defaultSyncEngine``
318318
- ``IdentifierStringConvertible``
319319
- ``SyncMetadata``
320+
- ``StructuredQueriesCore/PrimaryKeyedTableDefinition/syncMetadataID``
320321
- ``StructuredQueriesCore/PrimaryKeyedTableDefinition/hasMetadata(in:)``
321322
- ``SharedRecord``

0 commit comments

Comments
 (0)