1use async_graphql::dynamic::{ObjectAccessor, ValueAccessor};
2
3use crate::compiler::ir::{CompareOp, FilterNode, SqlValue};
4use crate::cube::definition::{ArrayFieldDef, ArrayFieldType, DimType, DimensionNode};
5
6pub fn parse_where(
7 accessor: &ObjectAccessor,
8 dimensions: &[DimensionNode],
9) -> Result<FilterNode, async_graphql::Error> {
10 let mut conditions = Vec::new();
11
12 if let Ok(any_val) = accessor.try_get("any") {
13 if let Ok(list) = any_val.list() {
14 let mut or_children = Vec::new();
15 for item in list.iter() {
16 if let Ok(obj) = item.object() {
17 let child = parse_where(&obj, dimensions)?;
18 if !child.is_empty() {
19 or_children.push(child);
20 }
21 }
22 }
23 if !or_children.is_empty() {
24 conditions.push(FilterNode::Or(or_children));
25 }
26 }
27 }
28
29 for node in dimensions {
30 match node {
31 DimensionNode::Leaf(dim) => {
32 if let Ok(filter_val) = accessor.try_get(&dim.graphql_name) {
33 if matches!(dim.dim_type, DimType::Bool) {
34 if let Ok(b) = filter_val.boolean() {
35 conditions.push(FilterNode::Condition {
36 column: dim.column.clone(),
37 op: CompareOp::Eq,
38 value: SqlValue::Bool(b),
39 });
40 } else if let Ok(filter_obj) = filter_val.object() {
41 let leaf_conditions =
42 parse_leaf_filter(&filter_obj, &dim.column, &dim.dim_type)?;
43 conditions.extend(leaf_conditions);
44 }
45 } else if let Ok(filter_obj) = filter_val.object() {
46 let leaf_conditions =
47 parse_leaf_filter(&filter_obj, &dim.column, &dim.dim_type)?;
48 conditions.extend(leaf_conditions);
49 }
50 }
51 }
52 DimensionNode::Group { graphql_name, children, .. } => {
53 if let Ok(group_val) = accessor.try_get(graphql_name) {
54 if let Ok(group_obj) = group_val.object() {
55 let child_filter = parse_where(&group_obj, children)?;
56 if !child_filter.is_empty() {
57 conditions.push(child_filter);
58 }
59 }
60 }
61 }
62 DimensionNode::Array { graphql_name, children, .. } => {
63 if let Ok(arr_val) = accessor.try_get(graphql_name) {
64 if let Ok(arr_obj) = arr_val.object() {
65 let arr_filter = parse_array_includes_filter(&arr_obj, children)?;
66 if !arr_filter.is_empty() {
67 conditions.push(arr_filter);
68 }
69 }
70 }
71 }
72 }
73 }
74
75 Ok(match conditions.len() {
76 0 => FilterNode::Empty,
77 1 => conditions.into_iter().next().unwrap(),
78 _ => FilterNode::And(conditions),
79 })
80}
81
82pub fn parse_leaf_filter_for_selector(
85 obj: &ObjectAccessor,
86 column: &str,
87 dim_type: &DimType,
88) -> Result<Vec<FilterNode>, async_graphql::Error> {
89 parse_leaf_filter(obj, column, dim_type)
90}
91
92fn parse_leaf_filter(
93 obj: &ObjectAccessor,
94 column: &str,
95 dim_type: &DimType,
96) -> Result<Vec<FilterNode>, async_graphql::Error> {
97 let mut conditions = Vec::new();
98
99 let ops: &[(&str, CompareOp)] = match dim_type {
100 DimType::Int | DimType::Float => &[
101 ("eq", CompareOp::Eq), ("ne", CompareOp::Ne),
102 ("gt", CompareOp::Gt), ("ge", CompareOp::Ge),
103 ("lt", CompareOp::Lt), ("le", CompareOp::Le),
104 ],
105 DimType::Decimal => &[
106 ("eq", CompareOp::Eq), ("ne", CompareOp::Ne),
107 ("gt", CompareOp::Gt), ("ge", CompareOp::Ge),
108 ("lt", CompareOp::Lt), ("le", CompareOp::Le),
109 ],
110 DimType::String => &[
111 ("is", CompareOp::Eq), ("not", CompareOp::Ne),
112 ("like", CompareOp::Like), ("includes", CompareOp::Includes),
113 ],
114 DimType::DateTime => &[
115 ("is", CompareOp::Eq), ("not", CompareOp::Ne),
116 ("after", CompareOp::Gt), ("since", CompareOp::Ge),
117 ("before", CompareOp::Lt), ("till", CompareOp::Le),
118 ],
119 DimType::Bool => &[("eq", CompareOp::Eq)],
120 };
121
122 for (key, op) in ops {
123 if let Ok(val) = obj.try_get(key) {
124 let sql_val = accessor_to_sql(&val, dim_type)?;
125 conditions.push(FilterNode::Condition {
126 column: column.to_string(),
127 op: op.clone(),
128 value: sql_val,
129 });
130 }
131 }
132
133 for (key, op) in &[
134 ("since_relative", CompareOp::Ge),
135 ("after_relative", CompareOp::Gt),
136 ("till_relative", CompareOp::Le),
137 ] {
138 if let Ok(val) = obj.try_get(key) {
139 if let Ok(rel_obj) = val.object() {
140 if let Some(expr) = parse_relative_time_accessor(&rel_obj) {
141 conditions.push(FilterNode::Condition {
142 column: column.to_string(),
143 op: op.clone(),
144 value: SqlValue::Expression(expr),
145 });
146 }
147 }
148 }
149 }
150
151 if let Ok(val) = obj.try_get("isNull") {
152 if let Ok(b) = val.boolean() {
153 conditions.push(FilterNode::Condition {
154 column: column.to_string(),
155 op: if b { CompareOp::IsNull } else { CompareOp::IsNotNull },
156 value: SqlValue::Bool(b),
157 });
158 }
159 }
160
161 for (key, op) in &[("in", CompareOp::In), ("notIn", CompareOp::NotIn)] {
162 if let Ok(val) = obj.try_get(key) {
163 if let Ok(list) = val.list() {
164 let mut values = Vec::new();
165 for item in list.iter() {
166 match dim_type {
167 DimType::String | DimType::DateTime | DimType::Decimal => {
168 if let Ok(s) = item.string() {
169 values.push(s.to_string());
170 }
171 }
172 DimType::Int => {
173 if let Ok(s) = item.string() {
174 if let Ok(i) = s.parse::<i64>() {
175 values.push(i.to_string());
176 }
177 }
178 }
179 DimType::Float => {
180 if let Ok(s) = item.string() {
181 if let Ok(f) = s.parse::<f64>() {
182 values.push(f.to_string());
183 }
184 }
185 }
186 _ => {}
187 }
188 }
189 if !values.is_empty() {
190 conditions.push(FilterNode::Condition {
191 column: column.to_string(),
192 op: op.clone(),
193 value: SqlValue::String(values.join(",")),
194 });
195 }
196 }
197 }
198 }
199
200 Ok(conditions)
201}
202
203pub fn parse_filter_from_value(
206 val: &async_graphql::Value,
207 dimensions: &[DimensionNode],
208) -> Result<FilterNode, async_graphql::Error> {
209 let obj = match val {
210 async_graphql::Value::Object(map) => map,
211 _ => return Ok(FilterNode::Empty),
212 };
213
214 let mut conditions = Vec::new();
215
216 if let Some(async_graphql::Value::List(items)) = obj.get("any") {
217 let mut or_children = Vec::new();
218 for item in items {
219 let child = parse_filter_from_value(item, dimensions)?;
220 if !child.is_empty() {
221 or_children.push(child);
222 }
223 }
224 if !or_children.is_empty() {
225 conditions.push(FilterNode::Or(or_children));
226 }
227 }
228
229 for node in dimensions {
230 match node {
231 DimensionNode::Leaf(dim) => {
232 if let Some(val) = obj.get(dim.graphql_name.as_str()) {
233 if matches!(dim.dim_type, DimType::Bool) {
234 if let async_graphql::Value::Boolean(b) = val {
235 conditions.push(FilterNode::Condition {
236 column: dim.column.clone(),
237 op: CompareOp::Eq,
238 value: SqlValue::Bool(*b),
239 });
240 } else if let async_graphql::Value::Object(filter_map) = val {
241 let leaf = parse_leaf_filter_from_value(filter_map, &dim.column, &dim.dim_type)?;
242 conditions.extend(leaf);
243 }
244 } else if let async_graphql::Value::Object(filter_map) = val {
245 let leaf = parse_leaf_filter_from_value(filter_map, &dim.column, &dim.dim_type)?;
246 conditions.extend(leaf);
247 }
248 }
249 }
250 DimensionNode::Group { graphql_name, children, .. } => {
251 if let Some(group_val) = obj.get(graphql_name.as_str()) {
252 let child_filter = parse_filter_from_value(group_val, children)?;
253 if !child_filter.is_empty() {
254 conditions.push(child_filter);
255 }
256 }
257 }
258 DimensionNode::Array { graphql_name, children, .. } => {
259 if let Some(async_graphql::Value::Object(arr_map)) = obj.get(graphql_name.as_str()) {
260 let arr_filter = parse_array_includes_from_value(arr_map, children)?;
261 if !arr_filter.is_empty() {
262 conditions.push(arr_filter);
263 }
264 }
265 }
266 }
267 }
268
269 Ok(match conditions.len() {
270 0 => FilterNode::Empty,
271 1 => conditions.into_iter().next().unwrap(),
272 _ => FilterNode::And(conditions),
273 })
274}
275
276fn parse_leaf_filter_from_value(
277 obj: &indexmap::IndexMap<async_graphql::Name, async_graphql::Value>,
278 column: &str,
279 dim_type: &DimType,
280) -> Result<Vec<FilterNode>, async_graphql::Error> {
281 let mut conditions = Vec::new();
282
283 let ops: &[(&str, CompareOp)] = match dim_type {
284 DimType::Int | DimType::Float => &[
285 ("eq", CompareOp::Eq), ("ne", CompareOp::Ne),
286 ("gt", CompareOp::Gt), ("ge", CompareOp::Ge),
287 ("lt", CompareOp::Lt), ("le", CompareOp::Le),
288 ],
289 DimType::Decimal => &[
290 ("eq", CompareOp::Eq), ("ne", CompareOp::Ne),
291 ("gt", CompareOp::Gt), ("ge", CompareOp::Ge),
292 ("lt", CompareOp::Lt), ("le", CompareOp::Le),
293 ],
294 DimType::String => &[
295 ("is", CompareOp::Eq), ("not", CompareOp::Ne),
296 ("like", CompareOp::Like), ("includes", CompareOp::Includes),
297 ],
298 DimType::DateTime => &[
299 ("is", CompareOp::Eq), ("not", CompareOp::Ne),
300 ("after", CompareOp::Gt), ("since", CompareOp::Ge),
301 ("before", CompareOp::Lt), ("till", CompareOp::Le),
302 ],
303 DimType::Bool => &[("eq", CompareOp::Eq)],
304 };
305
306 for (key, op) in ops {
307 if let Some(val) = obj.get(*key) {
308 if let Some(sql_val) = value_to_sql(val, dim_type) {
309 conditions.push(FilterNode::Condition {
310 column: column.to_string(),
311 op: op.clone(),
312 value: sql_val,
313 });
314 }
315 }
316 }
317
318 for (key, op) in &[
319 ("since_relative", CompareOp::Ge),
320 ("after_relative", CompareOp::Gt),
321 ("till_relative", CompareOp::Le),
322 ] {
323 if let Some(async_graphql::Value::Object(rel_map)) = obj.get(*key) {
324 if let Some(expr) = parse_relative_time_value(rel_map) {
325 conditions.push(FilterNode::Condition {
326 column: column.to_string(),
327 op: op.clone(),
328 value: SqlValue::Expression(expr),
329 });
330 }
331 }
332 }
333
334 if let Some(async_graphql::Value::Boolean(b)) = obj.get("isNull") {
335 conditions.push(FilterNode::Condition {
336 column: column.to_string(),
337 op: if *b { CompareOp::IsNull } else { CompareOp::IsNotNull },
338 value: SqlValue::Bool(*b),
339 });
340 }
341
342 for (key, op) in &[("in", CompareOp::In), ("notIn", CompareOp::NotIn)] {
343 if let Some(async_graphql::Value::List(list)) = obj.get(*key) {
344 let values: Vec<String> = list.iter().filter_map(|item| match (dim_type, item) {
345 (DimType::String | DimType::DateTime | DimType::Decimal, async_graphql::Value::String(s)) => Some(s.clone()),
346 (DimType::Decimal, async_graphql::Value::Number(n)) => n.as_f64().map(|f| f.to_string()),
347 (DimType::Int, async_graphql::Value::String(s)) => s.parse::<i64>().ok().map(|i| i.to_string()),
348 (DimType::Int, async_graphql::Value::Number(n)) => n.as_i64().map(|i| i.to_string()),
349 (DimType::Float, async_graphql::Value::String(s)) => s.parse::<f64>().ok().map(|f| f.to_string()),
350 (DimType::Float, async_graphql::Value::Number(n)) => n.as_f64().map(|f| f.to_string()),
351 _ => None,
352 }).collect();
353 if !values.is_empty() {
354 conditions.push(FilterNode::Condition {
355 column: column.to_string(),
356 op: op.clone(),
357 value: SqlValue::String(values.join(",")),
358 });
359 }
360 }
361 }
362
363 Ok(conditions)
364}
365
366fn normalize_datetime(s: &str) -> String {
367 let s = s.trim_end_matches('Z');
368 s.replacen('T', " ", 1)
369}
370
371fn value_to_sql(val: &async_graphql::Value, dim_type: &DimType) -> Option<SqlValue> {
372 match (dim_type, val) {
373 (DimType::Int, async_graphql::Value::Number(n)) => n.as_i64().map(SqlValue::Int),
374 (DimType::Int, async_graphql::Value::String(s)) => s.parse::<i64>().ok().map(SqlValue::Int),
375 (DimType::Float, async_graphql::Value::String(s)) => s.parse::<f64>().ok().map(SqlValue::Float),
376 (DimType::Float, async_graphql::Value::Number(n)) => n.as_f64().map(SqlValue::Float),
377 (DimType::Decimal, async_graphql::Value::String(s)) => Some(SqlValue::String(s.clone())),
378 (DimType::Decimal, async_graphql::Value::Number(n)) => n.as_f64().map(|f| SqlValue::String(f.to_string())),
379 (DimType::Bool, async_graphql::Value::Boolean(b)) => Some(SqlValue::Bool(*b)),
380 (DimType::DateTime, async_graphql::Value::String(s)) => {
381 Some(SqlValue::String(normalize_datetime(s)))
382 }
383 (DimType::String, async_graphql::Value::String(s)) => {
384 Some(SqlValue::String(s.clone()))
385 }
386 _ => None,
387 }
388}
389
390fn accessor_to_sql(
391 val: &ValueAccessor,
392 dim_type: &DimType,
393) -> Result<SqlValue, async_graphql::Error> {
394 match dim_type {
395 DimType::Int => {
396 let s = val.string()?;
397 let i = s.parse::<i64>().map_err(|_| {
398 async_graphql::Error::new(format!("Invalid integer value: {s}"))
399 })?;
400 Ok(SqlValue::Int(i))
401 }
402 DimType::Float => {
403 let s = val.string()?;
404 let f = s.parse::<f64>().map_err(|_| {
405 async_graphql::Error::new(format!("Invalid float value: {s}"))
406 })?;
407 Ok(SqlValue::Float(f))
408 }
409 DimType::Decimal => Ok(SqlValue::String(val.string()?.to_string())),
410 DimType::Bool => Ok(SqlValue::Bool(val.boolean()?)),
411 DimType::DateTime => Ok(SqlValue::String(normalize_datetime(val.string()?))),
412 DimType::String => Ok(SqlValue::String(val.string()?.to_string())),
413 }
414}
415
416fn array_columns_from_children(children: &[ArrayFieldDef]) -> Vec<String> {
421 children.iter().map(|f| f.column.clone()).collect()
422}
423
424fn dim_type_for_array_field(field: &ArrayFieldDef) -> DimType {
425 match &field.field_type {
426 ArrayFieldType::Scalar(dt) => dt.clone(),
427 ArrayFieldType::Union(_) => DimType::String,
428 }
429}
430
431fn parse_single_includes_object(
432 obj: &ObjectAccessor,
433 children: &[ArrayFieldDef],
434) -> Result<Vec<FilterNode>, async_graphql::Error> {
435 let mut conds = Vec::new();
436 for field in children {
437 if let Ok(filter_val) = obj.try_get(&field.graphql_name) {
438 if let Ok(filter_obj) = filter_val.object() {
439 let dt = dim_type_for_array_field(field);
440 let leaf = parse_leaf_filter(&filter_obj, &field.column, &dt)?;
441 conds.extend(leaf);
442 }
443 }
444 }
445 Ok(conds)
446}
447
448fn parse_array_includes_filter(
450 obj: &ObjectAccessor,
451 children: &[ArrayFieldDef],
452) -> Result<FilterNode, async_graphql::Error> {
453 if let Ok(includes_val) = obj.try_get("includes") {
454 let array_columns = array_columns_from_children(children);
455 if let Ok(list) = includes_val.list() {
456 let mut all_conditions = Vec::new();
457 for item in list.iter() {
458 if let Ok(item_obj) = item.object() {
459 let conds = parse_single_includes_object(&item_obj, children)?;
460 if !conds.is_empty() {
461 all_conditions.push(conds);
462 }
463 }
464 }
465 if all_conditions.is_empty() {
466 return Ok(FilterNode::Empty);
467 }
468 return Ok(FilterNode::ArrayIncludes { array_columns, element_conditions: all_conditions });
469 }
470 if let Ok(single_obj) = includes_val.object() {
471 let conds = parse_single_includes_object(&single_obj, children)?;
472 if conds.is_empty() {
473 return Ok(FilterNode::Empty);
474 }
475 return Ok(FilterNode::ArrayIncludes { array_columns, element_conditions: vec![conds] });
476 }
477 }
478 Ok(FilterNode::Empty)
479}
480
481fn parse_single_includes_from_value(
482 map: &indexmap::IndexMap<async_graphql::Name, async_graphql::Value>,
483 children: &[ArrayFieldDef],
484) -> Result<Vec<FilterNode>, async_graphql::Error> {
485 let mut conds = Vec::new();
486 for field in children {
487 if let Some(async_graphql::Value::Object(filter_map)) = map.get(field.graphql_name.as_str()) {
488 let dt = dim_type_for_array_field(field);
489 let leaf = parse_leaf_filter_from_value(filter_map, &field.column, &dt)?;
490 conds.extend(leaf);
491 }
492 }
493 Ok(conds)
494}
495
496fn parse_array_includes_from_value(
498 obj: &indexmap::IndexMap<async_graphql::Name, async_graphql::Value>,
499 children: &[ArrayFieldDef],
500) -> Result<FilterNode, async_graphql::Error> {
501 let array_columns = array_columns_from_children(children);
502
503 if let Some(async_graphql::Value::List(items)) = obj.get("includes") {
504 let mut all_conditions = Vec::new();
505 for item in items {
506 if let async_graphql::Value::Object(item_map) = item {
507 let conds = parse_single_includes_from_value(item_map, children)?;
508 if !conds.is_empty() {
509 all_conditions.push(conds);
510 }
511 }
512 }
513 if all_conditions.is_empty() {
514 return Ok(FilterNode::Empty);
515 }
516 return Ok(FilterNode::ArrayIncludes { array_columns, element_conditions: all_conditions });
517 }
518
519 if let Some(async_graphql::Value::Object(single_map)) = obj.get("includes") {
520 let conds = parse_single_includes_from_value(single_map, children)?;
521 if conds.is_empty() {
522 return Ok(FilterNode::Empty);
523 }
524 return Ok(FilterNode::ArrayIncludes { array_columns, element_conditions: vec![conds] });
525 }
526
527 Ok(FilterNode::Empty)
528}
529
530fn parse_relative_time_accessor(obj: &ObjectAccessor) -> Option<String> {
531 if let Ok(v) = obj.try_get("minutes_ago") {
532 if let Ok(n) = v.i64() { return Some(format!("now() - INTERVAL {n} MINUTE")); }
533 }
534 if let Ok(v) = obj.try_get("hours_ago") {
535 if let Ok(n) = v.i64() { return Some(format!("now() - INTERVAL {n} HOUR")); }
536 }
537 if let Ok(v) = obj.try_get("days_ago") {
538 if let Ok(n) = v.i64() { return Some(format!("now() - INTERVAL {n} DAY")); }
539 }
540 None
541}
542
543fn parse_relative_time_value(
544 obj: &indexmap::IndexMap<async_graphql::Name, async_graphql::Value>,
545) -> Option<String> {
546 if let Some(async_graphql::Value::Number(n)) = obj.get("minutes_ago") {
547 if let Some(v) = n.as_i64() { return Some(format!("now() - INTERVAL {v} MINUTE")); }
548 }
549 if let Some(async_graphql::Value::Number(n)) = obj.get("hours_ago") {
550 if let Some(v) = n.as_i64() { return Some(format!("now() - INTERVAL {v} HOUR")); }
551 }
552 if let Some(async_graphql::Value::Number(n)) = obj.get("days_ago") {
553 if let Some(v) = n.as_i64() { return Some(format!("now() - INTERVAL {v} DAY")); }
554 }
555 None
556}
557