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 { 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.. 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(_ 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(_ 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(_ 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(_ 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(_ 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(_ 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(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(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(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 { 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(_ 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(_ 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(_ 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 = author["books"] /// ``` /// /// See also: ``prefetchedRows`` /// /// - parameter key: An association key. public subscript(_ key: String) -> Set { try! decode(Set.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( _ 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( _ 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( _ 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( _ 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( _ 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(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( _ 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( _ 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( _ 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 = row["books"] /// print(books.first!.title) /// // Prints "Moby-Dick" func decode( _ type: Set.Type = Set.self, forKey key: String) throws -> Set { 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(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 { // 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 { 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 = """ /// 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 = """ /// 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 = """ /// 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 { 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 = """ /// 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 { // MARK: Fetching Rows /// Returns a cursor over fetched rows. /// /// For example: /// /// ```swift /// try dbQueue.read { db in /// let lastName = "O'Reilly" /// let request: SQLRequest = """ /// 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 = """ /// 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 = """ /// 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 { try Row.fetchSet(db, self) } /// Returns a single row. /// /// For example: /// /// ```swift /// try dbQueue.read { db in /// let lastName = "O'Reilly" /// let request: SQLRequest = """ /// 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.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.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 { var names = Set() 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 } /// 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 = [:] /// 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 { var result: Set = [] 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 { 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( _ type: Value.Type, atUncheckedIndex index: Int) throws -> Value func withUnsafeData(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( _ 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(atUncheckedIndex index: Int, _ body: (Data?) throws -> T) throws -> T { // unless customized, copy data (see StatementRowImpl and AdaptedRowImpl for customization) let data = try Optional.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) 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 let columnNames: [String] init(sqliteStatement: SQLiteStatement, columnNames: [String]) { let sqliteStatement = sqliteStatement self.dbValues = ContiguousArray( (0.. 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.. 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( _ type: Value.Type, atUncheckedIndex index: Int) throws -> Value { try Value.fastDecode( fromStatement: sqliteStatement, atUncheckedIndex: CInt(index), context: RowDecodingContext(statement: statement, index: index)) } func withUnsafeData(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.. 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 } }