@perspective-dev/client 4.1.1 → 4.3.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.
- package/dist/cdn/perspective.js +2 -2
- package/dist/cdn/perspective.js.map +4 -4
- package/dist/esm/perspective.browser.d.ts +4 -0
- package/dist/esm/perspective.inline.js +2 -2
- package/dist/esm/perspective.inline.js.map +4 -4
- package/dist/esm/perspective.js +2 -2
- package/dist/esm/perspective.js.map +4 -4
- package/dist/esm/perspective.node.d.ts +9 -0
- package/dist/esm/perspective.node.js +1629 -1291
- package/dist/esm/perspective.node.js.map +3 -3
- package/dist/esm/ts-rs/GroupRollupMode.d.ts +1 -0
- package/dist/esm/ts-rs/ViewConfig.d.ts +2 -0
- package/dist/esm/ts-rs/ViewConfigUpdate.d.ts +2 -0
- package/dist/esm/ts-rs/ViewWindow.d.ts +0 -1
- package/dist/esm/virtual_server.d.ts +1 -8
- package/dist/esm/virtual_servers/clickhouse.js +2 -0
- package/dist/esm/virtual_servers/clickhouse.js.map +7 -0
- package/dist/esm/virtual_servers/duckdb.d.ts +22 -7
- package/dist/esm/virtual_servers/duckdb.js +1 -11
- package/dist/esm/virtual_servers/duckdb.js.map +3 -3
- package/dist/wasm/perspective-js.d.ts +723 -647
- package/dist/wasm/perspective-js.js +2115 -1841
- package/dist/wasm/perspective-js.wasm +0 -0
- package/dist/wasm/perspective-js.wasm.d.ts +61 -49
- package/package.json +2 -1
- package/src/rust/generic_sql_model.rs +189 -0
- package/src/rust/lib.rs +2 -2
- package/src/rust/utils/console_logger.rs +4 -4
- package/src/rust/utils/futures.rs +1 -6
- package/src/rust/virtual_server.rs +50 -46
- package/src/ts/perspective.browser.ts +15 -2
- package/src/ts/perspective.node.ts +21 -1
- package/src/ts/ts-rs/GroupRollupMode.ts +3 -0
- package/src/ts/ts-rs/ViewConfig.ts +2 -1
- package/src/ts/ts-rs/ViewConfigUpdate.ts +2 -1
- package/src/ts/ts-rs/ViewWindow.ts +1 -1
- package/src/ts/virtual_server.ts +4 -14
- package/src/ts/virtual_servers/clickhouse.ts +363 -0
- package/src/ts/virtual_servers/duckdb.ts +138 -300
- package/tsconfig.json +1 -0
|
@@ -10,10 +10,18 @@
|
|
|
10
10
|
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
/**
|
|
14
|
+
* An implementation of a Perspective Virtual Server for DuckDB.
|
|
15
|
+
*
|
|
16
|
+
* This import is optional, and so must be imported manually from either
|
|
17
|
+
* `@perspective-dev/client/dist/esm/virtual_servers/duckdb.js` or
|
|
18
|
+
* `@perspective-dev/client/src/ts/virtual_servers/duckdb.ts`, it is not
|
|
19
|
+
* exported from the package root `@perspective-dev/client`
|
|
20
|
+
*
|
|
21
|
+
* @module
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type * as perspective from "@perspective-dev/client";
|
|
17
25
|
import type { ColumnType } from "@perspective-dev/client/dist/esm/ts-rs/ColumnType.d.ts";
|
|
18
26
|
import type { ViewConfig } from "@perspective-dev/client/dist/esm/ts-rs/ViewConfig.d.ts";
|
|
19
27
|
import type { ViewWindow } from "@perspective-dev/client/dist/esm/ts-rs/ViewWindow.d.ts";
|
|
@@ -68,32 +76,51 @@ const FILTER_OPS = [
|
|
|
68
76
|
];
|
|
69
77
|
|
|
70
78
|
function duckdbTypeToPsp(name: string): ColumnType {
|
|
71
|
-
|
|
79
|
+
name = name.toLowerCase();
|
|
80
|
+
if (name === "varchar" || name == "utf8") {
|
|
81
|
+
return "string";
|
|
82
|
+
}
|
|
83
|
+
|
|
72
84
|
if (
|
|
73
|
-
name === "
|
|
74
|
-
name === "
|
|
75
|
-
name === "
|
|
76
|
-
name
|
|
77
|
-
|
|
85
|
+
name === "double" ||
|
|
86
|
+
name === "bigint" ||
|
|
87
|
+
name === "hugeint" ||
|
|
88
|
+
name === "float64" ||
|
|
89
|
+
name.startsWith("decimal")
|
|
90
|
+
) {
|
|
78
91
|
return "float";
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (name
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (name
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (name.startsWith("int")) {
|
|
95
|
+
return "integer";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (name.startsWith("date")) {
|
|
99
|
+
return "date";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (name.startsWith("bool")) {
|
|
103
|
+
return "boolean";
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (name.startsWith("timestamp")) {
|
|
107
|
+
return "datetime";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (name.startsWith("json")) {
|
|
111
|
+
return "string";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (name.startsWith("struct")) {
|
|
115
|
+
return "string";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
console.warn(`Unknown type '${name}'`);
|
|
119
|
+
return "string";
|
|
89
120
|
}
|
|
90
121
|
|
|
91
122
|
function convertDecimalToNumber(value: any, dtypeString: string) {
|
|
92
|
-
if (
|
|
93
|
-
value === null ||
|
|
94
|
-
value === undefined ||
|
|
95
|
-
!(value instanceof Uint32Array || value instanceof Int32Array)
|
|
96
|
-
) {
|
|
123
|
+
if (!(value instanceof Uint32Array || value instanceof Int32Array)) {
|
|
97
124
|
return value;
|
|
98
125
|
}
|
|
99
126
|
|
|
@@ -102,15 +129,9 @@ function convertDecimalToNumber(value: any, dtypeString: string) {
|
|
|
102
129
|
bigIntValue |= BigInt(value[i]) << BigInt(i * 32);
|
|
103
130
|
}
|
|
104
131
|
|
|
105
|
-
const maxInt128 = BigInt(2) ** BigInt(127);
|
|
106
|
-
if (bigIntValue >= maxInt128) {
|
|
107
|
-
bigIntValue -= BigInt(2) ** BigInt(128);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
132
|
const scaleMatch = dtypeString.match(/Decimal\[\d+e(\d+)\]/);
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (scale > 0) {
|
|
133
|
+
if (scaleMatch) {
|
|
134
|
+
const scale = parseInt(scaleMatch[1]);
|
|
114
135
|
return Number(bigIntValue) / Math.pow(10, scale);
|
|
115
136
|
} else {
|
|
116
137
|
return Number(bigIntValue);
|
|
@@ -130,7 +151,7 @@ async function runQuery(
|
|
|
130
151
|
async function runQuery(
|
|
131
152
|
db: duckdb.AsyncDuckDBConnection,
|
|
132
153
|
query: string,
|
|
133
|
-
options?: { columns:
|
|
154
|
+
options?: { columns: false },
|
|
134
155
|
): Promise<any[]>;
|
|
135
156
|
|
|
136
157
|
async function runQuery(
|
|
@@ -139,7 +160,6 @@ async function runQuery(
|
|
|
139
160
|
options: { columns?: boolean } = {},
|
|
140
161
|
) {
|
|
141
162
|
query = query.replace(/\s+/g, " ").trim();
|
|
142
|
-
// console.log("Query:", query);
|
|
143
163
|
try {
|
|
144
164
|
const result = await db.query(query);
|
|
145
165
|
if (options.columns) {
|
|
@@ -158,11 +178,28 @@ async function runQuery(
|
|
|
158
178
|
}
|
|
159
179
|
}
|
|
160
180
|
|
|
161
|
-
|
|
181
|
+
/**
|
|
182
|
+
* An implementation of Perspective's Virtual Server for `@duckdb/duckdb-wasm`.
|
|
183
|
+
*/
|
|
184
|
+
export class DuckDBHandler implements perspective.VirtualServerHandler {
|
|
162
185
|
private db: duckdb.AsyncDuckDBConnection;
|
|
186
|
+
private sqlBuilder: perspective.GenericSQLVirtualServerModel;
|
|
187
|
+
constructor(db: duckdb.AsyncDuckDBConnection, mod?: typeof perspective) {
|
|
188
|
+
if (!mod) {
|
|
189
|
+
if (customElements) {
|
|
190
|
+
const viewer_class: any =
|
|
191
|
+
customElements.get("perspective-viewer");
|
|
192
|
+
if (viewer_class) {
|
|
193
|
+
mod = viewer_class.__wasm_module__;
|
|
194
|
+
} else {
|
|
195
|
+
throw new Error("Missing perspective-client.wasm");
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
}
|
|
199
|
+
}
|
|
163
200
|
|
|
164
|
-
constructor(db: duckdb.AsyncDuckDBConnection) {
|
|
165
201
|
this.db = db;
|
|
202
|
+
this.sqlBuilder = new mod!.GenericSQLVirtualServerModel();
|
|
166
203
|
}
|
|
167
204
|
|
|
168
205
|
getFeatures() {
|
|
@@ -171,6 +208,7 @@ export class DuckDBHandler implements VirtualServerHandler {
|
|
|
171
208
|
split_by: true,
|
|
172
209
|
sort: true,
|
|
173
210
|
expressions: true,
|
|
211
|
+
group_rollup_mode: ["rollup", "flat", "total"],
|
|
174
212
|
filter_ops: {
|
|
175
213
|
integer: FILTER_OPS,
|
|
176
214
|
float: FILTER_OPS,
|
|
@@ -191,20 +229,25 @@ export class DuckDBHandler implements VirtualServerHandler {
|
|
|
191
229
|
}
|
|
192
230
|
|
|
193
231
|
async getHostedTables() {
|
|
194
|
-
const
|
|
195
|
-
|
|
232
|
+
const query = this.sqlBuilder.getHostedTables();
|
|
233
|
+
const results = await runQuery(this.db, query);
|
|
234
|
+
return results.map((row) => {
|
|
235
|
+
const json = row.toJSON();
|
|
236
|
+
return `${json.database || "memory"}.${json.name}`;
|
|
237
|
+
});
|
|
196
238
|
}
|
|
197
239
|
|
|
198
|
-
async tableSchema(tableId: string) {
|
|
199
|
-
const query =
|
|
240
|
+
async tableSchema(tableId: string, config?: ViewConfig) {
|
|
241
|
+
const query = this.sqlBuilder.tableSchema(tableId);
|
|
200
242
|
const results = await runQuery(this.db, query);
|
|
201
243
|
const schema = {} as Record<string, ColumnType>;
|
|
202
244
|
for (const result of results) {
|
|
203
245
|
const res = result.toJSON();
|
|
204
246
|
const colName = res.column_name;
|
|
205
|
-
if (!colName.startsWith("__")
|
|
206
|
-
|
|
207
|
-
|
|
247
|
+
if (!colName.startsWith("__")) {
|
|
248
|
+
schema[colName] = duckdbTypeToPsp(
|
|
249
|
+
res.column_type,
|
|
250
|
+
) as ColumnType;
|
|
208
251
|
}
|
|
209
252
|
}
|
|
210
253
|
|
|
@@ -212,304 +255,99 @@ export class DuckDBHandler implements VirtualServerHandler {
|
|
|
212
255
|
}
|
|
213
256
|
|
|
214
257
|
async viewColumnSize(viewId: string, config: ViewConfig) {
|
|
215
|
-
const query =
|
|
258
|
+
const query = this.sqlBuilder.viewColumnSize(viewId);
|
|
216
259
|
const results = await runQuery(this.db, query);
|
|
217
|
-
const gs = config.group_by?.length || 0;
|
|
218
260
|
const count = Number(Object.values(results[0].toJSON())[0]);
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
);
|
|
261
|
+
const gs = config.group_by?.length || 0;
|
|
262
|
+
const is_flat = config.group_rollup_mode === "flat";
|
|
263
|
+
return count - (gs === 0 ? 0 : is_flat ? gs : gs + 1);
|
|
223
264
|
}
|
|
224
265
|
|
|
225
266
|
async tableSize(tableId: string) {
|
|
226
|
-
const query =
|
|
267
|
+
const query = this.sqlBuilder.tableSize(tableId);
|
|
227
268
|
const results = await runQuery(this.db, query);
|
|
228
269
|
return Number(results[0].toJSON()["count_star()"]);
|
|
229
270
|
}
|
|
230
271
|
|
|
231
|
-
// async viewSchema(viewId: string, config: ViewConfig) {
|
|
232
|
-
// return this.tableSchema(viewId);
|
|
233
|
-
// }
|
|
234
|
-
|
|
235
|
-
// async viewSize(viewId: string) {
|
|
236
|
-
// return this.tableSize(viewId);
|
|
237
|
-
// }
|
|
238
|
-
|
|
239
272
|
async tableMakeView(tableId: string, viewId: string, config: ViewConfig) {
|
|
240
|
-
const
|
|
241
|
-
const group_by = config.group_by || [];
|
|
242
|
-
const split_by = config.split_by || [];
|
|
243
|
-
const aggregates = config.aggregates || {};
|
|
244
|
-
const sort = config.sort || [];
|
|
245
|
-
const expressions = config.expressions || {};
|
|
246
|
-
const filter = config.filter || [];
|
|
247
|
-
|
|
248
|
-
const colName = (col: string) => {
|
|
249
|
-
const expr = expressions[col];
|
|
250
|
-
return expr || `"${col}"`;
|
|
251
|
-
};
|
|
252
|
-
|
|
253
|
-
const getAggregate = (col: string) => aggregates[col] || null;
|
|
254
|
-
|
|
255
|
-
const generateSelectClauses = () => {
|
|
256
|
-
const clauses = [];
|
|
257
|
-
if (group_by.length > 0) {
|
|
258
|
-
for (const col of columns) {
|
|
259
|
-
if (col !== null) {
|
|
260
|
-
// TODO texodus
|
|
261
|
-
const agg = getAggregate(col) || "any_value";
|
|
262
|
-
clauses.push(`${agg}(${colName(col)}) as "${col}"`);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (split_by.length === 0) {
|
|
267
|
-
for (let idx = 0; idx < group_by.length; idx++) {
|
|
268
|
-
clauses.push(
|
|
269
|
-
`${colName(group_by[idx])} as __ROW_PATH_${idx}__`,
|
|
270
|
-
);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
const groups = group_by.map(colName).join(", ");
|
|
274
|
-
clauses.push(`GROUPING_ID(${groups}) AS __GROUPING_ID__`);
|
|
275
|
-
}
|
|
276
|
-
} else if (columns.length > 0) {
|
|
277
|
-
for (const col of columns) {
|
|
278
|
-
if (col !== null) {
|
|
279
|
-
// TODO texodus
|
|
280
|
-
clauses.push(
|
|
281
|
-
`${colName(col)} as "${col.replace(/"/g, '""')}"`,
|
|
282
|
-
);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
return clauses;
|
|
288
|
-
};
|
|
289
|
-
|
|
290
|
-
const orderByClauses = [];
|
|
291
|
-
const windowClauses = [];
|
|
292
|
-
const whereClauses = [];
|
|
293
|
-
|
|
294
|
-
if (group_by.length > 0) {
|
|
295
|
-
for (let gidx = 0; gidx < group_by.length; gidx++) {
|
|
296
|
-
const groups = group_by
|
|
297
|
-
.slice(0, gidx + 1)
|
|
298
|
-
.map(colName)
|
|
299
|
-
.join(", ");
|
|
300
|
-
if (split_by.length === 0) {
|
|
301
|
-
orderByClauses.push(`GROUPING_ID(${groups}) DESC`);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
for (const [sort_col, sort_dir] of sort) {
|
|
305
|
-
if (sort_dir !== "none") {
|
|
306
|
-
const agg = getAggregate(sort_col) || "any_value";
|
|
307
|
-
if (gidx >= group_by.length - 1) {
|
|
308
|
-
orderByClauses.push(
|
|
309
|
-
`${agg}(${colName(sort_col)}) ${sort_dir}`,
|
|
310
|
-
);
|
|
311
|
-
} else {
|
|
312
|
-
orderByClauses.push(
|
|
313
|
-
`first(${agg}(${colName(sort_col)})) OVER __WINDOW_${gidx}__ ${sort_dir}`,
|
|
314
|
-
);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
orderByClauses.push(`__ROW_PATH_${gidx}__ ASC`);
|
|
320
|
-
}
|
|
321
|
-
} else {
|
|
322
|
-
for (const [sort_col, sort_dir] of sort) {
|
|
323
|
-
if (sort_dir) {
|
|
324
|
-
orderByClauses.push(`${colName(sort_col)} ${sort_dir}`);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
if (sort.length > 0 && group_by.length > 1) {
|
|
330
|
-
for (let gidx = 0; gidx < group_by.length - 1; gidx++) {
|
|
331
|
-
const partition = Array.from(
|
|
332
|
-
{ length: gidx + 1 },
|
|
333
|
-
(_, i) => `__ROW_PATH_${i}__`,
|
|
334
|
-
).join(", ");
|
|
335
|
-
const sub_groups = group_by
|
|
336
|
-
.slice(0, gidx + 1)
|
|
337
|
-
.map(colName)
|
|
338
|
-
.join(", ");
|
|
339
|
-
const groups = group_by.map(colName).join(", ");
|
|
340
|
-
windowClauses.push(
|
|
341
|
-
`__WINDOW_${gidx}__ AS (PARTITION BY GROUPING_ID(${sub_groups}), ${partition} ORDER BY ${groups})`,
|
|
342
|
-
);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
for (const [name, op, value] of filter) {
|
|
347
|
-
if (value !== null && value !== undefined) {
|
|
348
|
-
const term_lit =
|
|
349
|
-
typeof value === "string" ? `'${value}'` : String(value);
|
|
350
|
-
whereClauses.push(`${colName(name)} ${op} ${term_lit}`);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
let query;
|
|
355
|
-
if (split_by.length > 0) {
|
|
356
|
-
query = `SELECT * FROM ${tableId}`;
|
|
357
|
-
} else {
|
|
358
|
-
const selectClauses = generateSelectClauses();
|
|
359
|
-
query = `SELECT ${selectClauses.join(", ")} FROM ${tableId}`;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
if (whereClauses.length > 0) {
|
|
363
|
-
query = `${query} WHERE ${whereClauses.join(" AND ")}`;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
if (split_by.length > 0) {
|
|
367
|
-
const groups = group_by.map(colName).join(", ");
|
|
368
|
-
const group_aliases = group_by
|
|
369
|
-
.map((x, i) => `${colName(x)} AS __ROW_PATH_${i}__`)
|
|
370
|
-
.join(", ");
|
|
371
|
-
const pivotOn = split_by.map((c) => `"${c}"`).join(", ");
|
|
372
|
-
const pivotUsing = generateSelectClauses().join(", ");
|
|
373
|
-
|
|
374
|
-
query = `
|
|
375
|
-
SELECT * EXCLUDE (${groups}), ${group_aliases} FROM (
|
|
376
|
-
PIVOT (${query})
|
|
377
|
-
ON ${pivotOn}
|
|
378
|
-
USING ${pivotUsing}
|
|
379
|
-
GROUP BY ${groups}
|
|
380
|
-
)
|
|
381
|
-
`;
|
|
382
|
-
} else if (group_by.length > 0) {
|
|
383
|
-
const groups = group_by.map(colName).join(", ");
|
|
384
|
-
query = `${query} GROUP BY ROLLUP(${groups})`;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
if (windowClauses.length > 0) {
|
|
388
|
-
query = `${query} WINDOW ${windowClauses.join(", ")}`;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
if (orderByClauses.length > 0) {
|
|
392
|
-
query = `${query} ORDER BY ${orderByClauses.join(", ")}`;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
query = `CREATE TABLE ${viewId} AS (${query})`;
|
|
273
|
+
const query = this.sqlBuilder.tableMakeView(tableId, viewId, config);
|
|
396
274
|
await runQuery(this.db, query);
|
|
397
275
|
}
|
|
398
276
|
|
|
399
277
|
async tableValidateExpression(tableId: string, expression: string) {
|
|
400
|
-
const query =
|
|
278
|
+
const query = this.sqlBuilder.tableValidateExpression(
|
|
279
|
+
tableId,
|
|
280
|
+
expression,
|
|
281
|
+
);
|
|
401
282
|
const results = await runQuery(this.db, query);
|
|
402
|
-
return duckdbTypeToPsp(
|
|
283
|
+
return duckdbTypeToPsp(
|
|
284
|
+
results[0].toJSON()["column_type"],
|
|
285
|
+
) as ColumnType;
|
|
403
286
|
}
|
|
404
287
|
|
|
405
288
|
async viewDelete(viewId: string) {
|
|
406
|
-
const query =
|
|
289
|
+
const query = this.sqlBuilder.viewDelete(viewId);
|
|
407
290
|
await runQuery(this.db, query);
|
|
408
291
|
}
|
|
409
292
|
|
|
410
293
|
async viewGetData(
|
|
411
294
|
viewId: string,
|
|
412
295
|
config: ViewConfig,
|
|
296
|
+
schema: Record<string, ColumnType>,
|
|
413
297
|
viewport: ViewWindow,
|
|
414
|
-
dataSlice: VirtualDataSlice,
|
|
298
|
+
dataSlice: perspective.VirtualDataSlice,
|
|
415
299
|
) {
|
|
416
|
-
const
|
|
417
|
-
const
|
|
418
|
-
const
|
|
419
|
-
const
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
const schemaQuery = `DESCRIBE ${viewId}`;
|
|
429
|
-
const schemaResults = await runQuery(this.db, schemaQuery);
|
|
430
|
-
const columnTypes = new Map();
|
|
431
|
-
for (const result of schemaResults) {
|
|
432
|
-
const res = result.toJSON();
|
|
433
|
-
columnTypes.set(res.column_name, res.column_type);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
const dataColumns = Array.from(columnTypes.entries())
|
|
437
|
-
.filter(([colName]) => !colName.startsWith("__"))
|
|
438
|
-
.slice(start_col, end_col);
|
|
439
|
-
|
|
440
|
-
const groupByColsList = [];
|
|
441
|
-
if (group_by.length > 0) {
|
|
442
|
-
if (split_by.length === 0) {
|
|
443
|
-
groupByColsList.push("__GROUPING_ID__");
|
|
444
|
-
}
|
|
445
|
-
for (let idx = 0; idx < group_by.length; idx++) {
|
|
446
|
-
groupByColsList.push(`__ROW_PATH_${idx}__`);
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
const allColumns = [
|
|
451
|
-
...groupByColsList.map((col) => `"${col}"`),
|
|
452
|
-
...dataColumns.map(([colName]) => `"${colName}"`),
|
|
453
|
-
];
|
|
454
|
-
|
|
455
|
-
const query = `
|
|
456
|
-
SELECT ${allColumns.join(", ")}
|
|
457
|
-
FROM ${viewId} ${limit}
|
|
458
|
-
`;
|
|
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
|
+
const query = this.sqlBuilder.viewGetData(
|
|
305
|
+
viewId,
|
|
306
|
+
config,
|
|
307
|
+
viewport,
|
|
308
|
+
schema,
|
|
309
|
+
);
|
|
459
310
|
|
|
460
311
|
const { rows, columns, dtypes } = await runQuery(this.db, query, {
|
|
461
312
|
columns: true,
|
|
462
313
|
});
|
|
463
314
|
|
|
464
315
|
for (let cidx = 0; cidx < columns.length; cidx++) {
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
if (cidx === 0 && group_by.length > 0 && split_by.length === 0) {
|
|
316
|
+
if (cidx === 0 && has_grouping_id) {
|
|
317
|
+
// This is the grouping_id column, skip it
|
|
468
318
|
continue;
|
|
469
319
|
}
|
|
470
320
|
|
|
471
|
-
let
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
if (row_path_match) {
|
|
475
|
-
group_by_index = parseInt(row_path_match[1]);
|
|
476
|
-
max_grouping_id = 2 ** (group_by.length - group_by_index) - 1;
|
|
321
|
+
let col = columns[cidx];
|
|
322
|
+
if (is_split_by && !col.startsWith("__")) {
|
|
323
|
+
col = col.replaceAll("_", "|");
|
|
477
324
|
}
|
|
478
325
|
|
|
479
|
-
const dtype = duckdbTypeToPsp(dtypes[cidx]);
|
|
326
|
+
const dtype = duckdbTypeToPsp(dtypes[cidx]) as ColumnType;
|
|
480
327
|
const isDecimal = dtypes[cidx].startsWith("Decimal");
|
|
481
|
-
const colName =
|
|
482
|
-
group_by_index !== null
|
|
483
|
-
? "__ROW_PATH__"
|
|
484
|
-
: col.replace(/_/g, "|");
|
|
485
|
-
|
|
486
328
|
for (let ridx = 0; ridx < rows.length; ridx++) {
|
|
487
|
-
const
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
let value = rowArray[cidx];
|
|
496
|
-
|
|
497
|
-
if (isDecimal) {
|
|
498
|
-
value = convertDecimalToNumber(value, dtypes[cidx]);
|
|
499
|
-
}
|
|
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
|
+
}
|
|
500
337
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
338
|
+
if (typeof value === "bigint") {
|
|
339
|
+
value = Number(value);
|
|
340
|
+
}
|
|
504
341
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
value
|
|
510
|
-
|
|
511
|
-
);
|
|
342
|
+
if (typeof value !== "string" && dtype === "string") {
|
|
343
|
+
try {
|
|
344
|
+
value = JSON.stringify(value);
|
|
345
|
+
} catch (e) {
|
|
346
|
+
value = `${value}`;
|
|
347
|
+
}
|
|
512
348
|
}
|
|
349
|
+
|
|
350
|
+
dataSlice.setCol(dtype, col, ridx, value, grouping_id);
|
|
513
351
|
}
|
|
514
352
|
}
|
|
515
353
|
}
|