1use 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 = "tablePath")]
57 pub fn table_path(&self, parts: Vec<String>) -> Result<String, JsValue> {
58 let parts = parts.iter().map(String::as_str).collect::<Vec<_>>();
59 self.inner
60 .table_path(&parts)
61 .map_err(|e| JsValue::from_str(&e.to_string()))
62 }
63
64 #[wasm_bindgen(js_name = "quoteEntity")]
66 pub fn quote_entity(&self, entity_id: &str) -> Result<String, JsValue> {
67 self.inner
68 .quote_entity(entity_id)
69 .map_err(|e| JsValue::from_str(&e.to_string()))
70 }
71
72 #[wasm_bindgen(js_name = "getHostedTables")]
74 pub fn get_hosted_tables(&self) -> Result<String, JsValue> {
75 self.inner
76 .get_hosted_tables()
77 .map_err(|e| JsValue::from_str(&e.to_string()))
78 }
79
80 #[wasm_bindgen(js_name = "tableSchema")]
82 pub fn table_schema(&self, table_id: &str) -> Result<String, JsValue> {
83 self.inner
84 .table_schema(table_id)
85 .map_err(|e| JsValue::from_str(&e.to_string()))
86 }
87
88 #[wasm_bindgen(js_name = "tableSize")]
90 pub fn table_size(&self, table_id: &str) -> Result<String, JsValue> {
91 self.inner
92 .table_size(table_id)
93 .map_err(|e| JsValue::from_str(&e.to_string()))
94 }
95
96 #[wasm_bindgen(js_name = "viewColumnSize")]
98 pub fn view_column_size(&self, view_id: &str) -> Result<String, JsValue> {
99 self.inner
100 .view_column_size(view_id)
101 .map_err(|e| JsValue::from_str(&e.to_string()))
102 }
103
104 #[wasm_bindgen(js_name = "expressionsDescribe")]
108 pub fn expressions_describe(
109 &self,
110 table_id: &str,
111 config: JsValue,
112 ) -> Result<Option<String>, JsValue> {
113 let config: ViewConfig = serde_wasm_bindgen::from_value(config)
114 .map_err(|e| JsValue::from_str(&e.to_string()))?;
115
116 self.inner
117 .expressions_describe(table_id, &config)
118 .map_err(|e| JsValue::from_str(&e.to_string()))
119 }
120
121 #[wasm_bindgen(js_name = "expressionDescribe")]
124 pub fn expression_describe(&self, table_id: &str, expression: &str) -> Result<String, JsValue> {
125 self.inner
126 .expression_describe(table_id, expression)
127 .map_err(|e| JsValue::from_str(&e.to_string()))
128 }
129
130 #[wasm_bindgen(js_name = "tableDescribe")]
135 pub fn table_describe(
136 &self,
137 table_id: &str,
138 config: JsValue,
139 schema: JsValue,
140 unordered: Option<bool>,
141 ) -> Result<Option<String>, JsValue> {
142 let config: ViewConfig = serde_wasm_bindgen::from_value(config)
143 .map_err(|e| JsValue::from_str(&e.to_string()))?;
144
145 let schema = if schema.is_undefined() || schema.is_null() {
146 IndexMap::new()
147 } else {
148 self.parse_schema(schema)?
149 };
150
151 let result = if unordered.unwrap_or(false) {
152 self.inner
153 .table_describe_unordered(table_id, &config, &schema)
154 } else {
155 self.inner.table_describe(table_id, &config, &schema)
156 };
157
158 result.map_err(|e| JsValue::from_str(&e.to_string()))
159 }
160
161 #[wasm_bindgen(js_name = "viewDelete")]
163 pub fn view_delete(&self, view_id: &str) -> Result<String, JsValue> {
164 self.inner
165 .view_delete(view_id)
166 .map_err(|e| JsValue::from_str(&e.to_string()))
167 }
168
169 #[wasm_bindgen(js_name = "tableMakeView")]
172 pub fn table_make_view(
173 &self,
174 table_id: &str,
175 view_id: &str,
176 config: JsValue,
177 schema: JsValue,
178 unordered: Option<bool>,
179 ) -> Result<String, JsValue> {
180 let config: ViewConfig = serde_wasm_bindgen::from_value(config)
181 .map_err(|e| JsValue::from_str(&e.to_string()))?;
182
183 let schema = if schema.is_undefined() || schema.is_null() {
184 IndexMap::new()
185 } else {
186 self.parse_schema(schema)?
187 };
188
189 let result = if unordered.unwrap_or(false) {
190 self.inner
191 .table_make_view_unordered(table_id, view_id, &config, &schema)
192 } else {
193 self.inner
194 .table_make_view(table_id, view_id, &config, &schema)
195 };
196
197 result.map_err(|e| JsValue::from_str(&e.to_string()))
198 }
199
200 #[wasm_bindgen(js_name = "viewMakeTable")]
202 pub fn view_make_table(
203 &self,
204 view_id: &str,
205 table_id: &str,
206 config: JsValue,
207 view_schema: JsValue,
208 schema: JsValue,
209 ) -> Result<String, JsValue> {
210 let config: ViewConfig = serde_wasm_bindgen::from_value(config)
211 .map_err(|e| JsValue::from_str(&e.to_string()))?;
212
213 let view_schema = self.parse_schema(view_schema)?;
214 let schema = if schema.is_undefined() || schema.is_null() {
215 None
216 } else {
217 Some(self.parse_schema(schema)?)
218 };
219
220 self.inner
221 .view_make_table(view_id, table_id, &config, &view_schema, schema.as_ref())
222 .map_err(|e| JsValue::from_str(&e.to_string()))
223 }
224
225 #[wasm_bindgen(js_name = "viewGetData")]
227 pub fn view_get_data(
228 &self,
229 view_id: &str,
230 config: JsValue,
231 viewport: JsValue,
232 schema: JsValue,
233 ) -> Result<String, JsValue> {
234 let config: ViewConfig = serde_wasm_bindgen::from_value(config)
235 .map_err(|e| JsValue::from_str(&e.to_string()))?;
236
237 let viewport: ViewPort = serde_wasm_bindgen::from_value(viewport)
238 .map_err(|e| JsValue::from_str(&e.to_string()))?;
239
240 let schema = self.parse_schema(schema)?;
241
242 self.inner
243 .view_get_data(view_id, &config, &viewport, &schema)
244 .map_err(|e| JsValue::from_str(&e.to_string()))
245 }
246
247 #[wasm_bindgen(js_name = "viewSchema")]
249 pub fn view_schema(&self, view_id: &str) -> Result<String, JsValue> {
250 self.inner
251 .view_schema(view_id)
252 .map_err(|e| JsValue::from_str(&e.to_string()))
253 }
254
255 #[wasm_bindgen(js_name = "viewSize")]
257 pub fn view_size(&self, view_id: &str) -> Result<String, JsValue> {
258 self.inner
259 .view_size(view_id)
260 .map_err(|e| JsValue::from_str(&e.to_string()))
261 }
262
263 #[wasm_bindgen(js_name = "viewGetMinMax")]
265 pub fn view_get_min_max(
266 &self,
267 view_id: &str,
268 column_name: &str,
269 config: JsValue,
270 ) -> Result<String, JsValue> {
271 let config: ViewConfig = serde_wasm_bindgen::from_value(config)
272 .map_err(|e| JsValue::from_str(&e.to_string()))?;
273
274 self.inner
275 .view_get_min_max(view_id, column_name, &config)
276 .map_err(|e| JsValue::from_str(&e.to_string()))
277 }
278
279 #[wasm_bindgen(js_name = "viewExpand")]
282 pub fn view_expand(
283 &self,
284 view_id: &str,
285 config: JsValue,
286 row_index: u32,
287 ) -> Result<String, JsValue> {
288 let config: ViewConfig = serde_wasm_bindgen::from_value(config)
289 .map_err(|e| JsValue::from_str(&e.to_string()))?;
290
291 self.inner
292 .view_expand(view_id, &config, row_index)
293 .map_err(|e| JsValue::from_str(&e.to_string()))
294 }
295
296 #[wasm_bindgen(js_name = "viewCollapse")]
299 pub fn view_collapse(
300 &self,
301 view_id: &str,
302 config: JsValue,
303 row_index: u32,
304 ) -> Result<String, JsValue> {
305 let config: ViewConfig = serde_wasm_bindgen::from_value(config)
306 .map_err(|e| JsValue::from_str(&e.to_string()))?;
307
308 self.inner
309 .view_collapse(view_id, &config, row_index)
310 .map_err(|e| JsValue::from_str(&e.to_string()))
311 }
312
313 #[wasm_bindgen(js_name = "viewSetDepth")]
316 pub fn view_set_depth(
317 &self,
318 view_id: &str,
319 config: JsValue,
320 depth: u32,
321 ) -> Result<String, JsValue> {
322 let config: ViewConfig = serde_wasm_bindgen::from_value(config)
323 .map_err(|e| JsValue::from_str(&e.to_string()))?;
324
325 self.inner
326 .view_set_depth(view_id, &config, depth)
327 .map_err(|e| JsValue::from_str(&e.to_string()))
328 }
329}
330
331impl GenericSQLVirtualServerModel {
332 fn parse_schema(&self, schema: JsValue) -> Result<IndexMap<String, ColumnType>, JsValue> {
333 let obj = schema.dyn_ref::<Object>().ok_or_else(|| {
334 JsValue::from_str("Schema must be an object mapping column names to types")
335 })?;
336
337 let mut result = IndexMap::new();
338 let entries = Object::entries(obj);
339 for i in 0..entries.length() {
340 let entry = entries.get(i);
341 let entry_array = entry
342 .dyn_ref::<js_sys::Array>()
343 .ok_or_else(|| JsValue::from_str("Invalid schema entry"))?;
344 let key = entry_array
345 .get(0)
346 .as_string()
347 .ok_or_else(|| JsValue::from_str("Column name must be a string"))?;
348 let value = entry_array
349 .get(1)
350 .as_string()
351 .ok_or_else(|| JsValue::from_str("Column type must be a string"))?;
352 let column_type = ColumnType::from_str(&value)
353 .map_err(|_| JsValue::from_str(&format!("Unknown column type: {}", value)))?;
354 result.insert(key, column_type);
355 }
356 Ok(result)
357 }
358}