tank_core/
join.rs

1use crate::{
2    DynQuery, DataSet, Expression, TableRef,
3    writer::{Context, SqlWriter},
4};
5use proc_macro2::{TokenStream, TokenTree};
6use quote::{ToTokens, TokenStreamExt, quote};
7use syn::{
8    Ident,
9    parse::{Parse, ParseStream},
10};
11
12/// Binary join with optional ON predicate.
13#[derive(Debug)]
14pub struct Join<L: DataSet, R: DataSet, E: Expression> {
15    /// Join kind.
16    pub join: JoinType,
17    /// Left-hand data set.
18    pub lhs: L,
19    /// Right-hand data set.
20    pub rhs: R,
21    /// Optional ON expression.
22    pub on: Option<E>,
23}
24
25/// SQL join variants.
26#[derive(Default, Clone, Copy, Debug)]
27pub enum JoinType {
28    /// Plain `JOIN` (backend default semantics, often INNER).
29    #[default]
30    Default,
31    Inner,
32    Outer,
33    Left,
34    Right,
35    Cross,
36    Natural,
37}
38
39impl<L: DataSet, R: DataSet, E: Expression> DataSet for Join<L, R, E> {
40    fn qualified_columns() -> bool
41    where
42        Self: Sized,
43    {
44        true
45    }
46    fn write_query(&self, writer: &dyn SqlWriter, context: &mut Context, out: &mut DynQuery) {
47        writer.write_join(
48            context,
49            out,
50            &Join {
51                join: self.join,
52                lhs: &self.lhs,
53                rhs: &self.rhs,
54                on: self.on.as_ref().map(|v| v as &dyn Expression),
55            },
56        );
57    }
58
59    fn table_ref(&self) -> TableRef {
60        let mut result = self.lhs.table_ref();
61        let other = self.rhs.table_ref();
62        if result.name != other.name {
63            result.name = Default::default();
64        }
65        if result.schema != other.schema {
66            result.schema = Default::default();
67        }
68        if result.alias != other.alias {
69            result.alias = Default::default();
70        }
71        result
72    }
73}
74
75impl Parse for JoinType {
76    fn parse(input: ParseStream) -> syn::Result<Self> {
77        let tokens = input.cursor().token_stream().into_iter().map(|t| match t {
78            TokenTree::Ident(ident) => ident.to_string(),
79            _ => "".to_string(),
80        });
81        let patterns: &[(&[&str], JoinType)] = &[
82            (&["JOIN"], JoinType::Default),
83            (&["INNER", "JOIN"], JoinType::Inner),
84            (&["FULL", "OUTER", "JOIN"], JoinType::Outer),
85            (&["OUTER", "JOIN"], JoinType::Outer),
86            (&["LEFT", "OUTER", "JOIN"], JoinType::Left),
87            (&["LEFT", "JOIN"], JoinType::Left),
88            (&["RIGHT", "OUTER", "JOIN"], JoinType::Right),
89            (&["RIGHT", "JOIN"], JoinType::Right),
90            (&["CROSS", "JOIN"], JoinType::Cross),
91            (&["CROSS"], JoinType::Cross),
92            (&["NATURAL", "JOIN"], JoinType::Natural),
93        ];
94        for (keywords, join_type) in patterns {
95            let it = tokens.clone().take(keywords.len());
96            if it.eq(keywords.iter().copied()) {
97                for _ in 0..keywords.len() {
98                    input.parse::<Ident>().expect(&format!(
99                        "Unexpected error, the input should contain {:?} as next Ident tokens at this point",
100                        keywords
101                    ));
102                }
103                return Ok(*join_type);
104            }
105        }
106        Err(syn::Error::new(input.span(), "Not a join keyword"))
107    }
108}
109
110impl ToTokens for JoinType {
111    fn to_tokens(&self, tokens: &mut TokenStream) {
112        tokens.append_all(match self {
113            JoinType::Default => quote! { ::tank::JoinType::Default },
114            JoinType::Inner => quote! { ::tank::JoinType::Inner },
115            JoinType::Outer => quote! { ::tank::JoinType::Outer },
116            JoinType::Left => quote! { ::tank::JoinType::Left },
117            JoinType::Right => quote! { ::tank::JoinType::Right },
118            JoinType::Cross => quote! { ::tank::JoinType::Cross },
119            JoinType::Natural => quote! { ::tank::JoinType::Natural },
120        });
121    }
122}