Merge branch 'feature/multicast-delegate' into develop

This commit is contained in:
2025-09-20 18:28:46 +03:00
7 changed files with 78 additions and 75 deletions

View File

@@ -21,26 +21,26 @@ extension Connection {
/// - ``init(code:message:)`` /// - ``init(code:message:)``
public struct Error: Swift.Error, Equatable, CustomStringConvertible { public struct Error: Swift.Error, Equatable, CustomStringConvertible {
// MARK: - Properties // MARK: - Properties
/// The database engine error code. /// The database engine error code.
/// ///
/// This code indicates the specific error returned by SQLite during an operation. /// This code indicates the specific error returned by SQLite during an operation.
/// For a full list of possible error codes, see: /// For a full list of possible error codes, see:
/// [SQLite Result and Error Codes](https://www.sqlite.org/rescode.html). /// [SQLite Result and Error Codes](https://www.sqlite.org/rescode.html).
public let code: Int32 public let code: Int32
/// A human-readable error message describing the failure. /// A human-readable error message describing the failure.
public let message: String public let message: String
/// A textual representation of the error. /// A textual representation of the error.
/// ///
/// Combines the error code and message into a single descriptive string. /// Combines the error code and message into a single descriptive string.
public var description: String { public var description: String {
"Connection.Error code: \(code) message: \(message)" "Connection.Error code: \(code) message: \(message)"
} }
// MARK: - Initialization // MARK: - Initialization
/// Creates an error with the given code and message. /// Creates an error with the given code and message.
/// ///
/// - Parameters: /// - Parameters:
@@ -50,7 +50,7 @@ extension Connection {
self.code = code self.code = code
self.message = message self.message = message
} }
/// Creates an error by extracting details from a SQLite connection. /// Creates an error by extracting details from a SQLite connection.
/// ///
/// - Parameter connection: A pointer to the SQLite connection. /// - Parameter connection: A pointer to the SQLite connection.

View File

@@ -5,10 +5,7 @@ public final class Connection: ConnectionProtocol {
// MARK: - Private Properties // MARK: - Private Properties
private let connection: OpaquePointer private let connection: OpaquePointer
fileprivate var delegates = [DelegateBox]()
// MARK: - Delegation
public weak var delegate: (any ConnectionDelegate)?
// MARK: - Connection State // MARK: - Connection State
@@ -61,6 +58,17 @@ public final class Connection: ConnectionProtocol {
sqlite3_close_v2(connection) sqlite3_close_v2(connection)
} }
// MARK: - Delegation
public func addDelegate(_ delegate: ConnectionDelegate) {
delegates.removeAll { $0.delegate == nil }
delegates.append(.init(delegate: delegate))
}
public func removeDelegate(_ delegate: ConnectionDelegate) {
delegates.removeAll { $0.delegate == nil || $0.delegate === delegate }
}
// MARK: - Custom SQL Functions // MARK: - Custom SQL Functions
public func add(function: Function.Type) throws(Error) { public func add(function: Function.Type) throws(Error) {
@@ -111,6 +119,16 @@ public final class Connection: ConnectionProtocol {
} }
} }
fileprivate extension Connection {
class DelegateBox {
weak var delegate: ConnectionDelegate?
init(delegate: ConnectionDelegate? = nil) {
self.delegate = delegate
}
}
}
// MARK: - Functions // MARK: - Functions
private func traceCallback( private func traceCallback(
@@ -124,16 +142,18 @@ private func traceCallback(
.fromOpaque(ctx) .fromOpaque(ctx)
.takeUnretainedValue() .takeUnretainedValue()
if let delegate = connection.delegate { guard !connection.delegates.isEmpty,
guard let stmt = OpaquePointer(p), let stmt = OpaquePointer(p),
let pSql = sqlite3_expanded_sql(stmt), let pSql = sqlite3_expanded_sql(stmt),
let xSql = x?.assumingMemoryBound(to: CChar.self) let xSql = x?.assumingMemoryBound(to: CChar.self)
else { return SQLITE_OK } else { return SQLITE_OK }
let pSqlString = String(cString: pSql) let pSqlString = String(cString: pSql)
let xSqlString = String(cString: xSql) let xSqlString = String(cString: xSql)
let trace = (xSqlString, pSqlString) let trace = (xSqlString, pSqlString)
delegate.connection(connection, trace: trace)
for box in connection.delegates {
box.delegate?.connection(connection, trace: trace)
} }
return SQLITE_OK return SQLITE_OK
@@ -151,7 +171,7 @@ private func updateHookCallback(
.fromOpaque(ctx) .fromOpaque(ctx)
.takeUnretainedValue() .takeUnretainedValue()
if let delegate = connection.delegate { if !connection.delegates.isEmpty {
guard let dName = dName, let tName = tName else { return } guard let dName = dName, let tName = tName else { return }
let dbName = String(cString: dName) let dbName = String(cString: dName)
@@ -169,18 +189,21 @@ private func updateHookCallback(
return return
} }
delegate.connection(connection, didUpdate: updateAction) for box in connection.delegates {
box.delegate?.connection(connection, didUpdate: updateAction)
}
} }
} }
private func commitHookCallback(_ ctx: UnsafeMutableRawPointer?) -> Int32 { private func commitHookCallback(_ ctx: UnsafeMutableRawPointer?) -> Int32 {
guard let ctx = ctx else { return SQLITE_OK }
let connection = Unmanaged<Connection>
.fromOpaque(ctx)
.takeUnretainedValue()
do { do {
guard let ctx = ctx else { return SQLITE_OK } for box in connection.delegates {
let connection = Unmanaged<Connection> try box.delegate?.connectionDidCommit(connection)
.fromOpaque(ctx)
.takeUnretainedValue()
if let delegate = connection.delegate {
try delegate.connectionDidCommit(connection)
} }
return SQLITE_OK return SQLITE_OK
} catch { } catch {
@@ -193,7 +216,8 @@ private func rollbackHookCallback(_ ctx: UnsafeMutableRawPointer?) {
let connection = Unmanaged<Connection> let connection = Unmanaged<Connection>
.fromOpaque(ctx) .fromOpaque(ctx)
.takeUnretainedValue() .takeUnretainedValue()
if let delegate = connection.delegate {
delegate.connectionDidRollback(connection) for box in connection.delegates {
box.delegate?.connectionDidRollback(connection)
} }
} }

View File

@@ -360,6 +360,7 @@ private func xFinal(_ ctx: OpaquePointer?) {
let description = error.localizedDescription let description = error.localizedDescription
let message = "Error executing function '\(name)': \(description)" let message = "Error executing function '\(name)': \(description)"
sqlite3_result_error(ctx, message, -1) sqlite3_result_error(ctx, message, -1)
sqlite3_result_error_code(ctx, SQLITE_ERROR)
} }
} }

View File

@@ -201,6 +201,7 @@ private func xFunc(
let description = error.localizedDescription let description = error.localizedDescription
let message = "Error executing function '\(name)': \(description)" let message = "Error executing function '\(name)': \(description)"
sqlite3_result_error(ctx, message, -1) sqlite3_result_error(ctx, message, -1)
sqlite3_result_error_code(ctx, SQLITE_ERROR)
} }
} }

