diff --git a/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 5e9d9dc3..5c8d479d 100644 --- a/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "05461ecd2d6ddd848b677fd572ee263adc87ab3aebd38d91f3f4c49ddaf3cdde", + "originHash" : "80ce8831f89d2da19d6c4e6f30a71328a79a080602e0d57e255665812e2823d7", "pins" : [ { "identity" : "combine-schedulers", @@ -141,8 +141,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-structured-queries", "state" : { - "branch" : "main", - "revision" : "2d9f1d94f5cbfbd37c5ab0b2500b385db95516a3" + "revision" : "2468f4e34d909d11c053d773562c03ffea40a72e", + "version" : "0.12.1" } }, { diff --git a/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved.orig b/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved.orig new file mode 100644 index 00000000..23c487c1 --- /dev/null +++ b/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved.orig @@ -0,0 +1,181 @@ +{ +<<<<<<< HEAD + "originHash" : "80ce8831f89d2da19d6c4e6f30a71328a79a080602e0d57e255665812e2823d7", +======= + "originHash" : "05461ecd2d6ddd848b677fd572ee263adc87ab3aebd38d91f3f4c49ddaf3cdde", +>>>>>>> origin/cloudkit + "pins" : [ + { + "identity" : "combine-schedulers", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/combine-schedulers", + "state" : { + "revision" : "5928286acce13def418ec36d05a001a9641086f2", + "version" : "1.0.3" + } + }, + { + "identity" : "grdb.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/groue/GRDB.swift", + "state" : { + "revision" : "a5a1be26b4513dc7ec360eb56bc08a345bac6649", + "version" : "7.5.0" + } + }, + { + "identity" : "swift-case-paths", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-case-paths", + "state" : { + "revision" : "9810c8d6c2914de251e072312f01d3bf80071852", + "version" : "1.7.1" + } + }, + { + "identity" : "swift-clocks", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-clocks", + "state" : { + "revision" : "cc46202b53476d64e824e0b6612da09d84ffde8e", + "version" : "1.0.6" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections", + "state" : { + "revision" : "c1805596154bb3a265fd91b8ac0c4433b4348fb0", + "version" : "1.2.0" + } + }, + { + "identity" : "swift-concurrency-extras", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-concurrency-extras", + "state" : { + "revision" : "82a4ae7170d98d8538ec77238b7eb8e7199ef2e8", + "version" : "1.3.1" + } + }, + { + "identity" : "swift-custom-dump", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-custom-dump", + "state" : { + "revision" : "82645ec760917961cfa08c9c0c7104a57a0fa4b1", + "version" : "1.3.3" + } + }, + { + "identity" : "swift-dependencies", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-dependencies", + "state" : { + "revision" : "4c90d6b2b9bf0911af87b103bb40f41771891596", + "version" : "1.9.2" + } + }, + { + "identity" : "swift-docc-plugin", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-docc-plugin", + "state" : { + "revision" : "3e4f133a77e644a5812911a0513aeb7288b07d06", + "version" : "1.4.5" + } + }, + { + "identity" : "swift-docc-symbolkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swiftlang/swift-docc-symbolkit", + "state" : { + "revision" : "b45d1f2ed151d057b54504d653e0da5552844e34", + "version" : "1.0.0" + } + }, + { + "identity" : "swift-identified-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-identified-collections", + "state" : { + "revision" : "322d9ffeeba85c9f7c4984b39422ec7cc3c56597", + "version" : "1.1.1" + } + }, + { + "identity" : "swift-navigation", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-navigation", + "state" : { + "revision" : "ae208d1a5cf33aee1d43734ea780a09ada6e2a21", + "version" : "2.3.1" + } + }, + { + "identity" : "swift-perception", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-perception", + "state" : { + "revision" : "d924c62a70fca5f43872f286dbd7cef0957f1c01", + "version" : "1.6.0" + } + }, + { + "identity" : "swift-sharing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-sharing", + "state" : { + "revision" : "75e846ee3159dc75b3a29bfc24b6ce5a557ddca9", + "version" : "2.5.2" + } + }, + { + "identity" : "swift-snapshot-testing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-snapshot-testing", + "state" : { + "revision" : "37230a37e83f1b7023be08e1b1a2603fcb1567fb", + "version" : "1.18.4" + } + }, + { + "identity" : "swift-structured-queries", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-structured-queries", + "state" : { + "revision" : "2468f4e34d909d11c053d773562c03ffea40a72e", + "version" : "0.12.1" + } + }, + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swiftlang/swift-syntax", + "state" : { + "revision" : "f99ae8aa18f0cf0d53481901f88a0991dc3bd4a2", + "version" : "601.0.1" + } + }, + { + "identity" : "swift-tagged", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-tagged", + "state" : { + "revision" : "3907a9438f5b57d317001dc99f3f11b46882272b", + "version" : "0.10.0" + } + }, + { + "identity" : "xctest-dynamic-overlay", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", + "state" : { + "revision" : "39de59b2d47f7ef3ca88a039dff3084688fe27f4", + "version" : "1.5.2" + } + } + ], + "version" : 3 +} diff --git a/Examples/Reminders/ReminderForm.swift b/Examples/Reminders/ReminderForm.swift index e627a467..4fc55ec0 100644 --- a/Examples/Reminders/ReminderForm.swift +++ b/Examples/Reminders/ReminderForm.swift @@ -135,7 +135,7 @@ struct ReminderFormView: View { selectedTags = try await database.read { db in try Tag .order(by: \.title) - .join(ReminderTag.all) { $0.id.eq($1.tagID) } + .join(ReminderTag.all) { $0.primaryKey.eq($1.tagID) } .where { $1.reminderID.eq(reminderID) } .select { tag, _ in tag } .fetchAll(db) @@ -179,7 +179,7 @@ struct ReminderFormView: View { .execute(db) try ReminderTag.insert { selectedTags.map { tag in - ReminderTag.Draft(reminderID: reminderID, tagID: tag.id) + ReminderTag.Draft(reminderID: reminderID, tagID: tag.title) } } .execute(db) diff --git a/Examples/Reminders/RemindersDetail.swift b/Examples/Reminders/RemindersDetail.swift index 9565330a..ea9f2996 100644 --- a/Examples/Reminders/RemindersDetail.swift +++ b/Examples/Reminders/RemindersDetail.swift @@ -103,7 +103,7 @@ class RemindersDetailModel: HashableObject { case .flagged: reminder.isFlagged case .remindersList(let list): reminder.remindersListID.eq(list.id) case .scheduled: reminder.isScheduled - case .tags(let tags): tag.id.ifnull(UUID(0)).in(tags.map(\.id)) + case .tags(let tags): tag.title.ifnull("").in(tags.map(\.title)) case .today: reminder.isToday } } @@ -114,7 +114,7 @@ class RemindersDetailModel: HashableObject { remindersList: $3, isPastDue: $0.isPastDue, notes: $0.inlineNotes.substr(0, 200), - tags: #sql("\($2.jsonNames)") + tags: #sql("\($2.jsonTitles)") ) } } diff --git a/Examples/Reminders/RemindersLists.swift b/Examples/Reminders/RemindersLists.swift index 18b416d2..02d88a3c 100644 --- a/Examples/Reminders/RemindersLists.swift +++ b/Examples/Reminders/RemindersLists.swift @@ -124,10 +124,10 @@ class RemindersListsModel { func deleteTags(indexSet: IndexSet) { withErrorReporting { - let tagIDs = indexSet.map { tags[$0].id } + let tagTitles = indexSet.map { tags[$0].title } try database.write { db in try Tag - .where { $0.id.in(tagIDs) } + .where { $0.title.in(tagTitles) } .delete() .execute(db) } diff --git a/Examples/Reminders/Schema.swift b/Examples/Reminders/Schema.swift index 2edf2d31..c5b89c88 100644 --- a/Examples/Reminders/Schema.swift +++ b/Examples/Reminders/Schema.swift @@ -44,8 +44,9 @@ extension Reminder.Draft: Identifiable {} @Table struct Tag: Hashable, Identifiable { - let id: UUID - var title = "" + @Column(primaryKey: true) + var title: String + var id: String { title } } enum Priority: Int, Codable, QueryBindable { @@ -64,7 +65,7 @@ extension Reminder { } static let withTags = group(by: \.id) .leftJoin(ReminderTag.all) { $0.id.eq($1.reminderID) } - .leftJoin(Tag.all) { $1.tagID.eq($2.id) } + .leftJoin(Tag.all) { $1.tagID.eq($2.primaryKey) } } extension Reminder.TableColumns { @@ -85,13 +86,13 @@ extension Reminder.TableColumns { } extension Tag { - static let withReminders = group(by: \.id) - .leftJoin(ReminderTag.all) { $0.id.eq($1.tagID) } + static let withReminders = group(by: \.title) + .leftJoin(ReminderTag.all) { $0.primaryKey.eq($1.tagID) } .leftJoin(Reminder.all) { $1.reminderID.eq($2.id) } } extension Tag.TableColumns { - var jsonNames: some QueryExpression<[String].JSONRepresentation> { + var jsonTitles: some QueryExpression<[String].JSONRepresentation> { self.title.jsonGroupArray(filter: self.title.isNot(nil)) } } @@ -183,8 +184,7 @@ func appDatabase() throws -> any DatabaseWriter { try #sql( """ CREATE TABLE "tags" ( - "id" TEXT PRIMARY KEY NOT NULL ON CONFLICT REPLACE DEFAULT (uuid()), - "title" TEXT NOT NULL DEFAULT '' + "title" TEXT COLLATE NOCASE PRIMARY KEY NOT NULL ) STRICT """ ) @@ -193,11 +193,8 @@ func appDatabase() throws -> any DatabaseWriter { """ CREATE TABLE "remindersTags" ( "id" TEXT PRIMARY KEY NOT NULL ON CONFLICT REPLACE DEFAULT (uuid()), - "reminderID" TEXT NOT NULL, - "tagID" TEXT NOT NULL, - - FOREIGN KEY("reminderID") REFERENCES "reminders"("id") ON DELETE CASCADE, - FOREIGN KEY("tagID") REFERENCES "tags"("id") ON DELETE CASCADE + "reminderID" TEXT NOT NULL REFERENCES "reminders"("id") ON DELETE CASCADE, + "tagID" TEXT NOT NULL REFERENCES "tags"("title") ON DELETE CASCADE ON UPDATE CASCADE ) STRICT """ ) @@ -246,7 +243,6 @@ private let logger = Logger(subsystem: "Reminders", category: "Database") func seedSampleData() throws { let remindersListsIDs = (0...2).map { _ in UUID() } let remindersIDs = (0...10).map { _ in UUID() } - let tagsIDs = (0...6).map { _ in UUID() } try seed { RemindersList( id: remindersListsIDs[0], @@ -347,25 +343,25 @@ private let logger = Logger(subsystem: "Reminders", category: "Database") remindersListID: remindersListsIDs[2], title: "Prepare for WWDC" ) - Tag(id: tagsIDs[0], title: "car") - Tag(id: tagsIDs[1], title: "kids") - Tag(id: tagsIDs[2], title: "someday") - Tag(id: tagsIDs[3], title: "optional") - Tag(id: tagsIDs[4], title: "social") - Tag(id: tagsIDs[5], title: "night") - Tag(id: tagsIDs[6], title: "adulting") - ReminderTag.Draft(reminderID: remindersIDs[0], tagID: tagsIDs[2]) - ReminderTag.Draft(reminderID: remindersIDs[0], tagID: tagsIDs[3]) - ReminderTag.Draft(reminderID: remindersIDs[0], tagID: tagsIDs[6]) - ReminderTag.Draft(reminderID: remindersIDs[1], tagID: tagsIDs[2]) - ReminderTag.Draft(reminderID: remindersIDs[1], tagID: tagsIDs[3]) - ReminderTag.Draft(reminderID: remindersIDs[2], tagID: tagsIDs[6]) - ReminderTag.Draft(reminderID: remindersIDs[3], tagID: tagsIDs[0]) - ReminderTag.Draft(reminderID: remindersIDs[3], tagID: tagsIDs[1]) - ReminderTag.Draft(reminderID: remindersIDs[4], tagID: tagsIDs[4]) - ReminderTag.Draft(reminderID: remindersIDs[3], tagID: tagsIDs[4]) - ReminderTag.Draft(reminderID: remindersIDs[10], tagID: tagsIDs[4]) - ReminderTag.Draft(reminderID: remindersIDs[4], tagID: tagsIDs[5]) + Tag(title: "car") + Tag(title: "kids") + Tag(title: "someday") + Tag(title: "optional") + Tag(title: "social") + Tag(title: "night") + Tag(title: "adulting") + ReminderTag.Draft(reminderID: remindersIDs[0], tagID: "someday") + ReminderTag.Draft(reminderID: remindersIDs[0], tagID: "optional") + ReminderTag.Draft(reminderID: remindersIDs[0], tagID: "adulting") + ReminderTag.Draft(reminderID: remindersIDs[1], tagID: "someday") + ReminderTag.Draft(reminderID: remindersIDs[1], tagID: "optional") + ReminderTag.Draft(reminderID: remindersIDs[2], tagID: "adulting") + ReminderTag.Draft(reminderID: remindersIDs[3], tagID: "car") + ReminderTag.Draft(reminderID: remindersIDs[3], tagID: "kids") + ReminderTag.Draft(reminderID: remindersIDs[4], tagID: "social") + ReminderTag.Draft(reminderID: remindersIDs[3], tagID: "social") + ReminderTag.Draft(reminderID: remindersIDs[10], tagID: "social") + ReminderTag.Draft(reminderID: remindersIDs[4], tagID: "night") } } } diff --git a/Examples/Reminders/SearchReminders.swift b/Examples/Reminders/SearchReminders.swift index 0cc0ed5a..1c73ba6b 100644 --- a/Examples/Reminders/SearchReminders.swift +++ b/Examples/Reminders/SearchReminders.swift @@ -67,7 +67,7 @@ class SearchRemindersModel { notes: $0.inlineNotes, reminder: $0, remindersList: $3, - tags: #sql("\($2.jsonNames)") + tags: #sql("\($2.jsonTitles)") ) }, animation: .default diff --git a/Examples/Reminders/TagRow.swift b/Examples/Reminders/TagRow.swift index ffe38591..42b126f5 100644 --- a/Examples/Reminders/TagRow.swift +++ b/Examples/Reminders/TagRow.swift @@ -34,7 +34,7 @@ struct TagRow: View { #Preview { NavigationStack { List { - TagRow(tag: Tag(id: UUID(1), title: "optional")) + TagRow(tag: Tag(title: "optional")) } } } diff --git a/Examples/Reminders/TagsForm.swift b/Examples/Reminders/TagsForm.swift index 0c576c16..3545a977 100644 --- a/Examples/Reminders/TagsForm.swift +++ b/Examples/Reminders/TagsForm.swift @@ -4,12 +4,21 @@ import SwiftUI struct TagsView: View { @Fetch(Tags()) var tags = Tags.Value() @Binding var selectedTags: [Tag] + @State var editingTag: Tag.Draft? + @State var tagTitle = "" + @Dependency(\.defaultDatabase) var database @Environment(\.dismiss) var dismiss var body: some View { Form { let selectedTagIDs = Set(selectedTags.map(\.id)) + Section { + Button("New tag") { + tagTitle = "" + editingTag = Tag.Draft() + } + } if !tags.top.isEmpty { Section { ForEach(tags.top, id: \.id) { tag in @@ -18,6 +27,14 @@ struct TagsView: View { selectedTags: $selectedTags, tag: tag ) + .swipeActions { + Button("Delete", role: .destructive) { + deleteButtonTapped(tag: tag) + } + Button("Edit") { + editButtonTapped(tag: tag) + } + } } } header: { Text("Top tags") @@ -31,10 +48,26 @@ struct TagsView: View { selectedTags: $selectedTags, tag: tag ) + .swipeActions { + Button("Delete", role: .destructive) { + deleteButtonTapped(tag: tag) + } + Button("Edit") { + editButtonTapped(tag: tag) + } + } } } } } + .alert(item: $editingTag) { item in + Text(item.title == nil ? "New tag" : "Edit tag") + } actions: { item in + TextField("Tag name", text: $tagTitle) + Button("Save") { + saveButtonTapped() + } + } .toolbar { ToolbarItem { Button("Done") { dismiss() } @@ -43,6 +76,39 @@ struct TagsView: View { .navigationTitle(Text("Tags")) } + func deleteButtonTapped(tag: Tag) { + withErrorReporting { + try database.write { db in + try Tag.find(tag.title).delete().execute(db) + } + } + } + + func editButtonTapped(tag: Tag) { + tagTitle = tag.title + editingTag = Tag.Draft(tag) + } + + func saveButtonTapped() { + defer { tagTitle = "" } + let tag = Tag(title: tagTitle) + withErrorReporting { + try database.write { db in + if let existingTagTitle = editingTag?.title { + selectedTags.removeAll(where: { $0.title == existingTagTitle }) + try Tag + .update { $0.title = tagTitle } + .where { $0.title.eq(existingTagTitle) } + .execute(db) + } else { + try Tag.insert(or: .ignore) { tag } + .execute(db) + } + } + selectedTags.append(tag) + } + } + struct Tags: FetchKeyRequest { func fetch(_ db: Database) throws -> Value { let top = @@ -56,7 +122,7 @@ struct TagsView: View { let rest = try Tag - .where { !$0.id.in(top.map(\.id)) } + .where { !$0.title.in(top.map(\.title)) } .order(by: \.title) .fetchAll(db) diff --git a/Sources/SharingGRDBCore/CloudKit/SyncMetadata+MacroExpansion.swift b/Sources/SharingGRDBCore/CloudKit/SyncMetadata+MacroExpansion.swift index 3621ee08..d6928a25 100644 --- a/Sources/SharingGRDBCore/CloudKit/SyncMetadata+MacroExpansion.swift +++ b/Sources/SharingGRDBCore/CloudKit/SyncMetadata+MacroExpansion.swift @@ -36,7 +36,7 @@ public let lastKnownServerRecord = StructuredQueriesCore.TableColumn< QueryValue, CKRecord?.SystemFieldsRepresentation >("lastKnownServerRecord", keyPath: \QueryValue.lastKnownServerRecord) - public let _lastKnownServerRecordAllFields = StructuredQueriesCore.TableColumn< + package let _lastKnownServerRecordAllFields = StructuredQueriesCore.TableColumn< QueryValue, CKRecord?.AllFieldsRepresentation >("_lastKnownServerRecordAllFields", keyPath: \QueryValue._lastKnownServerRecordAllFields) public let share = StructuredQueriesCore.TableColumn<