graphrecords-macros 0.5.0

High-performance graph-based data records
Documentation
pub mod match_inputs;
pub mod node;
pub mod operation_inputs;
pub mod optimize_inputs;
pub mod optimizer_hints;
pub mod plan_identity;
pub mod plan_inputs;

use crate::{attribute::FromAttributes, query::resolve_query_crate_path};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{
    Data, DeriveInput, Error, Fields, Generics, Ident, Index, LitStr, Path, Result, Type,
    meta::ParseNestedMeta, parse_quote,
};

pub struct Hints {
    pub commutes_with_filter: TokenStream,
    pub allows_limit_pushdown: TokenStream,
    pub volatile: TokenStream,
    pub empty: TokenStream,
}

impl Default for Hints {
    fn default() -> Self {
        Self {
            commutes_with_filter: quote!(false),
            allows_limit_pushdown: quote!(false),
            volatile: quote!(false),
            empty: quote!(Never),
        }
    }
}

impl Hints {
    fn parse(meta: &ParseNestedMeta) -> Result<Self> {
        let mut hints = Self::default();

        meta.parse_nested_meta(|hint| {
            if hint.path.is_ident("commutes_with_filter") {
                hints.commutes_with_filter = quote!(true);
                Ok(())
            } else if hint.path.is_ident("allows_limit_pushdown") {
                hints.allows_limit_pushdown = quote!(true);
                Ok(())
            } else if hint.path.is_ident("volatile") {
                hints.volatile = quote!(true);
                Ok(())
            } else if hint.path.is_ident("empty") {
                let rule: Ident = hint.value()?.parse()?;
                hints.empty = match rule.to_string().as_str() {
                    "never" => quote!(Never),
                    "if_any" => quote!(IfAnyInput),
                    "if_all" => quote!(IfAllInputs),
                    other => {
                        return Err(hint.error(format!(
                            "unknown empty rule `{other}`, expected `never`, `if_any`, or `if_all`"
                        )));
                    }
                };
                Ok(())
            } else {
                Err(hint.error("unknown optimizer hint"))
            }
        })?;

        Ok(hints)
    }
}

pub struct PlanModel {
    pub ident: Ident,
    pub generics: Generics,
    pub crate_path: Path,
    pub operand: Option<Type>,
    pub hints: Hints,
    pub inputs: Vec<TokenStream>,
    pub input_types: Vec<Type>,
    pub arguments: Vec<TokenStream>,
    pub argument_types: Vec<Type>,
    pub payload: Vec<TokenStream>,
    pub is_unit: bool,
}

impl PlanModel {
    pub fn parse(input: &DeriveInput) -> Result<Self> {
        let Data::Struct(data) = &input.data else {
            return Err(Error::new_spanned(
                input,
                "plan derives can only be applied to structs",
            ));
        };

        let attribute = PlanAttribute::from_attributes(&input.attrs)?;

        let crate_path = match attribute.crate_path {
            Some(path) => path,
            None => resolve_query_crate_path()?,
        };

        let mut inputs = Vec::new();
        let mut input_types = Vec::new();
        let mut arguments = Vec::new();
        let mut argument_types = Vec::new();
        let mut payload = Vec::new();

        for (index, field) in data.fields.iter().enumerate() {
            let accessor = if let Some(name) = &field.ident {
                quote!(#name)
            } else {
                let index = Index::from(index);
                quote!(#index)
            };

            if field
                .attrs
                .iter()
                .any(|attribute| attribute.path().is_ident("input"))
            {
                inputs.push(accessor);
                input_types.push(field.ty.clone());
            } else if field
                .attrs
                .iter()
                .any(|attribute| attribute.path().is_ident("argument"))
            {
                arguments.push(accessor);
                argument_types.push(field.ty.clone());
            } else {
                payload.push(accessor);
            }
        }

        Ok(Self {
            ident: input.ident.clone(),
            generics: input.generics.clone(),
            crate_path,
            operand: attribute.operand,
            hints: attribute.hints,
            inputs,
            input_types,
            arguments,
            argument_types,
            payload,
            is_unit: matches!(data.fields, Fields::Unit),
        })
    }
}

pub fn with_bounds(generics: &Generics, types: &[Type], bound: &TokenStream) -> Generics {
    let mut generics = generics.clone();

    if types.is_empty() {
        return generics;
    }

    let where_clause = generics.make_where_clause();

    for ty in types {
        where_clause.predicates.push(parse_quote!(#ty: #bound));
    }

    generics
}

#[derive(Default)]
struct PlanAttribute {
    operand: Option<Type>,
    crate_path: Option<Path>,
    hints: Hints,
}

impl FromAttributes for PlanAttribute {
    const NAMESPACE: &'static str = "plan";

    fn parse_meta(&mut self, meta: ParseNestedMeta) -> Result<()> {
        if meta.path.is_ident("operand") {
            self.operand = Some(meta.value()?.parse::<Type>()?);
            Ok(())
        } else if meta.path.is_ident("crate") {
            self.crate_path = Some(meta.value()?.parse::<LitStr>()?.parse()?);
            Ok(())
        } else if meta.path.is_ident("optimizer_hints") {
            self.hints = Hints::parse(&meta)?;
            Ok(())
        } else {
            Err(meta.error("unknown plan attribute"))
        }
    }
}