Add unit tests
This commit is contained in:
@@ -7,7 +7,7 @@ extension Connection {
|
||||
/// - ``file(path:)``: A database at a specific file path or URI (persistent).
|
||||
/// - ``inMemory``: An in-memory database that exists only in RAM.
|
||||
/// - ``temporary``: A temporary on-disk database deleted when the connection closes.
|
||||
public enum Location {
|
||||
public enum Location: Sendable {
|
||||
/// A database stored at a given file path or URI.
|
||||
///
|
||||
/// Use this for persistent databases located on disk or referenced via SQLite URI.
|
||||
|
||||
@@ -152,7 +152,7 @@ extension Connection: ConnectionProtocol {
|
||||
sqlite3_key(connection, key.keyValue, key.length)
|
||||
}
|
||||
guard status == SQLITE_OK else {
|
||||
throw SQLiteError(connection)
|
||||
throw SQLiteError(code: status, message: "")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,13 +163,15 @@ extension Connection: ConnectionProtocol {
|
||||
sqlite3_rekey(connection, key.keyValue, key.length)
|
||||
}
|
||||
guard status == SQLITE_OK else {
|
||||
throw SQLiteError(connection)
|
||||
throw SQLiteError(code: status, message: "")
|
||||
}
|
||||
}
|
||||
|
||||
public func add(delegate: any ConnectionDelegate) {
|
||||
delegates.append(.init(delegate: delegate))
|
||||
delegates.removeAll { $0.delegate == nil }
|
||||
if !delegates.contains(where: { $0.delegate === delegate }) {
|
||||
delegates.append(.init(delegate: delegate))
|
||||
delegates.removeAll { $0.delegate == nil }
|
||||
}
|
||||
}
|
||||
|
||||
public func remove(delegate: any ConnectionDelegate) {
|
||||
@@ -179,8 +181,10 @@ extension Connection: ConnectionProtocol {
|
||||
}
|
||||
|
||||
public func add(trace delegate: any ConnectionTraceDelegate) {
|
||||
traceDelegates.append(.init(delegate: delegate))
|
||||
traceDelegates.removeAll { $0.delegate == nil }
|
||||
if !traceDelegates.contains(where: { $0.delegate === delegate }) {
|
||||
traceDelegates.append(.init(delegate: delegate))
|
||||
traceDelegates.removeAll { $0.delegate == nil }
|
||||
}
|
||||
}
|
||||
|
||||
public func remove(trace delegate: any ConnectionTraceDelegate) {
|
||||
|
||||
@@ -51,6 +51,19 @@ public final class Statement {
|
||||
// MARK: - StatementProtocol
|
||||
|
||||
extension Statement: StatementProtocol {
|
||||
public var sql: String? {
|
||||
let cSQL = sqlite3_sql(statement)
|
||||
guard let cSQL else { return nil }
|
||||
return String(cString: cSQL)
|
||||
}
|
||||
|
||||
public var expandedSQL: String? {
|
||||
let cSQL = sqlite3_expanded_sql(statement)
|
||||
defer { sqlite3_free(cSQL) }
|
||||
guard let cSQL else { return nil }
|
||||
return String(cString: cSQL)
|
||||
}
|
||||
|
||||
public func parameterCount() -> Int32 {
|
||||
sqlite3_bind_parameter_count(statement)
|
||||
}
|
||||
|
||||
@@ -73,12 +73,12 @@ public enum JournalMode: String, SQLiteRepresentable {
|
||||
/// - SeeAlso: [journal_mode](https://sqlite.org/pragma.html#pragma_journal_mode)
|
||||
public var rawValue: String {
|
||||
switch self {
|
||||
case .delete: "DELETE"
|
||||
case .delete: "DELETE"
|
||||
case .truncate: "TRUNCATE"
|
||||
case .persist: "PERSIST"
|
||||
case .memory: "MEMORY"
|
||||
case .wal: "WAL"
|
||||
case .off: "OFF"
|
||||
case .persist: "PERSIST"
|
||||
case .memory: "MEMORY"
|
||||
case .wal: "WAL"
|
||||
case .off: "OFF"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,13 +95,13 @@ public enum JournalMode: String, SQLiteRepresentable {
|
||||
/// - SeeAlso: [journal_mode](https://sqlite.org/pragma.html#pragma_journal_mode)
|
||||
public init?(rawValue: String) {
|
||||
switch rawValue.uppercased() {
|
||||
case "DELETE": self = .delete
|
||||
case "TRUNCATE": self = .truncate
|
||||
case "PERSIST": self = .persist
|
||||
case "MEMORY": self = .memory
|
||||
case "WAL": self = .wal
|
||||
case "OFF": self = .off
|
||||
default: return nil
|
||||
case "DELETE": self = .delete
|
||||
case "TRUNCATE": self = .truncate
|
||||
case "PERSIST": self = .persist
|
||||
case "MEMORY": self = .memory
|
||||
case "WAL": self = .wal
|
||||
case "OFF": self = .off
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,24 +13,48 @@ public enum TransactionType: String, CustomStringConvertible {
|
||||
/// With `BEGIN DEFERRED`, no locks are acquired immediately. If the first statement is a read
|
||||
/// (`SELECT`), a read transaction begins. If it is a write statement, a write transaction
|
||||
/// begins instead. Deferred transactions allow greater concurrency and are the default mode.
|
||||
case deferred = "DEFERRED"
|
||||
case deferred
|
||||
|
||||
/// Starts a write transaction immediately.
|
||||
///
|
||||
/// With `BEGIN IMMEDIATE`, a reserved lock is acquired right away to ensure that no other
|
||||
/// connection can start a conflicting write. The statement may fail with `SQLITE_BUSY` if
|
||||
/// another write transaction is already active.
|
||||
case immediate = "IMMEDIATE"
|
||||
case immediate
|
||||
|
||||
/// Starts an exclusive write transaction.
|
||||
///
|
||||
/// With `BEGIN EXCLUSIVE`, a write lock is acquired immediately. In rollback journal mode, it
|
||||
/// also prevents other connections from reading the database while the transaction is active.
|
||||
/// In WAL mode, it behaves the same as `.immediate`.
|
||||
case exclusive = "EXCLUSIVE"
|
||||
case exclusive
|
||||
|
||||
/// A textual representation of the transaction type.
|
||||
public var description: String {
|
||||
rawValue
|
||||
}
|
||||
|
||||
/// Returns the SQLite keyword that represents the transaction type.
|
||||
///
|
||||
/// The value is always uppercased to match the keywords used by SQLite statements.
|
||||
public var rawValue: String {
|
||||
switch self {
|
||||
case .deferred: "DEFERRED"
|
||||
case .immediate: "IMMEDIATE"
|
||||
case .exclusive: "EXCLUSIVE"
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a transaction type from an SQLite keyword.
|
||||
///
|
||||
/// The initializer accepts any ASCII case variant of the keyword (`"deferred"`, `"Deferred"`,
|
||||
/// etc.). Returns `nil` if the string does not correspond to a supported transaction type.
|
||||
public init?(rawValue: String) {
|
||||
switch rawValue.uppercased() {
|
||||
case "DEFERRED": self = .deferred
|
||||
case "IMMEDIATE": self = .immediate
|
||||
case "EXCLUSIVE": self = .exclusive
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,9 @@ import Foundation
|
||||
|
||||
/// A delegate that observes connection-level database events.
|
||||
///
|
||||
/// Conforming types can monitor row-level updates and transaction lifecycle events. All methods are
|
||||
/// optional — default implementations do nothing.
|
||||
///
|
||||
/// This protocol is typically used for debugging, logging, or synchronizing application state with
|
||||
/// database changes.
|
||||
/// Conforming types can monitor row-level updates and transaction lifecycle events. This protocol
|
||||
/// is typically used for debugging, logging, or synchronizing application state with database
|
||||
/// changes.
|
||||
///
|
||||
/// - 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.
|
||||
@@ -43,9 +41,3 @@ public protocol ConnectionDelegate: AnyObject {
|
||||
/// - Parameter connection: The connection that rolled back.
|
||||
func connectionDidRollback(_ connection: ConnectionProtocol)
|
||||
}
|
||||
|
||||
public extension ConnectionDelegate {
|
||||
func connection(_ connection: ConnectionProtocol, didUpdate action: SQLiteAction) {}
|
||||
func connectionWillCommit(_ connection: ConnectionProtocol) throws {}
|
||||
func connectionDidRollback(_ connection: ConnectionProtocol) {}
|
||||
}
|
||||
|
||||
@@ -12,10 +12,10 @@ import Foundation
|
||||
///
|
||||
/// - ``isAutocommit``
|
||||
/// - ``isReadonly``
|
||||
/// - ``busyTimeout``
|
||||
///
|
||||
/// ### Accessing PRAGMA Values
|
||||
///
|
||||
/// - ``busyTimeout``
|
||||
/// - ``applicationID``
|
||||
/// - ``foreignKeys``
|
||||
/// - ``journalMode``
|
||||
@@ -84,6 +84,8 @@ public protocol ConnectionProtocol: AnyObject {
|
||||
/// - 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
|
||||
@@ -93,8 +95,6 @@ public protocol ConnectionProtocol: AnyObject {
|
||||
/// - SeeAlso: [Set A Busy Timeout](https://sqlite.org/c3ref/busy_timeout.html)
|
||||
var busyTimeout: Int32 { get set }
|
||||
|
||||
// MARK: - PRAGMA Accessors
|
||||
|
||||
/// The application identifier stored in the database header.
|
||||
///
|
||||
/// Used to distinguish database files created by different applications or file formats. This
|
||||
|
||||
@@ -8,6 +8,11 @@ import Foundation
|
||||
///
|
||||
/// ## Topics
|
||||
///
|
||||
/// ### Retrieving Statement SQL
|
||||
///
|
||||
/// - ``sql``
|
||||
/// - ``expandedSQL``
|
||||
///
|
||||
/// ### Binding Parameters
|
||||
///
|
||||
/// - ``parameterCount()``
|
||||
@@ -34,6 +39,20 @@ import Foundation
|
||||
/// - ``columnValue(at:)->T?``
|
||||
/// - ``currentRow()``
|
||||
public protocol StatementProtocol {
|
||||
// MARK: - Retrieving Statement SQL
|
||||
|
||||
/// The original SQL text used to create this prepared statement.
|
||||
///
|
||||
/// Returns the statement text as it was supplied when the statement was prepared. Useful for
|
||||
/// diagnostics or query introspection.
|
||||
var sql: String? { get }
|
||||
|
||||
/// The SQL text with all parameter values expanded into their literal forms.
|
||||
///
|
||||
/// Shows the actual SQL string that would be executed after parameter binding. Useful for
|
||||
/// debugging and logging queries.
|
||||
var expandedSQL: String? { get }
|
||||
|
||||
// MARK: - Binding Parameters
|
||||
|
||||
/// Returns the number of parameters in the prepared SQLite statement.
|
||||
@@ -101,7 +120,7 @@ public protocol StatementProtocol {
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - value: The ``SQLiteValue`` to bind.
|
||||
/// - name: The parameter name as written in SQL, including its prefix.
|
||||
/// - name: The parameter name exactly as written in SQL, including its prefix.
|
||||
/// - Throws: ``SQLiteError`` if binding fails.
|
||||
///
|
||||
/// - SeeAlso: [Binding Values To Prepared Statements](
|
||||
@@ -130,7 +149,7 @@ public protocol StatementProtocol {
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - value: The value to bind. If `nil`, `NULL` is bound.
|
||||
/// - name: The parameter name as written in SQL, including its prefix.
|
||||
/// - name: The parameter name exactly as written in SQL, including its prefix.
|
||||
/// - Throws: ``SQLiteError`` if binding fails.
|
||||
///
|
||||
/// - SeeAlso: [Binding Values To Prepared Statements](
|
||||
|
||||
@@ -46,7 +46,7 @@ public struct SQLiteError: Error, Equatable, CustomStringConvertible, Sendable {
|
||||
/// name (`SQLiteError`), the numeric code, and the corresponding message, making it useful for
|
||||
/// debugging, logging, or diagnostic displays.
|
||||
public var description: String {
|
||||
"\(Self.self) code: \(code) message: \(message)"
|
||||
"\(Self.self)(\(code)): \(message)"
|
||||
}
|
||||
|
||||
/// Creates a new error instance with the specified result code and message.
|
||||
|
||||
Reference in New Issue
Block a user