Files
UUVPN/iOS-SwiftUI-Code/GRDB.swift-6.29.3/GRDB/Core/SQL.swift
T

388 lines
12 KiB
Swift

/// An SQL literal.
///
/// ``SQL`` literals allow you to safely embed raw values in your SQL,
/// without any risk of syntax errors or SQL injection. For example:
///
/// ```swift
/// try dbQueue.write { db in
/// let name: String = "O'Brien"
/// let id: Int64 = 42
/// let query: SQL = "UPDATE player SET name = \(name) WHERE id = \(id)"
///
/// // UPDATE player SET name = 'O''Brien' WHERE id = 42
/// try db.execute(literal: query)
/// }
/// ```
///
/// ## Topics
///
/// ### Creating an SQL Literal from a Literal Value
///
/// - ``init(stringLiteral:)``
/// - ``init(unicodeScalarLiteral:)-7p0al``
/// - ``init(extendedGraphemeClusterLiteral:)-1oham``
///
/// ### Creating an SQL Literal from an Interpolation
///
/// - ``init(stringInterpolation:)``
/// - ``SQLInterpolation``
///
/// ### Creating an SQL Literal from an SQL String
///
/// - ``init(sql:arguments:)``
///
/// ### Creating an SQL Literal from an SQL Expression
///
/// - ``init(_:)``
///
/// ### Concatenating SQL Literals
///
/// - ``append(literal:)``
/// - ``append(sql:arguments:)``
public struct SQL {
/// `SQL.Element` is a component of an `SQL` literal.
///
/// Elements can be qualified with table aliases, and this is how `SQL`
/// blends well in the query interface. See below how the `createdAt` column
/// is qualified with the `player` table in the generated SQL, in order to
/// avoid any conflict with the `team.createdAt` column:
///
/// func date(_ value: SQLSpecificExpressible) -> SQLExpression {
/// // An SQL literal made of three elements:
/// // - "DATE(" raw sql string
/// // - expression
/// // - ")" raw sql string
/// SQL("DATE(\(value))").sqlExpression
/// }
///
/// // SELECT player.*, team.*
/// // FROM player
/// // JOIN team ON team.id = player.teamId
/// // WHERE DATE(player.createdAt) = '2022-08-17'
/// let request = Player
/// .filter(date(Column("createdAt")) == "2022-08-17")
/// .including(required: Player.team)
enum Element {
/// A raw SQL literal with eventual arguments.
case sql(String, StatementArguments = StatementArguments())
/// A subquery.
case subquery(SQLSubquery)
/// An expression.
case expression(SQLExpression)
/// A selection.
case selection(SQLSelection)
/// An ordering.
case ordering(SQLOrdering)
var isEmpty: Bool {
switch self {
case let .sql(sql, _):
return sql.isEmpty
default:
// Subqueries, expressions, selections and orderings are
// assumed to be non-empty.
//
// Nothing prevents the user from creating an ill-formed empty
// expression, but we don't care about such misuse:
//
// // An ill-formed empty expression
// let expression = SQL("").sqlExpression
//
// let sql: SQL = "\(expression)"
// sql.isEmpty // false, deal with it ¯\_(ツ)_/¯
return false
}
}
fileprivate func sql(_ context: SQLGenerationContext) throws -> String {
switch self {
case let .sql(sql, arguments):
if context.append(arguments: arguments) == false {
// We don't know how to look for `?` in sql and
// replace them with literals.
fatalError("Not implemented: turning an SQL parameter into an SQL literal value")
}
return sql
case let .subquery(subquery):
return try subquery.sql(context)
case let .expression(expression):
return try expression.sql(context)
case let .selection(selection):
return try selection.sql(context)
case let .ordering(ordering):
return try ordering.sql(context)
}
}
fileprivate func qualified(with alias: TableAlias) -> Element {
switch self {
case .sql:
// A raw SQL string can't be qualified with a table alias,
// because we can't parse it.
return self
case .subquery:
// Subqueries don't need table alias
return self
case let .expression(expression):
return .expression(expression.qualified(with: alias))
case let .selection(selection):
return .selection(selection.qualified(with: alias))
case let .ordering(ordering):
return .ordering(ordering.qualified(with: alias))
}
}
}
private(set) var elements: [Element]
init(elements: [Element]) {
self.elements = elements
}
/// Creates an `SQL` literal from a plain SQL string, and
/// eventual arguments.
///
/// For example:
///
/// ```swift
/// let query = SQL(
/// sql: "UPDATE player SET name = ? WHERE id = ?",
/// arguments: [name, id])
/// ```
public init(sql: String, arguments: StatementArguments = StatementArguments()) {
self.init(elements: [.sql(sql, arguments)])
}
/// Creates an `SQL` literal from an SQL expression.
///
/// For example:
///
/// ```swift
/// let columnLiteral = SQL(Column("username"))
/// let suffixLiteral = SQL("@example.com".databaseValue)
/// let emailLiteral = [columnLiteral, suffixLiteral].joined(separator: " || ")
/// let request = User.select(emailLiteral.sqlExpression)
/// let emails = try String.fetchAll(db, request)
/// ```
public init(_ expression: some SQLSpecificExpressible) {
self.init(elements: [.expression(expression.sqlExpression)])
}
/// Returns true if this literal generates an empty SQL string
public var isEmpty: Bool {
elements.allSatisfy(\.isEmpty)
}
/// Turn a `SQL` literal into raw SQL and arguments.
///
/// - parameter db: A database connection.
/// - returns: A tuple made of a raw SQL string, and statement arguments.
public func build(_ db: Database) throws -> (sql: String, arguments: StatementArguments) {
let context = SQLGenerationContext(db)
let sql = try self.sql(context)
return (sql: sql, arguments: context.arguments)
}
/// Returns the literal SQL string given an SQL generation context.
func sql(_ context: SQLGenerationContext) throws -> String {
try elements.map { try $0.sql(context) }.joined()
}
func qualified(with alias: TableAlias) -> SQL {
SQL(elements: elements.map { $0.qualified(with: alias) })
}
}
extension SQL {
/// Returns the `SQL` literal produced by the concatenation of two literals.
///
/// For example:
///
/// ```swift
/// let name = "O'Brien"
/// let selection: SQL = "SELECT * FROM player "
/// let condition: SQL = "WHERE name = \(name)"
/// let query = selection + condition
/// ```
public static func + (lhs: SQL, rhs: SQL) -> SQL {
var result = lhs
result += rhs
return result
}
/// Appends an `SQL` literal to the receiver.
///
/// For example:
///
/// ```swift
/// let name = "O'Brien"
/// var query: SQL = "SELECT * FROM player "
/// query += "WHERE name = \(name)"
/// ```
public static func += (lhs: inout SQL, rhs: SQL) {
lhs.elements += rhs.elements
}
/// Appends an `SQL` literal to the receiver.
///
/// For example:
///
/// ```swift
/// let name = "O'Brien"
/// var query: SQL = "SELECT * FROM player "
/// query.append(literal: "WHERE name = \(name)")
/// ```
public mutating func append(literal sqlLiteral: SQL) {
self += sqlLiteral
}
/// Appends a plain SQL string to the receiver, and eventual arguments.
///
/// For example:
///
/// ```swift
/// let name = "O'Brien"
/// var query: SQL = "SELECT * FROM player "
/// query.append(sql: "WHERE name = ?", arguments: [name])
/// ```
public mutating func append(sql: String, arguments: StatementArguments = StatementArguments()) {
self += SQL(sql: sql, arguments: arguments)
}
}
extension SQL: SQLSpecificExpressible {
/// Creates a literal SQL expression.
///
/// Use this property when you need an explicit `SQLExpression`.
/// For example:
///
/// ```swift
/// func date(_ value: some SQLExpressible) -> SQLExpression {
/// SQL("DATE(\(value))").sqlExpression
/// }
///
/// // SELECT * FROM "player" WHERE DATE("createdAt") = '2020-01-23'
/// let createdAt = Column("createdAt")
/// let request = Player.filter(date(createdAt) == "2020-01-23")
/// ```
public var sqlExpression: SQLExpression {
.literal(self)
}
}
extension SQL: SQLSelectable {
/// Creates a literal SQL result column.
///
/// Use this property when you need an explicit `SQLSelection`. For example:
///
/// ```swift
/// // SELECT firstName AS givenName, lastName AS familyName FROM player
/// let selection = SQL("firstName AS givenName, lastName AS familyName").sqlSelection
/// let request = Player.select(selection)
/// ```
public var sqlSelection: SQLSelection {
.literal(self)
}
}
extension SQL: SQLOrderingTerm {
/// Creates a literal SQL ordering term.
///
/// Use this property when you need an explicit `SQLOrdering`. For example:
///
/// ```swift
/// // SELECT * FROM player ORDER BY name DESC
/// let ordering = SQL("name DESC").sqlOrdering
/// let request = Player.order(ordering)
/// ```
///
/// An ordering term is only valid if it represent a single ordering terms.
///
/// ```swift
/// // Valid
/// SQL("score DESC").sqlOrdering
/// SQL("name").sqlOrdering
///
/// // Invalid
/// SQL("score DESC, name").sqlOrdering
/// ```
public var sqlOrdering: SQLOrdering {
.literal(self)
}
}
extension Sequence where Element == SQL {
/// Returns the concatenated `SQL` literal of this sequence of literals,
/// inserting the given raw SQL separator between each element.
///
/// For example:
///
/// ```
/// let components: [SQL] = [
/// "UPDATE player",
/// "SET name = \(name)",
/// "WHERE id = \(id)"
/// ]
/// let query = components.joined(separator: " ")
/// ```
///
/// - Note: The separator is a raw SQL string, not an ``SQL`` literal.
public func joined(separator: String = "") -> SQL {
if separator.isEmpty {
return SQL(elements: flatMap(\.elements))
} else {
return SQL(elements: Array(map(\.elements).joined(separator: CollectionOfOne(.sql(separator)))))
}
}
}
extension Collection where Element == SQL {
/// Returns the concatenated `SQL` literal of this collection of literals,
/// inserting the given raw SQL separator between each element.
///
/// For example:
///
/// ```swift
/// let components: [SQL] = [
/// "UPDATE player",
/// "SET name = \(name)",
/// "WHERE id = \(id)"
/// ]
/// let query = components.joined(separator: " ")
/// ```
///
/// - Note: The separator is a raw SQL string, not an ``SQL`` literal.
public func joined(separator: String = "") -> SQL {
if separator.isEmpty {
return SQL(elements: flatMap(\.elements))
} else {
return SQL(elements: Array(map(\.elements).joined(separator: CollectionOfOne(.sql(separator)))))
}
}
}
// MARK: - ExpressibleByStringInterpolation
extension SQL: ExpressibleByStringInterpolation {
public init(unicodeScalarLiteral: String) {
self.init(sql: unicodeScalarLiteral, arguments: [])
}
public init(extendedGraphemeClusterLiteral: String) {
self.init(sql: extendedGraphemeClusterLiteral, arguments: [])
}
/// Creates an `SQL` literal from the given literal SQL string.
public init(stringLiteral: String) {
self.init(sql: stringLiteral, arguments: [])
}
public init(stringInterpolation sqlInterpolation: SQLInterpolation) {
self.init(elements: sqlInterpolation.elements)
}
}