Add CI/CD configuration and API documentation

This commit is contained in:
2026-07-01 21:40:53 +08:00
commit c590135d68
4168 changed files with 740252 additions and 0 deletions
@@ -0,0 +1,314 @@
import Foundation
/// A type that defines a connection between two tables.
///
/// ``Association`` feeds methods of the ``JoinableRequest`` protocol. They are
/// built from a ``TableRecord`` type, or a ``Table`` instance.
///
/// ## Topics
///
/// ### Instance Methods
///
/// - ``forKey(_:)-247af``
/// - ``forKey(_:)-54yh6``
///
/// ### Associations To One
///
/// - ``BelongsToAssociation``
/// - ``HasOneAssociation``
/// - ``HasOneThroughAssociation``
/// - ``AssociationToOne``
///
/// ### Associations To Many
///
/// - ``HasManyAssociation``
/// - ``HasManyThroughAssociation``
/// - ``AssociationToMany``
///
/// ### Associations to Common Table Expressions
///
/// - ``JoinAssociation``
///
/// ### Supporting Types
///
/// - ``ForeignKey``
/// - ``Inflections``
public protocol Association: DerivableRequest {
// OriginRowDecoder and RowDecoder inherited from DerivableRequest provide
// type safety:
//
// Book.including(required: Book.author) // compiles
// Fruit.including(required: Book.author) // does not compile
/// The record type at the origin of the association.
///
/// In the ``BelongsToAssociation`` association below, it is `Book`:
///
/// ```swift
/// struct Book: TableRecord {
/// // BelongsToAssociation<Book, Author>
/// static let author = belongsTo(Author.self)
/// }
/// ```
associatedtype OriginRowDecoder
var _sqlAssociation: _SQLAssociation { get set }
/// Returns an association with the given key.
///
/// For example:
///
/// ```swift
/// struct Employee: FetchableRecord, TableRecord {
/// static let manager = belongsTo(Employee.self).forKey("manager")
/// static let subordinates = hasMany(Employee.self).forKey("subordinates")
/// }
///
/// struct EmployeeInfo: FetchableRecord, Decodable {
/// var employee: Employee
/// var manager: Employee? // property name matches the association key
/// var subordinates: [Employee] // property name matches the association key
/// }
///
/// try dbQueue.read { db in
/// let employeeInfos: [EmployeeInfo] = try Employee
/// .including(optional: Employee.manager)
/// .including(all: Employee.subordinates)
/// .asRequest(of: EmployeeInfo.self)
/// .fetchAll(db)
/// }
/// ```
func forKey(_ key: String) -> Self
}
extension Association {
/// Returns self modified with the *update* function.
func with(_ update: (inout Self) throws -> Void) rethrows -> Self {
var result = self
try update(&result)
return result
}
/// Returns self with destination relation modified with the *update* function.
fileprivate func withDestinationRelation(_ update: (inout SQLRelation) throws -> Void) rethrows -> Self {
var result = self
try update(&result._sqlAssociation.destination.relation)
return result
}
}
extension Association {
public func _including(all association: _SQLAssociation) -> Self {
withDestinationRelation { relation in
relation = relation._including(all: association)
}
}
public func _including(optional association: _SQLAssociation) -> Self {
withDestinationRelation { relation in
relation = relation._including(optional: association)
}
}
public func _including(required association: _SQLAssociation) -> Self {
withDestinationRelation { relation in
relation = relation._including(required: association)
}
}
public func _joining(optional association: _SQLAssociation) -> Self {
withDestinationRelation { relation in
relation = relation._joining(optional: association)
}
}
public func _joining(required association: _SQLAssociation) -> Self {
withDestinationRelation { relation in
relation = relation._joining(required: association)
}
}
}
extension Association {
/// The association key defines how rows fetched from this association
/// should be consumed.
///
/// For example:
///
/// struct Player: TableRecord {
/// // The default key of this association is the name of the
/// // database table for teams, let's say "team":
/// static let team = belongsTo(Team.self)
/// }
/// print(Player.team.key) // Prints "team"
///
/// // Consume rows:
/// let request = Player.including(required: Player.team)
/// for row in Row.fetchAll(db, request) {
/// let team: Team = row["team"] // the association key
/// }
///
/// The key can be redefined with the `forKey` method:
///
/// let request = Player.including(required: Player.team.forKey("custom"))
/// for row in Row.fetchAll(db, request) {
/// let team: Team = row["custom"]
/// }
var key: SQLAssociationKey { _sqlAssociation.destination.key }
/// Returns an association with the given key.
public func forKey(_ codingKey: some CodingKey) -> Self {
forKey(codingKey.stringValue)
}
}
// TableRequest conformance
extension Association {
public func aliased(_ alias: TableAlias) -> Self {
withDestinationRelation { relation in
relation = relation.aliased(alias)
}
}
}
// SelectionRequest conformance
extension Association {
public func selectWhenConnected(_ selection: @escaping (Database) throws -> [any SQLSelectable]) -> Self {
withDestinationRelation { relation in
relation = relation.selectWhenConnected { db in
try selection(db).map(\.sqlSelection)
}
}
}
public func annotatedWhenConnected(with selection: @escaping (Database) throws -> [any SQLSelectable]) -> Self {
withDestinationRelation { relation in
relation = relation.annotatedWhenConnected { db in
try selection(db).map(\.sqlSelection)
}
}
}
}
// FilteredRequest conformance
extension Association {
public func filterWhenConnected(_ predicate: @escaping (Database) throws -> any SQLExpressible) -> Self {
withDestinationRelation { relation in
relation = relation.filterWhenConnected { db in
try predicate(db).sqlExpression
}
}
}
}
// OrderedRequest conformance
extension Association {
public func orderWhenConnected(_ orderings: @escaping (Database) throws -> [any SQLOrderingTerm]) -> Self {
withDestinationRelation { relation in
relation = relation.orderWhenConnected { db in
try orderings(db).map(\.sqlOrdering)
}
}
}
public func reversed() -> Self {
withDestinationRelation { relation in
relation = relation.reversed()
}
}
public func unordered() -> Self {
withDestinationRelation { relation in
relation = relation.unordered()
}
}
public func withStableOrder() -> Self {
withDestinationRelation { relation in
relation = relation.withStableOrder()
}
}
}
// TableRequest conformance
extension Association {
public var databaseTableName: String {
_sqlAssociation.destination.relation.source.tableName
}
}
// AggregatingRequest conformance
extension Association {
public func groupWhenConnected(_ expressions: @escaping (Database) throws -> [any SQLExpressible]) -> Self {
withDestinationRelation { relation in
relation = relation.groupWhenConnected { db in
try expressions(db).map(\.sqlExpression)
}
}
}
public func havingWhenConnected(_ predicate: @escaping (Database) throws -> any SQLExpressible) -> Self {
withDestinationRelation { relation in
relation = relation.havingWhenConnected { db in
try predicate(db).sqlExpression
}
}
}
}
// DerivableRequest conformance
extension Association {
public func distinct() -> Self {
withDestinationRelation { relation in
relation.isDistinct = true
}
}
public func with<RowDecoder>(_ cte: CommonTableExpression<RowDecoder>) -> Self {
withDestinationRelation { relation in
relation.ctes[cte.tableName] = cte.cte
}
}
}
// MARK: - AssociationToOne
/// An association that defines a to-one connection.
public protocol AssociationToOne: Association { }
extension AssociationToOne {
public func forKey(_ key: String) -> Self {
let associationKey = SQLAssociationKey.fixedSingular(key)
return with {
$0._sqlAssociation = $0._sqlAssociation.forDestinationKey(associationKey)
}
}
}
// MARK: - AssociationToMany
/// An association that defines a to-many connection.
///
/// ## Topics
///
/// ### Building Association Aggregates
///
/// - ``average(_:)``
/// - ``count``
/// - ``isEmpty``
/// - ``max(_:)``
/// - ``min(_:)``
/// - ``sum(_:)``
/// - ``total(_:)``
///
/// - ``AssociationAggregate``
public protocol AssociationToMany: Association { }
extension AssociationToMany {
public func forKey(_ key: String) -> Self {
let associationKey = SQLAssociationKey.fixedPlural(key)
return with {
$0._sqlAssociation = $0._sqlAssociation.forDestinationKey(associationKey)
}
}
}
@@ -0,0 +1,883 @@
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)
}
@@ -0,0 +1,94 @@
/// Thes `BelongsToAssociation` sets up a one-to-one connection from a record
/// type to another record type, such as each instance of the declaring record
/// "belongs to" an instance of the other record.
///
/// For example, if your application includes authors and books, and each book
/// is assigned its author, you'd declare the association this way:
///
/// ```swift
/// struct Author: TableRecord { }
/// struct Book: TableRecord {
/// static let author = belongsTo(Author.self)
/// }
/// ```
///
/// A `BelongsToAssociation` should be supported by an SQLite foreign key.
///
/// Foreign keys are the recommended way to declare relationships between
/// database tables because not only will SQLite guarantee the integrity of your
/// data, but GRDB will be able to use those foreign keys to automatically
/// configure your associations.
///
/// You define the foreign key when you create database tables. For example:
///
/// ```swift
/// try db.create(table: "author") { t in
/// t.autoIncrementedPrimaryKey("id") // (1)
/// t.column("name", .text)
/// }
/// try db.create(table: "book") { t in
/// t.autoIncrementedPrimaryKey("id")
/// t.belongsTo("author", onDelete: .cascade) // (2)
/// .notNull() // (3)
/// t.column("title", .text)
/// }
/// ```
///
/// 1. The author table has a primary key.
/// 2. The `book.authorId` column is used to link a book to the author it
/// belongs to. This column is indexed in order to ease the selection of
/// an author's books. A foreign key is defined from `book.authorId`
/// column to `authors.id`, so that SQLite guarantees that no book refers
/// to a missing author. The `onDelete: .cascade` option has SQLite
/// automatically delete all of an author's books when that author is
/// deleted. See <https://sqlite.org/foreignkeys.html#fk_actions> for
/// more information.
/// 3. Make the `book.authorId` column not null if you want SQLite to guarantee
/// that all books have an author.
///
/// The example above uses auto-incremented primary keys. But generally
/// speaking, all primary keys are supported.
///
/// If the database schema does not define foreign keys between tables, you can
/// still use `BelongsToAssociation`. But your help is needed to define the
/// missing foreign key:
///
/// ```swift
/// struct Book: TableRecord {
/// static let author = belongsTo(Author.self, using: ForeignKey(...))
/// }
/// ```
public struct BelongsToAssociation<Origin, Destination> {
public var _sqlAssociation: _SQLAssociation
init(
to destinationRelation: SQLRelation,
key: String?,
using foreignKey: ForeignKey?)
{
let destinationTable = destinationRelation.source.tableName
let foreignKeyCondition = SQLForeignKeyCondition(
destinationTable: destinationTable,
foreignKey: foreignKey,
originIsLeft: true)
let associationKey: SQLAssociationKey
if let key {
associationKey = .fixedSingular(key)
} else {
associationKey = .inflected(destinationTable)
}
_sqlAssociation = _SQLAssociation(
key: associationKey,
condition: .foreignKey(foreignKeyCondition),
relation: destinationRelation,
cardinality: .toOne)
}
}
extension BelongsToAssociation: AssociationToOne {
public typealias OriginRowDecoder = Origin
public typealias RowDecoder = Destination
}
@@ -0,0 +1,93 @@
/// The `HasManyAssociation` indicates a one-to-many connection between two
/// record types, such as each instance of the declaring record "has many"
/// instances of the other record.
///
/// For example, if your application includes authors and books, and each author
/// is assigned zero or more books, you'd declare the association this way:
///
/// ```swift
/// struct Book: TableRecord { }
/// struct Author: TableRecord {
/// static let books = hasMany(Book.self)
/// }
/// ```
///
/// A `HasManyAssociation` should be supported by an SQLite foreign key.
///
/// Foreign keys are the recommended way to declare relationships between
/// database tables because not only will SQLite guarantee the integrity of your
/// data, but GRDB will be able to use those foreign keys to automatically
/// configure your associations.
///
/// You define the foreign key when you create database tables. For example:
///
/// ```swift
/// try db.create(table: "author") { t in
/// t.autoIncrementedPrimaryKey("id") // (1)
/// t.column("name", .text)
/// }
/// try db.create(table: "book") { t in
/// t.autoIncrementedPrimaryKey("id")
/// t.belongsTo("author", onDelete: .cascade) // (2)
/// .notNull() // (3)
/// t.column("title", .text)
/// }
/// ```
///
/// 1. The author table has a primary key.
/// belongs to. This column is indexed in order to ease the selection of
/// an author's books. A foreign key is defined from `book.authorId`
/// column to `authors.id`, so that SQLite guarantees that no book refers
/// to a missing author. The `onDelete: .cascade` option has SQLite
/// automatically delete all of an author's books when that author is
/// deleted. See <https://sqlite.org/foreignkeys.html#fk_actions> for
/// more information.
/// 3. Make the `book.authorId` column not null if you want SQLite to guarantee
/// that all books have an author.
///
/// The example above uses auto-incremented primary keys. But generally
/// speaking, all primary keys are supported.
///
/// If the database schema does not define foreign keys between tables, you can
/// still use `HasManyAssociation`. But your help is needed to define the
/// missing foreign key:
///
/// ```swift
/// struct Author: TableRecord {
/// static let books = hasMany(Book.self, using: ForeignKey(...))
/// }
/// ```
public struct HasManyAssociation<Origin, Destination> {
public var _sqlAssociation: _SQLAssociation
init(
to destinationRelation: SQLRelation,
key: String?,
using foreignKey: ForeignKey?)
{
let destinationTable = destinationRelation.source.tableName
let foreignKeyCondition = SQLForeignKeyCondition(
destinationTable: destinationTable,
foreignKey: foreignKey,
originIsLeft: false)
let associationKey: SQLAssociationKey
if let key {
associationKey = .fixedPlural(key)
} else {
associationKey = .inflected(destinationTable)
}
_sqlAssociation = _SQLAssociation(
key: associationKey,
condition: .foreignKey(foreignKeyCondition),
relation: destinationRelation,
cardinality: .toMany)
}
}
extension HasManyAssociation: AssociationToMany {
public typealias OriginRowDecoder = Origin
public typealias RowDecoder = Destination
}
@@ -0,0 +1,66 @@
/// The `HasManyThroughAssociation` is often used to set up a many-to-many
/// connection with another record. This association indicates that the
/// declaring record can be matched with zero or more instances of another
/// record by proceeding through a third record.
///
/// For example, consider the practice of passport delivery. One country
/// "has many" citizens "through" its passports:
///
/// ```swift
/// struct Citizen: TableRecord { }
///
/// struct Passport: TableRecord {
/// static let citizen = belongsTo(Citizen.self)
/// }
///
/// struct Country: TableRecord {
/// static let passports = hasMany(Passport.self)
/// static let citizens = hasMany(Citizen.self,
/// through: passports,
/// using: Passport.citizen)
/// }
/// ```
///
/// The `HasManyThroughAssociation` is also useful for setting up "shortcuts"
/// through nested associations. For example, if a document has many sections,
/// and a section has many paragraphs, you may sometimes want to get a simple
/// collection of all paragraphs in the document. You could set
/// that up this way:
///
/// ```swift
/// struct Paragraph: TableRecord { }
///
/// struct Section: TableRecord {
/// static let paragraphs = hasMany(Paragraph.self)
/// }
///
/// struct Document: TableRecord {
/// static let sections = hasMany(Section.self)
/// static let paragraphs = hasMany(Paragraph.self,
/// through: sections,
/// using: Section.paragraphs)
/// }
/// ```
///
/// As in the examples above, `HasManyThroughAssociation` is always built from
/// two other associations. Those associations can be any ``Association``.
public struct HasManyThroughAssociation<Origin, Destination> {
public var _sqlAssociation: _SQLAssociation
init<Pivot, Target>(
through pivot: Pivot,
using target: Target)
where Pivot: Association,
Target: Association,
Pivot.OriginRowDecoder == Origin,
Pivot.RowDecoder == Target.OriginRowDecoder,
Target.RowDecoder == Destination
{
_sqlAssociation = target._sqlAssociation.through(pivot._sqlAssociation)
}
}
extension HasManyThroughAssociation: AssociationToMany {
public typealias OriginRowDecoder = Origin
public typealias RowDecoder = Destination
}
@@ -0,0 +1,100 @@
/// The `HasOneAssociation` indicates a one-to-one connection between two
/// record types, such as each instance of the declaring record "has one"
/// instances of the other record.
///
/// For example, if your application has one database table for countries, and
/// another for their demographic profiles, you'd declare the association
/// this way:
///
/// ```swift
/// struct Demographics: TableRecord { }
/// struct Country: TableRecord {
/// static let demographics = hasOne(Demographics.self)
/// }
/// ```
///
/// A `HasOneAssociation` should be supported by an SQLite foreign key.
///
/// Foreign keys are the recommended way to declare relationships between
/// database tables because not only will SQLite guarantee the integrity of your
/// data, but GRDB will be able to use those foreign keys to automatically
/// configure your associations.
///
/// You define the foreign key when you create database tables. For example:
///
/// ```swift
/// try db.create(table: "country") { t in
/// t.primaryKey("code", .text) // (1)
/// t.column("name", .text)
/// }
/// try db.create(table: "demographics") { t in
/// t.autoIncrementedPrimaryKey("id")
/// t.belongsTo("country", onDelete: .cascade) // (2)
/// .notNull() // (3)
/// .unique() // (4)
/// t.column("population", .integer)
/// t.column("density", .double)
/// }
/// ```
///
/// 1. The country table has a primary key.
/// 2. The `demographics.countryCode` column is used to link a demographic
/// profile to the country it belongs to. This column is indexed in order
/// to ease the selection of the demographics of a country. A foreign key
/// is defined from `demographics.countryCode` column to `country.code`,
/// so that SQLite guarantees that no profile refers to a missing
/// country. The `onDelete: .cascade` option has SQLite automatically
/// delete a profile when its country is deleted. See
/// <https://sqlite.org/foreignkeys.html#fk_actions> for more information.
/// 3. Make the `demographics.countryCode` column not null if you want SQLite to
/// guarantee that all profiles are linked to a country.
/// 4. Create a unique index on the `demographics.countryCode` column in order
/// to guarantee the unicity of any country's demographics.
///
/// The example above uses a string primary for the country table. But generally
/// speaking, all primary keys are supported.
///
/// If the database schema does not define foreign keys between tables, you can
/// still use `HasOneAssociation`. But your help is needed to define the
/// missing foreign key:
///
/// ```swift
/// struct Demographics: TableRecord { }
/// struct Country: TableRecord {
/// static let demographics = hasOne(Demographics.self, using: ForeignKey(...)
/// }
/// ```
public struct HasOneAssociation<Origin, Destination> {
public var _sqlAssociation: _SQLAssociation
init(
to destinationRelation: SQLRelation,
key: String?,
using foreignKey: ForeignKey?)
{
let destinationTable = destinationRelation.source.tableName
let foreignKeyCondition = SQLForeignKeyCondition(
destinationTable: destinationTable,
foreignKey: foreignKey,
originIsLeft: false)
let associationKey: SQLAssociationKey
if let key {
associationKey = .fixedSingular(key)
} else {
associationKey = .inflected(destinationTable)
}
_sqlAssociation = _SQLAssociation(
key: associationKey,
condition: .foreignKey(foreignKeyCondition),
relation: destinationRelation,
cardinality: .toOne)
}
}
extension HasOneAssociation: AssociationToOne {
public typealias OriginRowDecoder = Origin
public typealias RowDecoder = Destination
}
@@ -0,0 +1,47 @@
/// The `HasOneThroughAssociation` sets up a one-to-one connection with
/// another record. This association indicates that the declaring record can be
/// matched with one instance of another record by proceeding through a third
/// record.
///
/// For example, if each book belongs to a library, and each library has
/// one address, then one knows where the book should be returned to:
///
/// ```swift
/// struct Address: TableRecord { }
///
/// struct Library: TableRecord {
/// static let address = hasOne(Address.self)
/// }
///
/// struct Book: TableRecord {
/// static let library = belongsTo(Library.self)
/// static let returnAddress = hasOne(Address.self,
/// through: library,
/// using: Library.address,
/// key: "returnAddress")
/// }
/// ```
///
/// As in the example above, `HasOneThroughAssociation` is always built from
/// two other associations. Those associations can be any association that
/// declares a to-one connection (``AssociationToOne``).
public struct HasOneThroughAssociation<Origin, Destination> {
public var _sqlAssociation: _SQLAssociation
init<Pivot, Target>(
through pivot: Pivot,
using target: Target)
where Pivot: AssociationToOne,
Target: AssociationToOne,
Pivot.OriginRowDecoder == Origin,
Pivot.RowDecoder == Target.OriginRowDecoder,
Target.RowDecoder == Destination
{
_sqlAssociation = target._sqlAssociation.through(pivot._sqlAssociation)
}
}
extension HasOneThroughAssociation: AssociationToOne {
public typealias OriginRowDecoder = Origin
public typealias RowDecoder = Destination
}
@@ -0,0 +1,22 @@
/// The `JoinAssociation` joins common table expression to regular
/// tables or other common table expressions.
public struct JoinAssociation<Origin, Destination> {
public var _sqlAssociation: _SQLAssociation
/// Creates a `JoinAssociation` whose key is the table name of the relation.
init(
to relation: SQLRelation,
condition: SQLAssociationCondition)
{
_sqlAssociation = _SQLAssociation(
key: .inflected(relation.source.tableName),
condition: condition,
relation: relation,
cardinality: .toOne)
}
}
extension JoinAssociation: AssociationToOne {
public typealias OriginRowDecoder = Origin
public typealias RowDecoder = Destination
}