Skip to main content

perspective_client/virtual_server/
features.rs

1// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2// ┃ ██████ ██████ ██████       █      █      █      █      █ █▄  ▀███ █       ┃
3// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█  ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄  ▀█ █ ▀▀▀▀▀ ┃
4// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄   █ ▄▄▄▄▄ ┃
5// ┃ █      ██████ █  ▀█▄       █ ██████      █      ███▌▐███ ███████▄ █       ┃
6// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7// ┃ Copyright (c) 2017, the Perspective Authors.                              ┃
8// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9// ┃ This file is part of the Perspective library, distributed under the terms ┃
10// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
13use std::borrow::Cow;
14
15use indexmap::IndexMap;
16use serde::{Deserialize, Serialize};
17use ts_rs::TS;
18
19use crate::config::{GroupRollupMode, SplitRollupMode};
20use crate::proto::get_features_resp::{AggregateArgs, AggregateOptions, ColumnTypeOptions};
21use crate::proto::{ColumnType, GetFeaturesResp, WindowAggregateArgs};
22
23/// Describes the capabilities supported by a virtual server handler.
24///
25/// This struct is returned by
26/// [`VirtualServerHandler::get_features`](super::VirtualServerHandler::get_features)
27/// to inform clients about which operations are available.
28#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize, TS)]
29pub struct Features<'a> {
30    /// Whether group-by aggregation is supported.
31    #[serde(default)]
32    #[ts(optional, as = "Option<_>")]
33    pub group_by: bool,
34
35    /// Which `group_by_rollup_mode` options are supported
36    #[serde(default)]
37    #[ts(optional, as = "Option<_>")]
38    pub group_rollup_mode: Vec<GroupRollupMode>,
39
40    /// Whether split-by (pivot) operations are supported.
41    #[serde(default)]
42    #[ts(optional, as = "Option<_>")]
43    pub split_by: bool,
44
45    /// Which `split_rollup_mode` options are supported. Empty (the default)
46    /// means `["flat"]` - a server must opt in to `"rollup"` explicitly, as
47    /// it requires emitting subtotal and grand-total column groups.
48    #[serde(default)]
49    #[ts(optional, as = "Option<_>")]
50    pub split_rollup_mode: Vec<SplitRollupMode>,
51
52    /// Available filter operators per column type.
53    #[serde(default)]
54    #[ts(optional, as = "Option<_>")]
55    pub filter_ops: IndexMap<ColumnType, Vec<Cow<'a, str>>>,
56
57    /// Available aggregate functions per column type.
58    #[serde(default)]
59    #[ts(optional, as = "Option<_>")]
60    pub aggregates: IndexMap<ColumnType, Vec<AggSpec<'a>>>,
61
62    /// Whether sorting is supported.
63    #[serde(default)]
64    #[ts(optional, as = "Option<_>")]
65    pub sort: bool,
66
67    /// Whether computed expressions are supported.
68    #[serde(default)]
69    #[ts(optional, as = "Option<_>")]
70    pub expressions: bool,
71
72    /// Available window aggregates per column type.
73    #[serde(default)]
74    #[ts(optional, as = "Option<_>")]
75    pub window_aggregates: IndexMap<ColumnType, Vec<WindowAggSpec<'a>>>,
76
77    /// Whether update callbacks are supported.
78    #[serde(default)]
79    #[ts(optional, as = "Option<_>")]
80    pub on_update: bool,
81
82    /// The data store has no reliable natural row order
83    #[serde(default)]
84    #[ts(optional, as = "Option<_>")]
85    pub unordered: bool,
86}
87
88/// Specification for a window aggregate.
89#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, TS)]
90pub struct WindowAggSpec<'a> {
91    pub name: Cow<'a, str>,
92
93    /// Frame kinds this aggregate accepts (`rows`, `range`, `cumulative`).
94    /// Empty means it takes no frame at all.
95    #[serde(default)]
96    #[ts(optional, as = "Option<_>")]
97    pub frames: Vec<Cow<'a, str>>,
98
99    /// Takes a row offset, as `lag` / `lead` / `diff` do.
100    #[serde(default)]
101    #[ts(optional, as = "Option<_>")]
102    pub offset: bool,
103
104    /// Takes a smoothing factor, as `ema` does.
105    #[serde(default)]
106    #[ts(optional, as = "Option<_>")]
107    pub alpha: bool,
108
109    /// The output column type. `None` means the source column's type, which
110    /// is what `min` / `max` / `lag` do.
111    #[serde(default)]
112    #[serde(skip_serializing_if = "Option::is_none")]
113    #[ts(optional)]
114    pub result_type: Option<ColumnType>,
115}
116
117impl<'a> From<&'a str> for WindowAggSpec<'a> {
118    /// A bare name, taking no frame and no arguments.
119    fn from(name: &'a str) -> Self {
120        WindowAggSpec {
121            name: Cow::Borrowed(name),
122            frames: vec![],
123            offset: false,
124            alpha: false,
125            result_type: None,
126        }
127    }
128}
129
130/// Specification for an aggregate function.
131///
132/// Aggregates can either take no additional arguments ([`AggSpec::Single`])
133/// or require column type arguments ([`AggSpec::Multiple`]).
134#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, TS)]
135#[serde(untagged)]
136pub enum AggSpec<'a> {
137    /// An aggregate function with no additional arguments.
138    Single(Cow<'a, str>),
139    /// An aggregate function that requires column type arguments.
140    Multiple(Cow<'a, str>, Vec<ColumnType>),
141}
142
143impl<'a> From<Features<'a>> for GetFeaturesResp {
144    fn from(value: Features<'a>) -> GetFeaturesResp {
145        GetFeaturesResp {
146            group_by: value.group_by,
147            group_rollup_mode: value
148                .group_rollup_mode
149                .iter()
150                .map(|x| crate::proto::GroupRollupMode::from(*x) as i32)
151                .collect(),
152            split_rollup_mode: value
153                .split_rollup_mode
154                .iter()
155                .map(|x| crate::proto::SplitRollupMode::from(*x) as i32)
156                .collect(),
157            split_by: value.split_by,
158            expressions: value.expressions,
159            on_update: value.on_update,
160            sort: value.sort,
161            unordered: value.unordered,
162            window_aggregates: value
163                .window_aggregates
164                .iter()
165                .map(|(ty, aggs)| {
166                    (
167                        *ty as u32,
168                        crate::proto::get_features_resp::WindowAggregateOptions {
169                            options: aggs
170                                .iter()
171                                .map(|x| WindowAggregateArgs {
172                                    name: x.name.to_string(),
173                                    frames: x.frames.iter().map(|f| f.to_string()).collect(),
174                                    offset: x.offset,
175                                    alpha: x.alpha,
176                                    result_type: x.result_type.map(|t| t as i32),
177                                })
178                                .collect(),
179                        },
180                    )
181                })
182                .collect(),
183            aggregates: value
184                .aggregates
185                .iter()
186                .map(|(dtype, aggs)| {
187                    (*dtype as u32, AggregateOptions {
188                        aggregates: aggs
189                            .iter()
190                            .map(|agg| match agg {
191                                AggSpec::Single(cow) => AggregateArgs {
192                                    name: cow.to_string(),
193                                    args: vec![],
194                                },
195                                AggSpec::Multiple(cow, column_types) => AggregateArgs {
196                                    name: cow.to_string(),
197                                    args: column_types.iter().map(|x| *x as i32).collect(),
198                                },
199                            })
200                            .collect(),
201                    })
202                })
203                .collect(),
204            filter_ops: value
205                .filter_ops
206                .iter()
207                .map(|(ty, options)| {
208                    (*ty as u32, ColumnTypeOptions {
209                        options: options.iter().map(|x| (*x).to_string()).collect(),
210                    })
211                })
212                .collect(),
213        }
214    }
215}