Skip to content

Commit 6519f17

Browse files
committed
Public method for fetching metadata.
1 parent 91d9ac9 commit 6519f17

File tree

2 files changed

+153
-141
lines changed

2 files changed

+153
-141
lines changed
Lines changed: 139 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,149 +1,158 @@
11
#if canImport(CloudKit)
2-
import CloudKit
3-
4-
/// A table that tracks metadata related to synchronized data.
5-
///
6-
/// Each row of this table represents a synchronized record across all tables synchronized with
7-
/// CloudKit. This means that the sum of the count of rows across all synchronized tables in your
8-
/// application is the number of rows this one single table holds. However, this table is held
9-
/// in a database separate from your app's database.
10-
///
11-
///
12-
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
13-
// @Table("\(String.sqliteDataCloudKitSchemaName)_metadata")
14-
public struct SyncMetadata: Hashable, Sendable {
15-
/// The unique identifier of the record synchronized.
16-
public var recordPrimaryKey: String
17-
18-
/// The type of the record synchronized, _i.e._ its table name.
19-
public var recordType: String
20-
21-
/// The name of the record synchronized.
22-
///
23-
/// This field encodes both the table name and primary key of the record synchronized in
24-
/// the format "primaryKey:tableName", for example:
25-
///
26-
/// ```swift
27-
/// "8c4d1e4e-49b2-4f60-b6df-3c23881b87c6:reminders"
28-
/// ```
29-
// @Column(generated: .virtual)
30-
public let recordName: String
31-
32-
/// The unique identifier of this record's parent, if any.
33-
public var parentRecordPrimaryKey: String?
2+
import CloudKit
343

35-
/// The type of this record's parent, _i.e._ its table name, if any.
36-
public var parentRecordType: String?
37-
38-
/// The name of this record's parent, if any.
4+
/// A table that tracks metadata related to synchronized data.
395
///
40-
/// This field encodes both the table name and primary key of the parent record in the format
41-
/// "primaryKey:tableName", for example:
6+
/// Each row of this table represents a synchronized record across all tables synchronized with
7+
/// CloudKit. This means that the sum of the count of rows across all synchronized tables in your
8+
/// application is the number of rows this one single table holds. However, this table is held
9+
/// in a database separate from your app's database.
4210
///
43-
/// ```swift
44-
/// "d35e1f81-46e4-45d1-904b-2b7df1661e3e:remindersLists"
45-
/// ```
46-
// @Column(generated: .virtual)
47-
public let parentRecordName: String?
48-
49-
/// The last known `CKRecord` received from the server.
5011
///
51-
/// This record holds only the fields that are archived when using `encodeSystemFields(with:)`.
52-
// @Column(as: CKRecord?.SystemFieldsRepresentation.self)
53-
public var lastKnownServerRecord: CKRecord?
54-
55-
/// The last known `CKRecord` received from the server with all fields archived.
56-
// @Column(as: CKRecord?.SystemFieldsRepresentation.self)
57-
package var _lastKnownServerRecordAllFields: CKRecord?
58-
59-
/// The `CKShare` associated with this record, if it is shared.
60-
// @Column(as: CKShare?.SystemFieldsRepresentation.self)
61-
public var share: CKShare?
62-
63-
// @Column(generated: .virtual)
64-
public let isShared: Bool
65-
66-
/// The date the user last modified the record.
67-
public var userModificationDate: Date
68-
69-
package init(
70-
recordPrimaryKey: String,
71-
recordType: String,
72-
parentRecordPrimaryKey: String? = nil,
73-
parentRecordType: String? = nil,
74-
lastKnownServerRecord: CKRecord? = nil,
75-
_lastKnownServerRecordAllFields: CKRecord? = nil,
76-
share: CKShare? = nil,
77-
userModificationDate: Date
78-
) {
79-
self.recordPrimaryKey = recordPrimaryKey
80-
self.recordType = recordType
81-
self.recordName = "\(recordPrimaryKey):\(recordType)"
82-
self.parentRecordPrimaryKey = parentRecordPrimaryKey
83-
self.parentRecordType = parentRecordType
84-
if let parentRecordPrimaryKey, let parentRecordType {
85-
self.parentRecordName = "\(parentRecordPrimaryKey):\(parentRecordType)"
86-
} else {
87-
self.parentRecordName = nil
88-
}
89-
self.lastKnownServerRecord = lastKnownServerRecord
90-
self._lastKnownServerRecordAllFields = _lastKnownServerRecordAllFields
91-
self.share = share
92-
self.isShared = share != nil
93-
self.userModificationDate = userModificationDate
94-
}
12+
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
13+
// @Table("\(String.sqliteDataCloudKitSchemaName)_metadata")
14+
public struct SyncMetadata: Hashable, Sendable {
15+
/// The unique identifier of the record synchronized.
16+
public var recordPrimaryKey: String
17+
18+
/// The type of the record synchronized, _i.e._ its table name.
19+
public var recordType: String
20+
21+
/// The name of the record synchronized.
22+
///
23+
/// This field encodes both the table name and primary key of the record synchronized in
24+
/// the format "primaryKey:tableName", for example:
25+
///
26+
/// ```swift
27+
/// "8c4d1e4e-49b2-4f60-b6df-3c23881b87c6:reminders"
28+
/// ```
29+
// @Column(generated: .virtual)
30+
public let recordName: String
31+
32+
/// The unique identifier of this record's parent, if any.
33+
public var parentRecordPrimaryKey: String?
34+
35+
/// The type of this record's parent, _i.e._ its table name, if any.
36+
public var parentRecordType: String?
37+
38+
/// The name of this record's parent, if any.
39+
///
40+
/// This field encodes both the table name and primary key of the parent record in the format
41+
/// "primaryKey:tableName", for example:
42+
///
43+
/// ```swift
44+
/// "d35e1f81-46e4-45d1-904b-2b7df1661e3e:remindersLists"
45+
/// ```
46+
// @Column(generated: .virtual)
47+
public let parentRecordName: String?
48+
49+
/// The last known `CKRecord` received from the server.
50+
///
51+
/// This record holds only the fields that are archived when using `encodeSystemFields(with:)`.
52+
// @Column(as: CKRecord?.SystemFieldsRepresentation.self)
53+
public var lastKnownServerRecord: CKRecord?
9554

96-
// @Selection @Table
97-
struct AncestorMetadata {
98-
let recordName: String
99-
let parentRecordName: String?
55+
/// The last known `CKRecord` received from the server with all fields archived.
10056
// @Column(as: CKRecord?.SystemFieldsRepresentation.self)
101-
let lastKnownServerRecord: CKRecord?
57+
package var _lastKnownServerRecordAllFields: CKRecord?
58+
59+
/// The `CKShare` associated with this record, if it is shared.
60+
// @Column(as: CKShare?.SystemFieldsRepresentation.self)
61+
public var share: CKShare?
62+
63+
// @Column(generated: .virtual)
64+
public let isShared: Bool
65+
66+
/// The date the user last modified the record.
67+
public var userModificationDate: Date
68+
69+
package init(
70+
recordPrimaryKey: String,
71+
recordType: String,
72+
parentRecordPrimaryKey: String? = nil,
73+
parentRecordType: String? = nil,
74+
lastKnownServerRecord: CKRecord? = nil,
75+
_lastKnownServerRecordAllFields: CKRecord? = nil,
76+
share: CKShare? = nil,
77+
userModificationDate: Date
78+
) {
79+
self.recordPrimaryKey = recordPrimaryKey
80+
self.recordType = recordType
81+
self.recordName = "\(recordPrimaryKey):\(recordType)"
82+
self.parentRecordPrimaryKey = parentRecordPrimaryKey
83+
self.parentRecordType = parentRecordType
84+
if let parentRecordPrimaryKey, let parentRecordType {
85+
self.parentRecordName = "\(parentRecordPrimaryKey):\(parentRecordType)"
86+
} else {
87+
self.parentRecordName = nil
88+
}
89+
self.lastKnownServerRecord = lastKnownServerRecord
90+
self._lastKnownServerRecordAllFields = _lastKnownServerRecordAllFields
91+
self.share = share
92+
self.isShared = share != nil
93+
self.userModificationDate = userModificationDate
94+
}
95+
96+
// @Selection @Table
97+
struct AncestorMetadata {
98+
let recordName: String
99+
let parentRecordName: String?
100+
// @Column(as: CKRecord?.SystemFieldsRepresentation.self)
101+
let lastKnownServerRecord: CKRecord?
102+
}
102103
}
103-
}
104-
105-
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
106-
extension SyncMetadata {
107-
package static func find<T: PrimaryKeyedTable>(
108-
_ primaryKey: T.PrimaryKey.QueryOutput,
109-
table _: T.Type,
110-
) -> Where<Self> {
111-
Self.where {
112-
SQLQueryExpression(
113-
"""
114-
\($0.recordPrimaryKey) = \(T.PrimaryKey(queryOutput: primaryKey)) \
115-
AND \($0.recordType) = \(bind: T.tableName)
116-
"""
117-
)
104+
105+
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
106+
extension SyncMetadata {
107+
package static func find<T: PrimaryKeyedTable>(
108+
_ primaryKey: T.PrimaryKey.QueryOutput,
109+
table _: T.Type,
110+
)
111+
-> Where<Self>
112+
where T.PrimaryKey: IdentifierStringConvertible {
113+
T.metadata(for: primaryKey)
118114
}
119115
}
120-
}
121116

122-
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
123-
extension PrimaryKeyedTable where PrimaryKey.QueryOutput: IdentifierStringConvertible {
124-
/// Constructs a ``SyncMetadata/RecordName-swift.struct`` for a primary keyed table give an ID.
125-
///
126-
/// - Parameter id: The ID of the record.
127-
package static func recordName(for id: PrimaryKey.QueryOutput) -> String {
128-
"\(id.rawIdentifier):\(tableName)"
117+
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
118+
extension PrimaryKeyedTable where PrimaryKey: IdentifierStringConvertible {
119+
public static func metadata(for primaryKey: PrimaryKey.QueryOutput) -> Where<SyncMetadata> {
120+
SyncMetadata.where {
121+
SQLQueryExpression(
122+
"""
123+
\($0.recordPrimaryKey) = \(PrimaryKey(queryOutput: primaryKey)) \
124+
AND \($0.recordType) = \(bind: tableName)
125+
"""
126+
)
127+
}
128+
}
129129
}
130130

131-
var recordName: String {
132-
Self.recordName(for: self[keyPath: Self.columns.primaryKey.keyPath])
131+
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
132+
extension PrimaryKeyedTable where PrimaryKey.QueryOutput: IdentifierStringConvertible {
133+
/// Constructs a ``SyncMetadata/RecordName-swift.struct`` for a primary keyed table give an ID.
134+
///
135+
/// - Parameter id: The ID of the record.
136+
package static func recordName(for id: PrimaryKey.QueryOutput) -> String {
137+
"\(id.rawIdentifier):\(tableName)"
138+
}
139+
140+
var recordName: String {
141+
Self.recordName(for: self[keyPath: Self.columns.primaryKey.keyPath])
142+
}
133143
}
134-
}
135144

136-
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
137-
extension PrimaryKeyedTableDefinition where PrimaryKey.QueryOutput: IdentifierStringConvertible {
138-
public var recordName: some QueryExpression<String> {
139-
_recordName
145+
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
146+
extension PrimaryKeyedTableDefinition where PrimaryKey.QueryOutput: IdentifierStringConvertible {
147+
public var recordName: some QueryExpression<String> {
148+
_recordName
149+
}
140150
}
141-
}
142151

143-
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
144-
extension PrimaryKeyedTableDefinition {
145-
var _recordName: some QueryExpression<String> {
146-
SQLQueryExpression("\(primaryKey) || ':' || \(quote: QueryValue.tableName, delimiter: .text)")
152+
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
153+
extension PrimaryKeyedTableDefinition {
154+
var _recordName: some QueryExpression<String> {
155+
SQLQueryExpression("\(primaryKey) || ':' || \(quote: QueryValue.tableName, delimiter: .text)")
156+
}
147157
}
148-
}
149158
#endif

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

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -500,21 +500,25 @@ exposed for you to query it in whichever way you want.
500500
to attach the metadatabase to your database connection. This can be done with the
501501
``GRDB/Database/attachMetadatabase(containerIdentifier:)`` method defined on `Database`.
502502

503+
With that done you can use the ``StructuredQueriesCore/PrimaryKeyedTable/metadata(for:)`` method
504+
to construct a SQL query for fetching the meta data associated with one of your records.
505+
503506
For example, if you want to retrieve the `CKRecord` that is associated with a particular row in
504507
one of your tables, say a reminder, then you can use ``SyncMetadata/lastKnownServerRecord`` to
505508
retreive the `CKRecord` and then invoke a CloudKit database function to retreive all of the details:
506509

507510
```swift
508-
let metadata = try database.read { db in
509-
try SyncMetadata
510-
.find(RemindersList.recordName(for: remindersListID))
511+
let lastKnownServerRecord = try database.read { db in
512+
try RemindersList
513+
.metadata(for: remindersListID)
514+
.select(\.lastKnownServerRecord)
511515
.fetchOne(db)
512516
}
513-
guard let metadata
517+
guard let lastKnownServerRecord
514518
else { return }
515519

516520
let ckRecord = try await container.privateCloudDatabase
517-
.record(for: metadata.lastKnownServerRecord.recordID)
521+
.record(for: lastKnownServerRecord.recordID)
518522
```
519523

520524
> Important: In the above snippet we are explicitly using `privateCloudDatabase`, but that is
@@ -530,14 +534,13 @@ It is also possible to fetch the `CKShare` associated with a record if it has be
530534
will give you access to the most current list of paricipants and permissions for the shared record:
531535

532536
```swift
533-
let metadata = try database.read { db in
534-
try SyncMetadata
535-
.find(RemindersList.recordName(for: remindersListID))
537+
let share = try database.read { db in
538+
try RemindersList
539+
.metadata(for: remindersListID)
540+
.select(\.share)
536541
.fetchOne(db)
537542
}
538-
guard
539-
let metadata,
540-
let share = metadata.share
543+
guard let share
541544
else { return }
542545

543546
let ckRecord = try await container.sharedCloudDatabase

0 commit comments

Comments
 (0)