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    // Unused
161
162    /// Creates a table from a view, with `schema` in place of the view's
163    /// columns when given.
164    ///
165    /// Default implementation panics with "not implemented".
166    fn view_make_table(
167        &mut self,
168        _view_id: &str,
169        _table_id: &str,
170        _config: &ViewConfig,
171        _schema: Option<&IndexMap<String, ColumnType>>,
172    ) -> VirtualServerFuture<'_, Result<(), Self::Error>> {
173        Box::pin(async { unimplemented!("view_make_table not implemented") })
174    }
175
176    /// Creates a new table with the given data.
177    ///
178    /// Default implementation panics with "not implemented".
179    fn make_table(
180        &mut self,
181        _table_id: &str,
182        _data: &crate::proto::MakeTableData,
183    ) -> VirtualServerFuture<'_, Result<(), Self::Error>> {
184        Box::pin(async { unimplemented!("make_table not implemented") })
185    }
186}
187
188/// A [`VirtualServerHandler::table_describe`] implementation for backends with
189/// no cheaper native answer: build a real view on a private id, read its
190/// schema, delete it.
191pub async fn describe_via_make_view<H: VirtualServerHandler + ?Sized>(
192    handler: &mut H,
193    table_id: &str,
194    config: &ViewConfig,
195) -> Result<Result<Description, DescribeError>, H::Error> {
196    static COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
197    let view_id = format!(
198        "__psp_describe_{}__",
199        COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
200    );
201
202    let mut update: ViewConfigUpdate = config.clone().into();
203    let view_id = match handler
204        .table_make_view(table_id, &view_id, &mut update)
205        .await
206    {
207        Ok(view_id) => view_id,
208        Err(e) => return Ok(Err(DescribeError::Config(e.to_string()))),
209    };
210
211    let config: ViewConfig = update.into();
212    let schema = handler.view_schema(&view_id, &config).await;
213    let deleted = handler.view_delete(&view_id).await;
214    let schema = schema?;
215    deleted?;
216    let view_schema = schema
217        .iter()
218        .map(|(name, ty)| {
219            let source = column_path_source(name, &config)
220                .map(|(_, col)| col.to_string())
221                .unwrap_or_else(|| name.clone());
222
223            (source, *ty)
224        })
225        .collect::<std::collections::HashMap<_, _>>();
226
227    let expression_schema = config
228        .expressions
229        .keys()
230        .filter_map(|name| view_schema.get(name).map(|ty| (name.clone(), *ty)))
231        .collect();
232
233    Ok(Ok(Description {
234        expression_schema,
235        view_schema,
236    }))
237}