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
174 changes: 174 additions & 0 deletions Sources/SQLiteData/FetchOne.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,51 @@ public struct FetchOne<Value: Sendable>: Sendable {
)
}

/// Initializes this property with a query that fetches the first row from a table.
///
/// - Parameters:
/// - wrappedValue: A default value to associate with this property.
/// - database: The database to read from. A value of `nil` will use the default database
/// (`@Dependency(\.defaultDatabase)`).
public init(
wrappedValue: sending Value,
database: (any DatabaseReader)? = nil
)
where
Value: StructuredQueriesCore._OptionalProtocol,
Value: PrimaryKeyedTable,
Value.QueryOutput == Value
{
let statement = Value.all.selectStar().asSelect().limit(1)
sharedReader = SharedReader(
wrappedValue: wrappedValue,
.fetch(FetchOneStatementOptionalProtocolRequest(statement: statement), database: database)
)
}

/// Initializes this property with a query that observes the given row from a table.
///
/// - Parameters:
/// - wrappedValue: A default value to associate with this property.
/// - database: The database to read from. A value of `nil` will use the default database
/// (`@Dependency(\.defaultDatabase)`).
public init(
wrappedValue: sending Value,
database: (any DatabaseReader)? = nil
)
where
Value: PrimaryKeyedTable & QueryRepresentable, Value.QueryOutput == Value
{
let statement = Value.all
.selectStar()
.asSelect()
.find(Value.PrimaryKey(queryOutput: wrappedValue.primaryKey))
sharedReader = SharedReader(
wrappedValue: wrappedValue,
.fetch(FetchOneStatementValueRequest(statement: statement), database: database)
)
}

/// Initializes this property with a query associated with the wrapped value.
///
/// - Parameters:
Expand Down Expand Up @@ -444,6 +489,20 @@ extension FetchOne {
sharedReader = SharedReader(value: wrappedValue)
}

@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: PrimaryKeyedTable,
Value.QueryOutput == Value
{
sharedReader = SharedReader(value: wrappedValue)
}

@available(*, deprecated, message: "Remove unused parameters: 'database', 'scheduler'.")
public init(
wrappedValue: sending Value = Value._none,
Expand Down Expand Up @@ -514,6 +573,65 @@ extension FetchOne {
)
}

/// Initializes this property with a query that fetches the first row from a table.
///
/// - Parameters:
/// - wrappedValue: A default value to associate with this property.
/// - database: The database to read from. A value of `nil` will use the default database
/// (`@Dependency(\.defaultDatabase)`).
/// - scheduler: The scheduler to observe from. By default, database observation is performed
/// asynchronously on the main queue.
public init(
wrappedValue: sending Value,
database: (any DatabaseReader)? = nil,
scheduler: some ValueObservationScheduler & Hashable
)
where
Value: StructuredQueriesCore._OptionalProtocol,
Value: PrimaryKeyedTable,
Value.QueryOutput == Value
{
let statement = Value.all.selectStar().asSelect().limit(1)
sharedReader = SharedReader(
wrappedValue: wrappedValue,
.fetch(
FetchOneStatementOptionalProtocolRequest(statement: statement),
database: database,
scheduler: scheduler
)
)
}

/// Initializes this property with a query that observes the given row from a table.
///
/// - Parameters:
/// - wrappedValue: A default value to associate with this property.
/// - database: The database to read from. A value of `nil` will use the default database
/// (`@Dependency(\.defaultDatabase)`).
/// - scheduler: The scheduler to observe from. By default, database observation is performed
/// asynchronously on the main queue.
public init(
wrappedValue: sending Value,
database: (any DatabaseReader)? = nil,
scheduler: some ValueObservationScheduler & Hashable
)
where
Value: PrimaryKeyedTable & QueryRepresentable, Value.QueryOutput == Value
{
let statement = Value.all
.selectStar()
.asSelect()
.find(Value.PrimaryKey(queryOutput: wrappedValue.primaryKey))
sharedReader = SharedReader(
wrappedValue: wrappedValue,
.fetch(
FetchOneStatementValueRequest(statement: statement),
database: database,
scheduler: scheduler
)
)
}

/// Initializes this property with a query associated with the wrapped value.
///
/// - Parameters:
Expand Down Expand Up @@ -921,6 +1039,20 @@ extension FetchOne: Equatable where Value: Equatable {
sharedReader = SharedReader(value: wrappedValue)
}

