use crate::{compiler::ir::IRQuery, error::Result};
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
#[non_exhaustive]
pub enum QueryCardinality {
Single,
Multiple,
List,
}
impl QueryCardinality {
#[must_use]
pub const fn expected_hit_rate(&self) -> f64 {
match self {
Self::Single => 0.91,
Self::Multiple => 0.88,
Self::List => 0.60,
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct QueryEntityProfile {
pub query_name: String,
pub entity_type: Option<String>,
pub cardinality: QueryCardinality,
}
impl QueryEntityProfile {
pub const fn new(
query_name: String,
entity_type: Option<String>,
cardinality: QueryCardinality,
) -> Self {
Self {
query_name,
entity_type,
cardinality,
}
}
#[must_use]
pub const fn expected_hit_rate(&self) -> f64 {
self.cardinality.expected_hit_rate()
}
}
#[derive(Debug, Clone)]
pub struct QueryAnalyzer;
impl QueryAnalyzer {
#[must_use]
pub const fn new() -> Self {
Self
}
pub fn analyze_query(
&self,
query_def: &IRQuery,
query_str: &str,
) -> Result<QueryEntityProfile> {
let cardinality = self.classify_cardinality(query_str);
let entity_type = self.extract_entity_type(query_def);
Ok(QueryEntityProfile {
query_name: query_def.name.clone(),
entity_type,
cardinality,
})
}
fn classify_cardinality(&self, query_str: &str) -> QueryCardinality {
let query_lower = query_str.to_lowercase();
if query_lower.contains("where")
&& query_lower.contains("id")
&& query_lower.contains('=')
&& !query_lower.contains("in")
{
return QueryCardinality::Single;
}
if query_lower.contains("where") && query_lower.contains("in") {
return QueryCardinality::Multiple;
}
QueryCardinality::List
}
fn extract_entity_type(&self, query_def: &IRQuery) -> Option<String> {
if query_def.return_type.is_empty() {
return None;
}
let return_type = &query_def.return_type;
let base_type = if return_type.ends_with("[]") {
&return_type[..return_type.len() - 2]
} else {
return_type.as_str()
};
if base_type.is_empty() {
None
} else {
Some(base_type.to_string())
}
}
}
impl Default for QueryAnalyzer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_where_id_constraint() {
let analyzer = QueryAnalyzer::new();
let cardinality = analyzer.classify_cardinality("SELECT * FROM users WHERE id = ?");
assert_eq!(cardinality, QueryCardinality::Single);
}
#[test]
fn test_parse_where_id_in_constraint() {
let analyzer = QueryAnalyzer::new();
let cardinality =
analyzer.classify_cardinality("SELECT * FROM users WHERE id IN (?, ?, ?)");
assert_eq!(cardinality, QueryCardinality::Multiple);
}
#[test]
fn test_list_queries_no_entity_constraint() {
let analyzer = QueryAnalyzer::new();
let cardinality = analyzer.classify_cardinality("SELECT * FROM users");
assert_eq!(cardinality, QueryCardinality::List);
}
#[test]
fn test_nested_entity_queries() {
let analyzer = QueryAnalyzer::new();
let cardinality = analyzer.classify_cardinality(
"SELECT * FROM (SELECT * FROM users WHERE id = ?) AS u WHERE u.active = true",
);
assert_eq!(cardinality, QueryCardinality::Single);
}
#[test]
fn test_complex_where_clauses() {
let analyzer = QueryAnalyzer::new();
let cardinality = analyzer.classify_cardinality(
"SELECT * FROM users WHERE id = ? AND status = 'active' AND created_at > ?",
);
assert_eq!(cardinality, QueryCardinality::Single);
}
#[test]
fn test_multiple_where_conditions() {
let analyzer = QueryAnalyzer::new();
let cardinality = analyzer
.classify_cardinality("SELECT * FROM users WHERE email = ? OR username = ? LIMIT 1");
assert_eq!(cardinality, QueryCardinality::List);
}
#[test]
fn test_cardinality_hit_rates() {
assert!((QueryCardinality::Single.expected_hit_rate() - 0.91).abs() < f64::EPSILON);
assert!((QueryCardinality::Multiple.expected_hit_rate() - 0.88).abs() < f64::EPSILON);
assert!((QueryCardinality::List.expected_hit_rate() - 0.60).abs() < f64::EPSILON);
}
}