@uwdata/mosaic-core 0.8.0 → 0.10.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 +1948 -664
- package/dist/mosaic-core.min.js +8 -8
- package/package.json +7 -7
- package/src/Coordinator.js +194 -49
- package/src/DataCubeIndexer.js +257 -198
- package/src/MosaicClient.js +3 -3
- package/src/QueryConsolidator.js +2 -2
- package/src/QueryManager.js +104 -94
- package/src/Selection.js +57 -21
- package/src/SelectionClause.js +161 -0
- package/src/connectors/rest.js +7 -0
- package/src/connectors/socket.js +7 -0
- package/src/connectors/wasm.js +2 -2
- package/src/index.js +10 -0
- package/src/util/AsyncDispatch.js +15 -5
- package/src/util/convert-arrow.js +14 -5
- package/src/util/index-columns.js +540 -0
- package/src/util/query-result.js +41 -8
- package/src/util/selection-types.ts +137 -0
- package/src/util/to-data-columns.js +71 -0
- package/src/FilterGroup.js +0 -70
package/src/QueryManager.js
CHANGED
|
@@ -1,52 +1,55 @@
|
|
|
1
1
|
import { consolidator } from './QueryConsolidator.js';
|
|
2
2
|
import { lruCache, voidCache } from './util/cache.js';
|
|
3
3
|
import { priorityQueue } from './util/priority-queue.js';
|
|
4
|
-
import {
|
|
4
|
+
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,87 @@ 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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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 = new QueryResult();
|
|
100
|
+
const entry = { request, result };
|
|
101
|
+
if (this._consolidate) {
|
|
102
|
+
this._consolidate.add(entry, priority);
|
|
103
|
+
} else {
|
|
104
|
+
this.enqueue(entry, priority);
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
cancel(requests) {
|
|
110
|
+
const set = new Set(requests);
|
|
111
|
+
if (set.size) {
|
|
112
|
+
this.queue.remove(({ result }) => set.has(result));
|
|
131
113
|
}
|
|
132
|
-
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
clear() {
|
|
117
|
+
this.queue.remove(({ result }) => {
|
|
118
|
+
result.reject('Cleared');
|
|
119
|
+
return true;
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
record() {
|
|
124
|
+
let state = [];
|
|
125
|
+
const recorder = {
|
|
126
|
+
add(query) {
|
|
127
|
+
state.push(query);
|
|
128
|
+
},
|
|
129
|
+
reset() {
|
|
130
|
+
state = [];
|
|
131
|
+
},
|
|
132
|
+
snapshot() {
|
|
133
|
+
return state.slice();
|
|
134
|
+
},
|
|
135
|
+
stop() {
|
|
136
|
+
this.recorders = this.recorders.filter(x => x !== recorder);
|
|
137
|
+
return state;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
this.recorders.push(recorder);
|
|
141
|
+
return recorder;
|
|
142
|
+
}
|
|
133
143
|
}
|
package/src/Selection.js
CHANGED
|
@@ -22,10 +22,13 @@ export class Selection extends Param {
|
|
|
22
22
|
* @param {boolean} [options.cross=false] Boolean flag indicating
|
|
23
23
|
* cross-filtered resolution. If true, selection clauses will not
|
|
24
24
|
* be applied to the clients they are associated with.
|
|
25
|
+
* @param {boolean} [options.empty=false] Boolean flag indicating if a lack
|
|
26
|
+
* of clauses should correspond to an empty selection with no records. This
|
|
27
|
+
* setting determines the default selection state.
|
|
25
28
|
* @returns {Selection} The new Selection instance.
|
|
26
29
|
*/
|
|
27
|
-
static intersect({ cross = false } = {}) {
|
|
28
|
-
return new Selection(new SelectionResolver({ cross }));
|
|
30
|
+
static intersect({ cross = false, empty = false } = {}) {
|
|
31
|
+
return new Selection(new SelectionResolver({ cross, empty }));
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
/**
|
|
@@ -35,10 +38,13 @@ export class Selection extends Param {
|
|
|
35
38
|
* @param {boolean} [options.cross=false] Boolean flag indicating
|
|
36
39
|
* cross-filtered resolution. If true, selection clauses will not
|
|
37
40
|
* be applied to the clients they are associated with.
|
|
41
|
+
* @param {boolean} [options.empty=false] Boolean flag indicating if a lack
|
|
42
|
+
* of clauses should correspond to an empty selection with no records. This
|
|
43
|
+
* setting determines the default selection state.
|
|
38
44
|
* @returns {Selection} The new Selection instance.
|
|
39
45
|
*/
|
|
40
|
-
static union({ cross = false } = {}) {
|
|
41
|
-
return new Selection(new SelectionResolver({ cross, union: true }));
|
|
46
|
+
static union({ cross = false, empty = false } = {}) {
|
|
47
|
+
return new Selection(new SelectionResolver({ cross, empty, union: true }));
|
|
42
48
|
}
|
|
43
49
|
|
|
44
50
|
/**
|
|
@@ -48,19 +54,26 @@ export class Selection extends Param {
|
|
|
48
54
|
* @param {boolean} [options.cross=false] Boolean flag indicating
|
|
49
55
|
* cross-filtered resolution. If true, selection clauses will not
|
|
50
56
|
* be applied to the clients they are associated with.
|
|
57
|
+
* @param {boolean} [options.empty=false] Boolean flag indicating if a lack
|
|
58
|
+
* of clauses should correspond to an empty selection with no records. This
|
|
59
|
+
* setting determines the default selection state.
|
|
51
60
|
* @returns {Selection} The new Selection instance.
|
|
52
61
|
*/
|
|
53
|
-
static single({ cross = false } = {}) {
|
|
54
|
-
return new Selection(new SelectionResolver({ cross, single: true }));
|
|
62
|
+
static single({ cross = false, empty = false } = {}) {
|
|
63
|
+
return new Selection(new SelectionResolver({ cross, empty, single: true }));
|
|
55
64
|
}
|
|
56
65
|
|
|
57
66
|
/**
|
|
58
67
|
* Create a new Selection instance with a
|
|
59
68
|
* cross-filtered intersect resolution strategy.
|
|
69
|
+
* @param {object} [options] The selection options.
|
|
70
|
+
* @param {boolean} [options.empty=false] Boolean flag indicating if a lack
|
|
71
|
+
* of clauses should correspond to an empty selection with no records. This
|
|
72
|
+
* setting determines the default selection state.
|
|
60
73
|
* @returns {Selection} The new Selection instance.
|
|
61
74
|
*/
|
|
62
|
-
static crossfilter() {
|
|
63
|
-
return new Selection(new SelectionResolver({ cross: true }));
|
|
75
|
+
static crossfilter({ empty = false } = {}) {
|
|
76
|
+
return new Selection(new SelectionResolver({ cross: true, empty }));
|
|
64
77
|
}
|
|
65
78
|
|
|
66
79
|
/**
|
|
@@ -98,19 +111,17 @@ export class Selection extends Param {
|
|
|
98
111
|
}
|
|
99
112
|
|
|
100
113
|
/**
|
|
101
|
-
* The
|
|
114
|
+
* The selection clause resolver.
|
|
102
115
|
*/
|
|
103
|
-
get
|
|
104
|
-
return this.
|
|
116
|
+
get resolver() {
|
|
117
|
+
return this._resolver;
|
|
105
118
|
}
|
|
106
119
|
|
|
107
120
|
/**
|
|
108
|
-
*
|
|
109
|
-
* This method ensures compatibility where a normal Param is expected.
|
|
121
|
+
* Indicate if this selection has a single resolution strategy.
|
|
110
122
|
*/
|
|
111
|
-
get
|
|
112
|
-
|
|
113
|
-
return this.active?.value;
|
|
123
|
+
get single() {
|
|
124
|
+
return this._resolver.single;
|
|
114
125
|
}
|
|
115
126
|
|
|
116
127
|
/**
|
|
@@ -121,10 +132,27 @@ export class Selection extends Param {
|
|
|
121
132
|
}
|
|
122
133
|
|
|
123
134
|
/**
|
|
124
|
-
*
|
|
135
|
+
* The current active (most recently updated) selection clause.
|
|
125
136
|
*/
|
|
126
|
-
get
|
|
127
|
-
return this.
|
|
137
|
+
get active() {
|
|
138
|
+
return this.clauses.active;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* The value corresponding to the current active selection clause.
|
|
143
|
+
* This method ensures compatibility where a normal Param is expected.
|
|
144
|
+
*/
|
|
145
|
+
get value() {
|
|
146
|
+
return this.active?.value;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* The value corresponding to a given source. Returns undefined if
|
|
151
|
+
* this selection does not include a clause from this source.
|
|
152
|
+
* @param {*} source The clause source to look up the value for.
|
|
153
|
+
*/
|
|
154
|
+
valueFor(source) {
|
|
155
|
+
return this.clauses.find(c => c.source === source)?.value;
|
|
128
156
|
}
|
|
129
157
|
|
|
130
158
|
/**
|
|
@@ -217,11 +245,15 @@ export class SelectionResolver {
|
|
|
217
245
|
* If false, an intersection strategy is used.
|
|
218
246
|
* @param {boolean} [options.cross=false] Boolean flag to indicate cross-filtering.
|
|
219
247
|
* @param {boolean} [options.single=false] Boolean flag to indicate single clauses only.
|
|
248
|
+
* @param {boolean} [options.empty=false] Boolean flag indicating if a lack
|
|
249
|
+
* of clauses should correspond to an empty selection with no records. This
|
|
250
|
+
* setting determines the default selection state.
|
|
220
251
|
*/
|
|
221
|
-
constructor({ union, cross, single } = {}) {
|
|
252
|
+
constructor({ union, cross, single, empty } = {}) {
|
|
222
253
|
this.union = !!union;
|
|
223
254
|
this.cross = !!cross;
|
|
224
255
|
this.single = !!single;
|
|
256
|
+
this.empty = !!empty;
|
|
225
257
|
}
|
|
226
258
|
|
|
227
259
|
/**
|
|
@@ -259,7 +291,11 @@ export class SelectionResolver {
|
|
|
259
291
|
* based on the current state of this selection.
|
|
260
292
|
*/
|
|
261
293
|
predicate(clauseList, active, client) {
|
|
262
|
-
const { union } = this;
|
|
294
|
+
const { empty, union } = this;
|
|
295
|
+
|
|
296
|
+
if (empty && !clauseList.length) {
|
|
297
|
+
return ['FALSE'];
|
|
298
|
+
}
|
|
263
299
|
|
|
264
300
|
// do nothing if cross-filtering and client is currently active
|
|
265
301
|
if (this.skip(client, active)) return undefined;
|
|
@@ -0,0 +1,161 @@
|
|
|
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 clausePoint(field, value, {
|
|
28
|
+
source,
|
|
29
|
+
clients = source ? new Set([source]) : undefined
|
|
30
|
+
}) {
|
|
31
|
+
/** @type {SQLExpression | null} */
|
|
32
|
+
const predicate = value !== undefined
|
|
33
|
+
? isNotDistinct(field, literal(value))
|
|
34
|
+
: null;
|
|
35
|
+
return {
|
|
36
|
+
meta: { type: 'point' },
|
|
37
|
+
source,
|
|
38
|
+
clients,
|
|
39
|
+
value,
|
|
40
|
+
predicate
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Generate a selection clause for multiple selected point values.
|
|
46
|
+
* @param {Field[]} fields The table columns or expressions to select.
|
|
47
|
+
* @param {any[][]} value The selected values, as an array of arrays where
|
|
48
|
+
* each subarray contains values corresponding to each *fields* entry.
|
|
49
|
+
* @param {object} options Additional clause properties.
|
|
50
|
+
* @param {*} options.source The source component generating this clause.
|
|
51
|
+
* @param {Set<MosaicClient>} [options.clients] The Mosaic clients associated
|
|
52
|
+
* with this clause. These clients are not filtered by this clause in
|
|
53
|
+
* cross-filtering contexts.
|
|
54
|
+
* @returns {SelectionClause} The generated selection clause.
|
|
55
|
+
*/
|
|
56
|
+
export function clausePoints(fields, value, {
|
|
57
|
+
source,
|
|
58
|
+
clients = source ? new Set([source]) : undefined
|
|
59
|
+
}) {
|
|
60
|
+
/** @type {SQLExpression | null} */
|
|
61
|
+
let predicate = null;
|
|
62
|
+
if (value) {
|
|
63
|
+
const clauses = value.map(vals => {
|
|
64
|
+
const list = vals.map((v, i) => isNotDistinct(fields[i], literal(v)));
|
|
65
|
+
return list.length > 1 ? and(list) : list[0];
|
|
66
|
+
});
|
|
67
|
+
predicate = clauses.length > 1 ? or(clauses) : clauses[0];
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
meta: { type: 'point' },
|
|
71
|
+
source,
|
|
72
|
+
clients,
|
|
73
|
+
value,
|
|
74
|
+
predicate
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Generate a selection clause for a selected 1D interval.
|
|
80
|
+
* @param {Field} field The table column or expression to select.
|
|
81
|
+
* @param {Extent} value The selected interval as a [lo, hi] array.
|
|
82
|
+
* @param {object} options Additional clause properties.
|
|
83
|
+
* @param {*} options.source The source component generating this clause.
|
|
84
|
+
* @param {Set<MosaicClient>} [options.clients] The Mosaic clients associated
|
|
85
|
+
* with this clause. These clients are not filtered by this clause in
|
|
86
|
+
* cross-filtering contexts.
|
|
87
|
+
* @param {Scale} [options.scale] The scale mapping descriptor.
|
|
88
|
+
* @param {BinMethod} [options.bin] A binning method hint.
|
|
89
|
+
* @param {number} [options.pixelSize=1] The interactive pixel size.
|
|
90
|
+
* @returns {SelectionClause} The generated selection clause.
|
|
91
|
+
*/
|
|
92
|
+
export function clauseInterval(field, value, {
|
|
93
|
+
source,
|
|
94
|
+
clients = source ? new Set([source]) : undefined,
|
|
95
|
+
bin,
|
|
96
|
+
scale,
|
|
97
|
+
pixelSize = 1
|
|
98
|
+
}) {
|
|
99
|
+
/** @type {SQLExpression | null} */
|
|
100
|
+
const predicate = value != null ? isBetween(field, value) : null;
|
|
101
|
+
/** @type {import('./util/selection-types.js').IntervalMetadata} */
|
|
102
|
+
const meta = { type: 'interval', scales: scale && [scale], bin, pixelSize };
|
|
103
|
+
return { meta, source, clients, value, predicate };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Generate a selection clause for multiple selected intervals.
|
|
108
|
+
* @param {Field[]} fields The table columns or expressions to select.
|
|
109
|
+
* @param {Extent[]} value The selected intervals, as an array of extents.
|
|
110
|
+
* @param {object} options Additional clause properties.
|
|
111
|
+
* @param {*} options.source The source component generating this clause.
|
|
112
|
+
* @param {Set<MosaicClient>} [options.clients] The Mosaic clients associated
|
|
113
|
+
* with this clause. These clients are not filtered by this clause in
|
|
114
|
+
* cross-filtering contexts.
|
|
115
|
+
* @param {Scale[]} [options.scales] The scale mapping descriptors,
|
|
116
|
+
* in an order matching the given *fields* and *value* extents.
|
|
117
|
+
* @param {BinMethod} [options.bin] A binning method hint.
|
|
118
|
+
* @param {number} [options.pixelSize=1] The interactive pixel size.
|
|
119
|
+
* @returns {SelectionClause} The generated selection clause.
|
|
120
|
+
*/
|
|
121
|
+
export function clauseIntervals(fields, value, {
|
|
122
|
+
source,
|
|
123
|
+
clients = source ? new Set([source]) : undefined,
|
|
124
|
+
bin,
|
|
125
|
+
scales = [],
|
|
126
|
+
pixelSize = 1
|
|
127
|
+
}) {
|
|
128
|
+
/** @type {SQLExpression | null} */
|
|
129
|
+
const predicate = value != null
|
|
130
|
+
? and(fields.map((f, i) => isBetween(f, value[i])))
|
|
131
|
+
: null;
|
|
132
|
+
/** @type {import('./util/selection-types.js').IntervalMetadata} */
|
|
133
|
+
const meta = { type: 'interval', scales, bin, pixelSize };
|
|
134
|
+
return { meta, source, clients, value, predicate };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const MATCH_METHODS = { contains, prefix, suffix, regexp: regexp_matches };
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Generate a selection clause for text search matching.
|
|
141
|
+
* @param {Field} field The table column or expression to select.
|
|
142
|
+
* @param {string} value The selected text search query string.
|
|
143
|
+
* @param {object} options Additional clause properties.
|
|
144
|
+
* @param {*} options.source The source component generating this clause.
|
|
145
|
+
* @param {Set<MosaicClient>} [options.clients] The Mosaic clients associated
|
|
146
|
+
* with this clause. These clients are not filtered by this clause in
|
|
147
|
+
* cross-filtering contexts.
|
|
148
|
+
* @param {MatchMethod} [options.method] The
|
|
149
|
+
* text matching method to use. Defaults to `'contains'`.
|
|
150
|
+
* @returns {SelectionClause} The generated selection clause.
|
|
151
|
+
*/
|
|
152
|
+
export function clauseMatch(field, value, {
|
|
153
|
+
source, clients = undefined, method = 'contains'
|
|
154
|
+
}) {
|
|
155
|
+
let fn = MATCH_METHODS[method];
|
|
156
|
+
/** @type {SQLExpression | null} */
|
|
157
|
+
const predicate = value ? fn(field, literal(value)) : null;
|
|
158
|
+
/** @type {import('./util/selection-types.js').MatchMetadata} */
|
|
159
|
+
const meta = { type: 'match', method };
|
|
160
|
+
return { meta, source, clients, value, predicate };
|
|
161
|
+
}
|
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
|
@@ -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
|
@@ -8,12 +8,22 @@ export { restConnector } from './connectors/rest.js';
|
|
|
8
8
|
export { socketConnector } from './connectors/socket.js';
|
|
9
9
|
export { wasmConnector } from './connectors/wasm.js';
|
|
10
10
|
|
|
11
|
+
export {
|
|
12
|
+
clauseInterval,
|
|
13
|
+
clauseIntervals,
|
|
14
|
+
clausePoint,
|
|
15
|
+
clausePoints,
|
|
16
|
+
clauseMatch
|
|
17
|
+
} from './SelectionClause.js';
|
|
18
|
+
|
|
11
19
|
export {
|
|
12
20
|
isArrowTable,
|
|
13
21
|
convertArrowArrayType,
|
|
14
22
|
convertArrowValue,
|
|
15
23
|
convertArrowColumn
|
|
16
24
|
} from './util/convert-arrow.js'
|
|
25
|
+
|
|
17
26
|
export { distinct } from './util/distinct.js';
|
|
18
27
|
export { synchronizer } from './util/synchronizer.js';
|
|
19
28
|
export { throttle } from './util/throttle.js';
|
|
29
|
+
export { toDataColumns } from './util/to-data-columns.js'
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Event dispatcher supporting asynchronous updates.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* of the same type.
|
|
2
|
+
* Event dispatcher supporting asynchronous updates. If an event handler
|
|
3
|
+
* callback returns a Promise, the dispatcher waits for all such Promises
|
|
4
|
+
* to settle before dispatching future events of the same type.
|
|
6
5
|
*/
|
|
7
6
|
export class AsyncDispatch {
|
|
8
7
|
|
|
@@ -63,7 +62,7 @@ export class AsyncDispatch {
|
|
|
63
62
|
* queue of unemitted event values prior to enqueueing a new value.
|
|
64
63
|
* This default implementation simply returns null, indicating that
|
|
65
64
|
* any other unemitted event values should be dropped (that is, all
|
|
66
|
-
* queued events are filtered)
|
|
65
|
+
* queued events are filtered).
|
|
67
66
|
* @param {string} type The event type.
|
|
68
67
|
* @param {*} value The new event value that will be enqueued.
|
|
69
68
|
* @returns {(value: *) => boolean|null} A dispatch queue filter
|
|
@@ -83,6 +82,17 @@ export class AsyncDispatch {
|
|
|
83
82
|
entry?.queue.clear();
|
|
84
83
|
}
|
|
85
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Returns a promise that resolves when any pending updates complete for
|
|
87
|
+
* the event of the given type currently being processed. The Promise will
|
|
88
|
+
* resolve immediately if the queue for the given event type is empty.
|
|
89
|
+
* @param {string} type The event type to wait for.
|
|
90
|
+
* @returns {Promise} A pending event promise.
|
|
91
|
+
*/
|
|
92
|
+
async pending(type) {
|
|
93
|
+
await this._callbacks.get(type)?.pending;
|
|
94
|
+
}
|
|
95
|
+
|
|
86
96
|
/**
|
|
87
97
|
* Emit an event value to listeners for the given event type.
|
|
88
98
|
* If a previous emit has not yet resolved, the event value
|