From 81722bd7c9e1b3f35ba142880879359182c5d8c2 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 8 Jul 2026 11:56:52 -0700 Subject: [PATCH 01/11] 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 02/11] 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 03/11] 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"])), ] From 04b368ecaee996bc5308f8bafdba9d0d206c7a19 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 11 Aug 2026 12:02:26 -0700 Subject: [PATCH 04/11] link --- Sources/SQLiteData/Documentation.docc/SQLiteData.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/SQLiteData/Documentation.docc/SQLiteData.md b/Sources/SQLiteData/Documentation.docc/SQLiteData.md index 3dad6730..09bd7e35 100644 --- a/Sources/SQLiteData/Documentation.docc/SQLiteData.md +++ b/Sources/SQLiteData/Documentation.docc/SQLiteData.md @@ -287,6 +287,7 @@ with SQLite to take full advantage of GRDB and SQLiteData. - - - +- ### Database configuration and access From d6bb645aedd30f1a37e8af05aaa64fbb26a0aaee Mon Sep 17 00:00:00 2001 From: Lukas Date: Tue, 11 Aug 2026 21:07:33 +0200 Subject: [PATCH 05/11] use typed throws in SQLite query/function decoder (#516) * use typed throws in SQLite query/function decoder This prevents an allocation, particularly on type mismatch, improving performance for columns containing multiple data types. * Refactor date decoding logic in SQLiteFunctionDecoder * Fix date decoding logic in SQLiteQueryDecoder --------- Co-authored-by: Stephen Celis Co-authored-by: Stephen Celis --- .../SQLiteFunctionDecoder.swift | 28 ++++++++++------- .../SQLiteQueryDecoder.swift | 31 +++++++++++-------- 2 files changed, 34 insertions(+), 25 deletions(-) diff --git a/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteFunctionDecoder.swift b/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteFunctionDecoder.swift index dab6e28f..a9acf171 100644 --- a/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteFunctionDecoder.swift +++ b/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteFunctionDecoder.swift @@ -34,7 +34,7 @@ struct SQLiteFunctionDecoder: QueryDecoder { } @inlinable - mutating func decode(_ columnType: [UInt8].Type) throws -> [UInt8]? { + mutating func decode(_ columnType: [UInt8].Type) throws(QueryDecodingError) -> [UInt8]? { precondition(argumentCount > currentIndex) let value = arguments?[Int(currentIndex)] switch sqlite3_value_type(value) { @@ -57,18 +57,22 @@ struct SQLiteFunctionDecoder: QueryDecoder { } @inlinable - mutating func decode(_ columnType: Bool.Type) throws -> Bool? { + mutating func decode(_ columnType: Bool.Type) throws(QueryDecodingError) -> Bool? { try decode(Int64.self).map { $0 != 0 } } @usableFromInline - mutating func decode(_ columnType: Date.Type) throws -> Date? { + mutating func decode(_ columnType: Date.Type) throws(QueryDecodingError) -> Date? { guard let iso8601String = try decode(String.self) else { return nil } - return try Date(iso8601String: iso8601String) + do { + return try Date(iso8601String: iso8601String) + } catch { + throw .other(error) + } } @inlinable - mutating func decode(_ columnType: Double.Type) throws -> Double? { + mutating func decode(_ columnType: Double.Type) throws(QueryDecodingError) -> Double? { precondition(argumentCount > currentIndex) let value = arguments?[Int(currentIndex)] switch sqlite3_value_type(value) { @@ -85,12 +89,12 @@ struct SQLiteFunctionDecoder: QueryDecoder { } @inlinable - mutating func decode(_ columnType: Int.Type) throws -> Int? { + mutating func decode(_ columnType: Int.Type) throws(QueryDecodingError) -> Int? { try decode(Int64.self).map(Int.init) } @inlinable - mutating func decode(_ columnType: Int64.Type) throws -> Int64? { + mutating func decode(_ columnType: Int64.Type) throws(QueryDecodingError) -> Int64? { precondition(argumentCount > currentIndex) let value = arguments?[Int(currentIndex)] switch sqlite3_value_type(value) { @@ -107,7 +111,7 @@ struct SQLiteFunctionDecoder: QueryDecoder { } @inlinable - mutating func decode(_ columnType: String.Type) throws -> String? { + mutating func decode(_ columnType: String.Type) throws(QueryDecodingError) -> String? { precondition(argumentCount > currentIndex) let value = arguments?[Int(currentIndex)] switch sqlite3_value_type(value) { @@ -124,20 +128,20 @@ struct SQLiteFunctionDecoder: QueryDecoder { } @inlinable - mutating func decode(_ columnType: UInt64.Type) throws -> UInt64? { + mutating func decode(_ columnType: UInt64.Type) throws(QueryDecodingError) -> UInt64? { guard let n = try decode(Int64.self) else { return nil } - guard n >= 0 else { throw UInt64OverflowError(signedInteger: n) } + guard n >= 0 else { throw .other(UInt64OverflowError(signedInteger: n)) } return UInt64(n) } @usableFromInline - mutating func decode(_ columnType: UUID.Type) throws -> UUID? { + mutating func decode(_ columnType: UUID.Type) throws(QueryDecodingError) -> UUID? { guard let uuidString = try decode(String.self) else { return nil } return UUID(uuidString: uuidString) } @usableFromInline - func reportTypeMismatch(_ columnType: Any.Type) throws { + func reportTypeMismatch(_ columnType: Any.Type) throws(QueryDecodingError) { #if StrictDecoding throw QueryDecodingError.typeMismatch(columnType) #else diff --git a/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteQueryDecoder.swift b/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteQueryDecoder.swift index c8de563d..5c2e1189 100644 --- a/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteQueryDecoder.swift +++ b/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteQueryDecoder.swift @@ -26,7 +26,7 @@ struct SQLiteQueryDecoder: QueryDecoder { } @inlinable - mutating func decode(_ columnType: [UInt8].Type) throws -> [UInt8]? { + mutating func decode(_ columnType: [UInt8].Type) throws(QueryDecodingError) -> [UInt8]? { switch sqlite3_column_type(statement, currentIndex) { case SQLITE_NULL: currentIndex += 1 @@ -46,17 +46,22 @@ struct SQLiteQueryDecoder: QueryDecoder { } @inlinable - mutating func decode(_ columnType: Bool.Type) throws -> Bool? { + mutating func decode(_ columnType: Bool.Type) throws(QueryDecodingError) -> Bool? { try decode(Int64.self).map { $0 != 0 } } @inlinable - mutating func decode(_ columnType: Date.Type) throws -> Date? { - try decode(String.self).map { try Date(iso8601String: $0) } + mutating func decode(_ columnType: Date.Type) throws(QueryDecodingError) -> Date? { + guard let iso8601String = try decode(String.self) else { return nil } + do { + return try Date(iso8601String: iso8601String) } + } catch { + throw .other(error) + } } @inlinable - mutating func decode(_ columnType: Double.Type) throws -> Double? { + mutating func decode(_ columnType: Double.Type) throws(QueryDecodingError) -> Double? { switch sqlite3_column_type(statement, currentIndex) { case SQLITE_NULL: currentIndex += 1 @@ -71,12 +76,12 @@ struct SQLiteQueryDecoder: QueryDecoder { } @inlinable - mutating func decode(_ columnType: Int.Type) throws -> Int? { + mutating func decode(_ columnType: Int.Type) throws(QueryDecodingError) -> Int? { try decode(Int64.self).map(Int.init) } @inlinable - mutating func decode(_ columnType: Int64.Type) throws -> Int64? { + mutating func decode(_ columnType: Int64.Type) throws(QueryDecodingError) -> Int64? { switch sqlite3_column_type(statement, currentIndex) { case SQLITE_NULL: currentIndex += 1 @@ -91,7 +96,7 @@ struct SQLiteQueryDecoder: QueryDecoder { } @inlinable - mutating func decode(_ columnType: String.Type) throws -> String? { + mutating func decode(_ columnType: String.Type) throws(QueryDecodingError) -> String? { switch sqlite3_column_type(statement, currentIndex) { case SQLITE_NULL: currentIndex += 1 @@ -106,21 +111,21 @@ struct SQLiteQueryDecoder: QueryDecoder { } @inlinable - mutating func decode(_ columnType: UInt64.Type) throws -> UInt64? { + mutating func decode(_ columnType: UInt64.Type) throws(QueryDecodingError) -> UInt64? { guard let n = try decode(Int64.self) else { return nil } - guard n >= 0 else { throw UInt64OverflowError(signedInteger: n) } + guard n >= 0 else { throw .other(UInt64OverflowError(signedInteger: n)) } return UInt64(n) } @inlinable - mutating func decode(_ columnType: UUID.Type) throws -> UUID? { + mutating func decode(_ columnType: UUID.Type) throws(QueryDecodingError) -> UUID? { guard let uuidString = try decode(String.self) else { return nil } - guard let uuid = UUID(uuidString: uuidString) else { throw InvalidUUID() } + guard let uuid = UUID(uuidString: uuidString) else { throw .other(InvalidUUID()) } return uuid } @usableFromInline - func reportTypeMismatch(_ columnType: Any.Type) throws { + func reportTypeMismatch(_ columnType: Any.Type) throws(QueryDecodingError) { #if StrictDecoding throw QueryDecodingError.typeMismatch(columnType) #else From c98a2da39a706998961cc041b73f6665e483f86a Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 11 Aug 2026 12:33:54 -0700 Subject: [PATCH 06/11] fix --- .../SQLiteData/StructuredQueries+GRDB/QueryCursor.swift | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Sources/SQLiteData/StructuredQueries+GRDB/QueryCursor.swift b/Sources/SQLiteData/StructuredQueries+GRDB/QueryCursor.swift index cd4f66f1..eb3d1975 100644 --- a/Sources/SQLiteData/StructuredQueries+GRDB/QueryCursor.swift +++ b/Sources/SQLiteData/StructuredQueries+GRDB/QueryCursor.swift @@ -124,12 +124,7 @@ final class QuerySectionedCursor< decoder.next() return (element, sectionName) } catch QueryDecodingError.missingRequiredColumn { - let columnIndex = Int(decoder.currentIndex) - 1 - throw DecodingError( - columnIndex: columnIndex, - columnName: _statement.columnNames[columnIndex], - sql: _statement.sql - ) + throw missingRequiredColumnError() } } } From e3d08b995d8e5bbd9f7a9692805e4cda1ab5d1ed Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 11 Aug 2026 12:34:21 -0700 Subject: [PATCH 07/11] fix --- .../SQLiteData/StructuredQueries+GRDB/SQLiteQueryDecoder.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteQueryDecoder.swift b/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteQueryDecoder.swift index 5c2e1189..b870b41a 100644 --- a/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteQueryDecoder.swift +++ b/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteQueryDecoder.swift @@ -54,7 +54,7 @@ struct SQLiteQueryDecoder: QueryDecoder { mutating func decode(_ columnType: Date.Type) throws(QueryDecodingError) -> Date? { guard let iso8601String = try decode(String.self) else { return nil } do { - return try Date(iso8601String: iso8601String) } + return try Date(iso8601String: iso8601String) } catch { throw .other(error) } From 23e1072d734b20ceb2dc10b0e6aec08627ea91cc Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 11 Aug 2026 12:37:32 -0700 Subject: [PATCH 08/11] wip --- .../StructuredQueries+GRDB/SQLiteQueryDecoder.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteQueryDecoder.swift b/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteQueryDecoder.swift index b870b41a..0fe58548 100644 --- a/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteQueryDecoder.swift +++ b/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteQueryDecoder.swift @@ -133,11 +133,13 @@ struct SQLiteQueryDecoder: QueryDecoder { let key = "\(currentIndex)|\(sql)" guard reportedTypeMismatches.withValue({ $0.insert(key).inserted }) else { return } - let columnName = sqlite3_column_name(statement, currentIndex).map { String(cString: $0) } + let columnName = + sqlite3_column_name(statement, currentIndex) + .map { " (\(String(cString: $0).debugDescription))" } + ?? "" reportIssue( """ - Expected column \(currentIndex) (\((columnName ?? "").debugDescription)) to decode \ - \(columnType), but found \ + Expected column \(currentIndex)\(columnName ?? "") to decode \(columnType), but found \ \(storageClassName(sqlite3_column_type(statement, currentIndex))): ... \(sql) From 11e7df9e01e30a4b150e26b8bf8ae44a5911e00a Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 11 Aug 2026 12:58:00 -0700 Subject: [PATCH 09/11] cleanup --- .../CustomFunctions.swift | 25 ++++++++----------- .../StructuredQueries+GRDB/Decoding.swift | 6 ----- .../SQLiteFunctionDecoder.swift | 22 ++++++++++------ .../SQLiteQueryDecoder.swift | 16 ++++++------ 4 files changed, 34 insertions(+), 35 deletions(-) diff --git a/Sources/SQLiteData/StructuredQueries+GRDB/CustomFunctions.swift b/Sources/SQLiteData/StructuredQueries+GRDB/CustomFunctions.swift index 81ceece1..d79b7423 100644 --- a/Sources/SQLiteData/StructuredQueries+GRDB/CustomFunctions.swift +++ b/Sources/SQLiteData/StructuredQueries+GRDB/CustomFunctions.swift @@ -20,17 +20,12 @@ extension Database { Unmanaged.passRetained(ScalarDatabaseFunctionDefinition(function)).toOpaque(), { context, argumentCount, arguments in do { - let function = Unmanaged + let definition = Unmanaged .fromOpaque(sqlite3_user_data(context)) .takeUnretainedValue() - .function - var decoder = SQLiteFunctionDecoder( - name: function.name, - argumentCount: argumentCount, - arguments: arguments - ) - try function - .invoke(&decoder) + definition.decoder.reset(argumentCount: argumentCount, arguments: arguments) + try definition.function + .invoke(&definition.decoder) .result(db: context) } catch { QueryBinding.invalid(error).result(db: context) @@ -59,13 +54,9 @@ extension Database { nil, { context, argumentCount, arguments in let function = AggregateDatabaseFunctionContext[context].takeUnretainedValue() - var decoder = SQLiteFunctionDecoder( - name: function.iterator.body.name, - argumentCount: argumentCount, - arguments: arguments - ) + function.decoder.reset(argumentCount: argumentCount, arguments: arguments) do { - try function.iterator.step(&decoder) + try function.iterator.step(&function.decoder) } catch { sqlite3_result_error(context, error.localizedDescription, -1) } @@ -118,8 +109,10 @@ extension DatabaseFunction { private final class ScalarDatabaseFunctionDefinition { let function: any ScalarDatabaseFunction + var decoder: SQLiteFunctionDecoder init(_ function: some ScalarDatabaseFunction) { self.function = function + self.decoder = SQLiteFunctionDecoder(name: function.name) } } @@ -152,8 +145,10 @@ private final class AggregateDatabaseFunctionContext { } } let iterator: any AggregateDatabaseFunctionIteratorProtocol + var decoder: SQLiteFunctionDecoder init(_ body: some AggregateDatabaseFunction) { self.iterator = AggregateDatabaseFunctionIterator(body) + self.decoder = SQLiteFunctionDecoder(name: body.name) } } diff --git a/Sources/SQLiteData/StructuredQueries+GRDB/Decoding.swift b/Sources/SQLiteData/StructuredQueries+GRDB/Decoding.swift index 6d1ca564..c82b117f 100644 --- a/Sources/SQLiteData/StructuredQueries+GRDB/Decoding.swift +++ b/Sources/SQLiteData/StructuredQueries+GRDB/Decoding.swift @@ -1,11 +1,5 @@ import GRDBSQLite -#if !StrictDecoding - import ConcurrencyExtras - - let reportedTypeMismatches = LockIsolated>([]) -#endif - @usableFromInline func storageClassName(_ type: Int32) -> String { switch type { diff --git a/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteFunctionDecoder.swift b/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteFunctionDecoder.swift index a9acf171..d0fb06d9 100644 --- a/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteFunctionDecoder.swift +++ b/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteFunctionDecoder.swift @@ -3,7 +3,6 @@ public import GRDBSQLite public import StructuredQueriesCore #if !StrictDecoding - import ConcurrencyExtras import IssueReporting #endif @@ -13,19 +12,29 @@ struct SQLiteFunctionDecoder: QueryDecoder { let name: String @usableFromInline - let argumentCount: Int32 + var argumentCount: Int32 = 0 @usableFromInline - let arguments: UnsafeMutablePointer? + var arguments: UnsafeMutablePointer? @usableFromInline var currentIndex: Int32 = 0 + #if !StrictDecoding + @usableFromInline + var reportedTypeMismatches: Set = [] + #endif + @usableFromInline - init(name: String, argumentCount: Int32, arguments: UnsafeMutablePointer?) { + init(name: String) { self.name = name + } + + @usableFromInline + mutating func reset(argumentCount: Int32, arguments: UnsafeMutablePointer?) { self.argumentCount = argumentCount self.arguments = arguments + self.currentIndex = 0 } @inlinable @@ -141,12 +150,11 @@ struct SQLiteFunctionDecoder: QueryDecoder { } @usableFromInline - func reportTypeMismatch(_ columnType: Any.Type) throws(QueryDecodingError) { + mutating func reportTypeMismatch(_ columnType: Any.Type) throws(QueryDecodingError) { #if StrictDecoding throw QueryDecodingError.typeMismatch(columnType) #else - let key = "\(currentIndex)|\(name)" - guard reportedTypeMismatches.withValue({ $0.insert(key).inserted }) + guard reportedTypeMismatches.insert(currentIndex).inserted else { return } let value = arguments?[Int(currentIndex)] reportIssue( diff --git a/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteQueryDecoder.swift b/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteQueryDecoder.swift index 0fe58548..edbdd195 100644 --- a/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteQueryDecoder.swift +++ b/Sources/SQLiteData/StructuredQueries+GRDB/SQLiteQueryDecoder.swift @@ -3,7 +3,6 @@ public import GRDBSQLite public import StructuredQueriesCore #if !StrictDecoding - import ConcurrencyExtras import IssueReporting #endif @@ -15,6 +14,11 @@ struct SQLiteQueryDecoder: QueryDecoder { @usableFromInline var currentIndex: Int32 = 0 + #if !StrictDecoding + @usableFromInline + var reportedTypeMismatches: Set = [] + #endif + @usableFromInline init(statement: OpaquePointer) { self.statement = statement @@ -125,13 +129,11 @@ struct SQLiteQueryDecoder: QueryDecoder { } @usableFromInline - func reportTypeMismatch(_ columnType: Any.Type) throws(QueryDecodingError) { + mutating func reportTypeMismatch(_ columnType: Any.Type) throws(QueryDecodingError) { #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 }) + guard reportedTypeMismatches.insert(currentIndex).inserted else { return } let columnName = sqlite3_column_name(statement, currentIndex) @@ -139,10 +141,10 @@ struct SQLiteQueryDecoder: QueryDecoder { ?? "" reportIssue( """ - Expected column \(currentIndex)\(columnName ?? "") to decode \(columnType), but found \ + Expected column \(currentIndex)\(columnName) to decode \(columnType), but found \ \(storageClassName(sqlite3_column_type(statement, currentIndex))): ... - \(sql) + \(sqlite3_sql(statement).map { String(cString: $0) } ?? "") """ ) #endif From ac16788c2faa3e733fcfb69b5f206805716567fa Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 11 Aug 2026 13:26:14 -0700 Subject: [PATCH 10/11] Point to release --- Package.resolved | 18 +++++++++--------- Package.swift | 3 +-- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/Package.resolved b/Package.resolved index 1874533f..c161271d 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "3d2fb3544d50adf14efe42eafe08ba91b98dc4f3cf663c805e069a4a63cf4b2e", + "originHash" : "c23a65a671a050ad4a9a14e2506d4c5d127c928aa245e735949f4e4891331401", "pins" : [ { "identity" : "combine-schedulers", @@ -42,8 +42,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-concurrency-extras", "state" : { - "revision" : "a90e2e40a7a840a853dd29e57cbef5dbb72c9d5b", - "version" : "1.4.0" + "revision" : "5fa253428866f2360c3754e88537f700ed2656b5", + "version" : "1.4.1" } }, { @@ -51,8 +51,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-custom-dump", "state" : { - "revision" : "a8cd6c976f335ed361dcecddb0dc39ebda51bc3e", - "version" : "1.6.1" + "revision" : "e9c34fd54ece006b491a0e6d23fe9a6024b9a828", + "version" : "1.7.0" } }, { @@ -114,8 +114,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-snapshot-testing", "state" : { - "revision" : "1bc16f430d8410e7f087d4c787767b26fd32fe30", - "version" : "1.19.3" + "revision" : "59a99c458de4d2dee580529b61b4f78dca7b7fa6", + "version" : "1.19.4" } }, { @@ -123,8 +123,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-structured-queries", "state" : { - "branch" : "main", - "revision" : "9d43c09b0ba5a10a06caea584d08fadf851f6822" + "revision" : "8a733f414adc1224c5185c3a35c30ee1ef217171", + "version" : "0.36.0" } }, { diff --git a/Package.swift b/Package.swift index 4b2b38b8..52445928 100644 --- a/Package.swift +++ b/Package.swift @@ -65,8 +65,7 @@ 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", - branch: "main", - // from: "0.35.0", + from: "0.36.0", traits: [ .trait( name: "LazyInitializableByDefault", From a0577d51450d2d492fce64707d8a82f4d3b40eb0 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 11 Aug 2026 13:27:24 -0700 Subject: [PATCH 11/11] bump --- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index b67f89ee..8b742760 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" : { - "branch" : "main", - "revision" : "9d43c09b0ba5a10a06caea584d08fadf851f6822" + "revision" : "8a733f414adc1224c5185c3a35c30ee1ef217171", + "version" : "0.36.0" } }, {