@perspective-dev/client 4.2.0 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/cdn/perspective.js +2 -2
  2. package/dist/cdn/perspective.js.map +3 -3
  3. package/dist/esm/perspective.inline.js +2 -2
  4. package/dist/esm/perspective.inline.js.map +3 -3
  5. package/dist/esm/perspective.js +2 -2
  6. package/dist/esm/perspective.js.map +3 -3
  7. package/dist/esm/perspective.node.d.ts +10 -0
  8. package/dist/esm/perspective.node.js +95 -9
  9. package/dist/esm/perspective.node.js.map +2 -2
  10. package/dist/esm/ts-rs/GroupRollupMode.d.ts +1 -0
  11. package/dist/esm/ts-rs/JoinOptions.d.ts +9 -0
  12. package/dist/esm/ts-rs/JoinType.d.ts +1 -0
  13. package/dist/esm/ts-rs/ViewConfig.d.ts +2 -0
  14. package/dist/esm/ts-rs/ViewConfigUpdate.d.ts +2 -0
  15. package/dist/esm/ts-rs/ViewWindow.d.ts +0 -1
  16. package/dist/esm/virtual_server.d.ts +7 -0
  17. package/dist/esm/virtual_servers/clickhouse.js +1 -1
  18. package/dist/esm/virtual_servers/clickhouse.js.map +3 -3
  19. package/dist/esm/virtual_servers/duckdb.d.ts +5 -0
  20. package/dist/esm/virtual_servers/duckdb.js +1 -1
  21. package/dist/esm/virtual_servers/duckdb.js.map +3 -3
  22. package/dist/wasm/perspective-js.d.ts +41 -5
  23. package/dist/wasm/perspective-js.js +90 -11
  24. package/dist/wasm/perspective-js.wasm +0 -0
  25. package/dist/wasm/perspective-js.wasm.d.ts +8 -5
  26. package/package.json +5 -5
  27. package/src/rust/client.rs +70 -1
  28. package/src/rust/generic_sql_model.rs +16 -0
  29. package/src/rust/lib.rs +4 -0
  30. package/src/rust/utils/errors.rs +4 -0
  31. package/src/rust/utils/futures.rs +5 -6
  32. package/src/rust/view.rs +13 -4
  33. package/src/rust/virtual_server.rs +68 -1
  34. package/src/ts/perspective.node.ts +18 -0
  35. package/src/ts/ts-rs/GroupRollupMode.ts +3 -0
  36. package/src/ts/ts-rs/JoinOptions.ts +7 -0
  37. package/src/ts/ts-rs/JoinType.ts +3 -0
  38. package/src/ts/ts-rs/ViewConfig.ts +2 -1
  39. package/src/ts/ts-rs/ViewConfigUpdate.ts +2 -1
  40. package/src/ts/ts-rs/ViewWindow.ts +1 -1
  41. package/src/ts/virtual_server.ts +5 -0
  42. package/src/ts/virtual_servers/clickhouse.ts +3 -13
  43. package/src/ts/virtual_servers/duckdb.ts +39 -69
  44. package/tsconfig.json +1 -0
@@ -12,6 +12,7 @@ export const client___getClassname: (a: number, b: number) => void;
12
12
  export const client_get_hosted_table_names: (a: number) => number;
13
13
  export const client_handle_error: (a: number, b: number, c: number, d: number) => number;
14
14
  export const client_handle_response: (a: number, b: number) => number;
15
+ export const client_join: (a: number, b: number, c: number, d: number, e: number, f: number) => number;
15
16
  export const client_new: (a: number, b: number, c: number) => void;
16
17
  export const client_new_proxy_session: (a: number, b: number) => number;
17
18
  export const client_on_error: (a: number, b: number) => number;
