1use std::collections::HashSet;
2
3use async_graphql::dynamic::ObjectAccessor;
4
5use crate::compiler::filter;
6use crate::compiler::ir::*;
7use crate::cube::definition::{CubeDefinition, SelectorDef};
8
9pub struct MetricRequest {
11 pub function: String,
12 pub of_dimension: String,
13 pub select_where_value: Option<async_graphql::Value>,
15 pub condition_filter: Option<FilterNode>,
17}
18
19pub fn parse_cube_query(
20 cube: &CubeDefinition,
21 network: &str,
22 args: &ObjectAccessor,
23 metrics: &[MetricRequest],
24 requested_fields: Option<HashSet<String>>,
25) -> Result<QueryIR, async_graphql::Error> {
26 let table = cube.table_for_chain(network);
27
28 let filters = if let Ok(where_val) = args.try_get("where") {
29 if let Ok(where_obj) = where_val.object() {
30 filter::parse_where(&where_obj, &cube.dimensions)?
31 } else {
32 FilterNode::Empty
33 }
34 } else {
35 FilterNode::Empty
36 };
37
38 let filters = merge_selector_filters(filters, args, &cube.selectors)?;
39 let filters = if let Some(ref chain_col) = cube.chain_column {
42 let chain_filter = FilterNode::Condition {
43 column: chain_col.clone(),
44 op: CompareOp::Eq,
45 value: SqlValue::String(network.to_string()),
46 };
47 if filters.is_empty() {
48 chain_filter
49 } else {
50 FilterNode::And(vec![chain_filter, filters])
51 }
52 } else {
53 filters
54 };
55 let filters = apply_default_filters(filters, &cube.default_filters);
56 let (limit, offset) = parse_limit(args, cube.default_limit, cube.max_limit)?;
57 let order_by = parse_order_by(args, cube)?;
58
59 let flat = cube.flat_dimensions();
60 let mut selects: Vec<SelectExpr> = flat
61 .iter()
62 .filter(|(path, _)| {
63 requested_fields
64 .as_ref()
65 .is_none_or(|rf| rf.contains(path))
66 })
67 .map(|(_, dim)| SelectExpr::Column {
68 column: dim.column.clone(),
69 alias: None,
70 })
71 .collect();
72
73 if selects.is_empty() && !flat.is_empty() {
75 selects = flat
76 .iter()
77 .map(|(_, dim)| SelectExpr::Column {
78 column: dim.column.clone(),
79 alias: None,
80 })
81 .collect();
82 }
83
84 let mut group_by = Vec::new();
85 let mut having = FilterNode::Empty;
86
87 if !metrics.is_empty() {
88 group_by = selects
89 .iter()
90 .filter_map(|s| match s {
91 SelectExpr::Column { column, .. } => Some(column.clone()),
92 _ => None,
93 })
94 .collect();
95
96 for m in metrics {
97 let dim_col = flat
98 .iter()
99 .find(|(path, _)| path == &m.of_dimension)
100 .map(|(_, dim)| dim.column.clone())
101 .unwrap_or_else(|| "*".to_string());
102
103 let func = m.function.to_uppercase();
104 let alias = format!("__{}", m.function);
105
106 let condition = m.condition_filter.as_ref().and_then(|f| {
107 let sql = compile_filter_inline(f);
108 if sql.is_empty() { None } else { Some(sql) }
109 });
110
111 selects.push(SelectExpr::Aggregate {
112 function: func.clone(),
113 column: dim_col.clone(),
114 alias,
115 condition,
116 });
117
118 if let Some(async_graphql::Value::Object(ref obj)) = m.select_where_value {
119 let agg_expr = if func == "COUNT" && dim_col == "*" {
120 "COUNT(*)".to_string()
121 } else if func == "UNIQ" {
122 format!("COUNT(DISTINCT `{dim_col}`)")
123 } else {
124 format!("{func}(`{dim_col}`)")
125 };
126
127 let h = parse_select_where_from_value(obj, &agg_expr)?;
128 if !h.is_empty() {
129 having = if having.is_empty() {
130 h
131 } else {
132 FilterNode::And(vec![having, h])
133 };
134 }
135 }
136 }
137 }
138
139 Ok(QueryIR {
140 cube: cube.name.clone(),
141 schema: cube.schema.clone(),
142 table,
143 selects,
144 filters,
145 having,
146 group_by,
147 order_by,
148 limit,
149 offset,
150 use_final: cube.use_final,
151 })
152}
153
154fn parse_select_where_from_value(
157 obj: &indexmap::IndexMap<async_graphql::Name, async_graphql::Value>,
158 aggregate_expr: &str,
159) -> Result<FilterNode, async_graphql::Error> {
160 let mut conditions = Vec::new();
161
162 for (key, op) in &[
163 ("eq", CompareOp::Eq),
164 ("gt", CompareOp::Gt),
165 ("ge", CompareOp::Ge),
166 ("lt", CompareOp::Lt),
167 ("le", CompareOp::Le),
168 ] {
169 if let Some(val) = obj.get(*key) {
170 let sql_val = match val {
171 async_graphql::Value::String(s) => {
172 if let Ok(f) = s.parse::<f64>() {
173 SqlValue::Float(f)
174 } else {
175 SqlValue::String(s.clone())
176 }
177 }
178 async_graphql::Value::Number(n) => {
179 if let Some(f) = n.as_f64() {
180 SqlValue::Float(f)
181 } else {
182 SqlValue::Int(n.as_i64().unwrap_or(0))
183 }
184 }
185 _ => continue,
186 };
187 conditions.push(FilterNode::Condition {
188 column: aggregate_expr.to_string(),
189 op: op.clone(),
190 value: sql_val,
191 });
192 }
193 }
194
195 Ok(match conditions.len() {
196 0 => FilterNode::Empty,
197 1 => conditions.into_iter().next().unwrap(),
198 _ => FilterNode::And(conditions),
199 })
200}
201
202fn merge_selector_filters(
203 base: FilterNode,
204 args: &ObjectAccessor,
205 selectors: &[SelectorDef],
206) -> Result<FilterNode, async_graphql::Error> {
207 let mut extra = Vec::new();
208
209 for sel in selectors {
210 if let Ok(val) = args.try_get(&sel.graphql_name) {
211 if let Ok(obj) = val.object() {
212 let leaf_filters =
213 filter::parse_leaf_filter_for_selector(&obj, &sel.column, &sel.dim_type)?;
214 extra.extend(leaf_filters);
215 }
216 }
217 }
218
219 if extra.is_empty() {
220 return Ok(base);
221 }
222 if base.is_empty() {
223 return Ok(if extra.len() == 1 {
224 extra.remove(0)
225 } else {
226 FilterNode::And(extra)
227 });
228 }
229 extra.push(base);
230 Ok(FilterNode::And(extra))
231}
232
233fn apply_default_filters(user_filters: FilterNode, defaults: &[(String, String)]) -> FilterNode {
234 if defaults.is_empty() {
235 return user_filters;
236 }
237
238 let mut default_nodes: Vec<FilterNode> = defaults
239 .iter()
240 .map(|(col, val)| {
241 let sql_val = if val == "true" || val == "false" {
242 SqlValue::Bool(val == "true")
243 } else if let Ok(n) = val.parse::<i64>() {
244 SqlValue::Int(n)
245 } else {
246 SqlValue::String(val.clone())
247 };
248 FilterNode::Condition {
249 column: col.clone(),
250 op: CompareOp::Eq,
251 value: sql_val,
252 }
253 })
254 .collect();
255
256 if user_filters.is_empty() {
257 if default_nodes.len() == 1 {
258 return default_nodes.remove(0);
259 }
260 return FilterNode::And(default_nodes);
261 }
262
263 default_nodes.push(user_filters);
264 FilterNode::And(default_nodes)
265}
266
267fn parse_limit(
268 args: &ObjectAccessor,
269 default: u32,
270 max: u32,
271) -> Result<(u32, u32), async_graphql::Error> {
272 let mut limit = default;
273 let mut offset = 0u32;
274
275 if let Ok(limit_val) = args.try_get("limit") {
276 if let Ok(limit_obj) = limit_val.object() {
277 if let Ok(count) = limit_obj.try_get("count") {
278 limit = (count.i64()? as u32).min(max);
279 }
280 if let Ok(off) = limit_obj.try_get("offset") {
281 offset = off.i64()? as u32;
282 }
283 }
284 }
285
286 Ok((limit, offset))
287}
288
289fn parse_order_by(
290 args: &ObjectAccessor,
291 cube: &CubeDefinition,
292) -> Result<Vec<OrderExpr>, async_graphql::Error> {
293 let flat = cube.flat_dimensions();
294
295 if let Ok(list_val) = args.try_get("orderByList") {
296 if let Ok(list) = list_val.list() {
297 let mut orders = Vec::new();
298 for item in list.iter() {
299 let obj = item.object()
300 .map_err(|_| async_graphql::Error::new("orderByList items must be objects"))?;
301 let field_accessor = obj.try_get("field")
302 .map_err(|_| async_graphql::Error::new("orderByList item requires 'field'"))?;
303 let field_str = field_accessor.enum_name()
304 .map_err(|_| async_graphql::Error::new("orderByList 'field' must be an enum value"))?;
305 let descending = if let Ok(dir_accessor) = obj.try_get("direction") {
306 dir_accessor.enum_name() == Ok("DESC")
307 } else {
308 false
309 };
310 let column = flat.iter()
311 .find(|(p, _)| p == field_str)
312 .map(|(_, dim)| dim.column.clone())
313 .ok_or_else(|| async_graphql::Error::new(format!("Unknown orderBy field: {field_str}")))?;
314 orders.push(OrderExpr { column, descending });
315 }
316 if !orders.is_empty() {
317 return Ok(orders);
318 }
319 }
320 }
321
322 let order_val = match args.try_get("orderBy") {
323 Ok(v) => v,
324 Err(_) => return Ok(Vec::new()),
325 };
326
327 let enum_str = order_val
328 .enum_name()
329 .map_err(|_| async_graphql::Error::new("orderBy must be an enum value"))?;
330
331 let (descending, field_path) = if let Some(path) = enum_str.strip_suffix("_DESC") {
332 (true, path)
333 } else if let Some(path) = enum_str.strip_suffix("_ASC") {
334 (false, path)
335 } else {
336 return Err(async_graphql::Error::new(format!(
337 "Invalid orderBy value: {enum_str}"
338 )));
339 };
340
341 let column = flat
342 .iter()
343 .find(|(p, _)| p == field_path)
344 .map(|(_, dim)| dim.column.clone())
345 .ok_or_else(|| {
346 async_graphql::Error::new(format!("Unknown orderBy field: {field_path}"))
347 })?;
348
349 Ok(vec![OrderExpr { column, descending }])
350}
351
352fn compile_filter_inline(node: &FilterNode) -> String {
355 match node {
356 FilterNode::Empty => String::new(),
357 FilterNode::Condition { column, op, value } => {
358 let col = if column.contains('(') { column.clone() } else { format!("`{column}`") };
359 if op.is_unary() {
360 return format!("{col} {}", op.sql_op());
361 }
362 let val_str = match value {
363 SqlValue::String(s) => format!("'{}'", s.replace('\'', "\\'")),
364 SqlValue::Int(i) => i.to_string(),
365 SqlValue::Float(f) => f.to_string(),
366 SqlValue::Bool(b) => if *b { "1".to_string() } else { "0".to_string() },
367 };
368 match op {
369 CompareOp::In | CompareOp::NotIn => {
370 if let SqlValue::String(csv) = value {
371 let items: Vec<String> = csv.split(',')
372 .map(|s| format!("'{}'", s.trim().replace('\'', "\\'")))
373 .collect();
374 format!("{col} {} ({})", op.sql_op(), items.join(", "))
375 } else {
376 format!("{col} {} ({val_str})", op.sql_op())
377 }
378 }
379 CompareOp::Includes => {
380 let like_val = match value {
381 SqlValue::String(s) => format!("'%{}%'", s.replace('\'', "\\'")),
382 _ => val_str,
383 };
384 format!("{col} LIKE {like_val}")
385 }
386 _ => format!("{col} {} {val_str}", op.sql_op()),
387 }
388 }
389 FilterNode::And(children) => {
390 let parts: Vec<String> = children.iter()
391 .map(compile_filter_inline)
392 .filter(|s| !s.is_empty())
393 .collect();
394 match parts.len() {
395 0 => String::new(),
396 1 => parts.into_iter().next().unwrap(),
397 _ => format!("({})", parts.join(" AND ")),
398 }
399 }
400 FilterNode::Or(children) => {
401 let parts: Vec<String> = children.iter()
402 .map(compile_filter_inline)
403 .filter(|s| !s.is_empty())
404 .collect();
405 match parts.len() {
406 0 => String::new(),
407 1 => parts.into_iter().next().unwrap(),
408 _ => format!("({})", parts.join(" OR ")),
409 }
410 }
411 }
412}