From 81722bd7c9e1b3f35ba142880879359182c5d8c2 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 8 Jul 2026 11:56:52 -0700 Subject: [PATCH 1/3] Add `StrictDecoding` trait Adopts strict decoding introduced in StructuredQueries, ensuring that type mismatches are better caught. By default, decoding is lenient but will begin to emit runtime warnings so that developers can resolve existing issues in their code base. Once migrated, folks can enable the `StrictDecoding` trait for harder failure, and in a 2.0 major release strict decoding will likely be the default. --- Package.resolved | 26 +++--- Package.swift | 10 ++- .../CustomFunctions.swift | 15 +++- .../StructuredQueries+GRDB/QueryCursor.swift | 49 +++++++---- .../SQLiteFunctionDecoder.swift | 76 +++++++++++++++-- .../SQLiteQueryDecoder.swift | 83 ++++++++++++++++++- .../CloudKitTests/SchemaChangeTests.swift | 6 +- .../SyncEngineDelegateTests.swift | 2 +- 8 files changed, 219 insertions(+), 48 deletions(-) diff --git a/Package.resolved b/Package.resolved index b0538ea6..fda925a4 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "e2cc6c054d5cb45b58ddae48858b3338ce4fc9283457edb40b284849c3914ed5", + "originHash" : "208b608351bdaad231682c75942344a5e93cbd7d4e2d03ef90c0a0623cb0bf68", "pins" : [ { "identity" : "combine-schedulers", @@ -15,8 +15,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/groue/GRDB.swift", "state" : { - "revision" : "9ed8c8457e00ff9c7aedb3bf213f20a2cfdf509e", - "version" : "7.11.0" + "revision" : "b83108d10f42680d78f23fe4d4d80fc88dab3212", + "version" : "7.11.1" } }, { @@ -51,8 +51,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-custom-dump", "state" : { - "revision" : "b9b59eb58c946236d6f16305c576ad194c36444e", - "version" : "1.6.0" + "revision" : "a8cd6c976f335ed361dcecddb0dc39ebda51bc3e", + "version" : "1.6.1" } }, { @@ -60,8 +60,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-dependencies", "state" : { - "revision" : "f80552807ec92f72fe3fe4543d71879182b0bfd5", - "version" : "1.13.0" + "revision" : "8dc1fbf2f6255a73dec53b4648164884898db4c5", + "version" : "1.14.1" } }, { @@ -105,8 +105,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-sharing", "state" : { - "revision" : "e47a2f545bafa3c0c702600f3e6ce02b3d566b6f", - "version" : "2.8.2" + "revision" : "8244fe63bf43e58188ab13851ad693eecf6a9e90", + "version" : "2.9.1" } }, { @@ -123,8 +123,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-structured-queries", "state" : { - "revision" : "859958c7cf76918abf5aa5d6ce8b068e00a77eb2", - "version" : "0.32.0" + "branch" : "strict-decoding", + "revision" : "eb47155b76011b4f1c77637e9efb51c69ef0083e" } }, { @@ -141,8 +141,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", "state" : { - "revision" : "cb281f343fd953280336dcbd3822cdf47c182f5b", - "version" : "1.10.0" + "revision" : "401bf70d95bfe8db2a1dc619f9e175a85c089321", + "version" : "1.10.1" } } ], diff --git a/Package.swift b/Package.swift index 837ac528..b0834f03 100644 --- a/Package.swift +++ b/Package.swift @@ -22,6 +22,13 @@ let package = Package( ), ], traits: [ + .trait( + name: "StrictDecoding", + description: """ + Throw an error, rather than coerce, when decoding a column whose storage type does not \ + match the expected type. + """ + ), .trait( name: "CasePaths", description: "Introduce support for enum tables." @@ -47,7 +54,8 @@ let package = Package( .package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.18.4"), .package( url: "https://github.com/pointfreeco/swift-structured-queries", - from: "0.32.0", + branch: "strict-decoding", +// from: "0.32.0", traits: [ .trait(name: "CasePaths", condition: .when(traits: ["CasePaths"])), .trait(name: "Tagged", condition: .when(traits: ["Tagged"])), diff --git a/Sources/SQLiteData/StructuredQueries+GRDB/CustomFunctions.swift b/Sources/SQLiteData/StructuredQueries+GRDB/CustomFunctions.swift index 45ef042b..81ceece1 100644 --- a/Sources/SQLiteData/StructuredQueries+GRDB/CustomFunctions.swift +++ b/Sources/SQLiteData/StructuredQueries+GRDB/CustomFunctions.swift @@ -20,11 +20,16 @@ extension Database { Unmanaged.passRetained(ScalarDatabaseFunctionDefinition(function)).toOpaque(), { context, argumentCount, arguments in do { - var decoder = SQLiteFunctionDecoder(argumentCount: argumentCount, arguments: arguments) - try Unmanaged + let function = Unmanaged .fromOpaque(sqlite3_user_data(context)) .takeUnretainedValue() .function + var decoder = SQLiteFunctionDecoder( + name: function.name, + argumentCount: argumentCount, + arguments: arguments + ) + try function .invoke(&decoder) .result(db: context) } catch { @@ -53,8 +58,12 @@ extension Database { body, nil, { context, argumentCount, arguments in - var decoder = SQLiteFunctionDecoder(argumentCount: argumentCount, arguments: arguments) let function = AggregateDatabaseFunctionContext[context].takeUnretainedValue() + var decoder = SQLiteFunctionDecoder( + name: function.iterator.body.name, + argumentCount: argumentCount, + arguments: arguments + ) do { try function.iterator.step(&decoder) } catch { diff --git a/Sources/SQLiteData/StructuredQueries+GRDB/QueryCursor.swift b/Sources/SQLiteData/StructuredQueries+GRDB/QueryCursor.swift index 587365dc..e195b410 100644 --- a/Sources/SQLiteData/StructuredQueries+GRDB/QueryCursor.swift +++ b/Sources/SQLiteData/StructuredQueries+GRDB/QueryCursor.swift @@ -30,24 +30,51 @@ public class QueryCursor: DatabaseCursor { struct DecodingError: Error, CustomStringConvertible { let columnIndex: Int let columnName: String + let reason: String let sql: String @usableFromInline - init(columnIndex: Int, columnName: String, sql: String) { + init(columnIndex: Int, columnName: String, reason: String, sql: String) { self.columnIndex = columnIndex self.columnName = columnName + self.reason = reason self.sql = sql } @usableFromInline var description: String { """ - Expected column \(columnIndex) (\(columnName.debugDescription)) to not be NULL: … + Expected column \(columnIndex) (\(columnName.debugDescription)) \(reason): ... \(sql) """ } } + + @usableFromInline + func missingRequiredColumnError() -> DecodingError { + let columnIndex = Int(decoder.currentIndex) - 1 + return DecodingError( + columnIndex: columnIndex, + columnName: _statement.columnNames[columnIndex], + reason: "to not be NULL", + sql: _statement.sql + ) + } + + @usableFromInline + func typeMismatchError(_ columnType: Any.Type) -> DecodingError { + let columnIndex = Int(decoder.currentIndex) + let storageClass = storageClassName( + sqlite3_column_type(_statement.sqliteStatement, Int32(columnIndex)) + ) + return DecodingError( + columnIndex: columnIndex, + columnName: _statement.columnNames[columnIndex], + reason: "to decode \(columnType), but found \(storageClass)", + sql: _statement.sql + ) + } } @usableFromInline @@ -68,12 +95,9 @@ final class QueryValueCursor: QueryCursor?) { + init(name: String, argumentCount: Int32, arguments: UnsafeMutablePointer?) { + self.name = name self.argumentCount = argumentCount self.arguments = arguments } @@ -26,10 +34,18 @@ struct SQLiteFunctionDecoder: QueryDecoder { @inlinable mutating func decode(_ columnType: [UInt8].Type) throws -> [UInt8]? { - defer { currentIndex += 1 } precondition(argumentCount > currentIndex) let value = arguments?[Int(currentIndex)] - guard sqlite3_value_type(value) != SQLITE_NULL else { return nil } + switch sqlite3_value_type(value) { + case SQLITE_NULL: + currentIndex += 1 + return nil + case SQLITE_BLOB: + break + default: + try reportTypeMismatch([UInt8].self) + } + defer { currentIndex += 1 } if let blob = sqlite3_value_blob(value) { let count = Int(sqlite3_value_bytes(value)) let buffer = UnsafeRawBufferPointer(start: blob, count: count) @@ -52,10 +68,18 @@ struct SQLiteFunctionDecoder: QueryDecoder { @inlinable mutating func decode(_ columnType: Double.Type) throws -> Double? { - defer { currentIndex += 1 } precondition(argumentCount > currentIndex) let value = arguments?[Int(currentIndex)] - guard sqlite3_value_type(value) != SQLITE_NULL else { return nil } + switch sqlite3_value_type(value) { + case SQLITE_NULL: + currentIndex += 1 + return nil + case SQLITE_FLOAT: + break + default: + try reportTypeMismatch(Double.self) + } + defer { currentIndex += 1 } return sqlite3_value_double(value) } @@ -66,19 +90,35 @@ struct SQLiteFunctionDecoder: QueryDecoder { @inlinable mutating func decode(_ columnType: Int64.Type) throws -> Int64? { - defer { currentIndex += 1 } precondition(argumentCount > currentIndex) let value = arguments?[Int(currentIndex)] - guard sqlite3_value_type(value) != SQLITE_NULL else { return nil } + switch sqlite3_value_type(value) { + case SQLITE_NULL: + currentIndex += 1 + return nil + case SQLITE_INTEGER: + break + default: + try reportTypeMismatch(Int64.self) + } + defer { currentIndex += 1 } return sqlite3_value_int64(value) } @inlinable mutating func decode(_ columnType: String.Type) throws -> String? { - defer { currentIndex += 1 } precondition(argumentCount > currentIndex) let value = arguments?[Int(currentIndex)] - guard sqlite3_value_type(value) != SQLITE_NULL else { return nil } + switch sqlite3_value_type(value) { + case SQLITE_NULL: + currentIndex += 1 + return nil + case SQLITE_TEXT: + break + default: + try reportTypeMismatch(String.self) + } + defer { currentIndex += 1 } return String(cString: sqlite3_value_text(value)) } @@ -94,4 +134,22 @@ struct SQLiteFunctionDecoder: QueryDecoder { guard let uuidString = try decode(String.self) else { return nil } return UUID(uuidString: uuidString) } + + @usableFromInline + func reportTypeMismatch(_ columnType: Any.Type) throws { + #if StrictDecoding + throw QueryDecodingError.typeMismatch(columnType) + #else + let key = "\(currentIndex)|\(name)" + guard reportedTypeMismatches.withValue({ $0.insert(key).inserted }) + else { return } + let value = arguments?[Int(currentIndex)] + reportIssue( + """ + Expected argument \(currentIndex) of \(name.debugDescription) to decode \(columnType), \ + but found \(storageClassName(sqlite3_value_type(value))) + """ + ) + #endif + } } diff --git a/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteQueryDecoder.swift b/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteQueryDecoder.swift index 8e926e0f..76344e91 100644 --- a/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteQueryDecoder.swift +++ b/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteQueryDecoder.swift @@ -2,6 +2,11 @@ public import Foundation public import GRDBSQLite public import StructuredQueriesCore +#if !StrictDecoding + import ConcurrencyExtras + import IssueReporting +#endif + @usableFromInline struct SQLiteQueryDecoder: QueryDecoder { @usableFromInline @@ -22,8 +27,16 @@ struct SQLiteQueryDecoder: QueryDecoder { @inlinable mutating func decode(_ columnType: [UInt8].Type) throws -> [UInt8]? { + switch sqlite3_column_type(statement, currentIndex) { + case SQLITE_NULL: + currentIndex += 1 + return nil + case SQLITE_BLOB: + break + default: + try reportTypeMismatch([UInt8].self) + } defer { currentIndex += 1 } - guard sqlite3_column_type(statement, currentIndex) != SQLITE_NULL else { return nil } return [UInt8]( UnsafeRawBufferPointer( start: sqlite3_column_blob(statement, currentIndex), @@ -44,8 +57,16 @@ struct SQLiteQueryDecoder: QueryDecoder { @inlinable mutating func decode(_ columnType: Double.Type) throws -> Double? { + switch sqlite3_column_type(statement, currentIndex) { + case SQLITE_NULL: + currentIndex += 1 + return nil + case SQLITE_FLOAT: + break + default: + try reportTypeMismatch(Double.self) + } defer { currentIndex += 1 } - guard sqlite3_column_type(statement, currentIndex) != SQLITE_NULL else { return nil } return sqlite3_column_double(statement, currentIndex) } @@ -56,15 +77,31 @@ struct SQLiteQueryDecoder: QueryDecoder { @inlinable mutating func decode(_ columnType: Int64.Type) throws -> Int64? { + switch sqlite3_column_type(statement, currentIndex) { + case SQLITE_NULL: + currentIndex += 1 + return nil + case SQLITE_INTEGER: + break + default: + try reportTypeMismatch(Int64.self) + } defer { currentIndex += 1 } - guard sqlite3_column_type(statement, currentIndex) != SQLITE_NULL else { return nil } return sqlite3_column_int64(statement, currentIndex) } @inlinable mutating func decode(_ columnType: String.Type) throws -> String? { + switch sqlite3_column_type(statement, currentIndex) { + case SQLITE_NULL: + currentIndex += 1 + return nil + case SQLITE_TEXT: + break + default: + try reportTypeMismatch(String.self) + } defer { currentIndex += 1 } - guard sqlite3_column_type(statement, currentIndex) != SQLITE_NULL else { return nil } return String(cString: sqlite3_column_text(statement, currentIndex)) } @@ -81,6 +118,44 @@ struct SQLiteQueryDecoder: QueryDecoder { guard let uuid = UUID(uuidString: uuidString) else { throw InvalidUUID() } return uuid } + + @usableFromInline + func reportTypeMismatch(_ columnType: Any.Type) throws { + #if StrictDecoding + throw QueryDecodingError.typeMismatch(columnType) + #else + let sql = sqlite3_sql(statement).map { String(cString: $0) } ?? "" + let key = "\(currentIndex)|\(sql)" + guard reportedTypeMismatches.withValue({ $0.insert(key).inserted }) + else { return } + let columnName = sqlite3_column_name(statement, currentIndex).map { String(cString: $0) } + reportIssue( + """ + Expected column \(currentIndex) (\((columnName ?? "").debugDescription)) to decode \ + \(columnType), but found \ + \(storageClassName(sqlite3_column_type(statement, currentIndex))): ... + + \(sql) + """ + ) + #endif + } +} + +#if !StrictDecoding + let reportedTypeMismatches = LockIsolated>([]) +#endif + +@usableFromInline +func storageClassName(_ type: Int32) -> String { + switch type { + case SQLITE_BLOB: "BLOB" + case SQLITE_FLOAT: "REAL" + case SQLITE_INTEGER: "INTEGER" + case SQLITE_TEXT: "TEXT" + case SQLITE_NULL: "NULL" + default: "unknown storage class \(type)" + } } @usableFromInline diff --git a/Tests/SQLiteDataTests/CloudKitTests/SchemaChangeTests.swift b/Tests/SQLiteDataTests/CloudKitTests/SchemaChangeTests.swift index 98601572..c565ef58 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SchemaChangeTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SchemaChangeTests.swift @@ -837,7 +837,7 @@ recordType: "images", recordID: Image.recordID(for: 1) ) - imageRecord.setValue("1", forKey: "id", at: now) + imageRecord.setValue(1, forKey: "id", at: now) imageRecord.setValue("A good image", forKey: "caption", at: now) imageRecord.setValue(Data("image".utf8), forKey: "image", at: now) @@ -854,7 +854,7 @@ try #sql( """ CREATE TABLE "images" ( - "id" TEXT NOT NULL PRIMARY KEY ON CONFLICT REPLACE DEFAULT (uuid()), + "id" INTEGER PRIMARY KEY AUTOINCREMENT, "caption" TEXT NOT NULL, "image" BLOB NOT NULL ) @@ -892,7 +892,7 @@ parent: nil, share: nil, caption: "A good image", - id: "1", + id: 1, image: Data(5 bytes) ) ] diff --git a/Tests/SQLiteDataTests/CloudKitTests/SyncEngineDelegateTests.swift b/Tests/SQLiteDataTests/CloudKitTests/SyncEngineDelegateTests.swift index 395be01e..308c22c0 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SyncEngineDelegateTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SyncEngineDelegateTests.swift @@ -232,7 +232,7 @@ wasCalled.withValue { $0 = true } } deinit { - guard wasCalled.withValue(\.self) + guard wasCalled.withValue(\.self) || Test.current == nil else { Issue.record("Delegate method 'syncEngine(_:accountChanged:)' was not called.") return From 63ad754ab4ab8b81a3636057ccdcd1ce00931d25 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 8 Jul 2026 13:06:55 -0700 Subject: [PATCH 2/3] wip --- .../xcshareddata/swiftpm/Package.resolved | 4 ++-- Examples/Reminders/SearchReminders.swift | 2 +- .../StructuredQueries+GRDB/Decoding.swift | 19 +++++++++++++++++++ .../SQLiteFunctionDecoder.swift | 1 + .../SQLiteQueryDecoder.swift | 16 ---------------- 5 files changed, 23 insertions(+), 19 deletions(-) create mode 100644 Sources/SQLiteData/StructuredQueries+GRDB/Decoding.swift diff --git a/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index b3c5cab9..099b2798 100644 --- a/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -123,8 +123,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-structured-queries", "state" : { - "revision" : "859958c7cf76918abf5aa5d6ce8b068e00a77eb2", - "version" : "0.32.0" + "branch" : "strict-decoding", + "revision" : "eb47155b76011b4f1c77637e9efb51c69ef0083e" } }, { diff --git a/Examples/Reminders/SearchReminders.swift b/Examples/Reminders/SearchReminders.swift index 7faf8f51..9c33dfca 100644 --- a/Examples/Reminders/SearchReminders.swift +++ b/Examples/Reminders/SearchReminders.swift @@ -96,7 +96,7 @@ class SearchRemindersModel { let existingTags = searchTokens.compactMap { $0.kind == .tag ? $0.rawValue : nil } try await $tags.load( Tag - .where { $0.title.hasPrefix(searchText.dropFirst()) && !$0.title.in(existingTags) } + .where { $0.title.like("\(searchText.dropFirst())%") && !$0.title.in(existingTags) } .order(by: \.title) ) } else { diff --git a/Sources/SQLiteData/StructuredQueries+GRDB/Decoding.swift b/Sources/SQLiteData/StructuredQueries+GRDB/Decoding.swift new file mode 100644 index 00000000..6d1ca564 --- /dev/null +++ b/Sources/SQLiteData/StructuredQueries+GRDB/Decoding.swift @@ -0,0 +1,19 @@ +import GRDBSQLite + +#if !StrictDecoding + import ConcurrencyExtras + + let reportedTypeMismatches = LockIsolated>([]) +#endif + +@usableFromInline +func storageClassName(_ type: Int32) -> String { + switch type { + case SQLITE_BLOB: "BLOB" + case SQLITE_FLOAT: "REAL" + case SQLITE_INTEGER: "INTEGER" + case SQLITE_TEXT: "TEXT" + case SQLITE_NULL: "NULL" + default: "unknown storage class \(type)" + } +} diff --git a/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteFunctionDecoder.swift b/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteFunctionDecoder.swift index 1c4064e4..dab6e28f 100644 --- a/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteFunctionDecoder.swift +++ b/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteFunctionDecoder.swift @@ -3,6 +3,7 @@ public import GRDBSQLite public import StructuredQueriesCore #if !StrictDecoding + import ConcurrencyExtras import IssueReporting #endif diff --git a/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteQueryDecoder.swift b/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteQueryDecoder.swift index 76344e91..c8de563d 100644 --- a/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteQueryDecoder.swift +++ b/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteQueryDecoder.swift @@ -142,22 +142,6 @@ struct SQLiteQueryDecoder: QueryDecoder { } } -#if !StrictDecoding - let reportedTypeMismatches = LockIsolated>([]) -#endif - -@usableFromInline -func storageClassName(_ type: Int32) -> String { - switch type { - case SQLITE_BLOB: "BLOB" - case SQLITE_FLOAT: "REAL" - case SQLITE_INTEGER: "INTEGER" - case SQLITE_TEXT: "TEXT" - case SQLITE_NULL: "NULL" - default: "unknown storage class \(type)" - } -} - @usableFromInline struct InvalidUUID: Error { @usableFromInline From 3753b522f494de5183593105457ee9b52e0c3b13 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 9 Jul 2026 08:55:59 -0700 Subject: [PATCH 3/3] Add `LazyInitializableByDefault` trait (#490) * Add `LazyInitializableByDefault` trait Makes it easier to turn on the StructuredQueries trait without an explicit dependency. * wip --- Examples/CaseStudies/DynamicQuery.swift | 2 +- Examples/Examples.xcodeproj/project.pbxproj | 2 ++ Examples/Reminders/Schema.swift | 1 + Examples/SyncUps/Dependencies/SpeechClient.swift | 2 +- Examples/SyncUps/SyncUpDetail.swift | 2 +- Package.swift | 8 ++++++++ 6 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Examples/CaseStudies/DynamicQuery.swift b/Examples/CaseStudies/DynamicQuery.swift index 9d388edd..b4c25163 100644 --- a/Examples/CaseStudies/DynamicQuery.swift +++ b/Examples/CaseStudies/DynamicQuery.swift @@ -89,7 +89,7 @@ struct DynamicQueryDemo: SwiftUICaseStudy { func fetch(_ db: Database) throws -> Value { let search = Fact - .where { $0.body.contains(query) } + .where { $0.body.like("%\(query)%") } .order { $0.id.desc() } return try Value( facts: search.fetchAll(db), diff --git a/Examples/Examples.xcodeproj/project.pbxproj b/Examples/Examples.xcodeproj/project.pbxproj index d21c4881..29b9f984 100644 --- a/Examples/Examples.xcodeproj/project.pbxproj +++ b/Examples/Examples.xcodeproj/project.pbxproj @@ -1100,6 +1100,8 @@ isa = XCLocalSwiftPackageReference; relativePath = ..; traits = ( + LazyInitializableByDefault, + StrictDecoding, ); }; /* End XCLocalSwiftPackageReference section */ diff --git a/Examples/Reminders/Schema.swift b/Examples/Reminders/Schema.swift index 50c7746f..54dc8f4f 100644 --- a/Examples/Reminders/Schema.swift +++ b/Examples/Reminders/Schema.swift @@ -36,6 +36,7 @@ nonisolated struct Reminder: Hashable, Identifiable { var notes = "" var position = 0 var priority: Priority? + @Column(lazyInitializable: false) var remindersListID: RemindersList.ID var status: Status = .incomplete var title = "" diff --git a/Examples/SyncUps/Dependencies/SpeechClient.swift b/Examples/SyncUps/Dependencies/SpeechClient.swift index f6725781..66debf07 100644 --- a/Examples/SyncUps/Dependencies/SpeechClient.swift +++ b/Examples/SyncUps/Dependencies/SpeechClient.swift @@ -37,7 +37,7 @@ nonisolated struct SpeechClient: DependencyKey { requestAuthorization: { .authorized }, startTask: { AsyncThrowingStream { continuation in - Task { @MainActor in + _ = Task { @MainActor in isRecording.setValue(true) var finalText = """ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor \ diff --git a/Examples/SyncUps/SyncUpDetail.swift b/Examples/SyncUps/SyncUpDetail.swift index e5b305c1..9818ff3e 100644 --- a/Examples/SyncUps/SyncUpDetail.swift +++ b/Examples/SyncUps/SyncUpDetail.swift @@ -44,7 +44,7 @@ final class SyncUpDetailModel: HashableObject { withErrorReporting { try database.write { db in let ids = indices.map { meetings[$0].id } - try Meeting.where { ids.contains($0.id) }.delete().execute(db) + try Meeting.where { $0.id.in(ids) }.delete().execute(db) } } } diff --git a/Package.swift b/Package.swift index b0834f03..a6595638 100644 --- a/Package.swift +++ b/Package.swift @@ -22,6 +22,10 @@ let package = Package( ), ], traits: [ + .trait( + name: "LazyInitializableByDefault", + description: "Optionalize draft properties that have no default." + ), .trait( name: "StrictDecoding", description: """ @@ -57,6 +61,10 @@ let package = Package( branch: "strict-decoding", // from: "0.32.0", traits: [ + .trait( + name: "LazyInitializableByDefault", + condition: .when(traits: ["LazyInitializableByDefault"]) + ), .trait(name: "CasePaths", condition: .when(traits: ["CasePaths"])), .trait(name: "Tagged", condition: .when(traits: ["Tagged"])), ]