From dc43b6298202e9f782cb232ecc0b2787a52051ec Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Sat, 6 Sep 2025 15:08:48 -0500 Subject: [PATCH 1/7] Improve tools for joining SyncMetadata table. --- Examples/Reminders/RemindersLists.swift | 4 +++- .../SQLiteData/CloudKit/SyncMetadata.swift | 21 +++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/Examples/Reminders/RemindersLists.swift b/Examples/Reminders/RemindersLists.swift index 71303b51..562fc55e 100644 --- a/Examples/Reminders/RemindersLists.swift +++ b/Examples/Reminders/RemindersLists.swift @@ -15,7 +15,9 @@ class RemindersListsModel { .leftJoin(Reminder.all) { $0.id.eq($1.remindersListID) && !$1.isCompleted } - .leftJoin(SyncMetadata.all) { $0.recordName.eq($2.recordName) } + .leftJoin(SyncMetadata.all) { + #sql("\($0.recordTypeAndPrimaryKey) = \($2.recordTypeAndPrimaryKey)") + } .select { ReminderListState.Columns( remindersCount: $1.id.count(), diff --git a/Sources/SQLiteData/CloudKit/SyncMetadata.swift b/Sources/SQLiteData/CloudKit/SyncMetadata.swift index 316aa9fc..83d2fb45 100644 --- a/Sources/SQLiteData/CloudKit/SyncMetadata.swift +++ b/Sources/SQLiteData/CloudKit/SyncMetadata.swift @@ -130,6 +130,13 @@ } } +@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) +extension SyncMetadata.TableColumns { + public var recordTypeAndPrimaryKey: some QueryExpression<(String, String)> { + #sql("(\(recordType), \(recordPrimaryKey))") + } +} + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) extension PrimaryKeyedTable where PrimaryKey: IdentifierStringConvertible { /// A query for finding the metadata associated with a record. @@ -163,8 +170,18 @@ @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) extension PrimaryKeyedTableDefinition where PrimaryKey.QueryOutput: IdentifierStringConvertible { - public var recordName: some QueryExpression { - _recordName + /// A query expression for the primary key as a string. + /// + /// This helper can be useful when joining your tables to the ``SyncMetadata`` table. It + /// allows you to join the `primaryKey` of your table to the ``SyncMetadata/recordPrimaryKey`` + /// column of ``SyncMetadata``: + /// + /// ```swift + /// RemindersList + /// .leftJoin(SyncMetadata.all) { $0.primaryKeyString.eq($1.recordPrimaryKey) } + /// ``` + public var recordTypeAndPrimaryKey: some QueryExpression<(String, String)> { + #sql("(\(QueryValue.tableName), \(primaryKey))") } } From db76cb671b9cb37462b0498458a85002e0d88475 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Sat, 6 Sep 2025 15:21:33 -0500 Subject: [PATCH 2/7] wip --- Sources/SQLiteData/Documentation.docc/Articles/CloudKit.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/SQLiteData/Documentation.docc/Articles/CloudKit.md b/Sources/SQLiteData/Documentation.docc/Articles/CloudKit.md index 9ef8274a..49817728 100644 --- a/Sources/SQLiteData/Documentation.docc/Articles/CloudKit.md +++ b/Sources/SQLiteData/Documentation.docc/Articles/CloudKit.md @@ -571,6 +571,7 @@ following: @FetchAll( RemindersList + // TODO: update this .leftJoin(SyncMetadata.all) { $0.recordName.eq($1.recordName) } .select { Row.Columns( From 994692760b0e7113d0332ca396d591635d90158a Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 8 Sep 2025 10:47:08 -0700 Subject: [PATCH 3/7] wip --- Examples/Reminders/RemindersLists.swift | 8 ++------ .../CloudKit/Internal/Metadatabase.swift | 14 ++++++------- .../SQLiteData/CloudKit/SyncMetadata.swift | 20 ++++++------------- .../Documentation.docc/Articles/CloudKit.md | 7 +++---- .../Documentation.docc/SQLiteData.md | 1 + 5 files changed, 19 insertions(+), 31 deletions(-) diff --git a/Examples/Reminders/RemindersLists.swift b/Examples/Reminders/RemindersLists.swift index 562fc55e..2eb3189a 100644 --- a/Examples/Reminders/RemindersLists.swift +++ b/Examples/Reminders/RemindersLists.swift @@ -12,12 +12,8 @@ class RemindersListsModel { RemindersList .group(by: \.id) .order(by: \.position) - .leftJoin(Reminder.all) { - $0.id.eq($1.remindersListID) && !$1.isCompleted - } - .leftJoin(SyncMetadata.all) { - #sql("\($0.recordTypeAndPrimaryKey) = \($2.recordTypeAndPrimaryKey)") - } + .leftJoin(Reminder.all) { $0.id.eq($1.remindersListID) && !$1.isCompleted } + .leftJoin(SyncMetadata.all) { $0.hasMetadata(in: $1) } .select { ReminderListState.Columns( remindersCount: $1.id.count(), diff --git a/Sources/SQLiteData/CloudKit/Internal/Metadatabase.swift b/Sources/SQLiteData/CloudKit/Internal/Metadatabase.swift index e1912ad7..2df3bb08 100644 --- a/Sources/SQLiteData/CloudKit/Internal/Metadatabase.swift +++ b/Sources/SQLiteData/CloudKit/Internal/Metadatabase.swift @@ -51,7 +51,7 @@ migrator.registerMigration("Create Metadata Tables") { db in try #sql( """ - CREATE TABLE IF NOT EXISTS "\(raw: .sqliteDataCloudKitSchemaName)_metadata" ( + CREATE TABLE "\(raw: .sqliteDataCloudKitSchemaName)_metadata" ( "recordPrimaryKey" TEXT NOT NULL, "recordType" TEXT NOT NULL, "recordName" TEXT NOT NULL AS ("recordPrimaryKey" || ':' || "recordType"), @@ -73,21 +73,21 @@ .execute(db) try #sql( """ - CREATE INDEX IF NOT EXISTS "\(raw: .sqliteDataCloudKitSchemaName)_metadata_parentRecordName" + CREATE INDEX "\(raw: .sqliteDataCloudKitSchemaName)_metadata_parentRecordName" ON "\(raw: .sqliteDataCloudKitSchemaName)_metadata"("parentRecordName") """ ) .execute(db) try #sql( """ - CREATE INDEX IF NOT EXISTS "\(raw: .sqliteDataCloudKitSchemaName)_metadata_isShared" + CREATE INDEX "\(raw: .sqliteDataCloudKitSchemaName)_metadata_isShared" ON "\(raw: .sqliteDataCloudKitSchemaName)_metadata"("isShared") """ ) .execute(db) try #sql( """ - CREATE TABLE IF NOT EXISTS "\(raw: .sqliteDataCloudKitSchemaName)_recordTypes" ( + CREATE TABLE "\(raw: .sqliteDataCloudKitSchemaName)_recordTypes" ( "tableName" TEXT NOT NULL PRIMARY KEY, "schema" TEXT NOT NULL, "tableInfo" TEXT NOT NULL @@ -97,7 +97,7 @@ .execute(db) try #sql( """ - CREATE TABLE IF NOT EXISTS "\(raw: .sqliteDataCloudKitSchemaName)_stateSerialization" ( + CREATE TABLE "\(raw: .sqliteDataCloudKitSchemaName)_stateSerialization" ( "scope" TEXT NOT NULL PRIMARY KEY, "data" TEXT NOT NULL ) STRICT @@ -106,7 +106,7 @@ .execute(db) try #sql( """ - CREATE TABLE IF NOT EXISTS "\(raw: .sqliteDataCloudKitSchemaName)_unsyncedRecordIDs" ( + CREATE TABLE "\(raw: .sqliteDataCloudKitSchemaName)_unsyncedRecordIDs" ( "recordName" TEXT NOT NULL, "zoneName" TEXT NOT NULL, "ownerName" TEXT NOT NULL, @@ -117,7 +117,7 @@ .execute(db) try #sql( """ - CREATE TABLE IF NOT EXISTS "\(raw: .sqliteDataCloudKitSchemaName)_pendingRecordZoneChanges" ( + CREATE TABLE "\(raw: .sqliteDataCloudKitSchemaName)_pendingRecordZoneChanges" ( "pendingRecordZoneChange" BLOB NOT NULL ) STRICT """ diff --git a/Sources/SQLiteData/CloudKit/SyncMetadata.swift b/Sources/SQLiteData/CloudKit/SyncMetadata.swift index 83d2fb45..962c3edd 100644 --- a/Sources/SQLiteData/CloudKit/SyncMetadata.swift +++ b/Sources/SQLiteData/CloudKit/SyncMetadata.swift @@ -130,13 +130,6 @@ } } -@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) -extension SyncMetadata.TableColumns { - public var recordTypeAndPrimaryKey: some QueryExpression<(String, String)> { - #sql("(\(recordType), \(recordPrimaryKey))") - } -} - @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) extension PrimaryKeyedTable where PrimaryKey: IdentifierStringConvertible { /// A query for finding the metadata associated with a record. @@ -170,18 +163,17 @@ extension SyncMetadata.TableColumns { @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) extension PrimaryKeyedTableDefinition where PrimaryKey.QueryOutput: IdentifierStringConvertible { - /// A query expression for the primary key as a string. + /// 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. It - /// allows you to join the `primaryKey` of your table to the ``SyncMetadata/recordPrimaryKey`` - /// column of ``SyncMetadata``: + /// This helper can be useful when joining your tables to the ``SyncMetadata`` table: /// /// ```swift /// RemindersList - /// .leftJoin(SyncMetadata.all) { $0.primaryKeyString.eq($1.recordPrimaryKey) } + /// .leftJoin(SyncMetadata.all) { $0.hasMetadata.in($1) } /// ``` - public var recordTypeAndPrimaryKey: some QueryExpression<(String, String)> { - #sql("(\(QueryValue.tableName), \(primaryKey))") + public func hasMetadata(in metadata: SyncMetadata.TableColumns) -> some QueryExpression { + metadata.recordName.eq(QueryValue.tableName) + && #sql("\(primaryKey)").eq(metadata.recordPrimaryKey) } } diff --git a/Sources/SQLiteData/Documentation.docc/Articles/CloudKit.md b/Sources/SQLiteData/Documentation.docc/Articles/CloudKit.md index 49817728..e9f1ead0 100644 --- a/Sources/SQLiteData/Documentation.docc/Articles/CloudKit.md +++ b/Sources/SQLiteData/Documentation.docc/Articles/CloudKit.md @@ -571,8 +571,7 @@ following: @FetchAll( RemindersList - // TODO: update this - .leftJoin(SyncMetadata.all) { $0.recordName.eq($1.recordName) } + .leftJoin(SyncMetadata.all) { $0.hasMetadata(in: $1) } .select { Row.Columns( remindersList: $0, @@ -583,8 +582,8 @@ following: var rows ``` -Here we have used the ``StructuredQueriesCore/PrimaryKeyedTableDefinition/recordName`` helper that -is defined on all primary key tables so that we can join ``SyncMetadata`` to `RemindersList`. +Here we have used the ``StructuredQueriesCore/PrimaryKeyedTableDefinition/hasMetadata(in:)`` helper +that is defined on all primary key tables so that we can join ``SyncMetadata`` to `RemindersList`.