audb 0.1.11

AuDB - Compile-time database application framework with gold files
Documentation
//! Query model types
//!
//! This module contains the internal representation of database queries,
//! supporting multiple query languages (HyperQL, SQL, Cypher).

use crate::error::{Error, Result};
use crate::schema::Type;
use std::collections::HashSet;

/// Query definition
#[derive(Debug, Clone)]
pub struct Query {
    /// Query name
    pub name: String,

    /// Query parameters
    pub params: Vec<Parameter>,

    /// Return type
    pub return_type: Type,

    /// Query language
    pub language: QueryLanguage,

    /// Query source code
    pub source: String,

    /// Documentation comment
    pub doc_comment: Option<String>,
}

impl Query {
    /// Create a new query
    pub fn new(name: String, language: QueryLanguage, source: String, return_type: Type) -> Self {
        Self {
            name,
            params: Vec::new(),
            return_type,
            language,
            source,
            doc_comment: None,
        }
    }

    /// Add a parameter to the query
    pub fn add_param(&mut self, param: Parameter) {
        self.params.push(param);
    }

    /// Get parameter by name
    pub fn get_param(&self, name: &str) -> Option<&Parameter> {
        self.params.iter().find(|p| p.name == name)
    }

    /// Validate the query
    pub fn validate(&self) -> Result<()> {
        // Check for empty name
        if self.name.is_empty() {
            return Err(Error::Query {
                query_name: "unnamed".to_string(),
                message: "Query name cannot be empty".to_string(),
            });
        }

        // Check for empty source
        if self.source.is_empty() {
            return Err(Error::Query {
                query_name: self.name.clone(),
                message: "Query source cannot be empty".to_string(),
            });
        }

        // Check for duplicate parameter names
        let mut seen = HashSet::new();
        for param in &self.params {
            if !seen.insert(&param.name) {
                return Err(Error::Query {
                    query_name: self.name.clone(),
                    message: format!("Duplicate parameter '{}'", param.name),
                });
            }
        }

        // Validate each parameter
        for param in &self.params {
            param.validate(&self.name)?;
        }

        Ok(())
    }

    /// Extract parameter names from the query
    pub fn param_names(&self) -> Vec<&str> {
        self.params.iter().map(|p| p.name.as_str()).collect()
    }
}

/// Query parameter
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Parameter {
    /// Parameter name
    pub name: String,

    /// Parameter type
    pub param_type: Type,
}

impl Parameter {
    /// Create a new parameter
    pub fn new(name: String, param_type: Type) -> Self {
        Self { name, param_type }
    }

    /// Validate the parameter
    pub fn validate(&self, query_name: &str) -> Result<()> {
        if self.name.is_empty() {
            return Err(Error::Query {
                query_name: query_name.to_string(),
                message: "Parameter name cannot be empty".to_string(),
            });
        }

        Ok(())
    }
}

/// Query language
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum QueryLanguage {
    /// HyperQL (default)
    HyperQL,

    /// Standard SQL
    SQL,

    /// Cypher (Neo4j)
    Cypher,

    /// Native Rust (direct schema method calls)
    Native,

    /// Custom query language
    Custom(String),
}

impl QueryLanguage {
    /// Parse query language from string
    pub fn from_str(s: &str) -> Self {
        match s.to_lowercase().as_str() {
            "hyperql" => QueryLanguage::HyperQL,
            "sql" => QueryLanguage::SQL,
            "cypher" => QueryLanguage::Cypher,
            "native" => QueryLanguage::Native,
            other => QueryLanguage::Custom(other.to_string()),
        }
    }

    /// Get string representation
    pub fn as_str(&self) -> &str {
        match self {
            QueryLanguage::HyperQL => "hyperql",
            QueryLanguage::SQL => "sql",
            QueryLanguage::Cypher => "cypher",
            QueryLanguage::Native => "native",
            QueryLanguage::Custom(s) => s,
        }
    }
}