@@ -30,6 +31,7 @@ export const genericsqlvirtualservermodel_tableValidateExpression: (a: number, b
30
31
  export const genericsqlvirtualservermodel_viewColumnSize: (a: number, b: number, c: number, d: number) => void;
31
32
  export const genericsqlvirtualservermodel_viewDelete: (a: number, b: number, c: number, d: number) => void;
32
33
  export const genericsqlvirtualservermodel_viewGetData: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
34
+ export const genericsqlvirtualservermodel_viewGetMinMax: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
33
35
  export const genericsqlvirtualservermodel_viewSchema: (a: number, b: number, c: number, d: number) => void;
34
36
  export const genericsqlvirtualservermodel_viewSize: (a: number, b: number, c: number, d: number) => void;
35
37
  export const init: () => void;
@@ -78,6 +80,7 @@ export const view_to_csv: (a: number, b: number) => number;
78
80
  export const view_to_json: (a: number, b: number) => number;
79
81
  export const view_to_json_string: (a: number, b: number) => number;
80
82
  export const view_to_ndjson: (a: number, b: number) => number;
83
+ export const virtualdataslice_fromArrowIpc: (a: number, b: number, c: number) => void;
81
84
  export const virtualdataslice_new: (a: number) => number;
82
85
  export const virtualdataslice_setBooleanCol: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
83
86
  export const virtualdataslice_setCol: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number) => void;
