1use async_graphql::dynamic::{ObjectAccessor, ValueAccessor};
2
3use crate::compiler::ir::{CompareOp, FilterNode, SqlValue};
4use crate::cube::definition::{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 let Ok(filter_obj) = filter_val.object() {
34 let leaf_conditions =
35 parse_leaf_filter(&filter_obj, &dim.column, &dim.dim_type)?;
36 conditions.extend(leaf_conditions);
37 }
38 }
39 }
40 DimensionNode::Group { graphql_name, children, .. } => {
41 if let Ok(group_val) = accessor.try_get(graphql_name) {
42 if let Ok(group_obj) = group_val.object() {
43 let child_filter = parse_where(&group_obj, children)?;
44 if !child_filter.is_empty() {
45 conditions.push(child_filter);
46 }
47 }
48 }
49 }
50 }
51 }
52
53 Ok(match conditions.len() {
54 0 => FilterNode::Empty,
55 1 => conditions.into_iter().next().unwrap(),
56 _ => FilterNode::And(conditions),
57 })
58}
59
60pub fn parse_leaf_filter_for_selector(
63 obj: &ObjectAccessor,
64 column: &str,
65 dim_type: &DimType,
66) -> Result<Vec<FilterNode>, async_graphql::Error> {
67 parse_leaf_filter(obj, column, dim_type)
68}
69
70fn parse_leaf_filter(
71 obj: &ObjectAccessor,
72 column: &str,
73 dim_type: &DimType,
74) -> Result<Vec<FilterNode>, async_graphql::Error> {
75 let mut conditions = Vec::new();
76
77 let ops: &[(&str, CompareOp)] = match dim_type {
78 DimType::Int | DimType::Float => &[
79 ("eq", CompareOp::Eq), ("ne", CompareOp::Ne),
80 ("gt", CompareOp::Gt), ("ge", CompareOp::Ge),
81 ("lt", CompareOp::Lt), ("le", CompareOp::Le),
82 ],
83 DimType::String => &[
84 ("is", CompareOp::Eq), ("not", CompareOp::Ne),
85 ("like", CompareOp::Like), ("includes", CompareOp::Includes),
86 ],
87 DimType::DateTime => &[
88 ("is", CompareOp::Eq), ("not", CompareOp::Ne),
89 ("after", CompareOp::Gt), ("since", CompareOp::Ge),
90 ("before", CompareOp::Lt), ("till", CompareOp::Le),
91 ],
92 DimType::Bool => &[("eq", CompareOp::Eq)],
93 };
94
95 for (key, op) in ops {
96 if let Ok(val) = obj.try_get(key) {
97 let sql_val = accessor_to_sql(&val, dim_type)?;
98 conditions.push(FilterNode::Condition {
99 column: column.to_string(),
100 op: op.clone(),
101 value: sql_val,
102 });
103 }
104 }
105
106 if let Ok(val) = obj.try_get("isNull") {
108 if let Ok(b) = val.boolean() {
109 conditions.push(FilterNode::Condition {
110 column: column.to_string(),
111 op: if b { CompareOp::IsNull } else { CompareOp::IsNotNull },
112 value: SqlValue::Bool(b),
113 });
114 }
115 }
116
117 for (key, op) in &[("in", CompareOp::In), ("notIn", CompareOp::NotIn)] {
118 if let Ok(val) = obj.try_get(key) {
119 if let Ok(list) = val.list() {
120 let mut values = Vec::new();
121 for item in list.iter() {
122 match dim_type {
123 DimType::String | DimType::DateTime => {
124 if let Ok(s) = item.string() {
125 values.push(s.to_string());
126 }
127 }
128 DimType::Int => {
129 if let Ok(n) = item.i64() {
130 values.push(n.to_string());
131 }
132 }
133 DimType::Float => {
134 if let Ok(f) = item.f64() {
135 values.push(f.to_string());
136 }
137 }
138 _ => {}
139 }
140 }
141 if !values.is_empty() {
142 conditions.push(FilterNode::Condition {
143 column: column.to_string(),
144 op: op.clone(),
145 value: SqlValue::String(values.join(",")),
146 });
147 }
148 }
149 }
150 }
151
152 Ok(conditions)
153}
154
155pub fn parse_filter_from_value(
158 val: &async_graphql::Value,
159 dimensions: &[DimensionNode],
160) -> Result<FilterNode, async_graphql::Error> {
161 let obj = match val {
162 async_graphql::Value::Object(map) => map,
163 _ => return Ok(FilterNode::Empty),
164 };
165
166 let mut conditions = Vec::new();
167
168 if let Some(async_graphql::Value::List(items)) = obj.get("any") {
169 let mut or_children = Vec::new();
170 for item in items {
171 let child = parse_filter_from_value(item, dimensions)?;
172 if !child.is_empty() {
173 or_children.push(child);
174 }
175 }
176 if !or_children.is_empty() {
177 conditions.push(FilterNode::Or(or_children));
178 }
179 }
180
181 for node in dimensions {
182 match node {
183 DimensionNode::Leaf(dim) => {
184 if let Some(async_graphql::Value::Object(filter_map)) = obj.get(dim.graphql_name.as_str()) {
185 let leaf = parse_leaf_filter_from_value(filter_map, &dim.column, &dim.dim_type)?;
186 conditions.extend(leaf);
187 }
188 }
189 DimensionNode::Group { graphql_name, children, .. } => {
190 if let Some(group_val) = obj.get(graphql_name.as_str()) {
191 let child_filter = parse_filter_from_value(group_val, children)?;
192 if !child_filter.is_empty() {
193 conditions.push(child_filter);
194 }
195 }
196 }
197 }
198 }
199
200 Ok(match conditions.len() {
201 0 => FilterNode::Empty,
202 1 => conditions.into_iter().next().unwrap(),
203 _ => FilterNode::And(conditions),
204 })
205}
206
207fn parse_leaf_filter_from_value(
208 obj: &indexmap::IndexMap<async_graphql::Name, async_graphql::Value>,
209 column: &str,
210 dim_type: &DimType,
211) -> Result<Vec<FilterNode>, async_graphql::Error> {
212 let mut conditions = Vec::new();
213
214 let ops: &[(&str, CompareOp)] = match dim_type {
215 DimType::Int | DimType::Float => &[
216 ("eq", CompareOp::Eq), ("ne", CompareOp::Ne),
217 ("gt", CompareOp::Gt), ("ge", CompareOp::Ge),
218 ("lt", CompareOp::Lt), ("le", CompareOp::Le),
219 ],
220 DimType::String => &[
221 ("is", CompareOp::Eq), ("not", CompareOp::Ne),
222 ("like", CompareOp::Like), ("includes", CompareOp::Includes),
223 ],
224 DimType::DateTime => &[
225 ("is", CompareOp::Eq), ("not", CompareOp::Ne),
226 ("after", CompareOp::Gt), ("since", CompareOp::Ge),
227 ("before", CompareOp::Lt), ("till", CompareOp::Le),
228 ],
229 DimType::Bool => &[("eq", CompareOp::Eq)],
230 };
231
232 for (key, op) in ops {
233 if let Some(val) = obj.get(*key) {
234 if let Some(sql_val) = value_to_sql(val, dim_type) {
235 conditions.push(FilterNode::Condition {
236 column: column.to_string(),
237 op: op.clone(),
238 value: sql_val,
239 });
240 }
241 }
242 }
243
244 if let Some(async_graphql::Value::Boolean(b)) = obj.get("isNull") {
245 conditions.push(FilterNode::Condition {
246 column: column.to_string(),
247 op: if *b { CompareOp::IsNull } else { CompareOp::IsNotNull },
248 value: SqlValue::Bool(*b),
249 });
250 }
251
252 for (key, op) in &[("in", CompareOp::In), ("notIn", CompareOp::NotIn)] {
253 if let Some(async_graphql::Value::List(list)) = obj.get(*key) {
254 let values: Vec<String> = list.iter().filter_map(|item| match (dim_type, item) {
255 (DimType::String | DimType::DateTime, async_graphql::Value::String(s)) => Some(s.clone()),
256 (DimType::Int, async_graphql::Value::Number(n)) => n.as_i64().map(|i| i.to_string()),
257 (DimType::Float, async_graphql::Value::Number(n)) => n.as_f64().map(|f| f.to_string()),
258 _ => None,
259 }).collect();
260 if !values.is_empty() {
261 conditions.push(FilterNode::Condition {
262 column: column.to_string(),
263 op: op.clone(),
264 value: SqlValue::String(values.join(",")),
265 });
266 }
267 }
268 }
269
270 Ok(conditions)
271}
272
273fn value_to_sql(val: &async_graphql::Value, dim_type: &DimType) -> Option<SqlValue> {
274 match (dim_type, val) {
275 (DimType::Int, async_graphql::Value::Number(n)) => n.as_i64().map(SqlValue::Int),
276 (DimType::Float, async_graphql::Value::Number(n)) => n.as_f64().map(SqlValue::Float),
277 (DimType::Bool, async_graphql::Value::Boolean(b)) => Some(SqlValue::Bool(*b)),
278 (DimType::String | DimType::DateTime, async_graphql::Value::String(s)) => {
279 Some(SqlValue::String(s.clone()))
280 }
281 (DimType::Int, async_graphql::Value::String(s)) => s.parse::<i64>().ok().map(SqlValue::Int),
282 (DimType::Float, async_graphql::Value::String(s)) => s.parse::<f64>().ok().map(SqlValue::Float),
283 _ => None,
284 }
285}
286
287fn accessor_to_sql(
288 val: &ValueAccessor,
289 dim_type: &DimType,
290) -> Result<SqlValue, async_graphql::Error> {
291 match dim_type {
292 DimType::Int => Ok(SqlValue::Int(val.i64()?)),
293 DimType::Float => Ok(SqlValue::Float(val.f64()?)),
294 DimType::Bool => Ok(SqlValue::Bool(val.boolean()?)),
295 DimType::String | DimType::DateTime => Ok(SqlValue::String(val.string()?.to_string())),
296 }
297}
298