Files
UUVPN/iOS-SwiftUI-Code/GRDB.swift-6.29.3/GRDB/Core/Statement.swift
T
2025-01-28 12:28:03 +08:00

1399 lines
48 KiB
Swift

import Foundation
/// A raw SQLite statement, suitable for the SQLite C API.
public typealias SQLiteStatement = OpaquePointer
extension String {
/// SQL statements are separated by semicolons and white spaces.
///
/// This character set is not an accurate representation of actual SQLite
/// separators (which do not include non-ASCII white spaces for example),
/// and must not be used for parsing. Its only purpose is to trim compiled
/// SQL statements with `String.trimmedSQLStatement`.
private static let sqlStatementSeparators = CharacterSet(charactersIn: ";").union(.whitespacesAndNewlines)
/// Returns a string trimmed from SQL statement separators.
///
/// For example:
///
/// // "SELECT * FROM player"
/// " SELECT * FROM player;".trimmedSQLStatement
///
/// - precondition: the input string is a successfully compiled SQL statement.
var trimmedSQLStatement: String {
trimmingCharacters(in: String.sqlStatementSeparators)
}
}
public final class Statement {
enum TransactionEffect {
case beginTransaction
case commitTransaction
case rollbackTransaction
case beginSavepoint(String)
case releaseSavepoint(String)
case rollbackSavepoint(String)
}
/// The raw SQLite statement, suitable for the SQLite C API.
public let sqliteStatement: SQLiteStatement
/// The SQL query.
public var sql: String {
SchedulingWatchdog.preconditionValidQueue(database)
// trim white space and semicolon for homogeneous output
return String(cString: sqlite3_sql(sqliteStatement)).trimmedSQLStatement
}
/// The column names, ordered from left to right.
public lazy var columnNames: [String] = {
// swiftlint:disable:next redundant_self_in_closure
let sqliteStatement = self.sqliteStatement
return (0..<CInt(columnCount)).map { String(cString: sqlite3_column_name(sqliteStatement, $0)) }
}()
// The database region is reported by `sqlite3_set_authorizer`, and maybe
// refined in `SQLQueryGenerator.makeStatement(_:)` when we have enough
// information about the statement.
/// The database region that the statement looks into.
///
/// The returned region describes the tables and columns read by
/// the statement. It does not describe the columns that the statement
/// writes into. For example:
///
/// ```swift
/// // Reads score, writes bonus
/// let statement = db.makeStatement(sql: """
/// UPDATE player SET bonus = 0 WHERE score = 0
/// """)
///
/// // Prints "player(score)"
/// print(statement.databaseRegion)
/// ```
public internal(set) var databaseRegion = DatabaseRegion()
/// If true, the database schema cache gets invalidated after this statement
/// is executed (reported by `sqlite3_set_authorizer`).
private(set) var invalidatesDatabaseSchemaCache = false
/// The eventual effect of transactions, as reported by `sqlite3_set_authorizer`.
private(set) var transactionEffect: TransactionEffect?
/// The effects on the database (reported by `sqlite3_set_authorizer`).
private(set) var authorizerEventKinds: [DatabaseEventKind] = []
/// A boolean value indicating if the prepared statement makes no direct
/// changes to the content of the database file.
///
/// Related SQLite documentation: <https://www.sqlite.org/c3ref/stmt_readonly.html>.
public var isReadonly: Bool {
sqlite3_stmt_readonly(sqliteStatement) != 0
}
/// A boolean value indicating if the statement can delete some rows.
var canDeleteRows: Bool {
authorizerEventKinds.contains(where: \.isDelete)
}
@usableFromInline
unowned let database: Database
/// Cache for index(ofColumn:). Keys are lowercase.
private lazy var columnIndexes: [String: Int] = {
Dictionary(
columnNames.enumerated().map { ($0.element.lowercased(), $0.offset) },
uniquingKeysWith: { (left, _) in left }) // keep leftmost indexes
}()
/// Creates a prepared statement. Returns nil if the compiled string is
/// blank or empty.
///
/// - parameter database: A database connection.
/// - parameter statementStart: A pointer to a UTF-8 encoded C string
/// containing SQL.
/// - parameter statementEnd: Upon success, the pointer to the next
/// statement in the C string.
/// - parameter prepFlags: Flags for sqlite3_prepare_v3 (available from
/// SQLite 3.20.0, see <http://www.sqlite.org/c3ref/prepare.html>)
/// - throws: DatabaseError in case of compilation error.
required init?(
database: Database,
statementStart: UnsafePointer<CChar>,
statementEnd: UnsafeMutablePointer<UnsafePointer<CChar>?>,
prepFlags: CUnsignedInt) throws
{
SchedulingWatchdog.preconditionValidQueue(database)
// Reset authorizer before preparing the statement
let authorizer = database.authorizer
authorizer.reset()
var sqliteStatement: SQLiteStatement? = nil
let code: CInt
// sqlite3_prepare_v3 was introduced in SQLite 3.20.0 http://www.sqlite.org/changes.html#version_3_20
#if GRDBCUSTOMSQLITE || GRDBCIPHER
code = sqlite3_prepare_v3(
database.sqliteConnection, statementStart, -1, prepFlags,
&sqliteStatement, statementEnd)
#else
if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *) { // SQLite 3.20+
code = sqlite3_prepare_v3(
database.sqliteConnection, statementStart, -1, prepFlags,
&sqliteStatement, statementEnd)
} else {
code = sqlite3_prepare_v2(database.sqliteConnection, statementStart, -1, &sqliteStatement, statementEnd)
}
#endif
guard code == SQLITE_OK else {
throw DatabaseError(
resultCode: code,
message: database.lastErrorMessage,
sql: String(cString: statementStart))
}
guard let sqliteStatement else {
return nil
}
self.database = database
self.sqliteStatement = sqliteStatement
self.databaseRegion = authorizer.selectedRegion
self.invalidatesDatabaseSchemaCache = authorizer.invalidatesDatabaseSchemaCache
self.transactionEffect = authorizer.transactionEffect
self.authorizerEventKinds = authorizer.databaseEventKinds
}
deinit {
sqlite3_finalize(sqliteStatement)
}
// MARK: Arguments
/// Whether arguments are valid and bound inside the SQLite statement.
///
/// If true, arguments are considered valid, and they are bound in
/// the SQLite statement:
///
/// - **Valid**: Arguments match the statement expectations, or the user has
/// called ``setUncheckedArguments(_:)``.
/// - **Bound**: The SQLite bindings are set. String and blob arguments
/// are bound with SQLITE_TRANSIENT (copied and managed by SQLite).
///
/// When false, arguments have not been validated yet, or they are
/// not bound.
///
/// - Not validated yet: this is the initial default (non-validated
/// empty arguments)
///
/// ```swift
/// // Default arguments are empty, argumentsAreValidAndBound is
/// // false. The statement needs one argument.
/// let statement = try db.makeStatement(sql: """
/// INSERT INTO t VALUES (?)
/// """)
///
/// // Because argumentsAreValidAndBound is false, we validate the
/// // empty arguments, and throw SQLITE_MISUSE: wrong number
/// // of statement arguments.
/// try statement.execute()
/// ```
///
/// - Not bound: this is the case after we have performed an optimized
/// execution with temporary bindings that avoid copying strings
/// and blobs:
///
/// ```swift
/// let statement = try db.makeStatement(sql: """
/// INSERT INTO t VALUES (?)
/// """)
/// // Arguments are set, and execution is performed with
/// // temporary bindings.
/// try statement.execute(arguments: ["Hello"])
/// // <- Here statement.arguments is ["Hello"]
/// // <- Here statement.argumentsAreValidAndBound is false
/// ```
///
/// See `withArguments(_:do:)`.
private var argumentsAreValidAndBound = false
/// The statement arguments. They may be bound, or not, in the SQLite
/// statement. See `argumentsAreValidAndBound`.
private var _arguments = StatementArguments()
lazy var sqliteArgumentCount: Int = {
Int(sqlite3_bind_parameter_count(sqliteStatement))
}()
// Returns ["id", nil, "name"] for "INSERT INTO table VALUES (:id, ?, :name)"
fileprivate lazy var sqliteArgumentNames: [String?] = {
(1..<CInt(sqliteArgumentCount + 1)).map {
guard let cString = sqlite3_bind_parameter_name(sqliteStatement, $0) else {
return nil
}
return String(cString: cString + 1) // Drop initial ":", "@", "$"
}
}()
/// The statement arguments.
///
/// For example:
///
/// ```swift
/// // This statement expects two arguments
/// let statement = try db.makeUpdateArgument(sql: """
/// INSERT INTO player (id, name) VALUES (?, ?)
/// """)
///
/// // Set arguments
/// statement.arguments = [1, "Arthur"]
///
/// // Prints [1, "Arthur"]
/// print(statement.arguments)
/// ```
///
/// If is a programmer error to set arguments that do not provide all
/// values expected by the statement:
///
/// ```swift
/// // Fatal error
/// statement.arguments = [1]
/// statement.arguments = [1, "Arthur", Date()]
/// ```
///
/// Prefer ``setArguments(_:)`` when you are not sure that
/// arguments match, because it throws an error instead of raising a
/// fatal error.
public var arguments: StatementArguments {
get { _arguments }
set {
// Force arguments validity: it is a programmer error to provide
// arguments that do not match the statement.
try! setArguments(newValue)
}
}
/// Throws a ``DatabaseError`` of code `SQLITE_ERROR` if the provided
/// arguments do not provide all values expected by the statement.
///
/// For example:
///
/// ```swift
/// // This statement expects two arguments
/// let statement = try db.makeUpdateArgument(sql: """
/// INSERT INTO player (id, name) VALUES (?, ?)
/// """)
///
/// // OK
/// statement.validateArguments([1, "Arthur"])
///
/// // Throws
/// try statement.setArguments([1])
/// try statement.setArguments([1, "Arthur", Date()])
/// ```
///
/// See also ``setArguments(_:)``.
///
/// - throws: A ``DatabaseError`` if `arguments` don't fit the expected ones.
public func validateArguments(_ arguments: StatementArguments) throws {
var arguments = arguments
_ = try arguments.extractBindings(forStatement: self, allowingRemainingValues: false)
}
/// Set arguments without any validation. Trades safety for performance.
///
/// Only call this method if you are sure input arguments provide all
/// values expected by the statement.
///
/// For example:
///
/// ```swift
/// // This statement expects two arguments
/// let statement = try db.makeUpdateArgument(sql: """
/// INSERT INTO player (id, name) VALUES (?, ?)
/// """)
///
/// // OK
/// statement.setUncheckedArguments([1, "Arthur"])
///
/// // OK
/// let arguments = ... // some untrusted arguments
/// try statement.validateArguments(arguments)
/// statement.setUncheckedArguments(arguments)
///
/// // NOT OK
/// statement.setUncheckedArguments([1])
/// statement.setUncheckedArguments([1, "Arthur", Date()])
/// ```
public func setUncheckedArguments(_ arguments: StatementArguments) {
// Reset and bind arguments
try! reset()
_arguments = arguments
argumentsAreValidAndBound = true
clearBindings()
var valuesIterator = arguments.values.makeIterator()
for (index, argumentName) in zip(CInt(1)..., sqliteArgumentNames) {
if let argumentName, let value = arguments.namedValues[argumentName] {
bind(value, at: index)
} else if let value = valuesIterator.next() {
bind(value, at: index)
}
}
}
/// Validates and sets the statement arguments.
///
/// This method throws a ``DatabaseError`` of code `SQLITE_MISUSE` if
/// the provided arguments do not provide all values expected by
/// the statement.
///
/// For example:
///
/// ```swift
/// // This statement expects two arguments
/// let statement = try db.makeUpdateArgument(sql: """
/// INSERT INTO player (id, name) VALUES (?, ?)
/// """)
///
/// // OK
/// try statement.setArguments([1, "Arthur"])
///
/// // Throws
/// try statement.setArguments([1])
/// try statement.setArguments([1, "Arthur", Date()])
/// ```
///
/// - throws: A ``DatabaseError`` if `arguments` don't fit the expected ones.
public func setArguments(_ arguments: StatementArguments) throws {
// Validate
var consumedArguments = arguments
let bindings = try consumedArguments.extractBindings(forStatement: self, allowingRemainingValues: false)
// Reset and bind arguments
try reset()
_arguments = arguments
argumentsAreValidAndBound = true
clearBindings()
for (index, dbValue) in zip(CInt(1)..., bindings) {
bind(dbValue, at: index)
}
}
/// Resets, sets arguments, and calls the given closure after performing
/// temporary bindings that avoid copying strings and blobs.
///
/// The bindings are valid only during the execution of this method.
/// After it returns, the SQLite statement bindings are cleared (but the
/// statement arguments are set).
func withArguments<T>(_ arguments: StatementArguments, do body: () throws -> T) throws -> T {
// Validate
var consumedArguments = arguments
let bindings = try consumedArguments.extractBindings(forStatement: self, allowingRemainingValues: false)
// Reset and bind arguments (temporarily)
try reset()
_arguments = arguments
argumentsAreValidAndBound = false
clearBindings()
defer {
// Don't leave the SQLite statement in an invalid state
// (temporary bindings that point to undefined memory).
clearBindings()
}
return try withBindings(bindings, to: sqliteStatement, do: body)
}
// 1-based index
func bind(_ value: some StatementBinding, at index: CInt) {
let code = value.bind(to: sqliteStatement, at: index)
// It looks like sqlite3_bind_xxx() functions do not access the file system.
// They should thus succeed, unless a GRDB bug: there is no point throwing any error.
guard code == SQLITE_OK else {
fatalError(DatabaseError(resultCode: code, message: database.lastErrorMessage, sql: sql))
}
}
// Don't make this one public unless we keep the arguments property in sync.
func clearBindings() {
// It looks like sqlite3_clear_bindings() does not access the file system.
// This function call should thus succeed, unless a GRDB bug: there is
// no point throwing any error.
let code = sqlite3_clear_bindings(sqliteStatement)
guard code == SQLITE_OK else {
fatalError(DatabaseError(resultCode: code, message: database.lastErrorMessage, sql: sql))
}
}
// MARK: Execution
func reset() throws {
SchedulingWatchdog.preconditionValidQueue(database)
let code = sqlite3_reset(sqliteStatement)
guard code == SQLITE_OK else {
throw DatabaseError(resultCode: code, message: database.lastErrorMessage, sql: sql)
}
}
/// Convenience method that resets, sets arguments if needed, and checks
/// arguments validity.
///
/// - parameter newArguments: if not nil, this method sets arguments.
func prepareExecution(withArguments newArguments: StatementArguments? = nil) throws {
if let newArguments {
try setArguments(newArguments) // calls reset()
return
}
if argumentsAreValidAndBound {
try reset()
} else {
// Arguments needs to be validated, or bound.
if arguments.isEmpty {
// Only reset and perform validation.
try reset()
try validateArguments(arguments)
} else {
// The `setArguments` method binds and validates, and that's
// exactly what we want to do.
//
// To get there, perform statement.execute() after
// statement.execute(arguments:):
//
// // Step 1
// // Optimized execution with temporary bindings in order
// // to avoid copying strings and blobs: after execution,
// // arguments are set, but bindings have been cleared,
// // and argumentsAreValidAndBound is false.
// try statement.execute(arguments: StatementArguments(person)!)
//
// // Step 2 (we are here). Stop using temporary
// // bindings because user explicitly opt ins for
// // permanent ones.
// try statement.execute()
try setArguments(arguments) // calls reset()
}
}
}
/// Executes the prepared statement.
///
/// For example:
///
/// ```swift
/// try dbQueue.write { db in
/// // Statement without argument
/// let statement = try db.makeStatement(sql: """
/// CREATE TABLE player (
/// id INTEGER PRIMARY KEY AUTOINCREMENT,
/// name TEXT NOT NULL
/// )
/// """)
/// try statement.execute()
/// }
///
/// try dbQueue.write { db in
/// // Statement with argument
/// let statement = try db.makeStatement(sql: """
/// INSERT INTO player (name) VALUES (?)
/// """)
///
/// // Set argument and execute
/// try statement.setArguments(["Arthur"])
/// try statement.execute()
///
/// // Set argument and execute in one shot
/// try statement.execute(arguments: ["Barbara"])
/// }
/// ```
///
/// When arguments are set at the moment of execution, with an non-nil
/// `arguments` parameter, it is assumed that the statement won't be
/// reused with the same arguments. When the number of arguments is
/// small, execution is performed with temporary SQLite bindings that
/// avoid copying strings and blobs arguments.
///
/// For more information, see [`SQLITE_STATIC` and `SQLITE_TRANSIENT`](https://www.sqlite.org/c3ref/c_static.html).
/// Compare:
///
/// ```swift
/// // Uses SQLITE_STATIC if there are few arguments,
/// // SQLITE_TRANSIENT otherwise.
/// try statement.execute(arguments: ["Barbara"])
///
/// // Always uses SQLITE_TRANSIENT
/// try statement.setArguments(["Arthur"])
/// try statement.execute()
/// ```
///
/// Both techniques have the same results, but when you care about
/// performances, monitor your application in order to make the
/// best choice.
///
/// - parameter arguments: Optional statement arguments.
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs.
public func execute(arguments: StatementArguments? = nil) throws {
if let arguments {
// Assume that the statement won't be reused with the same arguments.
//
// Avoid a stack overflow, and don't perform an unbounded nesting
// of `withBinding(to:at:do:)` methods: only use temporary bindings
// for less than 20 arguments. This number 20 is completely
// arbitrary!
// See <https://forums.swift.org/t/avoiding-stack-overflow-when-nesting-string-withcstring-how-to-handle-an-arbitrary-number-of-temp-values-in-general/63663>
if sqliteArgumentCount <= 20 {
// Perform an optimized execution with temporary bindings
// in order to avoid copying strings and blobs.
try withArguments(arguments) {
try executeAllSteps()
}
} else {
try setArguments(arguments)
try executeAllSteps()
}
} else {
try prepareExecution()
try executeAllSteps()
}
}
private func executeAllSteps() throws {
try database.statementWillExecute(self)
// Iterate all rows, since they may execute side effects.
while true {
switch sqlite3_step(sqliteStatement) {
case SQLITE_DONE:
try database.statementDidExecute(self)
return
case SQLITE_ROW:
break
case let code:
try database.statementDidFail(self, withResultCode: code)
}
}
}
/// Calls the given closure after each successful call to `sqlite3_step()`.
///
/// This method is slighly faster than calling `step(_:)` repeatedly, due
/// to the single `sqlite3_stmt_busy` check.
@usableFromInline
func forEachStep(_ body: (SQLiteStatement) throws -> Void) throws {
SchedulingWatchdog.preconditionValidQueue(database)
if sqlite3_stmt_busy(sqliteStatement) == 0 {
try database.statementWillExecute(self)
}
while true {
switch sqlite3_step(sqliteStatement) {
case SQLITE_DONE:
try database.statementDidExecute(self)
return
case SQLITE_ROW:
try body(sqliteStatement)
case let code:
try database.statementDidFail(self, withResultCode: code)
}
}
}
/// Calls the given closure after one successful call to `sqlite3_step()`.
@usableFromInline
func step<T>(_ body: (SQLiteStatement) throws -> T) throws -> T? {
if sqlite3_stmt_busy(sqliteStatement) == 0 {
try database.statementWillExecute(self)
}
switch sqlite3_step(sqliteStatement) {
case SQLITE_DONE:
try database.statementDidExecute(self)
return nil
case SQLITE_ROW:
return try body(sqliteStatement)
case let code:
try database.statementDidFail(self, withResultCode: code)
}
}
}
// Explicit non-conformance to Sendable: statements must be used from
// a serialized database access dispatch queue.
@available(*, unavailable)
extension Statement: Sendable { }
extension Statement: CustomStringConvertible {
public var description: String {
SchedulingWatchdog.allows(database) ? sql : "Statement"
}
}
// MARK: - Select Statements
extension Statement {
/// The number of columns in the resulting rows.
public var columnCount: Int {
Int(sqlite3_column_count(self.sqliteStatement))
}
/// Returns the index of the leftmost column with the given name.
///
/// This method is case-insensitive.
public func index(ofColumn name: String) -> Int? {
columnIndexes[name.lowercased()]
}
/// Creates a cursor over the statement which does not produce any
/// value. Each call to the next() cursor method calls the sqlite3_step()
/// C function.
func makeCursor(arguments: StatementArguments? = nil) throws -> StatementCursor {
try StatementCursor(statement: self, arguments: arguments)
}
}
// MARK: - Cursors
/// A cursor that lazily iterates the results of a prepared ``Statement``.
///
/// ## Overview
///
/// To get a `DatabaseCursor` instance, use one of the `fetchCursor` methods.
/// For example:
///
/// - A cursor of ``Row`` built from a prepared ``Statement``:
///
/// ```swift
/// try dbQueue.read { db in
/// let statement = try db.makeStatement(sql: "SELECT * FROM player")
/// let rows = try Row.fetchCursor(statement)
/// while let row = try rows.next() {
/// let id: Int64 = row["id"]
/// let name: String = row["name"]
/// }
/// }
/// ```
///
/// - A cursor of `Int` built from an SQL string (see ``DatabaseValueConvertible``):
///
/// ```swift
/// try dbQueue.read { db in
/// let sql = "SELECT score FROM player"
/// let scores = try Int.fetchCursor(db, sql: sql)
/// while let score = try scores.next() {
/// print(score)
/// }
/// }
/// ```
///
/// - A cursor of `Player` records built from a request (see ``FetchableRecord`` and ``FetchRequest``):
///
/// ```swift
/// try dbQueue.read { db in
/// let request = Player.all()
/// let players = try request.fetchCursor(db)
/// while let player = try players.next() {
/// print(player.name, player.score)
/// }
/// }
/// ```
///
/// A database cursor is valid only during the current database access (read or
/// write). Do not store or escape a cursor for later use.
///
/// A database cursor resets its underlying prepared statement with
/// [`sqlite3_reset`](https://www.sqlite.org/c3ref/reset.html) when the cursor
/// is created, and when it is deallocated. Don't share the same prepared
/// statement between two cursors!
public protocol DatabaseCursor: Cursor {
/// Must be initialized to false.
var _isDone: Bool { get set }
/// The statement iterated by the cursor
var _statement: Statement { get }
/// Called after one successful call to `sqlite3_step()`. Returns the
/// element for the current statement step.
func _element(sqliteStatement: SQLiteStatement) throws -> Element
}
// Read-only access to statement information. We don't want the user to modify
// a statement through a cursor, in case this would mess with the cursor state.
extension DatabaseCursor {
/// The SQL query.
public var sql: String { _statement.sql }
/// The statement arguments.
public var arguments: StatementArguments { _statement.arguments }
/// The column names, ordered from left to right.
public var columnNames: [String] { _statement.columnNames }
/// The number of columns in the resulting rows.
public var columnCount: Int { _statement.columnCount }
/// The database region that the cursor looks into.
public var databaseRegion: DatabaseRegion { _statement.databaseRegion }
}
extension DatabaseCursor {
@inlinable
public func next() throws -> Element? {
if _isDone {
return nil
}
if let element = try _statement.step(_element) {
return element
}
_isDone = true
return nil
}
// Specific implementation of `forEach`, for a slight performance
// improvement due to the single `sqlite3_stmt_busy` check.
@inlinable
public func forEach(_ body: (Element) throws -> Void) throws {
if _isDone { return }
try _statement.forEachStep {
try body(_element(sqliteStatement: $0))
}
_isDone = true
}
}
/// A cursor that iterates a database statement without producing any value.
/// Each call to the `next()` method calls the `sqlite3_step()` C function.
///
/// For example:
///
/// ```swift
/// try dbQueue.read { db in
/// let statement = try db.makeStatement(sql: "SELECT performSideEffect()")
/// let cursor = statement.makeCursor()
/// try cursor.next()
/// }
/// ```
final class StatementCursor: DatabaseCursor {
typealias Element = Void
let _statement: Statement
var _isDone = false
// Use Statement.makeCursor() instead
init(statement: Statement, arguments: StatementArguments? = nil) throws {
self._statement = statement
// Assume cursor is created for immediate iteration: reset and set arguments
try statement.prepareExecution(withArguments: arguments)
}
deinit {
// Statement reset fails when sqlite3_step has previously failed.
// Just ignore reset error.
try? _statement.reset()
}
@inlinable
func _element(sqliteStatement: SQLiteStatement) throws { }
}
// MARK: - Update Statements
extension Statement {
var releasesDatabaseLock: Bool {
guard let transactionEffect else {
return false
}
switch transactionEffect {
case .commitTransaction, .rollbackTransaction,
.releaseSavepoint, .rollbackSavepoint:
// Not technically correct:
// - ROLLBACK TRANSACTION TO SAVEPOINT does not release any lock
// - RELEASE SAVEPOINT does not always release lock
//
// But both move in the direction of releasing locks :-)
return true
default:
return false
}
}
}
// MARK: - StatementBinding
/// A type that can bind a statement argument.
///
/// Related SQLite documentation: <https://www.sqlite.org/c3ref/bind_blob.html>
public protocol StatementBinding {
/// Binds a statement argument.
///
/// - parameter sqliteStatement: An SQLite statement.
/// - parameter index: 1-based index to statement arguments.
/// - returns: the code returned by the `sqlite3_bind_xxx` function.
func bind(to sqliteStatement: SQLiteStatement, at index: CInt) -> CInt
}
/// Helper function for `withBinding(to:at:do:)` methods.
func checkBindingSuccess(code: CInt, sqliteStatement: SQLiteStatement) throws {
if code == SQLITE_OK { return }
let message = String(cString: sqlite3_errmsg(sqlite3_db_handle(sqliteStatement)))
let sql = String(cString: sqlite3_sql(sqliteStatement)).trimmedSQLStatement
throw DatabaseError(resultCode: code, message: message, sql: sql)
}
/// Calls the given closure after performing temporary bindings that avoid
/// copying strings and blobs.
///
/// The bindings are valid only during the execution of this method.
///
/// - parameter bindings: The bindings
/// - parameter sqliteStatement: The SQLite statement
/// - parameter index: The index of the first binding.
/// - parameter body: The closure to execute when arguments are bound.
@usableFromInline
func withBindings<C, T>(
_ bindings: C,
to sqliteStatement: SQLiteStatement,
from index: CInt = 1,
do body: () throws -> T)
throws -> T
where C: Collection, C.Element == DatabaseValue
{
guard let binding = bindings.first else {
return try body()
}
return try binding.withBinding(to: sqliteStatement, at: index) {
try withBindings(
bindings.dropFirst(),
to: sqliteStatement,
from: index + 1,
do: body)
}
}
// MARK: - StatementArguments
/// An instance of `StatementArguments` provides the values for argument
/// placeholders in a prepared `Statement`.
///
/// Argument placeholders can take several forms in SQL queries (see
/// <https://www.sqlite.org/lang_expr.html#varparam> for more information):
///
/// - `?NNN` (e.g. `?2`): the NNN-th argument (starts at 1)
/// - `?`: the N-th argument, where N is one greater than the largest argument
/// number already assigned
/// - `:AAAA` (e.g. `:name`): named argument
/// - `@AAAA` (e.g. `@name`): named argument
/// - `$AAAA` (e.g. `$name`): named argument
///
/// All forms are supported, but GRDB does not allow to distinguish between
/// the `:AAAA`, `@AAAA`, and `$AAAA` syntaxes. You are encouraged to write
/// named arguments with a colon prefix: `:name`.
///
/// ## Positional Arguments
///
/// To fill question marks placeholders, feed `StatementArguments` with an array:
///
/// ```swift
/// try db.execute(
/// sql: "INSERT INTO player (name, score) VALUES (?, ?)",
/// arguments: StatementArguments(["Arthur", 41]))
///
/// // Array literals are automatically converted:
/// try db.execute(
/// sql: "INSERT INTO player (name, score) VALUES (?, ?)",
/// arguments: ["Arthur", 41])
/// ```
///
/// ## Named Arguments
///
/// To fill named arguments, feed `StatementArguments` with a dictionary:
///
/// ```swift
/// try db.execute(
/// sql: "INSERT INTO player (name, score) VALUES (:name, :score)",
/// arguments: StatementArguments(["name": "Arthur", "score": 41]))
///
/// // Dictionary literals are automatically converted:
/// try db.execute(
/// sql: "INSERT INTO player (name, score) VALUES (:name, :score)",
/// arguments: ["name": "Arthur", "score": 41])
/// ```
///
/// ## Concatenating Arguments
///
/// Several arguments can be concatenated and mixed with the
/// ``append(contentsOf:)`` method and the `+`, `&+`, `+=` operators:
///
/// ```swift
/// var arguments: StatementArguments = ["Arthur"]
/// arguments += [41]
/// try db.execute(
/// sql: "INSERT INTO player (name, score) VALUES (?, ?)",
/// arguments: arguments)
/// ```
///
/// The `+` and `+=` operators consider that overriding named arguments is a
/// programmer error:
///
/// ```swift
/// var arguments: StatementArguments = ["name": "Arthur"]
///
/// // fatal error: already defined statement argument: name
/// arguments += ["name": "Barbara"]
/// ```
///
/// On the other side, `&+` and ``append(contentsOf:)`` allow overriding
/// named arguments:
///
/// ```swift
/// var arguments: StatementArguments = ["name": "Arthur"]
/// arguments = arguments &+ ["name": "Barbara"]
///
/// // Prints ["name": "Barbara"]
/// print(arguments)
/// ```
///
/// ## Mixed Arguments
///
/// It is possible to mix named and positional arguments. Yet this is usually
/// confusing, and it is best to avoid this practice:
///
/// ```swift
/// let sql = "SELECT ?2 AS two, :foo AS foo, ?1 AS one, :foo AS foo2, :bar AS bar"
/// var arguments: StatementArguments = [1, 2, "bar"] + ["foo": "foo"]
/// let row = try Row.fetchOne(db, sql: sql, arguments: arguments)!
///
/// // Prints [two:2 foo:"foo" one:1 foo2:"foo" bar:"bar"]
/// print(row)
/// ```
///
/// Mixed arguments exist as a support for requests like the following:
///
/// ```swift
/// let players = try Player
/// .filter(sql: "team = :team", arguments: ["team": "Blue"])
/// .filter(sql: "score > ?", arguments: [1000])
/// .fetchAll(db)
/// ```
public struct StatementArguments: Hashable {
private(set) var values: [DatabaseValue]
private(set) var namedValues: [String: DatabaseValue]
public var isEmpty: Bool {
values.isEmpty && namedValues.isEmpty
}
// MARK: Empty Arguments
/// Creates an empty `StatementArguments`.
public init() {
values = .init()
namedValues = .init()
}
// MARK: Positional Arguments
/// Creates a `StatementArguments` from a sequence of values.
///
/// For example:
///
/// ```swift
/// let values: [(any DatabaseValueConvertible)?] = ["foo", 1, nil]
/// db.execute(sql: "INSERT ... (?,?,?)", arguments: StatementArguments(values))
/// ```
public init<S>(_ sequence: S)
where S: Sequence, S.Element == (any DatabaseValueConvertible)?
{
values = sequence.map { $0?.databaseValue ?? .null }
namedValues = .init()
}
/// Creates a `StatementArguments` from a sequence of values.
///
/// For example:
///
/// ```swift
/// let values: [String] = ["foo", "bar"]
/// db.execute(sql: "INSERT ... (?,?)", arguments: StatementArguments(values))
/// ```
public init<S>(_ sequence: S)
where S: Sequence, S.Element: DatabaseValueConvertible
{
values = sequence.map(\.databaseValue)
namedValues = .init()
}
/// Creates a `StatementArguments` from an array.
///
/// The result is nil unless all array elements conform to the
/// ``DatabaseValueConvertible`` protocol.
public init?(_ array: [Any]) {
var values = [(any DatabaseValueConvertible)?]()
for value in array {
guard let dbValue = DatabaseValue(value: value) else {
return nil
}
values.append(dbValue)
}
self.init(values)
}
private mutating func set(databaseValues: [DatabaseValue]) {
self.values = databaseValues
namedValues.removeAll(keepingCapacity: true)
}
// MARK: Named Arguments
/// Creates a `StatementArguments` of named arguments from a dictionary.
///
/// For example:
///
/// ```swift
/// let values: [String: (any DatabaseValueConvertible)?] = ["firstName": nil, "lastName": "Miller"]
/// db.execute(sql: "INSERT ... (:firstName, :lastName)", arguments: StatementArguments(values))
/// ```
public init(_ dictionary: [String: (any DatabaseValueConvertible)?]) {
namedValues = dictionary.mapValues { $0?.databaseValue ?? .null }
values = .init()
}
/// Creates a `StatementArguments` of named arguments from a sequence of
/// (key, value) pairs.
public init<S>(_ sequence: S)
where S: Sequence, S.Element == (String, (any DatabaseValueConvertible)?)
{
namedValues = .init(minimumCapacity: sequence.underestimatedCount)
for (key, value) in sequence {
namedValues[key] = value?.databaseValue ?? .null
}
values = .init()
}
/// Creates a `StatementArguments` from a dictionary.
///
/// The result is nil unless all dictionary keys are strings, and values
/// adopt DatabaseValueConvertible.
///
/// - parameter dictionary: A dictionary.
public init?(_ dictionary: [AnyHashable: Any]) {
var initDictionary = [String: (any DatabaseValueConvertible)?]()
for (key, value) in dictionary {
guard let columnName = key as? String else {
return nil
}
guard let dbValue = DatabaseValue(value: value) else {
return nil
}
initDictionary[columnName] = dbValue
}
self.init(initDictionary)
}
// MARK: Adding arguments
/// Appends statement arguments.
///
/// Positional arguments are concatenated:
///
/// ```swift
/// var arguments: StatementArguments = [1]
/// arguments.append(contentsOf: [2, 3])
///
/// // Prints [1, 2, 3]
/// print(arguments)
/// ```
///
/// Named arguments are inserted or updated:
///
/// ```swift
/// var arguments: StatementArguments = ["foo": 1]
/// arguments.append(contentsOf: ["bar": 2])
///
/// // Prints ["foo": 1, "bar": 2]
/// print(arguments)
/// ```
///
/// Named arguments that were replaced, if any, are returned:
///
/// ```swift
/// var arguments: StatementArguments = ["foo": 1, "bar": 2]
/// let replacedValues = arguments.append(contentsOf: ["foo": 3])
///
/// // Prints ["foo": 3, "bar": 2]
/// print(arguments)
///
/// // Prints ["foo": 1]
/// print(replacedValues)
/// ```
///
/// You can mix named and positional arguments (see the documentation of
/// the ``StatementArguments`` type for more information about
/// mixed arguments):
///
/// ```swift
/// var arguments: StatementArguments = ["foo": 1]
/// arguments.append(contentsOf: [2, 3])
///
/// // Prints ["foo": 1, 2, 3]
/// print(arguments)
/// ```
public mutating func append(contentsOf arguments: StatementArguments) -> [String: DatabaseValue] {
var replacedValues: [String: DatabaseValue] = [:]
values.append(contentsOf: arguments.values)
for (name, value) in arguments.namedValues {
if let replacedValue = namedValues.updateValue(value, forKey: name) {
replacedValues[name] = replacedValue
}
}
return replacedValues
}
/// Creates a new `StatementArguments` by extending the left-hand size
/// arguments with the right-hand side arguments.
///
/// Positional arguments are concatenated:
///
/// ```swift
/// let arguments: StatementArguments = [1] + [2, 3]
///
/// // Prints [1, 2, 3]
/// print(arguments)
/// ```
///
/// Named arguments are inserted:
///
/// ```swift
/// let arguments: StatementArguments = ["foo": 1] + ["bar": 2]
///
/// // Prints ["foo": 1, "bar": 2]
/// print(arguments)
/// ```
///
/// If the arguments on the right-hand side has named parameters that are
/// already defined on the left, a fatal error is raised:
///
/// ```swift
/// let arguments: StatementArguments = ["foo": 1] + ["foo": 2]
/// // fatal error: already defined statement argument: foo
/// ```
///
/// This fatal error can be avoided with the &+ operator, or the
/// ``append(contentsOf:)`` method.
///
/// You can mix named and positional arguments (see the documentation of
/// the ``StatementArguments`` type for more information about
/// mixed arguments):
///
/// ```swift
/// let arguments: StatementArguments = ["foo": 1] + [2, 3]
///
/// // Prints ["foo": 1, 2, 3]
/// print(arguments)
/// ```
public static func + (lhs: StatementArguments, rhs: StatementArguments) -> StatementArguments {
var lhs = lhs
lhs += rhs
return lhs
}
/// Creates a new `StatementArguments` by extending the left-hand size
/// arguments with the right-hand side arguments.
///
/// Positional arguments are concatenated:
///
/// ```swift
/// let arguments: StatementArguments = [1] &+ [2, 3]
///
/// // Prints [1, 2, 3]
/// print(arguments)
/// ```
///
/// Named arguments are inserted or updated:
///
/// ```swift
/// let arguments: StatementArguments = ["foo": 1] &+ ["bar": 2]
///
/// // Prints ["foo": 1, "bar": 2]
/// print(arguments)
/// ```
///
/// If a named arguments is defined in both arguments, the right-hand
/// side wins:
///
/// ```swift
/// let arguments: StatementArguments = ["foo": 1] &+ ["foo": 2]
///
/// // Prints ["foo": 2]
/// print(arguments)
/// ```
///
/// You can mix named and positional arguments (see the documentation of
/// the ``StatementArguments`` type for more information about
/// mixed arguments):
///
/// ```swift
/// let arguments: StatementArguments = ["foo": 1] &+ [2, 3]
/// // Prints ["foo": 1, 2, 3]
/// print(arguments)
/// ```
public static func &+ (lhs: StatementArguments, rhs: StatementArguments) -> StatementArguments {
var lhs = lhs
_ = lhs.append(contentsOf: rhs)
return lhs
}
/// Extends the left-hand size arguments with the right-hand side arguments.
///
/// Positional arguments are concatenated:
///
/// ```swift
/// var arguments: StatementArguments = [1]
/// arguments += [2, 3]
///
/// // Prints [1, 2, 3]
/// print(arguments)
/// ```
///
/// Named arguments are inserted:
///
/// ```swift
/// var arguments: StatementArguments = ["foo": 1]
/// arguments += ["bar": 2]
///
/// // Prints ["foo": 1, "bar": 2]
/// print(arguments)
/// ```
///
/// If the arguments on the right-hand side has named parameters that are
/// already defined on the left, a fatal error is raised:
///
/// ```swift
/// var arguments: StatementArguments = ["foo": 1]
///
/// // fatal error: already defined statement argument: foo
/// arguments += ["foo": 2]
/// ```
///
/// This fatal error can be avoided with the &+ operator, or the
/// ``append(contentsOf:)`` method.
///
/// You can mix named and positional arguments (see the documentation of
/// the ``StatementArguments`` type for more information about
/// mixed arguments):
///
/// ```swift
/// var arguments: StatementArguments = ["foo": 1]
/// arguments.append(contentsOf: [2, 3])
///
/// // Prints ["foo": 1, 2, 3]
/// print(arguments)
/// ```
public static func += (lhs: inout StatementArguments, rhs: StatementArguments) {
let replacedValues = lhs.append(contentsOf: rhs)
GRDBPrecondition(
replacedValues.isEmpty,
"already defined statement argument: \(replacedValues.keys.joined(separator: ", "))")
}
// MARK: Not Public
mutating func extractBindings(
forStatement statement: Statement,
allowingRemainingValues: Bool)
throws -> [DatabaseValue]
{
var iterator = values.makeIterator()
var consumedValuesCount = 0
let bindings = try statement.sqliteArgumentNames.map { argumentName -> DatabaseValue in
if let argumentName {
if let dbValue = namedValues[argumentName] {
return dbValue
} else if let value = iterator.next() {
consumedValuesCount += 1
return value
} else {
throw DatabaseError(
resultCode: .SQLITE_MISUSE,
message: "missing statement argument: \(argumentName)",
sql: statement.sql)
}
} else if let value = iterator.next() {
consumedValuesCount += 1
return value
} else {
throw DatabaseError(
resultCode: .SQLITE_MISUSE,
message: "wrong number of statement arguments: \(values.count)",
sql: statement.sql)
}
}
if !allowingRemainingValues && iterator.next() != nil {
throw DatabaseError(
resultCode: .SQLITE_MISUSE,
message: "wrong number of statement arguments: \(values.count)",
sql: statement.sql)
}
if consumedValuesCount == values.count {
values.removeAll()
} else {
values = Array(values[consumedValuesCount...])
}
return bindings
}
}
extension StatementArguments: ExpressibleByArrayLiteral {
/// Creates a `StatementArguments` from an array literal.
///
/// For example:
///
/// ```swift
/// let arguments: StatementArguments = ["Arthur", 41]
/// try db.execute(
/// sql: "INSERT INTO player (name, score) VALUES (?, ?)"
/// arguments: arguments)
/// ```
public init(arrayLiteral elements: (any DatabaseValueConvertible)?...) {
self.init(elements)
}
}
extension StatementArguments: ExpressibleByDictionaryLiteral {
/// Creates a `StatementArguments` from a dictionary literal.
///
/// For example:
///
/// ```swift
/// let arguments: StatementArguments = ["name": "Arthur", "score": 41]
/// try db.execute(
/// sql: "INSERT INTO player (name, score) VALUES (:name, :score)"
/// arguments: arguments)
/// ```
public init(dictionaryLiteral elements: (String, (any DatabaseValueConvertible)?)...) {
self.init(elements)
}
}
extension StatementArguments: CustomStringConvertible {
public var description: String {
let valuesDescriptions = values.map(\.description)
let namedValuesDescriptions = namedValues.map { (key, value) in
"\(String(reflecting: key)): \(value)"
}
return "[" + (namedValuesDescriptions + valuesDescriptions).joined(separator: ", ") + "]"
}
}
extension StatementArguments: Sendable { }