@@ -87,11 +90,11 @@ export const virtualdataslice_setIntegerCol: (a: number, b: number, c: number, d
87
90
  export const virtualdataslice_setStringCol: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
88
91
  export const virtualserver_handleRequest: (a: number, b: number, c: number) => number;
89
92
  export const virtualserver_new: (a: number, b: number) => void;
90
- export const __wasm_bindgen_func_elem_1421: (a: number, b: number) => void;
91
- export const __wasm_bindgen_func_elem_3793: (a: number, b: number) => void;
92
- export const __wasm_bindgen_func_elem_5384: (a: number, b: number, c: number, d: number) => void;
93
- export const __wasm_bindgen_func_elem_3795: (a: number, b: number, c: number) => void;
94
- export const __wasm_bindgen_func_elem_1668: (a: number, b: number) => number;
93
+ export const __wasm_bindgen_func_elem_838: (a: number, b: number) => void;
94
+ export const __wasm_bindgen_func_elem_3910: (a: number, b: number) => void;
95
+ export const __wasm_bindgen_func_elem_10782: (a: number, b: number, c: number, d: number) => void;
96
+ export const __wasm_bindgen_func_elem_3927: (a: number, b: number, c: number) => void;
97
+ export const __wasm_bindgen_func_elem_1727: (a: number, b: number) => number;
95
98
  export const __wbindgen_export: (a: number, b: number) => number;
96
99
  export const __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
97
100
  export const __wbindgen_export3: (a: number) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@perspective-dev/client",
3
- "version": "4.2.0",
3
+ "version": "4.4.0",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
@@ -49,13 +49,13 @@
49
49
  "@perspective-dev/metadata": "",
50
50
  "@perspective-dev/test": "",
51
51
  "@clickhouse/client-web": "^1.12.0",
52
- "@duckdb/duckdb-wasm": "^1.30.0",
53
- "@playwright/experimental-ct-react": "=1.52.0",
54
- "@playwright/test": "=1.52.0",
52
+ "@duckdb/duckdb-wasm": "1.33.1-dev18.0",
53
+ "@playwright/experimental-ct-react": "=1.58.0",
54
+ "@playwright/test": "=1.58.0",
55
55
  "@types/node": ">=22",
56
56
  "@types/ws": ">=8",
57
57
  "@types/stoppable": ">=1",
58
- "apache-arrow": "18.1.0",
58
+ "apache-arrow": "17.0.0",
59
59
  "superstore-arrow": "3.2.0",
60
60
  "lodash": "^4.17.20",
61
61
  "moment": "^2.30.1",
@@ -20,7 +20,7 @@ use js_sys::{Function, Uint8Array};
20
20
  #[cfg(doc)]
21
21
  use perspective_client::SystemInfo;
22
22
  use perspective_client::{
23
- ClientError, ReconnectCallback, Session, TableData, TableInitOptions, asyncfn,
23
+ ClientError, ReconnectCallback, Session, TableData, TableInitOptions, TableRef, asyncfn,
24
24
  };
25
25
  use wasm_bindgen::prelude::*;
26
26
  use wasm_bindgen_derive::TryFromJsValue;
@@ -35,6 +35,35 @@ extern "C" {
35
35
  #[derive(Clone)]
36
36
  #[wasm_bindgen(typescript_type = "TableInitOptions")]
37
37
  pub type JsTableInitOptions;
38
+
39
+ #[derive(Clone)]
40
+ #[wasm_bindgen(typescript_type = "JoinOptions")]
41
+ pub type JsJoinOptions;
42
+ }
43
+
44
+ async fn js_to_table_ref(val: &JsValue) -> ApiResult<TableRef> {
45
+ if let Some(name) = val.as_string() {
46
+ Ok(TableRef::from(name))
47
+ } else {
48
+ let get_name = js_sys::Reflect::get(val, &wasm_bindgen::intern("get_name").into())
49
+ .map_err(|_| apierror!(TableRefError))?
50
+ .dyn_into::<js_sys::Function>()
51
+ .map_err(|_| apierror!(TableRefError))?;
52
+
53
+ let promise = get_name
54
+ .call0(val)
55
+ .map_err(|_| apierror!(TableRefError))?
56
+ .dyn_into::<js_sys::Promise>()
57
+ .map_err(|_| apierror!(TableRefError))?;
58
+
59
+ let name = wasm_bindgen_futures::JsFuture::from(promise)
60
+ .await
61
+ .map_err(|_| apierror!(TableRefError))?
62
+ .as_string()
63
+ .ok_or_else(|| apierror!(TableRefError))?;
64
+
65
+ Ok(TableRef::from(name))
66
+ }
38
67
  }
39
68
 
40
69
  #[wasm_bindgen]
@@ -382,6 +411,46 @@ impl Client {
382
411
  Ok(Table(self.client.table(args, options).await?))
383
412
  }
384
413
 
414
+ /// Creates a new read-only [`Table`] by performing an INNER JOIN on two
415
+ /// source tables. The resulting table is reactive: when either source
416
+ /// table is updated, the join is automatically recomputed.
417
+ ///
418
+ /// # Arguments
419
+ ///
420
+ /// - `left` - The left source table (a [`Table`] instance or a table name
421
+ /// string).
422
+ /// - `right` - The right source table (a [`Table`] instance or a table name
423
+ /// string).
424
+ /// - `on` - The column name to join on. Must exist in both tables with the
425
+ /// same type.
426
+ /// - `options` - Optional join configuration: `{ join_type?: "inner" |
427
+ /// "left" | "outer", name?: string }`.
428
+ ///
429
+ /// # JavaScript Examples
430
+ ///
431
+ /// ```javascript
432
+ /// const joined = await client.join(orders_table, products_table, "Product ID", { join_type: "left" });
433
+ /// const joined = await client.join("orders", "products", "Product ID", { join_type: "left" });
434
+ /// ```
435
+ #[wasm_bindgen]
436
+ pub async fn join(
437
+ &self,
438
+ left: JsValue,
439
+ right: JsValue,
440
+ on: &str,
441
+ options: Option<JsJoinOptions>,
442
+ ) -> ApiResult<Table> {
443
+ let options = options
444
+ .into_serde_ext::<Option<perspective_client::JoinOptions>>()?
445
+ .unwrap_or_default();
446
+
447
+ let left_ref = js_to_table_ref(&left).await?;
448
+ let right_ref = js_to_table_ref(&right).await?;
449
+ Ok(Table(
450
+ self.client.join(left_ref, right_ref, on, options).await?,
451
+ ))
452
+ }
453
+
385
454
  /// Terminates this [`Client`], cleaning up any [`crate::View`] handles the
386
455
  /// [`Client`] has open as well as its callbacks.
387
456
  #[wasm_bindgen]
@@ -157,6 +157,22 @@ impl GenericSQLVirtualServerModel {
157
157
  .view_size(view_id)
158
158
  .map_err(|e| JsValue::from_str(&e.to_string()))
159
159
  }
160
+
161
+ /// Returns the SQL query to get the min and max values of a column.
162
+ #[wasm_bindgen(js_name = "viewGetMinMax")]
163
+ pub fn view_get_min_max(
164
+ &self,
165
+ view_id: &str,
166
+ column_name: &str,
167
+ config: JsValue,
168
+ ) -> Result<String, JsValue> {
169
+ let config: ViewConfig = serde_wasm_bindgen::from_value(config)
170
+ .map_err(|e| JsValue::from_str(&e.to_string()))?;
171
+
172
+ self.inner
173
+ .view_get_min_max(view_id, column_name, &config)
174
+ .map_err(|e| JsValue::from_str(&e.to_string()))
175
+ }
160
176
  }
