perspective_js/
generic_sql_model.rs1use std::str::FromStr;
16
17use indexmap::IndexMap;
18use js_sys::Object;
19use perspective_client::config::ViewConfig;
20use perspective_client::proto::{ColumnType, ViewPort};
21use perspective_client::virtual_server;
22use wasm_bindgen::prelude::*;
23
24use crate::utils::*;
25
26#[wasm_bindgen]
31pub struct GenericSQLVirtualServerModel {
32 inner: virtual_server::GenericSQLVirtualServerModel,
33}
34
35#[wasm_bindgen]
36extern "C" {
37 pub type JsGenericSQLVirtualServerModelArgs;
38}
39
40#[wasm_bindgen]
41impl GenericSQLVirtualServerModel {
42 #[wasm_bindgen(constructor)]
44 pub fn new(args: Option<JsGenericSQLVirtualServerModelArgs>) -> Result<Self, JsValue> {
45 Ok(Self {
46 inner: virtual_server::GenericSQLVirtualServerModel::new(
47 args.map(|x| x.into_serde_ext())
48 .transpose()?
49 .unwrap_or_default(),
50 ),
51 })
52 }
53
54 #[wasm_bindgen(js_name = "getHostedTables")]
56 pub fn get_hosted_tables(&self) -> Result<String, JsValue> {
57 self.inner
58 .get_hosted_tables()
59 .map_err(|e| JsValue::from_str(&e.to_string()))
60 }
61
62 #[wasm_bindgen(js_name = "tableSchema")]
64 pub fn table_schema(&self, table_id: &str) -> Result<String, JsValue> {
65 self.inner
66 .table_schema(table_id)
67 .map_err(|e| JsValue::from_str(&e.to_string()))
68 }
69
70 #[wasm_bindgen(js_name = "tableSize")]
72 pub fn table_size(&self, table_id: &str) -> Result<String, JsValue> {
73 self.inner
74 .table_size(table_id)
75 .map_err(|e| JsValue::from_str(&e.to_string()))
76 }
77
78 #[wasm_bindgen(js_name = "viewColumnSize")]
80 pub fn view_column_size(&self, view_id: &str) -> Result<String, JsValue> {
81 self.inner
82 .view_column_size(view_id)
83 .map_err(|e| JsValue::from_str(&e.to_string()))
84 }
85
86 #[wasm_bindgen(js_name = "expressionsDescribe")]
90 pub fn expressions_describe(
91 &self,
92 table_id: &str,
93 config: JsValue,
94 ) -> Result<Option<String>, JsValue> {
95 let config: ViewConfig = serde_wasm_bindgen::from_value(config)
96 .map_err(|e| JsValue::from_str(&e.to_string()))?;
97
98 self.inner
99 .expressions_describe(table_id, &config)
100 .map_err(|e| JsValue::from_str(&e.to_string()))
101 }
102
103 #[wasm_bindgen(js_name = "expressionDescribe")]
106 pub fn expression_describe(&self, table_id: &str, expression: &str) -> Result<String, JsValue> {
107 self.inner
108 .expression_describe(table_id, expression)
109 .map_err(|e| JsValue::from_str(&e.to_string()))
110 }
111
112 #[wasm_bindgen(js_name = "tableDescribe")]
117 pub fn table_describe(
118 &self,
119 table_id: &str,
120 config: JsValue,
121 schema: JsValue,
122 ) -> Result<Option<String>, JsValue> {
123 let config: ViewConfig = serde_wasm_bindgen::from_value(config)
124 .map_err(|e| JsValue::from_str(&e.to_string()))?;
125
126 let schema = if schema.is_undefined() || schema.is_null() {
127 IndexMap::new()
128 } else {
129 self.parse_schema(schema)?
130 };
131
132 self.inner
133 .table_describe(table_id, &config, &schema)
134 .map_err(|e| JsValue::from_str(&e.to_string()))
135 }
136
137 #[wasm_bindgen(js_name = "viewDelete")]
139 pub fn view_delete(&self, view_id: &str) -> Result<String, JsValue> {
140 self.inner
141 .view_delete(view_id)
142 .map_err(|e| JsValue::from_str(&e.to_string()))
143 }
144
145 #[wasm_bindgen(js_name = "tableMakeView")]
148 pub fn table_make_view(
149 &self,
150 table_id: &str,
151 view_id: &str,
152 config: JsValue,
153 schema: JsValue,
154 ) -> Result<String, JsValue> {
155 let config: ViewConfig = serde_wasm_bindgen::from_value(config)
156 .map_err(|e| JsValue::from_str(&e.to_string()))?;
157
158 let schema = if schema.is_undefined() || schema.is_null() {
159 IndexMap::new()
160 } else {
161 self.parse_schema(schema)?
162 };
163
164 self.inner
165 .table_make_view(table_id, view_id, &config, &schema)
166 .map_err(|e| JsValue::from_str(&e.to_string()))
167 }
168
169 #[wasm_bindgen(js_name = "viewMakeTable")]
171 pub fn view_make_table(
172 &self,
173 view_id: &str,
174 table_id: &str,
175 config: JsValue,
176 view_schema: JsValue,
177 schema: JsValue,
178 ) -> Result<String, JsValue> {
179 let config: ViewConfig = serde_wasm_bindgen::from_value(config)
180 .map_err(|e| JsValue::from_str(&e.to_string()))?;
181
182 let view_schema = self.parse_schema(view_schema)?;
183 let schema = if schema.is_undefined() || schema.is_null() {
184 None
185 } else {
186 Some(self.parse_schema(schema)?)
187 };
188
189 self.inner
190 .view_make_table(view_id, table_id, &config, &view_schema, schema.as_ref())
191 .map_err(|e| JsValue::from_str(&e.to_string()))
192 }
193
194 #[wasm_bindgen(js_name = "viewGetData")]
196 pub fn view_get_data(
197 &self,
198 view_id: &str,
199 config: JsValue,
200 viewport: JsValue,
201 schema: JsValue,
202 ) -> Result<String, JsValue> {
203 let config: ViewConfig = serde_wasm_bindgen::from_value(config)
204 .map_err(|e| JsValue::from_str(&e.to_string()))?;
205
206 let viewport: ViewPort = serde_wasm_bindgen::from_value(viewport)
207 .map_err(|e| JsValue::from_str(&e.to_string()))?;
208
209 let schema = self.parse_schema(schema)?;
210
211 self.inner
212 .view_get_data(view_id, &config, &viewport, &schema)
213 .map_err(|e| JsValue::from_str(&e.to_string()))
214 }
215
216 #[wasm_bindgen(js_name = "viewSchema")]
218 pub fn view_schema(&self, view_id: &str) -> Result<String, JsValue> {
219 self.inner
220 .view_schema(view_id)
221 .map_err(|e| JsValue::from_str(&e.to_string()))
222 }
223
224 #[wasm_bindgen(js_name = "viewSize")]
226 pub fn view_size(&self, view_id: &str) -> Result<String, JsValue> {
227 self.inner
228 .view_size(view_id)
229 .map_err(|e| JsValue::from_str(&e.to_string()))
230 }
231
232 #[wasm_bindgen(js_name = "viewGetMinMax")]
234 pub fn view_get_min_max(
235 &self,
236 view_id: &str,
237 column_name: &str,
238 config: JsValue,
239 ) -> Result<String, JsValue> {
240 let config: ViewConfig = serde_wasm_bindgen::from_value(config)
241 .map_err(|e| JsValue::from_str(&e.to_string()))?;
242
243 self.inner
244 .view_get_min_max(view_id, column_name, &config)
245 .map_err(|e| JsValue::from_str(&e.to_string()))
246 }
247}
248
249impl GenericSQLVirtualServerModel {
250 fn parse_schema(&self, schema: JsValue) -> Result<IndexMap<String, ColumnType>, JsValue> {
251 let obj = schema.dyn_ref::<Object>().ok_or_else(|| {
252 JsValue::from_str("Schema must be an object mapping column names to types")
253 })?;
254
255 let mut result = IndexMap::new();
256 let entries = Object::entries(obj);
257 for i in 0..entries.length() {
258 let entry = entries.get(i);
259 let entry_array = entry
260 .dyn_ref::<js_sys::Array>()
261 .ok_or_else(|| JsValue::from_str("Invalid schema entry"))?;
262 let key = entry_array
263 .get(0)
264 .as_string()
265 .ok_or_else(|| JsValue::from_str("Column name must be a string"))?;
266 let value = entry_array
267 .get(1)
268 .as_string()
269 .ok_or_else(|| JsValue::from_str("Column type must be a string"))?;
270 let column_type = ColumnType::from_str(&value)
271 .map_err(|_| JsValue::from_str(&format!("Unknown column type: {}", value)))?;
272 result.insert(key, column_type);
273 }
274 Ok(result)
275 }
276}