Files
UUVPN/iOS-SwiftUI-Code/GRDB.swift-6.29.3/GRDB/Core/RowAdapter.swift
T
2025-01-22 14:09:10 +08:00

752 lines
26 KiB
Swift

import Foundation
/// Returns an array of row adapters that split a row according to the
/// provided numbers of columns.
///
/// This method is useful for splitting a row into chunks.
///
/// For example, let's consider the following SQL query:
///
/// ```swift
/// let sql = """
/// SELECT player.*, team.*
/// FROM player
/// LEFT JOIN team ON team.id = player.teamId
/// WHERE player.id = ?
/// """
/// ```
///
/// The resulting rows contains columns from both player and team tables:
///
/// ```swift
/// // [id: 1, name: "Arthur", teamId: 42, id: 42, name: "Reds"]
/// // <---------------------------------><-------------------->
/// // player columns team columns
/// let row = try Row.fetchOne(db, sql: sql, arguments: [1])
/// ```
///
/// Because some columns have the same name (`id` and `name`), it is
/// difficult to access the team columns.
///
/// `splittingRowAdapters` and ``ScopeAdapter`` make it possible to
/// access player and team columns independently, with row ``Row/scopes``:
///
/// ```swift
/// let adapters = try splittingRowAdapters([
/// db.columns(in: "player").count,
/// db.columns(in: "team").count,
/// ])
/// let adapter = ScopeAdapter([
/// "player": adapters[0],
/// "team": adapters[1],
/// ])
/// if let row = try Row.fetchOne(db, sql: sql, arguments: [1], adapter: adapter) {
/// // A Row that only contains player columns
/// // [id: 1, name: "Arthur", teamId: 42]
/// row.scopes["player"]
///
/// // A Row that only contains team columns
/// // [id: 42, name: "Reds"]
/// row.scopes["team"]
/// }
/// ```
///
/// Decoding ``FetchableRecord`` types is easy:
///
/// ```swift
/// if let row = try Row.fetchOne(db, sql: sql, arguments: [1], adapter: adapter) {
/// // Player(id: 1, name: "Arthur", teamId: 42)
/// let player: Player = row["player"]
///
/// // Team(id: 42, name: "Reds")
/// // nil if the LEFT JOIN has fetched NULL team columns
/// if let team: Team? = row["team"]
/// }
/// ```
///
/// You can package this technique in a dedicated type, as in the next
/// example. It enhances the previous sample codes with:
///
/// - Support for record types that customize their fetched columns
/// with ``TableRecord/databaseSelection-7iphs``.
/// - ``SQLRequest`` and its support for [SQL Interpolation](https://github.com/groue/GRDB.swift/blob/master/Documentation/SQLInterpolation.md).
/// - ``FetchRequest/adapted(_:)`` for building a request that embeds the
/// row adapters.
///
/// ```swift
/// struct Player: TableRecord, FetchableRecord { ... }
/// struct Team: TableRecord, FetchableRecord { ... }
///
/// struct PlayerInfo {
/// var player: Player
/// var team: Team?
/// }
///
/// extension PlayerInfo: FetchableRecord {
/// init(row: Row) {
/// player = row["player"]
/// team = row["team"]
/// }
/// }
///
/// extension PlayerInfo {
/// /// The request for the player info, given a player id
/// static func filter(playerId: Int64) -> some FetchRequest<PlayerInfo> {
/// // Build SQL request with SQL interpolation
/// let request: SQLRequest<PlayerInfo> = """
/// SELECT
/// \(columnsOf: Player.self), -- Instead of player.*
/// \(columnsOf: Team.self), -- Instead of team.*
/// FROM player
/// LEFT JOIN team ON team.id = player.teamId
/// WHERE player.id = \(playerId)
/// """
///
/// // Returns an adapted request that defines the player and team
/// // scopes in the fetched row
/// return request.adapted { db in
/// let adapters = try splittingRowAdapters(columnCounts: [
/// Player.numberOfSelectedColumns(db),
/// Team.numberOfSelectedColumns(db),
/// ])
/// return ScopeAdapter([
/// "player": adapters[0],
/// "team": adapters[1],
/// ])
/// }
/// }
/// }
///
/// // Usage
/// try dbQueue.read { db in
/// if let playerInfo = try PlayerInfo.filter(playerId: 1).fetchOne(db) {
/// print(playerInfo.player) // Player(id: 1, name: "Arthur", teamId: 42)
/// print(playerInfo.team) // Team(id: 42, name: "Reds")
/// }
/// }
/// ```
///
/// - parameter columnCounts: An array of row chunk lengths.
/// - returns: An array of row adapters that split a row into as many chunks
/// as the number of elements in `columnCounts`, plus one (the row adapter
/// for all columns that remain on the right of the last chunk).
public func splittingRowAdapters(columnCounts: [Int]) -> [any RowAdapter] {
if columnCounts.isEmpty {
// Identity adapter
return [SuffixRowAdapter(fromIndex: 0)]
}
// [1, 3, 2] -> [0, 1, 4, 6]
let columnIndexes = columnCounts.reduce(into: [0]) { (acc, count) in
acc.append(acc.last! + count)
}
// [0, 1, 4, 6] -> [(0..<1), (1..<4), (4..<6)]
let rangeAdapters = zip(columnIndexes, columnIndexes.suffix(from: 1))
.map { RangeRowAdapter($0..<$1) }
// (6...)
let suffixAdapter = SuffixRowAdapter(fromIndex: columnIndexes.last!)
// [(0..<1), (1..<4), (4..<6), (6...)]
return rangeAdapters + [suffixAdapter]
}
/// _LayoutedColumnMapping is a type that supports the RowAdapter protocol.
public struct _LayoutedColumnMapping {
/// An array of (baseIndex, mappedName) pairs, where baseIndex is the index
/// of a column in a base row, and mappedName the mapped name of
/// that column.
public let _layoutColumns: [(Int, String)]
/// A cache for layoutIndex(ofColumn:)
let lowercaseColumnIndexes: [String: Int] // [mappedColumn: layoutColumnIndex]
/// Creates a _LayoutedColumnMapping from an array of (baseIndex, mappedName)
/// pairs. In each pair:
///
/// - baseIndex is the index of a column in a base row
/// - name is the mapped name of the column
///
/// For example, the following _LayoutedColumnMapping defines two columns, "foo"
/// and "bar", based on the base columns at indexes 1 and 2:
///
/// _LayoutedColumnMapping(layoutColumns: [(1, "foo"), (2, "bar")])
///
/// Use it in your custom RowAdapter type:
///
/// struct FooBarAdapter : RowAdapter {
/// func layoutAdapter(layout: _RowLayout) throws -> any _LayoutedRowAdapter {
/// return _LayoutedColumnMapping(layoutColumns: [(1, "foo"), (2, "bar")])
/// }
/// }
///
/// // [foo:"foo" bar: "bar"]
/// try Row.fetchOne(db, sql: "SELECT NULL, 'foo', 'bar'", adapter: FooBarAdapter())
init<S>(layoutColumns: S)
where S: Sequence, S.Element == (Int, String)
{
self._layoutColumns = Array(layoutColumns)
self.lowercaseColumnIndexes = Dictionary(
layoutColumns
.enumerated()
.map { ($0.element.1.lowercased(), $0.offset) },
uniquingKeysWith: { (left, _) in left }) // keep leftmost indexes
}
func baseColumnIndex(atMappingIndex index: Int) -> Int {
_layoutColumns[index].0
}
func columnName(atMappingIndex index: Int) -> String {
_layoutColumns[index].1
}
}
extension _LayoutedColumnMapping: _LayoutedRowAdapter {
/// Returns self.
public var _mapping: _LayoutedColumnMapping { self }
/// Returns the empty dictionary.
public var _scopes: [String: any _LayoutedRowAdapter] { [:] }
}
extension _LayoutedColumnMapping: _RowLayout {
/// Returns the index of the leftmost column named `name`, in a
/// case-insensitive way.
public func _layoutIndex(ofColumn name: String) -> Int? {
if let index = lowercaseColumnIndexes[name] {
return index
}
return lowercaseColumnIndexes[name.lowercased()]
}
}
/// `_LayoutedRowAdapter` is a protocol that supports the `RowAdapter` protocol.
///
/// GRBD ships with a ready-made type that adopts this protocol:
/// `_LayoutedColumnMapping`.
public protocol _LayoutedRowAdapter {
/// A LayoutedColumnMapping that defines how to map a column name to a
/// column in a base row.
var _mapping: _LayoutedColumnMapping { get }
/// The layouted row adapters for each scope.
var _scopes: [String: any _LayoutedRowAdapter] { get }
}
/// `_RowLayout` is a protocol that supports the `RowAdapter` protocol. It
/// describes the layout of a base row.
public protocol _RowLayout {
/// An array of (baseIndex, name) pairs, where baseIndex is the index
/// of a column in a base row, and name the name of that column.
var _layoutColumns: [(Int, String)] { get }
/// Returns the index of the leftmost column named `name`, in a
/// case-insensitive way.
func _layoutIndex(ofColumn name: String) -> Int?
}
extension Statement: _RowLayout {
public var _layoutColumns: [(Int, String)] {
Array(columnNames.enumerated())
}
public func _layoutIndex(ofColumn name: String) -> Int? {
index(ofColumn: name)
}
}
/// A type that helps two incompatible row interfaces working together.
///
/// Row adapters present database rows in the way expected by the
/// row consumers.
///
/// For example, when a row consumer expects a column named "consumed", but
/// the raw row has a column named "produced", the ``ColumnMapping`` row
/// adapter comes in handy:
///
/// ```swift
/// // Feeds the "consumed" column from "produced":
/// let adapter = ColumnMapping(["consumed": "produced"])
/// let sql = "SELECT 'Hello' AS produced"
/// let row = try Row.fetchOne(db, sql: sql, adapter: adapter)!
///
/// // [consumed:"Hello"]
/// print(row)
///
/// // "Hello"
/// print(row["consumed"])
/// ```
///
/// The raw fetched columns are not lost (see ``Row/unadapted``):
///
/// ```swift
/// // ▿ [consumed:"Hello"]
/// // unadapted: [produced:"Hello"]
/// print(row.debugDescription)
///
/// // [produced:"Hello"]
/// print(row.unadapted)
/// ```
///
/// There are several situations where row adapters are useful. Among them:
///
/// - Adapters help disambiguate columns with identical names, which may
/// happen when you select columns from several tables.
/// See ``splittingRowAdapters(columnCounts:)`` for some sample code.
///
/// - Adapters help when SQLite outputs unexpected column names, which may
/// happen with some subqueries. See ``RenameColumnAdapter`` for
/// an example.
///
/// ## Topics
///
/// ### Splitting a Row into Chunks
///
/// - ``splittingRowAdapters(columnCounts:)``
///
/// ### Adding Scopes to an Adapter
///
/// - ``addingScopes(_:)``
///
/// ### Built-in Adapters
///
/// - ``ColumnMapping``
/// - ``EmptyRowAdapter``
/// - ``RangeRowAdapter``
/// - ``RenameColumnAdapter``
/// - ``ScopeAdapter``
/// - ``SuffixRowAdapter``
public protocol RowAdapter {
/// You never call this method directly. It is called for you whenever an
/// adapter has to be applied.
///
/// The result is a value that adopts _LayoutedRowAdapter, such as
/// _LayoutedColumnMapping.
///
/// For example:
///
/// // An adapter that turns any row to a row that contains a single
/// // column named "foo" whose value is the leftmost value of the
/// // base row.
/// struct FirstColumnAdapter : RowAdapter {
/// func _layoutedAdapter(from layout: some _RowLayout) throws -> any _LayoutedRowAdapter {
/// return _LayoutedColumnMapping(layoutColumns: [(0, "foo")])
/// }
/// }
///
/// // [foo:1]
/// try Row.fetchOne(db, sql: "SELECT 1, 2, 3", adapter: FirstColumnAdapter())
func _layoutedAdapter(from layout: some _RowLayout) throws -> any _LayoutedRowAdapter
}
extension RowAdapter {
/// Returns an adapter based on self, with added scopes.
///
/// If self already defines scopes, the added scopes replace
/// eventual existing scopes with the same name.
///
/// - parameter scopes: A dictionary that maps scope names to
/// row adapters.
public func addingScopes(_ scopes: [String: any RowAdapter]) -> any RowAdapter {
if scopes.isEmpty {
return self
} else {
return ScopeAdapter(base: self, scopes: scopes)
}
}
}
extension RowAdapter {
func baseColumnIndex(atIndex index: Int, layout: some _RowLayout) throws -> Int {
try _layoutedAdapter(from: layout)._mapping.baseColumnIndex(atMappingIndex: index)
}
}
/// `EmptyRowAdapter` is a row adapter that hides all columns.
///
/// For example:
///
/// ```swift
/// let adapter = EmptyRowAdapter()
/// let sql = "SELECT 0 AS a, 1 AS b, 2 AS c"
///
/// let row = try Row.fetchOne(db, sql: sql, adapter: adapter)!
/// row.isEmpty // true
/// ```
///
/// This limit adapter may turn out useful in some narrow use cases. You'll
/// be happy to find it when you need it.
public struct EmptyRowAdapter: RowAdapter, Sendable {
/// Creates an `EmptyRowAdapter`.
public init() { }
public func _layoutedAdapter(from layout: some _RowLayout) throws -> any _LayoutedRowAdapter {
_LayoutedColumnMapping(layoutColumns: [])
}
}
/// `ColumnMapping` is a row adapter that maps column names.
///
/// Build a `ColumnMapping` with a dictionary whose keys
/// are adapted column names, and values the column names in the base row:
///
/// ```swift
/// // Feeds "newA" from "a", and "newB" from "b":
/// let adapter = ColumnMapping(["newA": "a", "newB": "b"])
/// let sql = "SELECT 0 AS a, 1 AS b, 2 AS c"
///
/// // [newA:0, newB:1]
/// let row = try Row.fetchOne(db, sql: sql, adapter: adapter)!
/// ```
///
/// Note that columns that are not present in the dictionary are not present
/// in the resulting adapted row.
public struct ColumnMapping: RowAdapter, Sendable {
/// A dictionary from mapped column names to column names in a base row.
let mapping: [String: String]
/// Creates a `ColumnMapping` with a dictionary from mapped column names
/// to column names in a base row.
public init(_ mapping: [String: String]) {
self.mapping = mapping
}
public func _layoutedAdapter(from layout: some _RowLayout) throws -> any _LayoutedRowAdapter {
let layoutColumns = try mapping
.map { (mappedColumn, baseColumn) -> (Int, String) in
guard let index = layout._layoutIndex(ofColumn: baseColumn) else {
let columnNames = layout._layoutColumns.map { $0.1 }
throw DatabaseError(
resultCode: .SQLITE_MISUSE,
message: """
Mapping references missing column \(baseColumn). \
Valid column names are: \(columnNames.joined(separator: ", ")).
""")
}
let baseIndex = layout._layoutColumns[index].0
return (baseIndex, mappedColumn)
}
.sorted { $0.0 < $1.0 } // preserve ordering of base columns
return _LayoutedColumnMapping(layoutColumns: layoutColumns)
}
}
/// `SuffixRowAdapter` hides the leftmost columns in a row.
///
/// For example:
///
/// ```swift
/// let adapter = SuffixRowAdapter(fromIndex: 2)
/// let sql = "SELECT 0 AS a, 1 AS b, 2 AS c, 3 AS d"
///
/// // [c:2, d: 3]
/// try Row.fetchOne(db, sql: sql, adapter: adapter)!
/// ```
public struct SuffixRowAdapter: RowAdapter, Sendable {
/// The suffix index
let index: Int
/// Creates a SuffixRowAdapter that hides all columns before the
/// provided index.
///
/// If index is 0, the layout row is identical to the base row.
public init(fromIndex index: Int) {
GRDBPrecondition(index >= 0, "Negative column index is out of range")
self.index = index
}
public func _layoutedAdapter(from layout: some _RowLayout) throws -> any _LayoutedRowAdapter {
_LayoutedColumnMapping(layoutColumns: layout._layoutColumns.suffix(from: index))
}
}
/// `RangeRowAdapter` is a row adapter that only exposes a range of columns.
///
/// For example:
///
/// ```swift
/// let adapter = RangeRowAdapter(1..<3)
/// let sql = "SELECT 0 AS a, 1 AS b, 2 AS c, 3 AS d"
///
/// // [b:1 c:2]
/// try Row.fetchOne(db, sql: sql, adapter: adapter)
/// ```
public struct RangeRowAdapter: RowAdapter, Sendable {
/// The range
let range: CountableRange<Int>
/// Creates a RangeRowAdapter that only exposes a range of columns.
public init(_ range: CountableRange<Int>) {
GRDBPrecondition(range.lowerBound >= 0, "Negative column index is out of range")
self.range = range
}
/// Creates a RangeRowAdapter that only exposes a range of columns.
public init(_ range: CountableClosedRange<Int>) {
GRDBPrecondition(range.lowerBound >= 0, "Negative column index is out of range")
self.range = range.lowerBound..<(range.upperBound + 1)
}
public func _layoutedAdapter(from layout: some _RowLayout) throws -> any _LayoutedRowAdapter {
_LayoutedColumnMapping(layoutColumns: layout._layoutColumns[range])
}
}
/// `ScopeAdapter` is a row adapter that defines row scopes.
///
/// `ScopeAdapter` does not change the columns and values of the fetched
/// row. Instead, it defines *scopes* based on other adapter, which you
/// access through the ``Row/scopes`` property of the fetched rows.
///
/// For example:
///
/// ```swift
/// let adapter = ScopeAdapter([
/// "left": RangeRowAdapter(0..<2),
/// "right": RangeRowAdapter(2..<4)])
/// let sql = "SELECT 0 AS a, 1 AS b, 2 AS c, 3 AS d"
///
/// let row = try Row.fetchOne(db, sql: sql, adapter: adapter)!
///
/// row // [a:0 b:1 c:2 d:3]
/// row.scopes["left"] // [a:0 b:1]
/// row.scopes["right"] // [c:2 d:3]
/// row.scopes["missing"] // nil
/// ```
///
/// Scopes can be nested:
///
/// ```swift
/// let adapter = ScopeAdapter([
/// "left": ScopeAdapter([
/// "left": RangeRowAdapter(0..<1),
/// "right": RangeRowAdapter(1..<2)]),
/// "right": ScopeAdapter([
/// "left": RangeRowAdapter(2..<3),
/// "right": RangeRowAdapter(3..<4)])
/// ])
/// let sql = "SELECT 0 AS a, 1 AS b, 2 AS c, 3 AS d"
/// let row = try Row.fetchOne(db, sql: sql, adapter: adapter)!
///
/// let leftRow = row.scopes["left"]!
/// leftRow.scopes["left"] // [a:0]
/// leftRow.scopes["right"] // [b:1]
///
/// let rightRow = row.scopes["right"]!
/// rightRow.scopes["left"] // [c:2]
/// rightRow.scopes["right"] // [d:3]
/// ```
///
/// Any adapter can be extended with scopes, with
/// ``RowAdapter/addingScopes(_:)``:
///
/// ```swift
/// let baseAdapter = RangeRowAdapter(0..<2)
/// let adapter = baseAdapter.addingScopes([
/// "remainder": SuffixRowAdapter(fromIndex: 2)
/// ])
/// let sql = "SELECT 0 AS a, 1 AS b, 2 AS c, 3 AS d"
/// let row = try Row.fetchOne(db, sql: sql, adapter: adapter)!
///
/// row // [a:0 b:1]
/// row.scopes["remainder"] // [c:2 d:3]
/// ```
public struct ScopeAdapter: RowAdapter {
/// The base adapter
let base: any RowAdapter
/// The scope adapters
let scopes: [String: any RowAdapter]
/// Creates an adapter that preserves row contents and add scoped rows.
///
/// For example:
///
/// let adapter = ScopeAdapter(["suffix": SuffixRowAdapter(fromIndex: 1)])
/// let row = try Row.fetchOne(db, sql: "SELECT 1, 2, 3", adapter: adapter)!
/// row // [1, 2, 3]
/// row.scopes["suffix"] // [2, 3]
///
/// - parameter scopes: A dictionary that maps scope names to
/// row adapters.
public init(_ scopes: [String: any RowAdapter]) {
// Use SuffixRowAdapter(fromIndex: 0) as the identity adapter
self.init(base: SuffixRowAdapter(fromIndex: 0), scopes: scopes)
}
/// Creates an adapter based on the base adapter, and add scoped rows.
///
/// For example:
///
/// let baseAdapter = RangeRowAdapter(0..<1)
/// let adapter = ScopeAdapter(base: baseAdapter, scopes: ["suffix": SuffixRowAdapter(fromIndex: 1)])
/// let row = try Row.fetchOne(db, sql: "SELECT 1, 2, 3", adapter: adapter)!
/// row // [1]
/// row.scopes["initial"] // [2, 3]
///
/// If the base adapter already defines scopes, the given scopes replace
/// eventual existing scopes with the same name.
///
/// This initializer is equivalent to `baseAdapter.addingScopes(scopes)`.
///
/// - parameter base: A dictionary that maps scope names to
/// row adapters.
/// - parameter scopes: A dictionary that maps scope names to
/// row adapters.
public init(base: some RowAdapter, scopes: [String: any RowAdapter]) {
self.base = base
self.scopes = scopes
}
public func _layoutedAdapter(from layout: some _RowLayout) throws -> any _LayoutedRowAdapter {
let layoutedAdapter = try base._layoutedAdapter(from: layout)
var layoutedScopes = layoutedAdapter._scopes
for (name, adapter) in scopes {
try layoutedScopes[name] = adapter._layoutedAdapter(from: layout)
}
return LayoutedScopeAdapter(
_mapping: layoutedAdapter._mapping,
_scopes: layoutedScopes)
}
}
/// The `_LayoutedRowAdapter` for `ScopeAdapter`
struct LayoutedScopeAdapter: _LayoutedRowAdapter {
let _mapping: _LayoutedColumnMapping
let _scopes: [String: any _LayoutedRowAdapter]
}
struct ChainedAdapter: RowAdapter {
let first: any RowAdapter
let second: any RowAdapter
func _layoutedAdapter(from layout: some _RowLayout) throws -> any _LayoutedRowAdapter {
try second._layoutedAdapter(from: first._layoutedAdapter(from: layout)._mapping)
}
}
/// `RenameColumnAdapter` is a row adapter that renames columns.
///
/// For example:
///
/// ```swift
/// let adapter = RenameColumnAdapter { column in column + "rrr" }
/// let sql = "SELECT 0 AS a, 1 AS b, 2 AS c"
///
/// // [arrr:0, brrr:1, crrr:2]
/// let row = try Row.fetchOne(db, sql: sql, adapter: adapter)!
/// ```
///
/// This adapter is useful when subqueries contain duplicated column names:
///
/// ```swift
/// let sql = "SELECT * FROM (SELECT 1 AS id, 2 AS id)"
///
/// // Prints ["id", "id:1"]
/// // Note the "id:1" column, generated by SQLite.
/// let row = try Row.fetchOne(db, sql: sql)!
/// print(Array(row.columnNames))
///
/// // Drop the `:...` suffix, and prints ["id", "id"]
/// let adapter = RenameColumnAdapter { String($0.prefix(while: { $0 != ":" })) }
/// let adaptedRow = try Row.fetchOne(db, sql: sql, adapter: adapter)!
/// print(Array(adaptedRow.columnNames))
/// ```
public struct RenameColumnAdapter: RowAdapter {
let transform: (String) -> String
/// Creates a `RenameColumnAdapter` adapter that renames columns according to the
/// provided transform function.
public init(_ transform: @escaping (String) -> String) {
self.transform = transform
}
public func _layoutedAdapter(from layout: some _RowLayout) throws -> any _LayoutedRowAdapter {
let layoutColumns = layout._layoutColumns.map { (index, column) in (index, transform(column)) }
return _LayoutedColumnMapping(layoutColumns: layoutColumns)
}
}
extension Row {
/// Creates a row from a base row and a statement adapter
convenience init(base: Row, adapter: some _LayoutedRowAdapter) {
self.init(impl: AdaptedRowImpl(base: base, adapter: adapter))
}
/// Returns self if adapter is nil
func adapted(with adapter: (any RowAdapter)?, layout: some _RowLayout) throws -> Row {
guard let adapter else {
return self
}
return try Row(base: self, adapter: adapter._layoutedAdapter(from: layout))
}
}
struct AdaptedRowImpl: RowImpl {
let base: Row
let adapter: any _LayoutedRowAdapter
let mapping: _LayoutedColumnMapping
init(base: Row, adapter: some _LayoutedRowAdapter) {
self.base = base
self.adapter = adapter
self.mapping = adapter._mapping
}
var count: Int { mapping._layoutColumns.count }
var isFetched: Bool { base.isFetched }
func scopes(prefetchedRows: Row.PrefetchedRowsView) -> Row.ScopesView {
Row.ScopesView(row: base, scopes: adapter._scopes, prefetchedRows: prefetchedRows)
}
func hasNull(atUncheckedIndex index: Int) -> Bool {
let mappedIndex = mapping.baseColumnIndex(atMappingIndex: index)
return base.impl.hasNull(atUncheckedIndex: mappedIndex)
}
func databaseValue(atUncheckedIndex index: Int) -> DatabaseValue {
let mappedIndex = mapping.baseColumnIndex(atMappingIndex: index)
return base.impl.databaseValue(atUncheckedIndex: mappedIndex)
}
func fastDecode<Value: DatabaseValueConvertible & StatementColumnConvertible>(
_ type: Value.Type,
atUncheckedIndex index: Int)
throws -> Value
{
let mappedIndex = mapping.baseColumnIndex(atMappingIndex: index)
return try Value.fastDecode(fromRow: base, atUncheckedIndex: mappedIndex)
}
func withUnsafeData<T>(atUncheckedIndex index: Int, _ body: (Data?) throws -> T) throws -> T {
let mappedIndex = mapping.baseColumnIndex(atMappingIndex: index)
return try base.impl.withUnsafeData(atUncheckedIndex: mappedIndex, body)
}
func columnName(atUncheckedIndex index: Int) -> String {
mapping.columnName(atMappingIndex: index)
}
func index(forColumn name: String) -> Int? {
mapping._layoutIndex(ofColumn: name)
}
func copiedRow(_ row: Row) -> Row {
Row(base: base.copy(), adapter: adapter)
}
func unscopedRow(_ row: Row) -> Row {
assert(adapter._mapping._scopes.isEmpty)
return Row(base: base, adapter: adapter._mapping)
}
func unadaptedRow(_ row: Row) -> Row {
base.unadapted
}
}