Skip to main content

drizzle_core/
relation.rs

1//! Traits that describe foreign-key relations between tables, generated by
2//! the table and schema macros.
3
4/// The table `Self` has a foreign key to `Target`.
5///
6/// Generated by the table macros for each declared foreign key, on the
7/// table that declares it.
8pub trait Relation<Target: ?Sized> {}
9
10/// `Self` has exactly one foreign key to `Target`.
11///
12/// Generated by the table macros on the table that declares the key. It
13/// lets `.join(table)` derive the ON condition from the key, in either
14/// direction (see [`JoinKey`]). With several foreign keys to the same table,
15/// or keys in both directions, pass an explicit condition:
16/// `.join((table, condition))`.
17pub trait Joinable<Target: ?Sized> {
18    /// Returns the `(self_column, target_column)` pairs the ON condition
19    /// matches.
20    fn fk_columns() -> &'static [(&'static str, &'static str)];
21}
22
23/// The foreign key `.join(table)` derives its ON condition from, joining
24/// `Self` to `From`.
25///
26/// `Via` says which table declares the key, and is inferred:
27/// [`JoinForward`] when `Self` has the key to `From`, [`JoinReverse`] when
28/// `From` has the key to `Self`. When both do, the join is ambiguous and
29/// does not compile; pass the condition instead.
30#[diagnostic::on_unimplemented(
31    message = "no single foreign key joins `{Self}` to `{From}`",
32    label = "a bare table joins on the one foreign key between the two tables",
33    note = "with no key, or several, name the condition: `.join((table, eq(left, right)))`"
34)]
35pub trait JoinKey<From: ?Sized, Via> {
36    /// `(joined_column, from_column)` pairs the ON condition matches, in
37    /// key order, read through [`Self::pair`].
38    fn pairs() -> &'static [(&'static str, &'static str)];
39
40    /// The `(joined_column, from_column)` names of `pair`.
41    fn pair(pair: &(&'static str, &'static str)) -> (&'static str, &'static str);
42}
43
44/// [`JoinKey`] direction: the joined table declares the key.
45#[derive(Debug, Clone, Copy)]
46pub struct JoinForward;
47
48/// [`JoinKey`] direction: the table joined from declares the key.
49#[derive(Debug, Clone, Copy)]
50pub struct JoinReverse;
51
52/// `.join((table, condition))`: an explicit ON condition, no key involved.
53#[derive(Debug, Clone, Copy)]
54pub struct JoinExplicit;
55
56impl<Joined: Joinable<From>, From: ?Sized> JoinKey<From, JoinForward> for Joined {
57    fn pairs() -> &'static [(&'static str, &'static str)] {
58        Joined::fk_columns()
59    }
60
61    fn pair(pair: &(&'static str, &'static str)) -> (&'static str, &'static str) {
62        *pair
63    }
64}
65
66impl<Joined, From: Joinable<Joined> + ?Sized> JoinKey<From, JoinReverse> for Joined {
67    fn pairs() -> &'static [(&'static str, &'static str)] {
68        From::fk_columns()
69    }
70
71    fn pair(pair: &(&'static str, &'static str)) -> (&'static str, &'static str) {
72        (pair.1, pair.0)
73    }
74}
75
76/// The table `T` is part of the schema `Self`.
77///
78/// Generated by the schema macros. Query builders require it, so using a
79/// table that is missing from the schema struct is a compile error.
80#[diagnostic::on_unimplemented(
81    message = "table `{T}` is not part of schema `{Self}`",
82    label = "add this table as a field in your schema struct",
83    note = "all tables used in queries must be included in the schema"
84)]
85pub trait SchemaHasTable<T: ?Sized> {}
86
87// =============================================================================
88// Query API: RelationDef and Cardinality
89// =============================================================================
90
91/// A relation between two tables for the relational query API (`db.query`).
92///
93/// Generated by the schema macros for each relation; not implemented by
94/// hand.
95#[cfg(feature = "query")]
96pub trait RelationDef: private::Sealed + 'static {
97    /// The table the relation starts from.
98    type Source;
99    /// The table the relation loads.
100    type Target: crate::query::QueryTable;
101    /// How many target rows each source row has: [`Many`], [`One`] or
102    /// [`OptionalOne`].
103    type Card: CardWrap;
104    /// The relation's name, used as its JSON key.
105    const NAME: &'static str;
106    /// Column pairs for the join condition.
107    ///
108    /// Each pair `(a, b)` renders `target_alias."a" = parent_alias."b"`.
109    fn fk_columns() -> &'static [(&'static str, &'static str)];
110
111    /// Junction table metadata for many-to-many relations.
112    ///
113    /// `None` for a direct foreign key. When `Some`, the generated SQL joins
114    /// through the junction table instead.
115    #[must_use]
116    fn junction() -> Option<JunctionMeta> {
117        None
118    }
119}
120
121/// Maps a cardinality marker to the type that holds loaded rows: `Vec<T>`,
122/// `T` or `Option<T>`.
123#[cfg(feature = "query")]
124pub trait CardWrap: private::Sealed {
125    /// The same cardinality, as a runtime value.
126    const CARDINALITY: crate::query::RelCardinality;
127    /// The container for loaded rows of type `T`.
128    type Wrap<T>;
129
130    /// Maps the wrapped value from `A` to `B`.
131    fn map_wrap<A, B>(data: Self::Wrap<A>, f: impl FnMut(A) -> B) -> Self::Wrap<B>;
132}
133
134/// Builds the row type that holds a loaded relation.
135///
136/// Generated for each relation. `Row<Inner, Child>` is a generated struct
137/// such as `UsersWithPosts<Inner, Child>`.
138#[cfg(feature = "query")]
139pub trait AssembleRel: RelationDef {
140    /// Row type produced when this relation is included in a query.
141    type Row<Inner, Child>;
142
143    /// Builds a with-row from the composed inner row and this relation's data.
144    fn assemble_row<Inner, Child>(
145        inner: Inner,
146        data: <Self::Card as CardWrap>::Wrap<Child>,
147    ) -> Self::Row<Inner, Child>;
148}
149
150/// Cardinalities that `.limit(...)`, `.offset(...)` and `.first()` apply
151/// to: those that load any number of rows.
152#[cfg(feature = "query")]
153#[diagnostic::on_unimplemented(
154    message = "`.limit`, `.offset` and `.first` apply only to relations that load many rows",
155    label = "this relation loads at most one row",
156    note = "a LIMIT or OFFSET here could only drop or skip that row"
157)]
158pub trait Paginated: private::Sealed {}
159
160/// Cardinalities that `.r#where(...)` applies to: every one but [`One`],
161/// whose row is required.
162#[cfg(feature = "query")]
163#[diagnostic::on_unimplemented(
164    message = "a required relation cannot be filtered with `.r#where`",
165    label = "this relation always loads exactly one row",
166    note = "filter the root query instead, or make the foreign key an `Option` so the relation \
167            loads an `Option`"
168)]
169pub trait Filtered: private::Sealed {}
170
171/// Cardinality marker: any number of rows, loaded as `Vec<T>`.
172#[cfg(feature = "query")]
173pub struct Many;
174
175/// Cardinality marker: exactly one row, loaded as `T`.
176#[cfg(feature = "query")]
177pub struct One;
178
179/// Cardinality marker: zero or one row, loaded as `Option<T>`.
180#[cfg(feature = "query")]
181pub struct OptionalOne;
182
183/// Metadata for many-to-many relations through a junction table.
184#[cfg(feature = "query")]
185#[derive(Debug, Clone, Copy)]
186pub struct JunctionMeta {
187    /// The junction table.
188    pub table: crate::TableSqlRef,
189    /// `(junction_col, source_col)` pairs that tie a junction row to the
190    /// parent row.
191    pub source_fk: &'static [(&'static str, &'static str)],
192    /// `(junction_col, target_col)` pairs used to join the target table.
193    pub target_fk: &'static [(&'static str, &'static str)],
194}
195
196#[cfg(feature = "query")]
197impl private::Sealed for Many {}
198#[cfg(feature = "query")]
199impl private::Sealed for One {}
200#[cfg(feature = "query")]
201impl private::Sealed for OptionalOne {}
202
203#[cfg(feature = "query")]
204impl Paginated for Many {}
205#[cfg(feature = "query")]
206impl Filtered for Many {}
207#[cfg(feature = "query")]
208impl Filtered for OptionalOne {}
209
210#[cfg(feature = "query")]
211impl CardWrap for Many {
212    const CARDINALITY: crate::query::RelCardinality = crate::query::RelCardinality::Many;
213    type Wrap<T> = crate::prelude::Vec<T>;
214
215    fn map_wrap<A, B>(data: Self::Wrap<A>, f: impl FnMut(A) -> B) -> Self::Wrap<B> {
216        data.into_iter().map(f).collect()
217    }
218}
219
220#[cfg(feature = "query")]
221impl CardWrap for One {
222    const CARDINALITY: crate::query::RelCardinality = crate::query::RelCardinality::One;
223    type Wrap<T> = T;
224
225    fn map_wrap<A, B>(data: Self::Wrap<A>, mut f: impl FnMut(A) -> B) -> Self::Wrap<B> {
226        f(data)
227    }
228}
229
230#[cfg(feature = "query")]
231impl CardWrap for OptionalOne {
232    const CARDINALITY: crate::query::RelCardinality = crate::query::RelCardinality::OptionalOne;
233    type Wrap<T> = Option<T>;
234
235    fn map_wrap<A, B>(data: Self::Wrap<A>, f: impl FnMut(A) -> B) -> Self::Wrap<B> {
236        data.map(f)
237    }
238}
239
240#[cfg(feature = "query")]
241#[doc(hidden)]
242pub mod private {
243    pub trait Sealed {}
244}