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}