102 lines
3.7 KiB
Swift
102 lines
3.7 KiB
Swift
/// A `ForeignKey` defines on which columns an association between two tables
|
|
/// is established.
|
|
///
|
|
/// You will need a `ForeignKey` when you define an ``Association`` between two
|
|
/// tables that are not unambiguously related with a single SQLite foreign key.
|
|
///
|
|
/// Sometimes the database schema does not define any foreign key between two
|
|
/// tables. And sometimes, there are several foreign keys from a table
|
|
/// to another:
|
|
///
|
|
/// | Table book | | Table person |
|
|
/// | ------------ | | ------------ |
|
|
/// | id | +-->• id |
|
|
/// | authorId •---+ | name |
|
|
/// | translatorId •---+
|
|
/// | title |
|
|
///
|
|
/// When this happens, associations can't be automatically inferred from the
|
|
/// database schema. GRDB will complain with a fatal error such as "Ambiguous
|
|
/// foreign key from book to person", or "Could not infer foreign key from book
|
|
/// to person".
|
|
///
|
|
/// Your help is needed. You have to instruct which foreign key to use.
|
|
/// For example:
|
|
///
|
|
/// ```swift
|
|
/// struct Book: TableRecord {
|
|
/// // Define foreign keys
|
|
/// static let authorForeignKey = ForeignKey(["authorId"]))
|
|
/// static let translatorForeignKey = ForeignKey(["translatorId"]))
|
|
///
|
|
/// // Use foreign keys to define associations:
|
|
/// static let author = belongsTo(
|
|
/// Person.self,
|
|
/// key: "author",
|
|
/// using: authorForeignKey)
|
|
/// static let translator = belongsTo(
|
|
/// Person.self,
|
|
/// key: "translator",
|
|
/// using: translatorForeignKey)
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// Foreign keys can also be defined from query interface columns:
|
|
///
|
|
/// ```swift
|
|
/// struct Book: TableRecord {
|
|
/// enum Columns: String, ColumnExpression {
|
|
/// case id, title, authorId, translatorId
|
|
/// }
|
|
///
|
|
/// static let authorForeignKey = ForeignKey([Columns.authorId]))
|
|
/// static let translatorForeignKey = ForeignKey([Columns.translatorId]))
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// When the destination table does not define any primary key, you need to
|
|
/// provide the destination columns:
|
|
///
|
|
/// ```swift
|
|
/// struct Book: TableRecord {
|
|
/// static let authorForeignKey = ForeignKey(["authorId"], to: ["id"]))
|
|
/// static let translatorForeignKey = ForeignKey(["translatorId"], to: ["id"]))
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// Foreign keys are always defined from the table that contains the columns at
|
|
/// the origin of the foreign key. `Person`'s symmetric associations reuse
|
|
/// foreign keys of `Book`:
|
|
///
|
|
/// ```swift
|
|
/// struct Person: TableRecord {
|
|
/// static let writtenBooks = hasMany(
|
|
/// Book.self,
|
|
/// key: "writtenBooks",
|
|
/// using: Book.authorForeignKey)
|
|
/// static let translatedBooks = hasMany(
|
|
/// Book.self,
|
|
/// key: "translatedBooks",
|
|
/// using: Book.translatorForeignKey)
|
|
/// }
|
|
/// ```
|
|
public struct ForeignKey: Equatable, Sendable {
|
|
var originColumns: [String]
|
|
var destinationColumns: [String]?
|
|
|
|
/// - parameter originColumns: The columns at the origin of the foreign key.
|
|
/// - parameter destinationColumns: The columns at the destination of the
|
|
/// foreign key. Use nil for the columns of the primary key.
|
|
public init(_ originColumns: [String], to destinationColumns: [String]? = nil) {
|
|
self.originColumns = originColumns
|
|
self.destinationColumns = destinationColumns
|
|
}
|
|
|
|
/// - parameter originColumns: The columns at the origin of the foreign key.
|
|
/// - parameter destinationColumns: The columns at the destination of the
|
|
/// foreign key. Use nil for the columns of the primary key.
|
|
public init(_ originColumns: [any ColumnExpression], to destinationColumns: [any ColumnExpression]? = nil) {
|
|
self.init(originColumns.map(\.name), to: destinationColumns?.map(\.name))
|
|
}
|
|
}
|