Files
UUVPN/iOS-SwiftUI-Code/GRDB.swift-6.29.3/GRDB/Core/FetchRequest.swift
T
2025-01-22 14:09:10 +08:00

312 lines
9.2 KiB
Swift

// MARK: - FetchRequest
/// A type that fetches and decodes database rows.
///
/// The main kinds of fetch requests are ``SQLRequest``
/// and ``QueryInterfaceRequest``:
///
/// ```swift
/// let lastName = "O'Reilly"
///
/// // SQLRequest
/// let request: SQLRequest<Player> = """
/// SELECT * FROM player WHERE lastName = \(lastName)
/// """
///
/// // QueryInterfaceRequest
/// let request = Player.filter(Column("lastName") == lastName)
///
/// // Use the request
/// try dbQueue.read { db in
/// let players = try request.fetchAll(db) // [Player]
/// }
/// ```
///
/// ## Topics
///
/// ### Counting the Results
///
/// - ``fetchCount(_:)``
///
/// ### Fetching Database Rows
///
/// - ``fetchCursor(_:)-9283d``
/// - ``fetchAll(_:)-7p809``
/// - ``fetchOne(_:)-9fafl``
/// - ``fetchSet(_:)-6bdrd``
///
/// ### Fetching Database Values
///
/// - ``fetchCursor(_:)-19f5g``
/// - ``fetchCursor(_:)-66xoi``
/// - ``fetchAll(_:)-1loau``
/// - ``fetchAll(_:)-28pne``
/// - ``fetchOne(_:)-44mvv``
/// - ``fetchOne(_:)-5hlkf``
/// - ``fetchSet(_:)-4hhtm``
/// - ``fetchSet(_:)-9wshm``
///
/// ### Fetching Records
///
/// - ``fetchCursor(_:)-2ah3q``
/// - ``fetchAll(_:)-vdos``
/// - ``fetchOne(_:)-2bq0k``
/// - ``fetchSet(_:)-4jdrq``
///
/// ### Preparing Database Requests
///
/// - ``makePreparedRequest(_:forSingleResult:)``
/// - ``PreparedRequest``
///
/// ### Adapting the Fetched Rows
///
/// - ``adapted(_:)``
/// - ``AdaptedFetchRequest``
///
/// ### Supporting Types
///
/// - ``AnyFetchRequest``
public protocol FetchRequest<RowDecoder>: SQLSubqueryable, DatabaseRegionConvertible {
/// The type that tells how fetched database rows should be interpreted.
associatedtype RowDecoder
/// Returns a ``PreparedRequest``.
///
/// The `singleResult` argument is a hint that a single result row will be
/// consumed. Implementations can optionally use it to optimize the
/// prepared statement, for example by adding a `LIMIT 1` SQL clause:
///
/// ```swift
/// // Calls makePreparedRequest(db, forSingleResult: true)
/// try request.fetchOne(db)
///
/// // Calls makePreparedRequest(db, forSingleResult: false)
/// try request.fetchAll(db)
/// ```
///
/// - parameter db: A database connection.
/// - parameter singleResult: A hint that a single result row will be
/// consumed.
func makePreparedRequest(_ db: Database, forSingleResult singleResult: Bool) throws -> PreparedRequest
/// Returns the number of rows fetched by the request.
///
/// - parameter db: A database connection.
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs.
func fetchCount(_ db: Database) throws -> Int
}
extension FetchRequest {
/// Returns the database region that the request feeds from.
///
/// - parameter db: A database connection.
public func databaseRegion(_ db: Database) throws -> DatabaseRegion {
try makePreparedRequest(db, forSingleResult: false).statement.databaseRegion
}
}
// MARK: - PreparedRequest
/// A closure executed before a supplementary fetch is performed.
///
/// Support for `Database.dumpRequest`.
///
/// - parameter request: The supplementary request
/// - parameter keyPath: The key path target of the supplementary fetch.
typealias WillExecuteSupplementaryRequest = (_ request: AnyFetchRequest<Row>, _ keyPath: [String]) throws -> Void
/// A closure that performs supplementary fetches.
///
/// Support for eager loading of hasMany associations.
///
/// - parameter db: A database connection.
/// - parameter rows: The rows that are modified by the supplementary fetch.
/// - parameter willExecuteSupplementaryRequest: A closure to execute before
/// performing supplementary fetches.
typealias SupplementaryFetch = (
_ db: Database,
_ rows: [Row],
_ willExecuteSupplementaryRequest: WillExecuteSupplementaryRequest?)
throws -> Void
/// A `PreparedRequest` is a request that is ready to be executed.
public struct PreparedRequest {
/// A prepared statement with bound parameters.
public var statement: Statement
/// An eventual adapter for rows fetched by the select statement.
public var adapter: (any RowAdapter)?
/// A closure that performs supplementary fetches.
/// Support for eager loading of hasMany associations.
var supplementaryFetch: SupplementaryFetch?
init(
statement: Statement,
adapter: (any RowAdapter)?,
supplementaryFetch: SupplementaryFetch? = nil)
{
self.statement = statement
self.adapter = adapter
self.supplementaryFetch = supplementaryFetch
}
}
// Explicit non-conformance to Sendable: `PreparedRequest` contains
// a statement.
@available(*, unavailable)
extension PreparedRequest: Sendable { }
extension PreparedRequest: Refinable { }
// MARK: - AdaptedFetchRequest
extension FetchRequest {
/// Returns an adapted request.
///
/// The returned request performs an identical database query, but adapts
/// the fetched rows. See ``RowAdapter``, and
/// ``splittingRowAdapters(columnCounts:)`` for a sample code that uses
/// `adapted(_:)`.
///
/// - parameter adapter: A closure that accepts a database connection and
/// returns a row adapter.
public func adapted(_ adapter: @escaping (Database) throws -> any RowAdapter) -> AdaptedFetchRequest<Self> {
AdaptedFetchRequest(self, adapter)
}
}
/// An adapted request.
///
/// See ``FetchRequest/adapted(_:)``.
public struct AdaptedFetchRequest<Base: FetchRequest> {
let base: Base
let adapter: (Database) throws -> any RowAdapter
/// Creates an adapted request from a base request and a closure that builds
/// a row adapter from a database connection.
init(_ base: Base, _ adapter: @escaping (Database) throws -> any RowAdapter) {
self.base = base
self.adapter = adapter
}
}
extension AdaptedFetchRequest: SQLSubqueryable {
public var sqlSubquery: SQLSubquery {
base.sqlSubquery
}
}
extension AdaptedFetchRequest: FetchRequest {
public typealias RowDecoder = Base.RowDecoder
public func fetchCount(_ db: Database) throws -> Int {
try base.fetchCount(db)
}
public func makePreparedRequest(
_ db: Database,
forSingleResult singleResult: Bool = false)
throws -> PreparedRequest
{
var preparedRequest = try base.makePreparedRequest(db, forSingleResult: singleResult)
if let baseAdapter = preparedRequest.adapter {
preparedRequest.adapter = try ChainedAdapter(first: baseAdapter, second: adapter(db))
} else {
preparedRequest.adapter = try adapter(db)
}
return preparedRequest
}
}
// MARK: - AnyFetchRequest
/// A type-erased FetchRequest.
///
/// An `AnyFetchRequest` forwards its operations to an underlying request,
/// hiding its specifics.
public struct AnyFetchRequest<RowDecoder> {
private let request: FetchRequestEraser
/// Returns a request that performs an identical database query, but decodes
/// database rows with `type`.
///
/// For example:
///
/// ```swift
/// // AnyFetchRequest<Player>
/// let playerRequest = AnyFetchRequest(Player.all())
///
/// // AnyFetchRequest<Row>
/// let rowRequest = playerRequest.asRequest(of: Row.self)
public func asRequest<T>(of type: T.Type) -> AnyFetchRequest<T> {
AnyFetchRequest<T>(request: request)
}
}
extension AnyFetchRequest {
/// Creates a request that wraps and forwards operations to `request`.
public init(_ request: some FetchRequest<RowDecoder>) {
self.init(request: ConcreteFetchRequestEraser(request: request))
}
}
extension AnyFetchRequest: SQLSubqueryable {
public var sqlSubquery: SQLSubquery {
request.sqlSubquery
}
}
extension AnyFetchRequest: FetchRequest {
public func fetchCount(_ db: Database) throws -> Int {
try request.fetchCount(db)
}
public func makePreparedRequest(
_ db: Database,
forSingleResult singleResult: Bool = false)
throws -> PreparedRequest
{
try request.makePreparedRequest(db, forSingleResult: singleResult)
}
}
// Class-based type erasure, so that we preserve full type information.
private class FetchRequestEraser: FetchRequest {
typealias RowDecoder = Void
var sqlSubquery: SQLSubquery {
fatalError("subclass must override")
}
func makePreparedRequest(_ db: Database, forSingleResult singleResult: Bool) throws -> PreparedRequest {
fatalError("subclass must override")
}
func fetchCount(_ db: Database) throws -> Int {
fatalError("subclass must override")
}
}
private final class ConcreteFetchRequestEraser<Request: FetchRequest>: FetchRequestEraser {
let request: Request
init(request: Request) {
self.request = request
}
override var sqlSubquery: SQLSubquery {
request.sqlSubquery
}
override func fetchCount(_ db: Database) throws -> Int {
try request.fetchCount(db)
}
override func makePreparedRequest(_ db: Database, forSingleResult singleResult: Bool) throws -> PreparedRequest {
try request.makePreparedRequest(db, forSingleResult: singleResult)
}
}