audb-codegen 0.1.11

Code generation for AuDB compile-time database applications
//! Filter Compiler
//!
//! Compiles WHERE clause expressions into inline Rust filter predicates.

use crate::hyperql::{
    CompilerError, CompilerResult, FilterCondition, FilterOperator, FilterValue, LiteralValue,
};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};

/// Compiles filter conditions into Rust code
pub struct FilterCompiler;

impl FilterCompiler {
    /// Create a new filter compiler
    pub fn new() -> Self {
        Self
    }

    /// Compile filter conditions into inline predicate code
    ///
    /// Generates code like:
    /// ```ignore
    /// age > min_age && active
    /// ```
    pub fn compile(&self, filters: &[FilterCondition]) -> CompilerResult<TokenStream> {
        if filters.is_empty() {
            return Ok(quote! { true });
        }

        // Compile each condition
        let conditions: Result<Vec<_>, _> =
            filters.iter().map(|f| self.compile_condition(f)).collect();

        let conditions = conditions?;

        // Combine with AND logic
        if conditions.len() == 1 {
            Ok(conditions[0].clone())
        } else {
            Ok(quote! {
                #(#conditions)&&*
            })
        }
    }

    /// Compile a single filter condition
    ///
    /// Generates code like:
    /// ```ignore
    /// entity.age > min_age
    /// entity.active == true
    /// ```
    pub fn compile_condition(&self, condition: &FilterCondition) -> CompilerResult<TokenStream> {
        let field_ident = format_ident!("{}", condition.field);

        // Compile the value (parameter or literal)
        let value_tokens = self.compile_value(&condition.value)?;

        // Generate the comparison - reference entity.field
        let comparison = match condition.operator {
            FilterOperator::Equal => quote! { entity.#field_ident == #value_tokens },
            FilterOperator::NotEqual => quote! { entity.#field_ident != #value_tokens },
            FilterOperator::LessThan => quote! { entity.#field_ident < #value_tokens },
            FilterOperator::LessThanOrEqual => quote! { entity.#field_ident <= #value_tokens },
            FilterOperator::GreaterThan => quote! { entity.#field_ident > #value_tokens },
            FilterOperator::GreaterThanOrEqual => quote! { entity.#field_ident >= #value_tokens },
            FilterOperator::Like => {
                // String pattern matching
                quote! { entity.#field_ident.contains(#value_tokens) }
            }
            FilterOperator::In => {
                return Err(CompilerError::UnsupportedPattern(
                    "IN operator not yet supported in filters".to_string(),
                ));
            }
        };

        Ok(comparison)
    }

    /// Compile a filter value (parameter or literal)
    fn compile_value(&self, value: &FilterValue) -> CompilerResult<TokenStream> {
        match value {
            FilterValue::Parameter(param_name) => {
                let param_ident = format_ident!("{}", param_name);
                Ok(quote! { #param_ident })
            }
            FilterValue::Literal(lit) => {
                let lit_tokens = self.compile_literal(lit);
                Ok(lit_tokens)
            }
        }
    }

    /// Compile a literal value
    fn compile_literal(&self, literal: &LiteralValue) -> TokenStream {
        match literal {
            LiteralValue::Null => quote! { None },
            LiteralValue::Boolean(b) => quote! { #b },
            LiteralValue::Integer(i) => quote! { #i },
            LiteralValue::Float(f) => quote! { #f },
            LiteralValue::String(s) => quote! { #s },
        }
    }

    /// Compile AND/OR logic for multiple conditions
    pub fn compile_logical_combination(
        &self,
        conditions: &[FilterCondition],
    ) -> CompilerResult<TokenStream> {
        self.compile(conditions)
    }
}

impl Default for FilterCompiler {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_filter_compiler_creation() {
        let compiler = FilterCompiler::new();
        assert!(true); // Compiler creates successfully
    }

    #[test]
    fn test_compile_single_equal_filter() {
        let compiler = FilterCompiler::new();
        let filter = FilterCondition {
            field: "age".to_string(),
            operator: FilterOperator::Equal,
            value: FilterValue::Parameter("min_age".to_string()),
        };

        let result = compiler.compile_condition(&filter);
        assert!(
            result.is_ok(),
            "Failed to compile equal filter: {:?}",
            result
        );

        let code = result.unwrap();
        let code_str = code.to_string();
        assert!(code_str.contains("entity"));
        assert!(code_str.contains("age"));
        assert!(code_str.contains("=="));
        assert!(code_str.contains("min_age"));
    }

    #[test]
    fn test_compile_greater_than_filter() {
        let compiler = FilterCompiler::new();
        let filter = FilterCondition {
            field: "age".to_string(),
            operator: FilterOperator::GreaterThan,
            value: FilterValue::Literal(LiteralValue::Integer(25)),
        };

        let result = compiler.compile_condition(&filter);
        assert!(result.is_ok());

        let code = result.unwrap();
        let code_str = code.to_string();
        assert!(code_str.contains("entity"));
        assert!(code_str.contains("age"));
        assert!(code_str.contains(">"));
        assert!(code_str.contains("25"));
    }

    #[test]
    fn test_compile_multiple_filters() {
        let compiler = FilterCompiler::new();
        let filters = vec![
            FilterCondition {
                field: "age".to_string(),
                operator: FilterOperator::GreaterThan,
                value: FilterValue::Parameter("min_age".to_string()),
            },
            FilterCondition {
                field: "active".to_string(),
                operator: FilterOperator::Equal,
                value: FilterValue::Literal(LiteralValue::Boolean(true)),
            },
        ];

        let result = compiler.compile(&filters);
        assert!(
            result.is_ok(),
            "Failed to compile multiple filters: {:?}",
            result
        );

        let code = result.unwrap();
        let code_str = code.to_string();
        assert!(code_str.contains("entity . age"));
        assert!(code_str.contains("entity . active"));
        assert!(code_str.contains("&&"));
    }

    #[test]
    fn test_compile_all_comparison_operators() {
        let compiler = FilterCompiler::new();

        let operators = vec![
            (FilterOperator::Equal, "=="),
            (FilterOperator::NotEqual, "!="),
            (FilterOperator::LessThan, "<"),
            (FilterOperator::LessThanOrEqual, "<="),
            (FilterOperator::GreaterThan, ">"),
            (FilterOperator::GreaterThanOrEqual, ">="),
        ];

        for (op, expected_token) in operators {
            let filter = FilterCondition {
                field: "value".to_string(),
                operator: op,
                value: FilterValue::Parameter("param".to_string()),
            };

            let result = compiler.compile_condition(&filter);
            assert!(result.is_ok(), "Failed to compile operator {:?}", op);

            let code = result.unwrap();
            let code_str = code.to_string();
            assert!(
                code_str.contains(expected_token),
                "Expected '{}' in generated code for {:?}",
                expected_token,
                op
            );
        }
    }

    #[test]
    fn test_compile_literal_values() {
        let compiler = FilterCompiler::new();

        let literals = vec![
            (LiteralValue::Boolean(true), "true"),
            (LiteralValue::Integer(42), "42"),
            (LiteralValue::Float(3.14), "3.14"),
            (LiteralValue::String("test".to_string()), "\"test\""),
        ];

        for (lit, expected) in literals {
            let filter = FilterCondition {
                field: "field".to_string(),
                operator: FilterOperator::Equal,
                value: FilterValue::Literal(lit),
            };

            let result = compiler.compile_condition(&filter);
            assert!(result.is_ok());

            let code = result.unwrap();
            let code_str = code.to_string();
            assert!(
                code_str.contains(expected),
                "Expected '{}' in generated code",
                expected
            );
        }
    }

    #[test]
    fn test_compile_empty_filters() {
        let compiler = FilterCompiler::new();
        let filters = vec![];

        let result = compiler.compile(&filters);
        assert!(result.is_ok());

        let code = result.unwrap();
        let code_str = code.to_string();
        assert_eq!(code_str, "true");
    }

    #[test]
    fn test_compile_like_operator() {
        let compiler = FilterCompiler::new();
        let filter = FilterCondition {
            field: "name".to_string(),
            operator: FilterOperator::Like,
            value: FilterValue::Literal(LiteralValue::String("John".to_string())),
        };

        let result = compiler.compile_condition(&filter);
        assert!(result.is_ok());

        let code = result.unwrap();
        let code_str = code.to_string();
        assert!(code_str.contains("entity . name"));
        assert!(code_str.contains("contains"));
        assert!(code_str.contains("\"John\""));
    }

    #[test]
    fn test_compile_in_operator_unsupported() {
        let compiler = FilterCompiler::new();
        let filter = FilterCondition {
            field: "id".to_string(),
            operator: FilterOperator::In,
            value: FilterValue::Parameter("ids".to_string()),
        };

        let result = compiler.compile_condition(&filter);
        assert!(result.is_err());

        if let Err(CompilerError::UnsupportedPattern(msg)) = result {
            assert!(msg.contains("IN operator"));
        } else {
            panic!("Expected UnsupportedPattern error for IN operator");
        }
    }
}