161
177
 
162
178
  impl GenericSQLVirtualServerModel {
package/src/rust/lib.rs CHANGED
@@ -59,11 +59,15 @@ export type * from "../../src/ts/ts-rs/SystemInfo.d.ts";
59
59
  export type * from "../../src/ts/ts-rs/SortDir.d.ts";
60
60
  export type * from "../../src/ts/ts-rs/Filter.d.ts";
61
61
  export type * from "../../src/ts/ts-rs/ViewConfig.d.ts";
62
+ export type * from "../../src/ts/ts-rs/JoinOptions.ts";
63
+ export type * from "../../src/ts/ts-rs/JoinType.ts";
62
64
 
63
65
  import type {ColumnWindow} from "../../src/ts/ts-rs/ColumnWindow.d.ts";
64
66
  import type {ColumnType} from "../../src/ts/ts-rs/ColumnType.d.ts";
65
67
  import type {ViewWindow} from "../../src/ts/ts-rs/ViewWindow.d.ts";
66
68
  import type {TableInitOptions} from "../../src/ts/ts-rs/TableInitOptions.d.ts";
69
+ import type {JoinOptions} from "../../src/ts/ts-rs/JoinOptions.ts";
70
+ import type {JoinType} from "../../src/ts/ts-rs/JoinType.ts";
67
71
  import type {ViewConfigUpdate} from "../../src/ts/ts-rs/ViewConfigUpdate.d.ts";
68
72
  import type * as on_update_args from "../../src/ts/ts-rs/ViewOnUpdateResp.d.ts";
69
73
  import type {OnUpdateOptions} from "../../src/ts/ts-rs/OnUpdateOptions.d.ts";
@@ -97,6 +97,9 @@ pub enum ApiErrorType {
97
97
  #[error("Invalid `expressions` {}", format_valid_exprs(.0))]
98
98
  InvalidViewerConfigExpressionsError(Rc<ExprValidationResult>),
99
99
 
100
+ #[error("Expected a Table or string table name")]
101
+ TableRefError,
102
+
100
103
  #[error("No `Table` attached")]
101
104
  NoTableError,
102
105
 
@@ -134,6 +137,7 @@ impl ApiError {
134
137
  ApiErrorType::ProstError(_) => "[ProstError]",
135
138
  ApiErrorType::InvalidViewerConfigError(..) => "[InvalidViewerConfigError]",
136
139
  ApiErrorType::InvalidViewerConfigExpressionsError(_) => "[InvalidViewerConfigError]",
140
+ ApiErrorType::TableRefError => "[TableRefError]",
137
141
  ApiErrorType::NoTableError => "[NoTableError]",
138
142
  ApiErrorType::SerdeWasmBindgenError(_) => "[SerdeWasmBindgenError]",
139
143
  ApiErrorType::Utf8Error(_) => "[FromUtf8Error]",
@@ -64,6 +64,10 @@ where
64
64
  pub fn spawn<U: Future<Output = ApiResult<T>> + 'static>(x: U) {
65
65
  drop(js_sys::Promise::from(Self::new(x)))
66
66
  }
67
+
68
+ pub fn spawn_throttled<U: Future<Output = ApiResult<T>> + 'static>(x: U) {
69
+ drop(js_sys::Promise::from(Self::new_throttled(x)))
70
+ }
67
71
  }
68
72
 
69
73
  impl<T> Default for ApiFuture<T>
@@ -90,12 +94,7 @@ where
90
94
  Result<T, JsValue>: IntoJsResult + 'static,
91
95
  {
92
96
  fn from(fut: ApiFuture<T>) -> Self {
93
- future_to_promise(async move {
94
- match fut.0.await.ignore_view_delete()? {
95
- Some(x) => Ok(x).into_js_result(),
96
- None => Err("View not found".into()).into_js_result(),
97
- }
98
- })
97
+ future_to_promise(async move { Ok(fut.0.await?).into_js_result() })
99
98
  }
100
99
  }
101
100
 
package/src/rust/view.rs CHANGED
@@ -47,6 +47,15 @@ impl From<ViewWindow> for JsViewWindow {
47
47
  }
48
48
  }
49
49
 
