From 1cd990272f111e6af274de46dc640ca1a877f6a4 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 31 Dec 2025 15:17:55 -0800 Subject: [PATCH] Don't execute `@Fetch{All,One}` queries for selections It's currently possible to execute these queries if you specify a parameter like `animation`. These parameters aren't actually useful for a selection query, so let's deprecate those overloads. --- Sources/SQLiteData/FetchAll.swift | 20 ++++++ Sources/SQLiteData/FetchOne.swift | 77 +++++++++++++++++++---- Tests/SQLiteDataTests/FetchAllTests.swift | 20 ++++++ Tests/SQLiteDataTests/FetchOneTests.swift | 35 +++++++++++ 4 files changed, 140 insertions(+), 12 deletions(-) diff --git a/Sources/SQLiteData/FetchAll.swift b/Sources/SQLiteData/FetchAll.swift index e4960c8a..6e6e885f 100644 --- a/Sources/SQLiteData/FetchAll.swift +++ b/Sources/SQLiteData/FetchAll.swift @@ -219,6 +219,16 @@ public struct FetchAll: Sendable { } extension FetchAll { + @available(*, deprecated, message: "Remove unused parameters: 'database', 'scheduler'.") + public init( + wrappedValue: [Element] = [], + database: (any DatabaseReader)? = nil, + scheduler: some ValueObservationScheduler & Hashable + ) + where Element: StructuredQueriesCore._Selection, Element.QueryOutput == Element { + sharedReader = SharedReader(value: wrappedValue) + } + /// Initializes this property with a query that fetches every row from a table. /// /// - Parameters: @@ -393,6 +403,16 @@ extension FetchAll: Equatable where Element: Equatable { sharedReader.update() } + @available(*, deprecated, message: "Remove unused parameters: 'database', 'animation'.") + public init( + wrappedValue: [Element] = [], + database: (any DatabaseReader)? = nil, + animation: Animation + ) + where Element: StructuredQueriesCore._Selection, Element.QueryOutput == Element { + sharedReader = SharedReader(value: wrappedValue) + } + /// Initializes this property with a query that fetches every row from a table. /// /// - Parameters: diff --git a/Sources/SQLiteData/FetchOne.swift b/Sources/SQLiteData/FetchOne.swift index 5f06f972..e9e4312e 100644 --- a/Sources/SQLiteData/FetchOne.swift +++ b/Sources/SQLiteData/FetchOne.swift @@ -79,6 +79,17 @@ public struct FetchOne: Sendable { sharedReader = SharedReader(value: wrappedValue) } + /// Initializes this property with a wrapped value. + /// + /// - Parameter wrappedValue: A default value to associate with this property. + public init(wrappedValue: sending Value) + where + Value: _Selection, + Value.QueryOutput == Value + { + sharedReader = SharedReader(value: wrappedValue) + } + /// Initializes this property with a query that fetches the first row from a table. /// /// - Parameters: @@ -121,18 +132,6 @@ public struct FetchOne: Sendable { ) } - /// Initializes this property with a wrapped value. - /// - /// - Parameter wrappedValue: A default value to associate with this property. - public init(wrappedValue: sending Value) - where - Value: _OptionalProtocol, - Value: _Selection, - Value.QueryOutput == Value - { - sharedReader = SharedReader(value: wrappedValue) - } - /// Initializes this property with a query associated with the wrapped value. /// /// - Parameters: @@ -430,6 +429,33 @@ public struct FetchOne: Sendable { } extension FetchOne { + @available(*, deprecated, message: "Remove unused parameters: 'database', 'scheduler'.") + public init( + wrappedValue: sending Value, + database: (any DatabaseReader)? = nil, + scheduler: some ValueObservationScheduler & Hashable + ) + where + Value: _Selection, + Value.QueryOutput == Value + { + sharedReader = SharedReader(value: wrappedValue) + } + + @available(*, deprecated, message: "Remove unused parameters: 'database', 'scheduler'.") + public init( + wrappedValue: sending Value = Value._none, + database: (any DatabaseReader)? = nil, + scheduler: some ValueObservationScheduler & Hashable + ) + where + Value: _OptionalProtocol, + Value: _Selection, + Value.QueryOutput == Value + { + sharedReader = SharedReader(value: wrappedValue) + } + /// Initializes this property with a query that fetches the first row from a table. /// /// - Parameters: @@ -880,6 +906,33 @@ extension FetchOne: Equatable where Value: Equatable { sharedReader.update() } + @available(*, deprecated, message: "Remove unused parameters: 'database', 'animation'.") + public init( + wrappedValue: sending Value, + database: (any DatabaseReader)? = nil, + animation: Animation + ) + where + Value: _Selection, + Value.QueryOutput == Value + { + sharedReader = SharedReader(value: wrappedValue) + } + + @available(*, deprecated, message: "Remove unused parameters: 'database', 'animation'.") + public init( + wrappedValue: sending Value = Value._none, + database: (any DatabaseReader)? = nil, + animation: Animation + ) + where + Value: _OptionalProtocol, + Value: _Selection, + Value.QueryOutput == Value + { + sharedReader = SharedReader(value: wrappedValue) + } + /// Initializes this property with a query that fetches the first row from a table. /// /// - Parameters: diff --git a/Tests/SQLiteDataTests/FetchAllTests.swift b/Tests/SQLiteDataTests/FetchAllTests.swift index 0b787542..83cdbc60 100644 --- a/Tests/SQLiteDataTests/FetchAllTests.swift +++ b/Tests/SQLiteDataTests/FetchAllTests.swift @@ -3,6 +3,10 @@ import Foundation import SQLiteData import Testing +#if canImport(SwiftUI) + import SwiftUI +#endif + @Suite(.dependency(\.defaultDatabase, try .database())) struct FetchAllTests { @Dependency(\.defaultDatabase) var database @@ -63,6 +67,16 @@ struct FetchAllTests { ) } } + + @available(*, deprecated) + @Test func fetchAllSelection_Deprecated() async throws { + #if canImport(SwiftUI) + do { + @FetchAll(animation: .default) var row: [Row] + #expect($row.loadError == nil) + } + #endif + } } @Table @@ -73,6 +87,12 @@ private struct Record: Equatable { @Column(as: Date?.UnixTimeRepresentation.self) var optionalDate: Date? } + +@Selection +private struct Row { + let id: Int +} + extension DatabaseWriter where Self == DatabaseQueue { fileprivate static func database() throws -> DatabaseQueue { let database = try DatabaseQueue() diff --git a/Tests/SQLiteDataTests/FetchOneTests.swift b/Tests/SQLiteDataTests/FetchOneTests.swift index a0a214b9..1bfffe48 100644 --- a/Tests/SQLiteDataTests/FetchOneTests.swift +++ b/Tests/SQLiteDataTests/FetchOneTests.swift @@ -3,6 +3,10 @@ import Foundation import SQLiteData import Testing +#if canImport(SwiftUI) + import SwiftUI +#endif + @Suite(.dependency(\.defaultDatabase, try .database())) struct FetchOneTests { @Dependency(\.defaultDatabase) var database @@ -176,6 +180,31 @@ import Testing _record = FetchOne(wrappedValue: Record(id: 0), Record.all) #expect(record.id == 1) } + + @Test func fetchOneSelection() async throws { + do { + @FetchOne var row: Row? + #expect($row.loadError == nil) + } + do { + @FetchOne var row = Row(id: 1) + #expect($row.loadError == nil) + } + } + + @available(*, deprecated) + @Test func fetchOneSelection_Deprecated() async throws { + #if canImport(SwiftUI) + do { + @FetchOne(animation: .default) var row: Row? + #expect($row.loadError == nil) + } + do { + @FetchOne(animation: .default) var row = Row(id: 1) + #expect($row.loadError == nil) + } + #endif + } } @Table @@ -187,6 +216,12 @@ private struct Record: Equatable { @Column(as: Date?.UnixTimeRepresentation.self) var optionalDate: Date? } + +@Selection +private struct Row { + let id: Int +} + extension DatabaseWriter where Self == DatabaseQueue { fileprivate static func database() throws -> DatabaseQueue { let database = try DatabaseQueue()