DataLiteCore swift package

This commit is contained in:
2025-04-24 23:48:46 +03:00
parent b0e52a72b7
commit 6f955b2c43
70 changed files with 7939 additions and 1 deletions

View File

@@ -0,0 +1,170 @@
import Foundation
/// A protocol defining methods that can be implemented by delegates of a `Connection` object.
///
/// 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.
///
/// ### Default Implementations
///
/// 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.
///
/// ## Topics
///
/// ### Instance Methods
///
/// - ``ConnectionDelegate/connection(_:trace:)``
/// - ``ConnectionDelegate/connection(_:didUpdate:)``
/// - ``ConnectionDelegate/connectionDidCommit(_:)``
/// - ``ConnectionDelegate/connectionDidRollback(_:)``
public protocol ConnectionDelegate: AnyObject {
/// Informs the delegate that a SQL statement is being traced.
///
/// 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.
///
/// - 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))
/// Informs the delegate that an update action has occurred.
///
/// 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.
///
/// - 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)
/// Informs the delegate that a transaction has been successfully committed.
///
/// 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.
///
/// - 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 connectionDidCommit(_ 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 connectionDidCommit(_ 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)
}
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 `connectionDidCommit(_:)` 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 connectionDidCommit(_ 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) {}
}

View File

@@ -0,0 +1,390 @@
import Foundation
import DataLiteC
/// A protocol that defines the interface for a 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``
///
/// ## Topics
///
/// ### Delegation
///
/// - ``ConnectionDelegate``
/// - ``delegate``
///
/// ### Connection State
///
/// - ``isAutocommit``
/// - ``isReadonly``
/// - ``busyTimeout``
///
/// ### PRAGMA Accessors
///
/// - ``applicationID``
/// - ``foreignKeys``
/// - ``journalMode``
/// - ``synchronous``
/// - ``userVersion``
///
/// ### SQLite Lifecycle
///
/// - ``initialize()``
/// - ``shutdown()``
///
/// ### Custom SQL Functions
///
/// - ``add(function:)``
/// - ``remove(function:)``
///
/// ### Statement Preparation
///
/// - ``prepare(sql:options:)``
///
/// ### Script Execution
///
/// - ``execute(sql:)``
/// - ``execute(raw:)``
///
/// ### PRAGMA Execution
///
/// - ``get(pragma:)``
/// - ``set(pragma:value:)``
///
/// ### Transactions
///
/// - ``beginTransaction(_:)``
/// - ``commitTransaction()``
/// - ``rollbackTransaction()``
///
/// ### Encryption Keys
///
/// - ``Connection/Key``
/// - ``apply(_:name:)``
/// - ``rekey(_:name:)``
public protocol ConnectionProtocol: AnyObject {
// MARK: - Delegation
/// An optional delegate to receive connection-related events and callbacks.
///
/// The delegate allows external objects to monitor or respond to events
/// occurring during the lifetime of the connection, such as errors,
/// transaction commits, or other significant state changes.
var delegate: ConnectionDelegate? { get set }
// MARK: - Connection State
/// Indicates whether the database connection is in autocommit mode.
///
/// 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.
///
/// - Returns: `true` if the connection is in autocommit mode; otherwise, `false`.
/// - SeeAlso: [sqlite3_get_autocommit()](https://sqlite.org/c3ref/get_autocommit.html)
var isAutocommit: Bool { get }
/// Indicates whether the database connection is read-only.
///
/// 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 is read-only; otherwise, `false`.
/// - SeeAlso: [sqlite3_db_readonly()](https://www.sqlite.org/c3ref/db_readonly.html)
var isReadonly: Bool { get }
/// The busy timeout duration in milliseconds for the database connection.
///
/// 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.
///
/// - SeeAlso: [sqlite3_busy_timeout()](https://www.sqlite.org/c3ref/busy_timeout.html)
var busyTimeout: Int32 { get set }
// MARK: - PRAGMA Accessors
/// The application ID 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.
///
/// - SeeAlso: [PRAGMA application_id](https://www.sqlite.org/pragma.html#pragma_application_id)
var applicationID: Int32 { get set }
/// Indicates whether foreign key constraints are enforced.
///
/// 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.
///
/// - SeeAlso: [PRAGMA foreign_keys](https://www.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.
///
/// 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)
var journalMode: JournalMode { get set }
/// The synchronous mode used by the database connection.
///
/// This property controls how rigorously SQLite waits for data to be
/// physically written to disk, influencing durability and performance.
///
/// Setting this property updates the synchronous mode using the
/// corresponding SQLite PRAGMA.
///
/// - SeeAlso: [PRAGMA synchronous](https://www.sqlite.org/pragma.html#pragma_synchronous)
var synchronous: Synchronous { get set }
/// The user version number stored in the database.
///
/// 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.
///
/// Setting this property updates the corresponding SQLite PRAGMA.
///
/// - SeeAlso: [PRAGMA user_version](https://www.sqlite.org/pragma.html#pragma_user_version)
var userVersion: Int32 { get set }
// 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.
///
/// 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.
///
/// - Throws: ``Connection/Error`` if the initialization fails.
/// - SeeAlso: [sqlite3_initialize()](https://www.sqlite.org/c3ref/initialize.html)
static func initialize() throws(Connection.Error)
/// 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.
///
/// - Throws: ``Connection/Error`` if the shutdown process fails.
/// - SeeAlso: [sqlite3_shutdown()](https://www.sqlite.org/c3ref/initialize.html)
static func shutdown() throws(Connection.Error)
// MARK: - Custom SQL Functions
/// Registers a custom SQL function with the connection.
///
/// This allows adding user-defined functions callable from SQL queries.
///
/// - 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)
/// Removes a previously registered custom SQL function from the connection.
///
/// - 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)
// 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.
///
/// - 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.
///
/// 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)
/// 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: - PRAGMA Execution
/// Retrieves the value of a PRAGMA setting from the database.
///
/// - 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?
/// Sets the value of a PRAGMA setting in the database.
///
/// - 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?
// MARK: - Transactions
/// Begins a database 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.
///
/// - 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.
///
/// - Throws: ``Connection/Error`` if rolling back the transaction fails.
/// - SeeAlso: [ROLLBACK](https://www.sqlite.org/lang_transaction.html)
func rollbackTransaction() throws(Connection.Error)
// MARK: - Encryption Keys
/// Applies an encryption key to the database connection.
///
/// - 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.
///
/// - 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)
}
// MARK: - PRAGMA Accessors
public extension ConnectionProtocol {
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) }
}
}
// 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: "")
}
}
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) {
for query in script {
let stmt = try prepare(sql: query, options: [])
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: [])
switch try stmt.step() {
case true: return stmt.columnValue(at: 0)
case false: return nil
}
}
@discardableResult
func set<T: SQLiteRawRepresentable>(pragma: Pragma, value: T) throws(Connection.Error) -> T? {
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
}
}
}
// MARK: - Transactions
public extension ConnectionProtocol {
func beginTransaction(_ type: TransactionType = .deferred) throws(Connection.Error) {
try prepare(sql: "BEGIN \(type) TRANSACTION", options: []).step()
}
func commitTransaction() throws(Connection.Error) {
try prepare(sql: "COMMIT TRANSACTION", options: []).step()
}
func rollbackTransaction() throws(Connection.Error) {
try prepare(sql: "ROLLBACK TRANSACTION", options: []).step()
}
}

View File

@@ -0,0 +1,33 @@
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 }
}

View File

@@ -0,0 +1,36 @@
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
}
}

View File

@@ -0,0 +1,35 @@
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)
}