50
+ fn scalar_to_jsvalue(scalar: &perspective_client::config::Scalar) -> JsValue {
51
+ match scalar {
52
+ perspective_client::config::Scalar::Float(x) => JsValue::from_f64(*x),
53
+ perspective_client::config::Scalar::String(x) => JsValue::from_str(x),
54
+ perspective_client::config::Scalar::Bool(x) => JsValue::from_bool(*x),
55
+ perspective_client::config::Scalar::Null => JsValue::NULL,
56
+ }
57
+ }
58
+
50
59
  /// The [`View`] struct is Perspective's query and serialization interface. It
51
60
  /// represents a query on the `Table`'s dataset and is always created from an
52
61
  /// existing `Table` instance via the [`Table::view`] method.
@@ -147,10 +156,10 @@ impl View {
147
156
  #[wasm_bindgen]
148
157
  pub async fn get_min_max(&self, name: String) -> ApiResult<Array> {
149
158
  let result = self.0.get_min_max(name).await?;
150
- Ok([result.0, result.1]
151
- .iter()
152
- .map(|x| js_sys::JSON::parse(x))
153
- .collect::<Result<_, _>>()?)
159
+ let arr = Array::new();
160
+ arr.push(&scalar_to_jsvalue(&result.0));
161
+ arr.push(&scalar_to_jsvalue(&result.1));
162
+ Ok(arr)
154
163
  }
155
164
 
156
165
  /// The number of aggregated rows in this [`View`]. This is affected by the
@@ -18,7 +18,7 @@ use std::str::FromStr;
18
18
  use std::sync::{Arc, Mutex};
19
19
 
20
20
  use indexmap::IndexMap;
21
- use js_sys::{Array, Date, Object, Reflect};
21
+ use js_sys::{Array, Date, Object, Reflect, Uint8Array};
22
22
  use perspective_client::proto::{ColumnType, HostedTable};
23
23
  use perspective_client::virtual_server;
24
24
  use perspective_client::virtual_server::{Features, ResultExt, VirtualServerHandler};
@@ -60,6 +60,20 @@ impl From<serde_wasm_bindgen::Error> for JsError {
60
60
  }
61
61
  }
62
62
 
63
+ fn jsvalue_to_scalar(val: &JsValue) -> perspective_client::config::Scalar {
64
+ if val.is_null() || val.is_undefined() {
65
+ perspective_client::config::Scalar::Null
66
+ } else if let Some(b) = val.as_bool() {
67
+ perspective_client::config::Scalar::Bool(b)
68
+ } else if let Some(n) = val.as_f64() {
69
+ perspective_client::config::Scalar::Float(n)
70
+ } else if let Some(s) = val.as_string() {
71
+ perspective_client::config::Scalar::String(s)
72
+ } else {
73
+ perspective_client::config::Scalar::Null
74
+ }
75
+ }
76
+
63
77
  pub struct JsServerHandler(Object);
64
78
 
65
79
  impl JsServerHandler {
@@ -452,6 +466,48 @@ impl VirtualServerHandler for JsServerHandler {
452
466
  })
453
467
  }
454
468
 
469
+ fn view_get_min_max(
470
+ &self,
471
+ view_id: &str,
472
+ column_name: &str,
473
+ config: &perspective_client::config::ViewConfig,
474
+ ) -> HandlerFuture<
475
+ Result<
476
+ (
477
+ perspective_client::config::Scalar,
478
+ perspective_client::config::Scalar,
479
+ ),
480
+ Self::Error,
481
+ >,
482
+ > {
483
+ let has_method = Reflect::get(&self.0, &JsValue::from_str("viewGetMinMax"))
484
+ .map(|val| !val.is_undefined())
485
+ .unwrap_or(false);
486
+
487
+ if !has_method {
488
+ return Box::pin(async {
489
+ Err(JsError(JsValue::from_str("viewGetMinMax not implemented")))
490
+ });
491
+ }
492
+
493
+ let handler = self.0.clone();
494
+ let view_id = view_id.to_string();
495
+ let column_name = column_name.to_string();
496
+ let config_js = serde_wasm_bindgen::to_value(config).unwrap();
497
+ Box::pin(async move {
498
+ let this = JsServerHandler(handler);
499
+ let args = Array::new();
500
+ args.push(&JsValue::from_str(&view_id));
501
+ args.push(&JsValue::from_str(&column_name));
502
+ args.push(&config_js);
503
+ let result = this.call_method_js_async("viewGetMinMax", &args).await?;
504
+ let obj = result.dyn_ref::<Object>().unwrap();
505
+ let min_val = Reflect::get(obj, &JsValue::from_str(wasm_bindgen::intern("min")))?;
506
+ let max_val = Reflect::get(obj, &JsValue::from_str(wasm_bindgen::intern("max")))?;
507
+ Ok((jsvalue_to_scalar(&min_val), jsvalue_to_scalar(&max_val)))
508
+ })
509
+ }
510
+
455
511
  fn view_get_data(
456
512
  &self,
457
513
  view_id: &str,
@@ -531,6 +587,17 @@ impl VirtualDataSlice {
531
587
  )
532
588
  }
