tank_core/
join.rs

1use crate::{
2    DataSet, Expression,
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#[derive(Debug)]
13pub struct Join<L: DataSet, R: DataSet, E: Expression> {
14    pub join: JoinType,
15    pub lhs: L,
16    pub rhs: R,
17    pub on: Option<E>,
18}
19
20#[derive(Default, Clone, Copy, Debug)]
21pub enum JoinType {
22    #[default]
23    Default,
24    Inner,
25    Outer,
26    Left,
27    Right,
28    Cross,
29    Natural,
30}
31
32impl<L: DataSet, R: DataSet, E: Expression> DataSet for Join<L, R, E> {
33    fn qualified_columns() -> bool
34    where
35        Self: Sized,
36    {
37        true
38    }
39    fn write_query(&self, writer: &dyn SqlWriter, context: &mut Context, buff: &mut String) {
40        writer.write_join(
41            context,
42            buff,
43            &Join {
44                join: self.join,
45                lhs: &self.lhs,
46                rhs: &self.rhs,
47                on: self.on.as_ref().map(|v| v as &dyn Expression),
48            },
49        );
50    }
51}
52
53impl Parse for JoinType {
54    fn parse(input: ParseStream) -> syn::Result<Self> {
55        let tokens = input.cursor().token_stream().into_iter().map(|t| match t {
56            TokenTree::Ident(ident) => ident.to_string(),
57            _ => "".to_string(),
58        });
59        let patterns: &[(&[&str], JoinType)] = &[
60            (&["JOIN"], JoinType::Default),
61            (&["INNER", "JOIN"], JoinType::Inner),
62            (&["FULL", "OUTER", "JOIN"], JoinType::Outer),
63            (&["OUTER", "JOIN"], JoinType::Outer),
64            (&["LEFT", "OUTER", "JOIN"], JoinType::Left),
65            (&["LEFT", "JOIN"], JoinType::Left),
66            (&["RIGHT", "OUTER", "JOIN"], JoinType::Right),
67            (&["RIGHT", "JOIN"], JoinType::Right),
68            (&["CROSS", "JOIN"], JoinType::Cross),
69            (&["CROSS"], JoinType::Cross),
70            (&["NATURAL", "JOIN"], JoinType::Natural),
71        ];
72        for (keywords, join_type) in patterns {
73            let it = tokens.clone().take(keywords.len());
74            if it.eq(keywords.iter().copied()) {
75                for _ in 0..keywords.len() {
76                    input.parse::<Ident>().expect(&format!(
77                        "Unexpected error, the input should contain {:?} as next Ident tokens at this point",
78                        keywords
79                    ));
80                }
81                return Ok(*join_type);
82            }
83        }
84        Err(syn::Error::new(input.span(), "Not a join keyword"))
85    }
86}
87
88impl ToTokens for JoinType {
89    fn to_tokens(&self, tokens: &mut TokenStream) {
90        tokens.append_all(match self {
91            JoinType::Default => quote! { ::tank::JoinType::Default },
92            JoinType::Inner => quote! { ::tank::JoinType::Inner },
93            JoinType::Outer => quote! { ::tank::JoinType::Outer },
94            JoinType::Left => quote! { ::tank::JoinType::Left },
95            JoinType::Right => quote! { ::tank::JoinType::Right },
96            JoinType::Cross => quote! { ::tank::JoinType::Cross },
97            JoinType::Natural => quote! { ::tank::JoinType::Natural },
98        });
99    }
100}