impl Default for QueryLanguage {
    fn default() -> Self {
        QueryLanguage::HyperQL
    }
}

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

    #[test]
    fn test_query_creation() {
        let query = Query::new(
            "get_user".to_string(),
            QueryLanguage::HyperQL,
            "SELECT * FROM users".to_string(),
            "User".to_string(),
        );

        assert_eq!(query.name, "get_user");
        assert_eq!(query.language, QueryLanguage::HyperQL);
        assert_eq!(query.params.len(), 0);
    }

    #[test]
    fn test_query_add_param() {
        let mut query = Query::new(
            "get_user".to_string(),
            QueryLanguage::HyperQL,
            "SELECT * FROM users WHERE id = :id".to_string(),
            "User".to_string(),
        );

        query.add_param(Parameter::new("id".to_string(), "EntityId".to_string()));

        assert_eq!(query.params.len(), 1);
        assert!(query.get_param("id").is_some());
    }

    #[test]
    fn test_query_validation_empty_name() {
        let query = Query::new(
            String::new(),
            QueryLanguage::HyperQL,
            "SELECT *".to_string(),
            "User".to_string(),
        );

        let result = query.validate();
        assert!(result.is_err());
    }

    #[test]
    fn test_query_validation_empty_source() {
        let query = Query::new(
            "test".to_string(),
            QueryLanguage::HyperQL,
            String::new(),
            "User".to_string(),
        );

        let result = query.validate();
        assert!(result.is_err());
    }

    #[test]
    fn test_query_validation_duplicate_params() {
        let mut query = Query::new(
            "test".to_string(),
            QueryLanguage::HyperQL,
            "SELECT *".to_string(),
            "User".to_string(),
        );

        query.add_param(Parameter::new("id".to_string(), "EntityId".to_string()));
        query.add_param(Parameter::new("id".to_string(), "String".to_string()));

        let result = query.validate();
        assert!(result.is_err());
    }

    #[test]
    fn test_param_names() {
        let mut query = Query::new(
            "test".to_string(),
            QueryLanguage::HyperQL,
            "SELECT *".to_string(),
            "User".to_string(),
        );

        query.add_param(Parameter::new("id".to_string(), "EntityId".to_string()));
        query.add_param(Parameter::new("name".to_string(), "String".to_string()));

        let names = query.param_names();
        assert_eq!(names.len(), 2);
        assert!(names.contains(&"id"));
        assert!(names.contains(&"name"));
    }

    #[test]
    fn test_base_return_type() {
        let query1 = Query::new(
            "test".to_string(),
            QueryLanguage::HyperQL,
            "SELECT *".to_string(),
            "User".to_string(),
        );
        assert_eq!(query1.base_return_type(), "User");

        let query2 = Query::new(
            "test".to_string(),
            QueryLanguage::HyperQL,
            "SELECT *".to_string(),
            "Vec<User>".to_string(),
        );
        assert_eq!(query2.base_return_type(), "User");

        let query3 = Query::new(
            "test".to_string(),
            QueryLanguage::HyperQL,
            "SELECT *".to_string(),
            "Option<User>".to_string(),
        );
        assert_eq!(query3.base_return_type(), "User");
    }

    #[test]
    fn test_query_language_from_str() {
        assert_eq!(QueryLanguage::from_str("hyperql"), QueryLanguage::HyperQL);
        assert_eq!(QueryLanguage::from_str("sql"), QueryLanguage::SQL);
        assert_eq!(QueryLanguage::from_str("cypher"), QueryLanguage::Cypher);
        assert_eq!(
            QueryLanguage::from_str("custom"),
            QueryLanguage::Custom("custom".to_string())
        );
    }

    #[test]
    fn test_query_language_as_str() {
        assert_eq!(QueryLanguage::HyperQL.as_str(), "hyperql");
        assert_eq!(QueryLanguage::SQL.as_str(), "sql");
        assert_eq!(QueryLanguage::Cypher.as_str(), "cypher");
    }

    #[test]
    fn test_query_language_default() {
        assert_eq!(QueryLanguage::default(), QueryLanguage::HyperQL);
    }

    #[test]
    fn test_parameter_validation() {
        let param = Parameter::new("id".to_string(), "EntityId".to_string());
        assert!(param.validate("test_query").is_ok());

        let empty_name = Parameter::new(String::new(), "EntityId".to_string());
        assert!(empty_name.validate("test_query").is_err());

        let empty_type = Parameter::new("id".to_string(), String::new());
        assert!(empty_type.validate("test_query").is_err());
    }

    #[test]
    fn test_extract_base_type() {
        assert_eq!(extract_base_type("User"), "User");
        assert_eq!(extract_base_type("Vec<User>"), "User");
        assert_eq!(extract_base_type("Option<String>"), "String");
        assert_eq!(extract_base_type("Vec<Vec<User>>"), "Vec<User>");
    }
}