Files
UUVPN/iOS-SwiftUI-Code/GRDB.swift-6.29.3/GRDB/QueryInterface/SQL/Table.swift
T
2025-01-28 12:28:03 +08:00

1912 lines
61 KiB
Swift

/// 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<RowDecoder>: 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<Row>("player")
/// let table = Table<Player>("player")
/// ```
public init(_ tableName: String) {
self.init(tableName, RowDecoder.self)
}
}
extension Table where RowDecoder == Row {
/// Create a `Table<Row>`.
///
/// For example:
///
/// ```swift
/// let table = Table("player") // Table<Row>
/// ```
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>("player")
/// let request = table.all()
/// let players: [Player] = try request.fetchAll(db)
/// ```
public func all() -> QueryInterfaceRequest<RowDecoder> {
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<RowDecoder> {
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<RowDecoder> {
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<RowDecoder> {
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<RowDecoder>
{
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<RowDecoder> {
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<T>(
_ selection: [any SQLSelectable],
as type: T.Type = T.self)
-> QueryInterfaceRequest<T>
{
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<T>(
_ selection: any SQLSelectable...,
as type: T.Type = T.self)
-> QueryInterfaceRequest<T>
{
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<T>(
sql: String,
arguments: StatementArguments = StatementArguments(),
as type: T.Type = T.self)
-> QueryInterfaceRequest<T>
{
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<T>(
literal sqlLiteral: SQL,
as type: T.Type = T.self)
-> QueryInterfaceRequest<T>
{
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<PrimaryKey>(as type: PrimaryKey.Type = PrimaryKey.self)
-> QueryInterfaceRequest<PrimaryKey>
{
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<RowDecoder> {
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<RowDecoder> {
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<RowDecoder> {
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<RowDecoder> {
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: Keys)
-> QueryInterfaceRequest<RowDecoder>
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<RowDecoder> {
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<RowDecoder> {
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<RowDecoder>
{
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<RowDecoder> {
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<RowDecoder> {
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<RowDecoder> {
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<RowDecoder> {
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<RowDecoder>
{
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<RowDecoder> {
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<RowDecoder> {
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<RowDecoder> {
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<T>(_ cte: CommonTableExpression<T>) -> QueryInterfaceRequest<RowDecoder> {
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>("player")
/// let countryTable = Table<Country>("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<RowDecoder> {
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>("player")
/// let countryTable = Table<Country>("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: IDS) -> QueryInterfaceRequest<RowDecoder>
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>("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<RowDecoder> {
try all().fetchCursor(db)
}
/// Returns an array of all records fetched from the database.
///
/// For example:
///
/// ```swift
/// struct Player: FetchableRecord { }
/// let playerTable = Table<Player>("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>("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>("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<RowDecoder> {
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<Row> {
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<RowDecoder> {
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<RowDecoder> {
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<RowDecoder> {
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<RowDecoder> {
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: Destination.Type,
key: String? = nil,
using foreignKey: ForeignKey? = nil)
-> BelongsToAssociation<RowDecoder, Destination>
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: Destination.Type,
key: String? = nil,
using foreignKey: ForeignKey? = nil)
-> HasManyAssociation<RowDecoder, Destination>
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: Destination.Type,
key: String? = nil,
using foreignKey: ForeignKey? = nil)
-> HasOneAssociation<RowDecoder, Destination>
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>(
_ destination: Table<Destination>,
key: String? = nil,
using foreignKey: ForeignKey? = nil)
-> BelongsToAssociation<RowDecoder, Destination>
{
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>(
_ destination: Table<Destination>,
key: String? = nil,
using foreignKey: ForeignKey? = nil)
-> HasManyAssociation<RowDecoder, Destination>
{
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>(
_ destination: Table<Destination>,
key: String? = nil,
using foreignKey: ForeignKey? = nil)
-> HasOneAssociation<RowDecoder, Destination>
{
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<Destination>(
to cte: CommonTableExpression<Destination>,
on condition: @escaping (_ left: TableAlias, _ right: TableAlias) -> any SQLExpressible)
-> JoinAssociation<RowDecoder, Destination>
{
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<Destination>(
to cte: CommonTableExpression<Destination>)
-> JoinAssociation<RowDecoder, Destination>
{
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<Pivot, Target>(
_ destination: Target.RowDecoder.Type,
through pivot: Pivot,
using target: Target,
key: String? = nil)
-> HasManyThroughAssociation<RowDecoder, Target.RowDecoder>
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<Pivot, Target>(
_ destination: Target.RowDecoder.Type,
through pivot: Pivot,
using target: Target,
key: String? = nil)
-> HasOneThroughAssociation<RowDecoder, Target.RowDecoder>
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<A: AssociationToMany>(all association: A)
-> QueryInterfaceRequest<RowDecoder>
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<A: Association>(optional association: A)
-> QueryInterfaceRequest<RowDecoder>
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<A: Association>(required association: A)
-> QueryInterfaceRequest<RowDecoder>
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<A: Association>(optional association: A)
-> QueryInterfaceRequest<RowDecoder>
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<A: Association>(required association: A)
-> QueryInterfaceRequest<RowDecoder>
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<A: Association>(withOptional association: A)
-> QueryInterfaceRequest<RowDecoder>
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<A: Association>(withRequired association: A)
-> QueryInterfaceRequest<RowDecoder>
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<RowDecoder>...) -> QueryInterfaceRequest<RowDecoder> {
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<RowDecoder>]) -> QueryInterfaceRequest<RowDecoder> {
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<RowDecoder>) -> QueryInterfaceRequest<RowDecoder> {
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>("player")
/// let countryTable = Table<Country>("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<Keys>(_ 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>("player")
/// let countryTable = Table<Country>("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<IDS>(_ 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>("player")
/// let countryTable = Table<Country>("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)
}
}