1use std::marker::PhantomData;
4use clicktype_core::traits::{ClickTable, TypedColumn};
5
6pub trait JoinType {
8 fn keyword() -> &'static str;
9}
10
11pub struct Inner;
13impl JoinType for Inner {
14 fn keyword() -> &'static str {
15 "INNER JOIN"
16 }
17}
18
19pub struct Left;
21impl JoinType for Left {
22 fn keyword() -> &'static str {
23 "LEFT JOIN"
24 }
25}
26
27pub struct Right;
29impl JoinType for Right {
30 fn keyword() -> &'static str {
31 "RIGHT JOIN"
32 }
33}
34
35pub struct FullOuter;
37impl JoinType for FullOuter {
38 fn keyword() -> &'static str {
39 "FULL OUTER JOIN"
40 }
41}
42
43pub struct Cross;
45impl JoinType for Cross {
46 fn keyword() -> &'static str {
47 "CROSS JOIN"
48 }
49}
50
51pub struct JoinSpec<T1: ClickTable, T2: ClickTable, J: JoinType> {
53 pub(crate) _table1: PhantomData<T1>,
54 pub(crate) _table2: PhantomData<T2>,
55 pub(crate) _join_type: PhantomData<J>,
56 pub(crate) table2_name: &'static str,
57 pub(crate) on_condition: Option<String>,
58}
59
60impl<T1: ClickTable, T2: ClickTable, J: JoinType> JoinSpec<T1, T2, J> {
61 pub fn new() -> Self {
62 Self {
63 _table1: PhantomData,
64 _table2: PhantomData,
65 _join_type: PhantomData,
66 table2_name: T2::table_name(),
67 on_condition: None,
68 }
69 }
70
71 pub fn on<C1, C2>(mut self, _col1: C1, _col2: C2) -> Self
72 where
73 C1: TypedColumn<Table = T1>,
74 C2: TypedColumn<Table = T2, Type = C1::Type>,
75 {
76 self.on_condition = Some(format!(
77 "{}.{} = {}.{}",
78 T1::table_name(),
79 C1::name(),
80 T2::table_name(),
81 C2::name()
82 ));
83 self
84 }
85
86 pub fn to_sql(&self) -> String {
88 let mut sql = format!("{} {}", J::keyword(), self.table2_name);
89 if let Some(ref condition) = self.on_condition {
90 sql.push_str(" ON ");
91 sql.push_str(condition);
92 }
93 sql
94 }
95}
96
97pub fn inner_join<T1: ClickTable, T2: ClickTable>() -> JoinSpec<T1, T2, Inner> {
99 JoinSpec::new()
100}
101
102pub fn left_join<T1: ClickTable, T2: ClickTable>() -> JoinSpec<T1, T2, Left> {
103 JoinSpec::new()
104}
105
106pub fn right_join<T1: ClickTable, T2: ClickTable>() -> JoinSpec<T1, T2, Right> {
107 JoinSpec::new()
108}
109
110pub fn full_outer_join<T1: ClickTable, T2: ClickTable>() -> JoinSpec<T1, T2, FullOuter> {
111 JoinSpec::new()
112}
113
114pub fn cross_join<T1: ClickTable, T2: ClickTable>() -> JoinSpec<T1, T2, Cross> {
115 JoinSpec::new()
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn test_join_types() {
124 assert_eq!(Inner::keyword(), "INNER JOIN");
125 assert_eq!(Left::keyword(), "LEFT JOIN");
126 assert_eq!(Right::keyword(), "RIGHT JOIN");
127 assert_eq!(FullOuter::keyword(), "FULL OUTER JOIN");
128 assert_eq!(Cross::keyword(), "CROSS JOIN");
129 }
130}