158 lines
4.8 KiB
Rust
158 lines
4.8 KiB
Rust
use crate::database::Database;
|
|
use std::sync::Arc;
|
|
|
|
pub mod compiler;
|
|
|
|
use dashmap::DashMap;
|
|
|
|
pub struct Queryer {
|
|
pub db: Arc<Database>,
|
|
cache: DashMap<String, String>,
|
|
}
|
|
|
|
impl Queryer {
|
|
pub fn new(db: Arc<Database>) -> 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<String, serde_json::Value>>,
|
|
) -> Result<(Vec<String>, Vec<serde_json::Value>), 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<String> = filter_entries.iter().map(|(k, _)| k.clone()).collect();
|
|
let args: Vec<serde_json::Value> = 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<String, crate::drop::Drop> {
|
|
if let Some(cached_sql) = self.cache.get(cache_key) {
|
|
return Ok(cached_sql.value().clone());
|
|
}
|
|
|
|
let compiler = compiler::SqlCompiler::new(self.db.clone());
|
|
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()),
|
|
},
|
|
}]),
|
|
}
|
|
}
|
|
}
|