@available(*, deprecated, message: "Remove unused parameters: 'database', 'animation'.")
public init(
wrappedValue: sending Value,
database: (any DatabaseReader)? = nil,
animation: Animation
)
where
Value: _Selection,
Value: PrimaryKeyedTable,
Value.QueryOutput == Value
{
sharedReader = SharedReader(value: wrappedValue)
}

@available(*, deprecated, message: "Remove unused parameters: 'database', 'animation'.")
public init(
wrappedValue: sending Value = Value._none,
Expand Down Expand Up @@ -977,6 +1109,48 @@ extension FetchOne: Equatable where Value: Equatable {
self.init(wrappedValue: wrappedValue, database: database, scheduler: .animation(animation))
}

/// Initializes this property with a query that fetches the first row from a table.
///
/// - Parameters:
/// - wrappedValue: A default value to associate with this property.
/// - database: The database to read from. A value of `nil` will use the default database
/// (`@Dependency(\.defaultDatabase)`).
/// - animation: The animation to use for user interface changes that result from changes to
/// the fetched results.
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
public init(
wrappedValue: sending Value,
database: (any DatabaseReader)? = nil,
animation: Animation
)
where
Value: StructuredQueriesCore._OptionalProtocol,
Value: PrimaryKeyedTable,
Value.QueryOutput == Value
{
self.init(wrappedValue: wrappedValue, database: database, scheduler: .animation(animation))
}

/// Initializes this property with a query that observes the given row from a table.
///
/// - Parameters:
/// - wrappedValue: A default value to associate with this property.
/// - database: The database to read from. A value of `nil` will use the default database
/// (`@Dependency(\.defaultDatabase)`).
/// - animation: The animation to use for user interface changes that result from changes to
/// the fetched results.
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
public init(
wrappedValue: sending Value,
database: (any DatabaseReader)? = nil,
animation: Animation
)
where
Value: PrimaryKeyedTable & QueryRepresentable, Value.QueryOutput == Value
{
self.init(wrappedValue: wrappedValue, database: database, scheduler: .animation(animation))
}

/// Initializes this property with a query associated with the wrapped value.
///
/// - Parameters:
Expand Down
30 changes: 27 additions & 3 deletions Tests/SQLiteDataTests/FetchOneTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,28 @@ import Testing
}

@Test func tableInit() async throws {
@FetchOne var record = Record(id: 0)
@FetchOne var record = Record(id: 2)
try await $record.load()
#expect(record == Record(id: 1))
#expect(record == Record(id: 2))
#expect($record.loadError == nil)
try await database.write { try Record.find(2).update { $0.parentID = #bind(1) }.execute($0) }
try await $record.load()
#expect(record == Record(id: 2, parentID: 1))
try await database.write { try Record.delete().execute($0) }
await #expect(throws: NotFound.self) {
try await $record.load()
}
#expect(record == Record(id: 1))
#expect(record == Record(id: 2, parentID: 1))
#expect($record.loadError is NotFound)
}

@Test func nonPrimaryKeyedTableInit() async throws {
@FetchOne var log = Log(message: "")
try await $log.load()
#expect(log == Log(message: "first"))
#expect($log.loadError == nil)
}

@Test func optionalTableInit() async throws {
@FetchOne var record: Record?
try await $record.load()
Expand Down Expand Up @@ -222,6 +232,11 @@ private struct Row {
let id: Int
}

@Table
private struct Log: Equatable {
var message = ""
}

extension DatabaseWriter where Self == DatabaseQueue {
fileprivate static func database() throws -> DatabaseQueue {
let database = try DatabaseQueue()
Expand All @@ -240,6 +255,15 @@ extension DatabaseWriter where Self == DatabaseQueue {
for _ in 1...3 {
_ = try Record.insert { Record.Draft() }.execute(db)
}
try #sql(
"""
CREATE TABLE "logs" (
"message" TEXT NOT NULL
)
"""
)
.execute(db)
try Log.insert { [Log(message: "first"), Log(message: "second")] }.execute(db)
}
return database
}
Expand Down
6 changes: 3 additions & 3 deletions Tests/SQLiteDataTests/FetchTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,16 @@ struct FetchTests {
}

@Test func fetchOneWithDefault() async throws {
@FetchOne var record = Record(id: 0)
@FetchOne var record = Record(id: 2)
try await $record.load()
#expect(record == Record(id: 1))
#expect(record == Record(id: 2))

try await database.write { try Record.delete().execute($0) }
await #expect(throws: NotFound.self) {
try await $record.load()
}
#expect($record.loadError is NotFound)
#expect(record == Record(id: 1))
#expect(record == Record(id: 2))
}

@Test func fetchOneOptional_SQL() async throws {
Expand Down