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 super::windows::*;
24use crate::proto;
25use crate::proto::columns_update;
26
27#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq, Eq, TS)]
28pub enum GroupRollupMode {
29 #[default]
30 #[serde(rename = "rollup")]
31 Rollup,
32
33 #[serde(rename = "flat")]
34 Flat,
35
36 #[serde(rename = "total")]
37 Total,
38}
39
40impl Display for GroupRollupMode {
41 fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
42 write!(fmt, "{}", match self {
43 Self::Rollup => "Rollup",
44 Self::Flat => "Flat",
45 Self::Total => "Total",
46 })
47 }
48}
49
50impl From<proto::GroupRollupMode> for GroupRollupMode {
51 fn from(value: proto::GroupRollupMode) -> Self {
52 match value {
53 proto::GroupRollupMode::Rollup => Self::Rollup,
54 proto::GroupRollupMode::Flat => Self::Flat,
55 proto::GroupRollupMode::Total => Self::Total,
56 }
57 }
58}
59
60impl From<GroupRollupMode> for proto::GroupRollupMode {
61 fn from(value: GroupRollupMode) -> Self {
62 match value {
63 GroupRollupMode::Rollup => proto::GroupRollupMode::Rollup,
64 GroupRollupMode::Flat => proto::GroupRollupMode::Flat,
65 GroupRollupMode::Total => proto::GroupRollupMode::Total,
66 }
67 }
68}
69
70#[derive(Clone, Debug, Deserialize, Default, PartialEq, Serialize, TS)]
71#[serde(deny_unknown_fields)]
72pub struct ViewConfig {
73 #[serde(default)]
74 pub group_by: Vec<String>,
75
76 #[serde(default)]
77 pub split_by: Vec<String>,
78
79 #[serde(default)]
80 pub sort: Vec<Sort>,
81
82 #[serde(default)]
83 pub filter: Vec<Filter>,
84
85 #[serde(default)]
87 pub group_rollup_mode: GroupRollupMode,
88
89 #[serde(skip_serializing_if = "is_default_value")]
90 #[serde(default)]
91 pub filter_op: FilterReducer,
92
93 #[serde(default)]
94 pub expressions: Expressions,
95
96 #[serde(default)]
97 #[serde(skip_serializing_if = "is_default_value")]
98 pub windows: Windows,
99
100 #[serde(default)]
101 pub columns: Vec<Option<String>>,
102
103 #[serde(default)]
104 pub aggregates: HashMap<String, Aggregate>,
105
106 #[serde(skip_serializing_if = "Option::is_none")]
107 #[serde(default)]
108 pub group_by_depth: Option<u32>,
109}
110
111fn is_default_value<A: Default + PartialEq>(value: &A) -> bool {
112 value == &A::default()
113}
114
115#[derive(Clone, Debug, Deserialize, Default, PartialEq, Serialize, TS)]
116#[serde(deny_unknown_fields)]
117pub struct ViewConfigUpdate {
118 #[serde(skip_serializing_if = "Option::is_none")]
131 #[serde(default)]
132 #[ts(optional)]
133 pub group_by: Option<Vec<String>>,
134
135 #[serde(skip_serializing_if = "Option::is_none")]
143 #[serde(default)]
144 #[ts(optional)]
145 pub split_by: Option<Vec<String>>,
146
147 #[serde(skip_serializing_if = "Option::is_none")]
153 #[serde(default)]
154 #[ts(optional)]
155 pub columns: Option<Vec<Option<String>>>,
156
157 #[serde(skip_serializing_if = "Option::is_none")]
168 #[serde(default)]
169 #[ts(optional)]
170 pub filter: Option<Vec<Filter>>,
171
172 #[serde(skip_serializing_if = "Option::is_none")]
181 #[serde(default)]
182 #[ts(optional)]
183 pub sort: Option<Vec<Sort>>,
184
185 #[serde(skip_serializing_if = "Option::is_none")]
191 #[serde(default)]
192 #[ts(optional)]
193 pub expressions: Option<Expressions>,
194
195 #[serde(skip_serializing_if = "Option::is_none")]
201 #[serde(default)]
202 #[ts(optional)]
203 pub windows: Option<Windows>,
204
205 #[serde(skip_serializing_if = "Option::is_none")]
217 #[serde(default)]
218 #[ts(optional)]
219 pub aggregates: Option<HashMap<String, Aggregate>>,
220
221 #[serde(skip_serializing)]
222 #[serde(default)]
223 #[ts(optional)]
224 pub group_by_depth: Option<u32>,
225
226 #[serde(skip_serializing_if = "Option::is_none")]
227 #[serde(default)]
228 #[ts(optional)]
229 pub filter_op: Option<FilterReducer>,
230
231 #[serde(skip_serializing_if = "Option::is_none")]
232 #[serde(default)]
233 #[ts(optional)]
234 pub group_rollup_mode: Option<GroupRollupMode>,
235}
236
237impl From<ViewConfigUpdate> for proto::ViewConfig {
238 fn from(value: ViewConfigUpdate) -> Self {
239 proto::ViewConfig {
240 group_by: value.group_by.unwrap_or_default(),
241 split_by: value.split_by.unwrap_or_default(),
242 columns: value.columns.map(|x| proto::ColumnsUpdate {
243 opt_columns: Some(columns_update::OptColumns::Columns(
244 proto::columns_update::Columns {
245 columns: x.into_iter().flatten().collect(),
246 },
247 )),
248 }),
249 filter: value
250 .filter
251 .unwrap_or_default()
252 .into_iter()
253 .map(|x| x.into())
254 .collect(),
255 filter_op: value
256 .filter_op
257 .map(proto::view_config::FilterReducer::from)
258 .unwrap_or_default() as i32,
259 sort: value
260 .sort
261 .unwrap_or_default()
262 .into_iter()
263 .map(|x| x.into())
264 .collect(),
265 expressions: value.expressions.unwrap_or_default().0,
266 windows: value
267 .windows
268 .unwrap_or_default()
269 .0
270 .into_iter()
271 .map(|(k, v)| (k, v.into()))
272 .collect(),
273 aggregates: value
274 .aggregates
275 .unwrap_or_default()
276 .into_iter()
277 .map(|(x, y)| (x, y.into()))
278 .collect(),
279 group_by_depth: value.group_by_depth,
280 group_rollup_mode: value
281 .group_rollup_mode
282 .map(|x| proto::GroupRollupMode::from(x).into()),
283 }
284 }
285}
286
287impl From<FilterReducer> for proto::view_config::FilterReducer {
288 fn from(value: FilterReducer) -> Self {
289 match value {
290 FilterReducer::And => proto::view_config::FilterReducer::And,
291 FilterReducer::Or => proto::view_config::FilterReducer::Or,
292 }
293 }
294}
295
296impl From<proto::view_config::FilterReducer> for FilterReducer {
297 fn from(value: proto::view_config::FilterReducer) -> Self {
298 match value {
299 proto::view_config::FilterReducer::And => FilterReducer::And,
300 proto::view_config::FilterReducer::Or => FilterReducer::Or,
301 }
302 }
303}
304
305impl From<ViewConfig> for ViewConfigUpdate {
306 fn from(value: ViewConfig) -> Self {
307 ViewConfigUpdate {
308 group_by: Some(value.group_by),
309 split_by: Some(value.split_by),
310 columns: Some(value.columns),
311 filter: Some(value.filter),
312 filter_op: Some(value.filter_op),
313 sort: Some(value.sort),
314 expressions: Some(value.expressions),
315 windows: Some(value.windows),
316 aggregates: Some(value.aggregates),
317 group_by_depth: value.group_by_depth,
318 group_rollup_mode: Some(value.group_rollup_mode),
319 }
320 }
321}
322
323impl From<proto::ViewConfig> for ViewConfig {
324 fn from(value: proto::ViewConfig) -> Self {
325 ViewConfig {
326 group_by: value.group_by,
327 split_by: value.split_by,
328 columns: match value.columns.unwrap_or_default().opt_columns {
329 Some(columns_update::OptColumns::Columns(x)) => {
330 x.columns.into_iter().map(Some).collect()
331 },
332 _ => {
333 vec![]
334 },
335 },
336 filter: value.filter.into_iter().map(|x| x.into()).collect(),
337 filter_op: proto::view_config::FilterReducer::try_from(value.filter_op)
338 .unwrap_or_default()
339 .into(),
340 sort: value.sort.into_iter().map(|x| x.into()).collect(),
341 expressions: Expressions(value.expressions),
342 windows: Windows(
343 value
344 .windows
345 .into_iter()
346 .map(|(k, v)| (k, v.into()))
347 .collect(),
348 ),
349 aggregates: value
350 .aggregates
351 .into_iter()
352 .map(|(x, y)| (x, y.into()))
353 .collect(),
354 group_by_depth: value.group_by_depth,
355 group_rollup_mode: value
356 .group_rollup_mode
357 .map(proto::GroupRollupMode::try_from)
358 .and_then(|x| x.ok())
359 .map(|x| x.into())
360 .unwrap_or_default(),
361 }
362 }
363}
364
365impl From<ViewConfigUpdate> for ViewConfig {
366 fn from(value: ViewConfigUpdate) -> Self {
367 ViewConfig {
368 group_by: value.group_by.unwrap_or_default(),
369 split_by: value.split_by.unwrap_or_default(),
370 columns: value.columns.unwrap_or_default(),
371 filter: value.filter.unwrap_or_default(),
372 filter_op: value.filter_op.unwrap_or_default(),
373 sort: value.sort.unwrap_or_default(),
374 expressions: value.expressions.unwrap_or_default(),
375 windows: value.windows.unwrap_or_default(),
376 aggregates: value.aggregates.unwrap_or_default(),
377 group_by_depth: value.group_by_depth,
378 group_rollup_mode: value.group_rollup_mode.unwrap_or_default(),
379 }
380 }
381}
382
383impl From<proto::ViewConfig> for ViewConfigUpdate {
384 fn from(value: proto::ViewConfig) -> Self {
385 ViewConfigUpdate {
386 group_by: Some(value.group_by),
387 split_by: Some(value.split_by),
388 columns: match value.columns.unwrap_or_default().opt_columns {
389 Some(columns_update::OptColumns::Columns(x)) => {
390 Some(x.columns.into_iter().map(Some).collect())
391 },
392 _ => None,
393 },
394 filter: Some(value.filter.into_iter().map(|x| x.into()).collect()),
395 filter_op: Some(
396 proto::view_config::FilterReducer::try_from(value.filter_op)
397 .unwrap_or_default()
398 .into(),
399 ),
400 sort: Some(value.sort.into_iter().map(|x| x.into()).collect()),
401 expressions: Some(Expressions(value.expressions)),
402 windows: Some(Windows(
403 value
404 .windows
405 .into_iter()
406 .map(|(k, v)| (k, v.into()))
407 .collect(),
408 )),
409 aggregates: Some(
410 value
411 .aggregates
412 .into_iter()
413 .map(|(x, y)| (x, y.into()))
414 .collect(),
415 ),
416 group_by_depth: value.group_by_depth,
417 group_rollup_mode: value
418 .group_rollup_mode
419 .and_then(|x| proto::GroupRollupMode::try_from(x).ok())
420 .map(|x| x.into()),
421 }
422 }
423}
424
425impl ViewConfig {
426 fn _apply<T>(field: &mut T, update: Option<T>) -> bool {
427 match update {
428 None => false,
429 Some(update) => {
430 *field = update;
431 true
432 },
433 }
434 }
435
436 pub fn reset(&mut self, reset_expressions: bool) {
437 let mut config = Self::default();
438 if !reset_expressions {
439 config.expressions = self.expressions.clone();
440 }
441 std::mem::swap(self, &mut config);
442 }
443
444 pub fn apply_update(&mut self, mut update: ViewConfigUpdate) -> bool {
447 let mut changed = false;
448 if ((self.group_rollup_mode == GroupRollupMode::Total
449 && update.group_rollup_mode.is_none())
450 || update.group_rollup_mode == Some(GroupRollupMode::Total))
451 && update
452 .group_by
453 .as_ref()
454 .map(|x| !x.is_empty())
455 .unwrap_or_default()
456 {
457 tracing::info!("`total` incompatible with `group_by`");
458 changed = true;
459 update.group_rollup_mode = Some(GroupRollupMode::Rollup);
460 }
461
462 if update.group_rollup_mode == Some(GroupRollupMode::Total) && !self.group_by.is_empty() {
463 tracing::info!("`group_by` incompatible with `total`");
464 changed = true;
465 update.group_by = Some(vec![]);
466 }
467
468 changed = Self::_apply(&mut self.group_by, update.group_by) || changed;
469 changed = Self::_apply(&mut self.split_by, update.split_by) || changed;
470 changed = Self::_apply(&mut self.columns, update.columns) || changed;
471 changed = Self::_apply(&mut self.filter, update.filter) || changed;
472 changed = Self::_apply(&mut self.sort, update.sort) || changed;
473 changed = Self::_apply(&mut self.aggregates, update.aggregates) || changed;
474 changed = Self::_apply(&mut self.expressions, update.expressions) || changed;
475 changed = Self::_apply(&mut self.windows, update.windows) || changed;
476 changed = Self::_apply(&mut self.group_rollup_mode, update.group_rollup_mode) || changed;
477 if self.group_rollup_mode == GroupRollupMode::Total && !self.group_by.is_empty() {
478 tracing::info!("`total` incompatible with `group_by`");
479 changed = true;
480 self.group_by = vec![];
481 }
482
483 changed
484 }
485
486 pub fn is_aggregated(&self) -> bool {
487 !self.group_by.is_empty() || self.group_rollup_mode == GroupRollupMode::Total
488 }
489
490 pub fn is_column_expression_in_use(&self, name: &str) -> bool {
491 let name = name.to_owned();
492 self.group_by.contains(&name)
493 || self.split_by.contains(&name)
494 || self.sort.iter().any(|x| x.0 == name)
495 || self.filter.iter().any(|x| x.column() == name)
496 || self.columns.contains(&Some(name))
497 }
498
499 pub fn is_equivalent(&self, other: &Self) -> bool {
504 let _self = self.clone();
505 let _self = ViewConfig {
506 columns: _self.columns.into_iter().filter(|x| x.is_some()).collect(),
507 .._self
508 };
509
510 let _other = other.clone();
511 let _other = ViewConfig {
512 columns: _other.columns.into_iter().filter(|x| x.is_some()).collect(),
513 ..other.clone()
514 };
515
516 _self == _other
517 }
518}