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

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

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,16 +142,18 @@ private func traceCallback(
.fromOpaque(ctx)
.takeUnretainedValue()
if let delegate = connection.delegate {
guard let stmt = OpaquePointer(p),
let pSql = sqlite3_expanded_sql(stmt),
let xSql = x?.assumingMemoryBound(to: CChar.self)
else { return SQLITE_OK }
let pSqlString = String(cString: pSql)
let xSqlString = String(cString: xSql)
let trace = (xSqlString, pSqlString)
delegate.connection(connection, trace: trace)
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 }
let pSqlString = String(cString: pSql)
let xSqlString = String(cString: xSql)
let trace = (xSqlString, pSqlString)
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 {
guard let ctx = ctx else { return SQLITE_OK }
let connection = Unmanaged<Connection>
.fromOpaque(ctx)
.takeUnretainedValue()
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)
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),