Refactor entire codebase and rewrite documentation

This commit is contained in:
2025-10-10 18:06:34 +03:00
parent b4e9755c15
commit 8e471f2b9f
74 changed files with 3405 additions and 4149 deletions

View File

@@ -1,687 +1,139 @@
import Foundation
import DataLiteC
/// A value representing a static destructor for SQLite.
/// A prepared SQLite statement used to execute SQL commands.
///
/// `SQLITE_STATIC` is used to indicate that the SQLite library should not free the associated
/// memory when the statement is finalized.
let SQLITE_STATIC = unsafeBitCast(
OpaquePointer(bitPattern: 0),
to: sqlite3_destructor_type.self
)
/// A value representing a transient destructor for SQLite.
/// `Statement` encapsulates the lifecycle of a compiled SQL statement, including parameter binding,
/// execution, and result retrieval. The statement is finalized automatically when the instance is
/// deallocated.
///
/// `SQLITE_TRANSIENT` is used to indicate that the SQLite library should make a copy of the
/// associated memory and free the original memory when the statement is finalized.
let SQLITE_TRANSIENT = unsafeBitCast(
OpaquePointer(bitPattern: -1),
to: sqlite3_destructor_type.self
)
/// A class representing a prepared SQL statement in SQLite.
///
/// ## Overview
///
/// This class provides functionality for preparing, binding parameters, and executing SQL
/// statements using SQLite. It also supports retrieving results and resource management, ensuring
/// the statement is finalized when no longer needed.
///
/// ## Preparing an SQL Statement
///
/// To create a prepared SQL statement, use the ``Connection/prepare(sql:options:)`` method of the
/// ``Connection`` object.
///
/// ```swift
/// do {
/// let statement = try connection.prepare(
/// sql: "SELECT id, name FROM users WHERE age > ?",
/// options: [.persistent, .normalize]
/// )
/// } catch {
/// print("Error: \(error)")
/// }
/// ```
///
/// ## Binding Parameters
///
/// SQL queries can contain parameters whose values can be bound after the statement is prepared.
/// This prevents SQL injection and makes the code more secure.
///
/// ### Binding Parameters by Index
///
/// When preparing an SQL query, you can use the question mark (`?`) as a placeholder for parameter
/// values. Parameter indexing starts from one (1). It is important to keep this in mind for
/// correctly binding values to parameters in the SQL query. The method ``bind(_:at:)-(T?,_)``
/// is used to bind values to parameters.
///
/// ```swift
/// do {
/// let query = "INSERT INTO users (name, age) VALUES (?, ?)"
/// let statement = try connection.prepare(sql: query)
/// try statement.bind("John Doe", at: 1)
/// try statement.bind(30, at: 2)
/// } catch {
/// print("Error binding parameters: \(error)")
/// }
/// ```
///
/// ### Binding Parameters by Explicit Index
///
/// Parameters can be explicitly bound by indices `?1`, `?2`, and so on. This improves readability
/// and simplifies working with queries containing many parameters. Explicit indices do not need to
/// start from one, be sequential, or contiguous.
///
/// ```swift
/// do {
/// let query = "INSERT INTO users (name, age) VALUES (?1, ?2)"
/// let statement = try connection.prepare(sql: query)
/// try statement.bind("Jane Doe", at: 1)
/// try statement.bind(25, at: 2)
/// } catch {
/// print("Error binding parameters: \(error)")
/// }
/// ```
///
/// ### Binding Parameters by Name
///
/// Parameters can also be bound by names. This increases code readability and simplifies managing
/// complex queries. Use ``bind(parameterIndexBy:)`` to retrieve the index of a named parameter.
///
/// ```swift
/// do {
/// let query = "INSERT INTO users (name, age) VALUES (:userName, :userAge)"
/// let statement = try connection.prepare(sql: query)
///
/// let indexName = statement.bind(parameterIndexBy: ":userName")
/// let indexAge = statement.bind(parameterIndexBy: ":userAge")
///
/// try statement.bind("Jane Doe", at: indexName)
/// try statement.bind(25, at: indexAge)
/// } catch {
/// print("Error binding parameters: \(error)")
/// }
/// ```
///
/// ### Duplicating Parameters
///
/// Parameters with explicit indices or names can be duplicated. This allows the same value to be
/// bound to multiple places in the query.
///
/// ```swift
/// do {
/// let query = """
/// INSERT INTO users (name, age)
/// VALUES
/// (:userName, :userAge),
/// (:userName, :userAge)
/// """
/// let statement = try connection.prepare(sql: query)
///
/// let indexName = statement.bind(parameterIndexBy: ":userName")
/// let indexAge = statement.bind(parameterIndexBy: ":userAge")
///
/// try statement.bind("Jane Doe", at: indexName)
/// try statement.bind(25, at: indexAge)
/// } catch {
/// print("Error binding parameters: \(error)")
/// }
/// ```
///
/// ### Mixing Indexed and Named Parameters
///
/// You can mix positional (`?`, `?NNN`) and named (`:name`, `@name`, `$name`) parameters
/// in a single SQL statement. This is supported by SQLite and allows you to use different parameter
/// styles simultaneously.
///
/// ```swift
/// do {
/// let query = """
/// SELECT * FROM users WHERE age = ? AND name = :name
/// """
/// let statement = try connection.prepare(sql: query)
/// let nameIndex = statement.bind(parameterIndexBy: ":name")
///
/// try statement.bind(88, at: 1)
/// try statement.bind("Alice", at: nameIndex)
/// } catch {
/// print("Error binding parameters: \(error)")
/// }
/// ```
///
/// - Important: Although mixing parameter styles is technically allowed, it is generally not recommended.
/// For clarity and maintainability, you should consistently use either indexed or named parameters
/// throughout a query. Mixing styles may lead to confusion or hard-to-diagnose bugs in more complex queries.
///
/// ## Generating SQL Using SQLiteRow
///
/// The ``SQLiteRow`` type can be used not only for retrieving query results, but also for dynamically
/// generating SQL statements. Its ordered keys and parameter-friendly formatting make it especially
/// convenient for constructing `INSERT`, `UPDATE`, and similar queries with named parameters.
///
/// ### Inserting a Row
///
/// To insert a new row into a table using values from a ``SQLiteRow``, you can use the
/// ``SQLiteRow/columns`` and ``SQLiteRow/namedParameters`` properties.
/// This ensures the correct number and order of columns and parameters.
///
/// ```swift
/// var row = SQLiteRow()
/// row["name"] = .text("Alice")
/// row["age"] = .int(30)
/// row["email"] = .text("alice@example.com")
///
/// let columns = row.columns.joined(separator: ", ") // name, age, email
/// let values = row.namedParameters.joined(separator: ", ") // :name, :age, :email
///
/// let sql = "INSERT INTO users (\(columns)) VALUES (\(values))"
/// let statement = try connection.prepare(sql: sql)
/// try statement.bind(row)
/// ```
///
/// This approach eliminates the need to manually write parameter placeholders or maintain their order.
/// It also ensures full compatibility with the ``bind(_:)-(SQLiteRow)`` method.
///
/// ### Updating a Row
///
/// To construct an `UPDATE` statement using a ``SQLiteRow``, you can dynamically
/// map the column names to SQL assignments in the form `column = :column`.
///
/// ```swift
/// var row = SQLiteRow()
/// row["id"] = .int(123)
/// row["name"] = .text("Alice")
/// row["age"] = .int(30)
/// row["email"] = .text("alice@example.com")
///
/// let assignments = zip(row.columns, row.namedParameters)
/// .map { "\($0.0) = \($0.1)" }
/// .joined(separator: ", ")
///
/// let sql = "UPDATE users SET \(assignments) WHERE id = :id"
/// let statement = try connection.prepare(sql: sql)
/// try statement.bind(row)
/// try statement.step()
/// ```
///
/// - Important: Ensure the SQLiteRow includes any values used in conditions
/// (e.g., `:id` in `WHERE`), or binding will fail.
///
/// ## Executing an SQL Statement
///
/// The SQL statement is executed using the ``step()`` method. It returns `true` if there is a
/// result to process, and `false` when execution is complete. To retrieve the results of an SQL
/// statement, use ``columnCount()``, ``columnType(at:)``, ``columnName(at:)``,
/// ``columnValue(at:)->SQLiteRawValue``, and ``currentRow()``.
///
/// ```swift
/// do {
/// let query = "SELECT id, name FROM users WHERE age > ?"
/// let statement = try connection.prepare(sql: query)
/// try statement.bind(18, at: 1)
/// while try statement.step() {
/// for index in 0..<statement.columnCount() {
/// let columnName = statement.columnName(at: index)
/// let columnValue = statement.columnValue(at: index)
/// print("\(columnName): \(columnValue)")
/// }
/// }
/// } catch {
/// print("Error: \(error)")
/// }
/// ```
///
/// ## Preparing for Reuse
///
/// Before reusing a prepared SQL statement, you should call the ``clearBindings()`` method to
/// remove the values bound to the parameters and then call the ``reset()`` method to restore it to
/// its original state.
///
/// ```swift
/// do {
/// let query = "INSERT INTO users (name, age) VALUES (?, ?)"
/// let statement = try connection.prepare(sql: query)
///
/// try statement.bind("John Doe", at: 1)
/// try statement.bind(30, at: 2)
/// try statement.step()
///
/// try statement.clearBindings()
/// try statement.reset()
///
/// try statement.bind("Jane Doe", at: 1)
/// try statement.bind(25, at: 2)
/// try statement.step()
/// } catch {
/// print("Error: \(error)")
/// }
/// ```
/// This class serves as a thin, type-safe wrapper over the SQLite C API, providing a Swift
/// interface for managing prepared statements.
///
/// ## Topics
///
/// ### Subtypes
/// ### Statement Options
///
/// - ``Options``
/// - ``Arguments``
///
/// ### Binding Parameters
///
/// - ``bindParameterCount()``
/// - ``bind(parameterIndexBy:)``
/// - ``bind(parameterNameBy:)``
/// - ``bind(_:at:)-(SQLiteRawValue,_)``
/// - ``bind(_:at:)-(T?,_)``
/// - ``bind(_:)-2ymd1``
/// - ``bind(_:)-6887r``
/// - ``clearBindings()``
///
/// ### Getting Results
///
/// - ``columnCount()``
/// - ``columnType(at:)``
/// - ``columnName(at:)``
/// - ``columnValue(at:)->SQLiteRawValue``
/// - ``columnValue(at:)->T?``
/// - ``currentRow()``
///
/// ### Evaluating
///
/// - ``step()``
/// - ``reset()``
/// - ``execute(rows:)``
/// - ``execute(args:)``
///
/// ### Hashing
///
/// - ``hash(into:)``
public final class Statement: Equatable, Hashable {
public final class Statement {
// MARK: - Private Properties
/// The SQLite statement pointer associated with this `Statement` instance.
private let statement: OpaquePointer
/// The SQLite database connection pointer used to create this statement.
private let connection: OpaquePointer
// MARK: - Inits
/// Initializes a new `Statement` instance with a given SQL query and options.
///
/// This initializer prepares the SQL statement for execution and sets up any necessary
/// options. It throws an ``Connection/Error`` if the SQL preparation fails.
///
/// - Parameters:
/// - connection: A pointer to the SQLite database connection to use.
/// - query: The SQL query string to prepare.
/// - options: The options to use when preparing the SQL statement.
/// - Throws: ``Connection/Error`` if the SQL statement preparation fails.
init(db connection: OpaquePointer, sql query: String, options: Options) throws(Connection.Error) {
init(
db connection: OpaquePointer,
sql query: String,
options: Options
) throws(SQLiteError) {
var statement: OpaquePointer! = nil
let status = sqlite3_prepare_v3(connection, query, -1, options.rawValue, &statement, nil)
let status = sqlite3_prepare_v3(
connection, query, -1,
options.rawValue, &statement, nil
)
if status == SQLITE_OK, let statement {
self.statement = statement
self.connection = connection
} else {
sqlite3_finalize(statement)
throw Connection.Error(connection)
throw SQLiteError(connection)
}
}
/// Finalizes the SQL statement, releasing any associated resources.
deinit {
sqlite3_finalize(statement)
}
// MARK: - Binding Parameters
/// Returns the count of parameters that can be bound to this statement.
///
/// This method provides the number of parameters that can be bound in the SQL statement,
/// allowing you to determine how many parameters need to be set.
///
/// - Returns: The number of bindable parameters in the statement.
public func bindParameterCount() -> Int32 {
}
// MARK: - StatementProtocol
extension Statement: StatementProtocol {
public func parameterCount() -> Int32 {
sqlite3_bind_parameter_count(statement)
}
/// Returns the index of a parameter by its name.
///
/// This method is used to find the index of a parameter in the SQL statement given its name.
/// This is useful for binding values to named parameters.
///
/// - Parameters:
/// - name: The name of the parameter.
/// - Returns: The index of the parameter, or 0 if the parameter does not exist.
public func bind(parameterIndexBy name: String) -> Int32 {
public func parameterIndexBy(_ name: String) -> Int32 {
sqlite3_bind_parameter_index(statement, name)
}
/// Returns the name of a parameter by its index.
///
/// This method retrieves the name of a parameter based on its index in the SQL statement. This
/// is useful for debugging or when parameter names are needed.
///
/// - Parameters:
/// - index: The index of the parameter (1-based).
/// - Returns: The name of the parameter, or `nil` if the name could not be retrieved.
public func bind(parameterNameBy index: Int32) -> String? {
guard let cString = sqlite3_bind_parameter_name(statement, index) else {
return nil
}
return String(cString: cString)
public func parameterNameBy(_ index: Int32) -> String? {
sqlite3_bind_parameter_name(statement, index)
}
/// Binds a value to a parameter at a specified index.
///
/// This method allows you to bind various types of values (integer, real, text, or blob) to a
/// parameter in the SQL statement. The appropriate SQLite function is called based on the type
/// of value being bound.
///
/// - Parameters:
/// - value: The value to bind to the parameter.
/// - index: The index of the parameter to bind (1-based).
/// - Throws: ``Connection/Error`` if the binding operation fails.
public func bind(_ value: SQLiteRawValue, at index: Int32) throws {
let status: Int32
switch value {
case .int(let value): status = sqlite3_bind_int64(statement, index, value)
case .real(let value): status = sqlite3_bind_double(statement, index, value)
case .text(let value): status = sqlite3_bind_text(statement, index, value)
case .blob(let value): status = sqlite3_bind_blob(statement, index, value)
case .null: status = sqlite3_bind_null(statement, index)
public func bind(_ value: SQLiteValue, at index: Int32) throws(SQLiteError) {
let status = switch value {
case .int(let value): sqlite3_bind_int64(statement, index, value)
case .real(let value): sqlite3_bind_double(statement, index, value)
case .text(let value): sqlite3_bind_text(statement, index, value)
case .blob(let value): sqlite3_bind_blob(statement, index, value)
case .null: sqlite3_bind_null(statement, index)
}
if status != SQLITE_OK {
throw Connection.Error(connection)
throw SQLiteError(connection)
}
}
/// Binds a value conforming to `RawBindable` to a parameter at a specified index.
///
/// This method provides a generic way to bind values that conform to `RawBindable`,
/// allowing for flexibility in the types of values that can be bound to SQL statements.
///
/// - Parameters:
/// - value: The value to bind to the parameter.
/// - index: The index of the parameter to bind (1-based).
/// - Throws: ``Connection/Error`` if the binding operation fails.
public func bind<T: SQLiteRawBindable>(_ value: T?, at index: Int32) throws {
try bind(value?.sqliteRawValue ?? .null, at: index)
}
/// Binds all values from a `SQLiteRow` to their corresponding named parameters in the statement.
///
/// This method iterates through each key-value pair in the given `SQLiteRow` and binds the value to
/// the statements named parameter using the `:<column>` syntax. Column names from the row must
/// match named parameters defined in the SQL statement.
///
/// For example, a column named `"userID"` will be bound to a parameter `:userID` in the SQL.
///
/// - Throws: ``Connection/Error`` if a parameter is missing or if a binding operation fails.
public func bind(_ row: SQLiteRow) throws {
try row.forEach { column, value in
try bind(value, at: bind(parameterIndexBy: ":\(column)"))
}
}
/// Binds all values from an `Arguments` instance to their corresponding parameters in the statement.
///
/// This method iterates through each tokenvalue pair in the provided `Arguments` collection and binds
/// the value to the appropriate parameter in the SQL statement. Both indexed (`?NNN`) and named (`:name`)
/// parameters are supported.
///
/// - Parameter arguments: The `Arguments` instance containing tokens and their associated values.
/// - Throws: ``Connection/Error`` if a parameter is not found or if the binding fails.
public func bind(_ arguments: Arguments) throws {
try arguments.forEach { token, value in
let index = switch token {
case .indexed(let index):
Int32(index)
case .named(let name):
bind(parameterIndexBy: ":\(name)")
}
try bind(value, at: index)
}
}
/// Clears all parameter bindings from the statement.
///
/// This method resets any parameter bindings, allowing you to reuse the same SQL statement
/// with different parameter values. This is useful for executing the same statement multiple
/// times with different parameters.
///
/// - Throws: ``Connection/Error`` if the operation to clear bindings fails.
public func clearBindings() throws {
public func clearBindings() throws(SQLiteError) {
if sqlite3_clear_bindings(statement) != SQLITE_OK {
throw Connection.Error(connection)
throw SQLiteError(connection)
}
}
// MARK: - Retrieving Results
@discardableResult
public func step() throws(SQLiteError) -> Bool {
switch sqlite3_step(statement) {
case SQLITE_ROW: true
case SQLITE_DONE: false
default: throw SQLiteError(connection)
}
}
public func reset() throws(SQLiteError) {
if sqlite3_reset(statement) != SQLITE_OK {
throw SQLiteError(connection)
}
}
/// Returns the number of columns in the result set.
///
/// This method provides the count of columns returned by the SQL statement result, which is
/// useful for iterating over query results and processing data.
///
/// - Returns: The number of columns in the result set.
public func columnCount() -> Int32 {
sqlite3_column_count(statement)
}
/// Returns the type of data stored in a column at a specified index.
///
/// This method retrieves the type of data stored in a particular column of the result set,
/// allowing you to handle different data types appropriately.
///
/// - Parameters:
/// - index: The index of the column (0-based).
/// - Returns: The type of data in the column as `SQLiteRawType`.
public func columnType(at index: Int32) -> SQLiteRawType {
.init(rawValue: sqlite3_column_type(statement, index)) ?? .null
public func columnName(at index: Int32) -> String? {
sqlite3_column_name(statement, index)
}
/// Returns the name of a column at a specified index.
///
/// This method retrieves the name of a column, which is useful for debugging or when you need
/// to work with column names directly.
///
/// - Parameters:
/// - index: The index of the column (0-based).
/// - Returns: The name of the column as a `String`.
public func columnName(at index: Int32) -> String {
String(cString: sqlite3_column_name(statement, index))
}
/// Retrieves the value from a column at a specified index.
///
/// This method extracts the value from a column and returns it as an `SQLiteRawValue`, which
/// can represent different data types like integer, real, text, or blob.
///
/// - Parameters:
/// - index: The index of the column (0-based).
/// - Returns: The value from the column as `SQLiteRawValue`.
public func columnValue(at index: Int32) -> SQLiteRawValue {
switch columnType(at: index) {
case .int: return .int(sqlite3_column_int64(statement, index))
case .real: return .real(sqlite3_column_double(statement, index))
case .text: return .text(sqlite3_column_text(statement, index))
case .blob: return .blob(sqlite3_column_blob(statement, index))
case .null: return .null
public func columnValue(at index: Int32) -> SQLiteValue {
switch sqlite3_column_type(statement, index) {
case SQLITE_INTEGER: .int(sqlite3_column_int64(statement, index))
case SQLITE_FLOAT: .real(sqlite3_column_double(statement, index))
case SQLITE_TEXT: .text(sqlite3_column_text(statement, index))
case SQLITE_BLOB: .blob(sqlite3_column_blob(statement, index))
default: .null
}
}
/// Retrieves the value from a column at a specified index and converts it to a value
/// conforming to `SQLiteRawRepresentable`.
///
/// This method provides a way to convert column values into types that conform to
/// ``SQLiteRawRepresentable``, allowing for easier integration with custom data models.
///
/// - Parameters:
/// - index: The index of the column (0-based).
/// - Returns: The value from the column converted to `T`, or `nil` if conversion fails.
public func columnValue<T: SQLiteRawRepresentable>(at index: Int32) -> T? {
T(columnValue(at: index))
}
/// Retrieves the current row of the result set as a `SQLiteRow` instance.
///
/// This method iterates over the columns of the current row in the result set.
/// For each column, it retrieves the column name and the corresponding value using the
/// ``columnName(at:)`` and ``columnValue(at:)->SQLiteRawValue`` methods.
/// It then populates a ``SQLiteRow`` instance with these column-value pairs.
///
/// - Returns: A `SQLiteRow` instance representing the current row of the result set.
public func currentRow() -> SQLiteRow {
var row = SQLiteRow()
for index in 0..<columnCount() {
let name = columnName(at: index)
let value = columnValue(at: index)
row[name] = value
}
return row
}
// MARK: - Evaluating
/// Advances to the next row in the result set.
///
/// This method steps through the result set row by row, returning `true` if there is a row
/// available and `false` if the end of the result set is reached.
///
/// - Returns: `true` if there is a row available, `false` if the end of the result set is
/// reached.
/// - Throws: ``Connection/Error`` if an error occurs during execution.
@discardableResult
public func step() throws(Connection.Error) -> Bool {
switch sqlite3_step(statement) {
case SQLITE_ROW: return true
case SQLITE_DONE: return false
default: throw Connection.Error(connection)
}
}
/// Resets the prepared SQL statement to its initial state.
///
/// Use this method before re-executing the statement. It does not clear the bound parameters,
/// allowing their values to persist between executions. To clear the parameters, use the
/// `clearBindings()` method.
///
/// - Throws: ``Connection/Error`` if the statement reset fails.
public func reset() throws {
if sqlite3_reset(statement) != SQLITE_OK {
throw Connection.Error(connection)
}
}
/// Executes the statement once for each row, returning the collected result rows if any.
///
/// This method binds each rows named values to the statement parameters and executes the
/// statement. After each execution, any resulting rows are collected and returned. If the `rows`
/// array is empty, the statement will still execute once with no parameters bound.
///
/// Use this method for queries such as `INSERT` or `UPDATE` statements with changing
/// parameter values.
///
/// - Note: If `rows` is empty, the statement executes once with no bound values.
///
/// - Parameter rows: A list of `SQLiteRow` values to bind to the statement.
/// - Returns: An array of result rows collected from all executions of the statement.
/// - Throws: ``Connection/Error`` if binding or execution fails.
@discardableResult
public func execute(rows: [SQLiteRow]) throws -> [SQLiteRow] {
var result = [SQLiteRow]()
var index = 0
repeat {
if rows.count > index {
try bind(rows[index])
}
while try step() {
result.append(currentRow())
}
try clearBindings()
try reset()
index += 1
} while index < rows.count
return result
}
/// Executes the statement once for each arguments set, returning any resulting rows.
///
/// This method binds each `Arguments` set (indexed or named) to the statement and executes it. All
/// result rows from each execution are collected and returned. If no arguments are provided, the
/// statement executes once with no values bound.
///
/// Use this method for queries such as `SELECT`, `INSERT`, `UPDATE`, or `DELETE` where results
/// may be expected and multiple executions are needed.
///
/// ```swift
/// let stmt = try connection.prepare(
/// sql: "SELECT * FROM logs WHERE level = :level"
/// )
/// let result = try stmt.execute(args: [
/// ["level": "info"],
/// ["level": "error"]
/// ])
/// ```
///
/// - Note: If `args` is `nil` or empty, the statement executes once with no bound values.
///
/// - Parameter args: A list of `Arguments` to bind and execute. Defaults to `nil`.
/// - Returns: A flat array of result rows produced by all executions.
/// - Throws: ``Connection/Error`` if binding or execution fails.
@discardableResult
public func execute(args: [Arguments]? = nil) throws -> [SQLiteRow] {
var result = [SQLiteRow]()
var index = 0
repeat {
if let args, args.count > index {
try bind(args[index])
}
while try step() {
result.append(currentRow())
}
try clearBindings()
try reset()
index += 1
} while index < args?.count ?? 0
return result
}
// MARK: - Equatable
/// Compares two `Statement` instances for equality.
///
/// This method checks whether two `Statement` instances are equal by comparing their
/// underlying SQLite statement pointers and connection pointers.
///
/// - Parameters:
/// - lhs: The first `Statement` instance.
/// - rhs: The second `Statement` instance.
/// - Returns: `true` if the two instances are equal, `false` otherwise.
public static func == (lhs: Statement, rhs: Statement) -> Bool {
lhs.statement == rhs.statement && lhs.connection == rhs.connection
}
// MARK: - Hashable
/// Computes a hash value for the `Statement` instance.
///
/// This method computes a hash value based on the SQLite statement pointer and connection
/// pointer. It is used to support hash-based collections like sets and dictionaries.
///
/// - Parameter hasher: The hasher to use for computing the hash value.
public func hash(into hasher: inout Hasher) {
hasher.combine(statement)
hasher.combine(connection)
}
}
// MARK: - Functions
// MARK: - Constants
let SQLITE_STATIC = unsafeBitCast(
OpaquePointer(bitPattern: 0),
to: sqlite3_destructor_type.self
)
let SQLITE_TRANSIENT = unsafeBitCast(
OpaquePointer(bitPattern: -1),
to: sqlite3_destructor_type.self
)
// MARK: - Private Sunctions
private func sqlite3_bind_parameter_name(_ stmt: OpaquePointer!, _ index: Int32) -> String? {
guard let cString = DataLiteC.sqlite3_bind_parameter_name(stmt, index) else { return nil }
return String(cString: cString)
}
private func sqlite3_bind_text(_ stmt: OpaquePointer!, _ index: Int32, _ string: String) -> Int32 {
sqlite3_bind_text(stmt, index, string, -1, SQLITE_TRANSIENT)
@@ -693,6 +145,13 @@ private func sqlite3_bind_blob(_ stmt: OpaquePointer!, _ index: Int32, _ data: D
}
}
private func sqlite3_column_name(_ stmt: OpaquePointer!, _ iCol: Int32) -> String? {
guard let cString = DataLiteC.sqlite3_column_name(stmt, iCol) else {
return nil
}
return String(cString: cString)
}
private func sqlite3_column_text(_ stmt: OpaquePointer!, _ iCol: Int32) -> String {
String(cString: DataLiteC.sqlite3_column_text(stmt, iCol))
}