use crate::error::{Error, Result};
use crate::schema::Type;
use std::collections::HashSet;
#[derive(Debug, Clone)]
pub struct Query {
pub name: String,
pub params: Vec<Parameter>,
pub return_type: Type,
pub language: QueryLanguage,
pub source: String,
pub doc_comment: Option<String>,
}
impl 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,
}
}
pub fn add_param(&mut self, param: Parameter) {
self.params.push(param);
}
pub fn get_param(&self, name: &str) -> Option<&Parameter> {
self.params.iter().find(|p| p.name == name)
}
pub fn validate(&self) -> Result<()> {
if self.name.is_empty() {
return Err(Error::Query {
query_name: "unnamed".to_string(),
message: "Query name cannot be empty".to_string(),
});
}
if self.source.is_empty() {
return Err(Error::Query {
query_name: self.name.clone(),
message: "Query source cannot be empty".to_string(),
});
}
let mut seen = HashSet::new();
for param in &self.params {
if !seen.insert(¶m.name) {
return Err(Error::Query {
query_name: self.name.clone(),
message: format!("Duplicate parameter '{}'", param.name),
});
}
}
for param in &self.params {
param.validate(&self.name)?;
}
Ok(())
}
pub fn param_names(&self) -> Vec<&str> {
self.params.iter().map(|p| p.name.as_str()).collect()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Parameter {
pub name: String,
pub param_type: Type,
}
impl Parameter {
pub fn new(name: String, param_type: Type) -> Self {
Self { name, param_type }
}
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(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum QueryLanguage {
HyperQL,
SQL,
Cypher,
Native,
Custom(String),
}
impl QueryLanguage {
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()),
}
}
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>");
}
}