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

237 lines
7.4 KiB
Swift

/// An SQL request that can decode database rows.
///
/// `SQLRequest` allows you to safely embed raw values in your SQL,
/// without any risk of syntax errors or SQL injection:
///
/// ```swift
/// extension Player: FetchableRecord {
/// static func filter(name: String) -> SQLRequest<Player> {
/// "SELECT * FROM player WHERE name = \(name)"
/// }
///
/// static func maximumScore() -> SQLRequest<Int> {
/// "SELECT MAX(score) FROM player"
/// }
/// }
///
/// try dbQueue.read { db in
/// let players = try Player.filter(name: "O'Brien").fetchAll(db) // [Player]
/// let maxScore = try Player.maximumScore().fetchOne(db) // Int?
/// }
/// ```
///
/// An `SQLRequest` can be created from a string literal or interpolation, as in
/// the above examples, and from the initializers documented below.
///
/// ## Topics
///
/// ### Creating an SQL Request from a Literal Value
///
/// - ``init(stringLiteral:)``
/// - ``init(unicodeScalarLiteral:)-84mq8``
/// - ``init(extendedGraphemeClusterLiteral:)-1sf75``
///
/// ### Creating an SQL Request from an Interpolation
///
/// - ``init(stringInterpolation:)``
///
/// ### Creating an SQL Request from an SQL Literal
///
/// - ``init(literal:adapter:cached:)-4vuxn``
/// - ``init(literal:adapter:cached:)-82f97``
///
/// ### Creating an SQL Request from an SQL String
///
/// - ``init(sql:arguments:adapter:cached:)-3qq8t``
/// - ``init(sql:arguments:adapter:cached:)-5ecx2``
public struct SQLRequest<RowDecoder> {
/// There are two statement caches: one "public" for statements generated by
/// the user, and one "internal" for the statements generated by GRDB. Those
/// are separated so that GRDB has no opportunity to inadvertently modify
/// the arguments of user's cached statements.
enum Cache {
/// The public cache, for library user
case `public`
/// The internal cache, for GRDB
case `internal`
}
/// The row adapter.
public var adapter: (any RowAdapter)?
private(set) var sqlLiteral: SQL
let cache: Cache?
private init(
literal sqlLiteral: SQL,
adapter: (any RowAdapter)?,
fromCache cache: Cache?,
type: RowDecoder.Type)
{
self.sqlLiteral = sqlLiteral
self.adapter = adapter
self.cache = cache
}
}
extension SQLRequest {
/// Creates a request from an SQL string.
///
/// ```swift
/// let request = SQLRequest<String>(sql: """
/// SELECT name FROM player
/// """)
/// let request = SQLRequest<Player>(sql: """
/// SELECT * FROM player WHERE name = ?
/// """, arguments: ["O'Brien"])
/// ```
///
/// - parameters:
/// - sql: An SQL string.
/// - arguments: Statement arguments.
/// - adapter: Optional RowAdapter.
/// - cached: Defaults to false. If true, the request reuses a cached
/// prepared statement.
public init(
sql: String,
arguments: StatementArguments = StatementArguments(),
adapter: (any RowAdapter)? = nil,
cached: Bool = false)
{
self.init(
literal: SQL(sql: sql, arguments: arguments),
adapter: adapter,
fromCache: cached ? .public : nil,
type: RowDecoder.self)
}
/// Creates a request from an ``SQL`` literal.
///
/// ``SQL`` literals allow you to safely embed raw values in your SQL,
/// without any risk of syntax errors or SQL injection:
///
/// ```swift
/// let name = "O'Brien"
/// let request = SQLRequest<Player>(literal: """
/// SELECT * FROM player WHERE name = \(name)
/// """)
/// ```
///
/// - parameters:
/// - sqlLiteral: An `SQL` literal.
/// - adapter: Optional RowAdapter.
/// - cached: Defaults to false. If true, the request reuses a cached
/// prepared statement.
public init(literal sqlLiteral: SQL, adapter: (any RowAdapter)? = nil, cached: Bool = false) {
self.init(
literal: sqlLiteral,
adapter: adapter,
fromCache: cached ? .public : nil,
type: RowDecoder.self)
}
}
extension SQLRequest<Row> {
/// Creates a request of database rows, from an SQL string.
///
/// ```swift
/// let request = SQLRequest(sql: """
/// SELECT * FROM player WHERE name = ?
/// """, arguments: ["O'Brien"])
/// ```
///
/// - parameters:
/// - sql: An SQL string.
/// - arguments: Statement arguments.
/// - adapter: Optional RowAdapter.
/// - cached: Defaults to false. If true, the request reuses a cached
/// prepared statement.
public init(
sql: String,
arguments: StatementArguments = StatementArguments(),
adapter: (any RowAdapter)? = nil,
cached: Bool = false)
{
self.init(
literal: SQL(sql: sql, arguments: arguments),
adapter: adapter,
fromCache: cached ? .public : nil,
type: Row.self)
}
/// Creates a request of database rows, from an ``SQL`` literal.
///
/// ``SQL`` literals allow you to safely embed raw values in your SQL,
/// without any risk of syntax errors or SQL injection:
///
/// ```swift
/// let name = "O'Brien"
/// let request = SQLRequest(literal: """
/// SELECT * FROM player WHERE name = \(name)
/// """)
/// ```
///
/// - parameters:
/// - sqlLiteral: An `SQL` literal.
/// - adapter: Optional RowAdapter.
/// - cached: Defaults to false. If true, the request reuses a cached
/// prepared statement.
public init(literal sqlLiteral: SQL, adapter: (any RowAdapter)? = nil, cached: Bool = false) {
self.init(
literal: sqlLiteral,
adapter: adapter,
fromCache: cached ? .public : nil,
type: Row.self)
}
}
extension SQLRequest: FetchRequest {
public var sqlSubquery: SQLSubquery {
.literal(sqlLiteral)
}
public func fetchCount(_ db: Database) throws -> Int {
try SQLRequest<Int>("SELECT COUNT(*) FROM (\(self))").fetchOne(db)!
}
public func makePreparedRequest(
_ db: Database,
forSingleResult singleResult: Bool = false)
throws -> PreparedRequest
{
let context = SQLGenerationContext(db)
let sql = try sqlLiteral.sql(context)
let statement: Statement
switch cache {
case .none:
statement = try db.makeStatement(sql: sql)
case .public:
statement = try db.cachedStatement(sql: sql)
case .internal:
statement = try db.internalCachedStatement(sql: sql)
}
try statement.setArguments(context.arguments)
return PreparedRequest(statement: statement, adapter: adapter)
}
}
extension SQLRequest: ExpressibleByStringInterpolation {
public init(unicodeScalarLiteral: String) {
self.init(sql: unicodeScalarLiteral)
}
public init(extendedGraphemeClusterLiteral: String) {
self.init(sql: extendedGraphemeClusterLiteral)
}
/// Creates an `SQLRequest` from the given literal SQL string.
public init(stringLiteral: String) {
self.init(sql: stringLiteral)
}
public init(stringInterpolation sqlInterpolation: SQLInterpolation) {
self.init(literal: SQL(stringInterpolation: sqlInterpolation))
}
}