import Foundation /// A protocol that defines a prepared SQLite statement. /// /// Conforming types manage the statement's lifetime, including initialization and finalization. /// The protocol exposes facilities for parameter discovery and binding, stepping, resetting, and /// reading result columns. /// /// ## Topics /// /// ### Retrieving Statement SQL /// /// - ``sql`` /// - ``expandedSQL`` /// /// ### Binding Parameters /// /// - ``parameterCount()`` /// - ``parameterIndexBy(_:)`` /// - ``parameterNameBy(_:)`` /// - ``bind(_:at:)-(SQLiteValue,_)`` /// - ``bind(_:by:)-(SQLiteValue,_)`` /// - ``bind(_:at:)-(T?,_)`` /// - ``bind(_:by:)-(T?,_)`` /// - ``bind(_:)`` /// - ``clearBindings()`` /// /// ### Statement Execution /// /// - ``step()`` /// - ``reset()`` /// - ``execute(_:)`` /// /// ### Result Set /// /// - ``columnCount()`` /// - ``columnName(at:)`` /// - ``columnValue(at:)->SQLiteValue`` /// - ``columnValue(at:)->T?`` /// - ``currentRow()`` public protocol StatementProtocol { // MARK: - Retrieving Statement SQL /// The original SQL text used to create this prepared statement. /// /// Returns the statement text as it was supplied when the statement was prepared. Useful for /// diagnostics or query introspection. var sql: String? { get } /// The SQL text with all parameter values expanded into their literal forms. /// /// Shows the actual SQL string that would be executed after parameter binding. Useful for /// debugging and logging queries. var expandedSQL: String? { get } // MARK: - Binding Parameters /// Returns the number of parameters in the prepared SQLite statement. /// /// This value corresponds to the highest parameter index in the compiled SQL statement. /// Parameters may be specified using anonymous placeholders (`?`), numbered placeholders /// (`?NNN`), or named placeholders (`:name`, `@name`, `$name`). /// /// For statements using only `?` or named parameters, this value equals the number of parameters. /// However, if numbered placeholders are used, the sequence may contain gaps — for example, /// a statement containing `?2` and `?5` will report a parameter count of `5`. /// /// - Returns: The index of the largest (rightmost) parameter in the prepared statement. /// /// - SeeAlso: [Number Of SQL Parameters](https://sqlite.org/c3ref/bind_parameter_count.html) func parameterCount() -> Int32 /// Returns the index of a parameter identified by its name. /// /// The `name` must exactly match the placeholder used in the SQL statement, including its /// prefix character (`:`, `@`, or `$`). For example, if the SQL includes `WHERE id = :id`, /// you must call `parameterIndexBy(":id")`. /// /// If no parameter with the specified `name` exists in the prepared statement, this function /// returns `0`. /// /// - Parameter name: The parameter name as written in the SQL statement, including its prefix. /// - Returns: The 1-based parameter index corresponding to `name`, or `0` if not found. /// /// - SeeAlso: [Index Of A Parameter With A Given Name](https://sqlite.org/c3ref/bind_parameter_index.html) func parameterIndexBy(_ name: String) -> Int32 /// Returns the name of the parameter at the specified index. /// /// The returned string matches the placeholder as written in the SQL statement, including its /// prefix (`:`, `@`, or `$`). For positional (unnamed) parameters, or if the `index` is out of /// range, this function returns `nil`. /// /// - Parameter index: A 1-based parameter index. /// - Returns: The parameter name as written in the SQL statement, or `nil` if unavailable. /// /// - SeeAlso: [Name Of A Host Parameter](https://sqlite.org/c3ref/bind_parameter_name.html) func parameterNameBy(_ index: Int32) -> String? /// Binds a raw SQLite value to a parameter at the specified index. /// /// Assigns the given `SQLiteValue` to the parameter at the provided 1-based index within the /// prepared statement. If the index is out of range, or if the statement is invalid or /// finalized, this function throws an error. /// /// - Parameters: /// - value: The `SQLiteValue` to bind to the parameter. /// - index: The 1-based index of the parameter to bind. /// - Throws: ``SQLiteError`` if the value cannot be bound (e.g., index out of range). /// /// - SeeAlso: [Binding Values To Prepared Statements]( /// https://sqlite.org/c3ref/bind_blob.html) func bind(_ value: SQLiteValue, at index: Int32) throws(SQLiteError) /// Binds a raw SQLite value to a parameter by its name. /// /// Resolves `name` to an index and binds `value` to that parameter. The `name` must include /// its prefix (e.g., `:AAA`, `@AAA`, `$AAA`). Binding a value to a parameter that does not /// exist results in an error. /// /// - Parameters: /// - value: The ``SQLiteValue`` to bind. /// - name: The parameter name exactly as written in SQL, including its prefix. /// - Throws: ``SQLiteError`` if binding fails. /// /// - SeeAlso: [Binding Values To Prepared Statements]( /// https://sqlite.org/c3ref/bind_blob.html) func bind(_ value: SQLiteValue, by name: String) throws(SQLiteError) /// Binds a typed value conforming to `SQLiteBindable` by index. /// /// Converts `value` to its raw SQLite representation and binds it at `index`. If `value` is /// `nil`, binds `NULL`. /// /// - Parameters: /// - value: The value to bind. If `nil`, `NULL` is bound. /// - index: The 1-based parameter index. /// - Throws: ``SQLiteError`` if binding fails. /// /// - SeeAlso: [Binding Values To Prepared Statements]( /// https://sqlite.org/c3ref/bind_blob.html) func bind(_ value: T?, at index: Int32) throws(SQLiteError) /// Binds a typed value conforming to `SQLiteBindable` by name. /// /// Resolves `name` to a parameter index and binds the raw SQLite representation of `value`. /// If `value` is `nil`, binds `NULL`. The `name` must include its prefix (e.g., `:AAA`, /// `@AAA`, `$AAA`). Binding to a non-existent parameter results in an error. /// /// - Parameters: /// - value: The value to bind. If `nil`, `NULL` is bound. /// - name: The parameter name exactly as written in SQL, including its prefix. /// - Throws: ``SQLiteError`` if binding fails. /// /// - SeeAlso: [Binding Values To Prepared Statements]( /// https://sqlite.org/c3ref/bind_blob.html) func bind(_ value: T?, by name: String) throws(SQLiteError) /// Binds the contents of a row to named statement parameters by column name. /// /// For each `(column, value)` pair in `row`, treats `column` as a named parameter `:column` /// and binds `value` to that parameter. Parameter names in the SQL must match the row's /// column names (including the leading colon). Binding to a non-existent parameter results /// in an error. /// /// - Parameter row: The row whose column values are to be bound. /// - Throws: ``SQLiteError`` if any value cannot be bound. /// /// - SeeAlso: [Binding Values To Prepared Statements]( /// https://sqlite.org/c3ref/bind_blob.html) func bind(_ row: SQLiteRow) throws(SQLiteError) /// Clears all parameter bindings of the prepared statement. /// /// After calling this function, all parameters are set to `NULL`. Call this when reusing the /// statement with a different set of parameter values. /// /// - Throws: ``SQLiteError`` if clearing bindings fails. /// /// - SeeAlso: [Reset All Bindings](https://sqlite.org/c3ref/clear_bindings.html) func clearBindings() throws(SQLiteError) // MARK: - Statement Execution /// Evaluates the prepared statement and advances to the next result row. /// /// Call repeatedly to iterate over all rows. It returns `true` while a new row is available. /// After the final row it returns `false`. Statements that produce no rows return `false` /// immediately. Reset the statement and clear bindings before re-executing. /// /// - Returns: `true` if a new row is available, or `false` when no more rows remain. /// - Throws: ``SQLiteError`` if evaluation fails. /// /// - SeeAlso: [Evaluate An SQL Statement](https://sqlite.org/c3ref/step.html) @discardableResult func step() throws(SQLiteError) -> Bool /// Resets the prepared SQLite statement to its initial state, ready for re-execution. /// /// Undoes the effects of previous calls to ``step()``. After reset, the statement may be /// executed again with the same or new inputs. This does not clear parameter bindings. /// Call ``clearBindings()`` to set all parameters to `NULL` if needed. /// /// - Throws: ``SQLiteError`` if the statement cannot be reset. /// /// - SeeAlso: [Reset A Prepared Statement](https://sqlite.org/c3ref/reset.html) func reset() throws(SQLiteError) /// Executes the statement once per provided parameter row. /// /// For each row, binds values, steps until completion (discarding any result rows), clears /// bindings, and resets the statement. Use this for efficient batch executions (e.g., inserts /// or updates) with different parameters per run. /// /// - Parameter rows: Parameter rows to bind for each execution. /// - Throws: ``SQLiteError`` if binding, stepping, clearing, or resetting fails. func execute(_ rows: [SQLiteRow]) throws(SQLiteError) // MARK: - Result Set /// Returns the number of columns in the current result set. /// /// If this value is `0`, the prepared statement does not produce rows. This is typically /// the case for statements that do not return data. /// /// - Returns: The number of columns in the result set, or `0` if there are no result columns. /// /// - SeeAlso: [Number Of Columns In A Result Set]( /// https://sqlite.org/c3ref/column_count.html) func columnCount() -> Int32 /// Returns the name of the column at the specified index in the result set. /// /// The column name appears as defined in the SQL statement. If the index is out of bounds, this /// function returns `nil`. /// /// - Parameter index: The 0-based index of the column for which to retrieve the name. /// - Returns: The name of the column at the given index, or `nil` if the index is invalid. /// /// - SeeAlso: [Column Names In A Result Set](https://sqlite.org/c3ref/column_name.html) func columnName(at index: Int32) -> String? /// Returns the raw SQLite value at the given result column index. /// /// Retrieves the value for the specified column in the current result row of the prepared /// statement, represented as a ``SQLiteValue``. If the index is out of range, returns /// ``SQLiteValue/null``. /// /// - Parameter index: The 0-based index of the result column to access. /// - Returns: The raw ``SQLiteValue`` at the specified column. /// /// - SeeAlso: [Result Values From A Query](https://sqlite.org/c3ref/column_blob.html) func columnValue(at index: Int32) -> SQLiteValue /// Returns the value of the result column at `index`, converted to `T`. /// /// Attempts to initialize `T` from the raw ``SQLiteValue`` at `index` using /// ``SQLiteRepresentable``. Returns `nil` if the conversion is not possible. /// /// - Parameter index: The 0-based result column index. /// - Returns: A value of type `T` if conversion succeeds, otherwise `nil`. /// /// - SeeAlso: [Result Values From A Query](https://sqlite.org/c3ref/column_blob.html) func columnValue(at index: Int32) -> T? /// Returns the current result row. /// /// Builds a row by iterating over all result columns at the current cursor position, reading /// each column's name and value, and inserting them into the row. /// /// - Returns: A `SQLiteRow` mapping column names to values, or `nil` if there are no columns. /// /// - SeeAlso: [Result Values From A Query](https://sqlite.org/c3ref/column_blob.html) func currentRow() -> SQLiteRow? } // MARK: - Default Implementation public extension StatementProtocol { func bind(_ value: SQLiteValue, by name: String) throws(SQLiteError) { try bind(value, at: parameterIndexBy(name)) } func bind(_ value: T?, at index: Int32) throws(SQLiteError) { try bind(value?.sqliteValue ?? .null, at: index) } func bind(_ value: T?, by name: String) throws(SQLiteError) { try bind(value?.sqliteValue ?? .null, at: parameterIndexBy(name)) } func bind(_ row: SQLiteRow) throws(SQLiteError) { for (column, value) in row { let index = parameterIndexBy(":\(column)") try bind(value, at: index) } } func execute(_ rows: [SQLiteRow]) throws(SQLiteError) { for row in rows { try bind(row) var hasStep: Bool repeat { hasStep = try step() } while hasStep try clearBindings() try reset() } } func columnValue(at index: Int32) -> T? { T(columnValue(at: index)) } func currentRow() -> SQLiteRow? { let columnCount = columnCount() guard columnCount > 0 else { return nil } var row = SQLiteRow() row.reserveCapacity(columnCount) for index in 0..