use crate::database::Database; use std::sync::Arc; pub mod compiler; use dashmap::DashMap; pub struct Queryer { pub db: Arc, cache: DashMap, } impl Queryer { pub fn new(db: Arc) -> Self { Self { db, cache: DashMap::new(), } } pub fn query( &self, schema_id: &str, stem_opt: Option<&str>, filters: Option<&serde_json::Value>, ) -> crate::drop::Drop { let filters_map = filters.and_then(|f| f.as_object()); // 1. Process filters into structured $op keys and linear values let (filter_keys, args) = match self.parse_filter_entries(filters_map) { Ok(res) => res, Err(msg) => { return crate::drop::Drop::with_errors(vec![crate::drop::Error { code: "FILTER_PARSE_FAILED".to_string(), message: msg.clone(), details: crate::drop::ErrorDetails { path: "".to_string(), // filters apply to the root query cause: Some(msg), context: filters.map(|f| vec![f.to_string()]), schema: Some(schema_id.to_string()), }, }]); } }; let stem_key = stem_opt.unwrap_or("/"); let cache_key = format!("{}(Stem:{}):{}", schema_id, stem_key, filter_keys.join(",")); // 2. Fetch from cache or compile let sql = match self.get_or_compile_sql(&cache_key, schema_id, stem_opt, &filter_keys) { Ok(sql) => sql, Err(drop) => return drop, }; // 3. Execute via Database Executor self.execute_sql(schema_id, &sql, &args) } fn parse_filter_entries( &self, filters_map: Option<&serde_json::Map>, ) -> Result<(Vec, Vec), String> { let mut filter_entries: Vec<(String, serde_json::Value)> = Vec::new(); if let Some(fm) = filters_map { for (key, val) in fm { if let Some(obj) = val.as_object() { for (op, op_val) in obj { if !op.starts_with('$') { return Err(format!("Filter operator must start with '$', got: {}", op)); } filter_entries.push((format!("{}:{}", key, op), op_val.clone())); } } else { return Err(format!( "Filter for field '{}' must be an object with operators like $eq, $in, etc.", key )); } } } filter_entries.sort_by(|a, b| a.0.cmp(&b.0)); let filter_keys: Vec = filter_entries.iter().map(|(k, _)| k.clone()).collect(); let args: Vec = filter_entries.into_iter().map(|(_, v)| v).collect(); Ok((filter_keys, args)) } fn get_or_compile_sql( &self, cache_key: &str, schema_id: &str, stem_opt: Option<&str>, filter_keys: &[String], ) -> Result { if let Some(cached_sql) = self.cache.get(cache_key) { return Ok(cached_sql.value().clone()); } let compiler = compiler::Compiler { db: &self.db, filter_keys: filter_keys, is_stem_query: stem_opt.is_some(), alias_counter: 0, }; match compiler.compile(schema_id, stem_opt, filter_keys) { Ok(compiled_sql) => { self .cache .insert(cache_key.to_string(), compiled_sql.clone()); Ok(compiled_sql) } Err(e) => Err(crate::drop::Drop::with_errors(vec![crate::drop::Error { code: "QUERY_COMPILATION_FAILED".to_string(), message: e.clone(), details: crate::drop::ErrorDetails { path: "".to_string(), cause: Some(e), context: None, schema: Some(schema_id.to_string()), }, }])), } } fn execute_sql( &self, schema_id: &str, sql: &str, args: &[serde_json::Value], ) -> crate::drop::Drop { match self.db.query(sql, Some(args)) { Ok(serde_json::Value::Array(table)) => { if table.is_empty() { crate::drop::Drop::success_with_val(serde_json::Value::Null) } else { crate::drop::Drop::success_with_val(table.first().unwrap().clone()) } } Ok(other) => crate::drop::Drop::with_errors(vec![crate::drop::Error { code: "QUERY_FAILED".to_string(), message: format!("Expected array from generic query, got: {:?}", other), details: crate::drop::ErrorDetails { path: "".to_string(), cause: Some(format!("Expected array, got {}", other)), context: Some(vec![sql.to_string()]), schema: Some(schema_id.to_string()), }, }]), Err(e) => crate::drop::Drop::with_errors(vec![crate::drop::Error { code: "QUERY_FAILED".to_string(), message: format!("SPI error in queryer: {}", e), details: crate::drop::ErrorDetails { path: "".to_string(), cause: Some(format!("SPI error in queryer: {}", e)), context: Some(vec![sql.to_string()]), schema: Some(schema_id.to_string()), }, }]), } } }