add swiftUI code
This commit is contained in:
+314
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user