@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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uwdata/mosaic-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.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-dev195.0",
|
|
32
|
+
"@uwdata/mosaic-sql": "^0.9.0",
|
|
33
|
+
"apache-arrow": "^15.0.2"
|
|
34
34
|
},
|
|
35
|
-
"
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@uwdata/mosaic-duckdb": "^0.9.0"
|
|
37
|
+
},
|
|
38
|
+
"gitHead": "89bb9b0dfa747aed691eaeba35379525a6764c61"
|
|
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) {
|
|
@@ -25,7 +24,7 @@ export class Coordinator {
|
|
|
25
24
|
constructor(db = socketConnector(), options = {}) {
|
|
26
25
|
const {
|
|
27
26
|
logger = console,
|
|
28
|
-
manager = QueryManager()
|
|
27
|
+
manager = new QueryManager()
|
|
29
28
|
} = options;
|
|
30
29
|
this.manager = manager;
|
|
31
30
|
this.logger(logger);
|
|
@@ -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
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Query, and, create, isBetween, scaleTransform, sql } from '@uwdata/mosaic-sql';
|
|
1
|
+
import { Query, and, asColumn, create, isBetween, scaleTransform, sql } from '@uwdata/mosaic-sql';
|
|
2
2
|
import { fnv_hash } from './util/hash.js';
|
|
3
|
+
import { indexColumns } from './util/index-columns.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Build and query optimized indices ("data cubes") for fast computation of
|
|
@@ -9,7 +10,7 @@ import { fnv_hash } from './util/hash.js';
|
|
|
9
10
|
* as temporary database tables that can be queried for rapid updates.
|
|
10
11
|
* Compatible client queries must pull data from the same backing table and
|
|
11
12
|
* must consist of only groupby dimensions and supported aggregates.
|
|
12
|
-
* Compatible selections must contain an active clause that exposes
|
|
13
|
+
* Compatible selections must contain an active clause that exposes metadata
|
|
13
14
|
* for an interval or point value predicate.
|
|
14
15
|
*/
|
|
15
16
|
export class DataCubeIndexer {
|
|
@@ -30,38 +31,40 @@ export class DataCubeIndexer {
|
|
|
30
31
|
this.enabled = false;
|
|
31
32
|
this.clients = null;
|
|
32
33
|
this.indices = null;
|
|
33
|
-
this.
|
|
34
|
+
this.active = null;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
clear() {
|
|
37
38
|
if (this.indices) {
|
|
38
|
-
this.mc.cancel(Array.from(this.indices.values(), index => index
|
|
39
|
+
this.mc.cancel(Array.from(this.indices.values(), index => index?.result));
|
|
39
40
|
this.indices = null;
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
index(clients,
|
|
44
|
+
index(clients, activeClause) {
|
|
44
45
|
if (this.clients !== clients) {
|
|
45
46
|
// test client views for compatibility
|
|
46
|
-
const cols = Array.from(clients,
|
|
47
|
+
const cols = Array.from(clients, indexColumns).filter(x => x);
|
|
47
48
|
const from = cols[0]?.from;
|
|
48
|
-
this.enabled = cols.every(c => c
|
|
49
|
+
this.enabled = cols.length && cols.every(c => c.from === from);
|
|
49
50
|
this.clients = clients;
|
|
50
|
-
this.
|
|
51
|
+
this.active = null;
|
|
51
52
|
this.clear();
|
|
52
53
|
}
|
|
53
54
|
if (!this.enabled) return false; // client views are not indexable
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
const { source } =
|
|
57
|
-
|
|
56
|
+
activeClause = activeClause || this.selection.active;
|
|
57
|
+
const { source } = activeClause;
|
|
58
|
+
// exit early if indexes already set up for active view
|
|
59
|
+
if (source && source === this.active?.source) return true;
|
|
58
60
|
|
|
59
61
|
this.clear();
|
|
60
62
|
if (!source) return false; // nothing to work with
|
|
61
|
-
const
|
|
62
|
-
if (!
|
|
63
|
+
const active = this.active = activeColumns(activeClause);
|
|
64
|
+
if (!active) return false; // active selection clause not compatible
|
|
63
65
|
|
|
64
|
-
this.mc.logger()
|
|
66
|
+
const logger = this.mc.logger();
|
|
67
|
+
logger.warn('DATA CUBE INDEX CONSTRUCTION');
|
|
65
68
|
|
|
66
69
|
// create a selection with the active source removed
|
|
67
70
|
const sel = this.selection.remove(source);
|
|
@@ -70,18 +73,29 @@ export class DataCubeIndexer {
|
|
|
70
73
|
const indices = this.indices = new Map;
|
|
71
74
|
const { mc, temp } = this;
|
|
72
75
|
for (const client of clients) {
|
|
73
|
-
if
|
|
74
|
-
|
|
76
|
+
// determine if client should be skipped due to cross-filtering
|
|
77
|
+
if (sel.skip(client, activeClause)) {
|
|
78
|
+
indices.set(client, null);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// generate column definitions for data cube and cube queries
|
|
83
|
+
const index = indexColumns(client);
|
|
84
|
+
|
|
85
|
+
// skip if client is not indexable
|
|
86
|
+
if (!index) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
75
89
|
|
|
76
|
-
// build index construction query
|
|
90
|
+
// build index table construction query
|
|
77
91
|
const query = client.query(sel.predicate(client))
|
|
78
|
-
.select({ ...
|
|
79
|
-
.groupby(Object.keys(
|
|
92
|
+
.select({ ...active.columns, ...index.aux })
|
|
93
|
+
.groupby(Object.keys(active.columns));
|
|
80
94
|
|
|
81
95
|
// ensure active view columns are selected by subqueries
|
|
82
96
|
const [subq] = query.subqueries;
|
|
83
97
|
if (subq) {
|
|
84
|
-
const cols = Object.values(
|
|
98
|
+
const cols = Object.values(active.columns).flatMap(c => c.columns);
|
|
85
99
|
subqueryPushdown(subq, cols);
|
|
86
100
|
}
|
|
87
101
|
|
|
@@ -93,26 +107,37 @@ export class DataCubeIndexer {
|
|
|
93
107
|
const id = (fnv_hash(sql) >>> 0).toString(16);
|
|
94
108
|
const table = `cube_index_${id}`;
|
|
95
109
|
const result = mc.exec(create(table, sql, { temp }));
|
|
110
|
+
result.catch(e => logger.error(e));
|
|
96
111
|
indices.set(client, { table, result, order, ...index });
|
|
97
112
|
}
|
|
113
|
+
|
|
114
|
+
// index creation successful
|
|
115
|
+
return true;
|
|
98
116
|
}
|
|
99
117
|
|
|
100
118
|
async update() {
|
|
101
|
-
const { clients, selection,
|
|
102
|
-
const filter =
|
|
119
|
+
const { clients, selection, active } = this;
|
|
120
|
+
const filter = active.predicate(selection.active.predicate);
|
|
103
121
|
return Promise.all(
|
|
104
122
|
Array.from(clients).map(client => this.updateClient(client, filter))
|
|
105
123
|
);
|
|
106
124
|
}
|
|
107
125
|
|
|
108
126
|
async updateClient(client, filter) {
|
|
127
|
+
const { mc, indices, selection } = this;
|
|
128
|
+
|
|
129
|
+
// if client has no index, perform a standard update
|
|
130
|
+
if (!indices.has(client)) {
|
|
131
|
+
filter = selection.predicate(client);
|
|
132
|
+
return mc.updateClient(client, client.query(filter));
|
|
133
|
+
};
|
|
134
|
+
|
|
109
135
|
const index = this.indices.get(client);
|
|
110
|
-
if (!index) return;
|
|
111
136
|
|
|
112
|
-
if
|
|
113
|
-
|
|
114
|
-
}
|
|
137
|
+
// skip update if cross-filtered
|
|
138
|
+
if (!index) return;
|
|
115
139
|
|
|
140
|
+
// otherwise, query a data cube index table
|
|
116
141
|
const { table, dims, aggr, order = [] } = index;
|
|
117
142
|
const query = Query
|
|
118
143
|
.select(dims, aggr)
|
|
@@ -120,25 +145,30 @@ export class DataCubeIndexer {
|
|
|
120
145
|
.groupby(dims)
|
|
121
146
|
.where(filter)
|
|
122
147
|
.orderby(order);
|
|
123
|
-
return
|
|
148
|
+
return mc.updateClient(client, query);
|
|
124
149
|
}
|
|
125
150
|
}
|
|
126
151
|
|
|
127
|
-
function
|
|
128
|
-
const { source,
|
|
152
|
+
function activeColumns(clause) {
|
|
153
|
+
const { source, meta } = clause;
|
|
129
154
|
let columns = clause.predicate?.columns;
|
|
130
|
-
if (!
|
|
131
|
-
const { type, scales, pixelSize = 1 } =
|
|
155
|
+
if (!meta || !columns) return null;
|
|
156
|
+
const { type, scales, bin, pixelSize = 1 } = meta;
|
|
132
157
|
let predicate;
|
|
133
158
|
|
|
134
159
|
if (type === 'interval' && scales) {
|
|
135
|
-
|
|
136
|
-
|
|
160
|
+
// determine pixel-level binning
|
|
161
|
+
const bins = scales.map(s => binInterval(s, pixelSize, bin));
|
|
162
|
+
|
|
163
|
+
// bail if the scale type is unsupported
|
|
164
|
+
if (bins.some(b => b == null)) return null;
|
|
137
165
|
|
|
138
166
|
if (bins.length === 1) {
|
|
167
|
+
// single interval selection
|
|
139
168
|
predicate = p => p ? isBetween('active0', p.range.map(bins[0])) : [];
|
|
140
169
|
columns = { active0: bins[0](clause.predicate.field) };
|
|
141
170
|
} else {
|
|
171
|
+
// multiple interval selection
|
|
142
172
|
predicate = p => p
|
|
143
173
|
? and(p.children.map(({ range }, i) => isBetween(`active${i}`, range.map(bins[i]))))
|
|
144
174
|
: [];
|
|
@@ -148,87 +178,27 @@ function getActiveView(clause) {
|
|
|
148
178
|
}
|
|
149
179
|
} else if (type === 'point') {
|
|
150
180
|
predicate = x => x;
|
|
151
|
-
columns = Object.fromEntries(columns.map(col => [col
|
|
181
|
+
columns = Object.fromEntries(columns.map(col => [`${col}`, asColumn(col)]));
|
|
152
182
|
} else {
|
|
153
|
-
|
|
183
|
+
// unsupported selection type
|
|
184
|
+
return null;
|
|
154
185
|
}
|
|
155
186
|
|
|
156
187
|
return { source, columns, predicate };
|
|
157
188
|
}
|
|
158
189
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const NO_INDEX = { from: NaN };
|
|
172
|
-
|
|
173
|
-
function getIndexColumns(client) {
|
|
174
|
-
if (!client.filterIndexable) return NO_INDEX;
|
|
175
|
-
const q = client.query();
|
|
176
|
-
const from = getBaseTable(q);
|
|
177
|
-
if (!from || !q.groupby) return NO_INDEX;
|
|
178
|
-
const g = new Set(q.groupby().map(c => c.column));
|
|
179
|
-
|
|
180
|
-
const aggr = [];
|
|
181
|
-
const dims = [];
|
|
182
|
-
let count;
|
|
183
|
-
|
|
184
|
-
for (const { as, expr: { aggregate } } of q.select()) {
|
|
185
|
-
switch (aggregate?.toUpperCase?.()) {
|
|
186
|
-
case 'COUNT':
|
|
187
|
-
case 'SUM':
|
|
188
|
-
aggr.push({ [as]: sql`SUM("${as}")::DOUBLE` });
|
|
189
|
-
break;
|
|
190
|
-
case 'AVG':
|
|
191
|
-
count = '_count_';
|
|
192
|
-
aggr.push({ [as]: sql`(SUM("${as}" * ${count}) / SUM(${count}))::DOUBLE` });
|
|
193
|
-
break;
|
|
194
|
-
case 'MAX':
|
|
195
|
-
aggr.push({ [as]: sql`MAX("${as}")` });
|
|
196
|
-
break;
|
|
197
|
-
case 'MIN':
|
|
198
|
-
aggr.push({ [as]: sql`MIN("${as}")` });
|
|
199
|
-
break;
|
|
200
|
-
default:
|
|
201
|
-
if (g.has(as)) dims.push(as);
|
|
202
|
-
else return null;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return {
|
|
207
|
-
aggr,
|
|
208
|
-
dims,
|
|
209
|
-
count: count ? { [count]: sql`COUNT(*)` } : {},
|
|
210
|
-
from
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function getBaseTable(query) {
|
|
215
|
-
const subq = query.subqueries;
|
|
216
|
-
|
|
217
|
-
// select query
|
|
218
|
-
if (query.select) {
|
|
219
|
-
const from = query.from();
|
|
220
|
-
if (!from.length) return undefined;
|
|
221
|
-
if (subq.length === 0) return from[0].from.table;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// handle set operations / subqueries
|
|
225
|
-
const base = getBaseTable(subq[0]);
|
|
226
|
-
for (let i = 1; i < subq.length; ++i) {
|
|
227
|
-
const from = getBaseTable(subq[i]);
|
|
228
|
-
if (from === undefined) continue;
|
|
229
|
-
if (from !== base) return NaN;
|
|
230
|
-
}
|
|
231
|
-
return base;
|
|
190
|
+
const BIN = { ceil: 'CEIL', round: 'ROUND' };
|
|
191
|
+
|
|
192
|
+
function binInterval(scale, pixelSize, bin) {
|
|
193
|
+
const { type, domain, range, apply, sqlApply } = scaleTransform(scale);
|
|
194
|
+
if (!apply) return; // unsupported scale type
|
|
195
|
+
const fn = BIN[`${bin}`.toLowerCase()] || 'FLOOR';
|
|
196
|
+
const lo = apply(Math.min(...domain));
|
|
197
|
+
const hi = apply(Math.max(...domain));
|
|
198
|
+
const a = type === 'identity' ? 1 : Math.abs(range[1] - range[0]) / (hi - lo);
|
|
199
|
+
const s = a / pixelSize === 1 ? '' : `${a / pixelSize}::DOUBLE * `;
|
|
200
|
+
const d = lo === 0 ? '' : ` - ${lo}::DOUBLE`;
|
|
201
|
+
return value => sql`${fn}(${s}(${sqlApply(value)}${d}))::INTEGER`;
|
|
232
202
|
}
|
|
233
203
|
|
|
234
204
|
function subqueryPushdown(query, cols) {
|
package/src/FilterGroup.js
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
|
+
import { Coordinator } from './Coordinator.js';
|
|
1
2
|
import { DataCubeIndexer } from './DataCubeIndexer.js';
|
|
3
|
+
import { MosaicClient } from './MosaicClient.js';
|
|
4
|
+
import { Selection } from './Selection.js';
|
|
2
5
|
|
|
3
6
|
export class FilterGroup {
|
|
4
7
|
/**
|
|
5
|
-
* @param {
|
|
6
|
-
* @param {
|
|
7
|
-
* @param {
|
|
8
|
-
* Falsy values disable indexing.
|
|
8
|
+
* @param {Coordinator} coordinator The Mosaic coordinator.
|
|
9
|
+
* @param {Selection} selection The shared filter selection.
|
|
10
|
+
* @param {object|boolean} index Boolean flag or options hash for
|
|
11
|
+
* a data cube indexer. Falsy values disable indexing.
|
|
9
12
|
*/
|
|
10
13
|
constructor(coordinator, selection, index = true) {
|
|
11
|
-
/** @type import('./Coordinator.js').Coordinator */
|
|
12
14
|
this.mc = coordinator;
|
|
13
15
|
this.selection = selection;
|
|
16
|
+
/** @type {Set<MosaicClient>} */
|
|
14
17
|
this.clients = new Set();
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
/** @type {DataCubeIndexer | null} */
|
|
19
|
+
this.indexer = null;
|
|
20
|
+
this.index(index);
|
|
18
21
|
|
|
19
22
|
const { value, activate } = this.handlers = {
|
|
20
23
|
value: () => this.update(),
|
|
21
|
-
activate: clause => this.indexer?.index(this.clients, clause)
|
|
24
|
+
activate: clause => { this.indexer?.index(this.clients, clause); }
|
|
22
25
|
};
|
|
23
26
|
selection.addEventListener('value', value);
|
|
24
27
|
selection.addEventListener('activate', activate);
|
|
@@ -30,6 +33,14 @@ export class FilterGroup {
|
|
|
30
33
|
this.selection.removeEventListener('activate', activate);
|
|
31
34
|
}
|
|
32
35
|
|
|
36
|
+
index(state) {
|
|
37
|
+
const { selection } = this;
|
|
38
|
+
const { resolver } = selection;
|
|
39
|
+
this.indexer = state && (resolver.single || !resolver.union)
|
|
40
|
+
? new DataCubeIndexer(this.mc, { ...state, selection })
|
|
41
|
+
: null;
|
|
42
|
+
}
|
|
43
|
+
|
|
33
44
|
reset() {
|
|
34
45
|
this.indexer?.reset();
|
|
35
46
|
}
|
|
@@ -46,9 +57,15 @@ export class FilterGroup {
|
|
|
46
57
|
return this;
|
|
47
58
|
}
|
|
48
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Internal method to process a selection update.
|
|
62
|
+
* The return value is passed as a selection callback value.
|
|
63
|
+
* @returns {Promise} A Promise that resolves when the update completes.
|
|
64
|
+
*/
|
|
49
65
|
update() {
|
|
50
66
|
const { mc, indexer, clients, selection } = this;
|
|
51
|
-
|
|
67
|
+
const hasIndex = indexer?.index(clients);
|
|
68
|
+
return hasIndex
|
|
52
69
|
? indexer.update()
|
|
53
70
|
: defaultUpdate(mc, clients, selection);
|
|
54
71
|
}
|
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
|
|