2574 lines
86 KiB
Swift
2574 lines
86 KiB
Swift
import Foundation
|
|
|
|
/// A database row.
|
|
///
|
|
/// To get `Row` instances, you will generally fetch them from a ``Database``
|
|
/// instance. For example:
|
|
///
|
|
/// ```swift
|
|
/// try dbQueue.read { db in
|
|
/// let rows = try Row.fetchCursor(db, sql: """
|
|
/// SELECT * FROM player
|
|
/// """)
|
|
/// while let row = try rows.next() {
|
|
/// let id: Int64 = row["id"]
|
|
/// let name: String = row["name"]
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// ## Topics
|
|
///
|
|
/// ### Creating Rows
|
|
///
|
|
/// - ``init()``
|
|
/// - ``init(_:)-5uezw``
|
|
/// - ``init(_:)-65by6``
|
|
///
|
|
/// ### Copying a Row
|
|
///
|
|
/// - ``copy()``
|
|
///
|
|
/// ### Row Informations
|
|
///
|
|
/// - ``columnNames``
|
|
/// - ``containsNonNullValue``
|
|
/// - ``count-5flaw``
|
|
/// - ``databaseValues``
|
|
/// - ``hasColumn(_:)``
|
|
/// - ``hasNull(atIndex:)``
|
|
///
|
|
/// ### Accessing Row Values by Int Index
|
|
///
|
|
/// - ``subscript(_:)-9c1fw``
|
|
/// - ``subscript(_:)-3jhwm``
|
|
/// - ``subscript(_:)-7krrg``
|
|
/// - ``withUnsafeData(atIndex:_:)``
|
|
/// - ``dataNoCopy(atIndex:)``
|
|
///
|
|
/// ### Accessing Row Values by Column Name
|
|
///
|
|
/// - ``subscript(_:)-3tp8o``
|
|
/// - ``subscript(_:)-4k8od``
|
|
/// - ``subscript(_:)-9rbo7``
|
|
/// - ``withUnsafeData(named:_:)``
|
|
/// - ``dataNoCopy(named:)``
|
|
///
|
|
/// ### Accessing Row Values by Column
|
|
///
|
|
/// - ``subscript(_:)-9txgm``
|
|
/// - ``subscript(_:)-2esg7``
|
|
/// - ``subscript(_:)-wl9a``
|
|
/// - ``withUnsafeData(at:_:)``
|
|
/// - ``dataNoCopy(_:)``
|
|
///
|
|
/// ### Row Scopes & Associated Rows
|
|
///
|
|
/// - ``prefetchedRows``
|
|
/// - ``scopes``
|
|
/// - ``scopesTree``
|
|
/// - ``unadapted``
|
|
/// - ``unscoped``
|
|
/// - ``subscript(_:)-4dx01``
|
|
/// - ``subscript(_:)-8god3``
|
|
/// - ``subscript(_:)-jwnx``
|
|
/// - ``subscript(_:)-6ge6t``
|
|
/// - ``PrefetchedRowsView``
|
|
/// - ``ScopesTreeView``
|
|
/// - ``ScopesView``
|
|
///
|
|
/// ### Fetching Rows from Raw SQL
|
|
///
|
|
/// - ``fetchCursor(_:sql:arguments:adapter:)``
|
|
/// - ``fetchAll(_:sql:arguments:adapter:)``
|
|
/// - ``fetchSet(_:sql:arguments:adapter:)``
|
|
/// - ``fetchOne(_:sql:arguments:adapter:)``
|
|
///
|
|
/// ### Fetching Rows from a Prepared Statement
|
|
///
|
|
/// - ``fetchCursor(_:arguments:adapter:)``
|
|
/// - ``fetchAll(_:arguments:adapter:)``
|
|
/// - ``fetchSet(_:arguments:adapter:)``
|
|
/// - ``fetchOne(_:arguments:adapter:)``
|
|
///
|
|
/// ### Fetching Rows from a Request
|
|
///
|
|
/// - ``fetchCursor(_:_:)``
|
|
/// - ``fetchAll(_:_:)``
|
|
/// - ``fetchSet(_:_:)``
|
|
/// - ``fetchOne(_:_:)``
|
|
///
|
|
/// ### Row as RandomAccessCollection
|
|
///
|
|
/// - ``count-5flaw``
|
|
/// - ``subscript(_:)-68yae``
|
|
/// - ``Index``
|
|
///
|
|
/// ### Adapting Rows
|
|
///
|
|
/// - ``RowAdapter``
|
|
///
|
|
/// ### Supporting Types
|
|
///
|
|
/// - ``RowCursor``
|
|
public final class Row {
|
|
// It is not a violation of the Demeter law when another type uses this
|
|
// property, which is exposed for optimizations.
|
|
let impl: any RowImpl
|
|
|
|
/// Unless we are producing a row array, we use a single row when iterating
|
|
/// a statement:
|
|
///
|
|
/// let rows = try Row.fetchCursor(db, sql: "SELECT ...")
|
|
/// let players = try Player.fetchAll(db, sql: "SELECT ...")
|
|
let statement: Statement?
|
|
@usableFromInline
|
|
let sqliteStatement: SQLiteStatement?
|
|
|
|
/// The number of columns in the row.
|
|
public let count: Int
|
|
|
|
/// A view on the prefetched associated rows.
|
|
///
|
|
/// Prefetched rows are defined by the ``JoinableRequest/including(all:)``
|
|
/// request method.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// struct Author: TableRecord {
|
|
/// static let books = hasMany(Book.self)
|
|
/// }
|
|
///
|
|
/// struct Book: TableRecord { }
|
|
///
|
|
/// let request = Author.including(all: Author.books)
|
|
/// let authorRow = try Row.fetchOne(db, request)!
|
|
///
|
|
/// print(authorRow)
|
|
/// // Prints [id:1, name:"Herman Melville"]
|
|
///
|
|
/// let bookRows = authorRow.prefetchedRows["books"]!
|
|
/// print(bookRows[0])
|
|
/// // Prints [id:42, title:"Moby-Dick", authorId:1]
|
|
/// print(bookRows[1])
|
|
/// // Prints [id:57, title:"Pierre", authorId:1]
|
|
/// ```
|
|
public internal(set) var prefetchedRows = PrefetchedRowsView()
|
|
|
|
// MARK: - Building rows
|
|
|
|
/// Creates an empty row.
|
|
public convenience init() {
|
|
self.init(impl: EmptyRowImpl())
|
|
}
|
|
|
|
/// Creates a row from a dictionary of database values.
|
|
public convenience init(_ dictionary: [String: (any DatabaseValueConvertible)?]) {
|
|
self.init(impl: ArrayRowImpl(columns: dictionary.map { ($0, $1?.databaseValue ?? .null) }))
|
|
}
|
|
|
|
/// Creates a row from a dictionary.
|
|
///
|
|
/// The result is nil unless all dictionary keys are strings, and values
|
|
/// conform to ``DatabaseValueConvertible``.
|
|
public convenience init?(_ dictionary: [AnyHashable: Any]) {
|
|
var initDictionary = [String: (any DatabaseValueConvertible)?]()
|
|
for (key, value) in dictionary {
|
|
guard let columnName = key as? String else {
|
|
return nil
|
|
}
|
|
guard let dbValue = DatabaseValue(value: value) else {
|
|
return nil
|
|
}
|
|
initDictionary[columnName] = dbValue
|
|
}
|
|
self.init(initDictionary)
|
|
}
|
|
|
|
/// Returns an immutable copy of the row.
|
|
///
|
|
/// For performance reasons, rows fetched from a cursor are reused during
|
|
/// the iteration of a query: make sure to make a copy of it whenever you
|
|
/// want to keep a specific one: `row.copy()`.
|
|
public func copy() -> Row {
|
|
impl.copiedRow(self)
|
|
}
|
|
|
|
// MARK: - Not Public
|
|
|
|
/// Returns true if and only if the row was fetched from a database.
|
|
var isFetched: Bool { impl.isFetched }
|
|
|
|
/// Creates a row that maps an SQLite statement. Further calls to
|
|
/// sqlite3_step() modify the row.
|
|
///
|
|
/// The row is implemented on top of StatementRowImpl, which grants *direct*
|
|
/// access to the SQLite statement. Iteration of the statement does modify
|
|
/// the row.
|
|
init(statement: Statement) {
|
|
self.statement = statement
|
|
self.sqliteStatement = statement.sqliteStatement
|
|
self.impl = StatementRowImpl(sqliteStatement: statement.sqliteStatement, statement: statement)
|
|
self.count = Int(sqlite3_column_count(sqliteStatement))
|
|
}
|
|
|
|
/// Creates a row that maps an SQLite statement. Further calls to
|
|
/// sqlite3_step() modify the row.
|
|
init(sqliteStatement: SQLiteStatement) {
|
|
self.statement = nil
|
|
self.sqliteStatement = sqliteStatement
|
|
self.impl = SQLiteStatementRowImpl(sqliteStatement: sqliteStatement)
|
|
self.count = Int(sqlite3_column_count(sqliteStatement))
|
|
}
|
|
|
|
/// Creates a row that contain a copy of the current state of the
|
|
/// SQLite statement. Further calls to sqlite3_step() do not modify the row.
|
|
///
|
|
/// The row is implemented on top of StatementCopyRowImpl, which *copies*
|
|
/// the values from the SQLite statement so that further iteration of the
|
|
/// statement does not modify the row.
|
|
convenience init(
|
|
copiedFromSQLiteStatement sqliteStatement: SQLiteStatement,
|
|
statement: Statement)
|
|
{
|
|
self.init(impl: StatementCopyRowImpl(
|
|
sqliteStatement: sqliteStatement,
|
|
columnNames: statement.columnNames))
|
|
}
|
|
|
|
init(impl: any RowImpl) {
|
|
self.statement = nil
|
|
self.sqliteStatement = nil
|
|
self.impl = impl
|
|
self.count = impl.count
|
|
}
|
|
}
|
|
|
|
// Explicit non-conformance to Sendable: a row contains transient
|
|
// information. TODO GRDB7: split non sendable statement rows from sendable
|
|
// copied rows.
|
|
@available(*, unavailable)
|
|
extension Row: Sendable { }
|
|
|
|
extension Row {
|
|
|
|
// MARK: - Columns
|
|
|
|
/// The names of columns in the row, from left to right.
|
|
///
|
|
/// Columns appear in the same order as they occur as the `.0` member
|
|
/// of column-value pairs in `self`.
|
|
public var columnNames: LazyMapCollection<Row, String> {
|
|
lazy.map { $0.0 }
|
|
}
|
|
|
|
/// Returns whether the row has one column with the given name
|
|
/// (case-insensitive).
|
|
public func hasColumn(_ columnName: String) -> Bool {
|
|
index(forColumn: columnName) != nil
|
|
}
|
|
|
|
@usableFromInline
|
|
func index(forColumn name: String) -> Int? {
|
|
impl.index(forColumn: name)
|
|
}
|
|
}
|
|
|
|
extension Row {
|
|
|
|
// MARK: - Extracting Values
|
|
|
|
/// Fatal errors if index is out of bounds
|
|
@inline(__always)
|
|
@usableFromInline
|
|
/* private */ func _checkIndex(_ index: Int, file: StaticString = #file, line: UInt = #line) {
|
|
GRDBPrecondition(index >= 0 && index < count, "row index out of range", file: file, line: line)
|
|
}
|
|
|
|
/// Returns a boolean value indicating if the row contains one value this
|
|
/// is not `NULL`.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// let row = try Row.fetchOne(db, sql: "SELECT 'foo', NULL")!
|
|
/// row.containsNonNullValue // true
|
|
///
|
|
/// let row = try Row.fetchOne(db, sql: "SELECT NULL, NULL")!
|
|
/// row.containsNonNullValue // false
|
|
/// ```
|
|
public var containsNonNullValue: Bool {
|
|
for i in (0..<count) where !impl.hasNull(atUncheckedIndex: i) {
|
|
return true
|
|
}
|
|
|
|
for (_, scopedRow) in scopes where scopedRow.containsNonNullValue {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
/// Returns whether the row has a `NULL` value at given index.
|
|
///
|
|
/// Indexes span from `0` for the leftmost column to `row.count - 1` for the
|
|
/// rightmost column.
|
|
///
|
|
/// This method is equivalent to `row[index] == nil`, but may be preferred
|
|
/// in performance-critical code because it avoids decoding
|
|
/// database values.
|
|
public func hasNull(atIndex index: Int) -> Bool {
|
|
_checkIndex(index)
|
|
return impl.hasNull(atUncheckedIndex: index)
|
|
}
|
|
|
|
/// Returns `Int64`, `Double`, `String`, `Data` or nil, depending on the
|
|
/// value stored at the given index.
|
|
///
|
|
/// Indexes span from `0` for the leftmost column to `row.count - 1` for the
|
|
/// rightmost column.
|
|
public subscript(_ index: Int) -> (any DatabaseValueConvertible)? {
|
|
_checkIndex(index)
|
|
return impl.databaseValue(atUncheckedIndex: index).storage.value
|
|
}
|
|
|
|
/// Returns the value at given index, converted to the requested type.
|
|
///
|
|
/// Indexes span from `0` for the leftmost column to `row.count - 1` for the
|
|
/// rightmost column.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// let row = try Row.fetchOne(db, sql: "SELECT 42")!
|
|
/// let score: Int = row[0] // 42
|
|
///
|
|
/// let row = try Row.fetchOne(db, sql: "SELECT 'Alice'")!
|
|
/// let name: String = row[0] // "Alice"
|
|
/// ```
|
|
///
|
|
/// When the database value may be nil, ask for an optional:
|
|
///
|
|
/// ```swift
|
|
/// let row = try Row.fetchOne(db, sql: "SELECT NULL")!
|
|
/// let name: String? = row[0] // nil
|
|
/// ```
|
|
@inlinable
|
|
public subscript<Value: DatabaseValueConvertible>(_ index: Int) -> Value {
|
|
try! decode(Value.self, atIndex: index)
|
|
}
|
|
|
|
/// Returns the value at given index, converted to the requested type.
|
|
///
|
|
/// This method exists as an optimization opportunity for types that adopt
|
|
/// ``StatementColumnConvertible``. It can trigger [SQLite built-in
|
|
/// conversions](https://www.sqlite.org/datatype3.html).
|
|
///
|
|
/// Indexes span from `0` for the leftmost column to `row.count - 1` for the
|
|
/// rightmost column.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// let row = try Row.fetchOne(db, sql: "SELECT 42")!
|
|
/// let score: Int = row[0] // 42
|
|
///
|
|
/// let row = try Row.fetchOne(db, sql: "SELECT 'Alice'")!
|
|
/// let name: String = row[0] // "Alice"
|
|
/// ```
|
|
///
|
|
/// When the database value may be nil, ask for an optional:
|
|
///
|
|
/// ```swift
|
|
/// let row = try Row.fetchOne(db, sql: "SELECT NULL")!
|
|
/// let name: String? = row[0] // nil
|
|
/// ```
|
|
@inline(__always)
|
|
@inlinable
|
|
public subscript<Value: DatabaseValueConvertible & StatementColumnConvertible>(_ index: Int) -> Value {
|
|
try! decode(Value.self, atIndex: index)
|
|
}
|
|
|
|
/// Returns `Int64`, `Double`, `String`, `Data` or nil, depending on the
|
|
/// value stored at the given column.
|
|
///
|
|
/// Column name lookup is case-insensitive. When several columns exist with
|
|
/// the same name, the leftmost column is considered.
|
|
///
|
|
/// The result is nil if the row does not contain any column with that name.
|
|
public subscript(_ columnName: String) -> (any DatabaseValueConvertible)? {
|
|
// IMPLEMENTATION NOTE
|
|
// This method has a single known use case: checking if the value is nil,
|
|
// as in:
|
|
//
|
|
// if row["foo"] != nil { ... }
|
|
//
|
|
// Without this method, the code above would not compile.
|
|
guard let index = index(forColumn: columnName) else {
|
|
return nil
|
|
}
|
|
return impl.databaseValue(atUncheckedIndex: index).storage.value
|
|
}
|
|
|
|
/// Returns the value at given column, converted to the requested type.
|
|
///
|
|
/// Column name lookup is case-insensitive. When several columns exist with
|
|
/// the same name, the leftmost column is considered.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// let row = try Row.fetchOne(db, sql: "SELECT 42 AS score")!
|
|
/// let score: Int = row["score"] // 42
|
|
///
|
|
/// let row = try Row.fetchOne(db, sql: "SELECT 'Alice' AS name")!
|
|
/// let name: String = row["name"] // "Alice"
|
|
/// ```
|
|
///
|
|
/// When the database value may be nil, ask for an optional:
|
|
///
|
|
/// ```swift
|
|
/// let row = try Row.fetchOne(db, sql: "SELECT NULL AS name")!
|
|
/// let name: String? = row["name"] // nil
|
|
/// ```
|
|
///
|
|
/// When the column does not exist, nil is returned:
|
|
///
|
|
/// ```swift
|
|
/// let row = try Row.fetchOne(db, sql: "SELECT 'Alice' AS name")!
|
|
/// let name: String? = row["missing"] // nil
|
|
/// ```
|
|
@inlinable
|
|
public subscript<Value: DatabaseValueConvertible>(_ columnName: String) -> Value {
|
|
try! decode(Value.self, forKey: columnName)
|
|
}
|
|
|
|
/// Returns the value at given column, converted to the requested type.
|
|
///
|
|
/// This method exists as an optimization opportunity for types that adopt
|
|
/// ``StatementColumnConvertible``. It can trigger [SQLite built-in
|
|
/// conversions](https://www.sqlite.org/datatype3.html).
|
|
///
|
|
/// Column name lookup is case-insensitive. When several columns exist with
|
|
/// the same name, the leftmost column is considered.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// let row = try Row.fetchOne(db, sql: "SELECT 42 AS score")!
|
|
/// let score: Int = row["score"] // 42
|
|
///
|
|
/// let row = try Row.fetchOne(db, sql: "SELECT 'Alice' AS name")!
|
|
/// let name: String = row["name"] // "Alice"
|
|
/// ```
|
|
///
|
|
/// When the database value may be nil, ask for an optional:
|
|
///
|
|
/// ```swift
|
|
/// let row = try Row.fetchOne(db, sql: "SELECT NULL AS name")!
|
|
/// let name: String? = row["name"] // nil
|
|
/// ```
|
|
///
|
|
/// When the column does not exist, nil is returned:
|
|
///
|
|
/// ```swift
|
|
/// let row = try Row.fetchOne(db, sql: "SELECT 'Alice' AS name")!
|
|
/// let name: String? = row["missing"] // nil
|
|
/// ```
|
|
@inlinable
|
|
public subscript<Value: DatabaseValueConvertible & StatementColumnConvertible>(_ columnName: String) -> Value {
|
|
try! decode(Value.self, forKey: columnName)
|
|
}
|
|
|
|
/// Returns `Int64`, `Double`, `String`, `Data` or nil, depending on the
|
|
/// value stored at the given column.
|
|
///
|
|
/// Column name lookup is case-insensitive. When several columns exist with
|
|
/// the same name, the leftmost column is considered.
|
|
///
|
|
/// The result is nil if the row does not contain any column with that name.
|
|
public subscript(_ column: some ColumnExpression) -> (any DatabaseValueConvertible)? {
|
|
self[column.name]
|
|
}
|
|
|
|
/// Returns the value at given column, converted to the requested type.
|
|
///
|
|
/// Column name lookup is case-insensitive. When several columns exist with
|
|
/// the same name, the leftmost column is considered.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// let row = try Row.fetchOne(db, sql: "SELECT 42 AS score")!
|
|
/// let score: Int = row[Column("score")] // 42
|
|
///
|
|
/// let row = try Row.fetchOne(db, sql: "SELECT 'Alice' AS name")!
|
|
/// let name: String = row[Column("name")] // "Alice"
|
|
/// ```
|
|
///
|
|
/// When the database value may be nil, ask for an optional:
|
|
///
|
|
/// ```swift
|
|
/// let row = try Row.fetchOne(db, sql: "SELECT NULL AS name")!
|
|
/// let name: String? = row[Column("name")] // nil
|
|
/// ```
|
|
///
|
|
/// When the column does not exist, nil is returned:
|
|
///
|
|
/// ```swift
|
|
/// let row = try Row.fetchOne(db, sql: "SELECT 'Alice' AS name")!
|
|
/// let name: String? = row[Column("missing")] // nil
|
|
/// ```
|
|
@inlinable
|
|
public subscript<Value: DatabaseValueConvertible>(_ column: some ColumnExpression) -> Value {
|
|
try! decode(Value.self, forKey: column.name)
|
|
}
|
|
|
|
/// Returns the value at given column, converted to the requested type.
|
|
///
|
|
/// This method exists as an optimization opportunity for types that adopt
|
|
/// ``StatementColumnConvertible``. It can trigger [SQLite built-in
|
|
/// conversions](https://www.sqlite.org/datatype3.html).
|
|
///
|
|
/// Column name lookup is case-insensitive. When several columns exist with
|
|
/// the same name, the leftmost column is considered.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// let row = try Row.fetchOne(db, sql: "SELECT 42 AS score")!
|
|
/// let score: Int = row[Column("score")] // 42
|
|
///
|
|
/// let row = try Row.fetchOne(db, sql: "SELECT 'Alice' AS name")!
|
|
/// let name: String = row[Column("name")] // "Alice"
|
|
/// ```
|
|
///
|
|
/// When the database value may be nil, ask for an optional:
|
|
///
|
|
/// ```swift
|
|
/// let row = try Row.fetchOne(db, sql: "SELECT NULL AS name")!
|
|
/// let name: String? = row[Column("name")] // nil
|
|
/// ```
|
|
///
|
|
/// When the column does not exist, nil is returned:
|
|
///
|
|
/// ```swift
|
|
/// let row = try Row.fetchOne(db, sql: "SELECT 'Alice' AS name")!
|
|
/// let name: String? = row[Column("missing")] // nil
|
|
/// ```
|
|
@inlinable
|
|
public subscript<Value>(_ column: some ColumnExpression)
|
|
-> Value
|
|
where Value: DatabaseValueConvertible & StatementColumnConvertible
|
|
{
|
|
try! decode(Value.self, forKey: column.name)
|
|
}
|
|
|
|
/// Calls the given closure with the `Data` at given index.
|
|
///
|
|
/// Indexes span from `0` for the leftmost column to `row.count - 1` for the
|
|
/// rightmost column.
|
|
///
|
|
/// If the SQLite value is `NULL`, the data is nil. If the SQLite value can
|
|
/// not be converted to `Data`, an error is thrown.
|
|
///
|
|
/// - warning: The `Data` argument to the body must not be stored or used
|
|
/// outside of the lifetime of the call to the closure.
|
|
public func withUnsafeData<T>(atIndex index: Int, _ body: (Data?) throws -> T) throws -> T {
|
|
_checkIndex(index)
|
|
return try impl.withUnsafeData(atUncheckedIndex: index, body)
|
|
}
|
|
|
|
/// Calls the given closure with the `Data` at the given column.
|
|
///
|
|
/// Column name lookup is case-insensitive. When several columns exist with
|
|
/// the same name, the leftmost column is considered.
|
|
///
|
|
/// If the row does not contain any column with that name, or if the SQLite
|
|
/// value is `NULL`, the data is nil. If the SQLite value can not be
|
|
/// converted to `Data`, an error is thrown.
|
|
///
|
|
/// - warning: The `Data` argument to the body must not be stored or used
|
|
/// outside of the lifetime of the call to the closure.
|
|
public func withUnsafeData<T>(named columnName: String, _ body: (Data?) throws -> T) throws -> T {
|
|
guard let index = index(forColumn: columnName) else {
|
|
return try body(nil)
|
|
}
|
|
return try impl.withUnsafeData(atUncheckedIndex: index, body)
|
|
}
|
|
|
|
/// Calls the given closure with the `Data` at the given column.
|
|
///
|
|
/// Column name lookup is case-insensitive. When several columns exist with
|
|
/// the same name, the leftmost column is considered.
|
|
///
|
|
/// If the row does not contain any column with that name, or if the SQLite
|
|
/// value is `NULL`, the data is nil. If the SQLite value can not be
|
|
/// converted to `Data`, an error is thrown.
|
|
///
|
|
/// - warning: The `Data` argument to the body must not be stored or used
|
|
/// outside of the lifetime of the call to the closure.
|
|
public func withUnsafeData<T>(at column: some ColumnExpression, _ body: (Data?) throws -> T) throws -> T {
|
|
try withUnsafeData(named: column.name, body)
|
|
}
|
|
|
|
/// Returns the optional `Data` at given index.
|
|
///
|
|
/// Indexes span from `0` for the leftmost column to `row.count - 1` for the
|
|
/// rightmost column.
|
|
///
|
|
/// If the SQLite value is NULL, the result is nil. If the SQLite value can
|
|
/// not be converted to Data, a fatal error is raised.
|
|
///
|
|
/// The returned data does not owns its bytes: it must not be used longer
|
|
/// than the row's lifetime.
|
|
@available(*, deprecated, message: "Use withUnsafeData(atIndex:_:) instead.")
|
|
public func dataNoCopy(atIndex index: Int) -> Data? {
|
|
try! withUnsafeData(atIndex: index, { $0 })
|
|
}
|
|
|
|
/// Returns the optional `Data` at given column.
|
|
///
|
|
/// Column name lookup is case-insensitive. When several columns exist with
|
|
/// the same name, the leftmost column is considered.
|
|
///
|
|
/// If the column is missing or if the SQLite value is NULL, the result is
|
|
/// nil. If the SQLite value can not be converted to Data, a fatal error
|
|
/// is raised.
|
|
///
|
|
/// The returned data does not owns its bytes: it must not be used longer
|
|
/// than the row's lifetime.
|
|
@available(*, deprecated, message: "Use withUnsafeData(named:_:) instead.")
|
|
public func dataNoCopy(named columnName: String) -> Data? {
|
|
guard let index = index(forColumn: columnName) else {
|
|
return nil
|
|
}
|
|
return try! withUnsafeData(atUncheckedIndex: index, { $0 })
|
|
}
|
|
|
|
/// Returns the optional `Data` at given column.
|
|
///
|
|
/// Column name lookup is case-insensitive. When several columns exist with
|
|
/// the same name, the leftmost column is considered.
|
|
///
|
|
/// If the column is missing or if the SQLite value is NULL, the result is
|
|
/// nil. If the SQLite value can not be converted to Data, a fatal error
|
|
/// is raised.
|
|
///
|
|
/// The returned data does not owns its bytes: it must not be used longer
|
|
/// than the row's lifetime.
|
|
@available(*, deprecated, message: "Use withUnsafeData(at:_:) instead.")
|
|
public func dataNoCopy(_ column: some ColumnExpression) -> Data? {
|
|
dataNoCopy(named: column.name)
|
|
}
|
|
}
|
|
|
|
extension Row {
|
|
|
|
// MARK: - Extracting DatabaseValue
|
|
|
|
/// The database values in the row, from left to right.
|
|
///
|
|
/// Values appear in the same order as they occur as the `.1` member
|
|
/// of column-value pairs in `self`.
|
|
public var databaseValues: LazyMapCollection<Row, DatabaseValue> {
|
|
lazy.map { $0.1 }
|
|
}
|
|
}
|
|
|
|
extension Row {
|
|
|
|
// MARK: - Extracting Records
|
|
|
|
/// The record associated to the given scope.
|
|
///
|
|
/// Row scopes can be defined manually, with ``ScopeAdapter``.
|
|
/// The ``JoinableRequest/including(required:)`` and
|
|
/// ``JoinableRequest/including(optional:)`` request methods define scopes
|
|
/// named after the key of included associations between record types.
|
|
///
|
|
/// A breadth-first search is performed in all available scopes in the row,
|
|
/// recursively.
|
|
///
|
|
/// A fatal error is raised if the scope is not available, or contains only
|
|
/// `NULL` values.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// struct Book: TableRecord, FetchableRecord {
|
|
/// static let author = belongsTo(Author.self)
|
|
/// }
|
|
///
|
|
/// struct Author: TableRecord, FetchableRecord {
|
|
/// static let country = belongsTo(Country.self)
|
|
/// }
|
|
///
|
|
/// struct Country: TableRecord, FetchableRecord { }
|
|
///
|
|
/// // Fetch a book, with its author, and the country of its author.
|
|
/// let request = Book
|
|
/// .including(required: Book.author
|
|
/// .including(required: Author.country))
|
|
/// let bookRow = try Row.fetchOne(db, request)!
|
|
///
|
|
/// let book = try Book(row: bookRow)
|
|
/// let author: Author = bookRow["author"]
|
|
/// let country: Country = bookRow["country"]
|
|
/// ```
|
|
///
|
|
/// See also: ``scopesTree``
|
|
///
|
|
/// - parameter scope: A scope identifier.
|
|
public subscript<Record: FetchableRecord>(_ scope: String) -> Record {
|
|
try! decode(Record.self, forKey: scope)
|
|
}
|
|
|
|
/// The eventual record associated to the given scope.
|
|
///
|
|
/// Row scopes can be defined manually, with ``ScopeAdapter``.
|
|
/// The ``JoinableRequest/including(required:)`` and
|
|
/// ``JoinableRequest/including(optional:)`` request methods define scopes
|
|
/// named after the key of included associations between record types.
|
|
///
|
|
/// A breadth-first search is performed in all available scopes in the row,
|
|
/// recursively.
|
|
///
|
|
/// The result is nil if the scope is not available, or contains only
|
|
/// `NULL` values.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// struct Book: TableRecord, FetchableRecord {
|
|
/// static let author = belongsTo(Author.self)
|
|
/// }
|
|
///
|
|
/// struct Author: TableRecord, FetchableRecord {
|
|
/// static let country = belongsTo(Country.self)
|
|
/// }
|
|
///
|
|
/// struct Country: TableRecord, FetchableRecord { }
|
|
///
|
|
/// // Fetch a book, with its author, and the country of its author.
|
|
/// let request = Book
|
|
/// .including(optional: Book.author
|
|
/// .including(optional: Author.country))
|
|
/// let bookRow = try Row.fetchOne(db, request)!
|
|
///
|
|
/// let book = try Book(row: bookRow)
|
|
/// let author: Author? = bookRow["author"]
|
|
/// let country: Country? = bookRow["country"]
|
|
/// ```
|
|
///
|
|
/// See also: ``scopesTree``
|
|
///
|
|
/// - parameter scope: A scope identifier.
|
|
public subscript<Record: FetchableRecord>(_ scope: String) -> Record? {
|
|
try! decodeIfPresent(Record.self, forKey: scope)
|
|
}
|
|
|
|
/// A collection of prefetched records associated to the given
|
|
/// association key.
|
|
///
|
|
/// Prefetched rows are defined by the ``JoinableRequest/including(all:)``
|
|
/// request method.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// struct Author: TableRecord, FetchableRecord {
|
|
/// static let books = hasMany(Book.self)
|
|
/// }
|
|
///
|
|
/// struct Book: TableRecord, FetchableRecord { }
|
|
///
|
|
/// let request = Author.including(all: Author.books)
|
|
/// let authorRow = try Row.fetchOne(db, request)!
|
|
///
|
|
/// let author = try Author(row: authorRow)
|
|
/// let books: [Book] = author["books"]
|
|
/// ```
|
|
///
|
|
/// See also: ``prefetchedRows``
|
|
///
|
|
/// - parameter key: An association key.
|
|
public subscript<Records>(_ key: String)
|
|
-> Records
|
|
where
|
|
Records: RangeReplaceableCollection,
|
|
Records.Element: FetchableRecord
|
|
{
|
|
try! decode(Records.self, forKey: key)
|
|
}
|
|
|
|
/// A set prefetched records associated to the given association key.
|
|
///
|
|
/// Prefetched rows are defined by the ``JoinableRequest/including(all:)``
|
|
/// request method.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// struct Author: TableRecord, FetchableRecord {
|
|
/// static let books = hasMany(Book.self)
|
|
/// }
|
|
///
|
|
/// struct Book: TableRecord, FetchableRecord, Hashable { }
|
|
///
|
|
/// let request = Author.including(all: Author.books)
|
|
/// let authorRow = try Row.fetchOne(db, request)!
|
|
///
|
|
/// let author = try Author(row: authorRow)
|
|
/// let books: Set<Book> = author["books"]
|
|
/// ```
|
|
///
|
|
/// See also: ``prefetchedRows``
|
|
///
|
|
/// - parameter key: An association key.
|
|
public subscript<Record: FetchableRecord & Hashable>(_ key: String) -> Set<Record> {
|
|
try! decode(Set<Record>.self, forKey: key)
|
|
}
|
|
}
|
|
|
|
extension Row {
|
|
|
|
// MARK: - Scopes
|
|
|
|
/// A view on the scopes defined by row adapters.
|
|
///
|
|
/// The returned object provides an access to all available scopes in
|
|
/// the row.
|
|
///
|
|
/// Row scopes can be defined manually, with ``ScopeAdapter``.
|
|
/// The ``JoinableRequest/including(required:)`` and
|
|
/// ``JoinableRequest/including(optional:)`` request methods define scopes
|
|
/// named after the key of included associations between record types.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// struct Book: TableRecord {
|
|
/// static let author = belongsTo(Author.self)
|
|
/// }
|
|
///
|
|
/// struct Author: TableRecord {
|
|
/// static let country = belongsTo(Country.self)
|
|
/// }
|
|
///
|
|
/// struct Country: TableRecord { }
|
|
///
|
|
/// // Fetch a book, with its author, and the country of its author.
|
|
/// let request = Book
|
|
/// .including(required: Book.author
|
|
/// .including(required: Author.country))
|
|
/// let bookRow = try Row.fetchOne(db, request)!
|
|
///
|
|
/// print(bookRow)
|
|
/// // Prints [id:42, title:"Moby-Dick", authorId:1]
|
|
///
|
|
/// let authorRow = bookRow.scopes["author"]!
|
|
/// print(authorRow)
|
|
/// // Prints [id:1, name:"Herman Melville", countryCode: "US"]
|
|
///
|
|
/// let countryRow = authorRow.scopes["country"]!
|
|
/// print(countryRow)
|
|
/// // Prints [code:"US" name:"United States of America"]
|
|
/// ```
|
|
///
|
|
/// See also ``scopesTree``.
|
|
public var scopes: ScopesView {
|
|
impl.scopes(prefetchedRows: prefetchedRows)
|
|
}
|
|
|
|
/// A view on the scopes tree defined by row adapters.
|
|
///
|
|
/// The returned object provides an access to all available scopes in
|
|
/// the row, recursively. For any given scope identifier, a breadth-first
|
|
/// search is performed.
|
|
///
|
|
/// Row scopes can be defined manually, with ``ScopeAdapter``.
|
|
/// The ``JoinableRequest/including(required:)`` and
|
|
/// ``JoinableRequest/including(optional:)`` request methods define scopes
|
|
/// named after the key of included associations between record types.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// struct Book: TableRecord {
|
|
/// static let author = belongsTo(Author.self)
|
|
/// }
|
|
///
|
|
/// struct Author: TableRecord {
|
|
/// static let country = belongsTo(Country.self)
|
|
/// }
|
|
///
|
|
/// struct Country: TableRecord { }
|
|
///
|
|
/// // Fetch a book, with its author, and the country of its author.
|
|
/// let request = Book
|
|
/// .including(required: Book.author
|
|
/// .including(required: Author.country))
|
|
/// let bookRow = try Row.fetchOne(db, request)!
|
|
///
|
|
/// print(bookRow)
|
|
/// // Prints [id:42, title:"Moby-Dick", authorId:1]
|
|
///
|
|
/// print(bookRow.scopesTree["author"])
|
|
/// // Prints [id:1, name:"Herman Melville", countryCode: "US"]
|
|
///
|
|
/// print(bookRow.scopesTree["country"])
|
|
/// // Prints [code:"US" name:"United States of America"]
|
|
/// ```
|
|
///
|
|
/// See also ``scopes``.
|
|
public var scopesTree: ScopesTreeView {
|
|
ScopesTreeView(scopes: scopes)
|
|
}
|
|
|
|
/// The row, without any scope of prefetched rows.
|
|
///
|
|
/// This property is useful when testing the content of rows fetched from
|
|
/// joined requests.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// struct Book: TableRecord {
|
|
/// static let author = belongsTo(Author.self)
|
|
/// static let awards = hasMany(Award.self)
|
|
/// }
|
|
///
|
|
/// struct Author: TableRecord { }
|
|
/// struct Award: TableRecord { }
|
|
///
|
|
/// // Fetch a book, with its author, and its awards.
|
|
/// let request = Book
|
|
/// .including(required: Book.author)
|
|
/// .including(all: Book.awards)
|
|
/// let bookRow = try Row.fetchOne(db, request)!
|
|
///
|
|
/// // Failure
|
|
/// XCTAssertEqual(bookRow, ["id":42, "title":"Moby-Dick", "authorId":1])
|
|
///
|
|
/// // Success
|
|
/// XCTAssertEqual(bookRow.unscoped, ["id":42, "title":"Moby-Dick", "authorId":1])
|
|
/// ```
|
|
public var unscoped: Row {
|
|
var row = impl.unscopedRow(self)
|
|
|
|
// Remove prefetchedRows
|
|
if row.prefetchedRows.isEmpty == false {
|
|
// Make sure we build another Row instance
|
|
row = Row(impl: row.copy().impl)
|
|
assert(row !== self)
|
|
assert(row.prefetchedRows.isEmpty)
|
|
}
|
|
return row
|
|
}
|
|
|
|
/// The raw row fetched from the database.
|
|
///
|
|
/// This property is useful when debugging the content of rows fetched from
|
|
/// joined requests.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// struct Book: TableRecord {
|
|
/// static let author = belongsTo(Author.self)
|
|
/// }
|
|
///
|
|
/// struct Author: TableRecord { }
|
|
///
|
|
/// // SELECT book.*, author.*
|
|
/// // FROM book
|
|
/// // JOIN author ON author.id = book.authorId
|
|
/// let request = Book.including(required: Book.author)
|
|
/// let bookRow = try Row.fetchOne(db, request)!
|
|
///
|
|
/// print(bookRow)
|
|
/// // Prints [id:42, title:"Moby-Dick", authorId:1]
|
|
///
|
|
/// print(bookRow.unadapted)
|
|
/// // Prints [id:42, title:"Moby-Dick", authorId:1, id:1, name:"Herman Melville"]
|
|
/// ```
|
|
public var unadapted: Row {
|
|
impl.unadaptedRow(self)
|
|
}
|
|
}
|
|
|
|
// MARK: - Throwing DatabaseValueConvertible Decoding Methods
|
|
|
|
extension Row {
|
|
/// Returns the value at given index, converted to the requested type.
|
|
///
|
|
/// Indexes span from `0` for the leftmost column to `row.count - 1` for the
|
|
/// rightmost column.
|
|
///
|
|
/// If the SQLite value is NULL, or if the conversion fails, a
|
|
/// `RowDecodingError` is thrown.
|
|
@inlinable
|
|
func decode<Value: DatabaseValueConvertible>(
|
|
_ type: Value.Type = Value.self,
|
|
atIndex index: Int)
|
|
throws -> Value
|
|
{
|
|
_checkIndex(index)
|
|
return try Value.decode(fromRow: self, atUncheckedIndex: index)
|
|
}
|
|
|
|
/// Returns the value at given column, converted to the requested type.
|
|
///
|
|
/// Column name lookup is case-insensitive. When several columns exist with
|
|
/// the same name, the leftmost column is considered.
|
|
///
|
|
/// If the row does not contain the column, or if the SQLite value is NULL,
|
|
/// or if the SQLite value can not be converted to `Value`, a
|
|
/// `RowDecodingError` is thrown.
|
|
@inlinable
|
|
func decode<Value: DatabaseValueConvertible>(
|
|
_ type: Value.Type = Value.self,
|
|
forKey columnName: String)
|
|
throws -> Value
|
|
{
|
|
guard let index = index(forColumn: columnName) else {
|
|
if let value = Value.fromMissingColumn() {
|
|
return value
|
|
} else {
|
|
throw RowDecodingError.columnNotFound(columnName, context: RowDecodingContext(row: self))
|
|
}
|
|
}
|
|
return try Value.decode(fromRow: self, atUncheckedIndex: index)
|
|
}
|
|
}
|
|
|
|
// MARK: - Throwing DatabaseValueConvertible & StatementColumnConvertible Decoding Methods
|
|
|
|
extension Row {
|
|
/// Returns the value at given index, converted to the requested type.
|
|
///
|
|
/// This method exists as an optimization opportunity for types that adopt
|
|
/// ``StatementColumnConvertible``. It can trigger [SQLite built-in
|
|
/// conversions](https://www.sqlite.org/datatype3.html).
|
|
///
|
|
/// Indexes span from `0` for the leftmost column to `row.count - 1` for the
|
|
/// rightmost column.
|
|
///
|
|
/// If the SQLite value is NULL, or if the conversion fails, a
|
|
/// `RowDecodingError` is thrown.
|
|
@inline(__always)
|
|
@inlinable
|
|
func decode<Value: DatabaseValueConvertible & StatementColumnConvertible>(
|
|
_ type: Value.Type = Value.self,
|
|
atIndex index: Int)
|
|
throws -> Value
|
|
{
|
|
_checkIndex(index)
|
|
return try Value.fastDecode(fromRow: self, atUncheckedIndex: index)
|
|
}
|
|
|
|
/// Returns the value at given column, converted to the requested type.
|
|
///
|
|
/// This method exists as an optimization opportunity for types that adopt
|
|
/// ``StatementColumnConvertible``. It can trigger [SQLite built-in
|
|
/// conversions](https://www.sqlite.org/datatype3.html).
|
|
///
|
|
/// Column name lookup is case-insensitive. When several columns exist with
|
|
/// the same name, the leftmost column is considered.
|
|
///
|
|
/// If the row does not contain the column, or if the SQLite value is NULL,
|
|
/// or if the SQLite value can not be converted to `Value`, a
|
|
/// `RowDecodingError` is thrown.
|
|
@inlinable
|
|
func decode<Value: DatabaseValueConvertible & StatementColumnConvertible>(
|
|
_ type: Value.Type = Value.self,
|
|
forKey columnName: String)
|
|
throws -> Value
|
|
{
|
|
guard let index = index(forColumn: columnName) else {
|
|
if let value = Value.fromMissingColumn() {
|
|
return value
|
|
} else {
|
|
throw RowDecodingError.columnNotFound(columnName, context: RowDecodingContext(row: self))
|
|
}
|
|
}
|
|
return try Value.fastDecode(fromRow: self, atUncheckedIndex: index)
|
|
}
|
|
|
|
// Support for fast decoding in scoped rows
|
|
@usableFromInline
|
|
func fastDecode<Value: DatabaseValueConvertible & StatementColumnConvertible>(
|
|
_ type: Value.Type,
|
|
atUncheckedIndex index: Int)
|
|
throws -> Value
|
|
{
|
|
try impl.fastDecode(type, atUncheckedIndex: index)
|
|
}
|
|
}
|
|
|
|
// MARK: - Throwing Data Decoding Methods
|
|
|
|
extension Row {
|
|
/// Calls the given closure with the `Data` at given index.
|
|
///
|
|
/// Indexes span from `0` for the leftmost column to `row.count - 1` for the
|
|
/// rightmost column.
|
|
///
|
|
/// If the SQLite value is `NULL`, the data is nil. If the SQLite value can
|
|
/// not be converted to `Data`, an error is thrown.
|
|
///
|
|
/// - warning: The `Data` argument to the body must not be stored or used
|
|
/// outside of the lifetime of the call to the closure.
|
|
func withUnsafeData<T>(atUncheckedIndex index: Int, _ body: (Data?) throws -> T) throws -> T {
|
|
try impl.withUnsafeData(atUncheckedIndex: index, body)
|
|
}
|
|
}
|
|
|
|
// MARK: - Throwing Record Decoding Methods
|
|
|
|
extension Row {
|
|
/// Returns the eventual record associated with the given scope.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// let request = Book.including(optional: Book.author)
|
|
/// let row = try Row.fetchOne(db, request)!
|
|
///
|
|
/// try print(Book(row: row).title)
|
|
/// // Prints "Moby-Dick"
|
|
///
|
|
/// let author: Author? = row["author"]
|
|
/// print(author.name)
|
|
/// // Prints "Herman Melville"
|
|
///
|
|
/// Associated records stored in nested associations are available, too:
|
|
///
|
|
/// let request = Book.including(optional: Book.author.including(optional: Author.country))
|
|
/// let row = try Row.fetchOne(db, request)!
|
|
///
|
|
/// try print(Book(row: row).title)
|
|
/// // Prints "Moby-Dick"
|
|
///
|
|
/// let country: Country? = row["country"]
|
|
/// print(country.name)
|
|
/// // Prints "United States"
|
|
///
|
|
/// Nil is returned if the scope is not available, or contains only
|
|
/// null values.
|
|
///
|
|
/// See ``splittingRowAdapters(columnCounts:)`` for a sample code.
|
|
func decodeIfPresent<Record: FetchableRecord>(
|
|
_ type: Record.Type = Record.self,
|
|
forKey scope: String)
|
|
throws -> Record?
|
|
{
|
|
guard let scopedRow = scopesTree[scope], scopedRow.containsNonNullValue else {
|
|
return nil
|
|
}
|
|
return try Record(row: scopedRow)
|
|
}
|
|
|
|
/// Returns the record associated with the given scope.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// let request = Book.including(required: Book.author)
|
|
/// let row = try Row.fetchOne(db, request)!
|
|
///
|
|
/// try print(Book(row: row).title)
|
|
/// // Prints "Moby-Dick"
|
|
///
|
|
/// let author: Author = row["author"]
|
|
/// print(author.name)
|
|
/// // Prints "Herman Melville"
|
|
///
|
|
/// Associated records stored in nested associations are available, too:
|
|
///
|
|
/// let request = Book.including(required: Book.author.including(required: Author.country))
|
|
/// let row = try Row.fetchOne(db, request)!
|
|
///
|
|
/// try print(Book(row: row).title)
|
|
/// // Prints "Moby-Dick"
|
|
///
|
|
/// let country: Country = row["country"]
|
|
/// print(country.name)
|
|
/// // Prints "United States"
|
|
///
|
|
/// A fatal error is raised if the scope is not available, or contains only
|
|
/// null values.
|
|
///
|
|
/// See ``splittingRowAdapters(columnCounts:)`` for a sample code.
|
|
func decode<Record: FetchableRecord>(
|
|
_ type: Record.Type = Record.self,
|
|
forKey scope: String)
|
|
throws -> Record
|
|
{
|
|
guard let scopedRow = scopesTree[scope] else {
|
|
let availableScopes = scopesTree.names
|
|
if availableScopes.isEmpty {
|
|
throw RowDecodingError.keyNotFound(
|
|
.scope(scope),
|
|
RowDecodingError.Context(
|
|
decodingContext: RowDecodingContext(row: self),
|
|
debugDescription: """
|
|
scope not found: \(String(reflecting: scope))
|
|
"""))
|
|
} else {
|
|
throw RowDecodingError.keyNotFound(
|
|
.scope(scope),
|
|
RowDecodingError.Context(
|
|
decodingContext: RowDecodingContext(row: self),
|
|
debugDescription: """
|
|
scope not found: \(String(reflecting: scope)) - \
|
|
available scopes: \(availableScopes.sorted())
|
|
"""))
|
|
}
|
|
}
|
|
guard scopedRow.containsNonNullValue else {
|
|
throw RowDecodingError.valueMismatch(
|
|
Record.self,
|
|
RowDecodingError.Context(
|
|
decodingContext: RowDecodingContext(row: self, key: .scope(scope)),
|
|
debugDescription: """
|
|
scope \(String(reflecting: scope)) only contains null values
|
|
"""))
|
|
}
|
|
return try Record(row: scopedRow)
|
|
}
|
|
}
|
|
|
|
// MARK: - Throwing Record RangeReplaceableCollection and Set Methods
|
|
|
|
extension Row {
|
|
/// Returns the records encoded in the given prefetched rows.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// let request = Author.including(all: Author.books)
|
|
/// let row = try Row.fetchOne(db, request)!
|
|
///
|
|
/// try print(Author(row: row).name)
|
|
/// // Prints "Herman Melville"
|
|
///
|
|
/// let books: [Book] = row["books"]
|
|
/// print(books[0].title)
|
|
/// // Prints "Moby-Dick"
|
|
func decode<Collection>(
|
|
_ type: Collection.Type = Collection.self,
|
|
forKey key: String)
|
|
throws -> Collection
|
|
where
|
|
Collection: RangeReplaceableCollection,
|
|
Collection.Element: FetchableRecord
|
|
{
|
|
guard let rows = prefetchedRows[key] else {
|
|
let availableKeys = prefetchedRows.keys
|
|
if availableKeys.isEmpty {
|
|
throw RowDecodingError.keyNotFound(
|
|
.prefetchKey(key),
|
|
RowDecodingError.Context(
|
|
decodingContext: RowDecodingContext(row: self),
|
|
debugDescription: """
|
|
key for prefetched rows not found: \(String(reflecting: key))
|
|
"""))
|
|
} else {
|
|
throw RowDecodingError.keyNotFound(
|
|
.prefetchKey(key),
|
|
RowDecodingError.Context(
|
|
decodingContext: RowDecodingContext(row: self),
|
|
debugDescription: """
|
|
key for prefetched rows not found: \(String(reflecting: key)) \
|
|
- available keys: \(availableKeys.sorted())
|
|
"""))
|
|
}
|
|
}
|
|
|
|
var collection = Collection()
|
|
collection.reserveCapacity(rows.count)
|
|
for row in rows {
|
|
try collection.append(Collection.Element(row: row))
|
|
}
|
|
return collection
|
|
}
|
|
|
|
/// Returns the set of records encoded in the given prefetched rows.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// let request = Author.including(all: Author.books)
|
|
/// let row = try Row.fetchOne(db, request)!
|
|
///
|
|
/// try print(Author(row: row).name)
|
|
/// // Prints "Herman Melville"
|
|
///
|
|
/// let books: Set<Book> = row["books"]
|
|
/// print(books.first!.title)
|
|
/// // Prints "Moby-Dick"
|
|
func decode<Record: FetchableRecord & Hashable>(
|
|
_ type: Set<Record>.Type = Set<Record>.self,
|
|
forKey key: String)
|
|
throws -> Set<Record>
|
|
{
|
|
guard let rows = prefetchedRows[key] else {
|
|
let availableKeys = prefetchedRows.keys
|
|
if availableKeys.isEmpty {
|
|
throw RowDecodingError.keyNotFound(
|
|
.prefetchKey(key),
|
|
RowDecodingError.Context(
|
|
decodingContext: RowDecodingContext(row: self),
|
|
debugDescription: """
|
|
key for prefetched rows not found: \(String(reflecting: key))
|
|
"""))
|
|
} else {
|
|
throw RowDecodingError.keyNotFound(
|
|
.prefetchKey(key),
|
|
RowDecodingError.Context(
|
|
decodingContext: RowDecodingContext(row: self),
|
|
debugDescription: """
|
|
key for prefetched rows not found: \(String(reflecting: key)) \
|
|
- available keys: \(availableKeys.sorted())
|
|
"""))
|
|
}
|
|
}
|
|
var set = Set<Record>(minimumCapacity: rows.count)
|
|
for row in rows {
|
|
try set.insert(Record(row: row))
|
|
}
|
|
return set
|
|
}
|
|
}
|
|
|
|
// MARK: - RowCursor
|
|
|
|
/// A cursor of raw database rows.
|
|
///
|
|
/// A `RowCursor` iterates all rows from a database request.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// try dbQueue.read { db in
|
|
/// let rows = try Row.fetchCursor(db, sql: """
|
|
/// SELECT * FROM player
|
|
/// """)
|
|
/// while let row = try rows.next() {
|
|
/// let id: Int64 = row["id"]
|
|
/// let name: String = row["name"]
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
public final class RowCursor: DatabaseCursor {
|
|
public typealias Element = Row
|
|
public let _statement: Statement
|
|
public var _isDone = false
|
|
@usableFromInline let _row: Row // Reused for performance
|
|
|
|
init(statement: Statement, arguments: StatementArguments? = nil, adapter: (any RowAdapter)? = nil) throws {
|
|
self._statement = statement
|
|
self._row = try Row(statement: statement).adapted(with: adapter, layout: statement)
|
|
|
|
// Assume cursor is created for immediate iteration: reset and set arguments
|
|
try statement.prepareExecution(withArguments: arguments)
|
|
}
|
|
|
|
deinit {
|
|
// Statement reset fails when sqlite3_step has previously failed.
|
|
// Just ignore reset error.
|
|
try? _statement.reset()
|
|
}
|
|
|
|
@inlinable
|
|
public func _element(sqliteStatement: SQLiteStatement) -> Row { _row }
|
|
}
|
|
|
|
// Explicit non-conformance to Sendable: database cursors must be used from
|
|
// a serialized database access dispatch queue.
|
|
@available(*, unavailable)
|
|
extension RowCursor: Sendable { }
|
|
|
|
extension Row {
|
|
|
|
// MARK: - Fetching From Prepared Statement
|
|
|
|
/// Returns a cursor over rows fetched from a prepared statement.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// try dbQueue.read { db in
|
|
/// let lastName = "O'Reilly"
|
|
/// let sql = "SELECT * FROM player WHERE lastName = ?"
|
|
/// let statement = try db.makeStatement(sql: sql)
|
|
/// let rows = try Row.fetchCursor(statement, arguments: [lastName])
|
|
/// while let row = try rows.next() {
|
|
/// let id: Int64 = row["id"]
|
|
/// let name: String = row["name"]
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// Fetched rows are reused during the cursor iteration: don't turn a row
|
|
/// cursor into an array with `Array(rows)` since you would not get the
|
|
/// distinct rows you expect.
|
|
/// Use ``fetchAll(_:arguments:adapter:)`` instead.
|
|
///
|
|
/// For the same reason, make sure you make a copy whenever you extract a
|
|
/// row for later use: `row.copy()`.
|
|
///
|
|
/// The returned cursor is valid only during the remaining execution of the
|
|
/// database access. Do not store or return the cursor for later use.
|
|
///
|
|
/// If the database is modified during the cursor iteration, the remaining
|
|
/// elements are undefined.
|
|
///
|
|
/// - parameters:
|
|
/// - db: A database connection.
|
|
/// - sql: An SQL string.
|
|
/// - arguments: Optional statement arguments.
|
|
/// - adapter: Optional RowAdapter
|
|
/// - returns: A ``RowCursor`` over fetched rows.
|
|
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs.
|
|
public static func fetchCursor(
|
|
_ statement: Statement,
|
|
arguments: StatementArguments? = nil,
|
|
adapter: (any RowAdapter)? = nil)
|
|
throws -> RowCursor
|
|
{
|
|
try RowCursor(statement: statement, arguments: arguments, adapter: adapter)
|
|
}
|
|
|
|
/// Returns an array of rows fetched from a prepared statement.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// try dbQueue.read { db in
|
|
/// let lastName = "O'Reilly"
|
|
/// let sql = "SELECT * FROM player WHERE lastName = ?"
|
|
/// let statement = try db.makeStatement(sql: sql)
|
|
/// let rows = try Row.fetchAll(statement, arguments: [lastName])
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// - parameters:
|
|
/// - statement: The statement to run.
|
|
/// - arguments: Optional statement arguments.
|
|
/// - adapter: Optional RowAdapter
|
|
/// - returns: An array of rows.
|
|
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs.
|
|
public static func fetchAll(
|
|
_ statement: Statement,
|
|
arguments: StatementArguments? = nil,
|
|
adapter: (any RowAdapter)? = nil)
|
|
throws -> [Row]
|
|
{
|
|
// The cursor reuses a single mutable row. Return immutable copies.
|
|
try Array(fetchCursor(statement, arguments: arguments, adapter: adapter).map { $0.copy() })
|
|
}
|
|
|
|
/// Returns a set of rows fetched from a prepared statement.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// try dbQueue.read { db in
|
|
/// let lastName = "O'Reilly"
|
|
/// let sql = "SELECT * FROM player WHERE lastName = ?"
|
|
/// let statement = try db.makeStatement(sql: sql)
|
|
/// let rows = try Row.fetchSet(statement, arguments: [lastName])
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// - parameters:
|
|
/// - statement: The statement to run.
|
|
/// - arguments: Optional statement arguments.
|
|
/// - adapter: Optional RowAdapter
|
|
/// - returns: A set of rows.
|
|
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs.
|
|
public static func fetchSet(
|
|
_ statement: Statement,
|
|
arguments: StatementArguments? = nil,
|
|
adapter: (any RowAdapter)? = nil)
|
|
throws -> Set<Row>
|
|
{
|
|
// The cursor reuses a single mutable row. Return immutable copies.
|
|
try Set(fetchCursor(statement, arguments: arguments, adapter: adapter).map { $0.copy() })
|
|
}
|
|
|
|
/// Returns a single row fetched from a prepared statement.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// try dbQueue.read { db in
|
|
/// let lastName = "O'Reilly"
|
|
/// let sql = "SELECT * FROM player WHERE lastName = ? LIMIT 1"
|
|
/// let statement = try db.makeStatement(sql: sql)
|
|
/// let row = try Row.fetchOne(statement, arguments: [lastName])
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// - parameters:
|
|
/// - statement: The statement to run.
|
|
/// - arguments: Optional statement arguments.
|
|
/// - adapter: Optional RowAdapter
|
|
/// - returns: An optional row.
|
|
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs.
|
|
public static func fetchOne(
|
|
_ statement: Statement,
|
|
arguments: StatementArguments? = nil,
|
|
adapter: (any RowAdapter)? = nil)
|
|
throws -> Row?
|
|
{
|
|
let cursor = try fetchCursor(statement, arguments: arguments, adapter: adapter)
|
|
// Keep cursor alive until we can copy the fetched row
|
|
return try withExtendedLifetime(cursor) {
|
|
try cursor.next().map { $0.copy() }
|
|
}
|
|
}
|
|
}
|
|
|
|
extension Row {
|
|
|
|
// MARK: - Fetching From SQL
|
|
|
|
/// Returns a cursor over rows fetched from an SQL query.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// try dbQueue.read { db in
|
|
/// let lastName = "O'Reilly"
|
|
/// let sql = "SELECT * FROM player WHERE lastName = ?"
|
|
/// let rows = try Row.fetchCursor(db, sql: sql, arguments: [lastName])
|
|
/// while let row = try rows.next() {
|
|
/// let id: Int64 = row["id"]
|
|
/// let name: String = row["name"]
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// Fetched rows are reused during the cursor iteration: don't turn a row
|
|
/// cursor into an array with `Array(rows)` since you would not get the
|
|
/// distinct rows you expect.
|
|
/// Use ``fetchAll(_:sql:arguments:adapter:)`` instead.
|
|
///
|
|
/// For the same reason, make sure you make a copy whenever you extract a
|
|
/// row for later use: `row.copy()`.
|
|
///
|
|
/// The returned cursor is valid only during the remaining execution of the
|
|
/// database access. Do not store or return the cursor for later use.
|
|
///
|
|
/// If the database is modified during the cursor iteration, the remaining
|
|
/// elements are undefined.
|
|
///
|
|
/// - parameters:
|
|
/// - db: A database connection.
|
|
/// - sql: An SQL string.
|
|
/// - arguments: Statement arguments.
|
|
/// - adapter: Optional RowAdapter
|
|
/// - returns: A ``RowCursor`` over fetched rows.
|
|
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs.
|
|
public static func fetchCursor(
|
|
_ db: Database,
|
|
sql: String,
|
|
arguments: StatementArguments = StatementArguments(),
|
|
adapter: (any RowAdapter)? = nil)
|
|
throws -> RowCursor
|
|
{
|
|
try fetchCursor(db, SQLRequest(sql: sql, arguments: arguments, adapter: adapter))
|
|
}
|
|
|
|
/// Returns an array of rows fetched from an SQL query.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// try dbQueue.read { db in
|
|
/// let lastName = "O'Reilly"
|
|
/// let sql = "SELECT * FROM player WHERE lastName = ?"
|
|
/// let rows = try Row.fetchAll(db, sql: sql, arguments: [lastName])
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// - parameters:
|
|
/// - db: A database connection.
|
|
/// - sql: An SQL string.
|
|
/// - arguments: Statement arguments.
|
|
/// - adapter: Optional RowAdapter
|
|
/// - returns: An array of rows.
|
|
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs.
|
|
public static func fetchAll(
|
|
_ db: Database,
|
|
sql: String,
|
|
arguments: StatementArguments = StatementArguments(),
|
|
adapter: (any RowAdapter)? = nil)
|
|
throws -> [Row]
|
|
{
|
|
try fetchAll(db, SQLRequest(sql: sql, arguments: arguments, adapter: adapter))
|
|
}
|
|
|
|
/// Returns a set of rows fetched from an SQL query.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// try dbQueue.read { db in
|
|
/// let lastName = "O'Reilly"
|
|
/// let sql = "SELECT * FROM player WHERE lastName = ?"
|
|
/// let rows = try Row.fetchSet(db, sql: sql, arguments: [lastName])
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// - parameters:
|
|
/// - db: A database connection.
|
|
/// - sql: An SQL string.
|
|
/// - arguments: Statement arguments.
|
|
/// - adapter: Optional RowAdapter
|
|
/// - returns: A set of rows.
|
|
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs.
|
|
public static func fetchSet(
|
|
_ db: Database,
|
|
sql: String,
|
|
arguments: StatementArguments = StatementArguments(),
|
|
adapter: (any RowAdapter)? = nil)
|
|
throws -> Set<Row>
|
|
{
|
|
try fetchSet(db, SQLRequest(sql: sql, arguments: arguments, adapter: adapter))
|
|
}
|
|
|
|
/// Returns a single row fetched from an SQL query.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// try dbQueue.read { db in
|
|
/// let lastName = "O'Reilly"
|
|
/// let sql = "SELECT * FROM player WHERE lastName = ? LIMIT 1"
|
|
/// let row = try Row.fetchOne(db, sql: sql, arguments: [lastName])
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// - parameters:
|
|
/// - db: A database connection.
|
|
/// - sql: An SQL string.
|
|
/// - arguments: Statement arguments.
|
|
/// - adapter: Optional RowAdapter
|
|
/// - returns: An optional row.
|
|
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs.
|
|
public static func fetchOne(
|
|
_ db: Database,
|
|
sql: String,
|
|
arguments: StatementArguments = StatementArguments(),
|
|
adapter: (any RowAdapter)? = nil)
|
|
throws -> Row?
|
|
{
|
|
try fetchOne(db, SQLRequest(sql: sql, arguments: arguments, adapter: adapter))
|
|
}
|
|
}
|
|
|
|
extension Row {
|
|
|
|
// MARK: - Fetching From FetchRequest
|
|
|
|
/// Returns a cursor over rows fetched from a fetch request.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// try dbQueue.read { db in
|
|
/// let lastName = "O'Reilly"
|
|
///
|
|
/// // Query interface request
|
|
/// let request = Player.filter(Column("lastName") == lastName)
|
|
///
|
|
/// // SQL request
|
|
/// let request: SQLRequest<Row> = """
|
|
/// SELECT * FROM player WHERE lastName = \(lastName)
|
|
/// """
|
|
///
|
|
/// let rows = try Row.fetchCursor(db, request)
|
|
/// while let row = try rows.next() {
|
|
/// let id: Int64 = row["id"]
|
|
/// let name: String = row["name"]
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// Fetched rows are reused during the cursor iteration: don't turn a row
|
|
/// cursor into an array with `Array(rows)` since you would not get the
|
|
/// distinct rows you expect.
|
|
/// Use ``fetchAll(_:_:)`` instead.
|
|
///
|
|
/// For the same reason, make sure you make a copy whenever you extract a
|
|
/// row for later use: `row.copy()`.
|
|
///
|
|
/// The returned cursor is valid only during the remaining execution of the
|
|
/// database access. Do not store or return the cursor for later use.
|
|
///
|
|
/// If the database is modified during the cursor iteration, the remaining
|
|
/// elements are undefined.
|
|
///
|
|
/// - parameters:
|
|
/// - db: A database connection.
|
|
/// - request: A FetchRequest.
|
|
/// - returns: A ``RowCursor`` over fetched rows.
|
|
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs.
|
|
public static func fetchCursor(_ db: Database, _ request: some FetchRequest) throws -> RowCursor {
|
|
let request = try request.makePreparedRequest(db, forSingleResult: false)
|
|
precondition(request.supplementaryFetch == nil, "Not implemented: fetchCursor with supplementary fetch")
|
|
return try fetchCursor(request.statement, adapter: request.adapter)
|
|
}
|
|
|
|
/// Returns an array of rows fetched from a fetch request.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// try dbQueue.read { db in
|
|
/// let lastName = "O'Reilly"
|
|
///
|
|
/// // Query interface request
|
|
/// let request = Player.filter(Column("lastName") == lastName)
|
|
///
|
|
/// // SQL request
|
|
/// let request: SQLRequest<Row> = """
|
|
/// SELECT * FROM player WHERE lastName = \(lastName)
|
|
/// """
|
|
///
|
|
/// let rows = try Row.fetchAll(db, request)
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// - parameters:
|
|
/// - db: A database connection.
|
|
/// - request: A FetchRequest.
|
|
/// - returns: An array of rows.
|
|
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs.
|
|
public static func fetchAll(_ db: Database, _ request: some FetchRequest) throws -> [Row] {
|
|
let request = try request.makePreparedRequest(db, forSingleResult: false)
|
|
let rows = try fetchAll(request.statement, adapter: request.adapter)
|
|
try request.supplementaryFetch?(db, rows, nil)
|
|
return rows
|
|
}
|
|
|
|
/// Returns a set of rows fetched from a fetch request.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// try dbQueue.read { db in
|
|
/// let lastName = "O'Reilly"
|
|
///
|
|
/// // Query interface request
|
|
/// let request = Player.filter(Column("lastName") == lastName)
|
|
///
|
|
/// // SQL request
|
|
/// let request: SQLRequest<Row> = """
|
|
/// SELECT * FROM player WHERE lastName = \(lastName)
|
|
/// """
|
|
///
|
|
/// let rows = try Row.fetchSet(db, request)
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// - parameters:
|
|
/// - db: A database connection.
|
|
/// - request: A FetchRequest.
|
|
/// - returns: A set of rows.
|
|
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs.
|
|
public static func fetchSet(_ db: Database, _ request: some FetchRequest) throws -> Set<Row> {
|
|
let request = try request.makePreparedRequest(db, forSingleResult: false)
|
|
if let supplementaryFetch = request.supplementaryFetch {
|
|
let rows = try fetchAll(request.statement, adapter: request.adapter)
|
|
try supplementaryFetch(db, rows, nil)
|
|
return Set(rows)
|
|
} else {
|
|
return try fetchSet(request.statement, adapter: request.adapter)
|
|
}
|
|
}
|
|
|
|
/// Returns a single row fetched from a fetch request.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// try dbQueue.read { db in
|
|
/// let lastName = "O'Reilly"
|
|
///
|
|
/// // Query interface request
|
|
/// let request = Player.filter(Column("lastName") == lastName)
|
|
///
|
|
/// // SQL request
|
|
/// let request: SQLRequest<Row> = """
|
|
/// SELECT * FROM player WHERE lastName = \(lastName) LIMIT 1
|
|
/// """
|
|
///
|
|
/// let row = try Row.fetchOne(db, request)
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// - parameters:
|
|
/// - db: A database connection.
|
|
/// - request: A FetchRequest.
|
|
/// - returns: An optional row.
|
|
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs.
|
|
public static func fetchOne(_ db: Database, _ request: some FetchRequest) throws -> Row? {
|
|
let request = try request.makePreparedRequest(db, forSingleResult: true)
|
|
guard let row = try fetchOne(request.statement, adapter: request.adapter) else {
|
|
return nil
|
|
}
|
|
try request.supplementaryFetch?(db, [row], nil)
|
|
return row
|
|
}
|
|
}
|
|
|
|
extension FetchRequest<Row> {
|
|
|
|
// MARK: Fetching Rows
|
|
|
|
/// Returns a cursor over fetched rows.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// try dbQueue.read { db in
|
|
/// let lastName = "O'Reilly"
|
|
/// let request: SQLRequest<Row> = """
|
|
/// SELECT * FROM player WHERE lastName = \(lastName)
|
|
/// """
|
|
/// let rows = try request.fetchCursor(db)
|
|
/// while let row = try rows.next() {
|
|
/// let id: Int64 = row["id"]
|
|
/// let name: String = row["name"]
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// Fetched rows are reused during the cursor iteration: don't turn a row
|
|
/// cursor into an array with `Array(rows)` since you would not get the
|
|
/// distinct rows you expect.
|
|
/// Use ``FetchRequest/fetchAll(_:)-7p809`` instead.
|
|
///
|
|
/// For the same reason, make sure you make a copy whenever you extract a
|
|
/// row for later use: `row.copy()`.
|
|
///
|
|
/// The returned cursor is valid only during the remaining execution of the
|
|
/// database access. Do not store or return the cursor for later use.
|
|
///
|
|
/// If the database is modified during the cursor iteration, the remaining
|
|
/// elements are undefined.
|
|
///
|
|
/// - parameter db: A database connection.
|
|
/// - returns: A ``RowCursor`` over fetched rows.
|
|
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs.
|
|
public func fetchCursor(_ db: Database) throws -> RowCursor {
|
|
try Row.fetchCursor(db, self)
|
|
}
|
|
|
|
/// Returns an array of fetched rows.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// try dbQueue.read { db in
|
|
/// let lastName = "O'Reilly"
|
|
/// let request: SQLRequest<Row> = """
|
|
/// SELECT * FROM player WHERE lastName = \(lastName)
|
|
/// """
|
|
/// let rows = try request.fetchAll(db)
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// - parameter db: A database connection.
|
|
/// - returns: An array of fetched rows.
|
|
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs.
|
|
public func fetchAll(_ db: Database) throws -> [Row] {
|
|
try Row.fetchAll(db, self)
|
|
}
|
|
|
|
/// Returns a set of fetched rows.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// try dbQueue.read { db in
|
|
/// let lastName = "O'Reilly"
|
|
/// let request: SQLRequest<Row> = """
|
|
/// SELECT * FROM player WHERE lastName = \(lastName)
|
|
/// """
|
|
/// let rows = try request.fetchSet(db)
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// - parameter db: A database connection.
|
|
/// - returns: A set of fetched rows.
|
|
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs.
|
|
public func fetchSet(_ db: Database) throws -> Set<Row> {
|
|
try Row.fetchSet(db, self)
|
|
}
|
|
|
|
/// Returns a single row.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// try dbQueue.read { db in
|
|
/// let lastName = "O'Reilly"
|
|
/// let request: SQLRequest<Row> = """
|
|
/// SELECT * FROM player WHERE lastName = \(lastName) LIMIT 1
|
|
/// """
|
|
/// let rows = try request.fetchOne(db)
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// - parameter db: A database connection.
|
|
/// - returns: An optional row.
|
|
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs.
|
|
public func fetchOne(_ db: Database) throws -> Row? {
|
|
try Row.fetchOne(db, self)
|
|
}
|
|
}
|
|
|
|
extension Row: RandomAccessCollection {
|
|
public var startIndex: Index { Index(0) }
|
|
|
|
public var endIndex: Index { Index(count) }
|
|
|
|
/// Returns the (column, value) pair at given index.
|
|
public subscript(position: Index) -> (String, DatabaseValue) {
|
|
let index = position.index
|
|
_checkIndex(index)
|
|
return (
|
|
impl.columnName(atUncheckedIndex: index),
|
|
impl.databaseValue(atUncheckedIndex: index))
|
|
}
|
|
}
|
|
|
|
extension Row: Equatable {
|
|
/// Returns true if and only if both rows have the same columns and values,
|
|
/// in the same order. Columns are compared in a case-sensitive way.
|
|
public static func == (lhs: Row, rhs: Row) -> Bool {
|
|
if lhs === rhs {
|
|
return true
|
|
}
|
|
|
|
guard lhs.count == rhs.count else {
|
|
return false
|
|
}
|
|
|
|
for ((lcol, lval), (rcol, rval)) in zip(lhs, rhs) {
|
|
guard lcol == rcol else {
|
|
return false
|
|
}
|
|
guard lval == rval else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
let lscopeNames = lhs.scopes.names
|
|
let rscopeNames = rhs.scopes.names
|
|
guard lscopeNames == rscopeNames else {
|
|
return false
|
|
}
|
|
|
|
for name in lscopeNames {
|
|
let lscope = lhs.scopes[name]
|
|
let rscope = rhs.scopes[name]
|
|
guard lscope == rscope else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
guard lhs.prefetchedRows == rhs.prefetchedRows else {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
}
|
|
|
|
extension Row: Hashable {
|
|
public func hash(into hasher: inout Hasher) {
|
|
hasher.combine(count)
|
|
for (column, dbValue) in self {
|
|
hasher.combine(column)
|
|
hasher.combine(dbValue)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension Row: CustomStringConvertible {
|
|
public var description: String {
|
|
"["
|
|
+ map { (column, dbValue) in "\(column):\(dbValue)" }.joined(separator: " ")
|
|
+ "]"
|
|
}
|
|
}
|
|
|
|
extension Row: CustomDebugStringConvertible {
|
|
public var debugDescription: String {
|
|
debugDescription(level: 0)
|
|
}
|
|
|
|
private func debugDescription(level: Int) -> String {
|
|
if level == 0 && self == self.unadapted && prefetchedRows.prefetches.isEmpty {
|
|
return description
|
|
}
|
|
let prefix = repeatElement(" ", count: level + 1).joined()
|
|
var str = ""
|
|
if level == 0 {
|
|
str = "▿ " + description
|
|
let unadapted = self.unadapted
|
|
if self != unadapted {
|
|
str += "\n" + prefix + "unadapted: " + unadapted.description
|
|
}
|
|
} else {
|
|
str = description
|
|
}
|
|
for (name, scopedRow) in scopes.sorted(by: { $0.name < $1.name }) {
|
|
str += "\n" + prefix + "- " + name + ": " + scopedRow.debugDescription(level: level + 1)
|
|
}
|
|
for key in prefetchedRows.keys.sorted() {
|
|
// rows is nil if key is a pivot in a "through" association
|
|
if let rows = prefetchedRows[key] {
|
|
let prefetchedRowsDescription: String
|
|
switch rows.count {
|
|
case 0:
|
|
prefetchedRowsDescription = "0 row"
|
|
case 1:
|
|
prefetchedRowsDescription = "1 row"
|
|
case let count:
|
|
prefetchedRowsDescription = "\(count) rows"
|
|
}
|
|
str += "\n" + prefix + "+ " + key + ": \(prefetchedRowsDescription)"
|
|
}
|
|
}
|
|
|
|
return str
|
|
}
|
|
}
|
|
|
|
extension Row: ExpressibleByDictionaryLiteral {
|
|
/// Creates a row initialized with elements. Column order is preserved, and
|
|
/// duplicated columns names are allowed.
|
|
///
|
|
/// let row: Row = ["foo": 1, "foo": "bar", "baz": nil]
|
|
/// print(row)
|
|
/// // Prints [foo:1 foo:"bar" baz:NULL]
|
|
public convenience init(dictionaryLiteral elements: (String, (any DatabaseValueConvertible)?)...) {
|
|
self.init(impl: ArrayRowImpl(columns: elements.map { ($0, $1?.databaseValue ?? .null) }))
|
|
}
|
|
}
|
|
|
|
// MARK: - Index
|
|
|
|
@available(*, deprecated, renamed: "Row.Index")
|
|
typealias RowIndex = Row.Index
|
|
|
|
extension Row {
|
|
/// An index to a (column, value) pair in a ``Row``.
|
|
public struct Index: Sendable {
|
|
let index: Int
|
|
init(_ index: Int) { self.index = index }
|
|
}
|
|
}
|
|
|
|
extension Row.Index: Equatable {
|
|
public static func == (lhs: Self, rhs: Self) -> Bool {
|
|
lhs.index == rhs.index
|
|
}
|
|
}
|
|
|
|
extension Row.Index: Comparable {
|
|
public static func < (lhs: Self, rhs: Self) -> Bool {
|
|
lhs.index < rhs.index
|
|
}
|
|
}
|
|
|
|
extension Row.Index: Strideable {
|
|
public func distance(to other: Self) -> Int {
|
|
other.index - index
|
|
}
|
|
|
|
public func advanced(by n: Int) -> Self {
|
|
Row.Index(index + n)
|
|
}
|
|
}
|
|
|
|
// MARK: - Row.ScopesView
|
|
|
|
extension Row {
|
|
/// A view on the scopes defined by row adapters.
|
|
///
|
|
/// `ScopesView` is a `Collection` of `(name: String, row: Row)` pairs.
|
|
///
|
|
/// See ``Row/scopes`` for more information.
|
|
public struct ScopesView {
|
|
private let row: Row
|
|
private let scopes: [String: any _LayoutedRowAdapter]
|
|
private let prefetchedRows: Row.PrefetchedRowsView
|
|
|
|
/// The available scopes in this row.
|
|
public var names: Dictionary<String, any _LayoutedRowAdapter>.Keys {
|
|
scopes.keys
|
|
}
|
|
|
|
init() {
|
|
self.init(row: Row(), scopes: [:], prefetchedRows: Row.PrefetchedRowsView())
|
|
}
|
|
|
|
init(row: Row, scopes: [String: any _LayoutedRowAdapter], prefetchedRows: Row.PrefetchedRowsView) {
|
|
self.row = row
|
|
self.scopes = scopes
|
|
self.prefetchedRows = prefetchedRows
|
|
}
|
|
|
|
/// The row associated with the given scope, or nil if the scope is
|
|
/// not available.
|
|
public subscript(_ name: String) -> Row? {
|
|
scopes.index(forKey: name).map { self[$0].row }
|
|
}
|
|
}
|
|
}
|
|
|
|
extension Row.ScopesView: Collection {
|
|
public typealias Index = Dictionary<String, any _LayoutedRowAdapter>.Index
|
|
|
|
public var startIndex: Index {
|
|
scopes.startIndex
|
|
}
|
|
|
|
public var endIndex: Index {
|
|
scopes.endIndex
|
|
}
|
|
|
|
public func index(after i: Index) -> Index {
|
|
scopes.index(after: i)
|
|
}
|
|
|
|
public subscript(position: Index) -> (name: String, row: Row) {
|
|
let (name, adapter) = scopes[position]
|
|
let adaptedRow = Row(base: row, adapter: adapter)
|
|
if let prefetch = prefetchedRows.prefetches[name] {
|
|
// Let the adapted row access its own prefetched rows.
|
|
// Use case:
|
|
//
|
|
// let request = A.including(required: A.b.including(all: B.c))
|
|
// let row = try Row.fetchOne(db, request)!
|
|
// row.prefetchedRows["cs"] // Some array
|
|
// row.scopes["b"]!.prefetchedRows["cs"] // The same array
|
|
adaptedRow.prefetchedRows = Row.PrefetchedRowsView(prefetches: prefetch.prefetches)
|
|
}
|
|
return (name: name, row: adaptedRow)
|
|
}
|
|
}
|
|
|
|
// MARK: - Row.ScopesTreeView
|
|
|
|
extension Row {
|
|
|
|
/// A view on the scopes tree defined by row adapters.
|
|
///
|
|
/// See ``Row/scopesTree`` for more information.
|
|
public struct ScopesTreeView {
|
|
let scopes: ScopesView
|
|
|
|
/// The scopes available on this row, recursively.
|
|
public var names: Set<String> {
|
|
var names = Set<String>()
|
|
for (name, row) in scopes {
|
|
names.insert(name)
|
|
names.formUnion(row.scopesTree.names)
|
|
}
|
|
return names
|
|
}
|
|
|
|
/// The row associated with the given scope, or nil if the scope is
|
|
/// not available.
|
|
///
|
|
/// See ``Row/scopesTree`` for more information.
|
|
///
|
|
/// - parameter key: An association key.
|
|
public subscript(_ name: String) -> Row? {
|
|
var fifo = Array(scopes)
|
|
while !fifo.isEmpty {
|
|
let scope = fifo.removeFirst()
|
|
if scope.name == name {
|
|
return scope.row
|
|
}
|
|
fifo.append(contentsOf: scope.row.scopes)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Row.PrefetchedRowsView
|
|
|
|
extension Row {
|
|
fileprivate struct Prefetch: Equatable {
|
|
// Nil for intermediate associations
|
|
var rows: [Row]?
|
|
// OrderedDictionary so that breadth-first search gives a consistent result
|
|
// (we preserve the ordering of associations in the request)
|
|
var prefetches: OrderedDictionary<String, Prefetch>
|
|
}
|
|
|
|
/// A view on the prefetched associated rows.
|
|
///
|
|
/// See ``Row/prefetchedRows`` for more information.
|
|
public struct PrefetchedRowsView: Equatable {
|
|
// OrderedDictionary so that breadth-first search gives a consistent result
|
|
// (we preserve the ordering of associations in the request)
|
|
fileprivate var prefetches: OrderedDictionary<String, Prefetch> = [:]
|
|
|
|
/// A boolean value indicating if there is no prefetched
|
|
/// associated rows.
|
|
public var isEmpty: Bool {
|
|
prefetches.isEmpty
|
|
}
|
|
|
|
/// The available association keys.
|
|
///
|
|
/// Keys in the returned set can be used with ``subscript(_:)``.
|
|
///
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// struct Author: TableRecord {
|
|
/// static let books = hasMany(Book.self)
|
|
/// }
|
|
///
|
|
/// struct Book: TableRecord { }
|
|
///
|
|
/// let request = Author.including(all: Author.books)
|
|
/// let authorRow = try Row.fetchOne(db, request)!
|
|
///
|
|
/// print(authorRow.prefetchedRows.keys)
|
|
/// // Prints ["books"]
|
|
///
|
|
/// let bookRows = authorRow.prefetchedRows["books"]!
|
|
/// print(bookRows[0])
|
|
/// // Prints [id:42, title:"Moby-Dick", authorId:1]
|
|
/// print(bookRows[1])
|
|
/// // Prints [id:57, title:"Pierre", authorId:1]
|
|
/// ```
|
|
public var keys: Set<String> {
|
|
var result: Set<String> = []
|
|
var fifo = Array(prefetches)
|
|
while !fifo.isEmpty {
|
|
let (prefetchKey, prefetch) = fifo.removeFirst()
|
|
if prefetch.rows != nil {
|
|
result.insert(prefetchKey)
|
|
}
|
|
fifo.append(contentsOf: prefetch.prefetches)
|
|
}
|
|
return result
|
|
}
|
|
|
|
/// The prefetched rows associated with the given association key.
|
|
///
|
|
/// The result is nil if the key is not available.
|
|
///
|
|
/// See ``Row/prefetchedRows`` for more information.
|
|
///
|
|
/// - parameter key: An association key.
|
|
public subscript(_ key: String) -> [Row]? {
|
|
var fifo = Array(prefetches)
|
|
while !fifo.isEmpty {
|
|
let (prefetchKey, prefetch) = fifo.removeFirst()
|
|
if prefetchKey == key,
|
|
let rows = prefetch.rows // nil for "through" associations
|
|
{
|
|
return rows
|
|
}
|
|
fifo.append(contentsOf: prefetch.prefetches)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
mutating func setRows(_ rows: [Row], forKeyPath keyPath: [String]) {
|
|
prefetches.setRows(rows, forKeyPath: keyPath)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension OrderedDictionary<String, Row.Prefetch> {
|
|
fileprivate mutating func setRows(_ rows: [Row], forKeyPath keyPath: [String]) {
|
|
var keyPath = keyPath
|
|
let key = keyPath.removeFirst()
|
|
if keyPath.isEmpty {
|
|
self[key, default: Row.Prefetch(rows: nil, prefetches: [:])].rows = rows
|
|
} else {
|
|
self[key, default: Row.Prefetch(rows: nil, prefetches: [:])].prefetches.setRows(rows, forKeyPath: keyPath)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - RowImpl
|
|
|
|
// The protocol for Row underlying implementation
|
|
protocol RowImpl {
|
|
var count: Int { get }
|
|
var isFetched: Bool { get }
|
|
|
|
func scopes(prefetchedRows: Row.PrefetchedRowsView) -> Row.ScopesView
|
|
func columnName(atUncheckedIndex index: Int) -> String
|
|
func hasNull(atUncheckedIndex index: Int) -> Bool
|
|
func databaseValue(atUncheckedIndex index: Int) -> DatabaseValue
|
|
|
|
func fastDecode<Value: DatabaseValueConvertible & StatementColumnConvertible>(
|
|
_ type: Value.Type,
|
|
atUncheckedIndex index: Int)
|
|
throws -> Value
|
|
|
|
func withUnsafeData<T>(atUncheckedIndex index: Int, _ body: (Data?) throws -> T) throws -> T
|
|
|
|
/// Returns the index of the leftmost column that matches *name* (case-insensitive)
|
|
func index(forColumn name: String) -> Int?
|
|
|
|
// row.impl is guaranteed to be self.
|
|
func unscopedRow(_ row: Row) -> Row
|
|
func unadaptedRow(_ row: Row) -> Row
|
|
func copiedRow(_ row: Row) -> Row
|
|
}
|
|
|
|
extension RowImpl {
|
|
func copiedRow(_ row: Row) -> Row {
|
|
// unless customized, assume unsafe and unadapted row
|
|
Row(impl: ArrayRowImpl(columns: Array(row)))
|
|
}
|
|
|
|
func unscopedRow(_ row: Row) -> Row {
|
|
// unless customized, assume unadapted row (see AdaptedRowImpl for customization)
|
|
row
|
|
}
|
|
|
|
func unadaptedRow(_ row: Row) -> Row {
|
|
// unless customized, assume unadapted row (see AdaptedRowImpl for customization)
|
|
row
|
|
}
|
|
|
|
func scopes(prefetchedRows: Row.PrefetchedRowsView) -> Row.ScopesView {
|
|
// unless customized, assume unuscoped row (see AdaptedRowImpl for customization)
|
|
Row.ScopesView()
|
|
}
|
|
|
|
func hasNull(atUncheckedIndex index: Int) -> Bool {
|
|
// unless customized, use slow check (see StatementRowImpl and AdaptedRowImpl for customization)
|
|
databaseValue(atUncheckedIndex: index).isNull
|
|
}
|
|
|
|
func fastDecode<Value: DatabaseValueConvertible & StatementColumnConvertible>(
|
|
_ type: Value.Type,
|
|
atUncheckedIndex index: Int)
|
|
throws -> Value
|
|
{
|
|
// unless customized, use slow decoding (see StatementRowImpl and AdaptedRowImpl for customization)
|
|
try Value.decode(
|
|
fromDatabaseValue: databaseValue(atUncheckedIndex: index),
|
|
context: RowDecodingContext(row: Row(impl: self), key: .columnIndex(index)))
|
|
}
|
|
|
|
func withUnsafeData<T>(atUncheckedIndex index: Int, _ body: (Data?) throws -> T) throws -> T {
|
|
// unless customized, copy data (see StatementRowImpl and AdaptedRowImpl for customization)
|
|
let data = try Optional<Data>.decode(
|
|
fromDatabaseValue: databaseValue(atUncheckedIndex: index),
|
|
context: RowDecodingContext(row: Row(impl: self), key: .columnIndex(index)))
|
|
return try body(data)
|
|
}
|
|
}
|
|
|
|
// TODO: merge with StatementCopyRowImpl eventually?
|
|
/// See Row.init(dictionary:)
|
|
struct ArrayRowImpl: RowImpl {
|
|
let columns: [(String, DatabaseValue)]
|
|
|
|
init<Columns>(columns: Columns)
|
|
where Columns: Collection, Columns.Element == (String, DatabaseValue)
|
|
{
|
|
self.columns = Array(columns)
|
|
}
|
|
|
|
var count: Int { columns.count }
|
|
|
|
var isFetched: Bool { false }
|
|
|
|
func databaseValue(atUncheckedIndex index: Int) -> DatabaseValue {
|
|
columns[index].1
|
|
}
|
|
|
|
func columnName(atUncheckedIndex index: Int) -> String {
|
|
columns[index].0
|
|
}
|
|
|
|
func index(forColumn name: String) -> Int? {
|
|
let lowercaseName = name.lowercased()
|
|
return columns.firstIndex { (column, _) in column.lowercased() == lowercaseName }
|
|
}
|
|
|
|
func copiedRow(_ row: Row) -> Row {
|
|
row
|
|
}
|
|
}
|
|
|
|
extension ArrayRowImpl: Sendable { }
|
|
|
|
// TODO: merge with ArrayRowImpl eventually?
|
|
/// See Row.init(copiedFromStatementRef:sqliteStatement:)
|
|
private struct StatementCopyRowImpl: RowImpl {
|
|
let dbValues: ContiguousArray<DatabaseValue>
|
|
let columnNames: [String]
|
|
|
|
init(sqliteStatement: SQLiteStatement, columnNames: [String]) {
|
|
let sqliteStatement = sqliteStatement
|
|
self.dbValues = ContiguousArray(
|
|
(0..<sqlite3_column_count(sqliteStatement))
|
|
.map { DatabaseValue(sqliteStatement: sqliteStatement, index: $0) }
|
|
as [DatabaseValue])
|
|
self.columnNames = columnNames
|
|
}
|
|
|
|
var count: Int { columnNames.count }
|
|
|
|
var isFetched: Bool { true }
|
|
|
|
func databaseValue(atUncheckedIndex index: Int) -> DatabaseValue {
|
|
dbValues[index]
|
|
}
|
|
|
|
func columnName(atUncheckedIndex index: Int) -> String {
|
|
columnNames[index]
|
|
}
|
|
|
|
func index(forColumn name: String) -> Int? {
|
|
let lowercaseName = name.lowercased()
|
|
return columnNames.firstIndex { $0.lowercased() == lowercaseName }
|
|
}
|
|
|
|
func copiedRow(_ row: Row) -> Row {
|
|
row
|
|
}
|
|
}
|
|
|
|
/// See Row.init(statement:)
|
|
private struct StatementRowImpl: RowImpl {
|
|
let statement: Statement
|
|
let sqliteStatement: SQLiteStatement
|
|
let lowercaseColumnIndexes: [String: Int]
|
|
|
|
init(sqliteStatement: SQLiteStatement, statement: Statement) {
|
|
self.statement = statement
|
|
self.sqliteStatement = sqliteStatement
|
|
// Optimize row[columnName]
|
|
let lowercaseColumnNames = (0..<sqlite3_column_count(sqliteStatement))
|
|
.map { String(cString: sqlite3_column_name(sqliteStatement, CInt($0))).lowercased() }
|
|
self.lowercaseColumnIndexes = Dictionary(
|
|
lowercaseColumnNames
|
|
.enumerated()
|
|
.map { ($0.element, $0.offset) },
|
|
uniquingKeysWith: { (left, _) in left }) // keep leftmost indexes
|
|
}
|
|
|
|
var count: Int {
|
|
Int(sqlite3_column_count(sqliteStatement))
|
|
}
|
|
|
|
var isFetched: Bool { true }
|
|
|
|
func hasNull(atUncheckedIndex index: Int) -> Bool {
|
|
// Avoid extracting values, because this modifies the SQLite statement.
|
|
sqlite3_column_type(sqliteStatement, CInt(index)) == SQLITE_NULL
|
|
}
|
|
|
|
func databaseValue(atUncheckedIndex index: Int) -> DatabaseValue {
|
|
DatabaseValue(sqliteStatement: sqliteStatement, index: CInt(index))
|
|
}
|
|
|
|
func fastDecode<Value: DatabaseValueConvertible & StatementColumnConvertible>(
|
|
_ type: Value.Type,
|
|
atUncheckedIndex index: Int)
|
|
throws -> Value
|
|
{
|
|
try Value.fastDecode(
|
|
fromStatement: sqliteStatement,
|
|
atUncheckedIndex: CInt(index),
|
|
context: RowDecodingContext(statement: statement, index: index))
|
|
}
|
|
|
|
func withUnsafeData<T>(atUncheckedIndex index: Int, _ body: (Data?) throws -> T) throws -> T {
|
|
guard sqlite3_column_type(sqliteStatement, CInt(index)) != SQLITE_NULL else {
|
|
return try body(nil)
|
|
}
|
|
guard let bytes = sqlite3_column_blob(sqliteStatement, CInt(index)) else {
|
|
return try body(Data())
|
|
}
|
|
|
|
let count = Int(sqlite3_column_bytes(sqliteStatement, CInt(index)))
|
|
let data = Data(bytesNoCopy: UnsafeMutableRawPointer(mutating: bytes), count: count, deallocator: .none)
|
|
return try body(data)
|
|
}
|
|
|
|
func columnName(atUncheckedIndex index: Int) -> String {
|
|
statement.columnNames[index]
|
|
}
|
|
|
|
func index(forColumn name: String) -> Int? {
|
|
if let index = lowercaseColumnIndexes[name] {
|
|
return index
|
|
}
|
|
return lowercaseColumnIndexes[name.lowercased()]
|
|
}
|
|
|
|
func copiedRow(_ row: Row) -> Row {
|
|
Row(copiedFromSQLiteStatement: sqliteStatement, statement: statement)
|
|
}
|
|
}
|
|
|
|
// This one is not optimized at all, since it is only used in fatal conversion errors, so far
|
|
private struct SQLiteStatementRowImpl: RowImpl {
|
|
let sqliteStatement: SQLiteStatement
|
|
var count: Int { Int(sqlite3_column_count(sqliteStatement)) }
|
|
var isFetched: Bool { true }
|
|
|
|
func columnName(atUncheckedIndex index: Int) -> String {
|
|
String(cString: sqlite3_column_name(sqliteStatement, CInt(index)))
|
|
}
|
|
|
|
func databaseValue(atUncheckedIndex index: Int) -> DatabaseValue {
|
|
DatabaseValue(sqliteStatement: sqliteStatement, index: CInt(index))
|
|
}
|
|
|
|
func index(forColumn name: String) -> Int? {
|
|
let name = name.lowercased()
|
|
for index in 0..<count where columnName(atUncheckedIndex: index).lowercased() == name {
|
|
return index
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
/// See Row.init()
|
|
private struct EmptyRowImpl: RowImpl {
|
|
var count: Int { 0 }
|
|
|
|
var isFetched: Bool { false }
|
|
|
|
func databaseValue(atUncheckedIndex index: Int) -> DatabaseValue {
|
|
// Programmer error
|
|
fatalError("row index out of range")
|
|
}
|
|
|
|
func columnName(atUncheckedIndex index: Int) -> String {
|
|
// Programmer error
|
|
fatalError("row index out of range")
|
|
}
|
|
|
|
func index(forColumn name: String) -> Int? { nil }
|
|
|
|
func copiedRow(_ row: Row) -> Row { row }
|
|
}
|