zino_orm/
join.rs

1use super::{Entity, ModelColumn, Schema, query::QueryExt};
2use zino_core::model::Query;
3
4/// Variants for `JOIN` types.
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
6pub(super) enum JoinType {
7    /// The default join type.
8    #[default]
9    Default,
10    /// The `INNER JOIN` type.
11    Inner,
12    /// The `LEFT (OUTER) JOIN` type.
13    Left,
14    /// The `RIGHT (OUTER) JOIN` type.
15    Right,
16    /// The `FULL (OUTER) JOIN` type.
17    Full,
18    /// The `CROSS JOIN` type.
19    Cross,
20}
21
22impl JoinType {
23    /// Returns the join type as str.
24    #[inline]
25    pub(super) fn as_str(&self) -> &'static str {
26        match self {
27            JoinType::Default => "JOIN",
28            JoinType::Inner => "INNER JOIN",
29            JoinType::Left => "LEFT JOIN",
30            JoinType::Right => "RIGHT JOIN",
31            JoinType::Full => "FULL JOIN",
32            JoinType::Cross => "CROSS JOIN",
33        }
34    }
35}
36
37/// SQL joins on two tables.
38///
39/// # Examples
40/// ```rust,ignore
41/// use crate::model::{Project, ProjectColumn, Task, TaskColumn};
42/// use zino_core::Map;
43/// use zino_orm::{JoinOn, QueryBuilder, Schema};
44///
45/// let query = QueryBuilder::new()
46///     .fields([TaskColumn::Id, TaskColumn::Name, TaskColumn::ProjectId])
47///     .and_eq(TaskColumn::Status, "Completed")
48///     .and(
49///         QueryBuilder::new()
50///             .alias(ProjectColumn::Name, "project_name")
51///             .fields([ProjectColumn::StartDate, ProjectColumn::EndDate])
52///             .and_overlaps(
53///                 (ProjectColumn::StartDate, ProjectColumn::EndDate),
54///                 ("2023-10-01", "2023-10-07"),
55///             ),
56///     )
57///     .order_desc(TaskColumn::UpdatedAt)
58///     .build();
59/// let join_on = JoinOn::new::<Project>()
60///     .eq(TaskColumn::ProjectId, ProjectColumn::Id);
61/// let entries = Task::lookup::<Project, Map>(&query, &[join_on]).await?;
62/// ```
63#[derive(Debug, Clone)]
64pub struct JoinOn {
65    /// The join type.
66    join_type: JoinType,
67    /// The join table.
68    join_table: String,
69    /// The join conditions.
70    conditions: Vec<String>,
71}
72
73impl JoinOn {
74    /// Creates a new instance with the default join type.
75    #[inline]
76    pub fn new<M: Schema>() -> Self {
77        Self {
78            join_type: JoinType::default(),
79            join_table: Self::format_join_table::<M>(),
80            conditions: Vec::new(),
81        }
82    }
83
84    /// Constructs an instance with the `INNER JOIN` type.
85    #[inline]
86    pub fn inner_join<M: Schema>() -> Self {
87        Self {
88            join_type: JoinType::Inner,
89            join_table: Self::format_join_table::<M>(),
90            conditions: Vec::new(),
91        }
92    }
93
94    /// Constructs an instance with the `LEFT (OUTER) JOIN` type.
95    #[inline]
96    pub fn left_join<M: Schema>() -> Self {
97        Self {
98            join_type: JoinType::Left,
99            join_table: Self::format_join_table::<M>(),
100            conditions: Vec::new(),
101        }
102    }
103
104    /// Constructs an instance with the `RIGHT (OUTER) JOIN` type.
105    #[inline]
106    pub fn right_join<M: Schema>() -> Self {
107        Self {
108            join_type: JoinType::Right,
109            join_table: Self::format_join_table::<M>(),
110            conditions: Vec::new(),
111        }
112    }
113
114    /// Constructs an instance with the `FULL (OUTER) JOIN` type.
115    #[inline]
116    pub fn full_join<M: Schema>() -> Self {
117        Self {
118            join_type: JoinType::Full,
119            join_table: Self::format_join_table::<M>(),
120            conditions: Vec::new(),
121        }
122    }
123
124    /// Constructs an instance with the `CROSS JOIN` type.
125    #[inline]
126    pub fn cross_join<M: Schema>() -> Self {
127        Self {
128            join_type: JoinType::Cross,
129            join_table: Self::format_join_table::<M>(),
130            conditions: Vec::new(),
131        }
132    }
133
134    /// Specifies a relation for which the left column is equal to the right column.
135    #[inline]
136    pub fn eq<E1, E2, C1, C2>(self, left_col: C1, right_col: C2) -> Self
137    where
138        E1: Entity,
139        E2: Entity,
140        C1: ModelColumn<E1>,
141        C2: ModelColumn<E2>,
142    {
143        self.push_op::<E1, E2, C1, C2>(left_col, "=", right_col)
144    }
145
146    /// Specifies a relation for which the left column is not equal to the right column.
147    #[inline]
148    pub fn ne<E1, E2, C1, C2>(self, left_col: C1, right_col: C2) -> Self
149    where
150        E1: Entity,
151        E2: Entity,
152        C1: ModelColumn<E1>,
153        C2: ModelColumn<E2>,
154    {
155        self.push_op::<E1, E2, C1, C2>(left_col, "<>", right_col)
156    }
157
158    /// Specifies a relation for which the left column is less than the right column.
159    #[inline]
160    pub fn lt<E1, E2, C1, C2>(self, left_col: C1, right_col: C2) -> Self
161    where
162        E1: Entity,
163        E2: Entity,
164        C1: ModelColumn<E1>,
165        C2: ModelColumn<E2>,
166    {
167        self.push_op::<E1, E2, C1, C2>(left_col, "<", right_col)
168    }
169
170    /// Specifies a relation for which the left column is not greater than the right column.
171    #[inline]
172    pub fn le<E1, E2, C1, C2>(self, left_col: C1, right_col: C2) -> Self
173    where
174        E1: Entity,
175        E2: Entity,
176        C1: ModelColumn<E1>,
177        C2: ModelColumn<E2>,
178    {
179        self.push_op::<E1, E2, C1, C2>(left_col, "<=", right_col)
180    }
181
182    /// Specifies a relation for which the left column is greater than the right column.
183    #[inline]
184    pub fn gt<E1, E2, C1, C2>(self, left_col: C1, right_col: C2) -> Self
185    where
186        E1: Entity,
187        E2: Entity,
188        C1: ModelColumn<E1>,
189        C2: ModelColumn<E2>,
190    {
191        self.push_op::<E1, E2, C1, C2>(left_col, ">", right_col)
192    }
193
194    /// Specifies a relation for which the left column is not less than the right column.
195    #[inline]
196    pub fn ge<E1, E2, C1, C2>(self, left_col: C1, right_col: C2) -> Self
197    where
198        E1: Entity,
199        E2: Entity,
200        C1: ModelColumn<E1>,
201        C2: ModelColumn<E2>,
202    {
203        self.push_op::<E1, E2, C1, C2>(left_col, ">=", right_col)
204    }
205
206    /// Returns the join type.
207    #[inline]
208    pub(super) fn join_type(&self) -> JoinType {
209        self.join_type
210    }
211
212    /// Returns the join table name.
213    #[inline]
214    pub(super) fn join_table(&self) -> &str {
215        &self.join_table
216    }
217
218    /// Formats the conditions.
219    #[inline]
220    pub(super) fn format_conditions(&self) -> String {
221        self.conditions.join(" AND ")
222    }
223
224    /// Formats the join table.
225    #[inline]
226    fn format_join_table<M: Schema>() -> String {
227        let table_name = Query::escape_table_name(M::table_name());
228        let model_name = Query::escape_table_name(M::model_name());
229        format!("{table_name} AS {model_name}")
230    }
231
232    /// Pushes a condition for the two columns.
233    fn push_op<E1, E2, C1, C2>(mut self, left_col: C1, operator: &str, right_col: C2) -> Self
234    where
235        E1: Entity,
236        E2: Entity,
237        C1: ModelColumn<E1>,
238        C2: ModelColumn<E2>,
239    {
240        let left_col = left_col.into_column_expr();
241        let right_col = right_col.into_column_expr();
242        let left_col_field = Query::format_field(&left_col);
243        let right_col_field = Query::format_field(&right_col);
244        let condition = format!("{left_col_field} {operator} {right_col_field}");
245        self.conditions.push(condition);
246        self
247    }
248}