View File

@@ -683,46 +683,20 @@ public final class Statement: Equatable, Hashable {
// MARK: - Functions // MARK: - Functions
/// Binds a string to a parameter in an SQL statement.
///
/// - Parameters:
/// - stmt: A pointer to the prepared SQL statement.
/// - index: The index of the parameter (1-based).
/// - string: The string to be bound to the parameter.
/// - Returns: SQLite error code if binding fails.
private func sqlite3_bind_text(_ stmt: OpaquePointer!, _ index: Int32, _ string: String) -> Int32 { private func sqlite3_bind_text(_ stmt: OpaquePointer!, _ index: Int32, _ string: String) -> Int32 {
sqlite3_bind_text(stmt, index, string, -1, SQLITE_TRANSIENT) sqlite3_bind_text(stmt, index, string, -1, SQLITE_TRANSIENT)
} }
/// Binds binary data to a parameter in an SQL statement.
///
/// - Parameters:
/// - stmt: A pointer to the prepared SQL statement.
/// - index: The index of the parameter (1-based).
/// - data: The `Data` to be bound to the parameter.
/// - Returns: SQLite error code if binding fails.
private func sqlite3_bind_blob(_ stmt: OpaquePointer!, _ index: Int32, _ data: Data) -> Int32 { private func sqlite3_bind_blob(_ stmt: OpaquePointer!, _ index: Int32, _ data: Data) -> Int32 {
data.withUnsafeBytes { data.withUnsafeBytes {
sqlite3_bind_blob(stmt, index, $0.baseAddress, Int32($0.count), SQLITE_TRANSIENT) sqlite3_bind_blob(stmt, index, $0.baseAddress, Int32($0.count), SQLITE_TRANSIENT)
} }
} }
/// Retrieves text data from a result column of an SQL statement.
///
/// - Parameters:
/// - stmt: A pointer to the prepared SQL statement.
/// - iCol: The column index.
/// - Returns: A `String` containing the text data from the specified column.
private func sqlite3_column_text(_ stmt: OpaquePointer!, _ iCol: Int32) -> String { private func sqlite3_column_text(_ stmt: OpaquePointer!, _ iCol: Int32) -> String {
String(cString: DataLiteC.sqlite3_column_text(stmt, iCol)) String(cString: DataLiteC.sqlite3_column_text(stmt, iCol))
} }
/// Retrieves binary data from a result column of an SQL statement.
///
/// - Parameters:
/// - stmt: A pointer to the prepared SQL statement.
/// - iCol: The column index.
/// - Returns: A `Data` object containing the binary data from the specified column.
private func sqlite3_column_blob(_ stmt: OpaquePointer!, _ iCol: Int32) -> Data { private func sqlite3_column_blob(_ stmt: OpaquePointer!, _ iCol: Int32) -> Data {
Data( Data(
bytes: sqlite3_column_blob(stmt, iCol), bytes: sqlite3_column_blob(stmt, iCol),

View File

@@ -242,11 +242,6 @@ on an attached database. If omitted, they apply to the main database.
- ``init(location:options:)`` - ``init(location:options:)``
- ``init(path:options:)`` - ``init(path:options:)``
### Delegation
- ``ConnectionDelegate``
- ``delegate``
### Connection State ### Connection State
- ``isAutocommit`` - ``isAutocommit``
@@ -261,6 +256,11 @@ on an attached database. If omitted, they apply to the main database.
- ``synchronous`` - ``synchronous``
- ``userVersion`` - ``userVersion``
### Delegation
- ``addDelegate(_:)``
- ``removeDelegate(_:)``
### SQLite Lifecycle ### SQLite Lifecycle
- ``initialize()`` - ``initialize()``

View File

@@ -15,11 +15,6 @@ import DataLiteC
/// ///
/// ## Topics /// ## Topics
/// ///
/// ### Delegation
///
/// - ``ConnectionDelegate``
/// - ``delegate``
///
/// ### Connection State /// ### Connection State
/// ///
/// - ``isAutocommit`` /// - ``isAutocommit``
@@ -34,6 +29,11 @@ import DataLiteC
/// - ``synchronous`` /// - ``synchronous``
/// - ``userVersion`` /// - ``userVersion``
/// ///
/// ### Delegation
///
/// - ``addDelegate(_:)``
/// - ``removeDelegate(_:)``
///
/// ### SQLite Lifecycle /// ### SQLite Lifecycle
/// ///
/// - ``initialize()`` /// - ``initialize()``
@@ -70,15 +70,6 @@ import DataLiteC
/// - ``apply(_:name:)`` /// - ``apply(_:name:)``
/// - ``rekey(_:name:)`` /// - ``rekey(_:name:)``
public protocol ConnectionProtocol: AnyObject { public protocol ConnectionProtocol: AnyObject {
// MARK: - Delegation
/// An optional delegate to receive connection-related events and callbacks.
///
/// The delegate allows external objects to monitor or respond to events
/// occurring during the lifetime of the connection, such as errors,
/// transaction commits, or other significant state changes.
var delegate: ConnectionDelegate? { get set }
// MARK: - Connection State // MARK: - Connection State
/// Indicates whether the database connection is in autocommit mode. /// Indicates whether the database connection is in autocommit mode.
@@ -162,6 +153,18 @@ public protocol ConnectionProtocol: AnyObject {
/// - SeeAlso: [PRAGMA user_version](https://www.sqlite.org/pragma.html#pragma_user_version) /// - SeeAlso: [PRAGMA user_version](https://www.sqlite.org/pragma.html#pragma_user_version)
var userVersion: Int32 { get set } var userVersion: Int32 { get set }
// MARK: - Delegation
/// Adds a delegate to receive connection events.
///
/// - Parameter delegate: The delegate to add.
func addDelegate(_ delegate: ConnectionDelegate)
/// Removes a delegate from receiving connection events.
///
/// - Parameter delegate: The delegate to remove.
func removeDelegate(_ delegate: ConnectionDelegate)
// MARK: - SQLite Lifecycle // MARK: - SQLite Lifecycle
/// Initializes the SQLite library. /// Initializes the SQLite library.