Skip to main content

perspective_client/virtual_server/
handler.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::future::Future;
14use std::pin::Pin;
15
16use indexmap::IndexMap;
17
18use super::data::VirtualDataSlice;
19use super::features::Features;
20use super::generic_sql_model::column_path_source;
21use crate::config::{ViewConfig, ViewConfigUpdate};
22use crate::proto::{ColumnType, HostedTable, TableMakePortReq, ViewPort};
23use crate::table::{DescribeError, Description};
24
25#[cfg(feature = "sendable")]
26pub type VirtualServerFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
27
28/// A boxed future that conditionally implements `Send` based on the target
29/// architecture.
30///
31/// This only compiles on wasm, except for `rust-analyzer` and `metadata`
32/// generation, so this type exists to tryck the compiler
33#[cfg(not(feature = "sendable"))]
34pub type VirtualServerFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
35
36/// Handler trait for implementing virtual server backends.
37///
38/// This trait defines the interface that must be implemented to provide
39/// a custom data source for the Perspective virtual server. Implementors
40/// handle table and view operations, translating them to their underlying
41/// data store.
42pub trait VirtualServerHandler {
43    // Required
44
45    /// The error type returned by handler methods.
46    #[cfg(not(feature = "sendable"))]
47    type Error: std::error::Error + Send + Sync + 'static;
48
49    #[cfg(feature = "sendable")]
50    type Error: std::error::Error + 'static;
51
52    /// Returns a list of all tables hosted by this handler.
53    fn get_hosted_tables(&self) -> VirtualServerFuture<'_, Result<Vec<HostedTable>, Self::Error>>;
54
55    /// Returns the schema (column names and types) for a table.
56    fn table_schema(
57        &self,
58        table_id: &str,
59    ) -> VirtualServerFuture<'_, Result<IndexMap<String, ColumnType>, Self::Error>>;
60
61    /// Returns the number of rows in a table.
62    fn table_size(&self, table_id: &str) -> VirtualServerFuture<'_, Result<u32, Self::Error>>;
63
64    /// Creates a new view on a table with the given configuration.
65    ///
66    /// The handler may modify the configuration to reflect any adjustments
67    /// made during view creation.
68    fn table_make_view(
69        &mut self,
70        view_id: &str,
71        view_id: &str,
72        config: &mut ViewConfigUpdate,
73    ) -> VirtualServerFuture<'_, Result<String, Self::Error>>;
74
75    /// Validates a complete view config against a table and reports the schema
76    /// a view built from it would have, WITHOUT creating one.
77    fn table_describe(
78        &mut self,
79        table_id: &str,
80        config: &ViewConfig,
81    ) -> VirtualServerFuture<'_, Result<Result<Description, DescribeError>, Self::Error>>;
82
83    /// Deletes a view and releases its resources.
84    fn view_delete(&self, view_id: &str) -> VirtualServerFuture<'_, Result<(), Self::Error>>;
85
86    /// Retrieves data from a view within the specified viewport.
87    fn view_get_data(
88        &self,
89        view_id: &str,
90        config: &ViewConfig,
91        schema: &IndexMap<String, ColumnType>,
92        viewport: &ViewPort,
93    ) -> VirtualServerFuture<'_, Result<VirtualDataSlice, Self::Error>>;
94
95    // Optional
96
97    /// Return the column count of a `Table`
98    fn table_column_size(
99        &self,
100        table_id: &str,
101    ) -> VirtualServerFuture<'_, Result<u32, Self::Error>> {
102        let fut = self.table_schema(table_id);
103        Box::pin(async move { Ok(fut.await?.len() as u32) })
104    }
105
106    /// Returns the number of rows in a `View`.
107    fn view_size(&self, view_id: &str) -> VirtualServerFuture<'_, Result<u32, Self::Error>> {
108        Box::pin(self.table_size(view_id))
109    }
110
111    /// Return the column count of a `View`
112    fn view_column_size(
113        &self,
114        view_id: &str,
115        config: &ViewConfig,
116    ) -> VirtualServerFuture<'_, Result<u32, Self::Error>> {
117        let fut = self.view_schema(view_id, config);
118        Box::pin(async move { Ok(fut.await?.len() as u32) })
119    }
120
121    /// Returns the schema of a view after applying its configuration.
122    fn view_schema(
123        &self,
124        view_id: &str,
125        _config: &ViewConfig,
126    ) -> VirtualServerFuture<'_, Result<IndexMap<String, ColumnType>, Self::Error>> {
127        Box::pin(self.table_schema(view_id))
128    }
129
130    /// Returns the features supported by this handler.
131    ///
132    /// Default implementation returns default features.
133    fn get_features(&self) -> VirtualServerFuture<'_, Result<Features<'_>, Self::Error>> {
134        Box::pin(async { Ok(Features::default()) })
135    }
136
137    /// Creates a new input port on a table.
138    ///
139    /// Default implementation returns port ID 0.
140    fn table_make_port(
141        &self,
142        _req: &TableMakePortReq,
143    ) -> VirtualServerFuture<'_, Result<u32, Self::Error>> {
144        Box::pin(async { Ok(0) })
145    }
146
147    /// Returns the min and max values of a column in a view.
148    ///
149    /// Default implementation panics with "not implemented".
150    fn view_get_min_max(
151        &self,
152        _view_id: &str,
153        _column_name: &str,
154        _config: &crate::config::ViewConfig,
155    ) -> VirtualServerFuture<'_, Result<(crate::config::Scalar, crate::config::Scalar), Self::Error>>
156    {
157        Box::pin(async { unimplemented!("view_get_min_max not implemented") })
158    }
159
160    /// Expands the row at `row_index` of a `"rollup"` mode `group_by` view to
161    /// show its children, each collapsed, returning the number of rows shown.
162    fn view_expand(
163        &mut self,
164        _view_id: &str,
165        _config: &ViewConfig,
166        _row_index: u32,
167    ) -> VirtualServerFuture<'_, Result<u32, Self::Error>> {
168        Box::pin(async { unimplemented!("view_expand not implemented") })
169    }
170
171    /// Collapses the row at `row_index` of a `"rollup"` mode `group_by` view,
172    /// discarding its descendants' expansion, returning the number of rows
173    /// hidden.
174    fn view_collapse(
175        &mut self,
176        _view_id: &str,
177        _config: &ViewConfig,
178        _row_index: u32,
179    ) -> VirtualServerFuture<'_, Result<u32, Self::Error>> {
180        Box::pin(async { unimplemented!("view_collapse not implemented") })
181    }
182
183    /// Expands every row of a `"rollup"` mode `group_by` view whose path is no
184    /// longer than `depth` and collapses the rest.
185    fn view_set_depth(
186        &mut self,
187        _view_id: &str,
188        _config: &ViewConfig,
189        _depth: u32,
190    ) -> VirtualServerFuture<'_, Result<(), Self::Error>> {
191        Box::pin(async { unimplemented!("view_set_depth not implemented") })
192    }
193
194    // Unused
195
196    /// Creates a table from a view, with `schema` in place of the view's
197    /// columns when given.
198    ///
199    /// Default implementation panics with "not implemented".
200    fn view_make_table(
201        &mut self,
202        _view_id: &str,
203        _table_id: &str,
204        _config: &ViewConfig,
205        _schema: Option<&IndexMap<String, ColumnType>>,
206    ) -> VirtualServerFuture<'_, Result<(), Self::Error>> {
207        Box::pin(async { unimplemented!("view_make_table not implemented") })
208    }
209
210    /// Deletes a table made by [`Self::view_make_table`].
211    ///
212    /// Default implementation delegates to [`Self::view_delete`], for
213    /// backends which create both as the same kind of entity.
214    fn table_delete(&self, table_id: &str) -> VirtualServerFuture<'_, Result<(), Self::Error>> {
215        self.view_delete(table_id)
216    }
217
218    /// Creates a new table with the given data.
219    ///
220    /// Default implementation panics with "not implemented".
221    fn make_table(
222        &mut self,
223        _table_id: &str,
224        _data: &crate::proto::MakeTableData,
225    ) -> VirtualServerFuture<'_, Result<(), Self::Error>> {
226        Box::pin(async { unimplemented!("make_table not implemented") })
227    }
228}
229
230/// A [`VirtualServerHandler::table_describe`] implementation for backends with
231/// no cheaper native answer: build a real view on a private id, read its
232/// schema, delete it.
233pub async fn describe_via_make_view<H: VirtualServerHandler + ?Sized>(
234    handler: &mut H,
235    table_id: &str,
236    config: &ViewConfig,
237) -> Result<Result<Description, DescribeError>, H::Error> {
238    static COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
239    let view_id = format!(
240        "__psp_describe_{}__",
241        COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
242    );
243
244    let mut update: ViewConfigUpdate = config.clone().into();
245    let view_id = match handler
246        .table_make_view(table_id, &view_id, &mut update)
247        .await
248    {
249        Ok(view_id) => view_id,
250        Err(e) => return Ok(Err(DescribeError::Config(e.to_string()))),
251    };
252
253    let config: ViewConfig = update.into();
254    let schema = handler.view_schema(&view_id, &config).await;
255    let deleted = handler.view_delete(&view_id).await;
256    let schema = schema?;
257    deleted?;
258    let view_schema = schema
259        .iter()
260        .map(|(name, ty)| {
261            let source = column_path_source(name, &config)
262                .map(|(_, col)| col.to_string())
263                .unwrap_or_else(|| name.clone());
264
265            (source, *ty)
266        })
267        .collect::<std::collections::HashMap<_, _>>();
268
269    let expression_schema = config
270        .expressions
271        .keys()
272        .filter_map(|name| view_schema.get(name).map(|ty| (name.clone(), *ty)))
273        .collect();
274
275    Ok(Ok(Description {
276        expression_schema,
277        view_schema,
278    }))
279}