diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c2d594e5..c3bb21ef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,8 @@ jobs: - uses: actions/checkout@v4 - name: Select Xcode ${{ matrix.xcode }} run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app + - name: List devices available + run: xcrun simctl list --json devices available 'iPhone' - name: xcodebuild ${{ matrix.scheme }} run: make DERIVED_DATA_PATH=~/.derivedData SCHEME="${{ matrix.scheme }}" xcodebuild-raw - name: Output test failures diff --git a/Examples/Reminders/RemindersLists.swift b/Examples/Reminders/RemindersLists.swift index 71303b51..56865fc2 100644 --- a/Examples/Reminders/RemindersLists.swift +++ b/Examples/Reminders/RemindersLists.swift @@ -12,10 +12,8 @@ class RemindersListsModel { RemindersList .group(by: \.id) .order(by: \.position) - .leftJoin(Reminder.all) { - $0.id.eq($1.remindersListID) && !$1.isCompleted - } - .leftJoin(SyncMetadata.all) { $0.recordName.eq($2.recordName) } + .leftJoin(Reminder.all) { $0.id.eq($1.remindersListID) && !$1.isCompleted } + .leftJoin(SyncMetadata.all) { $0.hasMetadata(in: $2) } .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 316aa9fc..ce79f912 100644 --- a/Sources/SQLiteData/CloudKit/SyncMetadata.swift +++ b/Sources/SQLiteData/CloudKit/SyncMetadata.swift @@ -163,8 +163,17 @@ @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 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) } + /// ``` + public func hasMetadata(in metadata: SyncMetadata.TableColumns) -> some QueryExpression { + metadata.recordType.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 9ef8274a..e9f1ead0 100644 --- a/Sources/SQLiteData/Documentation.docc/Articles/CloudKit.md +++ b/Sources/SQLiteData/Documentation.docc/Articles/CloudKit.md @@ -571,7 +571,7 @@ following: @FetchAll( RemindersList - .leftJoin(SyncMetadata.all) { $0.recordName.eq($1.recordName) } + .leftJoin(SyncMetadata.all) { $0.hasMetadata(in: $1) } .select { Row.Columns( remindersList: $0, @@ -582,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`.