@uwdata/mosaic-core 0.7.1 → 0.8.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/mosaic-core.js +619 -463
- package/dist/mosaic-core.min.js +7 -7
- package/package.json +9 -6
- package/src/Coordinator.js +10 -4
- package/src/DataCubeIndexer.js +32 -16
- package/src/FilterGroup.js +7 -1
- package/src/MosaicClient.js +13 -5
- package/src/Param.js +4 -2
- package/src/QueryConsolidator.js +6 -0
- package/src/Selection.js +7 -6
- package/src/connectors/wasm.js +2 -2
- package/src/util/AsyncDispatch.js +15 -12
- package/src/util/convert-arrow.js +18 -30
- package/src/util/js-type.js +1 -0
- package/src/util/query-result.js +4 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uwdata/mosaic-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Scalable and extensible linked data views.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mosaic",
|
|
@@ -23,14 +23,17 @@
|
|
|
23
23
|
"scripts": {
|
|
24
24
|
"prebuild": "rimraf dist && mkdir dist",
|
|
25
25
|
"build": "node ../../esbuild.js mosaic-core",
|
|
26
|
-
"lint": "eslint src test
|
|
26
|
+
"lint": "eslint src test",
|
|
27
27
|
"test": "mocha 'test/**/*-test.js'",
|
|
28
28
|
"prepublishOnly": "npm run test && npm run lint && npm run build"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@duckdb/duckdb-wasm": "^1.28.1-
|
|
32
|
-
"@uwdata/mosaic-sql": "^0.
|
|
33
|
-
"apache-arrow": "^15.0.
|
|
31
|
+
"@duckdb/duckdb-wasm": "^1.28.1-dev194.0",
|
|
32
|
+
"@uwdata/mosaic-sql": "^0.8.0",
|
|
33
|
+
"apache-arrow": "^15.0.2"
|
|
34
34
|
},
|
|
35
|
-
"
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@uwdata/mosaic-duckdb": "^0.8.0"
|
|
37
|
+
},
|
|
38
|
+
"gitHead": "a24b4c9f7dfa1c38c6af96ec17e075326c1af9b0"
|
|
36
39
|
}
|
package/src/Coordinator.js
CHANGED
|
@@ -8,8 +8,7 @@ let _instance;
|
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Set or retrieve the coordinator instance.
|
|
11
|
-
*
|
|
12
|
-
* @param {Coordinator} instance the coordinator instance to set
|
|
11
|
+
* @param {Coordinator} [instance] the coordinator instance to set
|
|
13
12
|
* @returns {Coordinator} the coordinator instance
|
|
14
13
|
*/
|
|
15
14
|
export function coordinator(instance) {
|
|
@@ -42,7 +41,15 @@ export class Coordinator {
|
|
|
42
41
|
return this._logger;
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Set configuration options for this coordinator.
|
|
46
|
+
* @param {object} [options] Configration options.
|
|
47
|
+
* @param {boolean} [options.cache=true] Boolean flag to enable/disable query caching.
|
|
48
|
+
* @param {boolean} [options.consolidate=true] Boolean flag to enable/disable query consolidation.
|
|
49
|
+
* @param {boolean|object} [options.indexes=true] Boolean flag to enable/disable
|
|
50
|
+
* automatic data cube indexes or an index options object.
|
|
51
|
+
*/
|
|
52
|
+
configure({ cache = true, consolidate = true, indexes = true } = {}) {
|
|
46
53
|
this.manager.cache(cache);
|
|
47
54
|
this.manager.consolidate(consolidate);
|
|
48
55
|
this.indexes = indexes;
|
|
@@ -116,7 +123,6 @@ export class Coordinator {
|
|
|
116
123
|
|
|
117
124
|
/**
|
|
118
125
|
* Connect a client to the coordinator.
|
|
119
|
-
*
|
|
120
126
|
* @param {import('./MosaicClient.js').MosaicClient} client the client to disconnect
|
|
121
127
|
*/
|
|
122
128
|
async connect(client) {
|
package/src/DataCubeIndexer.js
CHANGED
|
@@ -54,7 +54,8 @@ export class DataCubeIndexer {
|
|
|
54
54
|
|
|
55
55
|
active = active || this.selection.active;
|
|
56
56
|
const { source } = active;
|
|
57
|
-
|
|
57
|
+
// exit early if indexes already set up for active view
|
|
58
|
+
if (source && source === this.activeView?.source) return true;
|
|
58
59
|
|
|
59
60
|
this.clear();
|
|
60
61
|
if (!source) return false; // nothing to work with
|
|
@@ -75,7 +76,7 @@ export class DataCubeIndexer {
|
|
|
75
76
|
|
|
76
77
|
// build index construction query
|
|
77
78
|
const query = client.query(sel.predicate(client))
|
|
78
|
-
.select({ ...activeView.columns, ...index.
|
|
79
|
+
.select({ ...activeView.columns, ...index.aux })
|
|
79
80
|
.groupby(Object.keys(activeView.columns));
|
|
80
81
|
|
|
81
82
|
// ensure active view columns are selected by subqueries
|
|
@@ -95,6 +96,9 @@ export class DataCubeIndexer {
|
|
|
95
96
|
const result = mc.exec(create(table, sql, { temp }));
|
|
96
97
|
indices.set(client, { table, result, order, ...index });
|
|
97
98
|
}
|
|
99
|
+
|
|
100
|
+
// index creation successful
|
|
101
|
+
return true;
|
|
98
102
|
}
|
|
99
103
|
|
|
100
104
|
async update() {
|
|
@@ -179,23 +183,40 @@ function getIndexColumns(client) {
|
|
|
179
183
|
|
|
180
184
|
const aggr = [];
|
|
181
185
|
const dims = [];
|
|
182
|
-
|
|
186
|
+
const aux = {}; // auxiliary columns needed by aggregates
|
|
187
|
+
let auxAs;
|
|
183
188
|
|
|
184
|
-
for (const
|
|
185
|
-
|
|
189
|
+
for (const entry of q.select()) {
|
|
190
|
+
const { as, expr: { aggregate, args } } = entry;
|
|
191
|
+
const op = aggregate?.toUpperCase?.();
|
|
192
|
+
switch (op) {
|
|
186
193
|
case 'COUNT':
|
|
187
194
|
case 'SUM':
|
|
188
195
|
aggr.push({ [as]: sql`SUM("${as}")::DOUBLE` });
|
|
189
196
|
break;
|
|
190
197
|
case 'AVG':
|
|
191
|
-
|
|
192
|
-
aggr.push({ [as]: sql`(SUM("${as}" * ${
|
|
198
|
+
aux[auxAs = '__count__'] = sql`COUNT(*)`;
|
|
199
|
+
aggr.push({ [as]: sql`(SUM("${as}" * ${auxAs}) / SUM(${auxAs}))::DOUBLE` });
|
|
193
200
|
break;
|
|
194
|
-
case '
|
|
195
|
-
|
|
201
|
+
case 'ARG_MAX':
|
|
202
|
+
aux[auxAs = `__max_${as}__`] = sql`MAX(${args[1]})`;
|
|
203
|
+
aggr.push({ [as]: sql`ARG_MAX("${as}", ${auxAs})` });
|
|
204
|
+
break;
|
|
205
|
+
case 'ARG_MIN':
|
|
206
|
+
aux[auxAs = `__min_${as}__`] = sql`MIN(${args[1]})`;
|
|
207
|
+
aggr.push({ [as]: sql`ARG_MIN("${as}", ${auxAs})` });
|
|
196
208
|
break;
|
|
209
|
+
|
|
210
|
+
// aggregates that commute directly
|
|
211
|
+
case 'MAX':
|
|
197
212
|
case 'MIN':
|
|
198
|
-
|
|
213
|
+
case 'BIT_AND':
|
|
214
|
+
case 'BIT_OR':
|
|
215
|
+
case 'BIT_XOR':
|
|
216
|
+
case 'BOOL_AND':
|
|
217
|
+
case 'BOOL_OR':
|
|
218
|
+
case 'PRODUCT':
|
|
219
|
+
aggr.push({ [as]: sql`${op}("${as}")` });
|
|
199
220
|
break;
|
|
200
221
|
default:
|
|
201
222
|
if (g.has(as)) dims.push(as);
|
|
@@ -203,12 +224,7 @@ function getIndexColumns(client) {
|
|
|
203
224
|
}
|
|
204
225
|
}
|
|
205
226
|
|
|
206
|
-
return {
|
|
207
|
-
aggr,
|
|
208
|
-
dims,
|
|
209
|
-
count: count ? { [count]: sql`COUNT(*)` } : {},
|
|
210
|
-
from
|
|
211
|
-
};
|
|
227
|
+
return { aggr, dims, aux, from };
|
|
212
228
|
}
|
|
213
229
|
|
|
214
230
|
function getBaseTable(query) {
|
package/src/FilterGroup.js
CHANGED
|
@@ -46,9 +46,15 @@ export class FilterGroup {
|
|
|
46
46
|
return this;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Internal method to process a selection update.
|
|
51
|
+
* The return value is passed as a selection callback value.
|
|
52
|
+
* @returns {Promise} A Promise that resolves when the update completes.
|
|
53
|
+
*/
|
|
49
54
|
update() {
|
|
50
55
|
const { mc, indexer, clients, selection } = this;
|
|
51
|
-
|
|
56
|
+
const hasIndex = indexer?.index(clients);
|
|
57
|
+
return hasIndex
|
|
52
58
|
? indexer.update()
|
|
53
59
|
: defaultUpdate(mc, clients, selection);
|
|
54
60
|
}
|
package/src/MosaicClient.js
CHANGED
|
@@ -48,6 +48,7 @@ export class MosaicClient {
|
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
50
|
* Return an array of fields queried by this client.
|
|
51
|
+
* @returns {object[]|null} The fields to retrieve info for.
|
|
51
52
|
*/
|
|
52
53
|
fields() {
|
|
53
54
|
return null;
|
|
@@ -55,21 +56,25 @@ export class MosaicClient {
|
|
|
55
56
|
|
|
56
57
|
/**
|
|
57
58
|
* Called by the coordinator to set the field info for this client.
|
|
59
|
+
* @param {*} info The field info result.
|
|
58
60
|
* @returns {this}
|
|
59
61
|
*/
|
|
60
|
-
fieldInfo() {
|
|
62
|
+
fieldInfo(info) { // eslint-disable-line no-unused-vars
|
|
61
63
|
return this;
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
/**
|
|
65
67
|
* Return a query specifying the data needed by this client.
|
|
68
|
+
* @param {*} [filter] The filtering criteria to apply in the query.
|
|
69
|
+
* @returns {*} The client query
|
|
66
70
|
*/
|
|
67
|
-
query() {
|
|
71
|
+
query(filter) { // eslint-disable-line no-unused-vars
|
|
68
72
|
return null;
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
/**
|
|
72
76
|
* Called by the coordinator to inform the client that a query is pending.
|
|
77
|
+
* @returns {this}
|
|
73
78
|
*/
|
|
74
79
|
queryPending() {
|
|
75
80
|
return this;
|
|
@@ -77,16 +82,17 @@ export class MosaicClient {
|
|
|
77
82
|
|
|
78
83
|
/**
|
|
79
84
|
* Called by the coordinator to return a query result.
|
|
80
|
-
*
|
|
81
|
-
* @param {*} data the query result
|
|
85
|
+
* @param {*} data The query result.
|
|
82
86
|
* @returns {this}
|
|
83
87
|
*/
|
|
84
|
-
queryResult() {
|
|
88
|
+
queryResult(data) { // eslint-disable-line no-unused-vars
|
|
85
89
|
return this;
|
|
86
90
|
}
|
|
87
91
|
|
|
88
92
|
/**
|
|
89
93
|
* Called by the coordinator to report a query execution error.
|
|
94
|
+
* @param {*} error
|
|
95
|
+
* @returns {this}
|
|
90
96
|
*/
|
|
91
97
|
queryError(error) {
|
|
92
98
|
console.error(error);
|
|
@@ -116,6 +122,8 @@ export class MosaicClient {
|
|
|
116
122
|
/**
|
|
117
123
|
* Requests a client update.
|
|
118
124
|
* For example to (re-)render an interface component.
|
|
125
|
+
*
|
|
126
|
+
* @returns {this | Promise<any>}
|
|
119
127
|
*/
|
|
120
128
|
update() {
|
|
121
129
|
return this;
|
package/src/Param.js
CHANGED
|
@@ -4,7 +4,7 @@ import { distinct } from './util/distinct.js';
|
|
|
4
4
|
/**
|
|
5
5
|
* Test if a value is a Param instance.
|
|
6
6
|
* @param {*} x The value to test.
|
|
7
|
-
* @returns {
|
|
7
|
+
* @returns {x is Param} True if the input is a Param, false otherwise.
|
|
8
8
|
*/
|
|
9
9
|
export function isParam(x) {
|
|
10
10
|
return x instanceof Param;
|
|
@@ -43,7 +43,9 @@ export class Param extends AsyncDispatch {
|
|
|
43
43
|
static array(values) {
|
|
44
44
|
if (values.some(v => isParam(v))) {
|
|
45
45
|
const p = new Param();
|
|
46
|
-
const update = () =>
|
|
46
|
+
const update = () => {
|
|
47
|
+
p.update(values.map(v => isParam(v) ? v.value : v));
|
|
48
|
+
};
|
|
47
49
|
update();
|
|
48
50
|
values.forEach(v => isParam(v) ? v.addEventListener('value', update) : 0);
|
|
49
51
|
return p;
|
package/src/QueryConsolidator.js
CHANGED
|
@@ -5,6 +5,7 @@ function wait(callback) {
|
|
|
5
5
|
const method = typeof requestAnimationFrame !== 'undefined'
|
|
6
6
|
? requestAnimationFrame
|
|
7
7
|
: typeof setImmediate !== 'undefined' ? setImmediate : setTimeout;
|
|
8
|
+
// @ts-ignore
|
|
8
9
|
return method(callback);
|
|
9
10
|
}
|
|
10
11
|
|
|
@@ -82,7 +83,9 @@ function consolidationKey(query, cache) {
|
|
|
82
83
|
const sql = `${query}`;
|
|
83
84
|
if (query instanceof Query && !cache.get(sql)) {
|
|
84
85
|
if (
|
|
86
|
+
// @ts-ignore
|
|
85
87
|
query.orderby().length || query.where().length ||
|
|
88
|
+
// @ts-ignore
|
|
86
89
|
query.qualify().length || query.having().length
|
|
87
90
|
) {
|
|
88
91
|
// do not try to analyze if query includes clauses
|
|
@@ -97,9 +100,12 @@ function consolidationKey(query, cache) {
|
|
|
97
100
|
// queries may refer to *derived* columns as group by criteria
|
|
98
101
|
// we resolve these against the true grouping expressions
|
|
99
102
|
const groupby = query.groupby();
|
|
103
|
+
// @ts-ignore
|
|
100
104
|
if (groupby.length) {
|
|
101
105
|
const map = {}; // expression map (as -> expr)
|
|
106
|
+
// @ts-ignore
|
|
102
107
|
query.select().forEach(({ as, expr }) => map[as] = expr);
|
|
108
|
+
// @ts-ignore
|
|
103
109
|
q.$groupby(groupby.map(e => (e instanceof Ref && map[e.column]) || e));
|
|
104
110
|
}
|
|
105
111
|
|
package/src/Selection.js
CHANGED
|
@@ -4,7 +4,7 @@ import { Param } from './Param.js';
|
|
|
4
4
|
/**
|
|
5
5
|
* Test if a value is a Selection instance.
|
|
6
6
|
* @param {*} x The value to test.
|
|
7
|
-
* @returns {
|
|
7
|
+
* @returns {x is Selection} True if the input is a Selection, false otherwise.
|
|
8
8
|
*/
|
|
9
9
|
export function isSelection(x) {
|
|
10
10
|
return x instanceof Selection;
|
|
@@ -76,7 +76,7 @@ export class Selection extends Param {
|
|
|
76
76
|
|
|
77
77
|
/**
|
|
78
78
|
* Create a cloned copy of this Selection instance.
|
|
79
|
-
* @returns {
|
|
79
|
+
* @returns {Selection} A clone of this selection.
|
|
80
80
|
*/
|
|
81
81
|
clone() {
|
|
82
82
|
const s = new Selection(this._resolver);
|
|
@@ -88,7 +88,7 @@ export class Selection extends Param {
|
|
|
88
88
|
* Create a clone of this Selection with clauses corresponding
|
|
89
89
|
* to the provided source removed.
|
|
90
90
|
* @param {*} source The clause source to remove.
|
|
91
|
-
* @returns {
|
|
91
|
+
* @returns {Selection} A cloned and updated Selection.
|
|
92
92
|
*/
|
|
93
93
|
remove(source) {
|
|
94
94
|
const s = this.clone();
|
|
@@ -168,9 +168,9 @@ export class Selection extends Param {
|
|
|
168
168
|
* Upon value-typed updates, returns a dispatch queue filter function.
|
|
169
169
|
* The return value depends on the selection resolution strategy.
|
|
170
170
|
* @param {string} type The event type.
|
|
171
|
-
* @param {*} value The
|
|
172
|
-
* @returns {*} For value-typed events,
|
|
173
|
-
* function. Otherwise returns null.
|
|
171
|
+
* @param {*} value The new event value that will be enqueued.
|
|
172
|
+
* @returns {(value: *) => boolean|null} For value-typed events,
|
|
173
|
+
* returns a dispatch queue filter function. Otherwise returns null.
|
|
174
174
|
*/
|
|
175
175
|
emitQueueFilter(type, value) {
|
|
176
176
|
return type === 'value'
|
|
@@ -285,5 +285,6 @@ export class SelectionResolver {
|
|
|
285
285
|
const source = value.active?.source;
|
|
286
286
|
return clauses => clauses.active?.source !== source;
|
|
287
287
|
}
|
|
288
|
+
return null;
|
|
288
289
|
}
|
|
289
290
|
}
|
package/src/connectors/wasm.js
CHANGED
|
@@ -22,7 +22,7 @@ export function wasmConnector(options = {}) {
|
|
|
22
22
|
/**
|
|
23
23
|
* Get the backing DuckDB-WASM instance.
|
|
24
24
|
* Will lazily initialize DuckDB-WASM if not already loaded.
|
|
25
|
-
* @returns {duckdb.AsyncDuckDB} The DuckDB-WASM instance.
|
|
25
|
+
* @returns {Promise<duckdb.AsyncDuckDB>} The DuckDB-WASM instance.
|
|
26
26
|
*/
|
|
27
27
|
async function getDuckDB() {
|
|
28
28
|
if (!db) await load();
|
|
@@ -32,7 +32,7 @@ export function wasmConnector(options = {}) {
|
|
|
32
32
|
/**
|
|
33
33
|
* Get the backing DuckDB-WASM connection.
|
|
34
34
|
* Will lazily initialize DuckDB-WASM if not already loaded.
|
|
35
|
-
* @returns {duckdb.AsyncDuckDBConnection} The DuckDB-WASM connection.
|
|
35
|
+
* @returns {Promise<duckdb.AsyncDuckDBConnection>} The DuckDB-WASM connection.
|
|
36
36
|
*/
|
|
37
37
|
async function getConnection() {
|
|
38
38
|
if (!con) await load();
|
|
@@ -16,7 +16,7 @@ export class AsyncDispatch {
|
|
|
16
16
|
/**
|
|
17
17
|
* Add an event listener callback for the provided event type.
|
|
18
18
|
* @param {string} type The event type.
|
|
19
|
-
* @param {(value: *) => Promise
|
|
19
|
+
* @param {(value: *) => void | Promise} callback The event handler
|
|
20
20
|
* callback function to add. If the callback has already been
|
|
21
21
|
* added for the event type, this method has no effect.
|
|
22
22
|
*/
|
|
@@ -35,7 +35,7 @@ export class AsyncDispatch {
|
|
|
35
35
|
/**
|
|
36
36
|
* Remove an event listener callback for the provided event type.
|
|
37
37
|
* @param {string} type The event type.
|
|
38
|
-
* @param {(value: *) => Promise
|
|
38
|
+
* @param {(value: *) => void | Promise} callback The event handler
|
|
39
39
|
* callback function to remove.
|
|
40
40
|
*/
|
|
41
41
|
removeEventListener(type, callback) {
|
|
@@ -64,11 +64,12 @@ export class AsyncDispatch {
|
|
|
64
64
|
* This default implementation simply returns null, indicating that
|
|
65
65
|
* any other unemitted event values should be dropped (that is, all
|
|
66
66
|
* queued events are filtered)
|
|
67
|
+
* @param {string} type The event type.
|
|
67
68
|
* @param {*} value The new event value that will be enqueued.
|
|
68
69
|
* @returns {(value: *) => boolean|null} A dispatch queue filter
|
|
69
70
|
* function, or null if all unemitted event values should be filtered.
|
|
70
71
|
*/
|
|
71
|
-
emitQueueFilter() {
|
|
72
|
+
emitQueueFilter(type, value) { // eslint-disable-line no-unused-vars
|
|
72
73
|
// removes all pending items
|
|
73
74
|
return null;
|
|
74
75
|
}
|
|
@@ -94,20 +95,22 @@ export class AsyncDispatch {
|
|
|
94
95
|
emit(type, value) {
|
|
95
96
|
const entry = this._callbacks.get(type) || {};
|
|
96
97
|
if (entry.pending) {
|
|
98
|
+
// an earlier emit is still processing
|
|
99
|
+
// enqueue the current update, possibly filtering other pending updates
|
|
97
100
|
entry.queue.enqueue(value, this.emitQueueFilter(type, value));
|
|
98
101
|
} else {
|
|
99
102
|
const event = this.willEmit(type, value);
|
|
100
103
|
const { callbacks, queue } = entry;
|
|
101
104
|
if (callbacks?.size) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
105
|
+
// broadcast update to callbacks, which may return promises
|
|
106
|
+
// wait until promises resolve, then process pending updates
|
|
107
|
+
const callbackValues = Array.from(callbacks, cb => cb(event));
|
|
108
|
+
entry.pending = Promise.allSettled(callbackValues).then(() => {
|
|
109
|
+
entry.pending = null;
|
|
110
|
+
if (!queue.isEmpty()) {
|
|
111
|
+
this.emit(type, queue.dequeue());
|
|
112
|
+
}
|
|
113
|
+
});
|
|
111
114
|
}
|
|
112
115
|
}
|
|
113
116
|
}
|
|
@@ -1,34 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
const INTEGER = 2;
|
|
3
|
-
const FLOAT = 3;
|
|
4
|
-
const DECIMAL = 7;
|
|
5
|
-
const TIMESTAMP = 10;
|
|
1
|
+
import { DataType } from 'apache-arrow';
|
|
6
2
|
|
|
7
3
|
/**
|
|
8
4
|
* Test if a value is an Apache Arrow table.
|
|
9
5
|
* As sometimes multiple Arrow versions may be used simultaneously,
|
|
10
6
|
* we use a "duck typing" approach and check for a getChild function.
|
|
11
7
|
* @param {*} values The value to test
|
|
12
|
-
* @returns true if the value duck types as Apache Arrow data
|
|
8
|
+
* @returns {values is import('apache-arrow').Table} true if the value duck types as Apache Arrow data
|
|
13
9
|
*/
|
|
14
10
|
export function isArrowTable(values) {
|
|
15
11
|
return typeof values?.getChild === 'function';
|
|
16
12
|
}
|
|
17
13
|
|
|
18
14
|
/**
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
* Return a JavaScript array type for an Apache Arrow column type.
|
|
16
|
+
* @param {DataType} type an Apache Arrow column type
|
|
17
|
+
* @returns a JavaScript array constructor
|
|
18
|
+
*/
|
|
23
19
|
export function convertArrowArrayType(type) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
case DECIMAL:
|
|
28
|
-
return Float64Array;
|
|
29
|
-
default:
|
|
30
|
-
return Array;
|
|
31
|
-
}
|
|
20
|
+
return DataType.isInt(type) || DataType.isFloat(type) || DataType.isDecimal(type)
|
|
21
|
+
? Float64Array
|
|
22
|
+
: Array;
|
|
32
23
|
}
|
|
33
24
|
|
|
34
25
|
/**
|
|
@@ -37,24 +28,22 @@ export function convertArrowArrayType(type) {
|
|
|
37
28
|
* Large integers (BigInt) are converted to Float64 numbers.
|
|
38
29
|
* Fixed-point decimal values are convert to Float64 numbers.
|
|
39
30
|
* Otherwise, the default Arrow values are used.
|
|
40
|
-
* @param {
|
|
31
|
+
* @param {DataType} type an Apache Arrow column type
|
|
41
32
|
* @returns a value conversion function
|
|
42
33
|
*/
|
|
43
34
|
export function convertArrowValue(type) {
|
|
44
|
-
const { typeId } = type;
|
|
45
|
-
|
|
46
35
|
// map timestamp numbers to date objects
|
|
47
|
-
if (
|
|
36
|
+
if (DataType.isTimestamp(type)) {
|
|
48
37
|
return v => v == null ? v : new Date(v);
|
|
49
38
|
}
|
|
50
39
|
|
|
51
40
|
// map bigint to number
|
|
52
|
-
if (
|
|
41
|
+
if (DataType.isInt(type) && type.bitWidth >= 64) {
|
|
53
42
|
return v => v == null ? v : Number(v);
|
|
54
43
|
}
|
|
55
44
|
|
|
56
45
|
// map decimal to number
|
|
57
|
-
if (
|
|
46
|
+
if (DataType.isDecimal(type)) {
|
|
58
47
|
const scale = 1 / Math.pow(10, type.scale);
|
|
59
48
|
return v => v == null ? v : decimalToNumber(v, scale);
|
|
60
49
|
}
|
|
@@ -74,10 +63,9 @@ export function convertArrowValue(type) {
|
|
|
74
63
|
*/
|
|
75
64
|
export function convertArrowColumn(column) {
|
|
76
65
|
const { type } = column;
|
|
77
|
-
const { typeId } = type;
|
|
78
66
|
|
|
79
67
|
// map timestamp numbers to date objects
|
|
80
|
-
if (
|
|
68
|
+
if (DataType.isTimestamp(type)) {
|
|
81
69
|
const size = column.length;
|
|
82
70
|
const array = new Array(size);
|
|
83
71
|
for (let row = 0; row < size; ++row) {
|
|
@@ -88,7 +76,7 @@ export function convertArrowColumn(column) {
|
|
|
88
76
|
}
|
|
89
77
|
|
|
90
78
|
// map bigint to number
|
|
91
|
-
if (
|
|
79
|
+
if (DataType.isInt(type) && type.bitWidth >= 64) {
|
|
92
80
|
const size = column.length;
|
|
93
81
|
const array = new Float64Array(size);
|
|
94
82
|
for (let row = 0; row < size; ++row) {
|
|
@@ -99,7 +87,7 @@ export function convertArrowColumn(column) {
|
|
|
99
87
|
}
|
|
100
88
|
|
|
101
89
|
// map decimal to number
|
|
102
|
-
if (
|
|
90
|
+
if (DataType.isDecimal(type)) {
|
|
103
91
|
const scale = 1 / Math.pow(10, type.scale);
|
|
104
92
|
const size = column.length;
|
|
105
93
|
const array = new Float64Array(size);
|
|
@@ -124,10 +112,10 @@ const BASE32 = Array.from(
|
|
|
124
112
|
/**
|
|
125
113
|
* Convert a fixed point decimal value to a double precision number.
|
|
126
114
|
* Note: if the value is sufficiently large the conversion may be lossy!
|
|
127
|
-
* @param {Uint32Array} v a fixed decimal value
|
|
115
|
+
* @param {Uint32Array & { signed: boolean }} v a fixed decimal value
|
|
128
116
|
* @param {number} scale a scale factor, corresponding to the
|
|
129
117
|
* number of fractional decimal digits in the fixed point value
|
|
130
|
-
* @returns the resulting number
|
|
118
|
+
* @returns {number} the resulting number
|
|
131
119
|
*/
|
|
132
120
|
function decimalToNumber(v, scale) {
|
|
133
121
|
const n = v.length;
|
package/src/util/js-type.js
CHANGED
package/src/util/query-result.js
CHANGED
|
@@ -2,7 +2,8 @@ export function queryResult() {
|
|
|
2
2
|
let resolve;
|
|
3
3
|
let reject;
|
|
4
4
|
const p = new Promise((r, e) => { resolve = r; reject = e; });
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
return Object.assign(p, {
|
|
6
|
+
fulfill: value => (resolve(value), p),
|
|
7
|
+
reject: err => (reject(err), p)
|
|
8
|
+
});
|
|
8
9
|
}
|