533
589
 
590
+ #[wasm_bindgen(js_name = "fromArrowIpc")]
591
+ pub fn from_arrow_ipc(&self, ipc: Uint8Array) -> Result<(), JsValue> {
592
+ self.1
593
+ .lock()
594
+ .unwrap()
595
+ .as_mut()
596
+ .unwrap()
597
+ .from_arrow_ipc(&ipc.to_vec())
598
+ .map_err(|e| JsValue::from_str(&e.to_string()))
599
+ }
600
+
534
601
  #[wasm_bindgen(js_name = "setCol")]
535
602
  pub fn set_col(
536
603
  &self,
@@ -273,6 +273,23 @@ export function on_error(callback: Function) {
273
273
  return SYNC_CLIENT.on_error(callback);
274
274
  }
275
275
 
276
+ /**
277
+ * Create a read-only table from a JOIN of two source tables.
278
+ * @param left - The left source table (a Table instance or a table name string).
279
+ * @param right - The right source table (a Table instance or a table name string).
280
+ * @param on
281
+ * @param options - Optional join configuration: { join_type?: "inner"|"left"|"outer", name?: string }
282
+ * @returns
283
+ */
284
+ export function join(
285
+ left: perspective_client.Table | string,
286
+ right: perspective_client.Table | string,
287
+ on: string,
288
+ options?: perspective_client.JoinOptions,
289
+ ) {
290
+ return SYNC_CLIENT.join(left as any, right as any, on, options);
291
+ }
292
+
276
293
  /**
277
294
  * Create a table from the global Perspective instance.
278
295
  * @param init_data
@@ -356,6 +373,7 @@ export { perspective_client as wasmModule };
356
373
 
357
374
  export default {
358
375
  table,
376
+ join,
359
377
  websocket,
360
378
  worker,
361
379
  get_hosted_table_names,
@@ -0,0 +1,3 @@
1
+ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
2
+
3
+ export type GroupRollupMode = "rollup" | "flat" | "total";
@@ -0,0 +1,7 @@
1
+ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
2
+ import type { JoinType } from "./JoinType.js";
3
+
4
+ /**
5
+ * Options for [`Client::join`].
6
+ */
7
+ export type JoinOptions = { join_type?: JoinType, name?: string, right_on?: string, };
@@ -0,0 +1,3 @@
1
+ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
2
+
3
+ export type JoinType = "inner" | "left" | "outer";
@@ -3,6 +3,7 @@ import type { Aggregate } from "./Aggregate.js";
3
3
  import type { Expressions } from "./Expressions.js";
4
4
  import type { Filter } from "./Filter.js";
5
5
  import type { FilterReducer } from "./FilterReducer.js";
6
+ import type { GroupRollupMode } from "./GroupRollupMode.js";
6
7
  import type { Sort } from "./Sort.js";
7
8
 
8
- export type ViewConfig = { group_by: Array<string>, split_by: Array<string>, sort: Array<Sort>, filter: Array<Filter>, filter_op?: FilterReducer, expressions: Expressions, columns: Array<string | null>, aggregates: { [key in string]?: Aggregate }, group_by_depth?: number | null, };
9
+ export type ViewConfig = { group_by: Array<string>, split_by: Array<string>, sort: Array<Sort>, filter: Array<Filter>, group_rollup_mode: GroupRollupMode, filter_op?: FilterReducer, expressions: Expressions, columns: Array<string | null>, aggregates: { [key in string]?: Aggregate }, group_by_depth?: number | null, };
@@ -3,6 +3,7 @@ import type { Aggregate } from "./Aggregate.js";
3
3
  import type { Expressions } from "./Expressions.js";
4
4
  import type { Filter } from "./Filter.js";
5
5
  import type { FilterReducer } from "./FilterReducer.js";
6
+ import type { GroupRollupMode } from "./GroupRollupMode.js";
6
7
  import type { Sort } from "./Sort.js";
7
8
 
8
9
  export type ViewConfigUpdate = {
@@ -84,4 +85,4 @@ expressions?: Expressions,
84
85
  * applied to columns in the `View` constructor using a dictionary of
85
86
  * column name to aggregate function name.
86
87
  */
87
- aggregates?: { [key in string]?: Aggregate }, group_by_depth?: number, filter_op?: FilterReducer, };
88
+ aggregates?: { [key in string]?: Aggregate }, group_by_depth?: number, filter_op?: FilterReducer, group_rollup_mode?: GroupRollupMode, };
@@ -6,7 +6,7 @@
6
6
  * Some fields of [`ViewWindow`] are only applicable to specific methods of
