Refactor entire codebase and rewrite documentation
This commit is contained in:
24
Sources/DataLiteCore/Protocols/ArgumentsProtocol.swift
Normal file
24
Sources/DataLiteCore/Protocols/ArgumentsProtocol.swift
Normal file
@@ -0,0 +1,24 @@
|
||||
import Foundation
|
||||
|
||||
/// A protocol representing a collection of SQLite argument values.
|
||||
///
|
||||
/// Conforming types provide indexed access to a sequence of ``SQLiteValue`` elements. This protocol
|
||||
/// extends `Collection` to allow convenient typed subscripting using types conforming to
|
||||
/// ``SQLiteRepresentable``.
|
||||
public protocol ArgumentsProtocol: Collection where Element == SQLiteValue, Index == Int {
|
||||
/// Returns the element at the specified index, converted to the specified type.
|
||||
///
|
||||
/// This subscript retrieves the argument value at the given index and attempts to convert it to
|
||||
/// a type conforming to ``SQLiteRepresentable``. If the conversion succeeds, the resulting
|
||||
/// value of type `T` is returned. Otherwise, `nil` is returned.
|
||||
///
|
||||
/// - Parameter index: The index of the value to retrieve and convert.
|
||||
/// - Returns: A value of type `T` if conversion succeeds, or `nil` if it fails.
|
||||
subscript<T: SQLiteRepresentable>(index: Index) -> T? { get }
|
||||
}
|
||||
|
||||
public extension ArgumentsProtocol {
|
||||
subscript<T: SQLiteRepresentable>(index: Index) -> T? {
|
||||
T.init(self[index])
|
||||
}
|
||||
}
|
||||
@@ -1,170 +1,51 @@
|
||||
import Foundation
|
||||
|
||||
/// A protocol defining methods that can be implemented by delegates of a `Connection` object.
|
||||
/// A delegate that observes connection-level database events.
|
||||
///
|
||||
/// The `ConnectionDelegate` protocol allows a delegate to receive notifications about various
|
||||
/// events that occur within a ``Connection``, including SQL statement tracing, database update
|
||||
/// actions, and transaction commits or rollbacks. Implementing this protocol provides a way
|
||||
/// to monitor and respond to database interactions in a structured manner.
|
||||
/// Conforming types can monitor row-level updates and transaction lifecycle events. All methods are
|
||||
/// optional — default implementations do nothing.
|
||||
///
|
||||
/// ### Default Implementations
|
||||
/// This protocol is typically used for debugging, logging, or synchronizing application state with
|
||||
/// database changes.
|
||||
///
|
||||
/// The protocol provides default implementations for all methods, which do nothing. This allows
|
||||
/// conforming types to only implement the methods they are interested in without the need to
|
||||
/// provide an implementation for each method.
|
||||
/// - Important: Delegate methods are invoked synchronously on SQLite’s internal execution thread.
|
||||
/// Implementations must be lightweight and non-blocking to avoid slowing down SQL operations.
|
||||
///
|
||||
/// ## Topics
|
||||
///
|
||||
/// ### Instance Methods
|
||||
///
|
||||
/// - ``ConnectionDelegate/connection(_:trace:)``
|
||||
/// - ``ConnectionDelegate/connection(_:didUpdate:)``
|
||||
/// - ``ConnectionDelegate/connectionWillCommit(_:)``
|
||||
/// - ``ConnectionDelegate/connectionDidRollback(_:)``
|
||||
public protocol ConnectionDelegate: AnyObject {
|
||||
/// Informs the delegate that a SQL statement is being traced.
|
||||
/// Called when a row is inserted, updated, or deleted.
|
||||
///
|
||||
/// This method is called right before a SQL statement is executed, allowing the delegate
|
||||
/// to monitor the queries being sent to SQLite. This can be particularly useful for debugging
|
||||
/// purposes or for performance analysis, as it provides insights into the exact SQL being
|
||||
/// executed against the database.
|
||||
/// Enables reacting to data changes, for example to refresh caches or UI.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - connection: The ``Connection`` instance that is executing the SQL statement.
|
||||
/// - sql: A tuple containing the unexpanded and expanded forms of the SQL statement being traced.
|
||||
/// - `unexpandedSQL`: The original SQL statement as it was written by the developer.
|
||||
/// - `expandedSQL`: The SQL statement with all parameters substituted in, which shows
|
||||
/// exactly what is being sent to SQLite.
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// You can implement this method to log or analyze SQL statements:
|
||||
///
|
||||
/// ```swift
|
||||
/// func connection(
|
||||
/// _ connection: Connection,
|
||||
/// trace sql: (unexpandedSQL: String, expandedSQL: String)
|
||||
/// ) {
|
||||
/// print("Tracing SQL: \(sql.unexpandedSQL)")
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// - Important: If the implementation of this method performs any heavy operations, it could
|
||||
/// potentially slow down the execution of the SQL statement. It is recommended to keep the
|
||||
/// implementation lightweight to avoid impacting performance.
|
||||
func connection(_ connection: Connection, trace sql: (unexpandedSQL: String, expandedSQL: String))
|
||||
/// - connection: The connection where the update occurred.
|
||||
/// - action: Describes the affected database, table, and row.
|
||||
func connection(_ connection: ConnectionProtocol, didUpdate action: SQLiteAction)
|
||||
|
||||
/// Informs the delegate that an update action has occurred.
|
||||
/// Called right before a transaction is committed.
|
||||
///
|
||||
/// This method is called whenever an update action, such as insertion, modification,
|
||||
/// or deletion, is performed on the database. It provides details about the action taken,
|
||||
/// allowing the delegate to respond appropriately to changes in the database.
|
||||
/// Throwing an error aborts the commit and causes a rollback.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - connection: The `Connection` instance where the update action occurred.
|
||||
/// - action: The type of update action that occurred, represented by the ``SQLiteAction`` enum.
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// You can implement this method to respond to specific update actions:
|
||||
///
|
||||
/// ```swift
|
||||
/// func connection(_ connection: Connection, didUpdate action: SQLiteAction) {
|
||||
/// switch action {
|
||||
/// case .insert(let db, let table, let rowID):
|
||||
/// print("Inserted row \(rowID) into \(table) in database \(db).")
|
||||
/// case .update(let db, let table, let rowID):
|
||||
/// print("Updated row \(rowID) in \(table) in database \(db).")
|
||||
/// case .delete(let db, let table, let rowID):
|
||||
/// print("Deleted row \(rowID) from \(table) in database \(db).")
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// - Note: Implementing this method can help you maintain consistency and perform any
|
||||
/// necessary actions (such as UI updates or logging) in response to database changes.
|
||||
func connection(_ connection: Connection, didUpdate action: SQLiteAction)
|
||||
/// - Parameter connection: The connection about to commit.
|
||||
/// - Throws: An error to cancel and roll back the transaction.
|
||||
func connectionWillCommit(_ connection: ConnectionProtocol) throws
|
||||
|
||||
/// Informs the delegate that a transaction has been successfully committed.
|
||||
/// Called after a transaction is rolled back.
|
||||
///
|
||||
/// This method is called when a transaction has been successfully committed. It provides an
|
||||
/// opportunity for the delegate to perform any necessary actions after the commit. If this
|
||||
/// method throws an error, the COMMIT operation will be converted into a ROLLBACK, ensuring
|
||||
/// data integrity in the database.
|
||||
/// Use to perform cleanup or maintain consistency after a failure.
|
||||
///
|
||||
/// - Parameter connection: The `Connection` instance where the transaction was committed.
|
||||
///
|
||||
/// - Throws: May throw an error to abort the commit process, which will cause the transaction
|
||||
/// to be rolled back.
|
||||
///
|
||||
/// ### Example
|
||||
/// You can implement this method to perform actions after a successful commit:
|
||||
///
|
||||
/// ```swift
|
||||
/// func connectionWillCommit(_ connection: Connection) throws {
|
||||
/// print("Transaction committed successfully.")
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// - Important: Be cautious when implementing this method. If it performs heavy operations,
|
||||
/// it could delay the commit process. It is advisable to keep the implementation lightweight
|
||||
/// to maintain optimal performance and responsiveness.
|
||||
func connectionWillCommit(_ connection: Connection) throws
|
||||
|
||||
/// Informs the delegate that a transaction has been rolled back.
|
||||
///
|
||||
/// This method is called when a transaction is rolled back, allowing the delegate to handle
|
||||
/// any necessary cleanup or logging related to the rollback. This can be useful for maintaining
|
||||
/// consistency in the application state or for debugging purposes.
|
||||
///
|
||||
/// - Parameter connection: The `Connection` instance where the rollback occurred.
|
||||
///
|
||||
/// ### Example
|
||||
/// You can implement this method to respond to rollback events:
|
||||
///
|
||||
/// ```swift
|
||||
/// func connectionDidRollback(_ connection: Connection) {
|
||||
/// print("Transaction has been rolled back.")
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// - Note: It's a good practice to keep any logic within this method lightweight, as it may
|
||||
/// be called frequently during database operations, especially in scenarios involving errors
|
||||
/// that trigger rollbacks.
|
||||
func connectionDidRollback(_ connection: Connection)
|
||||
/// - Parameter connection: The connection that rolled back.
|
||||
func connectionDidRollback(_ connection: ConnectionProtocol)
|
||||
}
|
||||
|
||||
public extension ConnectionDelegate {
|
||||
/// Default implementation of the `connection(_:trace:)` method.
|
||||
///
|
||||
/// This default implementation does nothing.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - connection: The `Connection` instance that is executing the SQL statement.
|
||||
/// - sql: A tuple containing the unexpanded and expanded forms of the SQL statement being traced.
|
||||
func connection(_ connection: Connection, trace sql: (unexpandedSQL: String, expandedSQL: String)) {}
|
||||
|
||||
/// Default implementation of the `connection(_:didUpdate:)` method.
|
||||
///
|
||||
/// This default implementation does nothing.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - connection: The `Connection` instance where the update action occurred.
|
||||
/// - action: The type of update action that occurred.
|
||||
func connection(_ connection: Connection, didUpdate action: SQLiteAction) {}
|
||||
|
||||
/// Default implementation of the `connectionWillCommit(_:)` method.
|
||||
///
|
||||
/// This default implementation does nothing.
|
||||
///
|
||||
/// - Parameter connection: The `Connection` instance where the transaction was committed.
|
||||
/// - Throws: May throw an error to abort the commit process.
|
||||
func connectionWillCommit(_ connection: Connection) throws {}
|
||||
|
||||
/// Default implementation of the `connectionDidRollback(_:)` method.
|
||||
///
|
||||
/// This default implementation does nothing.
|
||||
///
|
||||
/// - Parameter connection: The `Connection` instance where the rollback occurred.
|
||||
func connectionDidRollback(_ connection: Connection) {}
|
||||
func connection(_ connection: ConnectionProtocol, didUpdate action: SQLiteAction) {}
|
||||
func connectionWillCommit(_ connection: ConnectionProtocol) throws {}
|
||||
func connectionDidRollback(_ connection: ConnectionProtocol) {}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,20 @@
|
||||
import Foundation
|
||||
import DataLiteC
|
||||
|
||||
/// A protocol that defines the interface for a database connection.
|
||||
/// A protocol that defines an SQLite database connection.
|
||||
///
|
||||
/// This protocol specifies the requirements for managing a connection
|
||||
/// to an SQLite database, including connection state, configuration via PRAGMA,
|
||||
/// executing SQL statements and scripts, transaction control, and encryption support.
|
||||
///
|
||||
/// It also includes support for delegation to handle connection-related events.
|
||||
///
|
||||
/// ## See Also
|
||||
///
|
||||
/// - ``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
|
||||
///
|
||||
/// ### Connection State
|
||||
/// ### Managing Connection State
|
||||
///
|
||||
/// - ``isAutocommit``
|
||||
/// - ``isReadonly``
|
||||
/// - ``busyTimeout``
|
||||
///
|
||||
/// ### PRAGMA Accessors
|
||||
/// ### Accessing PRAGMA Values
|
||||
///
|
||||
/// - ``applicationID``
|
||||
/// - ``foreignKeys``
|
||||
@@ -29,276 +22,376 @@ import DataLiteC
|
||||
/// - ``synchronous``
|
||||
/// - ``userVersion``
|
||||
///
|
||||
/// ### Delegation
|
||||
///
|
||||
/// - ``addDelegate(_:)``
|
||||
/// - ``removeDelegate(_:)``
|
||||
///
|
||||
/// ### SQLite Lifecycle
|
||||
/// ### Managing SQLite Lifecycle
|
||||
///
|
||||
/// - ``initialize()``
|
||||
/// - ``shutdown()``
|
||||
///
|
||||
/// ### Custom SQL Functions
|
||||
/// ### Handling Encryption
|
||||
///
|
||||
/// - ``apply(_:name:)``
|
||||
/// - ``rekey(_:name:)``
|
||||
///
|
||||
/// ### Managing Delegates
|
||||
///
|
||||
/// - ``add(delegate:)``
|
||||
/// - ``remove(delegate:)``
|
||||
/// - ``add(trace:)``
|
||||
/// - ``remove(trace:)``
|
||||
///
|
||||
/// ### Registering Custom SQL Functions
|
||||
///
|
||||
/// - ``add(function:)``
|
||||
/// - ``remove(function:)``
|
||||
///
|
||||
/// ### Statement Preparation
|
||||
/// ### Preparing SQL Statements
|
||||
///
|
||||
/// - ``prepare(sql:)``
|
||||
/// - ``prepare(sql:options:)``
|
||||
///
|
||||
/// ### Script Execution
|
||||
/// ### Executing SQL Commands
|
||||
///
|
||||
/// - ``execute(sql:)``
|
||||
/// - ``execute(raw:)``
|
||||
/// - ``execute(sql:)``
|
||||
///
|
||||
/// ### PRAGMA Execution
|
||||
/// ### Controlling PRAGMA Settings
|
||||
///
|
||||
/// - ``get(pragma:)``
|
||||
/// - ``set(pragma:value:)``
|
||||
///
|
||||
/// ### Transactions
|
||||
/// ### Managing Transactions
|
||||
///
|
||||
/// - ``beginTransaction(_:)``
|
||||
/// - ``commitTransaction()``
|
||||
/// - ``rollbackTransaction()``
|
||||
///
|
||||
/// ### Encryption Keys
|
||||
///
|
||||
/// - ``Connection/Key``
|
||||
/// - ``apply(_:name:)``
|
||||
/// - ``rekey(_:name:)``
|
||||
public protocol ConnectionProtocol: AnyObject {
|
||||
// MARK: - Connection State
|
||||
|
||||
/// Indicates whether the database connection is in autocommit mode.
|
||||
/// The autocommit state of the connection.
|
||||
///
|
||||
/// Autocommit mode is enabled by default. It remains enabled as long as no
|
||||
/// explicit transactions are active. Executing `BEGIN` disables autocommit mode,
|
||||
/// and executing `COMMIT` or `ROLLBACK` re-enables it.
|
||||
/// 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 the connection is in autocommit mode; otherwise, `false`.
|
||||
/// - SeeAlso: [sqlite3_get_autocommit()](https://sqlite.org/c3ref/get_autocommit.html)
|
||||
/// - 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 }
|
||||
|
||||
/// Indicates whether the database connection is read-only.
|
||||
/// The read-only state of the connection.
|
||||
///
|
||||
/// This property reflects the access mode of the main database for the connection.
|
||||
/// It returns `true` if the database was opened with read-only access,
|
||||
/// and `false` if it allows read-write access.
|
||||
/// Returns `true` if the main database allows only read operations, or `false` if it permits
|
||||
/// both reading and writing.
|
||||
///
|
||||
/// - Returns: `true` if the main database is read-only; otherwise, `false`.
|
||||
/// - SeeAlso: [sqlite3_db_readonly()](https://www.sqlite.org/c3ref/db_readonly.html)
|
||||
/// - 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 }
|
||||
|
||||
/// The busy timeout duration in milliseconds for the database connection.
|
||||
/// The busy timeout of the connection, in milliseconds.
|
||||
///
|
||||
/// This value determines how long SQLite will wait for a locked database to become available
|
||||
/// before returning a `SQLITE_BUSY` error. A value of zero disables the timeout and causes
|
||||
/// operations to fail immediately if the database is locked.
|
||||
/// 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: [sqlite3_busy_timeout()](https://www.sqlite.org/c3ref/busy_timeout.html)
|
||||
/// - SeeAlso: [Set A Busy Timeout](https://sqlite.org/c3ref/busy_timeout.html)
|
||||
var busyTimeout: Int32 { get set }
|
||||
|
||||
// MARK: - PRAGMA Accessors
|
||||
|
||||
/// The application ID stored in the database header.
|
||||
/// The application identifier stored in the database header.
|
||||
///
|
||||
/// This 32-bit integer is used to identify the application that created or manages the database.
|
||||
/// It is stored at a fixed offset within the database file header and can be read or modified
|
||||
/// using the `application_id` pragma.
|
||||
/// 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: [PRAGMA application_id](https://www.sqlite.org/pragma.html#pragma_application_id)
|
||||
/// - SeeAlso: [Application ID](https://sqlite.org/pragma.html#pragma_application_id)
|
||||
var applicationID: Int32 { get set }
|
||||
|
||||
/// Indicates whether foreign key constraints are enforced.
|
||||
/// The foreign key enforcement state of the connection.
|
||||
///
|
||||
/// This property enables or disables enforcement of foreign key constraints
|
||||
/// by the database connection. When set to `true`, constraints are enforced;
|
||||
/// when `false`, they are ignored.
|
||||
/// When enabled, SQLite enforces foreign key constraints on all tables. This behavior can be
|
||||
/// controlled with `PRAGMA foreign_keys`.
|
||||
///
|
||||
/// - SeeAlso: [PRAGMA foreign_keys](https://www.sqlite.org/pragma.html#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.
|
||||
///
|
||||
/// The journal mode determines how SQLite manages rollback journals,
|
||||
/// impacting durability, concurrency, and performance.
|
||||
/// Determines how SQLite maintains the rollback journal for transactions.
|
||||
///
|
||||
/// Setting this property updates the journal mode using the corresponding SQLite PRAGMA.
|
||||
///
|
||||
/// - SeeAlso: [PRAGMA journal_mode](https://www.sqlite.org/pragma.html#pragma_journal_mode)
|
||||
/// - SeeAlso: [Journal Mode](https://sqlite.org/pragma.html#pragma_journal_mode)
|
||||
var journalMode: JournalMode { get set }
|
||||
|
||||
/// The synchronous mode used by the database connection.
|
||||
/// The synchronization mode for database writes.
|
||||
///
|
||||
/// This property controls how rigorously SQLite waits for data to be
|
||||
/// physically written to disk, influencing durability and performance.
|
||||
/// Controls how aggressively SQLite syncs data to disk for durability versus performance.
|
||||
///
|
||||
/// Setting this property updates the synchronous mode using the
|
||||
/// corresponding SQLite PRAGMA.
|
||||
///
|
||||
/// - SeeAlso: [PRAGMA synchronous](https://www.sqlite.org/pragma.html#pragma_synchronous)
|
||||
/// - SeeAlso: [Synchronous](https://sqlite.org/pragma.html#pragma_synchronous)
|
||||
var synchronous: Synchronous { get set }
|
||||
|
||||
/// The user version number stored in the database.
|
||||
/// The user-defined schema version number.
|
||||
///
|
||||
/// This 32-bit integer is stored as the `user_version` pragma and
|
||||
/// is typically used by applications to track the schema version
|
||||
/// or migration state of the database.
|
||||
/// This value is stored in the database header and can be used by applications to track schema
|
||||
/// migrations or format changes.
|
||||
///
|
||||
/// Setting this property updates the corresponding SQLite PRAGMA.
|
||||
///
|
||||
/// - SeeAlso: [PRAGMA user_version](https://www.sqlite.org/pragma.html#pragma_user_version)
|
||||
/// - SeeAlso: [User Version](https://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.
|
||||
///
|
||||
/// This method sets up the global state required by SQLite. It must be called before using
|
||||
/// any other SQLite interface, unless SQLite is initialized automatically.
|
||||
/// 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.
|
||||
///
|
||||
/// A successful call has an effect only the first time it is invoked during the lifetime of
|
||||
/// the process, or the first time after a call to ``shutdown()``. All other calls are no-ops.
|
||||
/// Only the first invocation during the process lifetime, or the first after
|
||||
/// ``shutdown()``, performs real initialization. All subsequent calls are no-ops.
|
||||
///
|
||||
/// - Throws: ``Connection/Error`` if the initialization fails.
|
||||
/// - SeeAlso: [sqlite3_initialize()](https://www.sqlite.org/c3ref/initialize.html)
|
||||
static func initialize() throws(Connection.Error)
|
||||
|
||||
/// - 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.
|
||||
///
|
||||
/// This method releases global resources used by SQLite and reverses the effects of a successful
|
||||
/// call to ``initialize()``. It must be called exactly once for each successful call to
|
||||
/// ``initialize()``, and only after all database connections are closed.
|
||||
/// 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.
|
||||
///
|
||||
/// - Throws: ``Connection/Error`` if the shutdown process fails.
|
||||
/// - SeeAlso: [sqlite3_shutdown()](https://www.sqlite.org/c3ref/initialize.html)
|
||||
static func shutdown() throws(Connection.Error)
|
||||
/// 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 SQL function with the connection.
|
||||
/// Registers a custom SQLite function with the current connection.
|
||||
///
|
||||
/// This allows adding user-defined functions callable from SQL queries.
|
||||
/// 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 type of the custom SQL function to add.
|
||||
/// - Throws: ``Connection/Error`` if the function registration fails.
|
||||
func add(function: Function.Type) throws(Connection.Error)
|
||||
/// - Parameter function: The custom function type to register.
|
||||
/// - Throws: ``SQLiteError`` if registration fails.
|
||||
func add(function: Function.Type) throws(SQLiteError)
|
||||
|
||||
/// Removes a previously registered custom SQL function from the connection.
|
||||
/// Unregisters a previously registered custom SQLite function.
|
||||
///
|
||||
/// - Parameter function: The type of the custom SQL function to remove.
|
||||
/// - Throws: ``Connection/Error`` if the function removal fails.
|
||||
func remove(function: Function.Type) throws(Connection.Error)
|
||||
/// 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 ``Statement`` object that can be executed or stepped through.
|
||||
/// 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 string to prepare.
|
||||
/// - options: Options that affect statement preparation.
|
||||
/// - Returns: A prepared ``Statement`` ready for execution.
|
||||
/// - Throws: ``Connection/Error`` if statement preparation fails.
|
||||
/// - SeeAlso: [sqlite3_prepare_v3()](https://www.sqlite.org/c3ref/prepare.html)
|
||||
func prepare(sql query: String, options: Statement.Options) throws(Connection.Error) -> Statement
|
||||
|
||||
// MARK: - Script Execution
|
||||
|
||||
/// Executes a sequence of SQL statements.
|
||||
/// - 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.
|
||||
///
|
||||
/// Processes the given SQL script by executing each individual statement in order.
|
||||
///
|
||||
/// - Parameter script: A collection of SQL statements to execute.
|
||||
/// - Throws: ``Connection/Error`` if any statement execution fails.
|
||||
func execute(sql script: SQLScript) throws(Connection.Error)
|
||||
/// - SeeAlso: [Compiling An SQL Statement](https://sqlite.org/c3ref/prepare.html)
|
||||
func prepare(
|
||||
sql query: String, options: Statement.Options
|
||||
) throws(SQLiteError) -> StatementProtocol
|
||||
|
||||
/// Executes a raw SQL string.
|
||||
///
|
||||
/// Executes the provided raw SQL string as a single operation.
|
||||
///
|
||||
/// - Parameter sql: The raw SQL string to execute.
|
||||
/// - Throws: ``Connection/Error`` if the execution fails.
|
||||
func execute(raw sql: String) throws(Connection.Error)
|
||||
// MARK: - SQL Execution
|
||||
|
||||
// MARK: - PRAGMA Execution
|
||||
|
||||
/// Retrieves the value of a PRAGMA setting from the database.
|
||||
/// Executes one or more SQL statements in a single step.
|
||||
///
|
||||
/// - Parameter pragma: The PRAGMA setting to retrieve.
|
||||
/// - Returns: The current value of the PRAGMA, or `nil` if the value is not available.
|
||||
/// - Throws: ``Connection/Error`` if the operation fails.
|
||||
func get<T: SQLiteRawRepresentable>(pragma: Pragma) throws(Connection.Error) -> T?
|
||||
/// 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 sql: 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(raw sql: String) throws(SQLiteError)
|
||||
|
||||
/// Sets the value of a PRAGMA setting in the database.
|
||||
/// Executes multiple SQL statements from a script.
|
||||
///
|
||||
/// The provided ``SQLScript`` may contain one or more SQL statements separated by semicolons.
|
||||
/// Each statement is executed sequentially using the current connection. This is useful for
|
||||
/// running migration scripts or initializing database schemas.
|
||||
///
|
||||
/// - Parameter script: The SQL script to execute.
|
||||
/// - Throws: ``SQLiteError`` if any statement in the script fails.
|
||||
func execute(sql script: SQLScript) 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<T: SQLiteRepresentable>(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 setting to modify.
|
||||
/// - value: The new value to assign to the PRAGMA.
|
||||
/// - Returns: The resulting value after the assignment, or `nil` if unavailable.
|
||||
/// - Throws: ``Connection/Error`` if the operation fails.
|
||||
@discardableResult
|
||||
func set<T: SQLiteRawRepresentable>(pragma: Pragma, value: T) throws(Connection.Error) -> T?
|
||||
/// - 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<T: SQLiteRepresentable>(pragma: Pragma, value: T) throws(SQLiteError)
|
||||
|
||||
// MARK: - Transactions
|
||||
|
||||
/// Begins a database transaction of the specified type.
|
||||
/// Begins a new transaction of the specified type.
|
||||
///
|
||||
/// - Parameter type: The type of transaction to begin (e.g., deferred, immediate, exclusive).
|
||||
/// - Throws: ``Connection/Error`` if starting the transaction fails.
|
||||
/// - SeeAlso: [BEGIN TRANSACTION](https://www.sqlite.org/lang_transaction.html)
|
||||
func beginTransaction(_ type: TransactionType) throws(Connection.Error)
|
||||
|
||||
/// Commits the current database transaction.
|
||||
/// Starts an explicit transaction using the given ``TransactionType``. If a transaction is
|
||||
/// already active, this method throws an error.
|
||||
///
|
||||
/// - Throws: ``Connection/Error`` if committing the transaction fails.
|
||||
/// - SeeAlso: [COMMIT](https://www.sqlite.org/lang_transaction.html)
|
||||
func commitTransaction() throws(Connection.Error)
|
||||
|
||||
/// Rolls back the current database transaction.
|
||||
/// - Parameter type: The transaction type to begin.
|
||||
/// - Throws: ``SQLiteError`` if the transaction could not be started.
|
||||
///
|
||||
/// - Throws: ``Connection/Error`` if rolling back the transaction fails.
|
||||
/// - SeeAlso: [ROLLBACK](https://www.sqlite.org/lang_transaction.html)
|
||||
func rollbackTransaction() throws(Connection.Error)
|
||||
/// - SeeAlso: [Transaction](https://sqlite.org/lang_transaction.html)
|
||||
func beginTransaction(_ type: TransactionType) throws(SQLiteError)
|
||||
|
||||
// MARK: - Encryption Keys
|
||||
|
||||
/// Applies an encryption key to the database connection.
|
||||
/// Commits the current transaction.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - key: The encryption key to apply.
|
||||
/// - name: An optional name identifying the database to apply the key to.
|
||||
/// - Throws: ``Connection/Error`` if applying the key fails.
|
||||
func apply(_ key: Connection.Key, name: String?) throws(Connection.Error)
|
||||
|
||||
/// Changes the encryption key for the database connection.
|
||||
/// Makes all changes made during the transaction permanent. If no transaction is active, this
|
||||
/// method has no effect.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - key: The new encryption key to set.
|
||||
/// - name: An optional name identifying the database to rekey.
|
||||
/// - Throws: ``Connection/Error`` if rekeying fails.
|
||||
func rekey(_ key: Connection.Key, name: String?) throws(Connection.Error)
|
||||
/// - 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: - PRAGMA Accessors
|
||||
// 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) }
|
||||
@@ -323,71 +416,40 @@ public extension ConnectionProtocol {
|
||||
get { try! get(pragma: .userVersion) ?? 0 }
|
||||
set { try! set(pragma: .userVersion, value: newValue) }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SQLite Lifecycle
|
||||
|
||||
public extension ConnectionProtocol {
|
||||
static func initialize() throws(Connection.Error) {
|
||||
let status = sqlite3_initialize()
|
||||
if status != SQLITE_OK {
|
||||
throw Connection.Error(code: status, message: "")
|
||||
}
|
||||
|
||||
func prepare(sql query: String) throws(SQLiteError) -> StatementProtocol {
|
||||
try prepare(sql: query, options: [])
|
||||
}
|
||||
|
||||
static func shutdown() throws(Connection.Error) {
|
||||
let status = sqlite3_shutdown()
|
||||
if status != SQLITE_OK {
|
||||
throw Connection.Error(code: status, message: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Script Execution
|
||||
|
||||
public extension ConnectionProtocol {
|
||||
func execute(sql script: SQLScript) throws(Connection.Error) {
|
||||
func execute(sql script: SQLScript) throws(SQLiteError) {
|
||||
for query in script {
|
||||
let stmt = try prepare(sql: query, options: [])
|
||||
let stmt = try prepare(sql: query)
|
||||
while try stmt.step() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - PRAGMA Execution
|
||||
|
||||
public extension ConnectionProtocol {
|
||||
func get<T: SQLiteRawRepresentable>(pragma: Pragma) throws(Connection.Error) -> T? {
|
||||
let stmt = try prepare(sql: "PRAGMA \(pragma)", options: [])
|
||||
|
||||
func get<T: SQLiteRepresentable>(pragma: Pragma) throws(SQLiteError) -> T? {
|
||||
let stmt = try prepare(sql: "PRAGMA \(pragma)")
|
||||
switch try stmt.step() {
|
||||
case true: return stmt.columnValue(at: 0)
|
||||
case true: return stmt.columnValue(at: 0)
|
||||
case false: return nil
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func set<T: SQLiteRawRepresentable>(pragma: Pragma, value: T) throws(Connection.Error) -> T? {
|
||||
func set<T: SQLiteRepresentable>(pragma: Pragma, value: T) throws(SQLiteError) {
|
||||
let query = "PRAGMA \(pragma) = \(value.sqliteLiteral)"
|
||||
let stmt = try prepare(sql: query, options: [])
|
||||
switch try stmt.step() {
|
||||
case true: return stmt.columnValue(at: 0)
|
||||
case false: return nil
|
||||
}
|
||||
try prepare(sql: query).step()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Transactions
|
||||
|
||||
public extension ConnectionProtocol {
|
||||
func beginTransaction(_ type: TransactionType = .deferred) throws(Connection.Error) {
|
||||
|
||||
func beginTransaction(_ type: TransactionType = .deferred) throws(SQLiteError) {
|
||||
try prepare(sql: "BEGIN \(type) TRANSACTION", options: []).step()
|
||||
}
|
||||
|
||||
func commitTransaction() throws(Connection.Error) {
|
||||
func commitTransaction() throws(SQLiteError) {
|
||||
try prepare(sql: "COMMIT TRANSACTION", options: []).step()
|
||||
}
|
||||
|
||||
func rollbackTransaction() throws(Connection.Error) {
|
||||
func rollbackTransaction() throws(SQLiteError) {
|
||||
try prepare(sql: "ROLLBACK TRANSACTION", options: []).step()
|
||||
}
|
||||
}
|
||||
|
||||
22
Sources/DataLiteCore/Protocols/ConnectionTraceDelegate.swift
Normal file
22
Sources/DataLiteCore/Protocols/ConnectionTraceDelegate.swift
Normal file
@@ -0,0 +1,22 @@
|
||||
import Foundation
|
||||
|
||||
/// A delegate that receives SQL statement trace callbacks.
|
||||
///
|
||||
/// Conforming types can inspect SQL before and after parameter expansion for logging, diagnostics,
|
||||
/// or profiling. Register a trace delegate with ``ConnectionProtocol/add(trace:)``.
|
||||
///
|
||||
/// - Important: Callbacks execute synchronously on SQLite’s internal thread. Keep implementations
|
||||
/// lightweight to avoid slowing down query execution.
|
||||
public protocol ConnectionTraceDelegate: AnyObject {
|
||||
/// Represents traced SQL text before and after parameter substitution.
|
||||
typealias Trace = (unexpandedSQL: String, expandedSQL: String)
|
||||
|
||||
/// Called before a SQL statement is executed.
|
||||
///
|
||||
/// Use to trace or log executed statements for debugging or profiling.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - connection: The active database connection.
|
||||
/// - sql: A tuple with the original and expanded SQL text.
|
||||
func connection(_ connection: ConnectionProtocol, trace sql: Trace)
|
||||
}
|
||||
42
Sources/DataLiteCore/Protocols/SQLiteBindable.swift
Normal file
42
Sources/DataLiteCore/Protocols/SQLiteBindable.swift
Normal file
@@ -0,0 +1,42 @@
|
||||
import Foundation
|
||||
|
||||
/// A protocol whose conforming types can be used in SQLite statements and queries.
|
||||
///
|
||||
/// Conforming types provide a raw SQLite value for binding to prepared-statement parameters
|
||||
/// and an SQL literal that can be inserted directly into SQL text.
|
||||
///
|
||||
/// ```swift
|
||||
/// struct Device: SQLiteBindable {
|
||||
/// var model: String
|
||||
///
|
||||
/// var sqliteValue: SQLiteValue {
|
||||
/// return .text(model)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## Topics
|
||||
///
|
||||
/// ### Instance Properties
|
||||
///
|
||||
/// - ``sqliteValue``
|
||||
/// - ``sqliteLiteral``
|
||||
public protocol SQLiteBindable {
|
||||
/// The raw SQLite value representation.
|
||||
///
|
||||
/// Supplies a value compatible with SQLite's internal representation. Used when binding
|
||||
/// conforming types to parameters of a prepared SQLite statement.
|
||||
var sqliteValue: SQLiteValue { get }
|
||||
|
||||
/// The SQL literal representation.
|
||||
///
|
||||
/// Provides a string that conforms to SQL syntax and is compatible with SQLite's rules
|
||||
/// for literals. Defaults to ``SQLiteValue/sqliteLiteral``.
|
||||
var sqliteLiteral: String { get }
|
||||
}
|
||||
|
||||
public extension SQLiteBindable {
|
||||
var sqliteLiteral: String {
|
||||
sqliteValue.sqliteLiteral
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
/// A type that can be represented as literals in an SQL query.
|
||||
///
|
||||
/// This protocol ensures that types conforming to it provide a string representation
|
||||
/// that can be used directly in SQL queries. Each conforming type must implement
|
||||
/// a way to return its corresponding SQLite literal representation.
|
||||
///
|
||||
/// **Example implementation:**
|
||||
///
|
||||
/// ```swift
|
||||
/// struct Device: SQLiteLiteralable {
|
||||
/// var model: String
|
||||
///
|
||||
/// var sqliteLiteral: String {
|
||||
/// return "'\(model)'"
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
public protocol SQLiteLiteralable {
|
||||
/// Returns the string representation of the object, formatted as an SQLite literal.
|
||||
///
|
||||
/// This property should return a string that adheres to SQL query syntax and is compatible
|
||||
/// with SQLite's rules for literals.
|
||||
///
|
||||
/// For example:
|
||||
/// - **Integers:** `42` -> `"42"`
|
||||
/// - **Strings:** `"Hello"` -> `"'Hello'"` (with single quotes)
|
||||
/// - **Booleans:** `true` -> `"1"`, `false` -> `"0"`
|
||||
/// - **Data:** `Data([0x01, 0x02])` -> `"X'0102'"`
|
||||
/// - **Null:** `NSNull()` -> `"NULL"`
|
||||
var sqliteLiteral: String { get }
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
/// A type that can be used as a parameter in an SQL statement.
|
||||
///
|
||||
/// Conforming types provide a raw SQLite-compatible representation of their values,
|
||||
/// enabling them to be directly bound to SQL statements.
|
||||
///
|
||||
/// **Example implementation:**
|
||||
///
|
||||
/// ```swift
|
||||
/// struct Device: SQLiteRawBindable {
|
||||
/// var model: String
|
||||
///
|
||||
/// var sqliteRawValue: SQLiteRawValue {
|
||||
/// return .text(model)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
public protocol SQLiteRawBindable: SQLiteLiteralable {
|
||||
/// The raw SQLite representation of the value.
|
||||
///
|
||||
/// This property provides a value that is compatible with SQLite's internal representation,
|
||||
/// such as text, integer, real, blob, or null. It is used when binding the conforming
|
||||
/// type to SQL statements.
|
||||
var sqliteRawValue: SQLiteRawValue { get }
|
||||
}
|
||||
|
||||
public extension SQLiteRawBindable {
|
||||
/// The string representation of the value as an SQLite literal.
|
||||
///
|
||||
/// This property leverages the `sqliteRawValue` to produce a valid SQLite-compatible literal,
|
||||
/// formatted appropriately for use in SQL queries.
|
||||
var sqliteLiteral: String {
|
||||
sqliteRawValue.sqliteLiteral
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
/// A type that can be initialized from a raw SQLite value.
|
||||
///
|
||||
/// This protocol extends `SQLiteRawBindable` and requires conforming types to implement
|
||||
/// an initializer that can convert a raw SQLite value into the corresponding type.
|
||||
///
|
||||
/// **Example implementation:**
|
||||
///
|
||||
/// ```swift
|
||||
/// struct Device: SQLiteRawRepresentable {
|
||||
/// var model: String
|
||||
///
|
||||
/// var sqliteRawValue: SQLiteRawValue {
|
||||
/// return .text(model)
|
||||
/// }
|
||||
///
|
||||
/// init?(_ sqliteRawValue: SQLiteRawValue) {
|
||||
/// guard
|
||||
/// case let .text(value) = sqliteRawValue
|
||||
/// else { return nil }
|
||||
/// self.model = value
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
public protocol SQLiteRawRepresentable: SQLiteRawBindable {
|
||||
/// Initializes an instance from a raw SQLite value.
|
||||
///
|
||||
/// This initializer should map the provided SQLite raw value to the appropriate type.
|
||||
/// If the conversion is not possible (e.g., if the raw value is of an incompatible type),
|
||||
/// the initializer should return `nil`.
|
||||
///
|
||||
/// - Parameter sqliteRawValue: A raw SQLite value to be converted.
|
||||
init?(_ sqliteRawValue: SQLiteRawValue)
|
||||
}
|
||||
35
Sources/DataLiteCore/Protocols/SQLiteRepresentable.swift
Normal file
35
Sources/DataLiteCore/Protocols/SQLiteRepresentable.swift
Normal file
@@ -0,0 +1,35 @@
|
||||
import Foundation
|
||||
|
||||
/// A protocol whose conforming types can be initialized from raw SQLite values.
|
||||
///
|
||||
/// This protocol extends ``SQLiteBindable`` and adds an initializer for converting a raw SQLite
|
||||
/// value into the corresponding type.
|
||||
///
|
||||
/// ```swift
|
||||
/// struct Device: SQLiteRepresentable {
|
||||
/// var model: String
|
||||
///
|
||||
/// var sqliteValue: SQLiteValue {
|
||||
/// return .text(model)
|
||||
/// }
|
||||
///
|
||||
/// init?(_ value: SQLiteValue) {
|
||||
/// switch value {
|
||||
/// case .text(let value):
|
||||
/// self.model = value
|
||||
/// default:
|
||||
/// return nil
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
public protocol SQLiteRepresentable: SQLiteBindable {
|
||||
/// Initializes an instance from a raw SQLite value.
|
||||
///
|
||||
/// The initializer should map the provided raw SQLite value to the corresponding type.
|
||||
/// If the conversion is not possible (for example, the value has an incompatible type),
|
||||
/// the initializer should return `nil`.
|
||||
///
|
||||
/// - Parameter value: The raw SQLite value to convert.
|
||||
init?(_ value: SQLiteValue)
|
||||
}
|
||||
311
Sources/DataLiteCore/Protocols/StatementProtocol.swift
Normal file
311
Sources/DataLiteCore/Protocols/StatementProtocol.swift
Normal file
@@ -0,0 +1,311 @@
|
||||
import Foundation
|
||||
|
||||
/// A protocol that defines a prepared SQLite statement.
|
||||
///
|
||||
/// Conforming types manage the statement's lifetime, including initialization and finalization.
|
||||
/// The protocol exposes facilities for parameter discovery and binding, stepping, resetting, and
|
||||
/// reading result columns.
|
||||
///
|
||||
/// ## Topics
|
||||
///
|
||||
/// ### Binding Parameters
|
||||
///
|
||||
/// - ``parameterCount()``
|
||||
/// - ``parameterIndexBy(_:)``
|
||||
/// - ``parameterNameBy(_:)``
|
||||
/// - ``bind(_:at:)-(SQLiteValue,_)``
|
||||
/// - ``bind(_:by:)-(SQLiteValue,_)``
|
||||
/// - ``bind(_:at:)-(T?,_)``
|
||||
/// - ``bind(_:by:)-(T?,_)``
|
||||
/// - ``bind(_:)``
|
||||
/// - ``clearBindings()``
|
||||
///
|
||||
/// ### Statement Execution
|
||||
///
|
||||
/// - ``step()``
|
||||
/// - ``reset()``
|
||||
/// - ``execute(_:)``
|
||||
///
|
||||
/// ### Result Set
|
||||
///
|
||||
/// - ``columnCount()``
|
||||
/// - ``columnName(at:)``
|
||||
/// - ``columnValue(at:)->SQLiteValue``
|
||||
/// - ``columnValue(at:)->T?``
|
||||
/// - ``currentRow()``
|
||||
public protocol StatementProtocol {
|
||||
// MARK: - Binding Parameters
|
||||
|
||||
/// Returns the number of parameters in the prepared SQLite statement.
|
||||
///
|
||||
/// This value corresponds to the highest parameter index in the compiled SQL statement.
|
||||
/// Parameters may be specified using anonymous placeholders (`?`), numbered placeholders
|
||||
/// (`?NNN`), or named placeholders (`:name`, `@name`, `$name`).
|
||||
///
|
||||
/// For statements using only `?` or named parameters, this value equals the number of parameters.
|
||||
/// However, if numbered placeholders are used, the sequence may contain gaps — for example,
|
||||
/// a statement containing `?2` and `?5` will report a parameter count of `5`.
|
||||
///
|
||||
/// - Returns: The index of the largest (rightmost) parameter in the prepared statement.
|
||||
///
|
||||
/// - SeeAlso: [Number Of SQL Parameters](https://sqlite.org/c3ref/bind_parameter_count.html)
|
||||
func parameterCount() -> Int32
|
||||
|
||||
/// Returns the index of a parameter identified by its name.
|
||||
///
|
||||
/// The `name` must exactly match the placeholder used in the SQL statement, including its
|
||||
/// prefix character (`:`, `@`, or `$`). For example, if the SQL includes `WHERE id = :id`,
|
||||
/// you must call `parameterIndexBy(":id")`.
|
||||
///
|
||||
/// If no parameter with the specified `name` exists in the prepared statement, this function
|
||||
/// returns `0`.
|
||||
///
|
||||
/// - Parameter name: The parameter name as written in the SQL statement, including its prefix.
|
||||
/// - Returns: The 1-based parameter index corresponding to `name`, or `0` if not found.
|
||||
///
|
||||
/// - SeeAlso: [Index Of A Parameter With A Given Name](https://sqlite.org/c3ref/bind_parameter_index.html)
|
||||
func parameterIndexBy(_ name: String) -> Int32
|
||||
|
||||
/// Returns the name of the parameter at the specified index.
|
||||
///
|
||||
/// The returned string matches the placeholder as written in the SQL statement, including its
|
||||
/// prefix (`:`, `@`, or `$`). For positional (unnamed) parameters, or if the `index` is out of
|
||||
/// range, this function returns `nil`.
|
||||
///
|
||||
/// - Parameter index: A 1-based parameter index.
|
||||
/// - Returns: The parameter name as written in the SQL statement, or `nil` if unavailable.
|
||||
///
|
||||
/// - SeeAlso: [Name Of A Host Parameter](https://sqlite.org/c3ref/bind_parameter_name.html)
|
||||
func parameterNameBy(_ index: Int32) -> String?
|
||||
|
||||
/// Binds a raw SQLite value to a parameter at the specified index.
|
||||
///
|
||||
/// Assigns the given `SQLiteValue` to the parameter at the provided 1-based index within the
|
||||
/// prepared statement. If the index is out of range, or if the statement is invalid or
|
||||
/// finalized, this function throws an error.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - value: The `SQLiteValue` to bind to the parameter.
|
||||
/// - index: The 1-based index of the parameter to bind.
|
||||
/// - Throws: ``SQLiteError`` if the value cannot be bound (e.g., index out of range).
|
||||
///
|
||||
/// - SeeAlso: [Binding Values To Prepared Statements](
|
||||
/// https://sqlite.org/c3ref/bind_blob.html)
|
||||
func bind(_ value: SQLiteValue, at index: Int32) throws(SQLiteError)
|
||||
|
||||
/// Binds a raw SQLite value to a parameter by its name.
|
||||
///
|
||||
/// Resolves `name` to an index and binds `value` to that parameter. The `name` must include
|
||||
/// its prefix (e.g., `:AAA`, `@AAA`, `$AAA`). Binding a value to a parameter that does not
|
||||
/// exist results in an error.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - value: The ``SQLiteValue`` to bind.
|
||||
/// - name: The parameter name as written in SQL, including its prefix.
|
||||
/// - Throws: ``SQLiteError`` if binding fails.
|
||||
///
|
||||
/// - SeeAlso: [Binding Values To Prepared Statements](
|
||||
/// https://sqlite.org/c3ref/bind_blob.html)
|
||||
func bind(_ value: SQLiteValue, by name: String) throws(SQLiteError)
|
||||
|
||||
/// Binds a typed value conforming to `SQLiteBindable` by index.
|
||||
///
|
||||
/// Converts `value` to its raw SQLite representation and binds it at `index`. If `value` is
|
||||
/// `nil`, binds `NULL`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - value: The value to bind. If `nil`, `NULL` is bound.
|
||||
/// - index: The 1-based parameter index.
|
||||
/// - Throws: ``SQLiteError`` if binding fails.
|
||||
///
|
||||
/// - SeeAlso: [Binding Values To Prepared Statements](
|
||||
/// https://sqlite.org/c3ref/bind_blob.html)
|
||||
func bind<T: SQLiteBindable>(_ value: T?, at index: Int32) throws(SQLiteError)
|
||||
|
||||
/// Binds a typed value conforming to `SQLiteBindable` by name.
|
||||
///
|
||||
/// Resolves `name` to a parameter index and binds the raw SQLite representation of `value`.
|
||||
/// If `value` is `nil`, binds `NULL`. The `name` must include its prefix (e.g., `:AAA`,
|
||||
/// `@AAA`, `$AAA`). Binding to a non-existent parameter results in an error.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - value: The value to bind. If `nil`, `NULL` is bound.
|
||||
/// - name: The parameter name as written in SQL, including its prefix.
|
||||
/// - Throws: ``SQLiteError`` if binding fails.
|
||||
///
|
||||
/// - SeeAlso: [Binding Values To Prepared Statements](
|
||||
/// https://sqlite.org/c3ref/bind_blob.html)
|
||||
func bind<T: SQLiteBindable>(_ value: T?, by name: String) throws(SQLiteError)
|
||||
|
||||
/// Binds the contents of a row to named statement parameters by column name.
|
||||
///
|
||||
/// For each `(column, value)` pair in `row`, treats `column` as a named parameter `:column`
|
||||
/// and binds `value` to that parameter. Parameter names in the SQL must match the row's
|
||||
/// column names (including the leading colon). Binding to a non-existent parameter results
|
||||
/// in an error.
|
||||
///
|
||||
/// - Parameter row: The row whose column values are to be bound.
|
||||
/// - Throws: ``SQLiteError`` if any value cannot be bound.
|
||||
///
|
||||
/// - SeeAlso: [Binding Values To Prepared Statements](
|
||||
/// https://sqlite.org/c3ref/bind_blob.html)
|
||||
func bind(_ row: SQLiteRow) throws(SQLiteError)
|
||||
|
||||
/// Clears all parameter bindings of the prepared statement.
|
||||
///
|
||||
/// After calling this function, all parameters are set to `NULL`. Call this when reusing the
|
||||
/// statement with a different set of parameter values.
|
||||
///
|
||||
/// - Throws: ``SQLiteError`` if clearing bindings fails.
|
||||
///
|
||||
/// - SeeAlso: [Reset All Bindings](https://sqlite.org/c3ref/clear_bindings.html)
|
||||
func clearBindings() throws(SQLiteError)
|
||||
|
||||
// MARK: - Statement Execution
|
||||
|
||||
/// Evaluates the prepared statement and advances to the next result row.
|
||||
///
|
||||
/// Call repeatedly to iterate over all rows. It returns `true` while a new row is available.
|
||||
/// After the final row it returns `false`. Statements that produce no rows return `false`
|
||||
/// immediately. Reset the statement and clear bindings before re-executing.
|
||||
///
|
||||
/// - Returns: `true` if a new row is available, or `false` when no more rows remain.
|
||||
/// - Throws: ``SQLiteError`` if evaluation fails.
|
||||
///
|
||||
/// - SeeAlso: [Evaluate An SQL Statement](https://sqlite.org/c3ref/step.html)
|
||||
@discardableResult
|
||||
func step() throws(SQLiteError) -> Bool
|
||||
|
||||
/// Resets the prepared SQLite statement to its initial state, ready for re-execution.
|
||||
///
|
||||
/// Undoes the effects of previous calls to ``step()``. After reset, the statement may be
|
||||
/// executed again with the same or new inputs. This does not clear parameter bindings.
|
||||
/// Call ``clearBindings()`` to set all parameters to `NULL` if needed.
|
||||
///
|
||||
/// - Throws: ``SQLiteError`` if the statement cannot be reset.
|
||||
///
|
||||
/// - SeeAlso: [Reset A Prepared Statement](https://sqlite.org/c3ref/reset.html)
|
||||
func reset() throws(SQLiteError)
|
||||
|
||||
/// Executes the statement once per provided parameter row.
|
||||
///
|
||||
/// For each row, binds values, steps until completion (discarding any result rows), clears
|
||||
/// bindings, and resets the statement. Use this for efficient batch executions (e.g., inserts
|
||||
/// or updates) with different parameters per run.
|
||||
///
|
||||
/// - Parameter rows: Parameter rows to bind for each execution.
|
||||
/// - Throws: ``SQLiteError`` if binding, stepping, clearing, or resetting fails.
|
||||
func execute(_ rows: [SQLiteRow]) throws(SQLiteError)
|
||||
|
||||
// MARK: - Result Set
|
||||
|
||||
/// Returns the number of columns in the current result set.
|
||||
///
|
||||
/// If this value is `0`, the prepared statement does not produce rows. This is typically
|
||||
/// the case for statements that do not return data.
|
||||
///
|
||||
/// - Returns: The number of columns in the result set, or `0` if there are no result columns.
|
||||
///
|
||||
/// - SeeAlso: [Number Of Columns In A Result Set](
|
||||
/// https://sqlite.org/c3ref/column_count.html)
|
||||
func columnCount() -> Int32
|
||||
|
||||
/// Returns the name of the column at the specified index in the result set.
|
||||
///
|
||||
/// The column name appears as defined in the SQL statement. If the index is out of bounds, this
|
||||
/// function returns `nil`.
|
||||
///
|
||||
/// - Parameter index: The 0-based index of the column for which to retrieve the name.
|
||||
/// - Returns: The name of the column at the given index, or `nil` if the index is invalid.
|
||||
///
|
||||
/// - SeeAlso: [Column Names In A Result Set](https://sqlite.org/c3ref/column_name.html)
|
||||
func columnName(at index: Int32) -> String?
|
||||
|
||||
/// Returns the raw SQLite value at the given result column index.
|
||||
///
|
||||
/// Retrieves the value for the specified column in the current result row of the prepared
|
||||
/// statement, represented as a ``SQLiteValue``. If the index is out of range, returns
|
||||
/// ``SQLiteValue/null``.
|
||||
///
|
||||
/// - Parameter index: The 0-based index of the result column to access.
|
||||
/// - Returns: The raw ``SQLiteValue`` at the specified column.
|
||||
///
|
||||
/// - SeeAlso: [Result Values From A Query](https://sqlite.org/c3ref/column_blob.html)
|
||||
func columnValue(at index: Int32) -> SQLiteValue
|
||||
|
||||
/// Returns the value of the result column at `index`, converted to `T`.
|
||||
///
|
||||
/// Attempts to initialize `T` from the raw ``SQLiteValue`` at `index` using
|
||||
/// ``SQLiteRepresentable``. Returns `nil` if the conversion is not possible.
|
||||
///
|
||||
/// - Parameter index: The 0-based result column index.
|
||||
/// - Returns: A value of type `T` if conversion succeeds, otherwise `nil`.
|
||||
///
|
||||
/// - SeeAlso: [Result Values From A Query](https://sqlite.org/c3ref/column_blob.html)
|
||||
func columnValue<T: SQLiteRepresentable>(at index: Int32) -> T?
|
||||
|
||||
/// Returns the current result row.
|
||||
///
|
||||
/// Builds a row by iterating over all result columns at the current cursor position, reading
|
||||
/// each column's name and value, and inserting them into the row.
|
||||
///
|
||||
/// - Returns: A `SQLiteRow` mapping column names to values, or `nil` if there are no columns.
|
||||
///
|
||||
/// - SeeAlso: [Result Values From A Query](https://sqlite.org/c3ref/column_blob.html)
|
||||
func currentRow() -> SQLiteRow?
|
||||
}
|
||||
|
||||
// MARK: - Default Implementation
|
||||
|
||||
public extension StatementProtocol {
|
||||
func bind(_ value: SQLiteValue, by name: String) throws(SQLiteError) {
|
||||
try bind(value, at: parameterIndexBy(name))
|
||||
}
|
||||
|
||||
func bind<T: SQLiteBindable>(_ value: T?, at index: Int32) throws(SQLiteError) {
|
||||
try bind(value?.sqliteValue ?? .null, at: index)
|
||||
}
|
||||
|
||||
func bind<T: SQLiteBindable>(_ value: T?, by name: String) throws(SQLiteError) {
|
||||
try bind(value?.sqliteValue ?? .null, at: parameterIndexBy(name))
|
||||
}
|
||||
|
||||
func bind(_ row: SQLiteRow) throws(SQLiteError) {
|
||||
for (column, value) in row {
|
||||
let index = parameterIndexBy(":\(column)")
|
||||
try bind(value, at: index)
|
||||
}
|
||||
}
|
||||
|
||||
func execute(_ rows: [SQLiteRow]) throws(SQLiteError) {
|
||||
for row in rows {
|
||||
try bind(row)
|
||||
var hasStep: Bool
|
||||
repeat {
|
||||
hasStep = try step()
|
||||
} while hasStep
|
||||
try clearBindings()
|
||||
try reset()
|
||||
}
|
||||
}
|
||||
|
||||
func columnValue<T: SQLiteRepresentable>(at index: Int32) -> T? {
|
||||
T(columnValue(at: index))
|
||||
}
|
||||
|
||||
func currentRow() -> SQLiteRow? {
|
||||
let columnCount = columnCount()
|
||||
guard columnCount > 0 else { return nil }
|
||||
|
||||
var row = SQLiteRow()
|
||||
row.reserveCapacity(columnCount)
|
||||
|
||||
for index in 0..<columnCount {
|
||||
let name = columnName(at: index)!
|
||||
let value = columnValue(at: index)
|
||||
row[name] = value
|
||||
}
|
||||
|
||||
return row
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user