tank-core 0.31.0

Core of Tank: the Rust data layer. This is intended to be used by drivers to implement a backend.
Documentation
use crate::{
    Dataset, DynQuery, Expression, TableRef,
    writer::{Context, SqlWriter},
};
use proc_macro2::{TokenStream, TokenTree};
use quote::{ToTokens, TokenStreamExt, quote};
use syn::{
    Ident,
    parse::{Parse, ParseStream},
};

/// Binary join with optional ON predicate.
///
/// Represents an intermediate node in a join tree.
#[derive(Debug)]
pub struct Join<L: Dataset, R: Dataset, E: Expression> {
    /// Join type (INNER, LEFT, etc.).
    pub join: JoinType,
    /// Left-hand dataset.
    pub lhs: L,
    /// Right-hand dataset.
    pub rhs: R,
    /// Join condition.
    pub on: Option<E>,
}

/// SQL join type variants.
#[derive(Default, Clone, Copy, Debug)]
pub enum JoinType {
    /// Standard JOIN (usually INNER).
    #[default]
    Default,
    Inner,
    Outer,
    Left,
    Right,
    Cross,
    Natural,
}

impl<L: Dataset, R: Dataset, E: Expression> Dataset for Join<L, R, E> {
    fn qualified_columns() -> bool
    where
        Self: Sized,
    {
        true
    }
    fn write_query(&self, writer: &dyn SqlWriter, context: &mut Context, out: &mut DynQuery) {
        writer.write_join(
            context,
            out,
            &Join {
                join: self.join,
                lhs: &self.lhs,
                rhs: &self.rhs,
                on: self.on.as_ref().map(|v| v as &dyn Expression),
            },
        );
    }

    fn table_ref(&self) -> TableRef {
        let mut result = self.lhs.table_ref();
        let other = self.rhs.table_ref();
        result.name = Default::default();
        if result.schema != other.schema {
            result.schema = Default::default();
        }
        result.alias = Default::default();
        result
    }
}

impl Parse for JoinType {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let tokens = input.cursor().token_stream().into_iter().map(|t| match t {
            TokenTree::Ident(ident) => ident.to_string(),
            _ => "".to_string(),
        });
        let patterns: &[(&[&str], JoinType)] = &[
            (&["JOIN"], JoinType::Default),
            (&["INNER", "JOIN"], JoinType::Inner),
            (&["FULL", "OUTER", "JOIN"], JoinType::Outer),
            (&["OUTER", "JOIN"], JoinType::Outer),
            (&["LEFT", "OUTER", "JOIN"], JoinType::Left),
            (&["LEFT", "JOIN"], JoinType::Left),
            (&["RIGHT", "OUTER", "JOIN"], JoinType::Right),
            (&["RIGHT", "JOIN"], JoinType::Right),
            (&["CROSS", "JOIN"], JoinType::Cross),
            (&["NATURAL", "JOIN"], JoinType::Natural),
        ];
        for (keywords, join_type) in patterns {
            let it = tokens.clone().take(keywords.len());
            if it.eq(keywords.iter().copied()) {
                for _ in 0..keywords.len() {
                    input.parse::<Ident>().expect(&format!(
                        "Unexpected error, the input should contain {:?} as next Ident tokens at this point",
                        keywords
                    ));
                }
                return Ok(*join_type);
            }
        }
        Err(syn::Error::new(input.span(), "Not a join keyword"))
    }
}

impl ToTokens for JoinType {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        tokens.append_all(match self {
            JoinType::Default => quote! { ::tank::JoinType::Default },
            JoinType::Inner => quote! { ::tank::JoinType::Inner },
            JoinType::Outer => quote! { ::tank::JoinType::Outer },
            JoinType::Left => quote! { ::tank::JoinType::Left },
            JoinType::Right => quote! { ::tank::JoinType::Right },
            JoinType::Cross => quote! { ::tank::JoinType::Cross },
            JoinType::Natural => quote! { ::tank::JoinType::Natural },
        });
    }
}