7
7
  * [`View`].
8
8
  */
9
- export type ViewWindow = { start_row?: number, start_col?: number, end_row?: number, end_col?: number, id?: boolean, index?: boolean, leaves_only?: boolean,
9
+ export type ViewWindow = { start_row?: number, start_col?: number, end_row?: number, end_col?: number, id?: boolean, index?: boolean,
10
10
  /**
11
11
  * Only impacts [`View::to_csv`]
12
12
  */
@@ -68,6 +68,11 @@ export interface VirtualServerHandler {
68
68
  tableId: string,
69
69
  expression: string,
70
70
  ): ColumnType | Promise<ColumnType>;
71
+ viewGetMinMax?(
72
+ viewId: string,
73
+ columnName: string,
74
+ config: ViewConfig,
75
+ ): { min: any; max: any } | Promise<{ min: any; max: any }>;
71
76
  getFeatures?(): ServerFeatures | Promise<ServerFeatures>;
72
77
  makeTable?(
73
78
  tableId: string,
@@ -223,6 +223,7 @@ export class ClickhouseHandler implements perspective.VirtualServerHandler {
223
223
  split_by: false,
224
224
  sort: true,
225
225
  expressions: true,
226
+ group_rollup_mode: ["rollup", "flat", "total"],
226
227
  filter_ops: {
227
228
  integer: FILTER_OPS,
228
229
  float: FILTER_OPS,
@@ -308,8 +309,6 @@ export class ClickhouseHandler implements perspective.VirtualServerHandler {
308
309
  viewport: ViewWindow,
309
310
  dataSlice: perspective.VirtualDataSlice,
310
311
  ) {
311
- const is_group_by = config.group_by?.length > 0;
312
- const is_split_by = config.split_by?.length > 0;
313
312
  const query = this.sqlBuilder.viewGetData(
314
313
  viewId,
315
314
  config,
@@ -322,23 +321,14 @@ export class ClickhouseHandler implements perspective.VirtualServerHandler {
322
321
  });
323
322
 
324
323
  for (let cidx = 0; cidx < columns.length; cidx++) {
325
- if (cidx === 0 && is_group_by && !is_split_by) {
326
- // This is the grouping_id column, skip it
327
- continue;
328
- }
329
-
330
- let col = columns[cidx];
331
- if (is_split_by && !col.startsWith("__ROW_PATH_")) {
332
- col = col.replaceAll("_", "|");
333
- }
334
-
324
+ const col = columns[cidx];
335
325
  const dtype = duckdbTypeToPsp(dtypes[cidx]) as ColumnType;
336
326
 
337
327
  const isDecimal = dtypes[cidx].startsWith("Decimal");
338
328
  for (let ridx = 0; ridx < rows.length; ridx++) {
339
329
  const row = rows[ridx];
340
330
  const grouping_id = row["__GROUPING_ID__"];
341
- let value = row[columns[cidx]];
331
+ let value = row[col];
342
332
  if (isDecimal) {
343
333
  value = convertDecimalToNumber(value, dtypes[cidx]);
344
334
  }