Refactor entire codebase and rewrite documentation

This commit is contained in:
2025-10-10 18:06:34 +03:00
parent b4e9755c15
commit 8e471f2b9f
74 changed files with 3405 additions and 4149 deletions

View File

@@ -2,10 +2,10 @@ import Foundation
/// A type representing SQLite pragmas.
///
/// The `Pragma` structure provides a convenient way to work with
/// SQLite pragmas, which are special commands used to control various aspects
/// of the SQLite database engine. For more information on SQLite pragmas,
/// visit the [SQLite Pragma Documentation](https://www.sqlite.org/pragma.html).
/// The `Pragma` structure provides a convenient way to work with SQLite pragmas, which are special
/// commands used to control various aspects of the SQLite database engine.
///
/// - SeeAlso: [SQLite Pragma Documentation](https://sqlite.org/pragma.html).
///
/// ## Topics
///
@@ -26,8 +26,8 @@ public struct Pragma: RawRepresentable, CustomStringConvertible, ExpressibleBySt
/// The raw string value of the pragma.
///
/// This is the underlying string that represents the pragma, which can be used
/// directly in SQL queries.
/// This is the underlying string that represents the pragma, which can be used directly in SQL
/// queries.
public var rawValue: String
/// A textual representation of the pragma.
@@ -41,64 +41,75 @@ public struct Pragma: RawRepresentable, CustomStringConvertible, ExpressibleBySt
/// Represents the `application_id` pragma.
///
/// This pragma allows you to query or set the application ID associated with the
/// SQLite database file. The application ID is a 32-bit integer that can be used for
/// versioning or identification purposes. For more details, see
/// [application_id](https://www.sqlite.org/pragma.html#pragma_application_id).
/// This pragma allows you to query or set the application ID associated with the SQLite
/// database file. The application ID is a 32-bit integer that can be used for versioning or
/// identification purposes.
///
/// - SeeAlso: [application_id](https://sqlite.org/pragma.html#pragma_application_id).
public static let applicationID: Pragma = "application_id"
/// Represents the `foreign_keys` pragma.
///
/// This pragma controls the enforcement of foreign key constraints in SQLite.
/// Foreign key constraints are disabled by default, but you can enable them
/// by using this pragma. For more details, see
/// [foreign_keys](https://www.sqlite.org/pragma.html#pragma_foreign_keys).
/// This pragma controls the enforcement of foreign key constraints in SQLite. Foreign key
/// constraints are disabled by default, but you can enable them by using this pragma.
///
/// - SeeAlso: [foreign_keys](https://sqlite.org/pragma.html#pragma_foreign_keys).
public static let foreignKeys: Pragma = "foreign_keys"
/// Represents the `journal_mode` pragma.
///
/// This pragma is used to query or configure the journal mode for the database connection.
/// The journal mode determines how transactions are logged, influencing both the
/// performance and recovery behavior of the database. For more details, see
/// [journal_mode](https://www.sqlite.org/pragma.html#pragma_journal_mode).
/// This pragma is used to query or configure the journal mode for the database connection. The
/// journal mode determines how transactions are logged, influencing both the performance and
/// recovery behavior of the database.
///
/// - SeeAlso: [journal_mode](https://sqlite.org/pragma.html#pragma_journal_mode).
public static let journalMode: Pragma = "journal_mode"
/// Represents the `synchronous` pragma.
///
/// This pragma is used to query or configure the synchronous mode for the database connection.
/// The synchronous mode controls how the database synchronizes with the disk during write operations,
/// affecting both performance and durability. For more details, see
/// [synchronous](https://www.sqlite.org/pragma.html#pragma_synchronous).
/// The synchronous mode controls how the database synchronizes with the disk during write
/// operations, affecting both performance and durability.
///
/// - SeeAlso: [synchronous](https://sqlite.org/pragma.html#pragma_synchronous).
public static let synchronous: Pragma = "synchronous"
/// Represents the `user_version` pragma.
///
/// This pragma is commonly used to query or set the user version number associated
/// with the database file. It is useful for schema versioning or implementing custom
/// database management strategies. For more details, see
/// [user_version](https://www.sqlite.org/pragma.html#pragma_user_version).
/// This pragma is commonly used to query or set the user version number associated with the
/// database file. It is useful for schema versioning or implementing custom database management
/// strategies.
///
/// - SeeAlso: [user_version](https://sqlite.org/pragma.html#pragma_user_version).
public static let userVersion: Pragma = "user_version"
/// Represents the `busy_timeout` pragma.
///
/// This pragma defines the number of milliseconds that SQLite will wait for a locked database
/// before returning a `SQLITE_BUSY` error. It helps prevent failures when multiple connections
/// attempt to write simultaneously.
///
/// - SeeAlso: [busy_timeout](https://sqlite.org/pragma.html#pragma_busy_timeout).
public static let busyTimeout: Pragma = "busy_timeout"
// MARK: - Inits
/// Initializes a `Pragma` instance with the provided raw value.
///
/// - Parameter rawValue: The raw string value of the pragma.
/// This initializer allows you to create a `Pragma` instance with any raw string that
/// represents a valid SQLite pragma.
///
/// This initializer allows you to create a `Pragma` instance with any raw string
/// that represents a valid SQLite pragma.
/// - Parameter rawValue: The raw string value of the pragma.
public init(rawValue: String) {
self.rawValue = rawValue
}
/// Initializes a `Pragma` instance with the provided string literal.
///
/// - Parameter value: The string literal representing the pragma.
/// This initializer allows you to create a `Pragma` instance using a string literal, providing
/// a convenient syntax for common pragmas.
///
/// This initializer allows you to create a `Pragma` instance using a string literal,
/// providing a convenient syntax for common pragmas.
/// - Parameter value: The string literal representing the pragma.
public init(stringLiteral value: String) {
self.rawValue = value
}

