import Foundation // MARK: - TypedRequest /// A request that knows how to decode database rows. public protocol TypedRequest { /// The type that can decode database rows. /// /// For example, it is `Player` in the request below: /// /// ```swift /// let request = Player.all() /// ``` associatedtype RowDecoder } // MARK: - SelectionRequest /// A request that can define the selected columns. /// /// ## Topics /// /// ### The SELECT Clause /// /// - ``annotated(with:)-4qcem`` /// - ``annotated(with:)-6ehs4`` /// - ``annotatedWhenConnected(with:)`` /// - ``select(_:)-30yzl`` /// - ``select(_:)-7e2y5`` /// - ``select(literal:)`` /// - ``select(sql:arguments:)`` /// - ``selectWhenConnected(_:)`` public protocol SelectionRequest { /// Defines the result columns. /// /// The `selection` parameter is a closure that accepts a database /// connection and returns an array of result columns. It is evaluated when /// the request has an access to the database, and can perform database /// requests in order to build its result. /// /// For example: /// /// ```swift /// // SELECT id, name FROM player /// let request = Player.all().selectWhenConnected { db in /// [Column("id"), Column("name")] /// } /// ``` /// /// Any previous selection is discarded: /// /// ```swift /// // SELECT name FROM player /// let request = Player.all() /// .selectWhenConnected { db in [Column("id")] } /// .selectWhenConnected { db in [Column("name")] } /// ``` /// /// - parameter selection: A closure that accepts a database connection and /// returns an array of result columns. func selectWhenConnected(_ selection: @escaping (Database) throws -> [any SQLSelectable]) -> Self /// Appends result columns to the selected columns. /// /// The `selection` parameter is a closure that accepts a database /// connection and returns an array of result columns. It is evaluated when /// the request has an access to the database, and can perform database /// requests in order to build its result. /// /// For example: /// /// ```swift /// // SELECT *, score + bonus AS totalScore FROM player /// let request = Player.all().annotatedWhenConnected { db in /// [(Column("score") + Column("bonus")).forKey("totalScore")] /// } /// ``` /// /// - parameter selection: A closure that accepts a database connection and /// returns an array of result columns. func annotatedWhenConnected(with selection: @escaping (Database) throws -> [any SQLSelectable]) -> Self } extension SelectionRequest { /// Defines the result columns. /// /// For example: /// /// ```swift /// // SELECT id, score FROM player /// let request = Player.all().select([Column("id"), Column("score")]) /// ``` /// /// Any previous selection is replaced: /// /// ```swift /// // SELECT score FROM player /// let request = Player.all() /// .select([Column("id")]) /// .select([Column("score")]) /// ``` public func select(_ selection: [any SQLSelectable]) -> Self { selectWhenConnected { _ in selection } } /// Defines the result columns. /// /// For example: /// /// ```swift /// // SELECT id, score FROM player /// let request = Player.all().select(Column("id"), Column("score")) /// ``` /// /// Any previous selection is discarded: /// /// ```swift /// // SELECT score FROM player /// let request = Player.all() /// .select(Column("id")) /// .select(Column("score")) /// ``` public func select(_ selection: any SQLSelectable...) -> Self { select(selection) } /// Defines the result columns with an SQL string. /// /// For example: /// /// ```swift /// // SELECT id, name FROM player /// let request = Player.all() /// .select(sql: "id, name") /// /// // SELECT id, IFNULL(name, 'Anonymous') FROM player /// let defaultName = "Anonymous" /// let request = Player.all() /// .select(sql: "id, IFNULL(name, ?)", arguments: [defaultName]) /// ``` /// /// Any previous selection is discarded: /// /// ```swift /// // SELECT score FROM player /// let request = Player.all() /// .select(sql: "id") /// .select(sql: "name") /// ``` public func select(sql: String, arguments: StatementArguments = StatementArguments()) -> Self { select(SQL(sql: sql, arguments: arguments)) } /// Defines the result columns 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 /// // SELECT id, IFNULL(name, 'Anonymous') FROM player /// let defaultName = "Anonymous" /// let request = Player.all() /// .select(literal: "id, IFNULL(name, \(defaultName))") /// ``` /// /// Any previous selection is discarded: /// /// ```swift /// // SELECT IFNULL(name, 'Anonymous') FROM player /// let request = Player.all() /// .select(literal: "id") /// .select(literal: "IFNULL(name, \(defaultName))") /// ``` public func select(literal sqlLiteral: SQL) -> Self { // NOT TESTED select(sqlLiteral) } /// Appends result columns to the selected columns. /// /// For example: /// /// ```swift /// // SELECT *, score + bonus AS totalScore FROM player /// let totalScore = (Column("score") + Column("bonus")).forKey("totalScore") /// let request = Player.all().annotated(with: [totalScore]) /// ``` public func annotated(with selection: [any SQLSelectable]) -> Self { annotatedWhenConnected(with: { _ in selection }) } /// Appends result columns to the selected columns. /// /// For example: /// /// ```swift /// // SELECT *, score + bonus AS totalScore FROM player /// let totalScore = (Column("score") + Column("bonus")).forKey("totalScore") /// let request = Player.all().annotated(with: totalScore) /// ``` public func annotated(with selection: any SQLSelectable...) -> Self { annotated(with: selection) } } // MARK: - FilteredRequest /// A request that can filter database rows. /// /// The filter applies to the `WHERE` clause, or to the `ON` clause of /// an SQL join. /// /// ## Topics /// /// ### The WHERE and JOIN ON Clauses /// /// - ``all()`` /// - ``filter(_:)`` /// - ``filter(literal:)`` /// - ``filter(sql:arguments:)`` /// - ``filterWhenConnected(_:)`` /// - ``none()`` public protocol FilteredRequest { /// Filters the fetched rows with a boolean SQL expression. /// /// The `predicate` parameter is a closure that accepts a database /// connection and returns a boolean SQL expression. It is evaluated when /// the request has an access to the database, and can perform database /// requests in order to build its result. /// /// For example: /// /// ```swift /// // SELECT * FROM player WHERE name = 'O''Brien' /// let name = "O'Brien" /// let request = Player.all().filterWhenConnected { db in /// Column("name") == name /// } /// ``` /// /// - parameter predicate: A closure that accepts a database connection and /// returns a boolean SQL expression. func filterWhenConnected(_ predicate: @escaping (Database) throws -> any SQLExpressible) -> Self } extension FilteredRequest { // Accept SQLSpecificExpressible instead of SQLExpressible, so that we // prevent the `Player.filter(42)` misuse. // See https://github.com/groue/GRDB.swift/pull/864 /// Filters the fetched rows with a boolean SQL expression. /// /// For example: /// /// ```swift /// // SELECT * FROM player WHERE name = 'O''Brien' /// let name = "O'Brien" /// let request = Player.all().filter(Column("name") == name) /// ``` public func filter(_ predicate: some SQLSpecificExpressible) -> Self { filterWhenConnected { _ in predicate } } /// Filters the fetched rows with an SQL string. /// /// For example: /// /// ```swift /// // SELECT * FROM player WHERE name = 'O''Brien' /// let name = "O'Brien" /// let request = Player.all().filter(sql: "name = ?", arguments: [name]) /// ``` public func filter(sql: String, arguments: StatementArguments = StatementArguments()) -> Self { filter(SQL(sql: sql, arguments: arguments)) } /// Filters the fetched rows 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 /// // SELECT * FROM player WHERE name = 'O''Brien' /// let name = "O'Brien" /// let request = Player.all().filter(literal: "name = \(name)") /// ``` public func filter(literal sqlLiteral: SQL) -> Self { // NOT TESTED filter(sqlLiteral) } /// Returns an empty request that fetches no row. public func none() -> Self { filterWhenConnected { _ in false } } /// Returns `self`: a request that fetches all rows from this request. /// /// This method, which does nothing, exists in order to match ``none()``. public func all() -> Self { self } } // MARK: - TableRequest /// A request that feeds from a database table /// /// ## Topics /// /// ## The Database Table /// /// - ``databaseTableName`` /// /// ### Instance Methods /// /// - ``aliased(_:)`` /// - ``TableAlias`` /// /// ### The WHERE Clause /// /// - ``filter(id:)`` /// - ``filter(ids:)`` /// - ``filter(key:)-1p9sq`` /// - ``filter(key:)-2te6v`` /// - ``filter(keys:)-6ggt1`` /// - ``filter(keys:)-8fbn9`` /// - ``matching(_:)-3s3zr`` /// - ``matching(_:)-7c1e8`` /// /// ### The GROUP BY and HAVING Clauses /// /// - ``groupByPrimaryKey()`` /// /// ### The ORDER BY Clause /// /// - ``orderByPrimaryKey()`` public protocol TableRequest { /// The name of the database table var databaseTableName: String { get } /// Returns a request that can be referred to with the provided alias. /// /// Use this method when you need to refer to this request from /// another request. /// /// The first example fetches posthumous books: /// /// ```swift /// struct Author: TableRecord, FetchableRecord { } /// struct Book: TableRecord, FetchableRecord { /// static let author = belongsTo(Author.self) /// } /// /// // SELECT book.* /// // FROM book /// // JOIN author ON author.id = book.authorId /// // WHERE book.publishDate >= author.deathDate /// let authorAlias = TableAlias() /// let posthumousBooks = try Book /// .joining(required: Book.author.aliased(authorAlias)) /// .filter(Column("publishDate") >= authorAlias[Column("deathDate")]) /// .fetchAll(db) /// ``` /// /// The second example sorts books by author name first, and then by title: /// /// ```swift /// // SELECT book.* /// // FROM book /// // JOIN author ON author.id = book.authorId /// // ORDER BY author.name, book.title /// let authorAlias = TableAlias() /// let books = try Book /// .joining(required: Book.author.aliased(authorAlias)) /// .order(authorAlias[Column("name")], Column("title")) /// .fetchAll(db) /// ``` /// /// The third example uses named ``TableAlias`` so that SQL snippets can /// refer to SQL tables with those names: /// /// ```swift /// // SELECT b.* /// // FROM book b /// // JOIN author a ON a.id = b.authorId /// // AND a.countryCode = 'FR' /// // WHERE b.publishDate >= a.deathDate /// let bookAlias = TableAlias(name: "b") /// let authorAlias = TableAlias(name: "a") /// let posthumousFrenchBooks = try Book.aliased(bookAlias) /// .joining(required: Book.author.aliased(authorAlias) /// .filter(sql: "a.countryCode = ?", arguments: ["FR"])) /// .filter(sql: "b.publishDate >= a.deathDate") /// .fetchAll(db) /// ``` func aliased(_ alias: TableAlias) -> Self } extension TableRequest where Self: FilteredRequest, Self: TypedRequest { /// Filters by primary key. /// /// All single-column primary keys are supported: /// /// ```swift /// // SELECT * FROM player WHERE id = 1 /// let request = Player.all().filter(key: 1) /// /// // SELECT * FROM country WHERE code = 'FR' /// let request = Country.all().filter(key: "FR") /// ``` /// /// - parameter key: A primary key public func filter(key: some DatabaseValueConvertible) -> Self { if key.databaseValue.isNull { return none() } return filter(keys: [key]) } /// Filters by primary key. /// /// All single-column primary keys are supported: /// /// ```swift /// // SELECT * FROM player WHERE id = IN (1, 2, 3) /// let request = Player.all().filter(keys: [1, 2, 3]) /// /// // SELECT * FROM country WHERE code = IN ('FR', 'US') /// let request = Country.all().filter(keys: ["FR", "US"]) /// ``` /// /// - parameter keys: A collection of primary keys public func filter(keys: Sequence) -> Self where Sequence.Element: DatabaseValueConvertible { // In order to encode keys in the database, we perform a runtime check // for EncodableRecord, and look for a customized encoding strategy. // Such dynamic dispatch is unusual in GRDB, but static dispatch // (customizing TableRequest where RowDecoder: EncodableRecord) would // make it impractical to define `filter(id:)`, `fetchOne(_:key:)`, // `deleteAll(_:ids:)` etc. if let recordType = RowDecoder.self as? any EncodableRecord.Type { if Sequence.Element.self == Data.self || Sequence.Element.self == Optional.self { let strategy = recordType.databaseDataEncodingStrategy let keys = keys.compactMap { ($0 as! Data?).flatMap(strategy.encode)?.databaseValue } return filter(rawKeys: keys) } else if Sequence.Element.self == Date.self || Sequence.Element.self == Optional.self { let strategy = recordType.databaseDateEncodingStrategy let keys = keys.compactMap { ($0 as! Date?).flatMap(strategy.encode)?.databaseValue } return filter(rawKeys: keys) } else if Sequence.Element.self == UUID.self || Sequence.Element.self == Optional.self { let strategy = recordType.databaseUUIDEncodingStrategy let keys = keys.map { ($0 as! UUID?).map(strategy.encode)?.databaseValue } return filter(rawKeys: keys) } } return filter(rawKeys: keys) } /// Creates a request filtered by primary key. /// /// // SELECT * FROM player WHERE ... id IN (1, 2, 3) /// let request = try Player...filter(rawKeys: [1, 2, 3]) /// /// - parameter keys: A collection of primary keys func filter(rawKeys: Keys) -> Self where Keys: Sequence, Keys.Element: DatabaseValueConvertible { // Don't bother removing NULLs. We'd lose CPU cycles, and this does not // change the SQLite results anyway. let expressions = rawKeys.map { $0.databaseValue.sqlExpression } if expressions.isEmpty { // Don't hit the database return none() } let databaseTableName = self.databaseTableName return filterWhenConnected { db in let primaryKey = try db.primaryKey(databaseTableName) GRDBPrecondition( primaryKey.columns.count == 1, "Requesting by key requires a single-column primary key in the table \(databaseTableName)") return SQLCollection.array(expressions).contains(Column(primaryKey.columns[0]).sqlExpression) } } /// Filters by primary or unique key. /// /// For example: /// /// ```swift /// // SELECT * FROM player WHERE id = 1 /// let request = Player.all().filter(key: ["id": 1]) /// /// // SELECT * FROM player WHERE email = 'arthur@example.com' /// let request = Player.all().filter(key: ["email": "arthur@example.com"]) /// /// // SELECT * FROM citizenship WHERE citizenId = 1 AND countryCode = 'FR' /// let request = Citizenship.all().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)?]?) -> Self { guard let key else { return none() } return filter(keys: [key]) } /// Filters by primary or unique key. /// /// For example: /// /// ```swift /// // SELECT * FROM player WHERE id = 1 /// let request = Player.all().filter(keys: [["id": 1]]) /// /// // SELECT * FROM player WHERE email = 'arthur@example.com' /// let request = Player.all().filter(keys: [["email": "arthur@example.com"]]) /// /// // SELECT * FROM citizenship WHERE citizenId = 1 AND countryCode = 'FR' /// let request = Citizenship.all().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)?]]) -> Self { if keys.isEmpty { return none() } let databaseTableName = self.databaseTableName return filterWhenConnected { db in try keys .map { key in // Prevent filter(keys: [["foo": 1, "bar": 2]]) where // ("foo", "bar") do not contain a unique key (primary key // or unique index). guard let columns = try db.columnsForUniqueKey(key.keys, in: databaseTableName) else { fatalError(""" table \(databaseTableName) has no unique key on column(s) \ \(key.keys.sorted().joined(separator: ", ")) """) } let lowercaseColumns = columns.map { $0.lowercased() } return key // Preserve ordering of columns in the unique index .sorted { (kv1, kv2) in guard let index1 = lowercaseColumns.firstIndex(of: kv1.key.lowercased()) else { // We allow extra columns which are not in the unique key // Put them last in the query return false } guard let index2 = lowercaseColumns.firstIndex(of: kv2.key.lowercased()) else { // We allow extra columns which are not in the unique key // Put them last in the query return true } return index1 < index2 } .map { (column, value) in Column(column) == value } .joined(operator: .and) } .joined(operator: .or) } } } @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) extension TableRequest where Self: FilteredRequest, Self: TypedRequest, RowDecoder: Identifiable, RowDecoder.ID: DatabaseValueConvertible { /// Filters by primary key. /// /// All single-column primary keys are supported: /// /// ```swift /// // SELECT * FROM player WHERE id = 1 /// let request = Player.all().filter(id: 1) /// /// // SELECT * FROM country WHERE code = 'FR' /// let request = Country.all().filter(id: "FR") /// ``` /// /// - parameter id: A primary key public func filter(id: RowDecoder.ID) -> Self { filter(key: id) } /// Filters by primary key. /// /// All single-column primary keys are supported: /// /// ```swift /// // SELECT * FROM player WHERE id = IN (1, 2, 3) /// let request = Player.all().filter(ids: [1, 2, 3]) /// /// // SELECT * FROM country WHERE code = IN ('FR', 'US') /// let request = Country.all().filter(ids: ["FR", "US"]) /// ``` /// /// - parameter ids: A collection of primary keys public func filter(ids: IDS) -> Self where IDS: Collection, IDS.Element == RowDecoder.ID { filter(keys: ids) } } extension TableRequest where Self: OrderedRequest { /// Sorts the fetched rows according to the primary key. /// /// All primary keys are supported: /// /// ```swift /// // SELECT * FROM player ORDER BY id /// let request = Player.all().orderByPrimaryKey() /// /// // SELECT * FROM country ORDER BY code /// let request = Country.all().orderByPrimaryKey() /// /// // SELECT * FROM citizenship ORDER BY citizenId, countryCode /// let request = Citizenship.all().orderByPrimaryKey() /// ``` /// /// Any previous ordering is discarded. public func orderByPrimaryKey() -> Self { let tableName = self.databaseTableName return orderWhenConnected { db in try db.primaryKey(tableName).columns.map(SQLExpression.column) } } } extension TableRequest where Self: AggregatingRequest { /// Returns an aggregate request grouped on the primary key. /// /// Any previous grouping is discarded. public func groupByPrimaryKey() -> Self { let tableName = self.databaseTableName return groupWhenConnected { db in let primaryKey = try db.primaryKey(tableName) if let rowIDColumn = primaryKey.rowIDColumn { // Prefer the user-provided name of the rowid: // // // CREATE TABLE player (id INTEGER PRIMARY KEY, ...) // // SELECT * FROM player GROUP BY id // Player.all().groupByPrimaryKey() return [Column(rowIDColumn)] } else if primaryKey.tableHasRowID { // Prefer the rowid // // // CREATE TABLE player (uuid TEXT NOT NULL PRIMARY KEY, ...) // // SELECT * FROM player GROUP BY rowid // Player.all().groupByPrimaryKey() return [.rowID] } else { // WITHOUT ROWID table: group by primary key columns // // // CREATE TABLE player (uuid TEXT NOT NULL PRIMARY KEY, ...) WITHOUT ROWID // // SELECT * FROM player GROUP BY uuid // Player.all().groupByPrimaryKey() return primaryKey.columns.map { Column($0) } } } } } // MARK: - AggregatingRequest /// A request that can aggregate database rows. /// /// ## Topics /// /// ### The GROUP BY Clause /// /// - ``group(_:)-edak`` /// - ``group(_:)-4216o`` /// - ``group(literal:)`` /// - ``group(sql:arguments:)`` /// - ``groupWhenConnected(_:)`` /// /// ### The HAVING Clause /// /// - ``having(_:)`` /// - ``having(literal:)`` /// - ``having(sql:arguments:)`` /// - ``havingWhenConnected(_:)`` public protocol AggregatingRequest { /// Returns an aggregate request grouped on the given SQL expressions. /// /// The `expressions` parameter is a closure that accepts a database /// connection and returns an array of grouping SQL expressions. It is /// evaluated when the request has an access to the database, and can /// perform database requests in order to build its result. /// /// For example: /// /// ```swift /// // SELECT teamId, MAX(score) /// // FROM player /// // GROUP BY teamId /// let request = Player /// .select(Column("teamId"), max(Column("score"))) /// .groupWhenConnected { db in [Column("teamId")] } /// ``` /// /// Any previous grouping is discarded. /// /// - parameter expressions: A closure that accepts a database connection /// and returns an array of SQL expressions. func groupWhenConnected(_ expressions: @escaping (Database) throws -> [any SQLExpressible]) -> Self /// Filters the aggregated groups with a boolean SQL expression. /// /// The `predicate` parameter is a closure that accepts a database /// connection and returns a boolean SQL expression. It is evaluated when /// the request has an access to the database, and can perform database /// requests in order to build its result. /// /// For example: /// /// ```swift /// // SELECT teamId, MAX(score) /// // FROM player /// // GROUP BY teamId /// // HAVING MAX(score) > 1000 /// let request = Player /// .select(Column("teamId"), max(Column("score"))) /// .group(Column("teamId")) /// .havingWhenConnected { db in max(Column("score")) > 1000 } /// ``` /// /// - parameter predicate: A closure that accepts a database connection and /// returns a boolean SQL expression. func havingWhenConnected(_ predicate: @escaping (Database) throws -> any SQLExpressible) -> Self } extension AggregatingRequest { /// Returns an aggregate request grouped on the given SQL expressions. /// /// For example: /// /// ```swift /// // SELECT teamId, MAX(score) /// // FROM player /// // GROUP BY teamId /// let request = Player /// .select(Column("teamId"), max(Column("score"))) /// .group([Column("teamId")]) /// ``` /// /// Any previous grouping is discarded. /// /// - parameter expressions: An array of SQL expressions. public func group(_ expressions: [any SQLExpressible]) -> Self { groupWhenConnected { _ in expressions } } /// Returns an aggregate request grouped on the given SQL expressions. /// /// For example: /// /// ```swift /// // SELECT teamId, MAX(score) /// // FROM player /// // GROUP BY teamId /// let request = Player /// .select(Column("teamId"), max(Column("score"))) /// .group(Column("teamId")) /// ``` /// /// Any previous grouping is discarded. /// /// - parameter expressions: An array of SQL expressions. public func group(_ expressions: any SQLExpressible...) -> Self { group(expressions) } /// Returns an aggregate request grouped on an SQL string. /// /// For example: /// /// ```swift /// // SELECT teamId, MAX(score) /// // FROM player /// // GROUP BY teamId /// let request = Player /// .select(Column("teamId"), max(Column("score"))) /// .group(sql: "teamId") /// ``` /// /// Any previous grouping is discarded. public func group(sql: String, arguments: StatementArguments = StatementArguments()) -> Self { group(SQL(sql: sql, arguments: arguments)) } /// Returns an aggregate request grouped on an ``SQL`` literal. /// /// For example: /// /// ```swift /// // SELECT teamId, MAX(score) /// // FROM player /// // GROUP BY teamId /// let request = Player /// .select(Column("teamId"), max(Column("score"))) /// .group(literal: "teamId") /// ``` /// /// Any previous grouping is discarded. public func group(literal sqlLiteral: SQL) -> Self { // NOT TESTED group(sqlLiteral) } /// Filters the aggregated groups with a boolean SQL expression. /// /// For example: /// /// ```swift /// // SELECT teamId, MAX(score) /// // FROM player /// // GROUP BY teamId /// // HAVING MAX(score) > 1000 /// let request = Player /// .select(Column("teamId"), max(Column("score"))) /// .group(Column("teamId")) /// .having(max(Column("score")) > 1000) /// ``` public func having(_ predicate: some SQLExpressible) -> Self { havingWhenConnected { _ in predicate } } /// Filters the aggregated groups with an SQL string. /// /// For example: /// /// ```swift /// // SELECT teamId, MAX(score) /// // FROM player /// // GROUP BY teamId /// // HAVING MAX(score) > 1000 /// let request = Player /// .select(Column("teamId"), max(Column("score"))) /// .group(Column("teamId")) /// .having(sql: "MAX(score) > 1000") /// ``` public func having(sql: String, arguments: StatementArguments = StatementArguments()) -> Self { having(SQL(sql: sql, arguments: arguments)) } /// Filters the aggregated groups with an ``SQL`` literal. /// /// For example: /// /// ```swift /// // SELECT teamId, MAX(score) /// // FROM player /// // GROUP BY teamId /// // HAVING MAX(score) > 1000 /// let request = Player /// .select(Column("teamId"), max(Column("score"))) /// .group(Column("teamId")) /// .having(literal: "MAX(score) > 1000") /// ``` public func having(literal sqlLiteral: SQL) -> Self { // NOT TESTED having(sqlLiteral) } } // MARK: - OrderedRequest /// A request that can sort database rows. /// /// ## Topics /// /// ### The ORDER BY Clause /// /// - ``order(_:)-63rzl`` /// - ``order(_:)-6co0m`` /// - ``order(literal:)`` /// - ``order(sql:arguments:)`` /// - ``orderWhenConnected(_:)`` /// - ``reversed()`` /// - ``unordered()`` /// - ``withStableOrder()`` public protocol OrderedRequest { /// Sorts the fetched rows according to the given SQL ordering terms. /// /// The `orderings` parameter is a closure that accepts a database /// connection and returns an array of SQL ordering terms. It is evaluated /// when the request has an access to the database, and can perform database /// requests in order to build its result. /// /// For example: /// /// ```swift /// // SELECT * FROM player ORDER BY score DESC, name /// let request = Player.all().orderWhenConnected { db in /// [Column("score").desc, Column("name")] /// } /// ``` /// /// Any previous ordering is discarded: /// /// ```swift /// // SELECT * FROM player ORDER BY name /// let request = Player.all() /// .orderWhenConnected { db in [Column("score").desc] } /// .orderWhenConnected { db in [Column("name")] } /// ``` /// /// - parameter orderings: A closure that accepts a database connection and /// returns an array of SQL ordering terms. func orderWhenConnected(_ orderings: @escaping (Database) throws -> [any SQLOrderingTerm]) -> Self /// Returns a request with reversed ordering. /// /// For example: /// /// ```swift /// // SELECT * FROM player ORDER BY name DESC /// let request = Player.all() /// .order(Column("name")) /// .reversed() /// ``` /// /// If no ordering was already specified, this method has no effect: /// /// ```swift /// // SELECT * FROM player /// let request = Player.all().reversed() /// ``` func reversed() -> Self /// Returns a request without any ordering. /// /// For example: /// /// ```swift /// // SELECT * FROM player /// let request = Player.all() /// .order(Column("name")) /// .unordered() /// ``` func unordered() -> Self /// Returns a request with a stable order. /// /// The returned request lifts ordering ambiguities and always return /// its results in the same order. /// /// The purpose of this method is to make requests testable. func withStableOrder() -> Self } extension OrderedRequest { /// Sorts the fetched rows according to the given SQL ordering terms. /// /// For example: /// /// ```swift /// // SELECT * FROM player ORDER BY score DESC, name /// let request = Player.all() /// .order(Column("score").desc, Column("name")) /// ``` /// /// Any previous ordering is discarded: /// /// ```swift /// // SELECT * FROM player ORDER BY name /// let request = Player.all() /// .order(Column("score").desc) /// .order(Column("name")) /// ``` public func order(_ orderings: any SQLOrderingTerm...) -> Self { orderWhenConnected { _ in orderings } } /// Sorts the fetched rows according to the given SQL ordering terms. /// /// For example: /// /// ```swift /// // SELECT * FROM player ORDER BY score DESC, name /// let request = Player.all() /// .order([Column("score").desc, Column("name")]) /// ``` /// /// Any previous ordering is discarded: /// /// ```swift /// // SELECT * FROM player ORDER BY name /// let request = Player.all() /// .order([Column("score").desc]) /// .order([Column("name")]) /// ``` public func order(_ orderings: [any SQLOrderingTerm]) -> Self { orderWhenConnected { _ in orderings } } /// Sorts the fetched rows according to the given SQL string. /// /// For example: /// /// ```swift /// // SELECT * FROM player ORDER BY score DESC, name /// let request = Player.all() /// .order(sql: "score DESC, name") /// ``` /// /// Any previous ordering is discarded. public func order(sql: String, arguments: StatementArguments = StatementArguments()) -> Self { order(SQL(sql: sql, arguments: arguments)) } /// Sorts the fetched rows according to the given ``SQL`` literal. /// /// For example: /// /// ```swift /// // SELECT * FROM player ORDER BY score DESC, name /// let request = Player.all() /// .order(literal: "score DESC, name") /// ``` /// /// Any previous ordering is discarded. public func order(literal sqlLiteral: SQL) -> Self { // NOT TESTED order(sqlLiteral) } } // MARK: - JoinableRequest /// A request that can join and prefetch associations. /// /// `JoinableRequest` is adopted by ``QueryInterfaceRequest`` and all /// types conforming to ``Association``. /// /// It provides the methods that build requests involving several tables linked /// through associations. /// /// ## Topics /// /// ### Extending the Selection with Columns of Associated Records /// /// - ``annotated(withOptional:)`` /// - ``annotated(withRequired:)`` /// /// ### Prefetching Associated Records /// /// - ``including(all:)`` /// - ``including(optional:)`` /// - ``including(required:)`` /// /// ### Joining Associated Records /// /// - ``joining(optional:)`` /// - ``joining(required:)`` public protocol JoinableRequest: TypedRequest { /// Creates a request that prefetches an association. func _including(all association: _SQLAssociation) -> Self /// Creates a request that includes an association. The columns of the /// associated record are selected. The returned request does not /// require that the associated database table contains a matching row. func _including(optional association: _SQLAssociation) -> Self /// Creates a request that includes an association. The columns of the /// associated record are selected. The returned request requires /// that the associated database table contains a matching row. func _including(required association: _SQLAssociation) -> Self /// Creates a request that joins an association. The columns of the /// associated record are not selected. The returned request does not /// require that the associated database table contains a matching row. func _joining(optional association: _SQLAssociation) -> Self /// Creates a request that joins an association. The columns of the /// associated record are not selected. The returned request requires /// that the associated database table contains a matching row. func _joining(required association: _SQLAssociation) -> Self } extension JoinableRequest { /// Returns a request that fetches all records associated with each record /// in this request. /// /// For example, we can fetch authors along with their books: /// /// ```swift /// struct Author: TableRecord, FetchableRecord, Decodable { /// static let books = hasMany(Book.self) /// } /// struct Book: TableRecord, FetchableRecord, Decodable { } /// /// struct AuthorInfo: FetchableRecord, Decodable { /// var author: Author /// var books: [Book] /// } /// /// let authorInfos = try Author.all() /// .including(all: Author.books) /// .asRequest(of: AuthorInfo.self) /// .fetchAll(db) /// ``` public func including(all association: A) -> Self where A.OriginRowDecoder == RowDecoder { _including(all: association._sqlAssociation) } /// Returns a request that fetches the eventual record associated with each /// record of this request. /// /// For example, we can fetch books along with their eventual author: /// /// ```swift /// struct Author: TableRecord, FetchableRecord, Decodable { } /// struct Book: TableRecord, FetchableRecord, Decodable { /// static let author = belongsTo(Author.self) /// } /// /// struct BookInfo: FetchableRecord, Decodable { /// var book: Book /// var author: Author? /// } /// /// let bookInfos = try Book.all() /// .including(optional: Book.author) /// .asRequest(of: BookInfo.self) /// .fetchAll(db) /// ``` public func including(optional association: A) -> Self where A.OriginRowDecoder == RowDecoder { _including(optional: association._sqlAssociation) } /// Returns a request that fetches the record associated with each record in /// this request. Records that do not have an associated record /// are discarded. /// /// For example, we can fetch books along with their eventual author: /// /// ```swift /// struct Author: TableRecord, FetchableRecord, Decodable { } /// struct Book: TableRecord, FetchableRecord, Decodable { /// static let author = belongsTo(Author.self) /// } /// /// struct BookInfo: FetchableRecord, Decodable { /// var book: Book /// var author: Author /// } /// /// let bookInfos = try Book.all() /// .including(required: Book.author) /// .asRequest(of: BookInfo.self) /// .fetchAll(db) /// ``` public func including(required association: A) -> Self where A.OriginRowDecoder == RowDecoder { _including(required: association._sqlAssociation) } /// Returns a request that joins each record of this request to its /// eventual associated record. public func joining(optional association: A) -> Self where A.OriginRowDecoder == RowDecoder { _joining(optional: association._sqlAssociation) } /// Returns a request that joins each record of this request to its /// associated record. Records that do not have an associated record /// are discarded. /// /// For example, we can fetch only books whose author is French: /// /// ```swift /// struct Author: TableRecord, FetchableRecord, Decodable { } /// struct Book: TableRecord, FetchableRecord, Decodable { /// static let author = belongsTo(Author.self) /// } /// /// let frenchAuthors = Book.author.filter(Column("countryCode") == "FR") /// let bookInfos = try Book.all() /// .joining(required: frenchAuthors) /// .fetchAll(db) /// ``` public func joining(required association: A) -> Self where A.OriginRowDecoder == RowDecoder { _joining(required: association._sqlAssociation) } } extension JoinableRequest where Self: SelectionRequest { /// Appends the columns of the eventual associated record to the /// selected columns. /// /// For example: /// /// ```swift /// // SELECT player.*, team.color /// // FROM player LEFT JOIN team ... /// let teamColor = Player.team.select(Column("color")) /// let request = Player.all().annotated(withOptional: teamColor) /// ``` /// /// This method performs the exact same SQL request as /// ``including(optional:)``. The difference is in the record type that can /// decode such a request: the columns of the associated record must be /// decoded at the same level as the main record. For example: /// /// ```swift /// struct PlayerWithTeamColor: FetchableRecord, Decodable { /// var player: Player /// var color: String? /// } /// try dbQueue.read { db in /// let players = try request /// .asRequest(of: PlayerWithTeamColor.self) /// .fetchAll(db) /// } /// ``` /// /// This method is a convenience. You can build the same request with /// ``TableAlias``, ``SelectionRequest/annotated(with:)-6ehs4``, and /// ``JoinableRequest/joining(optional:)``: /// /// ```swift /// let teamAlias = TableAlias() /// let request = Player.all() /// .annotated(with: teamAlias[Column("color")]) /// .joining(optional: Player.team.aliased(teamAlias)) /// ``` public func annotated(withOptional association: A) -> Self where A.OriginRowDecoder == RowDecoder { // TODO: find a way to prefix the selection with the association key let alias = TableAlias() let selection = association._sqlAssociation.destination.relation.selectionPromise return self .joining(optional: association.aliased(alias)) .annotatedWhenConnected(with: { db in try selection.resolve(db).map { selection in selection.qualified(with: alias) } }) } /// Appends the columns of the associated record to the selected columns. /// Records that do not have an associated record are discarded. /// /// For example: /// /// ```swift /// // SELECT player.*, team.color /// // FROM player JOIN team ... /// let teamColor = Player.team.select(Column("color")) /// let request = Player.all().annotated(withRequired: teamColor) /// ``` /// /// This method performs the exact same SQL request as /// ``including(required:)``. The difference is in the record type that can /// decode such a request: the columns of the associated record must be /// decoded at the same level as the main record. For example: /// /// ```swift /// struct PlayerWithTeamColor: FetchableRecord, Decodable { /// var player: Player /// var color: String /// } /// try dbQueue.read { db in /// let players = try request /// .asRequest(of: PlayerWithTeamColor.self) /// .fetchAll(db) /// } /// ``` /// /// This method is a convenience. You can build the same request with /// ``TableAlias``, ``SelectionRequest/annotated(with:)-6ehs4``, and /// ``JoinableRequest/joining(required:)``: /// /// ```swift /// let teamAlias = TableAlias() /// let request = Player.all() /// .annotated(with: teamAlias[Column("color")]) /// .joining(required: Player.team.aliased(teamAlias)) /// ``` public func annotated(withRequired association: A) -> Self where A.OriginRowDecoder == RowDecoder { // TODO: find a way to prefix the selection with the association key let selection = association._sqlAssociation.destination.relation.selectionPromise let alias = TableAlias() return self .joining(required: association.aliased(alias)) .annotatedWhenConnected(with: { db in try selection.resolve(db).map { selection in selection.qualified(with: alias) } }) } } // MARK: - DerivableRequest /// `DerivableRequest` is the base protocol for ``QueryInterfaceRequest`` /// and ``Association``. /// /// Most features of `DerivableRequest` come from the protocols it /// inherits from. /// /// ## Topics /// /// ### Instance Methods /// /// - ``TableRequest/aliased(_:)`` /// - ``TableAlias`` /// /// ### The WITH Clause /// /// - ``with(_:)`` /// /// ### The SELECT Clause /// /// - ``SelectionRequest/annotated(with:)-4qcem`` /// - ``SelectionRequest/annotated(with:)-6ehs4`` /// - ``SelectionRequest/annotatedWhenConnected(with:)`` /// - ``distinct()`` /// - ``SelectionRequest/select(_:)-30yzl`` /// - ``SelectionRequest/select(_:)-7e2y5`` /// - ``SelectionRequest/select(literal:)`` /// - ``SelectionRequest/select(sql:arguments:)`` /// - ``SelectionRequest/selectWhenConnected(_:)`` /// /// ### The WHERE Clause /// /// - ``FilteredRequest/all()`` /// - ``FilteredRequest/filter(_:)`` /// - ``TableRequest/filter(id:)`` /// - ``TableRequest/filter(ids:)`` /// - ``TableRequest/filter(key:)-1p9sq`` /// - ``TableRequest/filter(key:)-2te6v`` /// - ``TableRequest/filter(keys:)-6ggt1`` /// - ``TableRequest/filter(keys:)-8fbn9`` /// - ``FilteredRequest/filter(literal:)`` /// - ``FilteredRequest/filter(sql:arguments:)`` /// - ``FilteredRequest/filterWhenConnected(_:)`` /// - ``TableRequest/matching(_:)-3s3zr`` /// - ``TableRequest/matching(_:)-7c1e8`` /// - ``FilteredRequest/none()`` /// /// ### The GROUP BY and HAVING Clauses /// /// - ``AggregatingRequest/group(_:)-edak`` /// - ``AggregatingRequest/group(_:)-4216o`` /// - ``AggregatingRequest/group(literal:)`` /// - ``AggregatingRequest/group(sql:arguments:)`` /// - ``TableRequest/groupByPrimaryKey()`` /// - ``AggregatingRequest/groupWhenConnected(_:)`` /// - ``AggregatingRequest/having(_:)`` /// - ``AggregatingRequest/having(literal:)`` /// - ``AggregatingRequest/having(sql:arguments:)`` /// - ``AggregatingRequest/havingWhenConnected(_:)`` /// /// ### The ORDER BY Clause /// /// - ``OrderedRequest/order(_:)-63rzl`` /// - ``OrderedRequest/order(_:)-6co0m`` /// - ``OrderedRequest/order(literal:)`` /// - ``OrderedRequest/order(sql:arguments:)`` /// - ``OrderedRequest/orderWhenConnected(_:)`` /// - ``TableRequest/orderByPrimaryKey()`` /// - ``OrderedRequest/reversed()`` /// - ``OrderedRequest/unordered()`` /// - ``OrderedRequest/withStableOrder()`` /// /// ### Associations /// /// - ``JoinableRequest/annotated(withOptional:)`` /// - ``JoinableRequest/annotated(withRequired:)`` /// - ``annotated(with:)-74xfs`` /// - ``annotated(with:)-8snn4`` /// - ``having(_:)`` /// - ``JoinableRequest/including(all:)`` /// - ``JoinableRequest/including(optional:)`` /// - ``JoinableRequest/including(required:)`` /// - ``JoinableRequest/joining(optional:)`` /// - ``JoinableRequest/joining(required:)`` /// /// ### Supporting Types /// /// - ``AggregatingRequest`` /// - ``FilteredRequest`` /// - ``JoinableRequest`` /// - ``OrderedRequest`` /// - ``SelectionRequest`` /// - ``TableRequest`` /// - ``TypedRequest`` public protocol DerivableRequest: AggregatingRequest, FilteredRequest, JoinableRequest, OrderedRequest, SelectionRequest, TableRequest { /// Returns a request which returns distinct rows. /// /// For example: /// /// ```swift /// // SELECT DISTINCT * FROM player /// let request = Player.all().distinct() /// /// // SELECT DISTINCT name FROM player /// let request = Player.select(Column("name")).distinct() /// ``` func distinct() -> Self /// Embeds a common table expression. /// /// If a common table expression with the same table name had already been /// embedded, it is replaced by the new one. /// /// For example, you can build a request that fetches all chats with their /// latest message: /// /// ```swift /// let latestMessageRequest = Message /// .annotated(with: max(Column("date"))) /// .group(Column("chatID")) /// /// let latestMessageCTE = CommonTableExpression( /// named: "latestMessage", /// request: latestMessageRequest) /// /// let latestMessageAssociation = Chat.association( /// to: latestMessageCTE, /// on: { chat, latestMessage in /// chat[Column("id")] == latestMessage[Column("chatID")] /// }) /// /// // WITH latestMessage AS /// // (SELECT *, MAX(date) FROM message GROUP BY chatID) /// // SELECT chat.*, latestMessage.* /// // FROM chat /// // LEFT JOIN latestMessage ON chat.id = latestMessage.chatID /// let request = Chat.all() /// .with(latestMessageCTE) /// .including(optional: latestMessageAssociation) /// ``` func with(_ cte: CommonTableExpression) -> Self } // Association aggregates don't require all DerivableRequest abilities. The // minimum set of requirements is: // // - AggregatingRequest, for the GROUP BY and HAVING clauses // - TableRequest, for grouping by primary key // - JoinableRequest, for joining associations // - SelectionRequest, for annotating the selection // // It is just that extending DerivableRequest is simpler. We want the user to // use aggregates on QueryInterfaceRequest and associations: both conform to // DerivableRequest already. extension DerivableRequest { private func annotated(with aggregate: AssociationAggregate) -> Self { var request = self let expression = aggregate.prepare(&request) if let key = aggregate.key { return request.annotated(with: expression.forKey(key)) } else { return request.annotated(with: expression) } } /// Appends association aggregates to the selected columns. /// /// For example: /// /// ```swift /// struct Author: TableRecord, FetchableRecord, Decodable { /// static let books = hasMany(Book.self) /// } /// struct Book: TableRecord, FetchableRecord, Decodable { } /// /// struct AuthorInfo: FetchableRecord, Decodable { /// var author: Author /// var bookCount: Int /// } /// /// // SELECT author.*, COUNT(DISTINCT book.id) AS bookCount /// // FROM author /// // LEFT JOIN book ON book.authorId = author.id /// // GROUP BY author.id /// let authorInfos = try Author.all() /// .annotated(with: Author.books.count) /// .asRequest(of: AuthorInfo.self) /// .fetchAll(db) /// ``` public func annotated(with aggregates: AssociationAggregate...) -> Self { annotated(with: aggregates) } /// Appends association aggregates to the selected columns. /// /// For example: /// /// ```swift /// struct Author: TableRecord, FetchableRecord, Decodable { /// static let books = hasMany(Book.self) /// } /// struct Book: TableRecord, FetchableRecord, Decodable { } /// /// struct AuthorInfo: FetchableRecord, Decodable { /// var author: Author /// var bookCount: Int /// } /// /// // SELECT author.*, COUNT(DISTINCT book.id) AS bookCount /// // FROM author /// // LEFT JOIN book ON book.authorId = author.id /// // GROUP BY author.id /// let authorInfos = try Author.all() /// .annotated(with: [Author.books.count]) /// .asRequest(of: AuthorInfo.self) /// .fetchAll(db) /// ``` public func annotated(with aggregates: [AssociationAggregate]) -> Self { aggregates.reduce(self) { request, aggregate in request.annotated(with: aggregate) } } /// Filters the fetched records with an association aggregate. /// /// For example: /// /// ```swift /// struct Author: TableRecord, FetchableRecord { /// static let books = hasMany(Book.self) /// } /// struct Book: TableRecord, FetchableRecord { } /// /// // SELECT author.* /// // FROM author /// // LEFT JOIN book ON book.authorId = author.id /// // GROUP BY author.id /// // HAVING COUNT(DISTINCT book.id) > 5 /// let authors = try Author.all() /// .having(Author.books.count > 5) /// .fetchAll(db) /// ``` public func having(_ predicate: AssociationAggregate) -> Self { var request = self let expression = predicate.prepare(&request) return request.having(expression) } }