Skip to main content

perspective_client/virtual_server/
data.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::error::Error;
14use std::ops::{Deref, DerefMut};
15
16use indexmap::IndexMap;
17use serde::Serialize;
18
19use crate::config::{Scalar, ViewConfig};
20
21/// A column of data returned from a virtual server query.
22///
23/// Each variant represents a different column type, containing a vector
24/// of optional values. `None` values represent null/missing data.
25#[derive(Debug, Serialize)]
26#[serde(untagged)]
27pub enum VirtualDataColumn {
28    Boolean(Vec<Option<bool>>),
29    String(Vec<Option<String>>),
30    Float(Vec<Option<f64>>),
31    Integer(Vec<Option<i32>>),
32    Datetime(Vec<Option<i64>>),
33    IntegerIndex(Vec<Option<Vec<i32>>>),
34    RowPath(Vec<Vec<Scalar>>),
35}
36
37/// A single cell value in a row-oriented data representation.
38///
39/// Used when converting [`VirtualDataSlice`] to row format for JSON
40/// serialization.
41#[derive(Debug, Serialize)]
42#[serde(untagged)]
43pub enum VirtualDataCell {
44    Boolean(Option<bool>),
45    String(Option<String>),
46    Float(Option<f64>),
47    Integer(Option<i32>),
48    Datetime(Option<i64>),
49    IntegerIndex(Option<Vec<i32>>),
50    RowPath(Vec<Scalar>),
51}
52
53impl VirtualDataColumn {
54    /// Returns `true` if the column contains no elements.
55    pub fn is_empty(&self) -> bool {
56        match self {
57            VirtualDataColumn::Boolean(v) => v.is_empty(),
58            VirtualDataColumn::String(v) => v.is_empty(),
59            VirtualDataColumn::Float(v) => v.is_empty(),
60            VirtualDataColumn::Integer(v) => v.is_empty(),
61            VirtualDataColumn::Datetime(v) => v.is_empty(),
62            VirtualDataColumn::IntegerIndex(v) => v.is_empty(),
63            VirtualDataColumn::RowPath(v) => v.is_empty(),
64        }
65    }
66
67    /// Returns the number of elements in the column.
68    pub fn len(&self) -> usize {
69        match self {
70            VirtualDataColumn::Boolean(v) => v.len(),
71            VirtualDataColumn::String(v) => v.len(),
72            VirtualDataColumn::Float(v) => v.len(),
73            VirtualDataColumn::Integer(v) => v.len(),
74            VirtualDataColumn::Datetime(v) => v.len(),
75            VirtualDataColumn::IntegerIndex(v) => v.len(),
76            VirtualDataColumn::RowPath(v) => v.len(),
77        }
78    }
79}
80
81/// Trait for types that can be written to a [`VirtualDataColumn`] which
82/// enforces sequential construction.
83///
84/// This trait enables type-safe insertion of values into virtual data columns,
85/// ensuring that values are written to columns of the correct type.
86pub trait SetVirtualDataColumn {
87    /// Writes this value (sequentially) to the given column.
88    ///
89    /// Returns an error if the column type does not match the value type.
90    fn write_to(self, col: &mut VirtualDataColumn) -> Result<(), &'static str>;
91
92    /// Creates a new empty column of the appropriate type for this value.
93    fn new_column() -> VirtualDataColumn;
94
95    /// Converts this value to a [`Scalar`] representation.
96    fn to_scalar(self) -> Scalar;
97}
98
99macro_rules! template_psp {
100    ($t:ty, $u:ident, $v:ident, $w:ty) => {
101        impl SetVirtualDataColumn for Option<$t> {
102            fn write_to(self, col: &mut VirtualDataColumn) -> Result<(), &'static str> {
103                if let VirtualDataColumn::$u(x) = col {
104                    x.push(self);
105                    Ok(())
106                } else {
107                    Err("Bad type")
108                }
109            }
110
111            fn new_column() -> VirtualDataColumn {
112                VirtualDataColumn::$u(vec![])
113            }
114
115            fn to_scalar(self) -> Scalar {
116                if let Some(x) = self {
117                    Scalar::$v(x as $w)
118                } else {
119                    Scalar::Null
120                }
121            }
122        }
123    };
124}
125
126template_psp!(String, String, String, String);
127template_psp!(f64, Float, Float, f64);
128template_psp!(i32, Integer, Float, f64);
129template_psp!(i64, Datetime, Float, f64);
130template_psp!(bool, Boolean, Bool, bool);
131
132/// A columnar data slice returned from a virtual server view query.
133///
134/// This struct represents a rectangular slice of data from a view. It can be
135/// serialized to JSON in either column-oriented or row-oriented format.
136#[derive(Debug, Serialize)]
137#[serde(transparent)]
138pub struct VirtualDataSlice(
139    #[serde(skip)] ViewConfig,
140    IndexMap<String, VirtualDataColumn>,
141);
142
143impl Deref for VirtualDataSlice {
144    type Target = IndexMap<String, VirtualDataColumn>;
145
146    fn deref(&self) -> &Self::Target {
147        &self.1
148    }
149}
150
151impl DerefMut for VirtualDataSlice {
152    fn deref_mut(&mut self) -> &mut Self::Target {
153        &mut self.1
154    }
155}
156
157impl VirtualDataSlice {
158    pub fn new(config: ViewConfig) -> Self {
159        VirtualDataSlice(config, IndexMap::default())
160    }
161
162    pub(super) fn to_rows(&self) -> Vec<IndexMap<String, VirtualDataCell>> {
163        let num_rows = self.values().next().map(|x| x.len()).unwrap_or(0);
164        (0..num_rows)
165            .map(|row_idx| {
166                self.iter()
167                    .map(|(col_name, col_data)| {
168                        let row_value = match col_data {
169                            VirtualDataColumn::Boolean(v) => VirtualDataCell::Boolean(v[row_idx]),
170                            VirtualDataColumn::String(v) => {
171                                VirtualDataCell::String(v[row_idx].clone())
172                            },
173                            VirtualDataColumn::Float(v) => VirtualDataCell::Float(v[row_idx]),
174                            VirtualDataColumn::Integer(v) => VirtualDataCell::Integer(v[row_idx]),
175                            VirtualDataColumn::Datetime(v) => VirtualDataCell::Datetime(v[row_idx]),
176                            VirtualDataColumn::IntegerIndex(v) => {
177                                VirtualDataCell::IntegerIndex(v[row_idx].clone())
178                            },
179                            VirtualDataColumn::RowPath(v) => {
180                                VirtualDataCell::RowPath(v[row_idx].clone())
181                            },
182                        };
183                        (col_name.clone(), row_value)
184                    })
185                    .collect()
186            })
187            .collect()
188    }
189
190    /// Sets a value in a column at the specified row index.
191    ///
192    /// If `group_by_index` is `Some`, the value is added to the `__ROW_PATH__`
193    /// column as part of the row's group-by path. Otherwise, the value is
194    /// inserted into the named column.
195    ///
196    /// Creates the column if it does not already exist.
197    pub fn set_col<T: SetVirtualDataColumn>(
198        &mut self,
199        name: &str,
200        grouping_id: Option<usize>,
201        index: usize,
202        value: T,
203    ) -> Result<(), Box<dyn Error>> {
204        if name.starts_with("__ROW_PATH_") {
205            let group_by_index: u32 = name[11..name.len() - 2].parse()?;
206            let max_grouping_id = 2_i32.pow((self.0.group_by.len() as u32) - group_by_index) - 1;
207            if grouping_id.map(|x| x as i32).unwrap_or(i32::MAX) < max_grouping_id
208                || !self.0.split_by.is_empty()
209            {
210                if !self.contains_key("__ROW_PATH__") {
211                    self.insert(
212                        "__ROW_PATH__".to_owned(),
213                        VirtualDataColumn::RowPath(vec![]),
214                    );
215                }
216
217                let Some(VirtualDataColumn::RowPath(col)) = self.get_mut("__ROW_PATH__") else {
218                    return Err("__ROW_PATH__ column has unexpected type".into());
219                };
220
221                if let Some(row) = col.get_mut(index) {
222                    let scalar = value.to_scalar();
223                    row.push(scalar);
224                } else {
225                    while col.len() < index {
226                        col.push(vec![])
227                    }
228
229                    let scalar = value.to_scalar();
230                    col.push(vec![scalar]);
231                }
232            }
233
234            Ok(())
235        } else {
236            if !self.contains_key(name) {
237                self.insert(name.to_owned(), T::new_column());
238            }
239
240            let col = self
241                .get_mut(name)
242                .ok_or_else(|| format!("Column '{}' not found after insertion", name))?;
243
244            Ok(value.write_to(col)?)
245        }
246    }
247}