View File

@@ -13,8 +13,8 @@ import Foundation
///
/// ### Loading from a File
///
/// To load a SQL script from a file in your project, use the following code. In this example,
/// we load a file named `sample_script.sql` from the main app bundle.
/// To load a SQL script from a file in your project, use the following code. In this example, we
/// load a file named `sample_script.sql` from the main app bundle.
///
/// ```swift
/// do {
@@ -104,16 +104,16 @@ import Foundation
/// - Important: Nested comments are not supported, so avoid placing multi-line comments inside
/// other multi-line comments.
///
/// - Important: `SQLScript` does not support SQL scripts containing transactions.
/// To execute an `SQLScript`, use the method ``Connection/execute(sql:)``, which executes
/// each statement individually in autocommit mode.
/// - Important: `SQLScript` does not support SQL scripts containing transactions. To execute an
/// `SQLScript`, use the method ``ConnectionProtocol/execute(sql:)``, which executes each statement
/// individually in autocommit mode.
///
/// If you need to execute the entire `SQLScript` within a single transaction, use the methods
/// ``Connection/beginTransaction(_:)``, ``Connection/commitTransaction()``, and
/// ``Connection/rollbackTransaction()`` to manage the transaction explicitly.
/// ``ConnectionProtocol/beginTransaction(_:)``, ``ConnectionProtocol/commitTransaction()``, and
/// ``ConnectionProtocol/rollbackTransaction()`` to manage the transaction explicitly.
///
/// If your SQL script includes transaction statements (e.g., BEGIN, COMMIT, ROLLBACK),
/// execute the entire script using ``Connection/execute(raw:)``.
/// If your SQL script includes transaction statements (e.g., BEGIN, COMMIT, ROLLBACK), execute
/// the entire script using ``ConnectionProtocol/execute(raw:)``.
///
/// - Important: This class is not designed to work with untrusted user data. Never insert
/// user-provided data directly into SQL queries without proper sanitization or parameterization.
@@ -199,10 +199,12 @@ public struct SQLScript: Collection, ExpressibleByStringLiteral {
extension: String? = nil,
in bundle: Bundle = .main
) throws {
guard let url = bundle.url(
forResource: name,
withExtension: `extension`
) else { return nil }
guard
let url = bundle.url(
forResource: name,
withExtension: `extension`
)
else { return nil }
try self.init(contentsOf: url)
}
@@ -239,7 +241,8 @@ public struct SQLScript: Collection, ExpressibleByStringLiteral {
///
/// - Parameter string: The string containing SQL queries.
public init(string: String) {
elements = string
elements =
string
.removingComments()
.trimmingLines()
.splitStatements()

View File

@@ -0,0 +1,69 @@
import Foundation
import DataLiteC
/// A structure that represents an error produced by SQLite operations.
///
/// `SQLiteError` encapsulates both the numeric SQLite error code and its associated human-readable
/// message. It provides a unified way to report and inspect failures that occur during database
/// interactions.
///
/// - SeeAlso: [Result and Error Codes](https://sqlite.org/rescode.html)
///
/// ## Topics
///
/// ### Instance Properties
///
/// - ``code``
/// - ``message``
/// - ``description``
///
/// ### Initializers
///
/// - ``init(code:message:)``
public struct SQLiteError: Error, Equatable, CustomStringConvertible, Sendable {
/// The extended SQLite result code associated with the error.
///
/// This numeric value identifies the specific type of error that occurred. Extended result
/// codes offer more precise information than primary codes, enabling finer-grained error
/// handling and diagnostics.
///
/// - SeeAlso: [Result and Error Codes](https://sqlite.org/rescode.html)
public let code: Int32
/// The human-readable message returned by SQLite.
///
/// This string describes the error condition reported by SQLite. It is typically retrieved
/// directly from the database engine and may include details about constraint violations,
/// syntax errors, I/O issues, or resource limitations.
///
/// - Note: The content of this message is determined by SQLite and may vary between error
/// occurrences. Always refer to this property for detailed diagnostic information.
public let message: String
/// A textual representation of the error including its code and message.
///
/// The value of this property is a concise string describing the error. It includes the type
/// 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)"
}
/// Creates a new error instance with the specified result code and message.
///
/// Use this initializer to represent an SQLite error explicitly by providing both the numeric
/// result code and the associated descriptive message.
///
/// - Parameters:
/// - code: The extended SQLite result code associated with the error.
/// - message: A human-readable description of the error, as reported by SQLite.
public init(code: Int32, message: String) {
self.code = code
self.message = message
}
init(_ connection: OpaquePointer) {
self.code = sqlite3_extended_errcode(connection)
self.message = String(cString: sqlite3_errmsg(connection))
}
}

View File

@@ -1,192 +1,65 @@
import Foundation
import OrderedCollections
/// A structure representing a single row in an SQLite database, providing ordered access to columns and their values.
/// An ordered collection that stores the column-value pairs of a single SQLite result row.
///
/// The `SQLiteRow` structure allows for convenient access to the data stored in a row of an SQLite
/// database, using an ordered dictionary to maintain the insertion order of columns. This makes it
/// easy to retrieve, update, and manage the values associated with each column in the row.
/// `SQLiteRow` preserves the order of columns as provided by the underlying data source. Each key
/// is the column name exposed by the executed statement, and every value is represented as a
/// ``SQLiteValue``.
///
/// ```swift
/// let row = SQLiteRow()
/// row["name"] = "John Doe"
/// row["age"] = 30
/// print(row.description)
/// // Outputs: ["name": 'John Doe', "age": 30]
/// ```
///
/// ## Topics
///
/// ### Type Aliases
///
/// - ``Elements``
/// - ``Column``
/// - ``Value``
/// - ``Index``
/// - ``Element``
public struct SQLiteRow: Collection, CustomStringConvertible, Equatable {
// MARK: - Type Aliases
/// You can use dictionary-style lookup to access values by column name or iterate over ordered
/// pairs using standard collection APIs. Column names are unique, and insertion order is maintained
/// deterministically, making `SQLiteRow` safe to pass into APIs that rely on stable row layouts.
public struct SQLiteRow {
/// The type that identifies a column within a row.
///
/// In SQLite, a column name corresponds to the alias or identifier returned by the executing
/// statement. Each column name is unique within a single row.
public typealias Column = String
/// A type for the internal storage of column names and their associated values in a database row.
///
/// This ordered dictionary is used to store column data for a row, retaining the insertion order
/// of columns as they appear in the SQLite database. Each key-value pair corresponds to a column name
/// and its associated value, represented by `SQLiteRawValue`.
///
/// - Key: `String` representing the name of the column.
/// - Value: `SQLiteRawValue` representing the value of the column in the row.
public typealias Elements = OrderedDictionary<String, SQLiteRawValue>
/// A type representing the name of a column in a database row.
///
/// This type alias provides a convenient way to refer to column names within a row.
/// Each `Column` is a `String` key that corresponds to a specific column in the SQLite row,
/// matching the key type of the `Elements` dictionary.
public typealias Column = Elements.Key
/// A type representing the value of a column in a database row.
///
/// This type alias provides a convenient way to refer to the data stored in a column.
/// Each `Value` is of type `SQLiteRawValue`, which corresponds to the value associated
/// with a specific column in the SQLite row, matching the value type of the `Elements` dictionary.
public typealias Value = Elements.Value
/// A type representing the index of a column in a database row.
///
/// This type alias provides a convenient way to refer to the position of a column
/// within the ordered collection of columns. Each `Index` is an integer that corresponds
/// to the index of a specific column in the SQLite row, matching the index type of the `Elements` dictionary.
public typealias Index = Elements.Index
/// A type representing a column-value pair in a database row.
///
/// This type alias defines an element as a tuple consisting of a `Column` and its associated
/// `Value`. Each `Element` encapsulates a single column name and its corresponding value,
/// providing a clear structure for accessing and managing data within the SQLite row.
public typealias Element = (column: Column, value: Value)
/// The type that represents a value stored in a column.
public typealias Value = SQLiteValue
// MARK: - Properties
/// An ordered dictionary that stores the columns and their associated values in the row.
///
/// This private property holds the internal representation of the row's data as an
/// `OrderedDictionary`, maintaining the insertion order of columns. It is used to
/// facilitate access to the row's columns and values, ensuring that the original
/// order from the SQLite database is preserved.
private var elements: Elements
private var elements: OrderedDictionary<Column, Value>
/// The starting index of the row, which is always zero.
/// The column names in the order they appear in the result set.
///
/// This property indicates the initial position of the row's elements. Since the
/// elements in the row are indexed starting from zero, this property consistently
/// returns zero, allowing for predictable iteration through the row's data.
///
/// - Complexity: `O(1)`
public var startIndex: Index {
0
}
/// The ending index of the row, which is equal to the number of columns.
///
/// This property indicates the position one past the last element in the row.
/// It returns the count of columns in the row, allowing for proper iteration
/// through the row's data in a collection context. The `endIndex` is useful
/// for determining the bounds of the row's elements when traversing or accessing them.
///
/// - Complexity: `O(1)`
public var endIndex: Index {
elements.count
}
/// A Boolean value indicating whether the row is empty.
///
/// This property returns `true` if the row contains no columns; otherwise, it returns `false`.
/// It provides a quick way to check if there are any data present in the row, which can be
/// useful for validation or conditional logic when working with database rows.
///
/// - Complexity: `O(1)`
public var isEmpty: Bool {
elements.isEmpty
}
/// The number of columns in the row.
///
/// This property returns the total count of columns stored in the row. It reflects
/// the number of column-value pairs in the `elements` dictionary, providing a convenient
/// way to determine how much data is present in the row. This is useful for iteration
/// and conditional checks when working with database rows.
///
/// - Complexity: `O(1)`
public var count: Int {
elements.count
}
/// A textual description of the row, showing the columns and values.
///
/// This property returns a string representation of the row, including all column names
/// and their associated values. The description is generated from the `elements` dictionary,
/// providing a clear and concise overview of the row's data, which can be helpful for debugging
/// and logging purposes.
public var description: String {
elements.description
}
/// A list of column names in the row, preserving their insertion order.
///
/// Useful for dynamically generating SQL queries (e.g. `INSERT INTO ... (columns)`).
///
/// - Complexity: `O(1)`
public var columns: [String] {
/// The order of column names corresponds to the sequence defined in the executed SQL statement.
/// This order is preserved exactly as provided by SQLite, ensuring deterministic column
/// indexing across rows.
public var columns: [Column] {
elements.keys.elements
}
/// A list of SQL named parameters in the form `:column`, preserving column order.
/// The named parameter tokens corresponding to each column, in result order.
///
/// Useful for generating placeholders in SQL queries (e.g. `VALUES (:column1, :column2, ...)`)
/// to match the row's structure.
///
/// - Complexity: `O(n)`
/// Each element is formed by prefixing the column name with a colon (`:`), matching the syntax
/// of SQLite named parameters (e.g., `:username`, `:id`). The order of tokens matches the order
/// of columns in the result set.
public var namedParameters: [String] {
elements.keys.map { ":\($0)" }
}
// MARK: - Inits
/// Initializes an empty row.
///
/// This initializer creates a new instance of `SQLiteRow` with no columns or values.
/// Creates an empty row with no columns.
public init() {
elements = [:]
}
// MARK: - Subscripts
/// Accesses the element at the specified index.
/// Accesses the value associated with the specified column.
///
/// This subscript allows you to retrieve a column-value pair from the row by its index.
/// It returns an `Element`, which is a tuple containing the column name and its associated
/// value. The index must be valid; otherwise, it will trigger a runtime error.
/// Use this subscript to read or modify the value of a particular column by name. If the column
/// does not exist, the getter returns `nil` and assigning a value to a new column name adds it
/// to the row.
///
/// - Parameter index: The index of the element to access.
/// - Returns: A tuple containing the column name and its associated value.
///
/// - Complexity: `O(1)`
public subscript(index: Index) -> Element {
let element = elements.elements[index]
return (element.key, element.value)
}
/// Accesses the value for the specified column.
///
/// This subscript allows you to retrieve or set the value associated with a given column name.
/// It returns an optional `Value`, which is the value stored in the row for the specified column.
/// If the column does not exist, it returns `nil`. When setting a value, the column will be created
/// if it does not already exist.
///
/// - Parameter column: The name of the column to access.
/// - Returns: The value associated with the specified column, or `nil` if the column does not exist.
///
/// - Complexity: On average, the complexity is O(1) for lookups and amortized O(1) for updates.
/// - Parameter column: The name of the column.
/// - Returns: The value for the specified column, or `nil` if the column is not present.
/// - Complexity: Average O(1) lookup and amortized O(1) mutation.
public subscript(column: Column) -> Value? {
get { elements[column] }
set { elements[column] = newValue }
@@ -194,32 +67,132 @@ public struct SQLiteRow: Collection, CustomStringConvertible, Equatable {
// MARK: - Methods
/// Returns the index immediately after the given index.
/// Checks whether the row contains a column with the specified name.
///
/// This method provides the next valid index in the row's collection after the specified index.
/// It increments the given index by one, allowing for iteration through the row's elements
/// in a collection context. If the provided index is the last valid index, this method
/// will return an index that may not be valid for the collection, so it should be used
/// in conjunction with bounds checking.
/// Use this method to check if a column exists without retrieving its value or iterating
/// through all columns.
///
/// - Parameter i: A valid index of the row.
/// - Returns: The index immediately after `i`.
///
/// - Complexity: `O(1)`
public func index(after i: Index) -> Index {
i + 1
}
/// Checks if the row contains a value for the specified column.
///
/// This method determines whether a column with the given name exists in the row. It is
/// useful for validating the presence of data before attempting to access it.
///
/// - Parameter column: The name of the column to check for.
/// - Returns: `true` if the column exists; otherwise, `false`.
///
/// - Complexity: On average, the complexity is `O(1)`.
/// - Parameter column: The name of the column to look for.
/// - Returns: `true` if the column exists, otherwise `false`.
/// - Complexity: Average O(1).
public func contains(_ column: Column) -> Bool {
elements.keys.contains(column)
}
/// Reserves enough storage to hold the specified number of columns.
///
/// Calling this method can minimize reallocations when adding multiple columns to the row.
///
/// - Parameter minimumCapacity: The requested number of column-value pairs to store.
/// - Complexity: O(max(count, minimumCapacity))
public mutating func reserveCapacity(_ minimumCapacity: Int) {
elements.reserveCapacity(minimumCapacity)
}
/// Reserves enough storage to hold the specified number of columns.
///
/// This overload provides a convenient interface for values originating from SQLite APIs, which
/// commonly use 32-bit integer sizes.
///
/// - Parameter minimumCapacity: The requested number of column-value pairs to store.
/// - Complexity: O(max(count, minimumCapacity))
public mutating func reserveCapacity(_ minimumCapacity: Int32) {
elements.reserveCapacity(Int(minimumCapacity))
}
}
// MARK: - CustomStringConvertible
extension SQLiteRow: CustomStringConvertible {
/// A textual representation of the row as an ordered dictionary of column-value pairs.
public var description: String {
elements.description
}
}
// MARK: - Collection
extension SQLiteRow: Collection {
/// The element type of the row collection.
public typealias Element = (column: Column, value: Value)
/// The index type used to access elements in the row.
public typealias Index = OrderedDictionary<Column, Value>.Index
/// The position of the first element in the row.
///
/// If the row is empty, `startIndex` equals `endIndex`. Use this property as the starting
/// position when iterating over columns.
///
/// - Complexity: O(1)
public var startIndex: Index {
elements.elements.startIndex
}
/// The position one past the last valid element in the row.
///
/// Use this property to detect the end of iteration when traversing columns.
///
/// - Complexity: O(1)
public var endIndex: Index {
elements.elements.endIndex
}
/// A Boolean value that indicates whether the row contains no columns.
///
/// - Complexity: O(1)
public var isEmpty: Bool {
elements.isEmpty
}
/// The number of column-value pairs in the row.
///
/// - Complexity: O(1)
public var count: Int {
elements.count
}
/// Accesses the element at the specified position in the row.
///
/// - Parameter index: A valid index of the row.
/// - Returns: The (column, value) pair at the specified position.
/// - Complexity: O(1)
public subscript(index: Index) -> Element {
let element = elements.elements[index]
return (element.key, element.value)
}
/// Returns the position immediately after the specified index.
///
/// - Parameter i: A valid index of the row.
/// - Returns: The index immediately after `i`.
/// - Complexity: O(1)
public func index(after i: Index) -> Index {
elements.elements.index(after: i)
}
}
// MARK: - ExpressibleByDictionaryLiteral
extension SQLiteRow: ExpressibleByDictionaryLiteral {
/// Creates a `SQLiteRow` from a sequence of (column, value) pairs.
///
/// - Parameter elements: The column-value pairs to include in the row.
/// - Note: Preserves the argument order and requires unique column names.
/// - Complexity: O(n), where n is the number of pairs.
public init(dictionaryLiteral elements: (Column, Value)...) {
self.elements = .init(uniqueKeysWithValues: elements)
}
}
// MARK: - Equatable
extension SQLiteRow: Equatable {}
// MARK: - Hashable
extension SQLiteRow: Hashable {}
// MARK: - Sendable
extension SQLiteRow: Sendable {}