/// A `Table` builds database queries with the Swift language instead of SQL. /// /// ## Overview /// /// A `Table` instance is similar to a ``TableRecord`` type. You will use one /// when the other is impractical or impossible to use. /// /// For example: /// /// ```swift /// let table = Table("player") /// try dbQueue.read { db in /// // SELECT * FROM player WHERE score >= 1000 /// let rows: [Row] = table.filter(Column("score") >= 1000).fetchAll(db) /// } /// ``` /// /// ## Topics /// /// ### Creating a Table /// /// - ``init(_:)-2iz5y`` /// - ``init(_:)-3mfb8`` /// /// ### Instance Properties /// /// - ``tableName`` /// /// ### Counting Rows /// /// - ``fetchCount(_:)`` /// /// ### Testing for Row Existence /// /// - ``exists(_:id:)`` /// - ``exists(_:key:)-4dk7e`` /// - ``exists(_:key:)-36jtu`` /// /// ### Deleting Rows /// /// - ``deleteAll(_:)`` /// - ``deleteAll(_:ids:)`` /// - ``deleteAll(_:keys:)-5t865`` /// - ``deleteAll(_:keys:)-28sff`` /// - ``deleteOne(_:id:)`` /// - ``deleteOne(_:key:)-404su`` /// - ``deleteOne(_:key:)-64wmq`` /// /// ### Updating Rows /// /// - ``updateAll(_:onConflict:_:)-4w9b`` /// - ``updateAll(_:onConflict:_:)-4cvap`` /// /// ### Building Query Interface Requests /// /// `Table` provide convenience access to most ``DerivableRequest`` and /// ``QueryInterfaceRequest`` methods. /// /// - ``aliased(_:)`` /// - ``all()`` /// - ``annotated(with:)-6i101`` /// - ``annotated(with:)-6x399`` /// - ``annotated(with:)-4sbgw`` /// - ``annotated(with:)-98t4p`` /// - ``annotated(withOptional:)`` /// - ``annotated(withRequired:)`` /// - ``filter(_:)`` /// - ``filter(id:)`` /// - ``filter(ids:)`` /// - ``filter(key:)-tw3i`` /// - ``filter(key:)-4sun7`` /// - ``filter(keys:)-85e0v`` /// - ``filter(keys:)-qqgf`` /// - ``filter(literal:)`` /// - ``filter(sql:arguments:)`` /// - ``having(_:)`` /// - ``including(all:)`` /// - ``including(optional:)`` /// - ``including(required:)`` /// - ``joining(optional:)`` /// - ``joining(required:)`` /// - ``limit(_:offset:)`` /// - ``none()`` /// - ``order(_:)-2gvi7`` /// - ``order(_:)-9o5bb`` /// - ``order(literal:)`` /// - ``order(sql:arguments:)`` /// - ``orderByPrimaryKey()`` /// - ``select(_:)-1599q`` /// - ``select(_:)-2cnd1`` /// - ``select(_:as:)-20ci9`` /// - ``select(_:as:)-3pr6x`` /// - ``select(literal:)`` /// - ``select(literal:as:)`` /// - ``select(sql:arguments:)`` /// - ``select(sql:arguments:as:)`` /// - ``selectPrimaryKey(as:)`` /// - ``with(_:)`` /// /// ### Defining Associations /// /// - ``association(to:)`` /// - ``association(to:on:)`` /// - ``belongsTo(_:key:using:)-8p5xr`` /// - ``belongsTo(_:key:using:)-117wr`` /// - ``hasMany(_:key:using:)-3i6yk`` /// - ``hasMany(_:key:using:)-57dwf`` /// - ``hasMany(_:through:using:key:)`` /// - ``hasOne(_:key:using:)-81vqy`` /// - ``hasOne(_:key:using:)-3438j`` /// - ``hasOne(_:through:using:key:)`` /// /// ### Fetching Database Rows /// /// - ``fetchCursor(_:)-1oqex`` /// - ``fetchAll(_:)-4s7yn`` /// - ``fetchSet(_:)-5lp4s`` /// - ``fetchOne(_:)-3bduz`` /// /// ### Fetching Database Values /// /// - ``fetchCursor(_:)-65lci`` /// - ``fetchCursor(_:)-295uw`` /// - ``fetchAll(_:)-6xr01`` /// - ``fetchAll(_:)-7tjdp`` /// - ``fetchSet(_:)-3mchk`` /// - ``fetchSet(_:)-8k2uk`` /// - ``fetchOne(_:)-infc`` /// - ``fetchOne(_:)-71icb`` /// /// ### Fetching Records /// /// - ``fetchCursor(_:)-81wuu`` /// - ``fetchAll(_:)-3l7ol`` /// - ``fetchSet(_:)-ko77`` /// - ``fetchOne(_:)-8n1q`` /// /// ### Database Observation Support /// /// - ``databaseRegion(_:)`` public struct Table: Sendable { /// The table name. public var tableName: String private init(_ tableName: String, _ type: RowDecoder.Type) { self.tableName = tableName } /// Creates a `Table`. /// /// For example: /// /// ```swift /// let table = Table("player") /// let table = Table("player") /// ``` public init(_ tableName: String) { self.init(tableName, RowDecoder.self) } } extension Table where RowDecoder == Row { /// Create a `Table`. /// /// For example: /// /// ```swift /// let table = Table("player") // Table /// ``` public init(_ tableName: String) { self.init(tableName, Row.self) } } extension Table: DatabaseRegionConvertible { public func databaseRegion(_ db: Database) throws -> DatabaseRegion { DatabaseRegion(table: tableName) } } // MARK: Request Derivation extension Table { var relationForAll: SQLRelation { .all(fromTable: tableName) } /// Returns a request for all rows of the table. /// /// ```swift /// // Fetch all players /// let table = Table("player") /// let request = table.all() /// let players: [Player] = try request.fetchAll(db) /// ``` public func all() -> QueryInterfaceRequest { QueryInterfaceRequest(relation: relationForAll) } /// Returns an empty request that fetches no row. /// /// For example: /// /// ```swift /// try dbQueue.read { db in /// let request = Table("player").none() /// let rows = try request.fetchAll(db) // empty array /// } public func none() -> QueryInterfaceRequest { all().none() // don't laugh } /// Returns a request that selects the provided result columns. /// /// For example: /// /// ```swift /// let playerTable = Table("player") /// /// // SELECT id, score FROM player /// let request = playerTable.select(Column("id"), Column("score")) /// ``` public func select(_ selection: any SQLSelectable...) -> QueryInterfaceRequest { all().select(selection) } /// Returns a request that selects the provided result columns. /// /// For example: /// /// ```swift /// let playerTable = Table("player") /// /// // SELECT id, score FROM player /// let request = playerTable.select([Column("id"), Column("score")]) /// ``` public func select(_ selection: [any SQLSelectable]) -> QueryInterfaceRequest { all().select(selection) } /// Returns a request that selects the provided SQL string. /// /// For example: /// /// ```swift /// let playerTable = Table("player") /// /// // SELECT id, name FROM player /// let request = playerTable.select(sql: "id, name") /// /// // SELECT id, IFNULL(name, 'Anonymous') FROM player /// let defaultName = "Anonymous" /// let request = playerTable.select(sql: "id, IFNULL(name, ?)", arguments: [defaultName]) /// ``` public func select( sql: String, arguments: StatementArguments = StatementArguments()) -> QueryInterfaceRequest { all().select(SQL(sql: sql, arguments: arguments)) } /// Returns a request that selects the provided ``SQL`` literal. /// /// ``SQL`` literals allow you to safely embed raw values in your SQL, /// without any risk of syntax errors or SQL injection: /// /// ```swift /// let playerTable = Table("player") /// /// // SELECT id, IFNULL(name, 'Anonymous') FROM player /// let defaultName = "Anonymous" /// let request = playerTable.select(literal: "id, IFNULL(name, \(defaultName))") /// ``` public func select(literal sqlLiteral: SQL) -> QueryInterfaceRequest { all().select(sqlLiteral) } /// Returns a request that selects the provided result columns, and defines /// the type of decoded rows. /// /// For example: /// /// ```swift /// let playerTable = Table("player") /// /// let minScore = min(Column("score")) /// let maxScore = max(Column("score")) /// /// // SELECT MAX(score) FROM player /// let request = playerTable.select([maxScore], as: Int.self) /// let maxScore = try request.fetchOne(db) // Int? /// /// // SELECT MIN(score), MAX(score) FROM player /// let request = playerTable.select([minScore, maxScore], as: Row.self) /// if let row = try request.fetchOne(db) { /// let minScore: Int = row[0] /// let maxScore: Int = row[1] /// } /// ``` public func select( _ selection: [any SQLSelectable], as type: T.Type = T.self) -> QueryInterfaceRequest { all().select(selection, as: type) } /// Returns a request that selects the provided result columns, and defines /// the type of decoded rows. /// /// For example: /// /// ```swift /// let playerTable = Table("player") /// /// let minScore = min(Column("score")) /// let maxScore = max(Column("score")) /// /// // SELECT MAX(score) FROM player /// let request = playerTable.select(maxScore, as: Int.self) /// let maxScore = try request.fetchOne(db) // Int? /// /// // SELECT MIN(score), MAX(score) FROM player /// let request = playerTable.select(minScore, maxScore, as: Row.self) /// if let row = try request.fetchOne(db) { /// let minScore: Int = row[0] /// let maxScore: Int = row[1] /// } /// ``` public func select( _ selection: any SQLSelectable..., as type: T.Type = T.self) -> QueryInterfaceRequest { all().select(selection, as: type) } /// Returns a request that selects the provided SQL string, and defines the /// type of decoded rows. /// /// For example: /// /// ```swift /// let playerTable = Table("player") /// /// // SELECT name FROM player /// let request = playerTable.select(sql: "name", as: String.self) /// let names = try request.fetchAll(db) // [String] /// /// // SELECT IFNULL(name, 'Anonymous') FROM player /// let defaultName = "Anonymous" /// let request = playerTable.select(sql: "IFNULL(name, ?)", arguments: [defaultName], as: String.self) /// let names = try request.fetchAll(db) // [String] /// ``` public func select( sql: String, arguments: StatementArguments = StatementArguments(), as type: T.Type = T.self) -> QueryInterfaceRequest { all().select(SQL(sql: sql, arguments: arguments), as: type) } /// Returns a request that selects the provided ``SQL`` literal, and defines /// the type of decoded rows. /// /// ``SQL`` literals allow you to safely embed raw values in your SQL, /// without any risk of syntax errors or SQL injection: /// /// ```swift /// let playerTable = Table("player") /// /// // SELECT IFNULL(name, 'Anonymous') FROM player /// let defaultName = "Anonymous" /// let request = playerTable.select(literal: "IFNULL(name, \(defaultName))", as: String.self) /// let names = try request.fetchAll(db) // [String] /// ``` public func select( literal sqlLiteral: SQL, as type: T.Type = T.self) -> QueryInterfaceRequest { all().select(sqlLiteral, as: type) } /// Returns a request that selects the primary key. /// /// All primary keys are supported: /// /// ```swift /// let playerTable = Table("player") /// let countryTable = Table("country") /// let citizenshipTable = Table("citizenship") /// /// // SELECT id FROM player WHERE ... /// let request = try playerTable.selectPrimaryKey(as: Int64.self) /// let ids = try request.fetchAll(db) // [Int64] /// /// // SELECT code FROM country WHERE ... /// let request = try countryTable.selectPrimaryKey(as: String.self) /// let countryCodes = try request.fetchAll(db) // [String] /// /// // SELECT citizenId, countryCode FROM citizenship WHERE ... /// let request = try citizenshipTable.selectPrimaryKey(as: Row.self) /// let rows = try request.fetchAll(db) // [Row] /// ``` /// /// For composite primary keys, you can define a ``FetchableRecord`` type: /// /// ```swift /// extension Citizenship { /// struct ID: Decodable, FetchableRecord { /// var citizenId: Int64 /// var countryCode: String /// } /// } /// let request = try citizenshipTable.selectPrimaryKey(as: Citizenship.ID.self) /// let ids = try request.fetchAll(db) // [Citizenship.ID] /// ``` public func selectPrimaryKey(as type: PrimaryKey.Type = PrimaryKey.self) -> QueryInterfaceRequest { all().selectPrimaryKey(as: type) } /// Returns a request with the provided result columns appended to the /// table columns. /// /// For example: /// /// ```swift /// let playerTable = Table("player") /// /// // SELECT *, score + bonus AS totalScore FROM player /// let totalScore = (Column("score") + Column("bonus")).forKey("totalScore") /// let request = playerTable.annotated(with: [totalScore]) /// ``` public func annotated(with selection: [any SQLSelectable]) -> QueryInterfaceRequest { all().annotated(with: selection) } /// Returns a request with the provided result columns appended to the /// table columns. /// /// For example: /// /// ```swift /// let playerTable = Table("player") /// /// // SELECT *, score + bonus AS totalScore FROM player /// let totalScore = (Column("score") + Column("bonus")).forKey("totalScore") /// let request = playerTable.annotated(with: totalScore) /// ``` public func annotated(with selection: any SQLSelectable...) -> QueryInterfaceRequest { all().annotated(with: selection) } /// Returns a request filtered with a boolean SQL expression. /// /// For example: /// /// ```swift /// let playerTable = Table("player") /// /// // SELECT * FROM player WHERE name = 'O''Brien' /// let name = "O'Brien" /// let request = playerTable.filter(Column("name") == name) /// ``` public func filter(_ predicate: some SQLSpecificExpressible) -> QueryInterfaceRequest { all().filter(predicate) } /// Returns a request filtered by primary key. /// /// All single-column primary keys are supported: /// /// ```swift /// let playerTable = Table("player") /// let countryTable = Table("country") /// /// // SELECT * FROM player WHERE id = 1 /// let request = playerTable.filter(key: 1) /// /// // SELECT * FROM country WHERE code = 'FR' /// let request = countryTable.filter(key: "FR") /// ``` /// /// - parameter key: A primary key public func filter(key: some DatabaseValueConvertible) -> QueryInterfaceRequest { all().filter(key: key) } /// Returns a request filtered by primary key. /// /// All single-column primary keys are supported: /// /// ```swift /// let playerTable = Table("player") /// let countryTable = Table("country") /// /// // SELECT * FROM player WHERE id = IN (1, 2, 3) /// let request = playerTable.filter(keys: [1, 2, 3]) /// /// // SELECT * FROM country WHERE code = IN ('FR', 'US') /// let request = countryTable.filter(keys: ["FR", "US"]) /// ``` /// /// - parameter keys: A collection of primary keys public func filter(keys: Keys) -> QueryInterfaceRequest where Keys: Sequence, Keys.Element: DatabaseValueConvertible { all().filter(keys: keys) } /// Returns a request filtered by primary or unique key. /// /// For example: /// /// ```swift /// let playerTable = Table("player") /// let citizenshipTable = Table("citizenship") /// /// // SELECT * FROM player WHERE id = 1 /// let request = playerTable.filter(key: ["id": 1]) /// /// // SELECT * FROM player WHERE email = 'arthur@example.com' /// let request = playerTable.filter(key: ["email": "arthur@example.com"]) /// /// // SELECT * FROM citizenship WHERE citizenId = 1 AND countryCode = 'FR' /// let request = citizenshipTable.filter(key: [ /// "citizenId": 1, /// "countryCode": "FR", /// ]) /// ``` /// /// When executed, this request raises a fatal error if no unique index /// exists on a subset of the key columns. /// /// - parameter key: A key dictionary. public func filter(key: [String: (any DatabaseValueConvertible)?]?) -> QueryInterfaceRequest { all().filter(key: key) } /// Returns a request filtered by primary or unique key. /// /// For example: /// /// ```swift /// let playerTable = Table("player") /// let citizenshipTable = Table("citizenship") /// /// // SELECT * FROM player WHERE id = 1 /// let request = playerTable.filter(keys: [["id": 1]]) /// /// // SELECT * FROM player WHERE email = 'arthur@example.com' /// let request = playerTable.filter(keys: [["email": "arthur@example.com"]]) /// /// // SELECT * FROM citizenship WHERE citizenId = 1 AND countryCode = 'FR' /// let request = citizenshipTable.filter(keys: [ /// ["citizenId": 1, "countryCode": "FR"], /// ]) /// ``` /// /// When executed, this request raises a fatal error if no unique index /// exists on a subset of the key columns. /// /// - parameter keys: An array of key dictionaries. public func filter(keys: [[String: (any DatabaseValueConvertible)?]]) -> QueryInterfaceRequest { all().filter(keys: keys) } /// Returns a request filtered with an SQL string. /// /// For example: /// /// ```swift /// let playerTable = Table("player") /// /// // SELECT * FROM player WHERE name = 'O''Brien' /// let name = "O'Brien" /// let request = playerTable.filter(sql: "name = ?", arguments: [name]) /// ``` public func filter( sql: String, arguments: StatementArguments = StatementArguments()) -> QueryInterfaceRequest { filter(SQL(sql: sql, arguments: arguments)) } /// Returns a request filtered with an ``SQL`` literal. /// /// ``SQL`` literals allow you to safely embed raw values in your SQL, /// without any risk of syntax errors or SQL injection: /// /// ```swift /// let playerTable = Table("player") /// /// // SELECT * FROM player WHERE name = 'O''Brien' /// let name = "O'Brien" /// let request = playerTable.filter(literal: "name = \(name)") /// ``` public func filter(literal sqlLiteral: SQL) -> QueryInterfaceRequest { all().filter(sqlLiteral) } /// Returns a request sorted according to the given SQL ordering terms. /// /// For example: /// /// ```swift /// let playerTable = Table("player") /// /// // SELECT * FROM player ORDER BY score DESC, name /// let request = playerTable.order(Column("score").desc, Column("name")) /// ``` public func order(_ orderings: any SQLOrderingTerm...) -> QueryInterfaceRequest { all().order(orderings) } /// Returns a request sorted according to the given SQL ordering terms. /// /// For example: /// /// ```swift /// let playerTable = Table("player") /// /// // SELECT * FROM player ORDER BY score DESC, name /// let request = playerTable.order([Column("score").desc, Column("name")]) /// ``` public func order(_ orderings: [any SQLOrderingTerm]) -> QueryInterfaceRequest { all().order(orderings) } /// Returns a request sorted by primary key. /// /// All primary keys are supported: /// /// ```swift /// let playerTable = Table("player") /// let countryTable = Table("country") /// let citizenshipTable = Table("citizenship") /// /// // SELECT * FROM player ORDER BY id /// let request = playerTable.orderByPrimaryKey() /// /// // SELECT * FROM country ORDER BY code /// let request = countryTable.orderByPrimaryKey() /// /// // SELECT * FROM citizenship ORDER BY citizenId, countryCode /// let request = citizenshipTable.orderByPrimaryKey() /// ``` public func orderByPrimaryKey() -> QueryInterfaceRequest { all().orderByPrimaryKey() } /// Returns a request sorted according to the given SQL string. /// /// For example: /// /// ```swift /// let playerTable = Table("player") /// /// // SELECT * FROM player ORDER BY score DESC, name /// let request = playerTable.order(sql: "score DESC, name") /// ``` public func order( sql: String, arguments: StatementArguments = StatementArguments()) -> QueryInterfaceRequest { all().order(SQL(sql: sql, arguments: arguments)) } /// Returns a request sorted according to the given ``SQL`` literal. /// /// For example: /// /// ```swift /// let playerTable = Table("player") /// /// // SELECT * FROM player ORDER BY score DESC, name /// let request = playerTable.order(literal: "score DESC, name") /// ``` public func order(literal sqlLiteral: SQL) -> QueryInterfaceRequest { all().order(sqlLiteral) } /// Returns a limited request. /// /// The returned request fetches `limit` rows, starting at `offset`. For /// example: /// /// ```swift /// let playerTable = Table("player") /// /// // SELECT * FROM player LIMIT 10 /// let request = playerTable.limit(10) /// /// // SELECT * FROM player LIMIT 10 OFFSET 20 /// let request = playerTable.limit(10, offset: 20) /// ``` public func limit(_ limit: Int, offset: Int? = nil) -> QueryInterfaceRequest { all().limit(limit, offset: offset) } /// Returns a request that can be referred to with the provided alias. /// /// `table.aliased(alias)` is equivalent to `table.all().aliased(alias)`. /// See ``TableRequest/aliased(_:)`` for more information. public func aliased(_ alias: TableAlias) -> QueryInterfaceRequest { all().aliased(alias) } /// Returns a request that embeds a common table expression. /// /// `table.with(cte)` is equivalent to `table.all().with(cte)`. /// See ``DerivableRequest/with(_:)`` for more information. public func with(_ cte: CommonTableExpression) -> QueryInterfaceRequest { all().with(cte) } } @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) extension Table where RowDecoder: Identifiable, RowDecoder.ID: DatabaseValueConvertible { /// Returns a request filtered by primary key. /// /// All single-column primary keys are supported: /// /// ```swift /// struct Player: Identifiable { /// var id: Int64 /// } /// struct Country: Identifiable { /// var id: String /// } /// /// let playerTable = Table("player") /// let countryTable = Table("country") /// /// // SELECT * FROM player WHERE id = 1 /// let request = playerTable.filter(id: 1) /// /// // SELECT * FROM country WHERE code = 'FR' /// let request = countryTable.filter(id: "FR") /// ``` /// /// - parameter id: A primary key public func filter(id: RowDecoder.ID) -> QueryInterfaceRequest { all().filter(id: id) } /// Returns a request filtered by primary key. /// /// All single-column primary keys are supported: /// /// ```swift /// struct Player: Identifiable { /// var id: Int64 /// } /// struct Country: Identifiable { /// var id: String /// } /// /// let playerTable = Table("player") /// let countryTable = Table("country") /// /// // SELECT * FROM player WHERE id = IN (1, 2, 3) /// let request = playerTable.filter(ids: [1, 2, 3]) /// /// // SELECT * FROM country WHERE code = IN ('FR', 'US') /// let request = countryTable.filter(ids: ["FR", "US"]) /// ``` /// /// - parameter ids: A collection of primary keys public func filter(ids: IDS) -> QueryInterfaceRequest where IDS: Collection, IDS.Element == RowDecoder.ID { all().filter(ids: ids) } } extension Table { // MARK: - Counting All /// Returns the number of rows in the database table. /// /// For example: /// /// ```swift /// let playerTable = Table("player") /// /// try dbQueue.read { db in /// // SELECT COUNT(*) FROM player /// let count = try playerTable.fetchCount(db) /// } /// ``` /// /// - parameter db: A database connection. public func fetchCount(_ db: Database) throws -> Int { try all().fetchCount(db) } } // MARK: - Fetching Records from Table extension Table where RowDecoder: FetchableRecord { /// Returns a cursor over all records fetched from the database. /// /// For example: /// /// ```swift /// struct Player: FetchableRecord { } /// let playerTable = Table("player") /// /// try dbQueue.read { db in /// // SELECT * FROM player /// let players = try playerTable.fetchCursor(db) /// while let player = try players.next() { /// print(player.name) /// } /// } /// ``` /// /// The order in which the records are returned is undefined /// ([ref](https://www.sqlite.org/lang_select.html#the_order_by_clause)). /// /// 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 ``RecordCursor`` over fetched records. /// - throws: A ``DatabaseError`` whenever an SQLite error occurs. public func fetchCursor(_ db: Database) throws -> RecordCursor { try all().fetchCursor(db) } /// Returns an array of all records fetched from the database. /// /// For example: /// /// ```swift /// struct Player: FetchableRecord { } /// let playerTable = Table("player") /// /// try dbQueue.read { db in /// // SELECT * FROM player /// let players = try playerTable.fetchAll(db) /// } /// ``` /// /// The order in which the records are returned is undefined /// ([ref](https://www.sqlite.org/lang_select.html#the_order_by_clause)). /// /// - parameter db: A database connection. /// - throws: A ``DatabaseError`` whenever an SQLite error occurs. public func fetchAll(_ db: Database) throws -> [RowDecoder] { try all().fetchAll(db) } /// Returns a single record fetched from the database. /// /// For example: /// /// ```swift /// struct Player: FetchableRecord { } /// let playerTable = Table("player") /// /// try dbQueue.read { db in /// // SELECT * FROM player LIMIT 1 /// let player = try playerTable.fetchOne(db) /// } /// ``` /// /// - parameter db: A database connection. /// - throws: A ``DatabaseError`` whenever an SQLite error occurs. public func fetchOne(_ db: Database) throws -> RowDecoder? { try all().fetchOne(db) } } extension Table where RowDecoder: FetchableRecord & Hashable { /// Returns a set of all records fetched from the database. /// /// For example: /// /// ```swift /// struct Player: FetchableRecord, Hashable { } /// let playerTable = Table("player") /// /// try dbQueue.read { db in /// // SELECT * FROM player /// let players = try playerTable.fetchSet(db) /// } /// ``` /// /// - parameter db: A database connection. /// - throws: A ``DatabaseError`` whenever an SQLite error occurs. public func fetchSet(_ db: Database) throws -> Set { try all().fetchSet(db) } } // MARK: - Fetching Rows from Table extension Table where RowDecoder == Row { /// Returns a cursor over all rows fetched from the database. /// /// For example: /// /// ```swift /// let playerTable = Table("player") /// /// try dbQueue.read { db in /// // SELECT * FROM player /// let rows = try playerTable.fetchCursor(db) /// while let row = try rows.next() { /// let id: Int64 = row["id"] /// let name: String = row["name"] /// } /// } /// ``` /// /// The order in which the rows are returned is undefined /// ([ref](https://www.sqlite.org/lang_select.html#the_order_by_clause)). /// /// 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 all().fetchCursor(db) } /// Returns an array of all rows fetched from the database. /// /// For example: /// /// ```swift /// let playerTable = Table("player") /// /// try dbQueue.read { db in /// // SELECT * FROM player /// let rows = try playerTable.fetchAll(db) /// } /// ``` /// /// The order in which the rows are returned is undefined /// ([ref](https://www.sqlite.org/lang_select.html#the_order_by_clause)). /// /// - parameter db: A database connection. /// - throws: A ``DatabaseError`` whenever an SQLite error occurs. public func fetchAll(_ db: Database) throws -> [Row] { try all().fetchAll(db) } /// Returns a single row fetched from the database. /// /// For example: /// /// ```swift /// let playerTable = Table("player") /// /// try dbQueue.read { db in /// // SELECT * FROM player LIMIT 1 /// let row = try playerTable.fetchOne(db) /// } /// ``` /// /// - parameter db: A database connection. /// - throws: A ``DatabaseError`` whenever an SQLite error occurs. public func fetchOne(_ db: Database) throws -> Row? { try all().fetchOne(db) } /// Returns a set of all rows fetched from the database. /// /// For example: /// /// ```swift /// let playerTable = Table("player") /// /// try dbQueue.read { db in /// // SELECT * FROM player /// let rows = try playerTable.fetchSet(db) /// } /// ``` /// /// - parameter db: A database connection. /// - throws: A ``DatabaseError`` whenever an SQLite error occurs. public func fetchSet(_ db: Database) throws -> Set { try all().fetchSet(db) } } // MARK: - Fetching Values from Table extension Table where RowDecoder: DatabaseValueConvertible { /// Returns a cursor over fetched values. /// /// The order in which the values are returned is undefined /// ([ref](https://www.sqlite.org/lang_select.html#the_order_by_clause)). /// /// Values are decoded from the leftmost column. /// /// 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 ``DatabaseValueCursor`` over fetched values. /// - throws: A ``DatabaseError`` whenever an SQLite error occurs. public func fetchCursor(_ db: Database) throws -> DatabaseValueCursor { try all().fetchCursor(db) } /// Returns an array of fetched values. /// /// Values are decoded from the leftmost column. /// /// - parameter db: A database connection. /// - returns: An array of values. /// - throws: A ``DatabaseError`` whenever an SQLite error occurs. public func fetchAll(_ db: Database) throws -> [RowDecoder] { try all().fetchAll(db) } /// Returns a single fetched value. /// /// The value is decoded from the leftmost column. /// /// The result is nil if the request returns no row, or one row with a /// `NULL` value. /// /// - parameter db: A database connection. /// - returns: An optional value. /// - throws: A ``DatabaseError`` whenever an SQLite error occurs. public func fetchOne(_ db: Database) throws -> RowDecoder? { try all().fetchOne(db) } } extension Table where RowDecoder: DatabaseValueConvertible & Hashable { /// Returns a set of fetched values. /// /// Values are decoded from the leftmost column. /// /// - parameter db: A database connection. /// - returns: A set of values. /// - throws: A ``DatabaseError`` whenever an SQLite error occurs. public func fetchSet(_ db: Database) throws -> Set { try all().fetchSet(db) } } // MARK: - Fetching Fast Values from Table extension Table where RowDecoder: DatabaseValueConvertible & StatementColumnConvertible { /// Returns a cursor over fetched values. /// /// The order in which the values are returned is undefined /// ([ref](https://www.sqlite.org/lang_select.html#the_order_by_clause)). /// /// Values are decoded from the leftmost column. /// /// 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 ``FastDatabaseValueCursor`` over fetched values. /// - throws: A ``DatabaseError`` whenever an SQLite error occurs. public func fetchCursor(_ db: Database) throws -> FastDatabaseValueCursor { try all().fetchCursor(db) } /// Returns an array of fetched values. /// /// Values are decoded from the leftmost column. /// /// - parameter db: A database connection. /// - returns: An array of values. /// - throws: A ``DatabaseError`` whenever an SQLite error occurs. public func fetchAll(_ db: Database) throws -> [RowDecoder] { try all().fetchAll(db) } /// Returns a single fetched value. /// /// The value is decoded from the leftmost column. /// /// The result is nil if the request returns no row, or one row with a /// `NULL` value. /// /// - parameter db: A database connection. /// - returns: An optional value. /// - throws: A ``DatabaseError`` whenever an SQLite error occurs. public func fetchOne(_ db: Database) throws -> RowDecoder? { try all().fetchOne(db) } } extension Table where RowDecoder: DatabaseValueConvertible & StatementColumnConvertible & Hashable { /// Returns a set of fetched values. /// /// Values are decoded from the leftmost column. /// /// - parameter db: A database connection. /// - returns: A set of values. /// - throws: A ``DatabaseError`` whenever an SQLite error occurs. public func fetchSet(_ db: Database) throws -> Set { try all().fetchSet(db) } } // MARK: - Associations to TableRecord extension Table { /// Creates a ``BelongsToAssociation`` between this table and the /// destination `TableRecord` type. /// /// For more information, see ``TableRecord/belongsTo(_:key:using:)-13t5r``. /// /// - parameters: /// - destination: The record type at the other side of the association. /// - key: The association key. By default, it /// is `Destination.databaseTableName`. /// - foreignKey: An eventual foreign key. You need to provide one when /// no foreign key exists to the destination table, or several foreign /// keys exist. public func belongsTo( _ destination: Destination.Type, key: String? = nil, using foreignKey: ForeignKey? = nil) -> BelongsToAssociation where Destination: TableRecord { BelongsToAssociation( to: Destination.relationForAll, key: key, using: foreignKey) } /// Creates a ``HasManyAssociation`` between this table and the /// destination `TableRecord` type. /// /// For more information, see ``TableRecord/hasMany(_:key:using:)-45axo``. /// /// - parameters: /// - destination: The record type at the other side of the association. /// - key: The association key. By default, it /// is `Destination.databaseTableName`. /// - foreignKey: An eventual foreign key. You need to provide one when /// no foreign key exists to the destination table, or several foreign /// keys exist. public func hasMany( _ destination: Destination.Type, key: String? = nil, using foreignKey: ForeignKey? = nil) -> HasManyAssociation where Destination: TableRecord { HasManyAssociation( to: Destination.relationForAll, key: key, using: foreignKey) } /// Creates a ``HasOneAssociation`` between this table and the /// destination `TableRecord` type. /// /// For more information, see ``TableRecord/hasOne(_:key:using:)-4g9tm``. /// /// - parameters: /// - destination: The record type at the other side of the association. /// - key: The association key. By default, it /// is `Destination.databaseTableName`. /// - foreignKey: An eventual foreign key. You need to provide one when /// no foreign key exists to the destination table, or several foreign /// keys exist. public func hasOne( _ destination: Destination.Type, key: String? = nil, using foreignKey: ForeignKey? = nil) -> HasOneAssociation where Destination: TableRecord { HasOneAssociation( to: Destination.relationForAll, key: key, using: foreignKey) } } // MARK: - Associations to Table extension Table { /// Creates a ``BelongsToAssociation`` between this table and the /// destination `Table`. /// /// For more information, see ``TableRecord/belongsTo(_:key:using:)-13t5r``. /// /// - parameters: /// - destination: The table at the other side of the association. /// - key: The association key. By default, it /// is `destination.tableName`. /// - foreignKey: An eventual foreign key. You need to provide one when /// no foreign key exists to the destination table, or several foreign /// keys exist. public func belongsTo( _ destination: Table, key: String? = nil, using foreignKey: ForeignKey? = nil) -> BelongsToAssociation { BelongsToAssociation( to: destination.relationForAll, key: key, using: foreignKey) } /// Creates a ``HasManyAssociation`` between this table and the /// destination `Table`. /// /// For more information, see ``TableRecord/hasMany(_:key:using:)-45axo``. /// /// - parameters: /// - destination: The table at the other side of the association. /// - key: The association key. By default, it /// is `destination.tableName`. /// - foreignKey: An eventual foreign key. You need to provide one when /// no foreign key exists to the destination table, or several foreign /// keys exist. public func hasMany( _ destination: Table, key: String? = nil, using foreignKey: ForeignKey? = nil) -> HasManyAssociation { HasManyAssociation( to: destination.relationForAll, key: key, using: foreignKey) } /// Creates a ``HasOneAssociation`` between this table and the /// destination `Table`. /// /// For more information, see ``TableRecord/hasOne(_:key:using:)-4g9tm``. /// /// - parameters: /// - destination: The table at the other side of the association. /// - key: The association key. By default, it /// is `destination.tableName`. /// - foreignKey: An eventual foreign key. You need to provide one when /// no foreign key exists to the destination table, or several foreign /// keys exist. public func hasOne( _ destination: Table, key: String? = nil, using foreignKey: ForeignKey? = nil) -> HasOneAssociation { HasOneAssociation( to: destination.relationForAll, key: key, using: foreignKey) } } // MARK: - Associations to CommonTableExpression extension Table { /// Creates an association to a common table expression. /// /// For more information, see ``TableRecord/association(to:on:)``. /// /// - parameter cte: A common table expression. /// - parameter condition: A function that returns the joining clause. /// - parameter left: A `TableAlias` for the left table. /// - parameter right: A `TableAlias` for the right table. /// - returns: An association to the common table expression. public func association( to cte: CommonTableExpression, on condition: @escaping (_ left: TableAlias, _ right: TableAlias) -> any SQLExpressible) -> JoinAssociation { JoinAssociation( to: cte.relationForAll, condition: .expression { condition($0, $1).sqlExpression }) } /// Creates an association to a common table expression. /// /// For more information, see ``TableRecord/association(to:)``. /// /// - parameter cte: A common table expression. /// - returns: An association to the common table expression. public func association( to cte: CommonTableExpression) -> JoinAssociation { JoinAssociation(to: cte.relationForAll, condition: .none) } } // MARK: - "Through" Associations extension Table { /// Creates a ``HasManyThroughAssociation`` between this table and the /// destination type. /// /// For more information, see ``TableRecord/hasMany(_:through:using:key:)``. /// /// - parameters: /// - destination: The record type at the other side of the association. /// - pivot: An association from `Self` to the intermediate type. /// - target: A target association from the intermediate type to the /// destination type. /// - key: The association key. By default, it is the key of the target. public func hasMany( _ destination: Target.RowDecoder.Type, through pivot: Pivot, using target: Target, key: String? = nil) -> HasManyThroughAssociation where Pivot: Association, Target: Association, Pivot.OriginRowDecoder == RowDecoder, Pivot.RowDecoder == Target.OriginRowDecoder { let association = HasManyThroughAssociation(through: pivot, using: target) if let key { return association.forKey(key) } else { return association } } /// Creates a ``HasOneThroughAssociation`` between this table and the /// destination type. /// /// For more information, see ``TableRecord/hasOne(_:through:using:key:)``. /// /// - parameters: /// - destination: The record type at the other side of the association. /// - pivot: An association from Self to the intermediate type. /// - target: A target association from the intermediate type to the /// destination type. /// - key: An eventual decoding key for the association. By default, it /// is the same key as the target. public func hasOne( _ destination: Target.RowDecoder.Type, through pivot: Pivot, using target: Target, key: String? = nil) -> HasOneThroughAssociation where Pivot: AssociationToOne, Target: AssociationToOne, Pivot.OriginRowDecoder == RowDecoder, Pivot.RowDecoder == Target.OriginRowDecoder { let association = HasOneThroughAssociation(through: pivot, using: target) if let key { return association.forKey(key) } else { return association } } } // MARK: - Joining Methods extension Table { /// Returns a request that fetches all rows associated with each row /// in this request. /// /// See the ``TableRecord`` method ``TableRecord/including(all:)`` /// for more information. public func including(all association: A) -> QueryInterfaceRequest where A.OriginRowDecoder == RowDecoder { all().including(all: association) } /// Returns a request that fetches the eventual row associated with each /// row of this request. /// /// See the ``TableRecord`` method ``TableRecord/including(optional:)`` /// for more information. public func including(optional association: A) -> QueryInterfaceRequest where A.OriginRowDecoder == RowDecoder { all().including(optional: association) } /// Returns a request that fetches the row associated with each row in /// this request. Rows that do not have an associated row are discarded. /// /// See the ``TableRecord`` method ``TableRecord/including(required:)`` /// for more information. public func including(required association: A) -> QueryInterfaceRequest where A.OriginRowDecoder == RowDecoder { all().including(required: association) } /// Returns a request that joins each row of this request to its /// eventual associated row. /// /// See the ``TableRecord`` method ``TableRecord/including(optional:)`` /// for more information. public func joining(optional association: A) -> QueryInterfaceRequest where A.OriginRowDecoder == RowDecoder { all().joining(optional: association) } /// Returns a request that joins each row of this request to its /// associated row. Rows that do not have an associated row are discarded. /// /// See the ``TableRecord`` method ``TableRecord/including(required:)`` /// for more information. public func joining(required association: A) -> QueryInterfaceRequest where A.OriginRowDecoder == RowDecoder { all().joining(required: association) } /// Returns a request with the columns of the eventual associated row /// appended to the table columns. /// /// See the ``TableRecord`` method ``TableRecord/annotated(withOptional:)`` /// for more information. public func annotated(withOptional association: A) -> QueryInterfaceRequest where A.OriginRowDecoder == RowDecoder { all().annotated(withOptional: association) } /// Returns a request with the columns of the associated row appended to /// the table columns. Rows that do not have an associated row /// are discarded. /// /// See the ``TableRecord`` method ``TableRecord/annotated(withRequired:)`` /// for more information. public func annotated(withRequired association: A) -> QueryInterfaceRequest where A.OriginRowDecoder == RowDecoder { all().annotated(withRequired: association) } } // MARK: - Association Aggregates extension Table { /// Returns a request with the given association aggregates appended to /// the table colums. /// /// See the ``TableRecord`` method ``TableRecord/annotated(with:)-4xoen`` /// for more information. public func annotated(with aggregates: AssociationAggregate...) -> QueryInterfaceRequest { all().annotated(with: aggregates) } /// Returns a request with the given association aggregates appended to /// the table colums. /// /// See the ``TableRecord`` method ``TableRecord/annotated(with:)-8ce7u`` /// for more information. public func annotated(with aggregates: [AssociationAggregate]) -> QueryInterfaceRequest { all().annotated(with: aggregates) } /// Returns a request filtered according to the provided /// association aggregate. /// /// See the ``TableRecord`` method ``TableRecord/having(_:)`` /// for more information. public func having(_ predicate: AssociationAggregate) -> QueryInterfaceRequest { all().having(predicate) } } // MARK: - Batch Delete extension Table { /// Deletes all rows, and returns the number of deleted rows. /// /// - parameter db: A database connection. /// - returns: The number of deleted rows /// - throws: A ``DatabaseError`` whenever an SQLite error occurs. @discardableResult public func deleteAll(_ db: Database) throws -> Int { try all().deleteAll(db) } } // MARK: - Check Existence by Single-Column Primary Key extension Table { /// Returns whether a row exists for this primary key. /// /// All single-column primary keys are supported: /// /// ```swift /// let playerTable = Table("player") /// let countryTable = Table("country") /// /// try dbQueue.read { db in /// let playerExists = try playerTable.exists(db, key: 1) /// let countryExists = try countryTable.exists(db, key: "FR") /// } /// ``` /// /// - parameters: /// - db: A database connection. /// - key: A primary key value. /// - returns: Whether a row exists for this primary key. public func exists(_ db: Database, key: some DatabaseValueConvertible) throws -> Bool { try !filter(key: key).isEmpty(db) } } @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) extension Table where RowDecoder: Identifiable, RowDecoder.ID: DatabaseValueConvertible { /// Returns whether a row exists for this primary key. /// /// All single-column primary keys are supported: /// /// ```swift /// struct Player: Identifiable { /// var id: Int64 /// } /// struct Country: Identifiable { /// var id: String /// } /// /// let playerTable = Table("player") /// let countryTable = Table("country") /// /// try dbQueue.read { db in /// let playerExists = try playerTable.exists(db, id: 1) /// let countryExists = try countryTable.exists(db, id: "FR") /// } /// ``` /// /// - parameters: /// - db: A database connection. /// - id: A primary key value. /// - returns: Whether a row exists for this primary key. public func exists(_ db: Database, id: RowDecoder.ID) throws -> Bool { if id.databaseValue.isNull { // Don't hit the database return false } return try !filter(id: id).isEmpty(db) } } // MARK: - Check Existence by Key extension Table { /// Returns whether a row exists for this primary or unique key. /// /// For example: /// /// ```swift /// let playerTable = Table("player") /// let citizenshipTable = Table("citizenship") /// /// try dbQueue.read { db in /// let playerExists = playerTable.exists(db, key: ["id": 1]) /// let playerExists = playerTable.exists(db, key: ["email": "arthur@example.com"]) /// let citizenshipExists = citizenshipTable.exists(db, key: [ /// "citizenId": 1, /// "countryCode": "FR", /// ]) /// } /// ``` /// /// A fatal error is raised if no unique index exists on a subset of the /// key columns. /// /// - parameters: /// - db: A database connection. /// - key: A key dictionary. /// - returns: Whether a row exists for this key. public func exists(_ db: Database, key: [String: (any DatabaseValueConvertible)?]) throws -> Bool { try !filter(key: key).isEmpty(db) } } // MARK: - Deleting by Single-Column Primary Key extension Table { /// Deletes rows identified by their primary keys, and returns the number /// of deleted rows. /// /// All single-column primary keys are supported: /// /// ```swift /// let playerTable = Table("player") /// let countryTable = Table("country") /// /// try dbQueue.write { db in /// // DELETE FROM player WHERE id IN (1, 2, 3) /// try playerTable.deleteAll(db, keys: [1, 2, 3]) /// /// // DELETE FROM country WHERE code IN ('FR', 'US') /// try countryTable.deleteAll(db, keys: ["FR", "US"]) /// } /// ``` /// /// - parameters: /// - db: A database connection. /// - keys: A sequence of primary keys. /// - returns: The number of deleted rows. @discardableResult public func deleteAll(_ db: Database, keys: Keys) throws -> Int where Keys: Sequence, Keys.Element: DatabaseValueConvertible { let keys = Array(keys) if keys.isEmpty { // Avoid hitting the database return 0 } return try filter(keys: keys).deleteAll(db) } /// Deletes the row identified by its primary key, and returns whether a /// row was deleted. /// /// All single-column primary keys are supported: /// /// ```swift /// let playerTable = Table("player") /// let countryTable = Table("country") /// /// try dbQueue.write { db in /// // DELETE FROM player WHERE id = 1 /// try playerTable.deleteOne(db, key: 1) /// /// // DELETE FROM country WHERE code = 'FR' /// try countryTable.deleteOne(db, key: "FR") /// } /// ``` /// /// - parameters: /// - db: A database connection. /// - key: A primary key value. /// - returns: Whether a row was deleted. @discardableResult public func deleteOne(_ db: Database, key: some DatabaseValueConvertible) throws -> Bool { if key.databaseValue.isNull { // Don't hit the database return false } return try deleteAll(db, keys: [key]) > 0 } } @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) extension Table where RowDecoder: Identifiable, RowDecoder.ID: DatabaseValueConvertible { /// Deletes rows identified by their primary keys, and returns the number /// of deleted rows. /// /// All single-column primary keys are supported: /// /// ```swift /// struct Player: Identifiable { /// var id: Int64 /// } /// struct Country: Identifiable { /// var id: String /// } /// /// let playerTable = Table("player") /// let countryTable = Table("country") /// /// try dbQueue.write { db in /// // DELETE FROM player WHERE id IN (1, 2, 3) /// try playerTable.deleteAll(db, ids: [1, 2, 3]) /// /// // DELETE FROM country WHERE code IN ('FR', 'US') /// try countryTable.deleteAll(db, ids: ["FR", "US"]) /// } /// ``` /// /// - parameters: /// - db: A database connection. /// - ids: A collection of primary keys. /// - returns: The number of deleted rows. @discardableResult public func deleteAll(_ db: Database, ids: IDS) throws -> Int where IDS: Collection, IDS.Element == RowDecoder.ID { if ids.isEmpty { // Avoid hitting the database return 0 } return try filter(ids: ids).deleteAll(db) } /// Deletes the row identified by its primary key, and returns whether a /// row was deleted. /// /// All single-column primary keys are supported: /// /// ```swift /// struct Player: Identifiable { /// var id: Int64 /// } /// struct Country: Identifiable { /// var id: String /// } /// /// let playerTable = Table("player") /// let countryTable = Table("country") /// /// try dbQueue.write { db in /// // DELETE FROM player WHERE id = 1 /// try playerTable.deleteOne(db, id: 1) /// /// // DELETE FROM country WHERE code = 'FR' /// try countryTable.deleteOne(db, id: "FR") /// } /// ``` /// /// - parameters: /// - db: A database connection. /// - id: A primary key value. /// - returns: Whether a row was deleted. @discardableResult public func deleteOne(_ db: Database, id: RowDecoder.ID) throws -> Bool { if id.databaseValue.isNull { // Don't hit the database return false } return try deleteAll(db, ids: [id]) > 0 } } // MARK: - Deleting by Key extension Table { /// Deletes rows identified by their primary or unique keys, and returns /// the number of deleted rows. /// /// For example: /// /// ```swift /// let playerTable = Table("player") /// let citizenshipTable = Table("citizenship") /// /// try dbQueue.write { db in /// // DELETE FROM player WHERE id = 1 /// try playerTable.deleteAll(db, keys: [["id": 1]]) /// /// // DELETE FROM player WHERE email = 'arthur@example.com' /// try playerTable.deleteAll(db, keys: [["email": "arthur@example.com"]]) /// /// // DELETE FROM citizenship WHERE citizenId = 1 AND countryCode = 'FR' /// try citizenshipTable.deleteAll(db, keys: [ /// ["citizenId": 1, "countryCode": "FR"], /// ]) /// } /// ``` /// /// A fatal error is raised if no unique index exists on a subset of the /// key columns. /// /// - parameters: /// - db: A database connection. /// - keys: An array of key dictionaries. /// - returns: The number of deleted rows. @discardableResult public func deleteAll(_ db: Database, keys: [[String: (any DatabaseValueConvertible)?]]) throws -> Int { if keys.isEmpty { // Avoid hitting the database return 0 } return try filter(keys: keys).deleteAll(db) } /// Deletes the row identified by its primary or unique keys, and returns /// whether a row was deleted. /// /// For example: /// /// ```swift /// let playerTable = Table("player") /// let citizenshipTable = Table("citizenship") /// /// try dbQueue.write { db in /// // DELETE FROM player WHERE id = 1 /// try playerTable.deleteOne(db, key: ["id": 1]) /// /// // DELETE FROM player WHERE email = 'arthur@example.com' /// try playerTable.deleteOne(db, key: ["email": "arthur@example.com"]) /// /// // DELETE FROM citizenship WHERE citizenId = 1 AND countryCode = 'FR' /// try citizenshipTable.deleteOne(db, key: [ /// "citizenId": 1, /// "countryCode": "FR", /// ]) /// } /// ``` /// /// A fatal error is raised if no unique index exists on a subset of the /// key columns. /// - parameters: /// - db: A database connection. /// - key: A key dictionary. /// - returns: Whether a row was deleted. @discardableResult public func deleteOne(_ db: Database, key: [String: (any DatabaseValueConvertible)?]) throws -> Bool { try deleteAll(db, keys: [key]) > 0 } } // MARK: - Batch Update extension Table { /// Updates all rows, and returns the number of updated rows. /// /// For example: /// /// ```swift /// let playerTable = Table("player") /// /// try dbQueue.write { db in /// // UPDATE player SET score = 0 /// try playerTable.updateAll(db, [Column("score").set(to: 0)]) /// } /// ``` /// /// - parameter db: A database connection. /// - parameter conflictResolution: A policy for conflict resolution. /// - parameter assignments: An array of column assignments. /// - returns: The number of updated rows. /// - throws: A ``DatabaseError`` whenever an SQLite error occurs. @discardableResult public func updateAll( _ db: Database, onConflict conflictResolution: Database.ConflictResolution? = nil, _ assignments: [ColumnAssignment]) throws -> Int { try all().updateAll(db, onConflict: conflictResolution, assignments) } /// Updates all rows, and returns the number of updated rows. /// /// For example: /// /// ```swift /// let playerTable = Table("player") /// /// try dbQueue.write { db in /// // UPDATE player SET score = 0 /// try playerTable.updateAll(db, Column("score").set(to: 0)) /// } /// ``` /// /// - parameter db: A database connection. /// - parameter conflictResolution: A policy for conflict resolution. /// - parameter assignments: An array of column assignments. /// - returns: The number of updated rows. /// - throws: A ``DatabaseError`` whenever an SQLite error occurs. @discardableResult public func updateAll( _ db: Database, onConflict conflictResolution: Database.ConflictResolution? = nil, _ assignments: ColumnAssignment...) throws -> Int { try updateAll(db, onConflict: conflictResolution, assignments) } }