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;
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, Default, Serialize)]
137pub struct VirtualDataSlice(IndexMap<String, VirtualDataColumn>);
138
139impl Deref for VirtualDataSlice {
140    type Target = IndexMap<String, VirtualDataColumn>;
141
142    fn deref(&self) -> &Self::Target {
143        &self.0
144    }
145}
146
147impl DerefMut for VirtualDataSlice {
148    fn deref_mut(&mut self) -> &mut Self::Target {
149        &mut self.0
150    }
151}
152
153impl VirtualDataSlice {
154    pub(super) fn to_rows(&self) -> Vec<IndexMap<String, VirtualDataCell>> {
155        let num_rows = self.values().next().map(|x| x.len()).unwrap_or(0);
156        (0..num_rows)
157            .map(|row_idx| {
158                self.iter()
159                    .map(|(col_name, col_data)| {
160                        let row_value = match col_data {
161                            VirtualDataColumn::Boolean(v) => VirtualDataCell::Boolean(v[row_idx]),
162                            VirtualDataColumn::String(v) => {
163                                VirtualDataCell::String(v[row_idx].clone())
164                            },
165                            VirtualDataColumn::Float(v) => VirtualDataCell::Float(v[row_idx]),
166                            VirtualDataColumn::Integer(v) => VirtualDataCell::Integer(v[row_idx]),
167                            VirtualDataColumn::Datetime(v) => VirtualDataCell::Datetime(v[row_idx]),
168                            VirtualDataColumn::IntegerIndex(v) => {
169                                VirtualDataCell::IntegerIndex(v[row_idx].clone())
170                            },
171                            VirtualDataColumn::RowPath(v) => {
172                                VirtualDataCell::RowPath(v[row_idx].clone())
173                            },
174                        };
175                        (col_name.clone(), row_value)
176                    })
177                    .collect()
178            })
179            .collect()
180    }
181
182    /// Sets a value in a column at the specified row index.
183    ///
184    /// If `group_by_index` is `Some`, the value is added to the `__ROW_PATH__`
185    /// column as part of the row's group-by path. Otherwise, the value is
186    /// inserted into the named column.
187    ///
188    /// Creates the column if it does not already exist.
189    pub fn set_col<T: SetVirtualDataColumn>(
190        &mut self,
191        name: &str,
192        group_by_index: Option<usize>,
193        index: usize,
194        value: T,
195    ) -> Result<(), Box<dyn Error>> {
196        if group_by_index.is_some() {
197            if !self.contains_key("__ROW_PATH__") {
198                self.insert(
199                    "__ROW_PATH__".to_owned(),
200                    VirtualDataColumn::RowPath(vec![]),
201                );
202            }
203
204            let Some(VirtualDataColumn::RowPath(col)) = self.get_mut("__ROW_PATH__") else {
205                return Err("__ROW_PATH__ column has unexpected type".into());
206            };
207
208            if let Some(row) = col.get_mut(index) {
209                let scalar = value.to_scalar();
210                row.push(scalar);
211            } else {
212                while col.len() < index {
213                    col.push(vec![])
214                }
215
216                let scalar = value.to_scalar();
217                col.push(vec![scalar]);
218            }
219
220            Ok(())
221        } else {
222            if !self.contains_key(name) {
223                self.insert(name.to_owned(), T::new_column());
224            }
225
226            let col = self
227                .get_mut(name)
228                .ok_or_else(|| format!("Column '{}' not found after insertion", name))?;
229
230            Ok(value.write_to(col)?)
231        }
232    }
233}