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
2 changes: 1 addition & 1 deletion Examples/Reminders/RemindersListForm.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ struct RemindersListForm: View {
await withErrorReporting {
try await database.write { db in
let remindersListID =
try RemindersList
try RemindersList
.upsert { remindersList }
.returning(\.id)
.fetchOne(db)
Expand Down
3 changes: 1 addition & 2 deletions Examples/Reminders/Schema.swift
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ func appDatabase() throws -> any DatabaseWriter {
)
.execute(db)
}

try migrator.migrate(database)

try database.write { db in
Expand Down Expand Up @@ -542,4 +542,3 @@ nonisolated private let logger = Logger(subsystem: "Reminders", category: "Datab
}
}
#endif

9 changes: 5 additions & 4 deletions Examples/Reminders/SearchReminders.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,10 @@ class SearchRemindersModel {
try database.write { db in
try Reminder
.where {
$0.isCompleted && $0.id.in(
baseQuery(searchText: searchText, searchTokens: searchTokens).select { $1.id }
)
$0.isCompleted
&& $0.id.in(
baseQuery(searchText: searchText, searchTokens: searchTokens).select { $1.id }
)
}
.where {
if let monthsAgo {
Expand Down Expand Up @@ -264,7 +265,7 @@ struct SearchRemindersView: View {
}
}

nonisolated fileprivate func baseQuery(
nonisolated private func baseQuery(
searchText: String,
searchTokens: [SearchRemindersModel.Token]
) -> SelectOf<ReminderText, Reminder> {
Expand Down
8 changes: 4 additions & 4 deletions Examples/SyncUps/App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class AppModel {
private func bind() {
for destination in path {
switch destination {
case let .detail(detailModel):
case .detail(let detailModel):
bindDetail(model: detailModel)

case .meeting, .record:
Expand All @@ -64,11 +64,11 @@ struct AppView: View {
SyncUpsList(model: model.syncUpsList)
.navigationDestination(for: AppModel.Path.self) { path in
switch path {
case let .detail(model):
case .detail(let model):
SyncUpDetailView(model: model)
case let .meeting(meeting, attendees: attendees):
case .meeting(let meeting, attendees: let attendees):
MeetingView(meeting: meeting, attendees: attendees)
case let .record(model):
case .record(let model):
RecordMeetingView(model: model)
}
}
Expand Down
2 changes: 1 addition & 1 deletion Examples/SyncUps/Dependencies/SpeechClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ private actor Speech {
let speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "en-US"))!
recognitionTask = speechRecognizer.recognitionTask(with: request) { result, error in
switch (result, error) {
case let (.some(result), _):
case (.some(let result), _):
continuation.yield(SpeechRecognitionResult(result))
case (_, .some):
continuation.finish(throwing: error)
Expand Down
11 changes: 8 additions & 3 deletions Sources/SQLiteData/CloudKit/Internal/Triggers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,8 @@
zoneNameOverride = #sql("NULL")
ownerNameOverride = #sql("NULL")
}
return parentForeignKey
return
parentForeignKey
.map { foreignKey in
let parentRecordPrimaryKey = #sql(
#"\#(type(of: alias).QueryValue.self).\#(quote: foreignKey.from)"#,
Expand All @@ -385,8 +386,12 @@
return (
parentRecordPrimaryKey,
parentRecordType,
#sql("coalesce(\(zoneNameOverride), \($currentZoneName()), (\(parentMetadata.select(\.zoneName))))"),
#sql("coalesce(\(ownerNameOverride), \($currentOwnerName()), (\(parentMetadata.select(\.ownerName))))")
#sql(
"coalesce(\(zoneNameOverride), \($currentZoneName()), (\(parentMetadata.select(\.zoneName))))"
),
#sql(
"coalesce(\(ownerNameOverride), \($currentOwnerName()), (\(parentMetadata.select(\.ownerName))))"
)
)
}
?? (
Expand Down
21 changes: 12 additions & 9 deletions Sources/SQLiteData/CloudKit/SyncEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1735,16 +1735,19 @@
syncEngine.state.add(pendingRecordZoneChanges: [.deleteRecord(failedRecordID)])
break
case .networkFailure, .networkUnavailable, .zoneBusy, .serviceUnavailable,
.notAuthenticated, .operationCancelled, .internalError, .partialFailure,
.badContainer, .requestRateLimited, .missingEntitlement, .invalidArguments,
.resultsTruncated, .assetFileNotFound, .assetFileModified, .incompatibleVersion,
.constraintViolation, .changeTokenExpired, .badDatabase, .quotaExceeded,
.limitExceeded, .userDeletedZone, .tooManyParticipants, .alreadyShared,
.managedAccountRestricted, .participantMayNeedVerification, .serverResponseLost,
.assetNotAvailable, .accountTemporarilyUnavailable, .permissionFailure,
.unknownItem, .serverRecordChanged, .serverRejectedRequest, .zoneNotFound,
.participantAlreadyInvited:
.notAuthenticated, .operationCancelled, .internalError, .partialFailure,
.badContainer, .requestRateLimited, .missingEntitlement, .invalidArguments,
.resultsTruncated, .assetFileNotFound, .assetFileModified, .incompatibleVersion,
.constraintViolation, .changeTokenExpired, .badDatabase, .quotaExceeded,
.limitExceeded, .userDeletedZone, .tooManyParticipants, .alreadyShared,
.managedAccountRestricted, .participantMayNeedVerification, .serverResponseLost,
.assetNotAvailable, .accountTemporarilyUnavailable, .permissionFailure,
.unknownItem, .serverRecordChanged, .serverRejectedRequest, .zoneNotFound:
break
#if canImport(FoundationModels)
case .participantAlreadyInvited:
break
#endif
@unknown default:
break
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,9 @@ to specify that:
}
```

The library further requires your tables use globally unique identifiers (such as UUID) for their
primary keys, and in particular auto-incrementing integer IDs do _not_ work. You will need to
migrate your tables to use UUIDs, see
The library further requires your tables use globally unique identifiers (such as UUID) for their
primary keys, and in particular auto-incrementing integer IDs do _not_ work. You will need to
migrate your tables to use UUIDs, see
<doc:CloudKit#Preparing-an-existing-schema-for-synchronization> for more information.

[GRDB]: http://github.com/groue/GRDB.swift
12 changes: 6 additions & 6 deletions Sources/SQLiteData/Documentation.docc/Articles/CloudKit.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ CREATE TABLE "reminders"(
) STRICT
```

> Tip: See SQLite's documentation on [foreign keys](https://sqlite.org/foreignkeys.html) for more information.
> Tip: See SQLite's documentation on [foreign keys](https://sqlite.org/foreignkeys.html) for more information.

SQLiteData can synchronize many-to-one and many-to-many relationships to CloudKit,
and you can enforce foreign key constraints in your database connection. While it is possible for
Expand Down Expand Up @@ -706,19 +706,19 @@ And in previews you can use it like so:
If you have an existing app deployed to the app store using SQLite, then you may have to perform
a migration on your schema to prepare it for synchronization. The most important requirement
detailed above in <doc:CloudKit#Designing-your-schema-with-synchronization-in-mind> is that
all tables _must_ have a primary key, and all primary keys must be globally unique identifiers
all tables _must_ have a primary key, and all primary keys must be globally unique identifiers
such as UUID, and cannot be simple auto-incrementing integers.

The steps required to perform such a process are quite lengthy (the SQLite docs describe it in
The steps required to perform such a process are quite lengthy (the SQLite docs describe it in
[12 parts]), and those steps are easy to get wrong, which can either result in the migration
failing or your app accidentally corrupting your user's data.

SQLiteData provides a tool called ``SyncEngine/migratePrimaryKeys(_:tables:uuid:)`` that
SQLiteData provides a tool called ``SyncEngine/migratePrimaryKeys(_:tables:uuid:)`` that
makes it possible to perform this migration in just 2 steps:

* Update your Swift data types (then used annotated with `@Table`) to use UUID identifiers instead
of `Int`, and fix all of the resulting compiler errors in your features.
* Create a new migration and invoke ``SyncEngine/migratePrimaryKeys(_:tables:uuid:)`` with the
* Create a new migration and invoke ``SyncEngine/migratePrimaryKeys(_:tables:uuid:)`` with the
database handle from your migration and a list of all of your tables:

```swift
Expand All @@ -731,7 +731,7 @@ makes it possible to perform this migration in just 2 steps:
That will perform the many step process of migrating each table from integer-based primary keys
to UUIDs.

This migration tool tries to be conservative with its efforts so that if it ever detects a
This migration tool tries to be conservative with its efforts so that if it ever detects a
schema it does not know how to handle properly, it will throw an error. If this happens, then
you must migrate your tables manually using the introduces in <doc:ManuallyMigratingPrimaryKeys>.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ that all primary keys are UUIDs.
## Overview

If the [manual migration](<doc:SyncEngine/migratePrimaryKeys(_:tables:uuid:)>) tool provided
by this library does not work for you, then you will need to migrate your tables manually.
by this library does not work for you, then you will need to migrate your tables manually.
This consists of converting integer primary keys to UUIDs, and adding a primary key to all tables
that do not have one.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# Migrating to 1.4

SQLiteData 1.4 introduces a new tool for tying the lifecycle database subscriptions to the
lifecycle of the surrounding async context, but it may incidentally cause "Result of call …
lifecycle of the surrounding async context, but it may incidentally cause "Result of call …
is unused" warnings in your project.

## Overview

The `load` method defined on [`@FetchAll`](<doc:FetchAll>) / [`@FetchOne`](<doc:FetchOne>) /
[`@Fetch`](<doc:Fetch>) all now return a discardable result, ``FetchSubscription``. Awaiting the
``FetchSubscription/task`` of that result ties the lifecycle of the subscription to the database
to the lifecycle of the surrounding async context, which can help views to automatically
to the lifecycle of the surrounding async context, which can help views to automatically
unsubscribe from the database when they are not visible.

However, when used with `withErrorReporting` you are likely to get the following warning:
Expand Down
10 changes: 5 additions & 5 deletions Sources/SQLiteData/Documentation.docc/SQLiteData.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ synchronization.
## Overview

SQLiteData is a [fast](#Performance), lightweight replacement for SwiftData, supporting CloudKit
synchronization (and even CloudKit sharing), built on top of the popular
synchronization (and even CloudKit sharing), built on top of the popular
[GRDB](https://github.com/groue/GRDB.swift) library.

@Row {
Expand Down Expand Up @@ -178,8 +178,8 @@ a model context, via a property wrapper:
}
}

> Important: SQLiteData uses [GRDB](https://github.com/groue/GRDB.swift) under the hood for
> interacting with SQLite, and you will use its tools for creating transactions for writing
> Important: SQLiteData uses [GRDB](https://github.com/groue/GRDB.swift) under the hood for
> interacting with SQLite, and you will use its tools for creating transactions for writing
> to the database, such as the `database.write` method above.

For more information on how SQLiteData compares to SwiftData, see <doc:ComparisonWithSwiftData>.
Expand Down Expand Up @@ -248,8 +248,8 @@ for data and keep your views up-to-date when data in the database changes, and y
either using its type-safe, discoverable query building APIs, or using its `#sql` macro for writing
safe SQL strings.

Further, this library is built on the popular and battle-tested
[GRDB](https://github.com/groue/GRDB.swift) library for interacting with SQLite, such as executing
Further, this library is built on the popular and battle-tested
[GRDB](https://github.com/groue/GRDB.swift) library for interacting with SQLite, such as executing
queries and observing the database for changes.

## What is StructuredQueries?
Expand Down
14 changes: 7 additions & 7 deletions Sources/SQLiteData/Internal/FetchKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,9 @@ struct FetchKey<Value: Sendable>: SharedReaderKey {
}
scheduler.schedule {
switch result {
case let .success(value):
case .success(let value):
continuation.resume(returning: value)
case let .failure(error):
case .failure(let error):
continuation.resume(throwing: error)
}
}
Expand Down Expand Up @@ -126,16 +126,16 @@ struct FetchKey<Value: Sendable>: SharedReaderKey {
.dropFirst(dropFirst ? 1 : 0)
.sink { completion in
switch completion {
case let .failure(error):
case .failure(let error):
subscriber.yield(throwing: error)
case .finished:
break
}
} receiveValue: { newValue in
switch newValue {
case let .success(value):
case .success(let value):
subscriber.yield(value)
case let .failure(error):
case .failure(let error):
subscriber.yield(throwing: error)
}
}
Expand All @@ -147,9 +147,9 @@ struct FetchKey<Value: Sendable>: SharedReaderKey {
subscriber.yield(throwing: error)
} onChange: { newValue in
switch newValue {
case let .success(value):
case .success(let value):
subscriber.yield(value)
case let .failure(error):
case .failure(let error):
subscriber.yield(throwing: error)
}
}
Expand Down
20 changes: 10 additions & 10 deletions Sources/SQLiteData/StructuredQueries+GRDB/QueryCursor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -148,27 +148,27 @@ extension QueryBinding {
var databaseValue: DatabaseValue {
get throws {
switch self {
case let .blob(blob):
case .blob(let blob):
return Data(blob).databaseValue
case let .bool(bool):
case .bool(let bool):
return (bool ? 1 : 0).databaseValue
case let .date(date):
case .date(let date):
return date.iso8601String.databaseValue
case let .double(double):
case .double(let double):
return double.databaseValue
case let .int(int):
case .int(let int):
return int.databaseValue
case .null:
return .null
case let .text(text):
case .text(let text):
return text.databaseValue
case let .uint(uint) where uint <= UInt64(Int64.max):
case .uint(let uint) where uint <= UInt64(Int64.max):
return uint.databaseValue
case let .uint(uint):
case .uint(let uint):
throw Int64OverflowError(unsignedInteger: uint)
case let .uuid(uuid):
case .uuid(let uuid):
return uuid.uuidString.lowercased().databaseValue
case let .invalid(error):
case .invalid(let error):
throw error
}
}
Expand Down
8 changes: 6 additions & 2 deletions Tests/SQLiteDataTests/CloudKitTests/AtomicTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
for: RemindersList.recordID(for: 1)
)
remindersListRecord.setValue("My stuff", forKey: "title", at: 1)
let (saveResults, _) = try syncEngine.private.database.modifyRecords(saving: [remindersListRecord])
let (saveResults, _) = try syncEngine.private.database.modifyRecords(saving: [
remindersListRecord
])
#expect(saveResults.values.allSatisfy { $0.error == nil })

try await withDependencies {
Expand Down Expand Up @@ -120,7 +122,9 @@
for: RemindersList.recordID(for: 1)
)
remindersListRecord.setValue("My stuff", forKey: "title", at: 1)
let (saveResults, _) = try syncEngine.private.database.modifyRecords(saving: [remindersListRecord])
let (saveResults, _) = try syncEngine.private.database.modifyRecords(saving: [
remindersListRecord
])
#expect(saveResults.values.allSatisfy { $0.error == nil })

try await withDependencies {
Expand Down
10 changes: 7 additions & 3 deletions Tests/SQLiteDataTests/CloudKitTests/MockCloudDatabaseTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,9 @@
}

@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
@Test func deletingShareOwnedByCurrentUserDeletesShareAndDoesNotDeleteAssociatedData() async throws {
@Test func deletingShareOwnedByCurrentUserDeletesShareAndDoesNotDeleteAssociatedData()
async throws
{
let zone = syncEngine.defaultZone
_ = try syncEngine.private.database.modifyRecordZones(saving: [zone])

Expand Down Expand Up @@ -520,7 +522,9 @@
}

@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
@Test func deletingShareNotOwnedByCurrentUserDeletesOnlyShareAndNotAssociatedRecords() async throws {
@Test func deletingShareNotOwnedByCurrentUserDeletesOnlyShareAndNotAssociatedRecords()
async throws
{
let externalZone = CKRecordZone(
zoneID: CKRecordZone.ID(zoneName: "external.zone", ownerName: "external.owner")
)
Expand Down Expand Up @@ -640,7 +644,7 @@
#expect(
saveResults.compactMapValues { ($0.error as? CKError)?.code } == [
record1ID: .batchRequestFailed,
record2ID: .serverRecordChanged
record2ID: .serverRecordChanged,
]
)
}
Expand Down
Loading