Add multicast delegate

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

View File

@@ -5,10 +5,7 @@ public final class Connection: ConnectionProtocol {
// MARK: - Private Properties
private let connection: OpaquePointer
// MARK: - Delegation
public weak var delegate: (any ConnectionDelegate)?
fileprivate var delegates = [DelegateBox]()
// MARK: - Connection State
@@ -61,6 +58,17 @@ public final class Connection: ConnectionProtocol {
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
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
private func traceCallback(
@@ -124,8 +142,8 @@ private func traceCallback(
.fromOpaque(ctx)
.takeUnretainedValue()
if let delegate = connection.delegate {
guard let stmt = OpaquePointer(p),
guard !connection.delegates.isEmpty,
let stmt = OpaquePointer(p),
let pSql = sqlite3_expanded_sql(stmt),
let xSql = x?.assumingMemoryBound(to: CChar.self)
else { return SQLITE_OK }
@@ -133,7 +151,9 @@ private func traceCallback(
let pSqlString = String(cString: pSql)
let xSqlString = String(cString: xSql)
let trace = (xSqlString, pSqlString)
delegate.connection(connection, trace: trace)
for box in connection.delegates {
box.delegate?.connection(connection, trace: trace)
}
return SQLITE_OK
@@ -151,7 +171,7 @@ private func updateHookCallback(
.fromOpaque(ctx)
.takeUnretainedValue()
if let delegate = connection.delegate {
if !connection.delegates.isEmpty {
guard let dName = dName, let tName = tName else { return }
let dbName = String(cString: dName)
@@ -169,18 +189,21 @@ private func updateHookCallback(
return
}
delegate.connection(connection, didUpdate: updateAction)
for box in connection.delegates {
box.delegate?.connection(connection, didUpdate: updateAction)
}
}
}
private func commitHookCallback(_ ctx: UnsafeMutableRawPointer?) -> Int32 {
do {
guard let ctx = ctx else { return SQLITE_OK }
let connection = Unmanaged<Connection>
.fromOpaque(ctx)
.takeUnretainedValue()
if let delegate = connection.delegate {
try delegate.connectionDidCommit(connection)
do {
for box in connection.delegates {
try box.delegate?.connectionDidCommit(connection)
}
return SQLITE_OK
} catch {
@@ -193,7 +216,8 @@ private func rollbackHookCallback(_ ctx: UnsafeMutableRawPointer?) {
let connection = Unmanaged<Connection>
.fromOpaque(ctx)
.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 message = "Error executing function '\(name)': \(description)"
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 message = "Error executing function '\(name)': \(description)"
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
/// 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 {
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 {
data.withUnsafeBytes {
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 {
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 {
Data(
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(path:options:)``
### Delegation
- ``ConnectionDelegate``
- ``delegate``
### Connection State
- ``isAutocommit``
@@ -261,6 +256,11 @@ on an attached database. If omitted, they apply to the main database.
- ``synchronous``
- ``userVersion``
### Delegation
- ``addDelegate(_:)``
- ``removeDelegate(_:)``
### SQLite Lifecycle
- ``initialize()``

View File

@@ -15,11 +15,6 @@ import DataLiteC
///
/// ## Topics
///
/// ### Delegation
///
/// - ``ConnectionDelegate``
/// - ``delegate``
///
/// ### Connection State
///
/// - ``isAutocommit``
@@ -34,6 +29,11 @@ import DataLiteC
/// - ``synchronous``
/// - ``userVersion``
///
/// ### Delegation
///
/// - ``addDelegate(_:)``
/// - ``removeDelegate(_:)``
///
/// ### SQLite Lifecycle
///
/// - ``initialize()``
@@ -70,15 +70,6 @@ import DataLiteC
/// - ``apply(_:name:)``
/// - ``rekey(_:name:)``
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
/// 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)
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
/// Initializes the SQLite library.