@uwdata/mosaic-core 0.13.0 → 0.14.1
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/types/Coordinator.d.ts +1 -1
- package/dist/types/MosaicClient.d.ts +7 -5
- package/dist/types/Selection.d.ts +15 -4
- package/package.json +4 -4
- package/src/Coordinator.js +6 -2
- package/src/MosaicClient.js +9 -7
- package/src/Selection.js +20 -2
- package/src/preagg/PreAggregator.js +31 -2
- package/src/preagg/sufficient-statistics.js +17 -3
|
@@ -155,7 +155,7 @@ export class Coordinator {
|
|
|
155
155
|
* Connect a client to the coordinator.
|
|
156
156
|
* @param {MosaicClient} client The Mosaic client to connect.
|
|
157
157
|
*/
|
|
158
|
-
connect(client: MosaicClient):
|
|
158
|
+
connect(client: MosaicClient): void;
|
|
159
159
|
initializeClient(client: any): Promise<any>;
|
|
160
160
|
/**
|
|
161
161
|
* Disconnect a client from the coordinator.
|
|
@@ -81,7 +81,8 @@ export class MosaicClient {
|
|
|
81
81
|
/**
|
|
82
82
|
* Request the coordinator to execute a query for this client.
|
|
83
83
|
* If an explicit query is not provided, the client query method will
|
|
84
|
-
* be called, filtered by the current filterBy selection.
|
|
84
|
+
* be called, filtered by the current filterBy selection. This method
|
|
85
|
+
* has no effect if the client is not registered with a coordinator.
|
|
85
86
|
* @returns {Promise}
|
|
86
87
|
*/
|
|
87
88
|
requestQuery(query: any): Promise<any>;
|
|
@@ -93,14 +94,15 @@ export class MosaicClient {
|
|
|
93
94
|
*/
|
|
94
95
|
requestUpdate(): void;
|
|
95
96
|
/**
|
|
96
|
-
* Reset this client, initiating new field info, call the prepare method,
|
|
97
|
+
* Reset this client, initiating new field info, call the prepare method,
|
|
98
|
+
* and query requests. This method has no effect if the client is not
|
|
99
|
+
* registered with a coordinator.
|
|
97
100
|
* @returns {Promise}
|
|
98
101
|
*/
|
|
99
102
|
initialize(): Promise<any>;
|
|
100
103
|
/**
|
|
101
|
-
* Requests a client update
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
+
* Requests a client update, for example to (re-)render an interface
|
|
105
|
+
* component.
|
|
104
106
|
* @returns {this | Promise<any>}
|
|
105
107
|
*/
|
|
106
108
|
update(): this | Promise<any>;
|
|
@@ -133,15 +133,25 @@ export class Selection extends Param {
|
|
|
133
133
|
valueFor(source: any): any;
|
|
134
134
|
/**
|
|
135
135
|
* Emit an activate event with the given selection clause.
|
|
136
|
-
* @param {
|
|
136
|
+
* @param {SelectionClause} clause The clause repesenting the potential activation.
|
|
137
137
|
*/
|
|
138
|
-
activate(clause:
|
|
138
|
+
activate(clause: SelectionClause): void;
|
|
139
139
|
/**
|
|
140
140
|
* Update the selection with a new selection clause.
|
|
141
|
-
* @param {
|
|
141
|
+
* @param {SelectionClause} clause The selection clause to add.
|
|
142
142
|
* @returns {this} This Selection instance.
|
|
143
143
|
*/
|
|
144
|
-
update(clause:
|
|
144
|
+
update(clause: SelectionClause): this;
|
|
145
|
+
/**
|
|
146
|
+
* Reset the selection state by removing all provided clauses. If no clause
|
|
147
|
+
* array is provided as an argument, all current clauses are removed. The
|
|
148
|
+
* reset method (if defined) is invoked on all corresponding clause sources.
|
|
149
|
+
* The reset is relayed to downstream selections that include this selection.
|
|
150
|
+
* @param {SelectionClause[]} [clauses] The clauses to remove. If
|
|
151
|
+
* unspecified, all current clauses are removed.
|
|
152
|
+
* @returns {this} This selection instance.
|
|
153
|
+
*/
|
|
154
|
+
reset(clauses?: SelectionClause[]): this;
|
|
145
155
|
/**
|
|
146
156
|
* Indicates if a selection clause should not be applied to a given client.
|
|
147
157
|
* The return value depends on the selection resolution strategy.
|
|
@@ -221,4 +231,5 @@ export class SelectionResolver {
|
|
|
221
231
|
queueFilter(value: any): (value: any) => boolean | null;
|
|
222
232
|
}
|
|
223
233
|
import { Param } from './Param.js';
|
|
234
|
+
import type { SelectionClause } from './util/selection-types.js';
|
|
224
235
|
import { MosaicClient } from './MosaicClient.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uwdata/mosaic-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"description": "Scalable and extensible linked data views.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mosaic",
|
|
@@ -32,10 +32,10 @@
|
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@duckdb/duckdb-wasm": "^1.29.0",
|
|
34
34
|
"@uwdata/flechette": "^2.0.0",
|
|
35
|
-
"@uwdata/mosaic-sql": "^0.
|
|
35
|
+
"@uwdata/mosaic-sql": "^0.14.1"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@uwdata/mosaic-duckdb": "^0.
|
|
38
|
+
"@uwdata/mosaic-duckdb": "^0.14.1"
|
|
39
39
|
},
|
|
40
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "5959cb169e95bd8467e1e5d7a9b98954f09474f3"
|
|
41
41
|
}
|
package/src/Coordinator.js
CHANGED
|
@@ -222,13 +222,17 @@ export class Coordinator {
|
|
|
222
222
|
* Connect a client to the coordinator.
|
|
223
223
|
* @param {MosaicClient} client The Mosaic client to connect.
|
|
224
224
|
*/
|
|
225
|
-
|
|
225
|
+
connect(client) {
|
|
226
226
|
const { clients } = this;
|
|
227
227
|
|
|
228
228
|
if (clients.has(client)) {
|
|
229
229
|
throw new Error('Client already connected.');
|
|
230
230
|
}
|
|
231
|
-
|
|
231
|
+
|
|
232
|
+
// add client to client set
|
|
233
|
+
clients.add(client);
|
|
234
|
+
|
|
235
|
+
// register coordinator on client instance
|
|
232
236
|
client.coordinator = this;
|
|
233
237
|
|
|
234
238
|
// initialize client lifecycle
|
package/src/MosaicClient.js
CHANGED
|
@@ -123,12 +123,13 @@ export class MosaicClient {
|
|
|
123
123
|
/**
|
|
124
124
|
* Request the coordinator to execute a query for this client.
|
|
125
125
|
* If an explicit query is not provided, the client query method will
|
|
126
|
-
* be called, filtered by the current filterBy selection.
|
|
126
|
+
* be called, filtered by the current filterBy selection. This method
|
|
127
|
+
* has no effect if the client is not registered with a coordinator.
|
|
127
128
|
* @returns {Promise}
|
|
128
129
|
*/
|
|
129
130
|
requestQuery(query) {
|
|
130
131
|
const q = query || this.query(this.filterBy?.predicate(this));
|
|
131
|
-
return this._coordinator
|
|
132
|
+
return this._coordinator?.requestQuery(this, q);
|
|
132
133
|
}
|
|
133
134
|
|
|
134
135
|
/**
|
|
@@ -142,17 +143,18 @@ export class MosaicClient {
|
|
|
142
143
|
}
|
|
143
144
|
|
|
144
145
|
/**
|
|
145
|
-
* Reset this client, initiating new field info, call the prepare method,
|
|
146
|
+
* Reset this client, initiating new field info, call the prepare method,
|
|
147
|
+
* and query requests. This method has no effect if the client is not
|
|
148
|
+
* registered with a coordinator.
|
|
146
149
|
* @returns {Promise}
|
|
147
150
|
*/
|
|
148
151
|
initialize() {
|
|
149
|
-
return this._coordinator
|
|
152
|
+
return this._coordinator?.initializeClient(this);
|
|
150
153
|
}
|
|
151
154
|
|
|
152
155
|
/**
|
|
153
|
-
* Requests a client update
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
+
* Requests a client update, for example to (re-)render an interface
|
|
157
|
+
* component.
|
|
156
158
|
* @returns {this | Promise<any>}
|
|
157
159
|
*/
|
|
158
160
|
update() {
|
package/src/Selection.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** @import {SelectionClause} from './util/selection-types.js' */
|
|
1
2
|
import { literal, or } from '@uwdata/mosaic-sql';
|
|
2
3
|
import { Param } from './Param.js';
|
|
3
4
|
import { MosaicClient } from './MosaicClient.js';
|
|
@@ -187,7 +188,7 @@ export class Selection extends Param {
|
|
|
187
188
|
|
|
188
189
|
/**
|
|
189
190
|
* Emit an activate event with the given selection clause.
|
|
190
|
-
* @param {
|
|
191
|
+
* @param {SelectionClause} clause The clause repesenting the potential activation.
|
|
191
192
|
*/
|
|
192
193
|
activate(clause) {
|
|
193
194
|
this.emit('activate', clause);
|
|
@@ -196,7 +197,7 @@ export class Selection extends Param {
|
|
|
196
197
|
|
|
197
198
|
/**
|
|
198
199
|
* Update the selection with a new selection clause.
|
|
199
|
-
* @param {
|
|
200
|
+
* @param {SelectionClause} clause The selection clause to add.
|
|
200
201
|
* @returns {this} This Selection instance.
|
|
201
202
|
*/
|
|
202
203
|
update(clause) {
|
|
@@ -208,6 +209,23 @@ export class Selection extends Param {
|
|
|
208
209
|
return super.update(this._resolved);
|
|
209
210
|
}
|
|
210
211
|
|
|
212
|
+
/**
|
|
213
|
+
* Reset the selection state by removing all provided clauses. If no clause
|
|
214
|
+
* array is provided as an argument, all current clauses are removed. The
|
|
215
|
+
* reset method (if defined) is invoked on all corresponding clause sources.
|
|
216
|
+
* The reset is relayed to downstream selections that include this selection.
|
|
217
|
+
* @param {SelectionClause[]} [clauses] The clauses to remove. If
|
|
218
|
+
* unspecified, all current clauses are removed.
|
|
219
|
+
* @returns {this} This selection instance.
|
|
220
|
+
*/
|
|
221
|
+
reset(clauses) {
|
|
222
|
+
clauses ??= this._resolved;
|
|
223
|
+
clauses.forEach(c => c.source?.reset?.());
|
|
224
|
+
this._resolved = this._resolved.filter(c => clauses.includes(c));
|
|
225
|
+
this._relay.forEach(sel => sel.reset(clauses));
|
|
226
|
+
return super.update(this._resolved = []);
|
|
227
|
+
}
|
|
228
|
+
|
|
211
229
|
/**
|
|
212
230
|
* Upon value-typed updates, sets the current clause list to the
|
|
213
231
|
* input value and returns the active clause value.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Query, and, asNode, ceil, collectColumns, createTable, float64, floor, isBetween, int32, mul, round, scaleTransform, sub, isSelectQuery, ExprNode, SelectQuery } from '@uwdata/mosaic-sql';
|
|
1
|
+
import { Query, and, asNode, ceil, collectColumns, createTable, float64, floor, isBetween, int32, mul, round, scaleTransform, sub, isSelectQuery, ExprNode, SelectQuery, isAggregateExpression, ColumnNameRefNode } from '@uwdata/mosaic-sql';
|
|
2
2
|
import { preaggColumns } from './preagg-columns.js';
|
|
3
3
|
import { fnv_hash } from '../util/hash.js';
|
|
4
4
|
|
|
@@ -134,7 +134,7 @@ export class PreAggregator {
|
|
|
134
134
|
*/
|
|
135
135
|
request(client, selection, activeClause) {
|
|
136
136
|
// if not enabled, do nothing
|
|
137
|
-
if (!this.enabled) return null;
|
|
137
|
+
if (!this.enabled || activeClause == null) return null;
|
|
138
138
|
|
|
139
139
|
const { entries, mc, schema } = this;
|
|
140
140
|
const { source } = activeClause;
|
|
@@ -157,6 +157,10 @@ export class PreAggregator {
|
|
|
157
157
|
|
|
158
158
|
// if cached active columns are unset, analyze the active clause
|
|
159
159
|
if (!active) {
|
|
160
|
+
// if active clause predicate is null, we can't analyze it
|
|
161
|
+
// return null to backoff to standard client query
|
|
162
|
+
// non-null clauses may come later, so don't set active state
|
|
163
|
+
if (activeClause.predicate == null) return null;
|
|
160
164
|
// generate active dimension columns to select over
|
|
161
165
|
// will return an object with null source if it has unstable filters
|
|
162
166
|
this.active = active = activeColumns(activeClause);
|
|
@@ -331,20 +335,45 @@ function preaggregateInfo(clientQuery, active, preaggCols, schema) {
|
|
|
331
335
|
|
|
332
336
|
/**
|
|
333
337
|
* Push column selections down to subqueries.
|
|
338
|
+
* @param {Query} query The (sub)query to push down to.
|
|
339
|
+
* @param {string[]} cols The column names to push down.
|
|
334
340
|
*/
|
|
335
341
|
function subqueryPushdown(query, cols) {
|
|
336
342
|
const memo = new Set;
|
|
337
343
|
const pushdown = q => {
|
|
344
|
+
// it is possible to have duplicate subqueries
|
|
345
|
+
// so we memoize and exit early if already seen
|
|
338
346
|
if (memo.has(q)) return;
|
|
339
347
|
memo.add(q);
|
|
348
|
+
|
|
340
349
|
if (isSelectQuery(q) && q._from.length) {
|
|
350
|
+
// select the pushed down columns
|
|
351
|
+
// note that the select method will deduplicate for us
|
|
341
352
|
q.select(cols);
|
|
353
|
+
if (isAggregateQuery(q)) {
|
|
354
|
+
// if an aggregation query, we need to push to groupby as well
|
|
355
|
+
// we also deduplicate as the column may already be present
|
|
356
|
+
const set = new Set(
|
|
357
|
+
q._groupby.flatMap(x => x instanceof ColumnNameRefNode ? x.name : []
|
|
358
|
+
));
|
|
359
|
+
q.groupby(cols.filter(c => !set.has(c)));
|
|
360
|
+
}
|
|
342
361
|
}
|
|
343
362
|
q.subqueries.forEach(pushdown);
|
|
344
363
|
};
|
|
345
364
|
pushdown(query);
|
|
346
365
|
}
|
|
347
366
|
|
|
367
|
+
/**
|
|
368
|
+
* Test if a query performs aggregation.
|
|
369
|
+
* @param {SelectQuery} query
|
|
370
|
+
* @returns {boolean}
|
|
371
|
+
*/
|
|
372
|
+
function isAggregateQuery(query) {
|
|
373
|
+
return query._groupby.length > 0
|
|
374
|
+
|| query._select.some(node => isAggregateExpression(node));
|
|
375
|
+
}
|
|
376
|
+
|
|
348
377
|
/**
|
|
349
378
|
* Metadata and query generator for materialized views of pre-aggregated data.
|
|
350
379
|
* This object provides the information needed to generate and query the
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AggregateNode, and, argmax, argmin, count, div, exp, ExprNode, isNotNull, ln, max, min, mul, pow, regrAvgX, regrAvgY, regrCount, sql, sqrt, sub, sum } from '@uwdata/mosaic-sql';
|
|
1
|
+
import { AggregateNode, and, argmax, argmin, coalesce, count, div, exp, ExprNode, isNotNull, ln, max, min, mul, pow, regrAvgX, regrAvgY, regrCount, sql, sqrt, sub, sum } from '@uwdata/mosaic-sql';
|
|
2
2
|
import { fnv_hash } from '../util/hash.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -14,6 +14,7 @@ import { fnv_hash } from '../util/hash.js';
|
|
|
14
14
|
export function sufficientStatistics(node, preagg, avg) {
|
|
15
15
|
switch (node.name) {
|
|
16
16
|
case 'count':
|
|
17
|
+
return sumCountExpr(preagg, node);
|
|
17
18
|
case 'sum':
|
|
18
19
|
return sumExpr(preagg, node);
|
|
19
20
|
case 'avg':
|
|
@@ -128,11 +129,24 @@ function addStat(preagg, expr, node) {
|
|
|
128
129
|
*/
|
|
129
130
|
function countExpr(preagg, node) {
|
|
130
131
|
const name = addStat(preagg, count(node.args[0]), node);
|
|
131
|
-
return { expr: sum(name), name };
|
|
132
|
+
return { expr: coalesce(sum(name), 0), name };
|
|
132
133
|
}
|
|
133
134
|
|
|
134
135
|
/**
|
|
135
|
-
* Generate an expression for calculating counts
|
|
136
|
+
* Generate an expression for calculating counts over data dimensions.
|
|
137
|
+
* The expression is a summation with an additional coalesce operation
|
|
138
|
+
* to map null sums to zero-valued counts.
|
|
139
|
+
* @param {Record<string, ExprNode>} preagg A map of columns (such as
|
|
140
|
+
* sufficient statistics) to pre-aggregate.
|
|
141
|
+
* @param {AggregateNode} node The originating aggregate function call.
|
|
142
|
+
* @returns {ExprNode} An aggregate expression over pre-aggregated dimensions.
|
|
143
|
+
*/
|
|
144
|
+
function sumCountExpr(preagg, node) {
|
|
145
|
+
return coalesce(sumExpr(preagg, node), 0);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Generate an expression for calculating sums over data dimensions.
|
|
136
150
|
* @param {Record<string, ExprNode>} preagg A map of columns (such as
|
|
137
151
|
* sufficient statistics) to pre-aggregate.
|
|
138
152
|
* @param {AggregateNode} node The originating aggregate function call.
|