Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Examples/Reminders/RemindersLists.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class RemindersListsModel {
.group(by: \.id)
.order(by: \.position)
.leftJoin(Reminder.all) { $0.id.eq($1.remindersListID) && !$1.isCompleted }
.leftJoin(SyncMetadata.all) { $0.hasMetadata(in: $2) }
.leftJoin(SyncMetadata.all) { $0.syncMetadataID.eq($2.id) }
.select {
ReminderListState.Columns(
remindersCount: $1.id.count(),
Expand Down
102 changes: 86 additions & 16 deletions Sources/SQLiteData/CloudKit/SyncMetadata.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,25 @@
/// 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, Sendable {
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 var recordPrimaryKey: String

/// The type of the record synchronized, _i.e._ its table name.
public var 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
public var recordPrimaryKey: String { id.recordPrimaryKey }

/// The type of the record synchronized, _i.e._ its table name.
public var recordType: String
public var recordType: String { id.recordType }

/// The record zone name.
public var zoneName: String
Expand All @@ -35,11 +48,25 @@
@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 var parentRecordPrimaryKey: String

/// The type of the parent record synchronized, _i.e._ its table name.
public var parentRecordType: String
}

/// The identifier and type of this record's parent, if any.
public var parentRecordID: ParentID?

/// The unique identifier of this record's parent, if any.
public var parentRecordPrimaryKey: String?
public var parentRecordPrimaryKey: String? { parentRecordID?.parentRecordPrimaryKey }

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

/// The name of this record's parent, if any.
///
Expand Down Expand Up @@ -85,6 +112,25 @@
public var userModificationTime: Int64
}

@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
}
}

@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
extension SyncMetadata {
package init(
Expand All @@ -99,16 +145,18 @@
share: CKShare? = nil,
userModificationTime: Int64
) {
self.recordPrimaryKey = recordPrimaryKey
self.recordType = recordType
self.id = ID(recordPrimaryKey: recordPrimaryKey, recordType: recordType)
self.recordName = "\(recordPrimaryKey):\(recordType)"
self.zoneName = zoneName
self.ownerName = ownerName
self.parentRecordPrimaryKey = parentRecordPrimaryKey
self.parentRecordType = parentRecordType
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
Expand Down Expand Up @@ -139,10 +187,11 @@
}

@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
extension PrimaryKeyedTable where PrimaryKey: IdentifierStringConvertible {
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(
Expand All @@ -153,13 +202,15 @@
)
}
}
}

@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
extension PrimaryKeyedTable where PrimaryKey.QueryOutput: IdentifierStringConvertible {
/// Constructs a ``SyncMetadata/RecordName-swift.struct`` for a primary keyed table give an ID.
///
/// - Parameter id: The ID of the record.
/// 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)"
}
Expand All @@ -179,10 +230,29 @@
/// 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, *)
Expand Down
14 changes: 7 additions & 7 deletions Sources/SQLiteData/Documentation.docc/Articles/CloudKit.md
Original file line number Diff line number Diff line change
Expand Up @@ -541,17 +541,17 @@ to attach the metadatabase to your database connection. This can be done with th
``GRDB/Database/attachMetadatabase(containerIdentifier:)`` method defined on `Database`. See
<doc:CloudKit#Setting-up-a-SyncEngine> for more information on how to do this.

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

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

```swift
let lastKnownServerRecord = try database.read { db in
try RemindersList
.metadata(for: remindersListID)
try SyncMetadata
.find(remindersList.syncMetadataID)
.select(\.lastKnownServerRecord)
.fetchOne(db)
?? nil
Expand All @@ -578,7 +578,7 @@ will give you access to the most current list of participants and permissions fo
```swift
let share = try database.read { db in
try RemindersList
.metadata(for: remindersListID)
.find(remindersList.syncMetadataID)
.select(\.share)
.fetchOne(db)
}
Expand Down Expand Up @@ -606,7 +606,7 @@ following:

@FetchAll(
RemindersList
.leftJoin(SyncMetadata.all) { $0.hasMetadata(in: $1) }
.leftJoin(SyncMetadata.all) { $0.syncMetadataID.eq($1.id) }
.select {
Row.Columns(
remindersList: $0,
Expand All @@ -617,7 +617,7 @@ following:
var rows
```

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

<!--
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -393,8 +393,8 @@ select the ``SyncMetadata/share`` value:

```swift
let share = try await database.read { db in
RemindersList
.metadata(for: id)
SyncMetadata
.find(remindersList.syncMetadataID)
.select(\.share)
.fetchOne(db)
?? nil
Expand Down
1 change: 1 addition & 0 deletions Sources/SQLiteData/Documentation.docc/SQLiteData.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,5 +317,6 @@ with SQLite to take full advantage of GRDB and SQLiteData.
- ``Dependencies/DependencyValues/defaultSyncEngine``
- ``IdentifierStringConvertible``
- ``SyncMetadata``
- ``StructuredQueriesCore/PrimaryKeyedTableDefinition/syncMetadataID``
- ``StructuredQueriesCore/PrimaryKeyedTableDefinition/hasMetadata(in:)``
- ``SharedRecord``
Loading