Files
UUVPN/iOS-SwiftUI-Code/GRDB.swift-6.29.3/GRDB/QueryInterface/Request/Association/AssociationAggregate.swift
T
2025-01-22 14:09:10 +08:00

884 lines
28 KiB
Swift

import Foundation
extension AssociationToMany {
private func makeAggregate(_ expression: SQLExpression) -> AssociationAggregate<OriginRowDecoder> {
AssociationAggregate(preparation: BasePreparation(association: self, expression: expression))
}
/// The number of associated records.
///
/// For example:
///
/// ```swift
/// struct Player: TableRecord { }
/// struct Team: FetchableRecord, TableRecord {
/// static let players = Team.hasMany(Player.self)
/// }
///
/// try dbQueue.read { db in
/// // Fetch all teams with at least ten players:
/// let teams: [Team] = try Team
/// .having(Team.players.count >= 10)
/// .fetchAll(db)
/// }
/// ```
///
/// The returned association aggregate is named `"[key]Count"`, where `key`
/// is the association key. For example:
///
/// ```swift
/// struct TeamInfo: FetchableRecord, Decodable {
/// var team: Team
/// var playerCount: Int
/// }
///
/// try dbQueue.read { db in
/// let infos: [TeamInfo] = try Team
/// .annotated(with: Team.players.count)
/// .asRequest(of: TeamInfo.self)
/// .fetchAll(db)
/// }
/// ```
public var count: AssociationAggregate<OriginRowDecoder> {
makeAggregate(.countDistinct(.fastPrimaryKey))
.forKey("\(key.singularizedName)Count")
}
/// Returns a boolean aggregate that is true if no associated
/// records exist.
///
/// For example:
///
/// ```swift
/// struct Player: TableRecord { }
/// struct Team: FetchableRecord, TableRecord {
/// static let players = Team.hasMany(Player.self)
/// }
///
/// try dbQueue.read { db in
/// // Fetch all teams without any player
/// let teams: [Team] = try Team
/// .having(Team.players.isEmpty)
/// .fetchAll(db)
///
/// // Fetch all teams without some player
/// let teams: [Team] = try Team
/// .having(Team.players.isEmpty == false)
/// .fetchAll(db)
/// }
/// ```
///
/// The returned association aggregate is named `"hasNo[key]"`, where `key`
/// is the association key. For example:
///
/// ```swift
/// struct TeamInfo: FetchableRecord, Decodable {
/// var team: Team
/// var hasNoPlayer: Int
/// }
///
/// try dbQueue.read { db in
/// let infos: [TeamInfo] = try Team
/// .annotated(with: Team.players.isEmpty)
/// .asRequest(of: TeamInfo.self)
/// .fetchAll(db)
/// }
/// ```
public var isEmpty: AssociationAggregate<OriginRowDecoder> {
makeAggregate(.isEmpty(.countDistinct(.fastPrimaryKey)))
.forKey("hasNo\(key.singularizedName.uppercasingFirstCharacter)")
}
/// Returns the average of the given expression in associated records.
///
/// For example:
///
/// ```swift
/// struct Player: TableRecord { }
/// struct Team: FetchableRecord, TableRecord {
/// static let players = Team.hasMany(Player.self)
/// }
///
/// try dbQueue.read { db in
/// // Fetch all teams whose average player score is greater than 1000
/// let averageScore = Team.players.average(Column("score"))
/// let teams: [Team] = try Team
/// .having(averageScore >= 1000)
/// .fetchAll(db)
/// }
/// ```
///
/// When the input expression is a ``ColumnExpression``, the returned
/// association aggregate is named `"average[Key][Column]"`, where `key` is
/// the association key. For example:
///
/// ```swift
/// struct TeamInfo: FetchableRecord, Decodable {
/// var team: Team
/// var averagePlayerScore: Double
/// }
///
/// try dbQueue.read { db in
/// let averageScore = Team.players.average(Column("score"))
/// let infos: [TeamInfo] = try Team
/// .annotated(with: averageScore)
/// .asRequest(of: TeamInfo.self)
/// .fetchAll(db)
/// }
/// ```
public func average(_ expression: some SQLSpecificExpressible) -> AssociationAggregate<OriginRowDecoder> {
let aggregate = makeAggregate(.function("AVG", [expression.sqlExpression]))
if let column = expression as? any ColumnExpression {
let name = key.singularizedName
return aggregate.forKey("average\(name.uppercasingFirstCharacter)\(column.name.uppercasingFirstCharacter)")
} else {
return aggregate
}
}
/// Returns the maximum value of the given expression in associated records.
///
/// For example:
///
/// ```swift
/// struct Player: TableRecord { }
/// struct Team: FetchableRecord, TableRecord {
/// static let players = Team.hasMany(Player.self)
/// }
///
/// try dbQueue.read { db in
/// // Fetch all teams whose maximum player score is greater than 1000
/// let maxScore = Team.players.max(Column("score"))
/// let teams: [Team] = try Team
/// .having(maxScore >= 1000)
/// .fetchAll(db)
/// }
/// ```
///
/// When the input expression is a ``ColumnExpression``, the returned
/// association aggregate is named `"maximum[Key][Column]"`, where `key` is
/// the association key. For example:
///
/// ```swift
/// struct TeamInfo: FetchableRecord, Decodable {
/// var team: Team
/// var maximumPlayerScore: Double
/// }
///
/// try dbQueue.read { db in
/// let maxScore = Team.players.max(Column("score"))
/// let infos: [TeamInfo] = try Team
/// .annotated(with: maxScore)
/// .asRequest(of: TeamInfo.self)
/// .fetchAll(db)
/// }
/// ```
public func max(_ expression: some SQLSpecificExpressible) -> AssociationAggregate<OriginRowDecoder> {
let aggregate = makeAggregate(.function("MAX", [expression.sqlExpression]))
if let column = expression as? any ColumnExpression {
let name = key.singularizedName
return aggregate.forKey("max\(name.uppercasingFirstCharacter)\(column.name.uppercasingFirstCharacter)")
} else {
return aggregate
}
}
/// Returns the minimum value of the given expression in associated records.
///
/// For example:
///
/// ```swift
/// struct Player: TableRecord { }
/// struct Team: FetchableRecord, TableRecord {
/// static let players = Team.hasMany(Player.self)
/// }
///
/// try dbQueue.read { db in
/// // Fetch all teams whose minimum player score is less than 1000
/// let minScore = Team.players.min(Column("score"))
/// let teams: [Team] = try Team
/// .having(minScore < 1000)
/// .fetchAll(db)
/// }
/// ```
///
/// When the input expression is a ``ColumnExpression``, the returned
/// association aggregate is named `"minimum[Key][Column]"`, where `key` is
/// the association key. For example:
///
/// ```swift
/// struct TeamInfo: FetchableRecord, Decodable {
/// var team: Team
/// var minimumPlayerScore: Double
/// }
///
/// try dbQueue.read { db in
/// let minScore = Team.players.min(Column("score"))
/// let infos: [TeamInfo] = try Team
/// .annotated(with: minScore)
/// .asRequest(of: TeamInfo.self)
/// .fetchAll(db)
/// }
/// ```
public func min(_ expression: some SQLSpecificExpressible) -> AssociationAggregate<OriginRowDecoder> {
let aggregate = makeAggregate(.function("MIN", [expression.sqlExpression]))
if let column = expression as? any ColumnExpression {
let name = key.singularizedName
return aggregate.forKey("min\(name.uppercasingFirstCharacter)\(column.name.uppercasingFirstCharacter)")
} else {
return aggregate
}
}
/// Returns the sum of the given expression in associated records.
///
/// This aggregate invokes the `SUM` SQL function. See also ``total(_:)``
/// and <https://www.sqlite.org/lang_aggfunc.html#sumunc>.
///
/// For example:
///
/// ```swift
/// struct Player: TableRecord { }
/// struct Team: FetchableRecord, TableRecord {
/// static let players = Team.hasMany(Player.self)
/// }
///
/// try dbQueue.read { db in
/// // Fetch all teams whose sum of player scores is greater than 1000
/// let scoreSum = Team.players.sum(Column("score"))
/// let teams: [Team] = try Team
/// .having(scoreSum >= 1000)
/// .fetchAll(db)
/// }
/// ```
///
/// When the input expression is a ``ColumnExpression``, the returned
/// association aggregate is named `"[key][Column]Sum"`, where `key` is the
/// association key. For example:
///
/// ```swift
/// struct TeamInfo: FetchableRecord, Decodable {
/// var team: Team
/// var playerScoreSum: Double
/// }
///
/// try dbQueue.read { db in
/// let scoreSum = Team.players.sum(Column("score"))
/// let infos: [TeamInfo] = try Team
/// .annotated(with: scoreSum)
/// .asRequest(of: TeamInfo.self)
/// .fetchAll(db)
/// }
/// ```
public func sum(_ expression: some SQLSpecificExpressible) -> AssociationAggregate<OriginRowDecoder> {
let aggregate = makeAggregate(.function("SUM", [expression.sqlExpression]))
if let column = expression as? any ColumnExpression {
let name = key.singularizedName
return aggregate.forKey("\(name)\(column.name.uppercasingFirstCharacter)Sum")
} else {
return aggregate
}
}
/// Returns the sum of the given expression in associated records.
///
/// This aggregate invokes the `TOTAL` SQL function. See also ``sum(_:)``
/// and <https://www.sqlite.org/lang_aggfunc.html#sumunc>.
///
/// For example:
///
/// ```swift
/// struct Player: TableRecord { }
/// struct Team: FetchableRecord, TableRecord {
/// static let players = Team.hasMany(Player.self)
/// }
///
/// try dbQueue.read { db in
/// // Fetch all teams whose sum of player scores is greater than 1000
/// let totalScore = Team.players.total(Column("score"))
/// let teams: [Team] = try Team
/// .having(totalScore >= 1000)
/// .fetchAll(db)
/// }
/// ```
///
/// When the input expression is a ``ColumnExpression``, the returned
/// association aggregate is named `"[key][Column]Sum"`, where `key` is the
/// association key. For example:
///
/// ```swift
/// struct TeamInfo: FetchableRecord, Decodable {
/// var team: Team
/// var playerScoreSum: Double
/// }
///
/// try dbQueue.read { db in
/// let totalScore = Team.players.total(Column("score"))
/// let infos: [TeamInfo] = try Team
/// .annotated(with: totalScore)
/// .asRequest(of: TeamInfo.self)
/// .fetchAll(db)
/// }
/// ```
public func total(_ expression: some SQLSpecificExpressible) -> AssociationAggregate<OriginRowDecoder> {
let aggregate = makeAggregate(.function("TOTAL", [expression.sqlExpression]))
if let column = expression as? any ColumnExpression {
let name = key.singularizedName
// Yes we use the `Sum` suffix instead of `Total`. Both `total(_:)`
// and `sum(_:)` compute sums.
return aggregate.forKey("\(name)\(column.name.uppercasingFirstCharacter)Sum")
} else {
return aggregate
}
}
}
/// A value aggregated from a population of associated records.
///
/// You build an `AssociationAggregate` from an ``AssociationToMany``.
///
/// For example:
///
/// ```swift
/// struct Player: TableRecord { }
/// struct Team: FetchableRecord, TableRecord {
/// static let players = Team.hasMany(Player.self)
/// }
///
/// try dbQueue.read { db in
/// // An association aggregate
/// let playerCount = Team.players.count
///
/// // Fetch all teams with at least ten players:
/// let teams: [Team] = try Team
/// .having(playerCount >= 10)
/// .fetchAll(db)
/// }
/// ```
///
/// ## Topics
///
/// ### Instance Methods
///
/// - ``forKey(_:)-1rvux``
/// - ``forKey(_:)-1ua4j``
///
/// ### Top-Level Functions
///
/// - ``abs(_:)-43n8v``
/// - ``cast(_:as:)-63ttx``
/// - ``length(_:)-9dr2v``
public struct AssociationAggregate<RowDecoder> {
fileprivate let preparation: AssociationAggregatePreparation<RowDecoder>
/// The SQL name for the value of this aggregate. See forKey(_:).
var key: String? = nil
/// Extends the request with the associated records used to compute the
/// aggregate, and returns the aggregated expression.
///
/// For example:
///
/// struct Author: TableRecord {
/// static let books = hasMany(Book.self)
/// }
///
/// // SELECT * FROM author
/// var request = Author.all()
///
/// let aggregate = Author.books.count
/// let expression = aggregate.prepare(&request)
///
/// // The request has been extended with associated records:
/// //
/// // SELECT author.* FROM author
/// // LEFT JOIN book ON book.authorId = author.id
/// // GROUP BY author.id
/// request
///
/// // The aggregated value:
/// //
/// // COUNT(DISTINCT book.id)
/// expression
///
/// The aggregated expression is not embedded in the extended request:
///
/// - We don't know yet if the aggregated expression will be used in the
/// SQL selection, or in the HAVING clause.
/// - It helps implementing aggregate operators such as `&&`, `+`, etc.
func prepare(_ request: inout some DerivableRequest<RowDecoder>) -> SQLExpression {
preparation.prepare(&request)
}
}
extension AssociationAggregate: Refinable {
/// Returns an aggregate that is selected in a column with the given name.
///
/// For example:
///
/// ```swift
/// struct Player: TableRecord { }
/// struct Team: FetchableRecord, TableRecord {
/// static let players = Team.hasMany(Player.self)
/// }
///
/// struct TeamInfo: FetchableRecord, Decodable {
/// var team: Team
/// var numberOfBooks: Int
/// }
///
/// try dbQueue.read { db in
/// let playerCount = Team.players.count.forKey("numberOfBooks")
///
/// let infos: [TeamInfo] = try Team
/// .annotated(with: playerCount)
/// .asRequest(of: TeamInfo.self)
/// .fetchAll(db)
/// }
/// ```
public func forKey(_ key: String) -> Self {
with {
$0.key = key
}
}
/// Returns an aggregate that is selected in a column named like the given
/// coding key.
///
/// See ``forKey(_:)-1rvux``.
public func forKey(_ key: some CodingKey) -> Self {
forKey(key.stringValue)
}
}
// MARK: - AssociationAggregatePreparation
/// An abstract class that only exists as support for
/// `AssociationAggregate.prepare(_:)`, which needs to prepare both query
/// interface requests and associations through their conformance
/// to `DerivableRequest`:
///
/// aggregate.prepare(&request)
/// aggregate.prepare(&association)
///
/// We could have used a generic closure instead of this class... if only Swift
/// would support generic closures.
private class AssociationAggregatePreparation<RowDecoder> {
func prepare(_ request: inout some DerivableRequest<RowDecoder>) -> SQLExpression {
fatalError("subclass must override")
}
}
/// Prepares a request so that it can use association aggregates.
private class BasePreparation<Association: AssociationToMany>:
AssociationAggregatePreparation<Association.OriginRowDecoder>
{
private let association: Association
private let expression: SQLExpression
init(association: Association, expression: SQLExpression) {
self.association = association
self.expression = expression
}
override func prepare(_ request: inout some DerivableRequest<Association.OriginRowDecoder>) -> SQLExpression {
// The fundamental request that supports association aggregate:
//
// SELECT parent.*
// LEFT JOIN child ON child.parentID = parent.id
// GROUP BY parent.id
let tableAlias = TableAlias()
request = request
.joining(optional: association.aliased(tableAlias))
.groupByPrimaryKey()
// The fundamental request can now be annotated, or filtered in the
// having clause, with the association aggregate expression:
// MIN(child.score), COUNT(DISTINCT child.id), etc.
return expression.qualified(with: tableAlias)
}
}
/// Transforms the expression of an aggregate.
private class MapPreparation<RowDecoder>: AssociationAggregatePreparation<RowDecoder> {
private let base: AssociationAggregatePreparation<RowDecoder>
private let transform: (SQLExpression) -> SQLExpression
init(
base: AssociationAggregatePreparation<RowDecoder>,
transform: @escaping (SQLExpression) -> SQLExpression)
{
self.base = base
self.transform = transform
}
override func prepare(_ request: inout some DerivableRequest<RowDecoder>) -> SQLExpression {
transform(base.prepare(&request))
}
}
extension AssociationAggregate {
/// Transforms the expression, and does not preserve key.
fileprivate func map(_ transform: @escaping (SQLExpression) -> SQLExpression) -> Self {
AssociationAggregate(preparation: MapPreparation(base: preparation, transform: transform))
}
}
/// Combines the expressions of two aggregates.
private class CombinePreparation<RowDecoder>: AssociationAggregatePreparation<RowDecoder> {
private let lhs: AssociationAggregatePreparation<RowDecoder>
private let rhs: AssociationAggregatePreparation<RowDecoder>
private let combine: (_ lhs: SQLExpression, _ rhs: SQLExpression) -> SQLExpression
init(
_ lhs: AssociationAggregatePreparation<RowDecoder>,
_ rhs: AssociationAggregatePreparation<RowDecoder>,
combine: @escaping (_ lhs: SQLExpression, _ rhs: SQLExpression) -> SQLExpression)
{
self.lhs = lhs
self.rhs = rhs
self.combine = combine
}
override func prepare(_ request: inout some DerivableRequest<RowDecoder>) -> SQLExpression {
let lhsExpression = lhs.prepare(&request)
let rhsExpression = rhs.prepare(&request)
return combine(lhsExpression, rhsExpression)
}
}
/// Combines the expression of two aggregates.
private func combine<RowDecoder>(
_ lhs: AssociationAggregate<RowDecoder>,
_ rhs: AssociationAggregate<RowDecoder>,
with combine: @escaping (_ lhs: SQLExpression, _ rhs: SQLExpression) -> SQLExpression)
-> AssociationAggregate<RowDecoder>
{
AssociationAggregate(preparation: CombinePreparation(lhs.preparation, rhs.preparation, combine: combine))
}
// MARK: - Logical Operators (AND, OR, NOT)
extension AssociationAggregate {
/// A negated logical aggregate.
///
/// For example:
///
/// ```swift
/// Author.having(!Author.books.isEmpty)
/// ```
public static prefix func ! (aggregate: Self) -> Self {
aggregate.map { !$0 }
}
/// The `AND` SQL operator.
public static func && (lhs: Self, rhs: Self) -> Self {
combine(lhs, rhs, with: &&)
}
// TODO: test
/// The `AND` SQL operator.
public static func && (lhs: Self, rhs: some SQLExpressible) -> Self {
lhs.map { $0 && rhs }
}
// TODO: test
/// The `AND` SQL operator.
public static func && (lhs: some SQLExpressible, rhs: Self) -> Self {
rhs.map { lhs && $0 }
}
/// The `OR` SQL operator.
public static func || (lhs: Self, rhs: Self) -> Self {
combine(lhs, rhs, with: ||)
}
// TODO: test
/// The `OR` SQL operator.
public static func || (lhs: Self, rhs: some SQLExpressible) -> Self {
lhs.map { $0 || rhs }
}
// TODO: test
/// The `OR` SQL operator.
public static func || (lhs: some SQLExpressible, rhs: Self) -> Self {
rhs.map { lhs || $0 }
}
}
// MARK: - Egality and Identity Operators (=, <>, IS, IS NOT)
extension AssociationAggregate {
/// The `=` SQL operator.
public static func == (lhs: Self, rhs: Self) -> Self {
combine(lhs, rhs, with: ==)
}
/// The `=` SQL operator.
///
/// When the right operand is nil, `IS NULL` is used instead of the
/// `=` operator.
public static func == (lhs: Self, rhs: (any SQLExpressible)?) -> Self {
lhs.map { $0 == rhs }
}
/// The `=` SQL operator.
///
/// When the left operand is nil, `IS NULL` is used instead of the
/// `=` operator.
public static func == (lhs: (any SQLExpressible)?, rhs: Self) -> Self {
rhs.map { lhs == $0 }
}
/// The `=` SQL operator.
public static func == (lhs: Self, rhs: Bool) -> Self {
lhs.map { $0 == rhs }
}
/// The `=` SQL operator.
public static func == (lhs: Bool, rhs: Self) -> Self {
rhs.map { lhs == $0 }
}
/// The `<>` SQL operator.
public static func != (lhs: Self, rhs: Self) -> Self {
combine(lhs, rhs, with: !=)
}
/// The `<>` SQL operator.
///
/// When the right operand is nil, `IS NOT NULL` is used instead of the
/// `<>` operator.
public static func != (lhs: Self, rhs: (any SQLExpressible)?) -> Self {
lhs.map { $0 != rhs }
}
/// The `<>` SQL operator.
///
/// When the left operand is nil, `IS NOT NULL` is used instead of the
/// `<>` operator.
public static func != (lhs: (any SQLExpressible)?, rhs: Self) -> Self {
rhs.map { lhs != $0 }
}
/// The `<>` SQL operator.
public static func != (lhs: Self, rhs: Bool) -> Self {
lhs.map { $0 != rhs }
}
/// The `<>` SQL operator.
public static func != (lhs: Bool, rhs: Self) -> Self {
rhs.map { lhs != $0 }
}
/// The `IS` SQL operator.
public static func === (lhs: Self, rhs: Self) -> Self {
combine(lhs, rhs, with: ===)
}
/// The `IS` SQL operator.
public static func === (lhs: Self, rhs: (any SQLExpressible)?) -> Self {
lhs.map { $0 === rhs }
}
/// The `IS` SQL operator.
public static func === (lhs: (any SQLExpressible)?, rhs: Self) -> Self {
rhs.map { lhs === $0 }
}
/// The `IS NOT` SQL operator.
public static func !== (lhs: Self, rhs: Self) -> Self {
combine(lhs, rhs, with: !==)
}
/// The `IS NOT` SQL operator.
public static func !== (lhs: Self, rhs: (any SQLExpressible)?) -> Self {
lhs.map { $0 !== rhs }
}
/// The `IS NOT` SQL operator.
public static func !== (lhs: (any SQLExpressible)?, rhs: Self) -> Self {
rhs.map { lhs !== $0 }
}
}
// MARK: - Comparison Operators (<, >, <=, >=)
extension AssociationAggregate {
/// The `<=` SQL operator.
public static func <= (lhs: Self, rhs: Self) -> Self {
combine(lhs, rhs, with: <=)
}
/// The `<=` SQL operator.
public static func <= (lhs: Self, rhs: some SQLExpressible) -> Self {
lhs.map { $0 <= rhs }
}
/// The `<=` SQL operator.
public static func <= (lhs: some SQLExpressible, rhs: Self) -> Self {
rhs.map { lhs <= $0 }
}
/// The `<` SQL operator.
public static func < (lhs: Self, rhs: Self) -> Self {
combine(lhs, rhs, with: <)
}
/// The `<` SQL operator.
public static func < (lhs: Self, rhs: some SQLExpressible) -> Self {
lhs.map { $0 < rhs }
}
/// The `<` SQL operator.
public static func < (lhs: some SQLExpressible, rhs: Self) -> Self {
rhs.map { lhs < $0 }
}
/// The `>` SQL operator.
public static func > (lhs: Self, rhs: Self) -> Self {
combine(lhs, rhs, with: >)
}
/// The `>` SQL operator.
public static func > (lhs: Self, rhs: some SQLExpressible) -> Self {
lhs.map { $0 > rhs }
}
/// The `>` SQL operator.
public static func > (lhs: some SQLExpressible, rhs: Self) -> Self {
rhs.map { lhs > $0 }
}
/// The `>=` SQL operator.
public static func >= (lhs: Self, rhs: Self) -> Self {
combine(lhs, rhs, with: >=)
}
/// The `>=` SQL operator.
public static func >= (lhs: Self, rhs: some SQLExpressible) -> Self {
lhs.map { $0 >= rhs }
}
/// The `>=` SQL operator.
public static func >= (lhs: some SQLExpressible, rhs: Self) -> Self {
rhs.map { lhs >= $0 }
}
}
// MARK: - Arithmetic Operators (+, -, *, /)
extension AssociationAggregate {
/// The `-` SQL operator.
public static prefix func - (aggregate: Self) -> Self {
aggregate.map { -$0 }
}
/// The `+` SQL operator.
public static func + (lhs: Self, rhs: Self) -> Self {
combine(lhs, rhs, with: +)
}
/// The `+` SQL operator.
public static func + (lhs: Self, rhs: some SQLExpressible) -> Self {
lhs.map { $0 + rhs }
}
/// The `+` SQL operator.
public static func + (lhs: some SQLExpressible, rhs: Self) -> Self {
rhs.map { lhs + $0 }
}
/// The `-` SQL operator.
public static func - (lhs: Self, rhs: Self) -> Self {
combine(lhs, rhs, with: -)
}
/// The `-` SQL operator.
public static func - (lhs: Self, rhs: some SQLExpressible) -> Self {
lhs.map { $0 - rhs }
}
/// The `-` SQL operator.
public static func - (lhs: some SQLExpressible, rhs: Self) -> Self {
rhs.map { lhs - $0 }
}
/// The `*` SQL operator.
public static func * (lhs: Self, rhs: Self) -> Self {
combine(lhs, rhs, with: *)
}
/// The `*` SQL operator.
public static func * (lhs: Self, rhs: some SQLExpressible) -> Self {
lhs.map { $0 * rhs }
}
/// The `*` SQL operator.
public static func * (lhs: some SQLExpressible, rhs: Self) -> Self {
rhs.map { lhs * $0 }
}
/// The `/` SQL operator.
public static func / (lhs: Self, rhs: Self) -> Self {
combine(lhs, rhs, with: /)
}
/// The `/` SQL operator.
public static func / (lhs: Self, rhs: some SQLExpressible) -> Self {
lhs.map { $0 / rhs }
}
/// The `/` SQL operator.
public static func / (lhs: some SQLExpressible, rhs: Self) -> Self {
rhs.map { lhs / $0 }
}
}
// MARK: - Functions
extension AssociationAggregate {
/// The `IFNULL` SQL function.
///
/// For example:
///
/// ```swift
/// Team.annotated(with: Team.players.min(Column("score")) ?? 0)
/// ```
///
/// The returned aggregate has the same key as the input.
public static func ?? (lhs: Self, rhs: some SQLExpressible) -> Self {
lhs
.map { $0 ?? rhs }
.with { $0.key = lhs.key } // Preserve key
}
}
/// The `ABS` SQL function.
public func abs<RowDecoder>(_ aggregate: AssociationAggregate<RowDecoder>)
-> AssociationAggregate<RowDecoder>
{
aggregate.map(abs)
}
/// The `CAST` SQL function.
///
/// Related SQLite documentation: <https://www.sqlite.org/lang_expr.html#castexpr>
public func cast<RowDecoder>(
_ aggregate: AssociationAggregate<RowDecoder>,
as storageClass: Database.StorageClass)
-> AssociationAggregate<RowDecoder>
{
aggregate
.map { cast($0, as: storageClass) }
.with { $0.key = aggregate.key } // Preserve key
}
/// The `LENGTH` SQL function.
public func length<RowDecoder>(_ aggregate: AssociationAggregate<RowDecoder>)
-> AssociationAggregate<RowDecoder>
{
aggregate.map(length)
}