@uwdata/mosaic-core 0.7.1 → 0.9.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 +2126 -1655
- package/dist/mosaic-core.min.js +8 -8
- package/package.json +9 -6
- package/src/Coordinator.js +11 -5
- package/src/DataCubeIndexer.js +78 -108
- package/src/FilterGroup.js +27 -10
- package/src/MosaicClient.js +13 -5
- package/src/Param.js +4 -2
- package/src/QueryConsolidator.js +6 -0
- package/src/QueryManager.js +101 -93
- package/src/Selection.js +33 -17
- package/src/SelectionClause.js +147 -0
- package/src/connectors/rest.js +7 -0
- package/src/connectors/socket.js +7 -0
- package/src/connectors/wasm.js +4 -4
- package/src/index.js +1 -0
- package/src/util/AsyncDispatch.js +15 -12
- package/src/util/convert-arrow.js +32 -35
- package/src/util/index-columns.js +538 -0
- package/src/util/js-type.js +1 -0
- package/src/util/query-result.js +4 -3
- package/src/util/selection-types.ts +137 -0
package/src/QueryManager.js
CHANGED
|
@@ -5,48 +5,51 @@ import { queryResult } from './util/query-result.js';
|
|
|
5
5
|
|
|
6
6
|
export const Priority = { High: 0, Normal: 1, Low: 2 };
|
|
7
7
|
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (pending || queue.isEmpty()) return;
|
|
19
|
-
const { request, result } = queue.next();
|
|
20
|
-
pending = submit(request, result);
|
|
21
|
-
pending.finally(() => { pending = null; next(); });
|
|
8
|
+
export class QueryManager {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.queue = priorityQueue(3);
|
|
11
|
+
this.db = null;
|
|
12
|
+
this.clientCache = null;
|
|
13
|
+
this._logger = null;
|
|
14
|
+
this._logQueries = false;
|
|
15
|
+
this.recorders = [];
|
|
16
|
+
this.pending = null;
|
|
17
|
+
this._consolidate = null;
|
|
22
18
|
}
|
|
23
19
|
|
|
24
|
-
|
|
25
|
-
queue.
|
|
26
|
-
next();
|
|
20
|
+
next() {
|
|
21
|
+
if (this.pending || this.queue.isEmpty()) return;
|
|
22
|
+
const { request, result } = this.queue.next();
|
|
23
|
+
this.pending = this.submit(request, result);
|
|
24
|
+
this.pending.finally(() => { this.pending = null; this.next(); });
|
|
27
25
|
}
|
|
28
26
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
enqueue(entry, priority = Priority.Normal) {
|
|
28
|
+
this.queue.insert(entry, priority);
|
|
29
|
+
this.next();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
recordQuery(sql) {
|
|
33
|
+
if (this.recorders.length && sql) {
|
|
34
|
+
this.recorders.forEach(rec => rec.add(sql));
|
|
32
35
|
}
|
|
33
36
|
}
|
|
34
37
|
|
|
35
|
-
async
|
|
38
|
+
async submit(request, result) {
|
|
36
39
|
try {
|
|
37
40
|
const { query, type, cache = false, record = true, options } = request;
|
|
38
41
|
const sql = query ? `${query}` : null;
|
|
39
42
|
|
|
40
43
|
// update recorders
|
|
41
44
|
if (record) {
|
|
42
|
-
recordQuery(sql);
|
|
45
|
+
this.recordQuery(sql);
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
// check query cache
|
|
46
49
|
if (cache) {
|
|
47
|
-
const cached = clientCache.get(sql);
|
|
50
|
+
const cached = this.clientCache.get(sql);
|
|
48
51
|
if (cached) {
|
|
49
|
-
|
|
52
|
+
this._logger.debug('Cache');
|
|
50
53
|
result.fulfill(cached);
|
|
51
54
|
return;
|
|
52
55
|
}
|
|
@@ -54,80 +57,85 @@ export function QueryManager() {
|
|
|
54
57
|
|
|
55
58
|
// issue query, potentially cache result
|
|
56
59
|
const t0 = performance.now();
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
if (this._logQueries) {
|
|
61
|
+
this._logger.debug('Query', { type, sql, ...options });
|
|
62
|
+
}
|
|
63
|
+
const data = await this.db.query({ type, sql, ...options });
|
|
64
|
+
if (cache) this.clientCache.set(sql, data);
|
|
65
|
+
this._logger.debug(`Request: ${(performance.now() - t0).toFixed(1)}`);
|
|
60
66
|
result.fulfill(data);
|
|
61
67
|
} catch (err) {
|
|
62
68
|
result.reject(err);
|
|
63
69
|
}
|
|
64
70
|
}
|
|
65
71
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
cancel(requests) {
|
|
101
|
-
const set = new Set(requests);
|
|
102
|
-
queue.remove(({ result }) => set.has(result));
|
|
103
|
-
},
|
|
104
|
-
|
|
105
|
-
clear() {
|
|
106
|
-
queue.remove(({ result }) => {
|
|
107
|
-
result.reject('Cleared');
|
|
108
|
-
return true;
|
|
109
|
-
});
|
|
110
|
-
},
|
|
111
|
-
|
|
112
|
-
record() {
|
|
113
|
-
let state = [];
|
|
114
|
-
const recorder = {
|
|
115
|
-
add(query) {
|
|
116
|
-
state.push(query);
|
|
117
|
-
},
|
|
118
|
-
reset() {
|
|
119
|
-
state = [];
|
|
120
|
-
},
|
|
121
|
-
snapshot() {
|
|
122
|
-
return state.slice();
|
|
123
|
-
},
|
|
124
|
-
stop() {
|
|
125
|
-
recorders = recorders.filter(x => x !== recorder);
|
|
126
|
-
return state;
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
recorders.push(recorder);
|
|
130
|
-
return recorder;
|
|
72
|
+
cache(value) {
|
|
73
|
+
return value !== undefined
|
|
74
|
+
? (this.clientCache = value === true ? lruCache() : (value || voidCache()))
|
|
75
|
+
: this.clientCache;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
logger(value) {
|
|
79
|
+
return value ? (this._logger = value) : this._logger;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
logQueries(value) {
|
|
83
|
+
return value !== undefined ? this._logQueries = !!value : this._logQueries;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
connector(connector) {
|
|
87
|
+
return connector ? (this.db = connector) : this.db;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
consolidate(flag) {
|
|
91
|
+
if (flag && !this._consolidate) {
|
|
92
|
+
this._consolidate = consolidator(this.enqueue.bind(this), this.clientCache, this.recordQuery.bind(this));
|
|
93
|
+
} else if (!flag && this._consolidate) {
|
|
94
|
+
this._consolidate = null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
request(request, priority = Priority.Normal) {
|
|
99
|
+
const result = queryResult();
|
|
100
|
+
const entry = { request, result };
|
|
101
|
+
if (this._consolidate) {
|
|
102
|
+
this._consolidate.add(entry, priority);
|
|
103
|
+
} else {
|
|
104
|
+
this.enqueue(entry, priority);
|
|
131
105
|
}
|
|
132
|
-
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
cancel(requests) {
|
|
110
|
+
const set = new Set(requests);
|
|
111
|
+
this.queue.remove(({ result }) => set.has(result));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
clear() {
|
|
115
|
+
this.queue.remove(({ result }) => {
|
|
116
|
+
result.reject('Cleared');
|
|
117
|
+
return true;
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
record() {
|
|
122
|
+
let state = [];
|
|
123
|
+
const recorder = {
|
|
124
|
+
add(query) {
|
|
125
|
+
state.push(query);
|
|
126
|
+
},
|
|
127
|
+
reset() {
|
|
128
|
+
state = [];
|
|
129
|
+
},
|
|
130
|
+
snapshot() {
|
|
131
|
+
return state.slice();
|
|
132
|
+
},
|
|
133
|
+
stop() {
|
|
134
|
+
this.recorders = this.recorders.filter(x => x !== recorder);
|
|
135
|
+
return state;
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
this.recorders.push(recorder);
|
|
139
|
+
return recorder;
|
|
140
|
+
}
|
|
133
141
|
}
|
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();
|
|
@@ -98,19 +98,17 @@ export class Selection extends Param {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
/**
|
|
101
|
-
* The
|
|
101
|
+
* The selection clause resolver.
|
|
102
102
|
*/
|
|
103
|
-
get
|
|
104
|
-
return this.
|
|
103
|
+
get resolver() {
|
|
104
|
+
return this._resolver;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
/**
|
|
108
|
-
*
|
|
109
|
-
* This method ensures compatibility where a normal Param is expected.
|
|
108
|
+
* Indicate if this selection has a single resolution strategy.
|
|
110
109
|
*/
|
|
111
|
-
get
|
|
112
|
-
|
|
113
|
-
return this.active?.value;
|
|
110
|
+
get single() {
|
|
111
|
+
return this._resolver.single;
|
|
114
112
|
}
|
|
115
113
|
|
|
116
114
|
/**
|
|
@@ -121,10 +119,27 @@ export class Selection extends Param {
|
|
|
121
119
|
}
|
|
122
120
|
|
|
123
121
|
/**
|
|
124
|
-
*
|
|
122
|
+
* The current active (most recently updated) selection clause.
|
|
125
123
|
*/
|
|
126
|
-
get
|
|
127
|
-
return this.
|
|
124
|
+
get active() {
|
|
125
|
+
return this.clauses.active;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* The value corresponding to the current active selection clause.
|
|
130
|
+
* This method ensures compatibility where a normal Param is expected.
|
|
131
|
+
*/
|
|
132
|
+
get value() {
|
|
133
|
+
return this.active?.value;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The value corresponding to a given source. Returns undefined if
|
|
138
|
+
* this selection does not include a clause from this source.
|
|
139
|
+
* @param {*} source The clause source to look up the value for.
|
|
140
|
+
*/
|
|
141
|
+
valueFor(source) {
|
|
142
|
+
return this.clauses.find(c => c.source === source)?.value;
|
|
128
143
|
}
|
|
129
144
|
|
|
130
145
|
/**
|
|
@@ -168,9 +183,9 @@ export class Selection extends Param {
|
|
|
168
183
|
* Upon value-typed updates, returns a dispatch queue filter function.
|
|
169
184
|
* The return value depends on the selection resolution strategy.
|
|
170
185
|
* @param {string} type The event type.
|
|
171
|
-
* @param {*} value The
|
|
172
|
-
* @returns {*} For value-typed events,
|
|
173
|
-
* function. Otherwise returns null.
|
|
186
|
+
* @param {*} value The new event value that will be enqueued.
|
|
187
|
+
* @returns {(value: *) => boolean|null} For value-typed events,
|
|
188
|
+
* returns a dispatch queue filter function. Otherwise returns null.
|
|
174
189
|
*/
|
|
175
190
|
emitQueueFilter(type, value) {
|
|
176
191
|
return type === 'value'
|
|
@@ -285,5 +300,6 @@ export class SelectionResolver {
|
|
|
285
300
|
const source = value.active?.source;
|
|
286
301
|
return clauses => clauses.active?.source !== source;
|
|
287
302
|
}
|
|
303
|
+
return null;
|
|
288
304
|
}
|
|
289
305
|
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SQLExpression, and, contains, isBetween, isNotDistinct, literal,
|
|
3
|
+
or, prefix, regexp_matches, suffix
|
|
4
|
+
} from '@uwdata/mosaic-sql';
|
|
5
|
+
import { MosaicClient } from './MosaicClient.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {import('./util/selection-types.js').SelectionClause} SelectionClause
|
|
9
|
+
* @typedef {import('./util/selection-types.js').Scale} Scale
|
|
10
|
+
* @typedef {import('./util/selection-types.js').Extent} Extent
|
|
11
|
+
* @typedef {import('./util/selection-types.js').MatchMethod} MatchMethod
|
|
12
|
+
* @typedef {import('./util/selection-types.js').BinMethod} BinMethod
|
|
13
|
+
* @typedef {SQLExpression | string} Field
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Generate a selection clause for a single selected point value.
|
|
18
|
+
* @param {Field} field The table column or expression to select.
|
|
19
|
+
* @param {*} value The selected value.
|
|
20
|
+
* @param {object} options Additional clause properties.
|
|
21
|
+
* @param {*} options.source The source component generating this clause.
|
|
22
|
+
* @param {Set<MosaicClient>} [options.clients] The Mosaic clients associated
|
|
23
|
+
* with this clause. These clients are not filtered by this clause in
|
|
24
|
+
* cross-filtering contexts.
|
|
25
|
+
* @returns {SelectionClause} The generated selection clause.
|
|
26
|
+
*/
|
|
27
|
+
export function point(field, value, { source, clients = undefined }) {
|
|
28
|
+
/** @type {SQLExpression | null} */
|
|
29
|
+
const predicate = value !== undefined
|
|
30
|
+
? isNotDistinct(field, literal(value))
|
|
31
|
+
: null;
|
|
32
|
+
return {
|
|
33
|
+
meta: { type: 'point' },
|
|
34
|
+
source,
|
|
35
|
+
clients,
|
|
36
|
+
value,
|
|
37
|
+
predicate
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Generate a selection clause for multiple selected point values.
|
|
43
|
+
* @param {Field[]} fields The table columns or expressions to select.
|
|
44
|
+
* @param {any[][]} value The selected values, as an array of arrays where
|
|
45
|
+
* each subarray contains values corresponding to each *fields* entry.
|
|
46
|
+
* @param {object} options Additional clause properties.
|
|
47
|
+
* @param {*} options.source The source component generating this clause.
|
|
48
|
+
* @param {Set<MosaicClient>} [options.clients] The Mosaic clients associated
|
|
49
|
+
* with this clause. These clients are not filtered by this clause in
|
|
50
|
+
* cross-filtering contexts.
|
|
51
|
+
* @returns {SelectionClause} The generated selection clause.
|
|
52
|
+
*/
|
|
53
|
+
export function points(fields, value, { source, clients = undefined }) {
|
|
54
|
+
/** @type {SQLExpression | null} */
|
|
55
|
+
let predicate = null;
|
|
56
|
+
if (value) {
|
|
57
|
+
const clauses = value.map(vals => {
|
|
58
|
+
const list = vals.map((v, i) => isNotDistinct(fields[i], literal(v)));
|
|
59
|
+
return list.length > 1 ? and(list) : list[0];
|
|
60
|
+
});
|
|
61
|
+
predicate = clauses.length > 1 ? or(clauses) : clauses[0];
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
meta: { type: 'point' },
|
|
65
|
+
source,
|
|
66
|
+
clients,
|
|
67
|
+
value,
|
|
68
|
+
predicate
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Generate a selection clause for a selected 1D interval.
|
|
74
|
+
* @param {Field} field The table column or expression to select.
|
|
75
|
+
* @param {Extent} value The selected interval as a [lo, hi] array.
|
|
76
|
+
* @param {object} options Additional clause properties.
|
|
77
|
+
* @param {*} options.source The source component generating this clause.
|
|
78
|
+
* @param {Set<MosaicClient>} [options.clients] The Mosaic clients associated
|
|
79
|
+
* with this clause. These clients are not filtered by this clause in
|
|
80
|
+
* cross-filtering contexts.
|
|
81
|
+
* @param {Scale} [options.scale] The scale mapping descriptor.
|
|
82
|
+
* @param {BinMethod} [options.bin] A binning method hint.
|
|
83
|
+
* @param {number} [options.pixelSize=1] The interactive pixel size.
|
|
84
|
+
* @returns {SelectionClause} The generated selection clause.
|
|
85
|
+
*/
|
|
86
|
+
export function interval(field, value, {
|
|
87
|
+
source, clients, bin, scale, pixelSize = 1
|
|
88
|
+
}) {
|
|
89
|
+
/** @type {SQLExpression | null} */
|
|
90
|
+
const predicate = value != null ? isBetween(field, value) : null;
|
|
91
|
+
/** @type {import('./util/selection-types.js').IntervalMetadata} */
|
|
92
|
+
const meta = { type: 'interval', scales: [scale], bin, pixelSize };
|
|
93
|
+
return { meta, source, clients, value, predicate };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Generate a selection clause for multiple selected intervals.
|
|
98
|
+
* @param {Field[]} fields The table columns or expressions to select.
|
|
99
|
+
* @param {Extent[]} value The selected intervals, as an array of extents.
|
|
100
|
+
* @param {object} options Additional clause properties.
|
|
101
|
+
* @param {*} options.source The source component generating this clause.
|
|
102
|
+
* @param {Set<MosaicClient>} [options.clients] The Mosaic clients associated
|
|
103
|
+
* with this clause. These clients are not filtered by this clause in
|
|
104
|
+
* cross-filtering contexts.
|
|
105
|
+
* @param {Scale[]} [options.scales] The scale mapping descriptors,
|
|
106
|
+
* in an order matching the given *fields* and *value* extents.
|
|
107
|
+
* @param {BinMethod} [options.bin] A binning method hint.
|
|
108
|
+
* @param {number} [options.pixelSize=1] The interactive pixel size.
|
|
109
|
+
* @returns {SelectionClause} The generated selection clause.
|
|
110
|
+
*/
|
|
111
|
+
export function intervals(fields, value, {
|
|
112
|
+
source, clients, bin, scales = [], pixelSize = 1
|
|
113
|
+
}) {
|
|
114
|
+
/** @type {SQLExpression | null} */
|
|
115
|
+
const predicate = value != null
|
|
116
|
+
? and(fields.map((f, i) => isBetween(f, value[i])))
|
|
117
|
+
: null;
|
|
118
|
+
/** @type {import('./util/selection-types.js').IntervalMetadata} */
|
|
119
|
+
const meta = { type: 'interval', scales, bin, pixelSize };
|
|
120
|
+
return { meta, source, clients, value, predicate };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const MATCH_METHODS = { contains, prefix, suffix, regexp: regexp_matches };
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Generate a selection clause for text search matching.
|
|
127
|
+
* @param {Field} field The table column or expression to select.
|
|
128
|
+
* @param {string} value The selected text search query string.
|
|
129
|
+
* @param {object} options Additional clause properties.
|
|
130
|
+
* @param {*} options.source The source component generating this clause.
|
|
131
|
+
* @param {Set<MosaicClient>} [options.clients] The Mosaic clients associated
|
|
132
|
+
* with this clause. These clients are not filtered by this clause in
|
|
133
|
+
* cross-filtering contexts.
|
|
134
|
+
* @param {MatchMethod} [options.method] The
|
|
135
|
+
* text matching method to use. Defaults to `'contains'`.
|
|
136
|
+
* @returns {SelectionClause} The generated selection clause.
|
|
137
|
+
*/
|
|
138
|
+
export function match(field, value, {
|
|
139
|
+
source, clients = undefined, method = 'contains'
|
|
140
|
+
}) {
|
|
141
|
+
let fn = MATCH_METHODS[method];
|
|
142
|
+
/** @type {SQLExpression | null} */
|
|
143
|
+
const predicate = value ? fn(field, literal(value)) : null;
|
|
144
|
+
/** @type {import('./util/selection-types.js').MatchMetadata} */
|
|
145
|
+
const meta = { type: 'match', method };
|
|
146
|
+
return { meta, source, clients, value, predicate };
|
|
147
|
+
}
|
package/src/connectors/rest.js
CHANGED
|
@@ -2,6 +2,13 @@ import { tableFromIPC } from 'apache-arrow';
|
|
|
2
2
|
|
|
3
3
|
export function restConnector(uri = 'http://localhost:3000/') {
|
|
4
4
|
return {
|
|
5
|
+
/**
|
|
6
|
+
* Query the DuckDB server.
|
|
7
|
+
* @param {object} query
|
|
8
|
+
* @param {'exec' | 'arrow' | 'json'} [query.type] The query type: 'exec', 'arrow', or 'json'.
|
|
9
|
+
* @param {string} query.sql A SQL query string.
|
|
10
|
+
* @returns the query result
|
|
11
|
+
*/
|
|
5
12
|
async query(query) {
|
|
6
13
|
const req = fetch(uri, {
|
|
7
14
|
method: 'POST',
|
package/src/connectors/socket.js
CHANGED
|
@@ -81,6 +81,13 @@ export function socketConnector(uri = 'ws://localhost:3000/') {
|
|
|
81
81
|
get connected() {
|
|
82
82
|
return connected;
|
|
83
83
|
},
|
|
84
|
+
/**
|
|
85
|
+
* Query the DuckDB server.
|
|
86
|
+
* @param {object} query
|
|
87
|
+
* @param {'exec' | 'arrow' | 'json'} [query.type] The query type: 'exec', 'arrow', or 'json'.
|
|
88
|
+
* @param {string} query.sql A SQL query string.
|
|
89
|
+
* @returns the query result
|
|
90
|
+
*/
|
|
84
91
|
query(query) {
|
|
85
92
|
return new Promise(
|
|
86
93
|
(resolve, reject) => enqueue(query, resolve, reject)
|
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();
|
|
@@ -45,7 +45,7 @@ export function wasmConnector(options = {}) {
|
|
|
45
45
|
/**
|
|
46
46
|
* Query the DuckDB-WASM instance.
|
|
47
47
|
* @param {object} query
|
|
48
|
-
* @param {
|
|
48
|
+
* @param {'exec' | 'arrow' | 'json'} [query.type] The query type: 'exec', 'arrow', or 'json'.
|
|
49
49
|
* @param {string} query.sql A SQL query string.
|
|
50
50
|
* @returns the query result
|
|
51
51
|
*/
|
|
@@ -55,7 +55,7 @@ export function wasmConnector(options = {}) {
|
|
|
55
55
|
const result = await con.query(sql);
|
|
56
56
|
return type === 'exec' ? undefined
|
|
57
57
|
: type === 'arrow' ? result
|
|
58
|
-
:
|
|
58
|
+
: result.toArray();
|
|
59
59
|
}
|
|
60
60
|
};
|
|
61
61
|
}
|
package/src/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export { Coordinator, coordinator } from './Coordinator.js';
|
|
|
3
3
|
export { Selection, isSelection } from './Selection.js';
|
|
4
4
|
export { Param, isParam } from './Param.js';
|
|
5
5
|
export { Priority } from './QueryManager.js';
|
|
6
|
+
export { point, points, interval, intervals, match } from './SelectionClause.js';
|
|
6
7
|
|
|
7
8
|
export { restConnector } from './connectors/rest.js';
|
|
8
9
|
export { socketConnector } from './connectors/socket.js';
|
|
@@ -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
|
}
|