import Foundation import DataLiteC extension Function { /// Base class for creating custom SQLite aggregate functions. /// /// This class provides a basic implementation for creating aggregate functions in SQLite. /// Aggregate functions process a set of input values and return a single value. /// To create a custom aggregate function, subclass `Function.Aggregate` and override the /// following properties and methods: /// /// - ``name``: The name of the function used in SQL queries. /// - ``argc``: The number of arguments the function accepts. /// - ``options``: Options for the function, such as `deterministic` and `innocuous`. /// - ``step(args:)``: Method called for each input value. /// - ``finalize()``: Method called after processing all input values. /// /// ### Example /// /// This example shows how to create a custom aggregate function to calculate the sum /// of integers. /// /// ```swift /// final class SumAggregate: Function.Aggregate { /// enum Error: Swift.Error { /// case argumentsWrong /// } /// /// override class var argc: Int32 { 1 } /// override class var name: String { "sum_aggregate" } /// override class var options: Function.Options { /// [.deterministic, .innocuous] /// } /// /// private var sum: Int = 0 /// /// override func step(args: Arguments) throws { /// guard let value = args[0] as Int? else { /// throw Error.argumentsWrong /// } /// sum += value /// } /// /// override func finalize() throws -> SQLiteRawRepresentable? { /// return sum /// } /// } /// ``` /// /// ### Usage /// /// To use a custom aggregate function, first establish a database connection and /// register the function. /// /// ```swift /// let connection = try Connection( /// path: dbFileURL.path, /// options: [.create, .readwrite] /// ) /// try connection.add(function: SumAggregate.self) /// ``` /// /// ### SQL Example /// /// Example SQL query using the custom aggregate function to calculate the sum of /// values in the `value` column of the `my_table`. /// /// ```sql /// SELECT sum_aggregate(value) FROM my_table /// ``` /// /// ## Topics /// /// ### Initializers /// /// - ``init()`` /// /// ### Instance Methods /// /// - ``step(args:)`` /// - ``finalize()`` open class Aggregate: Function { // MARK: - Context /// Helper class for storing and managing the context of a custom SQLite aggregate function. /// /// This class is used to hold a reference to the `Aggregate` function implementation. /// It is created and managed when the aggregate function is installed in the SQLite /// database connection. The context is passed to SQLite and is used to invoke the /// corresponding function implementation when called. fileprivate final class Context { // MARK: Properties /// The type of the aggregate function managed by this context. /// /// This property holds a reference to the subclass of `Aggregate` that implements /// the custom aggregate function. It is used to create instances of the function /// and manage its state during SQL query execution. private let function: Aggregate.Type // MARK: Inits /// Initializes a new `Context` with a reference to the aggregate function type. /// /// This initializer creates an instance of the `Context` class that will hold /// a reference to the aggregate function type. It is used to manage state and /// perform operations with the custom aggregate function in the SQLite context. /// /// - Parameter function: The subclass of `Aggregate` implementing the custom /// aggregate function. This parameter specifies which function type will be /// used in the context. /// /// - Note: The initializer establishes a link between the context and the function /// type, allowing extraction of function instances and management of their state /// during SQL query processing. init(function: Aggregate.Type) { self.function = function } // MARK: Methods /// Retrieves or creates an instance of the aggregate function. /// /// This method retrieves an existing instance of the aggregate function from the /// SQLite context or creates a new one if it has not yet been created. The returned /// instance allows management of the aggregate function's state during query execution. /// /// - Parameter ctx: Pointer to the SQLite context associated with the current /// query. This parameter is used to access the aggregate context where the /// function state is stored. /// /// - Returns: An unmanaged reference to the `Aggregate` instance. /// /// - Note: The method checks whether an instance of the function already exists in /// the context. If no instance is found, a new one is created and saved in the /// context for use in subsequent calls. func function(ctx: OpaquePointer?) -> Unmanaged { let stride = MemoryLayout>.stride let functionBuffer = UnsafeMutableRawBufferPointer( start: sqlite3_aggregate_context(ctx, Int32(stride)), count: stride ) if functionBuffer.contains(where: { $0 != 0 }) { return functionBuffer.baseAddress!.assumingMemoryBound( to: Unmanaged.self ).pointee } else { let function = self.function.init() let unmanagedFunction = Unmanaged.passRetained(function) let functionPointer = unmanagedFunction.toOpaque() withUnsafeBytes(of: functionPointer) { functionBuffer.copyMemory(from: $0) } return unmanagedFunction } } } // MARK: - Properties /// Flag indicating whether an error occurred during execution. fileprivate var hasErrored = false // MARK: - Inits /// Initializes a new instance of the `Aggregate` class. /// /// This initializer is required for subclasses of ``Aggregate``. /// In the current implementation, it performs no additional actions but provides /// a basic structure for creating instances. /// /// Subclasses may override this initializer to implement their own initialization /// logic, including setting up additional properties or performing other necessary /// operations. /// /// ```swift /// public class MyCustomAggregate: Function.Aggregate { /// required public init() { /// super.init() // Call to superclass initializer /// // Additional initialization if needed /// } /// } /// ``` /// /// - Note: Always call `super.init()` in the overridden initializer to ensure /// proper initialization of the parent class. required public override init() {} // MARK: - Methods /// Installs the custom SQLite aggregate function into the specified database connection. /// /// This method registers the custom aggregate function in the SQLite database, /// allowing it to be used in SQL queries. The method creates a context for the function /// and passes it to SQLite, as well as specifying callback functions to handle input /// values and finalize results. /// /// ```swift /// // Assuming the database connection is already open /// let db: OpaquePointer = ... /// // Register the function in the database /// try MyCustomAggregate.install(db: db) /// ``` /// /// - Parameter connection: Pointer to the SQLite database connection where the function /// will be installed. /// /// - Throws: ``Connection/Error`` if the function installation fails. The error is thrown if /// the `sqlite3_create_function_v2` call does not return `SQLITE_OK`. /// /// - Note: This method must be called to register the custom function before using /// it in SQL queries. Ensure that the database connection is open and available at /// the time of this method call. override class func install(db connection: OpaquePointer) throws(Connection.Error) { let context = Context(function: self) let ctx = Unmanaged.passRetained(context).toOpaque() let status = sqlite3_create_function_v2( connection, name, argc, opts, ctx, nil, xStep(_:_:_:), xFinal(_:), xDestroy(_:) ) if status != SQLITE_OK { throw Connection.Error(connection) } } /// Called for each input value during aggregate computation. /// /// This method should be overridden by subclasses to implement the specific logic /// for processing each input value. Your implementation should handle the input /// arguments and accumulate results for later finalization. /// /// ```swift /// class MyCustomAggregate: Function.Aggregate { /// // ... /// /// private var sum: Int = 0 /// /// override func step(args: Arguments) throws { /// guard let value = args[0].intValue else { /// throw MyCustomError.invalidInput /// } /// sum += value /// } /// /// // ... /// } /// ``` /// /// - Parameter args: An ``Arguments`` object that contains the number of /// arguments and their values. /// /// - Throws: An error if the function execution fails. Subclasses may throw errors /// if the input values do not match the expected format or if other issues arise /// during processing. /// /// - Note: It is important to override this method in subclasses; otherwise, a /// runtime error will occur due to calling `fatalError()`. open func step(args: Arguments) throws { fatalError("The 'step' method should be overridden.") } /// Called when the aggregate computation is complete. /// /// This method should be overridden by subclasses to return the final result /// of the aggregate computation. Your implementation should return a value that will /// be used in SQL queries. If the aggregate should not return a value, you can /// return `nil`. /// /// ```swift /// class MyCustomAggregate: Function.Aggregate { /// // ... /// /// private var sum: Int = 0 /// /// override func finalize() throws -> SQLiteRawRepresentable? { /// return sum /// } /// } /// ``` /// /// - Returns: An optional ``SQLiteRawRepresentable`` representing the result of the /// aggregate function. The return value may be `nil` if a result is not required. /// /// - Throws: An error if the function execution fails. Subclasses may throw errors /// if the aggregate cannot be computed correctly or if other issues arise. /// /// - Note: It is important to override this method in subclasses; otherwise, a /// runtime error will occur due to calling `fatalError()`. open func finalize() throws -> SQLiteRawRepresentable? { fatalError("The 'finalize' method should be overridden.") } } } // MARK: - Functions /// C callback function to perform a step of the custom SQLite aggregate function. /// /// This function is called by SQLite for each input value passed to the aggregate function. /// It retrieves the function implementation from the context associated with the SQLite /// request, calls the `step(args:)` method, and handles any errors that may occur during /// execution. /// /// - Parameters: /// - ctx: Pointer to the SQLite context associated with the current query. /// - argc: Number of arguments passed to the function. /// - argv: Array of pointers to the argument values passed to the function. private func xStep( _ ctx: OpaquePointer?, _ argc: Int32, _ argv: UnsafeMutablePointer? ) { let context = Unmanaged .fromOpaque(sqlite3_user_data(ctx)) .takeUnretainedValue() let function = context .function(ctx: ctx) .takeUnretainedValue() assert(!function.hasErrored) do { let args = Function.Arguments(argc: argc, argv: argv) try function.step(args: args) } catch { let name = type(of: function).name let description = error.localizedDescription let message = "Error executing function '\(name)': \(description)" function.hasErrored = true sqlite3_result_error(ctx, message, -1) } } /// C callback function to finalize the result of the custom SQLite aggregate function. /// /// This function is called by SQLite when the aggregate computation is complete. /// It retrieves the function implementation from the context, calls the `finalize()` /// method, and sets the query result based on the returned value. /// /// - Parameter ctx: Pointer to the SQLite context associated with the current query. private func xFinal(_ ctx: OpaquePointer?) { let context = Unmanaged .fromOpaque(sqlite3_user_data(ctx)) .takeUnretainedValue() let unmanagedFunction = context.function(ctx: ctx) let function = unmanagedFunction.takeUnretainedValue() defer { unmanagedFunction.release() } guard !function.hasErrored else { return } do { let result = try function.finalize() sqlite3_result_value(ctx, result?.sqliteRawValue) } catch { let name = type(of: function).name let description = error.localizedDescription let message = "Error executing function '\(name)': \(description)" sqlite3_result_error(ctx, message, -1) sqlite3_result_error_code(ctx, SQLITE_ERROR) } } /// C callback function to destroy the context associated with the custom SQLite aggregate function. /// /// This function is called by SQLite when the function is uninstalled. It frees the memory /// allocated for the `Context` object associated with the function to avoid memory leaks. /// /// - Parameter ctx: Pointer to the SQLite query context. private func xDestroy(_ ctx: UnsafeMutableRawPointer?) { guard let ctx else { return } Unmanaged.fromOpaque(ctx).release() }