@perspective-dev/client 4.3.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 (35) 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/JoinOptions.d.ts +9 -0
  11. package/dist/esm/ts-rs/JoinType.d.ts +1 -0
  12. package/dist/esm/virtual_server.d.ts +7 -0
  13. package/dist/esm/virtual_servers/clickhouse.js +1 -1
  14. package/dist/esm/virtual_servers/clickhouse.js.map +3 -3
  15. package/dist/esm/virtual_servers/duckdb.d.ts +4 -0
  16. package/dist/esm/virtual_servers/duckdb.js +1 -1
  17. package/dist/esm/virtual_servers/duckdb.js.map +3 -3
  18. package/dist/wasm/perspective-js.d.ts +41 -5
  19. package/dist/wasm/perspective-js.js +89 -10
  20. package/dist/wasm/perspective-js.wasm +0 -0
  21. package/dist/wasm/perspective-js.wasm.d.ts +8 -5
  22. package/package.json +5 -5
  23. package/src/rust/client.rs +70 -1
  24. package/src/rust/generic_sql_model.rs +16 -0
  25. package/src/rust/lib.rs +4 -0
  26. package/src/rust/utils/errors.rs +4 -0
  27. package/src/rust/utils/futures.rs +4 -0
  28. package/src/rust/view.rs +13 -4
  29. package/src/rust/virtual_server.rs +68 -1
  30. package/src/ts/perspective.node.ts +18 -0
  31. package/src/ts/ts-rs/JoinOptions.ts +7 -0
  32. package/src/ts/ts-rs/JoinType.ts +3 -0
  33. package/src/ts/virtual_server.ts +5 -0
  34. package/src/ts/virtual_servers/clickhouse.ts +2 -13
  35. package/src/ts/virtual_servers/duckdb.ts +18 -64
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@perspective-dev/client",
3
- "version": "4.3.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>
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,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";
@@ -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,
@@ -309,8 +309,6 @@ export class ClickhouseHandler implements perspective.VirtualServerHandler {
309
309
  viewport: ViewWindow,
310
310
  dataSlice: perspective.VirtualDataSlice,
311
311
  ) {
312
- const is_group_by = config.group_by?.length > 0;
313
- const is_split_by = config.split_by?.length > 0;
314
312
  const query = this.sqlBuilder.viewGetData(
315
313
  viewId,
316
314
  config,
@@ -323,23 +321,14 @@ export class ClickhouseHandler implements perspective.VirtualServerHandler {
323
321
  });
324
322
 
325
323
  for (let cidx = 0; cidx < columns.length; cidx++) {
326
- if (cidx === 0 && is_group_by && !is_split_by) {
327
- // This is the grouping_id column, skip it
328
- continue;
329
- }
330
-
331
- let col = columns[cidx];
332
- if (is_split_by && !col.startsWith("__ROW_PATH_")) {
333
- col = col.replaceAll("_", "|");
334
- }
335
-
324
+ const col = columns[cidx];
336
325
  const dtype = duckdbTypeToPsp(dtypes[cidx]) as ColumnType;
337
326
 
338
327
  const isDecimal = dtypes[cidx].startsWith("Decimal");
339
328
  for (let ridx = 0; ridx < rows.length; ridx++) {
340
329
  const row = rows[ridx];
341
330
  const grouping_id = row["__GROUPING_ID__"];
342
- let value = row[columns[cidx]];
331
+ let value = row[col];
343
332
  if (isDecimal) {
344
333
  value = convertDecimalToNumber(value, dtypes[cidx]);
345
334
  }
@@ -119,25 +119,6 @@ function duckdbTypeToPsp(name: string): ColumnType {
119
119
  return "string";
120
120
  }
121
121
 
122
- function convertDecimalToNumber(value: any, dtypeString: string) {
123
- if (!(value instanceof Uint32Array || value instanceof Int32Array)) {
124
- return value;
125
- }
126
-
127
- let bigIntValue = BigInt(0);
128
- for (let i = 0; i < value.length; i++) {
129
- bigIntValue |= BigInt(value[i]) << BigInt(i * 32);
130
- }
131
-
132
- const scaleMatch = dtypeString.match(/Decimal\[\d+e(\d+)\]/);
133
- if (scaleMatch) {
134
- const scale = parseInt(scaleMatch[1]);
135
- return Number(bigIntValue) / Math.pow(10, scale);
136
- } else {
137
- return Number(bigIntValue);
138
- }
139
- }
140
-
141
122
  async function runQuery(
142
123
  db: duckdb.AsyncDuckDBConnection,
143
124
  query: string,
@@ -290,6 +271,20 @@ export class DuckDBHandler implements perspective.VirtualServerHandler {
290
271
  await runQuery(this.db, query);
291
272
  }
292
273
 
274
+ async viewGetMinMax(
275
+ viewId: string,
276
+ columnName: string,
277
+ config: ViewConfig,
278
+ ) {
279
+ const query = this.sqlBuilder.viewGetMinMax(viewId, columnName, config);
280
+ const results = await runQuery(this.db, query);
281
+ const row = results[0].toJSON();
282
+ let [min, max] = Object.values(row);
283
+ if (typeof min === "bigint") min = Number(min);
284
+ if (typeof max === "bigint") max = Number(max);
285
+ return { min: min ?? null, max: max ?? null };
286
+ }
287
+
293
288
  async viewGetData(
294
289
  viewId: string,
295
290
  config: ViewConfig,
@@ -297,10 +292,6 @@ export class DuckDBHandler implements perspective.VirtualServerHandler {
297
292
  viewport: ViewWindow,
298
293
  dataSlice: perspective.VirtualDataSlice,
299
294
  ) {
300
- const is_group_by = config.group_by?.length > 0;
301
- const is_split_by = config.split_by?.length > 0;
302
- const is_flat = config.group_rollup_mode === "flat";
303
- const has_grouping_id = is_group_by && !is_flat;
304
295
  const query = this.sqlBuilder.viewGetData(
305
296
  viewId,
306
297
  config,
@@ -308,47 +299,10 @@ export class DuckDBHandler implements perspective.VirtualServerHandler {
308
299
  schema,
309
300
  );
310
301
 
311
- const { rows, columns, dtypes } = await runQuery(this.db, query, {
312
- columns: true,
313
- });
314
-
315
- for (let cidx = 0; cidx < columns.length; cidx++) {
316
- if (cidx === 0 && has_grouping_id) {
317
- // This is the grouping_id column, skip it
318
- continue;
319
- }
320
-
321
- let col = columns[cidx];
322
- if (is_split_by && !col.startsWith("__")) {
323
- col = col.replaceAll("_", "|");
324
- }
325
-
326
- const dtype = duckdbTypeToPsp(dtypes[cidx]) as ColumnType;
327
- const isDecimal = dtypes[cidx].startsWith("Decimal");
328
- for (let ridx = 0; ridx < rows.length; ridx++) {
329
- const rowArray = rows[ridx].toArray();
330
- const grouping_id = has_grouping_id
331
- ? Number(rowArray[0])
332
- : undefined;
333
- let value = rowArray[cidx];
334
- if (isDecimal) {
335
- value = convertDecimalToNumber(value, dtypes[cidx]);
336
- }
337
-
338
- if (typeof value === "bigint") {
339
- value = Number(value);
340
- }
341
-
342
- if (typeof value !== "string" && dtype === "string") {
343
- try {
344
- value = JSON.stringify(value);
345
- } catch (e) {
346
- value = `${value}`;
347
- }
348
- }
302
+ const ipc = await this.db.useUnsafe((bindings, conn) =>
303
+ bindings.runQuery(conn, query),
304
+ );
349
305
 
350
- dataSlice.setCol(dtype, col, ridx, value, grouping_id);
351
- }
352
- }
306
+ dataSlice.fromArrowIpc(ipc);
353
307
  }
354
308
  }