import Foundation /// A protocol that defines an SQLite database connection. /// /// The `ConnectionProtocol` defines the essential API for managing a database connection, /// including configuration, statement preparation, transactions, encryption, and delegation. /// Conforming types are responsible for maintaining the connection’s lifecycle and settings. /// /// ## Topics /// /// ### Managing Connection State /// /// - ``isAutocommit`` /// - ``isReadonly`` /// /// ### Accessing PRAGMA Values /// /// - ``busyTimeout`` /// - ``applicationID`` /// - ``foreignKeys`` /// - ``journalMode`` /// - ``synchronous`` /// - ``userVersion`` /// /// ### Managing SQLite Lifecycle /// /// - ``initialize()`` /// - ``shutdown()`` /// /// ### Handling Encryption /// /// - ``apply(_:name:)`` /// - ``rekey(_:name:)`` /// /// ### Managing Delegates /// /// - ``add(delegate:)`` /// - ``remove(delegate:)`` /// - ``add(trace:)`` /// - ``remove(trace:)`` /// /// ### Registering Custom SQL Functions /// /// - ``add(function:)`` /// - ``remove(function:)`` /// /// ### Preparing SQL Statements /// /// - ``prepare(sql:)`` /// - ``prepare(sql:options:)`` /// /// ### Executing SQL Commands /// /// - ``execute(sql:)`` /// /// ### Controlling PRAGMA Settings /// /// - ``get(pragma:)`` /// - ``set(pragma:value:)`` /// /// ### Managing Transactions /// /// - ``beginTransaction(_:)`` /// - ``commitTransaction()`` /// - ``rollbackTransaction()`` public protocol ConnectionProtocol: AnyObject { // MARK: - Connection State /// The autocommit state of the connection. /// /// Autocommit is enabled by default and remains active when no explicit transaction is open. /// Executing `BEGIN` disables autocommit, while `COMMIT` or `ROLLBACK` re-enables it. /// /// - Returns: `true` if autocommit mode is active; otherwise, `false`. /// - SeeAlso: [Test For Auto-Commit Mode](https://sqlite.org/c3ref/get_autocommit.html) var isAutocommit: Bool { get } /// The read-only state of the connection. /// /// Returns `true` if the main database allows only read operations, or `false` if it permits /// both reading and writing. /// /// - Returns: `true` if the connection is read-only; otherwise, `false`. /// - SeeAlso: [Determine if a database is read-only](https://sqlite.org/c3ref/db_readonly.html) var isReadonly: Bool { get } // MARK: - PRAGMA Accessors /// The busy timeout of the connection, in milliseconds. /// /// Defines how long SQLite waits for a locked database to become available before returning /// a `SQLITE_BUSY` error. A value of zero disables the timeout, causing operations to fail /// immediately if the database is locked. /// /// - SeeAlso: [Set A Busy Timeout](https://sqlite.org/c3ref/busy_timeout.html) var busyTimeout: Int32 { get set } /// The application identifier stored in the database header. /// /// Used to distinguish database files created by different applications or file formats. This /// value is a 32-bit integer written to the database header and can be queried or modified /// through the `PRAGMA application_id` command. /// /// - SeeAlso: [Application ID](https://sqlite.org/pragma.html#pragma_application_id) var applicationID: Int32 { get set } /// The foreign key enforcement state of the connection. /// /// When enabled, SQLite enforces foreign key constraints on all tables. This behavior can be /// controlled with `PRAGMA foreign_keys`. /// /// - SeeAlso: [Foreign Keys](https://sqlite.org/pragma.html#pragma_foreign_keys) var foreignKeys: Bool { get set } /// The journal mode used by the database connection. /// /// Determines how SQLite maintains the rollback journal for transactions. /// /// - SeeAlso: [Journal Mode](https://sqlite.org/pragma.html#pragma_journal_mode) var journalMode: JournalMode { get set } /// The synchronization mode for database writes. /// /// Controls how aggressively SQLite syncs data to disk for durability versus performance. /// /// - SeeAlso: [Synchronous](https://sqlite.org/pragma.html#pragma_synchronous) var synchronous: Synchronous { get set } /// The user-defined schema version number. /// /// This value is stored in the database header and can be used by applications to track schema /// migrations or format changes. /// /// - SeeAlso: [User Version](https://sqlite.org/pragma.html#pragma_user_version) var userVersion: Int32 { get set } // MARK: - SQLite Lifecycle /// Initializes the SQLite library. /// /// Sets up the global state required by SQLite, including operating-system–specific /// initialization. This function must be called before using any other SQLite API, /// unless the library is initialized automatically. /// /// Only the first invocation during the process lifetime, or the first after /// ``shutdown()``, performs real initialization. All subsequent calls are no-ops. /// /// - Note: Workstation applications normally do not need to call this function explicitly, /// as it is invoked automatically by interfaces such as `sqlite3_open()`. It is mainly /// intended for embedded systems and controlled initialization scenarios. /// /// - Throws: ``SQLiteError`` if initialization fails. /// - SeeAlso: [Initialize The SQLite Library](https://sqlite.org/c3ref/initialize.html) static func initialize() throws(SQLiteError) /// Shuts down the SQLite library. /// /// Releases all global resources allocated by SQLite and undoes the effects of a /// successful call to ``initialize()``. This function should be called exactly once /// for each effective initialization and only after all database connections are closed. /// /// Only the first invocation since the last call to ``initialize()`` performs /// deinitialization. All other calls are harmless no-ops. /// /// - Note: Workstation applications normally do not need to call this function explicitly, /// as cleanup happens automatically at process termination. It is mainly used in /// embedded systems where precise resource control is required. /// /// - Important: This function is **not** threadsafe and must be called from a single thread. /// - Throws: ``SQLiteError`` if the shutdown process fails. /// - SeeAlso: [Initialize The SQLite Library](https://sqlite.org/c3ref/initialize.html) static func shutdown() throws(SQLiteError) // MARK: - Encryption /// Applies an encryption key to a database connection. /// /// If the database is newly created, this call initializes encryption and makes it encrypted. /// If the database already exists, this call decrypts its contents for access using the /// provided key. An existing unencrypted database cannot be encrypted using this method. /// /// This function must be called immediately after the connection is opened and before invoking /// any other operation on the same connection. /// /// - Parameters: /// - key: The encryption key to apply. /// - name: The database name, or `nil` for the main database. /// - Throws: ``SQLiteError`` if the key is invalid or the decryption process fails. /// - SeeAlso: [Setting The Key](https://www.zetetic.net/sqlcipher/sqlcipher-api/#key) func apply(_ key: Connection.Key, name: String?) throws(SQLiteError) /// Changes the encryption key for an open database. /// /// Re-encrypts the database file with a new key while preserving its existing data. The /// connection must already be open and unlocked with a valid key applied through /// ``apply(_:name:)``. This operation replaces the current encryption key but does not modify /// the database contents. /// /// This function can only be used with an encrypted database. It has no effect on unencrypted /// databases. /// /// - Parameters: /// - key: The new encryption key to apply. /// - name: The database name, or `nil` for the main database. /// - Throws: ``SQLiteError`` if rekeying fails or encryption is not supported. /// - SeeAlso: [Changing The Key](https://www.zetetic.net/sqlcipher/sqlcipher-api/#Changing_Key) func rekey(_ key: Connection.Key, name: String?) throws(SQLiteError) // MARK: - Delegation /// Adds a delegate to receive connection-level events. /// /// Registers an object conforming to ``ConnectionDelegate`` to receive notifications such as /// update actions and transaction events. /// /// - Parameter delegate: The delegate to add. func add(delegate: ConnectionDelegate) /// Removes a previously added delegate. /// /// Unregisters an object that was previously added with ``add(delegate:)`` so it no longer /// receives update and transaction events. /// /// - Parameter delegate: The delegate to remove. func remove(delegate: ConnectionDelegate) /// Adds a delegate to receive SQL trace callbacks. /// /// Registers an object conforming to ``ConnectionTraceDelegate`` to observe SQL statements as /// they are executed by the connection. /// /// - Parameter delegate: The trace delegate to add. func add(trace delegate: ConnectionTraceDelegate) /// Removes a previously added trace delegate. /// /// Unregisters an object that was previously added with ``add(trace:)`` so it no longer /// receives SQL trace callbacks. /// /// - Parameter delegate: The trace delegate to remove. func remove(trace delegate: ConnectionTraceDelegate) // MARK: - Custom SQL Functions /// Registers a custom SQLite function with the current connection. /// /// The specified function type must be a subclass of ``Function/Scalar`` or /// ``Function/Aggregate``. Once registered, the function becomes available in SQL queries /// executed through this connection. /// /// - Parameter function: The custom function type to register. /// - Throws: ``SQLiteError`` if registration fails. func add(function: Function.Type) throws(SQLiteError) /// Unregisters a previously registered custom SQLite function. /// /// The specified function type must match the one used during registration. After removal, /// the function will no longer be available for use in SQL statements. /// /// - Parameter function: The custom function type to unregister. /// - Throws: ``SQLiteError`` if the function could not be unregistered. func remove(function: Function.Type) throws(SQLiteError) // MARK: - Statement Preparation /// Prepares an SQL statement for execution. /// /// Compiles the provided SQL query into a prepared statement associated with this connection. /// Use the returned statement to bind parameters and execute queries safely and efficiently. /// /// - Parameter query: The SQL query to prepare. /// - Returns: A compiled statement ready for execution. /// - Throws: ``SQLiteError`` if the statement could not be prepared. /// /// - SeeAlso: [Compiling An SQL Statement](https://sqlite.org/c3ref/prepare.html) func prepare(sql query: String) throws(SQLiteError) -> StatementProtocol /// Prepares an SQL statement with custom compilation options. /// /// Similar to ``prepare(sql:)`` but allows specifying additional compilation flags through /// ``Statement/Options`` to control statement creation behavior. /// /// - Parameters: /// - query: The SQL query to prepare. /// - options: Additional compilation options. /// - Returns: A compiled statement ready for execution. /// - Throws: ``SQLiteError`` if the statement could not be prepared. /// /// - SeeAlso: [Compiling An SQL Statement](https://sqlite.org/c3ref/prepare.html) func prepare( sql query: String, options: Statement.Options ) throws(SQLiteError) -> StatementProtocol // MARK: - SQL Execution /// Executes one or more SQL statements in a single step. /// /// The provided SQL string may contain one or more statements separated by semicolons. /// Each statement is compiled and executed sequentially within the current connection. /// This method is suitable for operations that do not produce result sets, such as /// `CREATE TABLE`, `INSERT`, `UPDATE`, or `PRAGMA`. /// /// Execution stops at the first error, and the corresponding ``SQLiteError`` is thrown. /// /// - Parameter script: The SQL text containing one or more statements to execute. /// - Throws: ``SQLiteError`` if any statement fails to execute. /// /// - SeeAlso: [One-Step Query Execution Interface](https://sqlite.org/c3ref/exec.html) func execute(sql script: String) throws(SQLiteError) // MARK: - PRAGMA Control /// Reads the current value of a database PRAGMA. /// /// Retrieves the value of the specified PRAGMA and attempts to convert it to the provided /// generic type `T`. This method is typically used for reading configuration or status values /// such as `journal_mode`, `foreign_keys`, or `user_version`. /// /// If the PRAGMA query succeeds but the value cannot be converted to the requested type, /// the method returns `nil` instead of throwing an error. /// /// - Parameter pragma: The PRAGMA to query. /// - Returns: The current PRAGMA value, or `nil` if the result is empty or conversion fails. /// - Throws: ``SQLiteError`` if the PRAGMA query itself fails. /// /// - SeeAlso: [PRAGMA Statements](https://sqlite.org/pragma.html) func get(pragma: Pragma) throws(SQLiteError) -> T? /// Sets a database PRAGMA value. /// /// Assigns the specified value to the given PRAGMA. This can be used to change runtime /// configuration parameters, such as `foreign_keys`, `journal_mode`, or `synchronous`. /// /// - Parameters: /// - pragma: The PRAGMA to set. /// - value: The value to assign to the PRAGMA. /// - Throws: ``SQLiteError`` if the assignment fails. /// /// - SeeAlso: [PRAGMA Statements](https://sqlite.org/pragma.html) func set(pragma: Pragma, value: T) throws(SQLiteError) // MARK: - Transactions /// Begins a new transaction of the specified type. /// /// Starts an explicit transaction using the given ``TransactionType``. If a transaction is /// already active, this method throws an error. /// /// - Parameter type: The transaction type to begin. /// - Throws: ``SQLiteError`` if the transaction could not be started. /// /// - SeeAlso: [Transaction](https://sqlite.org/lang_transaction.html) func beginTransaction(_ type: TransactionType) throws(SQLiteError) /// Commits the current transaction. /// /// Makes all changes made during the transaction permanent. If no transaction is active, this /// method has no effect. /// /// - Throws: ``SQLiteError`` if the commit operation fails. /// /// - SeeAlso: [Transaction](https://sqlite.org/lang_transaction.html) func commitTransaction() throws(SQLiteError) /// Rolls back the current transaction. /// /// Reverts all changes made during the transaction. If no transaction is active, this method /// has no effect. /// /// - Throws: ``SQLiteError`` if the rollback operation fails. /// /// - SeeAlso: [Transaction](https://sqlite.org/lang_transaction.html) func rollbackTransaction() throws(SQLiteError) } // MARK: - Default Implementation public extension ConnectionProtocol { var busyTimeout: Int32 { get { try! get(pragma: .busyTimeout) ?? 0 } set { try! set(pragma: .busyTimeout, value: newValue) } } var applicationID: Int32 { get { try! get(pragma: .applicationID) ?? 0 } set { try! set(pragma: .applicationID, value: newValue) } } var foreignKeys: Bool { get { try! get(pragma: .foreignKeys) ?? false } set { try! set(pragma: .foreignKeys, value: newValue) } } var journalMode: JournalMode { get { try! get(pragma: .journalMode) ?? .off } set { try! set(pragma: .journalMode, value: newValue) } } var synchronous: Synchronous { get { try! get(pragma: .synchronous) ?? .off } set { try! set(pragma: .synchronous, value: newValue) } } var userVersion: Int32 { get { try! get(pragma: .userVersion) ?? 0 } set { try! set(pragma: .userVersion, value: newValue) } } func prepare(sql query: String) throws(SQLiteError) -> StatementProtocol { try prepare(sql: query, options: []) } func get(pragma: Pragma) throws(SQLiteError) -> T? { let stmt = try prepare(sql: "PRAGMA \(pragma)") switch try stmt.step() { case true: return stmt.columnValue(at: 0) case false: return nil } } func set(pragma: Pragma, value: T) throws(SQLiteError) { let query = "PRAGMA \(pragma) = \(value.sqliteLiteral)" try prepare(sql: query).step() } func beginTransaction(_ type: TransactionType = .deferred) throws(SQLiteError) { try prepare(sql: "BEGIN \(type) TRANSACTION", options: []).step() } func commitTransaction() throws(SQLiteError) { try prepare(sql: "COMMIT TRANSACTION", options: []).step() } func rollbackTransaction() throws(SQLiteError) { try prepare(sql: "ROLLBACK TRANSACTION", options: []).step() } }