use std::collections::HashMap;
use std::fmt::Display;
use serde::{Deserialize, Serialize};
use ts_rs::TS;
use super::aggregates::*;
use super::expressions::*;
use super::filters::*;
use super::sort::*;
use crate::proto;
use crate::proto::columns_update;
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq, Eq, TS)]
pub enum GroupRollupMode {
#[default]
#[serde(rename = "rollup")]
Rollup,
#[serde(rename = "flat")]
Flat,
#[serde(rename = "total")]
Total,
}
impl Display for GroupRollupMode {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
write!(fmt, "{}", match self {
Self::Rollup => "Rollup",
Self::Flat => "Flat",
Self::Total => "Total",
})
}
}
impl From<proto::GroupRollupMode> for GroupRollupMode {
fn from(value: proto::GroupRollupMode) -> Self {
match value {
proto::GroupRollupMode::Rollup => Self::Rollup,
proto::GroupRollupMode::Flat => Self::Flat,
proto::GroupRollupMode::Total => Self::Total,
}
}
}
impl From<GroupRollupMode> for proto::GroupRollupMode {
fn from(value: GroupRollupMode) -> Self {
match value {
GroupRollupMode::Rollup => proto::GroupRollupMode::Rollup,
GroupRollupMode::Flat => proto::GroupRollupMode::Flat,
GroupRollupMode::Total => proto::GroupRollupMode::Total,
}
}
}
#[derive(Clone, Debug, Deserialize, Default, PartialEq, Serialize, TS)]
#[serde(deny_unknown_fields)]
pub struct ViewConfig {
#[serde(default)]
pub group_by: Vec<String>,
#[serde(default)]
pub split_by: Vec<String>,
#[serde(default)]
pub sort: Vec<Sort>,
#[serde(default)]
pub filter: Vec<Filter>,
#[serde(default)]
pub group_rollup_mode: GroupRollupMode,
#[serde(skip_serializing_if = "is_default_value")]
#[serde(default)]
pub filter_op: FilterReducer,
#[serde(default)]
pub expressions: Expressions,
#[serde(default)]
pub columns: Vec<Option<String>>,
#[serde(default)]
pub aggregates: HashMap<String, Aggregate>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub group_by_depth: Option<u32>,
}
fn is_default_value<A: Default + PartialEq>(value: &A) -> bool {
value == &A::default()
}
#[derive(Clone, Debug, Deserialize, Default, PartialEq, Serialize, TS)]
#[serde(deny_unknown_fields)]
pub struct ViewConfigUpdate {
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
#[ts(optional)]
pub group_by: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
#[ts(optional)]
pub split_by: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
#[ts(optional)]
pub columns: Option<Vec<Option<String>>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
#[ts(optional)]
pub filter: Option<Vec<Filter>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
#[ts(optional)]
pub sort: Option<Vec<Sort>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
#[ts(optional)]
pub expressions: Option<Expressions>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
#[ts(optional)]
pub aggregates: Option<HashMap<String, Aggregate>>,
#[serde(skip_serializing)]
#[serde(default)]
#[ts(optional)]
pub group_by_depth: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
#[ts(optional)]
pub filter_op: Option<FilterReducer>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
#[ts(optional)]
pub group_rollup_mode: Option<GroupRollupMode>,
}
impl From<ViewConfigUpdate> for proto::ViewConfig {
fn from(value: ViewConfigUpdate) -> Self {
proto::ViewConfig {
group_by: value.group_by.unwrap_or_default(),
split_by: value.split_by.unwrap_or_default(),
columns: value.columns.map(|x| proto::ColumnsUpdate {
opt_columns: Some(columns_update::OptColumns::Columns(
proto::columns_update::Columns {
columns: x.into_iter().flatten().collect(),
},
)),
}),
filter: value
.filter
.unwrap_or_default()
.into_iter()
.map(|x| x.into())
.collect(),
filter_op: value
.filter_op
.map(proto::view_config::FilterReducer::from)
.unwrap_or_default() as i32,
sort: value
.sort
.unwrap_or_default()
.into_iter()
.map(|x| x.into())
.collect(),
expressions: value.expressions.unwrap_or_default().0,
aggregates: value
.aggregates
.unwrap_or_default()
.into_iter()
.map(|(x, y)| (x, y.into()))
.collect(),
group_by_depth: value.group_by_depth,
group_rollup_mode: value
.group_rollup_mode
.map(|x| proto::GroupRollupMode::from(x).into()),
}
}
}
impl From<FilterReducer> for proto::view_config::FilterReducer {
fn from(value: FilterReducer) -> Self {
match value {
FilterReducer::And => proto::view_config::FilterReducer::And,
FilterReducer::Or => proto::view_config::FilterReducer::Or,
}
}
}
impl From<proto::view_config::FilterReducer> for FilterReducer {
fn from(value: proto::view_config::FilterReducer) -> Self {
match value {
proto::view_config::FilterReducer::And => FilterReducer::And,
proto::view_config::FilterReducer::Or => FilterReducer::Or,
}
}
}
impl From<ViewConfig> for ViewConfigUpdate {
fn from(value: ViewConfig) -> Self {
ViewConfigUpdate {
group_by: Some(value.group_by),
split_by: Some(value.split_by),
columns: Some(value.columns),
filter: Some(value.filter),
filter_op: Some(value.filter_op),
sort: Some(value.sort),
expressions: Some(value.expressions),
aggregates: Some(value.aggregates),
group_by_depth: value.group_by_depth,
group_rollup_mode: Some(value.group_rollup_mode),
}
}
}
impl From<proto::ViewConfig> for ViewConfig {
fn from(value: proto::ViewConfig) -> Self {
ViewConfig {
group_by: value.group_by,
split_by: value.split_by,
columns: match value.columns.unwrap_or_default().opt_columns {
Some(columns_update::OptColumns::Columns(x)) => {
x.columns.into_iter().map(Some).collect()
},
_ => {
vec![]
},
},
filter: value.filter.into_iter().map(|x| x.into()).collect(),
filter_op: proto::view_config::FilterReducer::try_from(value.filter_op)
.unwrap_or_default()
.into(),
sort: value.sort.into_iter().map(|x| x.into()).collect(),
expressions: Expressions(value.expressions),
aggregates: value
.aggregates
.into_iter()
.map(|(x, y)| (x, y.into()))
.collect(),
group_by_depth: value.group_by_depth,
group_rollup_mode: value
.group_rollup_mode
.map(proto::GroupRollupMode::try_from)
.and_then(|x| x.ok())
.map(|x| x.into())
.unwrap_or_default(),
}
}
}
impl From<ViewConfigUpdate> for ViewConfig {
fn from(value: ViewConfigUpdate) -> Self {
ViewConfig {
group_by: value.group_by.unwrap_or_default(),
split_by: value.split_by.unwrap_or_default(),
columns: value.columns.unwrap_or_default(),
filter: value.filter.unwrap_or_default(),
filter_op: value.filter_op.unwrap_or_default(),
sort: value.sort.unwrap_or_default(),
expressions: value.expressions.unwrap_or_default(),
aggregates: value.aggregates.unwrap_or_default(),
group_by_depth: value.group_by_depth,
group_rollup_mode: value.group_rollup_mode.unwrap_or_default(),
}
}
}
impl From<proto::ViewConfig> for ViewConfigUpdate {
fn from(value: proto::ViewConfig) -> Self {
ViewConfigUpdate {
group_by: Some(value.group_by),
split_by: Some(value.split_by),
columns: match value.columns.unwrap_or_default().opt_columns {
Some(columns_update::OptColumns::Columns(x)) => {
Some(x.columns.into_iter().map(Some).collect())
},
_ => None,
},
filter: Some(value.filter.into_iter().map(|x| x.into()).collect()),
filter_op: Some(
proto::view_config::FilterReducer::try_from(value.filter_op)
.unwrap_or_default()
.into(),
),
sort: Some(value.sort.into_iter().map(|x| x.into()).collect()),
expressions: Some(Expressions(value.expressions)),
aggregates: Some(
value
.aggregates
.into_iter()
.map(|(x, y)| (x, y.into()))
.collect(),
),
group_by_depth: value.group_by_depth,
group_rollup_mode: value
.group_rollup_mode
.and_then(|x| proto::GroupRollupMode::try_from(x).ok())
.map(|x| x.into()),
}
}
}
impl ViewConfig {
fn _apply<T>(field: &mut T, update: Option<T>) -> bool {
match update {
None => false,
Some(update) => {
*field = update;
true
},
}
}
pub fn reset(&mut self, reset_expressions: bool) {
let mut config = Self::default();
if !reset_expressions {
config.expressions = self.expressions.clone();
}
std::mem::swap(self, &mut config);
}
pub fn apply_update(&mut self, mut update: ViewConfigUpdate) -> bool {
let mut changed = false;
if ((self.group_rollup_mode == GroupRollupMode::Total
&& update.group_rollup_mode.is_none())
|| update.group_rollup_mode == Some(GroupRollupMode::Total))
&& update
.group_by
.as_ref()
.map(|x| !x.is_empty())
.unwrap_or_default()
{
tracing::info!("`total` incompatible with `group_by`");
changed = true;
update.group_rollup_mode = Some(GroupRollupMode::Rollup);
}
if update.group_rollup_mode == Some(GroupRollupMode::Total) && !self.group_by.is_empty() {
tracing::info!("`group_by` incompatible with `total`");
changed = true;
update.group_by = Some(vec![]);
}
changed = Self::_apply(&mut self.group_by, update.group_by) || changed;
changed = Self::_apply(&mut self.split_by, update.split_by) || changed;
changed = Self::_apply(&mut self.columns, update.columns) || changed;
changed = Self::_apply(&mut self.filter, update.filter) || changed;
changed = Self::_apply(&mut self.sort, update.sort) || changed;
changed = Self::_apply(&mut self.aggregates, update.aggregates) || changed;
changed = Self::_apply(&mut self.expressions, update.expressions) || changed;
changed = Self::_apply(&mut self.group_rollup_mode, update.group_rollup_mode) || changed;
if self.group_rollup_mode == GroupRollupMode::Total && !self.group_by.is_empty() {
tracing::info!("`total` incompatible with `group_by`");
changed = true;
self.group_by = vec![];
}
changed
}
pub fn is_aggregated(&self) -> bool {
!self.group_by.is_empty() || self.group_rollup_mode == GroupRollupMode::Total
}
pub fn is_column_expression_in_use(&self, name: &str) -> bool {
let name = name.to_owned();
self.group_by.contains(&name)
|| self.split_by.contains(&name)
|| self.sort.iter().any(|x| x.0 == name)
|| self.filter.iter().any(|x| x.column() == name)
|| self.columns.contains(&Some(name))
}
pub fn is_equivalent(&self, other: &Self) -> bool {
let _self = self.clone();
let _self = ViewConfig {
columns: _self.columns.into_iter().filter(|x| x.is_some()).collect(),
.._self
};
let _other = other.clone();
let _other = ViewConfig {
columns: _other.columns.into_iter().filter(|x| x.is_some()).collect(),
..other.clone()
};
_self == _other
}
}