@uwdata/mosaic-core 0.12.2 → 0.14.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/types/Coordinator.d.ts +15 -16
- package/dist/types/MosaicClient.d.ts +26 -10
- package/dist/types/QueryManager.d.ts +3 -1
- package/dist/types/Selection.d.ts +15 -4
- package/dist/types/index-types.d.ts +3 -0
- package/dist/types/index.d.ts +2 -10
- package/dist/types/make-client.d.ts +48 -0
- package/dist/types/types.d.ts +12 -1
- package/dist/types/util/is-activatable.d.ts +6 -0
- package/dist/types/util/throttle.d.ts +5 -3
- package/package.json +12 -12
- package/src/Coordinator.js +20 -19
- package/src/MosaicClient.js +28 -7
- package/src/QueryManager.js +3 -1
- package/src/Selection.js +20 -2
- package/src/connectors/rest.js +9 -2
- package/src/index-types.ts +3 -0
- package/src/index.js +2 -13
- package/src/make-client.js +64 -0
- package/src/preagg/PreAggregator.js +30 -1
- package/src/preagg/sufficient-statistics.js +37 -3
- package/src/types.ts +17 -1
- package/src/util/is-activatable.js +8 -0
- package/src/util/throttle.js +4 -2
- package/vitest.config.ts +3 -0
- package/dist/mosaic-core.js +0 -18788
- package/dist/mosaic-core.min.js +0 -11
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.
|
package/src/connectors/rest.js
CHANGED
|
@@ -21,9 +21,16 @@ export function restConnector(uri = 'http://localhost:3000/') {
|
|
|
21
21
|
body: JSON.stringify(query)
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
+
|
|
25
|
+
const res = await req;
|
|
26
|
+
|
|
27
|
+
if (!res.ok) {
|
|
28
|
+
throw new Error(`Query failed with HTTP status ${res.status}: ${await res.text()}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
24
31
|
return query.type === 'exec' ? req
|
|
25
|
-
: query.type === 'arrow' ? decodeIPC(await
|
|
26
|
-
:
|
|
32
|
+
: query.type === 'arrow' ? decodeIPC(await res.arrayBuffer())
|
|
33
|
+
: res.json();
|
|
27
34
|
}
|
|
28
35
|
};
|
|
29
36
|
}
|
package/src/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { MosaicClient } from './MosaicClient.js';
|
|
2
|
+
export { makeClient } from './make-client.js';
|
|
2
3
|
export { Coordinator, coordinator } from './Coordinator.js';
|
|
3
4
|
export { Selection, isSelection } from './Selection.js';
|
|
4
5
|
export { Param, isParam } from './Param.js';
|
|
@@ -24,16 +25,4 @@ export { throttle } from './util/throttle.js';
|
|
|
24
25
|
export { toDataColumns } from './util/to-data-columns.js';
|
|
25
26
|
export { queryFieldInfo } from './util/field-info.js';
|
|
26
27
|
export { jsType } from './util/js-type.js';
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* @typedef {import('./util/selection-types.js').ClauseMetadata} ClauseMetadata
|
|
30
|
-
* @typedef {import('./util/selection-types.js').PointMetadata} PointMetadata
|
|
31
|
-
* @typedef {import('./util/selection-types.js').MatchMethod} MatchMethod
|
|
32
|
-
* @typedef {import('./util/selection-types.js').MatchMetadata} MatchMetadata
|
|
33
|
-
* @typedef {import('./util/selection-types.js').ScaleType} ScaleType
|
|
34
|
-
* @typedef {import('./util/selection-types.js').Extent} Extent
|
|
35
|
-
* @typedef {import('./util/selection-types.js').Scale} Scale
|
|
36
|
-
* @typedef {import('./util/selection-types.js').BinMethod} BinMethod
|
|
37
|
-
* @typedef {import('./util/selection-types.js').IntervalMetadata} IntervalMetadata
|
|
38
|
-
* @typedef {import('./util/selection-types.js').SelectionClause} SelectionClause
|
|
39
|
-
*/
|
|
28
|
+
export { isActivatable } from './util/is-activatable.js';
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { MosaicClient } from "./MosaicClient.js";
|
|
2
|
+
import {
|
|
3
|
+
coordinator as defaultCoordinator,
|
|
4
|
+
} from "./Coordinator.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {Object} MakeClientOptions
|
|
8
|
+
* @property {import('./Coordinator.js').Coordinator} [coordinator] - Mosaic coordinator. Default to the global coordinator.
|
|
9
|
+
* @property {import('./Selection.js').Selection|null} [selection] - A selection whose predicates will be fed into the query function to produce the SQL query.
|
|
10
|
+
* @property {function(): Promise<void>} [prepare] - An async function to prepare the client before running queries.
|
|
11
|
+
* @property {function(any): any} query - A function that returns a query from a list of selection predicates.
|
|
12
|
+
* @property {function(any): void} [queryResult] - Called by the coordinator to return a query result.
|
|
13
|
+
* @property {function(): void} [queryPending] - Called by the coordinator to report a query execution error.
|
|
14
|
+
* @property {function(any): void} [queryError] - Called by the coordinator to inform the client that a query is pending.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** Make a new client with the given options, and connect the client to the provided coordinator.
|
|
18
|
+
* @param {MakeClientOptions} options - The options for making the client
|
|
19
|
+
* @returns {MosaicClient & { destroy: () => void }} - The result object with methods to request an update or destroy the client.
|
|
20
|
+
*/
|
|
21
|
+
export function makeClient(options) {
|
|
22
|
+
const coordinator = options.coordinator ?? defaultCoordinator();
|
|
23
|
+
const client = new ProxyClient({ ...options, coordinator });
|
|
24
|
+
coordinator.connect(client);
|
|
25
|
+
return client;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** An internal class used to implement the makeClient API */
|
|
29
|
+
class ProxyClient extends MosaicClient {
|
|
30
|
+
/** @param {MakeClientOptions} options */
|
|
31
|
+
constructor(options) {
|
|
32
|
+
super(options.selection);
|
|
33
|
+
|
|
34
|
+
/** @type {MakeClientOptions} */
|
|
35
|
+
this._options = { ...options };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async prepare() {
|
|
39
|
+
await this._options.prepare?.();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
query(filter) {
|
|
43
|
+
return this._options.query(filter);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
queryResult(data) {
|
|
47
|
+
this._options.queryResult?.(data);
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
queryPending() {
|
|
52
|
+
this._options.queryPending?.();
|
|
53
|
+
return this;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
queryError(error) {
|
|
57
|
+
this._options.queryError?.(error);
|
|
58
|
+
return this;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
destroy() {
|
|
62
|
+
this._options.coordinator.disconnect(this);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -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
|
|
|
@@ -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, ExprNode, isNotNull, 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,10 +14,13 @@ 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':
|
|
20
21
|
return avgExpr(preagg, node);
|
|
22
|
+
case 'geomean':
|
|
23
|
+
return geomeanExpr(preagg, node);
|
|
21
24
|
case 'arg_max':
|
|
22
25
|
return argmaxExpr(preagg, node);
|
|
23
26
|
case 'arg_min':
|
|
@@ -126,11 +129,24 @@ function addStat(preagg, expr, node) {
|
|
|
126
129
|
*/
|
|
127
130
|
function countExpr(preagg, node) {
|
|
128
131
|
const name = addStat(preagg, count(node.args[0]), node);
|
|
129
|
-
return { expr: sum(name), name };
|
|
132
|
+
return { expr: coalesce(sum(name), 0), name };
|
|
130
133
|
}
|
|
131
134
|
|
|
132
135
|
/**
|
|
133
|
-
* 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.
|
|
134
150
|
* @param {Record<string, ExprNode>} preagg A map of columns (such as
|
|
135
151
|
* sufficient statistics) to pre-aggregate.
|
|
136
152
|
* @param {AggregateNode} node The originating aggregate function call.
|
|
@@ -155,6 +171,24 @@ function avgExpr(preagg, node) {
|
|
|
155
171
|
return div(sum(mul(as, name)), expr);
|
|
156
172
|
}
|
|
157
173
|
|
|
174
|
+
/**
|
|
175
|
+
* Generate an expression for calculating geometric means over data dimensions.
|
|
176
|
+
* This method uses log-based computations to ensure numerical stability. The
|
|
177
|
+
* geomean calculation uses two sufficient statistics: the sum of log values
|
|
178
|
+
* and the count of non-null values. As a side effect, this method adds columns
|
|
179
|
+
* for these statistics to the input *preagg* object.
|
|
180
|
+
* @param {Record<string, ExprNode>} preagg A map of columns (such as
|
|
181
|
+
* sufficient statistics) to pre-aggregate.
|
|
182
|
+
* @param {AggregateNode} node The originating aggregate function call.
|
|
183
|
+
* @returns {ExprNode} An aggregate expression over pre-aggregated dimensions.
|
|
184
|
+
*/
|
|
185
|
+
function geomeanExpr(preagg, node) {
|
|
186
|
+
const x = node.args[0];
|
|
187
|
+
const expr = addStat(preagg, sum(ln(x)), node);
|
|
188
|
+
const { expr: n } = countExpr(preagg, node);
|
|
189
|
+
return exp(div(sum(expr), n));
|
|
190
|
+
}
|
|
191
|
+
|
|
158
192
|
/**
|
|
159
193
|
* Generate an expression for calculating argmax over data dimensions.
|
|
160
194
|
* As a side effect, this method adds a column to the input *preagg* object
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import type { ExprNode } from '@uwdata/mosaic-sql';
|
|
1
|
+
import type { DescribeQuery, ExprNode, Query } from '@uwdata/mosaic-sql';
|
|
2
|
+
|
|
3
|
+
/** Query type accepted by a coordinator. */
|
|
4
|
+
export type QueryType =
|
|
5
|
+
| string
|
|
6
|
+
| Query
|
|
7
|
+
| DescribeQuery;
|
|
2
8
|
|
|
3
9
|
/** String indicating a JavaScript data type. */
|
|
4
10
|
export type JSType =
|
|
@@ -46,3 +52,13 @@ export interface ColumnDescription {
|
|
|
46
52
|
column_type: string,
|
|
47
53
|
null: 'YES' | 'NO'
|
|
48
54
|
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Interface for components that perform selection activation.
|
|
58
|
+
*/
|
|
59
|
+
export interface Activatable {
|
|
60
|
+
/**
|
|
61
|
+
* Activate the selection that this component publishes to.
|
|
62
|
+
*/
|
|
63
|
+
activate(): void;
|
|
64
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test if a value implements the Activatable interface.
|
|
3
|
+
* @param {*} value The value to test.
|
|
4
|
+
* @returns {value is import('../types.js').Activatable}
|
|
5
|
+
*/
|
|
6
|
+
export function isActivatable(value) {
|
|
7
|
+
return typeof value?.activate === 'function' && value.activate.length === 0;
|
|
8
|
+
}
|
package/src/util/throttle.js
CHANGED
|
@@ -5,10 +5,12 @@ const NIL = {};
|
|
|
5
5
|
* a Promise. Upon repeated invocation, the callback will not be invoked
|
|
6
6
|
* until a prior Promise resolves. If multiple invocations occurs while
|
|
7
7
|
* waiting, only the most recent invocation will be pending.
|
|
8
|
-
* @
|
|
8
|
+
* @template E, T
|
|
9
|
+
* @param {(event: E) => Promise<T>} callback The callback function.
|
|
9
10
|
* @param {boolean} [debounce=true] Flag indicating if invocations
|
|
10
11
|
* should also be debounced within the current animation frame.
|
|
11
|
-
* @returns A new function that throttles
|
|
12
|
+
* @returns {(event: E) => void} A new function that throttles
|
|
13
|
+
* access to the callback.
|
|
12
14
|
*/
|
|
13
15
|
export function throttle(callback, debounce = false) {
|
|
14
16
|
let curr;
|
package/vitest.config.ts
ADDED