-
Notifications
You must be signed in to change notification settings - Fork 120
Expand file tree
/
Copy pathSyncMetadata.swift
More file actions
316 lines (275 loc) · 11.4 KB
/
Copy pathSyncMetadata.swift
File metadata and controls
316 lines (275 loc) · 11.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
#if canImport(CloudKit)
import CloudKit
/// Represents the pending synchronization state of a record.
public enum PendingStatus: Int, Hashable, Sendable, QueryBindable, QueryDecodable, QueryRepresentable {
/// Indicates the metadata has been "soft" deleted. It will be fully deleted once the
/// next batch of pending changes is processed.
case deleted = 0
/// Indicates the record has been reinserted after being soft-deleted. This status
/// will be cleared after the next batch of pending changes is processed or server
/// record changes are applied.
case reinserted = 1
}
/// A table that tracks metadata related to synchronized data.
///
/// Each row of this table represents a synchronized record across all tables synchronized with
/// CloudKit. This means that the sum of the count of rows across all synchronized tables in your
/// application is the number of rows this one single table holds. However, this table is held
/// in a database separate from your app's database.
///
/// See <doc:CloudKit#Accessing-CloudKit-metadata> for more info.
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
@Table("sqlitedata_icloud_metadata")
public struct SyncMetadata: Hashable, Identifiable, Sendable {
/// A selection of columns representing a synchronized record's unique identifier and type.
@Selection
public struct ID: Hashable, Sendable {
/// The unique identifier of the record synchronized.
public let recordPrimaryKey: String
/// The type of the record synchronized, _i.e._ its table name.
public let recordType: String
}
/// The unique identifier and type of the record synchronized.
public let id: ID
/// The unique identifier of the record synchronized.
public var recordPrimaryKey: String { id.recordPrimaryKey }
/// The type of the record synchronized, _i.e._ its table name.
public var recordType: String { id.recordType }
/// The record zone name.
public let zoneName: String
/// The record owner name.
public let ownerName: String
/// The name of the record synchronized.
///
/// This field encodes both the table name and primary key of the record synchronized in
/// the format "primaryKey:tableName", for example:
///
/// ```swift
/// "8c4d1e4e-49b2-4f60-b6df-3c23881b87c6:reminders"
/// ```
@Column(generated: .virtual)
public let recordName: String
/// A selection of columns representing a synchronized parent record's unique identifier and
/// type.
@Selection
public struct ParentID: Hashable, Sendable {
/// The unique identifier of the parent record synchronized.
public let parentRecordPrimaryKey: String
/// The type of the parent record synchronized, _i.e._ its table name.
public let parentRecordType: String
}
/// The identifier and type of this record's parent, if any.
public let parentRecordID: ParentID?
/// The unique identifier of this record's parent, if any.
public var parentRecordPrimaryKey: String? { parentRecordID?.parentRecordPrimaryKey }
/// The type of this record's parent, _i.e._ its table name, if any.
public var parentRecordType: String? { parentRecordID?.parentRecordType }
/// The name of this record's parent, if any.
///
/// This field encodes both the table name and primary key of the parent record in the format
/// "primaryKey:tableName", for example:
///
/// ```swift
/// "d35e1f81-46e4-45d1-904b-2b7df1661e3e:remindersLists"
/// ```
@Column(generated: .virtual)
public let parentRecordName: String?
/// The last known `CKRecord` received from the server.
///
/// This record holds only the fields that are archived when using `encodeSystemFields(with:)`.
@Column(as: CKRecord?.SystemFieldsRepresentation.self)
public let lastKnownServerRecord: CKRecord?
/// The last known `CKRecord` received from the server with all fields archived.
@Column(as: CKRecord?._AllFieldsRepresentation.self)
public let _lastKnownServerRecordAllFields: CKRecord?
/// The `CKShare` associated with this record, if it is shared.
@Column(as: CKShare?.SystemFieldsRepresentation.self)
public let share: CKShare?
/// The pending synchronization state of the record which can require special handling.
///
/// `nil` indicates a normal record with standard sync behavior.
public let _pendingStatus: PendingStatus?
/// The appropriate all-fields record to use as a base when building a `CKRecord` for upload.
///
/// For reinserted rows, returns `lastKnownServerRecord` (system fields only)
/// For all others, returns `_lastKnownServerRecordAllFields`.
var allFieldsRecord: CKRecord? {
switch _pendingStatus {
case .reinserted:
lastKnownServerRecord
case nil, .deleted:
_lastKnownServerRecordAllFields
}
}
@Column("hasLastKnownServerRecord", generated: .virtual)
public let _hasLastKnownServerRecord: Bool
@Column("isShared", generated: .virtual)
fileprivate let _isShared: Bool
/// The time the user last modified the record.
public let userModificationTime: Int64
public var hasLastKnownServerRecord: Bool {
lastKnownServerRecord != nil
}
/// Determines if the record associated with this metadata is currently shared in CloudKit.
///
/// This can only return `true` for root records. For example, the metadata associated with a
/// `RemindersList` can have `isShared == true`, but a `Reminder` associated with the list
/// will have `isShared == false`.
public var isShared: Bool {
_isShared
}
}
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
extension SyncMetadata.TableColumns {
public var recordPrimaryKey: TableColumn<SyncMetadata, String> {
id.recordPrimaryKey
}
public var recordType: TableColumn<SyncMetadata, String> {
id.recordType
}
public var parentRecordPrimaryKey: TableColumn<SyncMetadata, String?> {
parentRecordID.parentRecordPrimaryKey
}
public var parentRecordType: TableColumn<SyncMetadata, String?> {
parentRecordID.parentRecordType
}
// NB: Workaround for https://github.com/groue/GRDB.swift/discussions/1844
public var hasLastKnownServerRecord: some QueryExpression<Bool> {
#sql(
"""
((\(self._hasLastKnownServerRecord) = 1) AND (\(self.lastKnownServerRecord) OR 1))
"""
)
}
// NB: Workaround for https://github.com/groue/GRDB.swift/discussions/1844
public var isShared: some QueryExpression<Bool> {
#sql(
"""
((\(self._isShared) = 1) AND (\(self.share) OR 1))
"""
)
}
}
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
extension SyncMetadata {
package init(
recordPrimaryKey: String,
recordType: String,
zoneName: String,
ownerName: String,
parentRecordPrimaryKey: String? = nil,
parentRecordType: String? = nil,
lastKnownServerRecord: CKRecord? = nil,
_lastKnownServerRecordAllFields: CKRecord? = nil,
share: CKShare? = nil,
userModificationTime: Int64
) {
self.id = ID(recordPrimaryKey: recordPrimaryKey, recordType: recordType)
self.recordName = "\(recordPrimaryKey):\(recordType)"
self.zoneName = zoneName
self.ownerName = ownerName
if let parentRecordPrimaryKey, let parentRecordType {
self.parentRecordID = ParentID(
parentRecordPrimaryKey: parentRecordPrimaryKey,
parentRecordType: parentRecordType
)
self.parentRecordName = "\(parentRecordPrimaryKey):\(parentRecordType)"
} else {
self.parentRecordID = nil
self.parentRecordName = nil
}
self.lastKnownServerRecord = lastKnownServerRecord
self._lastKnownServerRecordAllFields = _lastKnownServerRecordAllFields
self.share = share
self._hasLastKnownServerRecord = lastKnownServerRecord != nil
self._isShared = share != nil
self.userModificationTime = userModificationTime
self._pendingStatus = nil
}
package static func find(_ recordID: CKRecord.ID) -> Where<Self> {
Self.where {
$0.recordName.eq(recordID.recordName)
&& $0.zoneName.eq(recordID.zoneID.zoneName)
&& $0.ownerName.eq(recordID.zoneID.ownerName)
}
}
package static func findAll(_ recordIDs: some Collection<CKRecord.ID>) -> Where<Self> {
let condition: QueryFragment = recordIDs.map {
"(\(bind: $0.recordName), \(bind: $0.zoneID.zoneName), \(bind: $0.zoneID.ownerName))"
}
.joined(separator: ", ")
return Self.where {
#sql("(\($0.recordName), \($0.zoneName), \($0.ownerName)) IN (\(condition))")
}
}
}
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
extension PrimaryKeyedTable where PrimaryKey.QueryOutput: IdentifierStringConvertible {
/// A query for finding the metadata associated with a record.
///
/// - Parameter primaryKey: The primary key of the record whose metadata to look up.
@available(*, deprecated, message: "Use 'SyncMetadata.find(record.syncMetadataID)', instead")
public static func metadata(for primaryKey: PrimaryKey.QueryOutput) -> Where<SyncMetadata> {
SyncMetadata.where {
#sql(
"""
\($0.recordPrimaryKey) = \(PrimaryKey(queryOutput: primaryKey)) \
AND \($0.recordType) = \(bind: tableName)
"""
)
}
}
/// An identifier representing any associated synchronization metadata.
public var syncMetadataID: SyncMetadata.ID {
SyncMetadata.ID(
recordPrimaryKey: primaryKey.rawIdentifier,
recordType: Self.tableName
)
}
package static func recordName(for id: PrimaryKey.QueryOutput) -> String {
"\(id.rawIdentifier):\(tableName)"
}
var recordName: String {
Self.recordName(for: self[keyPath: Self.columns.primaryKey.keyPath])
}
}
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
extension PrimaryKeyedTableDefinition where PrimaryKey.QueryOutput: IdentifierStringConvertible {
/// A query expression for whether or not this row has associated sync metadata.
///
/// This helper can be useful when joining your tables to the ``SyncMetadata`` table:
///
/// ```swift
/// RemindersList
/// .leftJoin(SyncMetadata.all) { $0.hasMetadata.in($1) }
/// ```
@available(
*,
deprecated,
message: """
Join the 'SyncMetadata' table using 'SyncMetadata.id' and 'Table.syncMetadataID', instead.
"""
)
public func hasMetadata(in metadata: SyncMetadata.TableColumns) -> some QueryExpression<Bool> {
metadata.recordType.eq(QueryValue.tableName)
&& #sql("\(primaryKey)").eq(metadata.recordPrimaryKey)
}
/// An identifier representing any associated synchronization metadata.
///
/// This helper can be useful when joining your tables to the ``SyncMetadata`` table:
///
/// ```swift
/// RemindersList
/// .leftJoin(SyncMetadata.all) { $0.syncMetadataID.eq($1.id) }
/// ```
public var syncMetadataID: some QueryExpression<SyncMetadata.ID> {
#sql("\(primaryKey), \(bind: QueryValue.tableName)")
}
}
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
extension PrimaryKeyedTableDefinition {
var _recordName: some QueryExpression<String> {
#sql("\(primaryKey) || ':' || \(quote: QueryValue.tableName, delimiter: .text)")
}
}
#endif