1use async_graphql::dynamic::ObjectAccessor;
2
3use crate::compiler::filter;
4use crate::compiler::ir::*;
5use crate::cube::definition::{CubeDefinition, SelectorDef};
6
7pub struct MetricRequest {
9 pub function: String,
10 pub of_dimension: String,
11 pub select_where_value: Option<async_graphql::Value>,
13}
14
15pub fn parse_cube_query(
16 cube: &CubeDefinition,
17 network: &str,
18 args: &ObjectAccessor,
19 metrics: &[MetricRequest],
20) -> Result<QueryIR, async_graphql::Error> {
21 let table = cube.table_for_chain(network);
22
23 let filters = if let Ok(where_val) = args.try_get("where") {
24 if let Ok(where_obj) = where_val.object() {
25 filter::parse_where(&where_obj, &cube.dimensions)?
26 } else {
27 FilterNode::Empty
28 }
29 } else {
30 FilterNode::Empty
31 };
32
33 let filters = merge_selector_filters(filters, args, &cube.selectors)?;
34 let filters = if let Some(ref chain_col) = cube.chain_column {
37 let chain_filter = FilterNode::Condition {
38 column: chain_col.clone(),
39 op: CompareOp::Eq,
40 value: SqlValue::String(network.to_string()),
41 };
42 if filters.is_empty() {
43 chain_filter
44 } else {
45 FilterNode::And(vec![chain_filter, filters])
46 }
47 } else {
48 filters
49 };
50 let filters = apply_default_filters(filters, &cube.default_filters);
51 let (limit, offset) = parse_limit(args, cube.default_limit, cube.max_limit)?;
52 let order_by = parse_order_by(args, cube)?;
53
54 let flat = cube.flat_dimensions();
55 let mut selects: Vec<SelectExpr> = flat
56 .iter()
57 .map(|(_path, dim)| SelectExpr::Column {
58 column: dim.column.clone(),
59 alias: None,
60 })
61 .collect();
62
63 let mut group_by = Vec::new();
64 let mut having = FilterNode::Empty;
65
66 if !metrics.is_empty() {
67 group_by = flat.iter().map(|(_, dim)| dim.column.clone()).collect();
68
69 for m in metrics {
70 let dim_col = flat
71 .iter()
72 .find(|(path, _)| path == &m.of_dimension)
73 .map(|(_, dim)| dim.column.clone())
74 .unwrap_or_else(|| "*".to_string());
75
76 let func = m.function.to_uppercase();
77 let alias = format!("__{}", m.function);
78
79 selects.push(SelectExpr::Aggregate {
80 function: func.clone(),
81 column: dim_col.clone(),
82 alias,
83 });
84
85 if let Some(async_graphql::Value::Object(ref obj)) = m.select_where_value {
86 let agg_expr = if func == "COUNT" && dim_col == "*" {
87 "COUNT(*)".to_string()
88 } else if func == "UNIQ" {
89 format!("COUNT(DISTINCT `{dim_col}`)")
90 } else {
91 format!("{func}(`{dim_col}`)")
92 };
93
94 let h = parse_select_where_from_value(obj, &agg_expr)?;
95 if !h.is_empty() {
96 having = if having.is_empty() {
97 h
98 } else {
99 FilterNode::And(vec![having, h])
100 };
101 }
102 }
103 }
104 }
105
106 Ok(QueryIR {
107 cube: cube.name.clone(),
108 schema: cube.schema.clone(),
109 table,
110 selects,
111 filters,
112 having,
113 group_by,
114 order_by,
115 limit,
116 offset,
117 })
118}
119
120fn parse_select_where_from_value(
123 obj: &indexmap::IndexMap<async_graphql::Name, async_graphql::Value>,
124 aggregate_expr: &str,
125) -> Result<FilterNode, async_graphql::Error> {
126 let mut conditions = Vec::new();
127
128 for (key, op) in &[
129 ("eq", CompareOp::Eq),
130 ("gt", CompareOp::Gt),
131 ("ge", CompareOp::Ge),
132 ("lt", CompareOp::Lt),
133 ("le", CompareOp::Le),
134 ] {
135 if let Some(val) = obj.get(*key) {
136 let sql_val = match val {
137 async_graphql::Value::String(s) => {
138 if let Ok(f) = s.parse::<f64>() {
139 SqlValue::Float(f)
140 } else {
141 SqlValue::String(s.clone())
142 }
143 }
144 async_graphql::Value::Number(n) => {
145 if let Some(f) = n.as_f64() {
146 SqlValue::Float(f)
147 } else {
148 SqlValue::Int(n.as_i64().unwrap_or(0))
149 }
150 }
151 _ => continue,
152 };
153 conditions.push(FilterNode::Condition {
154 column: aggregate_expr.to_string(),
155 op: op.clone(),
156 value: sql_val,
157 });
158 }
159 }
160
161 Ok(match conditions.len() {
162 0 => FilterNode::Empty,
163 1 => conditions.into_iter().next().unwrap(),
164 _ => FilterNode::And(conditions),
165 })
166}
167
168fn merge_selector_filters(
169 base: FilterNode,
170 args: &ObjectAccessor,
171 selectors: &[SelectorDef],
172) -> Result<FilterNode, async_graphql::Error> {
173 let mut extra = Vec::new();
174
175 for sel in selectors {
176 if let Ok(val) = args.try_get(&sel.graphql_name) {
177 if let Ok(obj) = val.object() {
178 let leaf_filters =
179 filter::parse_leaf_filter_for_selector(&obj, &sel.column, &sel.dim_type)?;
180 extra.extend(leaf_filters);
181 }
182 }
183 }
184
185 if extra.is_empty() {
186 return Ok(base);
187 }
188 if base.is_empty() {
189 return Ok(if extra.len() == 1 {
190 extra.remove(0)
191 } else {
192 FilterNode::And(extra)
193 });
194 }
195 extra.push(base);
196 Ok(FilterNode::And(extra))
197}
198
199fn apply_default_filters(user_filters: FilterNode, defaults: &[(String, String)]) -> FilterNode {
200 if defaults.is_empty() {
201 return user_filters;
202 }
203
204 let mut default_nodes: Vec<FilterNode> = defaults
205 .iter()
206 .map(|(col, val)| {
207 let sql_val = if val == "true" || val == "false" {
208 SqlValue::Bool(val == "true")
209 } else if let Ok(n) = val.parse::<i64>() {
210 SqlValue::Int(n)
211 } else {
212 SqlValue::String(val.clone())
213 };
214 FilterNode::Condition {
215 column: col.clone(),
216 op: CompareOp::Eq,
217 value: sql_val,
218 }
219 })
220 .collect();
221
222 if user_filters.is_empty() {
223 if default_nodes.len() == 1 {
224 return default_nodes.remove(0);
225 }
226 return FilterNode::And(default_nodes);
227 }
228
229 default_nodes.push(user_filters);
230 FilterNode::And(default_nodes)
231}
232
233fn parse_limit(
234 args: &ObjectAccessor,
235 default: u32,
236 max: u32,
237) -> Result<(u32, u32), async_graphql::Error> {
238 let mut limit = default;
239 let mut offset = 0u32;
240
241 if let Ok(limit_val) = args.try_get("limit") {
242 if let Ok(limit_obj) = limit_val.object() {
243 if let Ok(count) = limit_obj.try_get("count") {
244 limit = (count.i64()? as u32).min(max);
245 }
246 if let Ok(off) = limit_obj.try_get("offset") {
247 offset = off.i64()? as u32;
248 }
249 }
250 }
251
252 Ok((limit, offset))
253}
254
255fn parse_order_by(
256 args: &ObjectAccessor,
257 cube: &CubeDefinition,
258) -> Result<Vec<OrderExpr>, async_graphql::Error> {
259 let order_val = match args.try_get("orderBy") {
260 Ok(v) => v,
261 Err(_) => return Ok(Vec::new()),
262 };
263
264 let enum_str = order_val
265 .enum_name()
266 .map_err(|_| async_graphql::Error::new("orderBy must be an enum value"))?;
267
268 let flat = cube.flat_dimensions();
269 let (descending, field_path) = if let Some(path) = enum_str.strip_suffix("_DESC") {
270 (true, path)
271 } else if let Some(path) = enum_str.strip_suffix("_ASC") {
272 (false, path)
273 } else {
274 return Err(async_graphql::Error::new(format!(
275 "Invalid orderBy value: {enum_str}"
276 )));
277 };
278
279 let column = flat
280 .iter()
281 .find(|(p, _)| p == field_path)
282 .map(|(_, dim)| dim.column.clone())
283 .ok_or_else(|| {
284 async_graphql::Error::new(format!("Unknown orderBy field: {field_path}"))
285 })?;
286
287 Ok(vec![OrderExpr { column, descending }])
288}