perspective_client/config/
view_config.rs1use std::collections::HashMap;
14use std::fmt::Display;
15
16use serde::{Deserialize, Serialize};
17use ts_rs::TS;
18
19use super::aggregates::*;
20use super::expressions::*;
21use super::filters::*;
22use super::sort::*;
23use crate::proto;
24use crate::proto::columns_update;
25
26#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq, Eq, TS)]
27pub enum GroupRollupMode {
28 #[default]
29 #[serde(rename = "rollup")]
30 Rollup,
31
32 #[serde(rename = "flat")]
33 Flat,
34
35 #[serde(rename = "total")]
36 Total,
37}
38
39impl Display for GroupRollupMode {
40 fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
41 write!(fmt, "{}", match self {
42 Self::Rollup => "Rollup",
43 Self::Flat => "Flat",
44 Self::Total => "Total",
45 })
46 }
47}
48
49impl From<proto::GroupRollupMode> for GroupRollupMode {
50 fn from(value: proto::GroupRollupMode) -> Self {
51 match value {
52 proto::GroupRollupMode::Rollup => Self::Rollup,
53 proto::GroupRollupMode::Flat => Self::Flat,
54 proto::GroupRollupMode::Total => Self::Total,
55 }
56 }
57}
58
59impl From<GroupRollupMode> for proto::GroupRollupMode {
60 fn from(value: GroupRollupMode) -> Self {
61 match value {
62 GroupRollupMode::Rollup => proto::GroupRollupMode::Rollup,
63 GroupRollupMode::Flat => proto::GroupRollupMode::Flat,
64 GroupRollupMode::Total => proto::GroupRollupMode::Total,
65 }
66 }
67}
68
69#[derive(Clone, Debug, Deserialize, Default, PartialEq, Serialize, TS)]
70#[serde(deny_unknown_fields)]
71pub struct ViewConfig {
72 #[serde(default)]
73 pub group_by: Vec<String>,
74
75 #[serde(default)]
76 pub split_by: Vec<String>,
77
78 #[serde(default)]
79 pub sort: Vec<Sort>,
80
81 #[serde(default)]
82 pub filter: Vec<Filter>,
83
84 #[serde(default)]
86 pub group_rollup_mode: GroupRollupMode,
87
88 #[serde(skip_serializing_if = "is_default_value")]
89 #[serde(default)]
90 pub filter_op: FilterReducer,
91
92 #[serde(default)]
93 pub expressions: Expressions,
94
95 #[serde(default)]
96 pub columns: Vec<Option<String>>,
97
98 #[serde(default)]
99 pub aggregates: HashMap<String, Aggregate>,
100
101 #[serde(skip_serializing_if = "Option::is_none")]
102 #[serde(default)]
103 pub group_by_depth: Option<u32>,
104}
105
106fn is_default_value<A: Default + PartialEq>(value: &A) -> bool {
107 value == &A::default()
108}
109
110#[derive(Clone, Debug, Deserialize, Default, PartialEq, Serialize, TS)]
111#[serde(deny_unknown_fields)]
112pub struct ViewConfigUpdate {
113 #[serde(skip_serializing_if = "Option::is_none")]
126 #[serde(default)]
127 #[ts(optional)]
128 pub group_by: Option<Vec<String>>,
129
130 #[serde(skip_serializing_if = "Option::is_none")]
138 #[serde(default)]
139 #[ts(optional)]
140 pub split_by: Option<Vec<String>>,
141
142 #[serde(skip_serializing_if = "Option::is_none")]
148 #[serde(default)]
149 #[ts(optional)]
150 pub columns: Option<Vec<Option<String>>>,
151
152 #[serde(skip_serializing_if = "Option::is_none")]
163 #[serde(default)]
164 #[ts(optional)]
165 pub filter: Option<Vec<Filter>>,
166
167 #[serde(skip_serializing_if = "Option::is_none")]
176 #[serde(default)]
177 #[ts(optional)]
178 pub sort: Option<Vec<Sort>>,
179
180 #[serde(skip_serializing_if = "Option::is_none")]
186 #[serde(default)]
187 #[ts(optional)]
188 pub expressions: Option<Expressions>,
189
190 #[serde(skip_serializing_if = "Option::is_none")]
202 #[serde(default)]
203 #[ts(optional)]
204 pub aggregates: Option<HashMap<String, Aggregate>>,
205
206 #[serde(skip_serializing)]
207 #[serde(default)]
208 #[ts(optional)]
209 pub group_by_depth: Option<u32>,
210
211 #[serde(skip_serializing_if = "Option::is_none")]
212 #[serde(default)]
213 #[ts(optional)]
214 pub filter_op: Option<FilterReducer>,
215
216 #[serde(skip_serializing_if = "Option::is_none")]
217 #[serde(default)]
218 #[ts(optional)]
219 pub group_rollup_mode: Option<GroupRollupMode>,
220}
221
222impl From<ViewConfigUpdate> for proto::ViewConfig {
223 fn from(value: ViewConfigUpdate) -> Self {
224 proto::ViewConfig {
225 group_by: value.group_by.unwrap_or_default(),
226 split_by: value.split_by.unwrap_or_default(),
227 columns: value.columns.map(|x| proto::ColumnsUpdate {
228 opt_columns: Some(columns_update::OptColumns::Columns(
229 proto::columns_update::Columns {
230 columns: x.into_iter().flatten().collect(),
231 },
232 )),
233 }),
234 filter: value
235 .filter
236 .unwrap_or_default()
237 .into_iter()
238 .map(|x| x.into())
239 .collect(),
240 filter_op: value
241 .filter_op
242 .map(proto::view_config::FilterReducer::from)
243 .unwrap_or_default() as i32,
244 sort: value
245 .sort
246 .unwrap_or_default()
247 .into_iter()
248 .map(|x| x.into())
249 .collect(),
250 expressions: value.expressions.unwrap_or_default().0,
251 aggregates: value
252 .aggregates
253 .unwrap_or_default()
254 .into_iter()
255 .map(|(x, y)| (x, y.into()))
256 .collect(),
257 group_by_depth: value.group_by_depth,
258 group_rollup_mode: value
259 .group_rollup_mode
260 .map(|x| proto::GroupRollupMode::from(x).into()),
261 }
262 }
263}
264
265impl From<FilterReducer> for proto::view_config::FilterReducer {
266 fn from(value: FilterReducer) -> Self {
267 match value {
268 FilterReducer::And => proto::view_config::FilterReducer::And,
269 FilterReducer::Or => proto::view_config::FilterReducer::Or,
270 }
271 }
272}
273
274impl From<proto::view_config::FilterReducer> for FilterReducer {
275 fn from(value: proto::view_config::FilterReducer) -> Self {
276 match value {
277 proto::view_config::FilterReducer::And => FilterReducer::And,
278 proto::view_config::FilterReducer::Or => FilterReducer::Or,
279 }
280 }
281}
282
283impl From<ViewConfig> for ViewConfigUpdate {
284 fn from(value: ViewConfig) -> Self {
285 ViewConfigUpdate {
286 group_by: Some(value.group_by),
287 split_by: Some(value.split_by),
288 columns: Some(value.columns),
289 filter: Some(value.filter),
290 filter_op: Some(value.filter_op),
291 sort: Some(value.sort),
292 expressions: Some(value.expressions),
293 aggregates: Some(value.aggregates),
294 group_by_depth: value.group_by_depth,
295 group_rollup_mode: Some(value.group_rollup_mode),
296 }
297 }
298}
299
300impl From<proto::ViewConfig> for ViewConfig {
301 fn from(value: proto::ViewConfig) -> Self {
302 ViewConfig {
303 group_by: value.group_by,
304 split_by: value.split_by,
305 columns: match value.columns.unwrap_or_default().opt_columns {
306 Some(columns_update::OptColumns::Columns(x)) => {
307 x.columns.into_iter().map(Some).collect()
308 },
309 _ => {
310 vec![]
311 },
312 },
313 filter: value.filter.into_iter().map(|x| x.into()).collect(),
314 filter_op: proto::view_config::FilterReducer::try_from(value.filter_op)
315 .unwrap_or_default()
316 .into(),
317 sort: value.sort.into_iter().map(|x| x.into()).collect(),
318 expressions: Expressions(value.expressions),
319 aggregates: value
320 .aggregates
321 .into_iter()
322 .map(|(x, y)| (x, y.into()))
323 .collect(),
324 group_by_depth: value.group_by_depth,
325 group_rollup_mode: value
326 .group_rollup_mode
327 .map(proto::GroupRollupMode::try_from)
328 .and_then(|x| x.ok())
329 .map(|x| x.into())
330 .unwrap_or_default(),
331 }
332 }
333}
334
335impl From<ViewConfigUpdate> for ViewConfig {
336 fn from(value: ViewConfigUpdate) -> Self {
337 ViewConfig {
338 group_by: value.group_by.unwrap_or_default(),
339 split_by: value.split_by.unwrap_or_default(),
340 columns: value.columns.unwrap_or_default(),
341 filter: value.filter.unwrap_or_default(),
342 filter_op: value.filter_op.unwrap_or_default(),
343 sort: value.sort.unwrap_or_default(),
344 expressions: value.expressions.unwrap_or_default(),
345 aggregates: value.aggregates.unwrap_or_default(),
346 group_by_depth: value.group_by_depth,
347 group_rollup_mode: value.group_rollup_mode.unwrap_or_default(),
348 }
349 }
350}
351
352impl From<proto::ViewConfig> for ViewConfigUpdate {
353 fn from(value: proto::ViewConfig) -> Self {
354 ViewConfigUpdate {
355 group_by: Some(value.group_by),
356 split_by: Some(value.split_by),
357 columns: match value.columns.unwrap_or_default().opt_columns {
358 Some(columns_update::OptColumns::Columns(x)) => {
359 Some(x.columns.into_iter().map(Some).collect())
360 },
361 _ => None,
362 },
363 filter: Some(value.filter.into_iter().map(|x| x.into()).collect()),
364 filter_op: Some(
365 proto::view_config::FilterReducer::try_from(value.filter_op)
366 .unwrap_or_default()
367 .into(),
368 ),
369 sort: Some(value.sort.into_iter().map(|x| x.into()).collect()),
370 expressions: Some(Expressions(value.expressions)),
371 aggregates: Some(
372 value
373 .aggregates
374 .into_iter()
375 .map(|(x, y)| (x, y.into()))
376 .collect(),
377 ),
378 group_by_depth: value.group_by_depth,
379 group_rollup_mode: value
380 .group_rollup_mode
381 .and_then(|x| proto::GroupRollupMode::try_from(x).ok())
382 .map(|x| x.into()),
383 }
384 }
385}
386
387impl ViewConfig {
388 fn _apply<T>(field: &mut T, update: Option<T>) -> bool {
389 match update {
390 None => false,
391 Some(update) => {
392 *field = update;
393 true
394 },
395 }
396 }
397
398 pub fn reset(&mut self, reset_expressions: bool) {
399 let mut config = Self::default();
400 if !reset_expressions {
401 config.expressions = self.expressions.clone();
402 }
403 std::mem::swap(self, &mut config);
404 }
405
406 pub fn apply_update(&mut self, mut update: ViewConfigUpdate) -> bool {
409 let mut changed = false;
410 if ((self.group_rollup_mode == GroupRollupMode::Total
411 && update.group_rollup_mode.is_none())
412 || update.group_rollup_mode == Some(GroupRollupMode::Total))
413 && update
414 .group_by
415 .as_ref()
416 .map(|x| !x.is_empty())
417 .unwrap_or_default()
418 {
419 tracing::warn!("`total` incompatible with `group_by`");
420 changed = true;
421 update.group_rollup_mode = Some(GroupRollupMode::Rollup);
422 }
423
424 if update.group_rollup_mode == Some(GroupRollupMode::Total) && !self.group_by.is_empty() {
425 tracing::warn!("`group_by` incompatible with `total`");
426 changed = true;
427 update.group_by = Some(vec![]);
428 }
429
430 changed = Self::_apply(&mut self.group_by, update.group_by) || changed;
431 changed = Self::_apply(&mut self.split_by, update.split_by) || changed;
432 changed = Self::_apply(&mut self.columns, update.columns) || changed;
433 changed = Self::_apply(&mut self.filter, update.filter) || changed;
434 changed = Self::_apply(&mut self.sort, update.sort) || changed;
435 changed = Self::_apply(&mut self.aggregates, update.aggregates) || changed;
436 changed = Self::_apply(&mut self.expressions, update.expressions) || changed;
437 changed = Self::_apply(&mut self.group_rollup_mode, update.group_rollup_mode) || changed;
438 if self.group_rollup_mode == GroupRollupMode::Total && !self.group_by.is_empty() {
439 tracing::warn!("`total` incompatible with `group_by`");
440 changed = true;
441 self.group_by = vec![];
442 }
443
444 changed
445 }
446
447 pub fn is_aggregated(&self) -> bool {
448 !self.group_by.is_empty() || self.group_rollup_mode == GroupRollupMode::Total
449 }
450
451 pub fn is_column_expression_in_use(&self, name: &str) -> bool {
452 let name = name.to_owned();
453 self.group_by.contains(&name)
454 || self.split_by.contains(&name)
455 || self.sort.iter().any(|x| x.0 == name)
456 || self.filter.iter().any(|x| x.column() == name)
457 || self.columns.contains(&Some(name))
458 }
459
460 pub fn is_equivalent(&self, other: &Self) -> bool {
465 let _self = self.clone();
466 let _self = ViewConfig {
467 columns: _self.columns.into_iter().filter(|x| x.is_some()).collect(),
468 .._self
469 };
470
471 let _other = other.clone();
472 let _other = ViewConfig {
473 columns: _other.columns.into_iter().filter(|x| x.is_some()).collect(),
474 ..other.clone()
475 };
476
477 _self == _other
478 }
479}