@uwdata/mosaic-core 0.17.0 → 0.19.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/LICENSE +47 -0
- package/README.md +0 -1
- package/dist/src/Coordinator.d.ts +159 -0
- package/dist/src/Coordinator.d.ts.map +1 -0
- package/dist/src/Coordinator.js +250 -0
- package/dist/src/Coordinator.js.map +1 -0
- package/dist/src/MosaicClient.d.ts +138 -0
- package/dist/src/MosaicClient.d.ts.map +1 -0
- package/dist/src/MosaicClient.js +214 -0
- package/dist/src/MosaicClient.js.map +1 -0
- package/dist/src/Param.d.ts +56 -0
- package/dist/src/Param.d.ts.map +1 -0
- package/dist/src/Param.js +89 -0
- package/dist/src/Param.js.map +1 -0
- package/dist/src/QueryConsolidator.d.ts +11 -0
- package/dist/src/QueryConsolidator.d.ts.map +1 -0
- package/dist/src/QueryConsolidator.js +249 -0
- package/dist/src/QueryConsolidator.js.map +1 -0
- package/dist/src/QueryManager.d.ts +77 -0
- package/dist/src/QueryManager.d.ts.map +1 -0
- package/dist/src/QueryManager.js +174 -0
- package/dist/src/QueryManager.js.map +1 -0
- package/dist/src/Selection.d.ts +222 -0
- package/dist/src/Selection.d.ts.map +1 -0
- package/dist/src/Selection.js +322 -0
- package/dist/src/Selection.js.map +1 -0
- package/dist/src/SelectionClause.d.ts +222 -0
- package/dist/src/SelectionClause.d.ts.map +1 -0
- package/dist/src/SelectionClause.js +168 -0
- package/dist/src/SelectionClause.js.map +1 -0
- package/dist/src/connectors/Connector.d.ts +26 -0
- package/dist/src/connectors/Connector.d.ts.map +1 -0
- package/dist/src/connectors/Connector.js +2 -0
- package/dist/src/connectors/Connector.js.map +1 -0
- package/dist/src/connectors/rest.d.ts +24 -0
- package/dist/src/connectors/rest.d.ts.map +1 -0
- package/dist/src/connectors/rest.js +37 -0
- package/dist/src/connectors/rest.js.map +1 -0
- package/dist/src/connectors/socket.d.ts +40 -0
- package/dist/src/connectors/socket.d.ts.map +1 -0
- package/dist/src/connectors/socket.js +115 -0
- package/dist/src/connectors/socket.js.map +1 -0
- package/dist/src/connectors/wasm.d.ts +56 -0
- package/dist/src/connectors/wasm.d.ts.map +1 -0
- package/dist/src/connectors/wasm.js +116 -0
- package/dist/src/connectors/wasm.js.map +1 -0
- package/dist/src/index.d.ts +28 -0
- package/dist/src/index.d.ts.map +1 -0
- package/{src → dist/src}/index.js +8 -11
- package/dist/src/index.js.map +1 -0
- package/dist/src/make-client.d.ts +33 -0
- package/dist/src/make-client.d.ts.map +1 -0
- package/dist/src/make-client.js +52 -0
- package/dist/src/make-client.js.map +1 -0
- package/dist/src/preagg/PreAggregator.d.ts +150 -0
- package/dist/src/preagg/PreAggregator.d.ts.map +1 -0
- package/dist/src/preagg/PreAggregator.js +382 -0
- package/dist/src/preagg/PreAggregator.js.map +1 -0
- package/dist/src/preagg/preagg-columns.d.ts +16 -0
- package/dist/src/preagg/preagg-columns.d.ts.map +1 -0
- package/dist/src/preagg/preagg-columns.js +95 -0
- package/dist/src/preagg/preagg-columns.js.map +1 -0
- package/dist/src/preagg/sufficient-statistics.d.ts +14 -0
- package/dist/src/preagg/sufficient-statistics.d.ts.map +1 -0
- package/dist/src/preagg/sufficient-statistics.js +446 -0
- package/dist/src/preagg/sufficient-statistics.js.map +1 -0
- package/dist/src/types.d.ts +77 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/util/AsyncDispatch.d.ts +121 -0
- package/dist/src/util/AsyncDispatch.d.ts.map +1 -0
- package/dist/src/util/AsyncDispatch.js +188 -0
- package/dist/src/util/AsyncDispatch.js.map +1 -0
- package/dist/src/util/cache.d.ts +19 -0
- package/dist/src/util/cache.d.ts.map +1 -0
- package/dist/src/util/cache.js +66 -0
- package/dist/src/util/cache.js.map +1 -0
- package/dist/src/util/decode-ipc.d.ts +12 -0
- package/dist/src/util/decode-ipc.d.ts.map +1 -0
- package/{src → dist/src}/util/decode-ipc.js +5 -6
- package/dist/src/util/decode-ipc.js.map +1 -0
- package/dist/src/util/distinct.d.ts +3 -0
- package/dist/src/util/distinct.d.ts.map +1 -0
- package/dist/src/util/distinct.js +16 -0
- package/dist/src/util/distinct.js.map +1 -0
- package/dist/src/util/field-info.d.ts +26 -0
- package/dist/src/util/field-info.d.ts.map +1 -0
- package/dist/src/util/field-info.js +91 -0
- package/dist/src/util/field-info.js.map +1 -0
- package/dist/src/util/hash.d.ts +2 -0
- package/dist/src/util/hash.d.ts.map +1 -0
- package/dist/src/util/hash.js +26 -0
- package/dist/src/util/hash.js.map +1 -0
- package/dist/src/util/is-activatable.d.ts +8 -0
- package/dist/src/util/is-activatable.d.ts.map +1 -0
- package/dist/src/util/is-activatable.js +10 -0
- package/dist/src/util/is-activatable.js.map +1 -0
- package/dist/src/util/is-arrow-table.d.ts +9 -0
- package/dist/src/util/is-arrow-table.d.ts.map +1 -0
- package/dist/src/util/is-arrow-table.js +11 -0
- package/dist/src/util/is-arrow-table.js.map +1 -0
- package/dist/src/util/js-type.d.ts +9 -0
- package/dist/src/util/js-type.d.ts.map +1 -0
- package/dist/src/util/js-type.js +59 -0
- package/dist/src/util/js-type.js.map +1 -0
- package/dist/src/util/priority-queue.d.ts +35 -0
- package/dist/src/util/priority-queue.d.ts.map +1 -0
- package/dist/src/util/priority-queue.js +81 -0
- package/dist/src/util/priority-queue.js.map +1 -0
- package/dist/src/util/query-result.d.ts +47 -0
- package/dist/src/util/query-result.d.ts.map +1 -0
- package/dist/src/util/query-result.js +83 -0
- package/dist/src/util/query-result.js.map +1 -0
- package/dist/src/util/synchronizer.d.ts +36 -0
- package/dist/src/util/synchronizer.d.ts.map +1 -0
- package/dist/src/util/synchronizer.js +52 -0
- package/dist/src/util/synchronizer.js.map +1 -0
- package/dist/src/util/throttle.d.ts +12 -0
- package/dist/src/util/throttle.d.ts.map +1 -0
- package/dist/src/util/throttle.js +51 -0
- package/dist/src/util/throttle.js.map +1 -0
- package/dist/src/util/to-data-columns.d.ts +22 -0
- package/dist/src/util/to-data-columns.d.ts.map +1 -0
- package/dist/src/util/to-data-columns.js +51 -0
- package/dist/src/util/to-data-columns.js.map +1 -0
- package/dist/src/util/void-logger.d.ts +13 -0
- package/dist/src/util/void-logger.d.ts.map +1 -0
- package/dist/src/util/void-logger.js +13 -0
- package/dist/src/util/void-logger.js.map +1 -0
- package/package.json +17 -11
- package/src/Coordinator.ts +396 -0
- package/src/{MosaicClient.js → MosaicClient.ts} +50 -43
- package/src/{Param.js → Param.ts} +29 -28
- package/src/{QueryConsolidator.js → QueryConsolidator.ts} +85 -62
- package/src/{QueryManager.js → QueryManager.ts} +61 -54
- package/src/Selection.ts +391 -0
- package/src/SelectionClause.ts +357 -0
- package/src/connectors/Connector.ts +6 -6
- package/src/connectors/rest.ts +56 -0
- package/src/connectors/{socket.js → socket.ts} +53 -42
- package/src/connectors/{wasm.js → wasm.ts} +52 -63
- package/src/index.ts +42 -0
- package/src/make-client.ts +93 -0
- package/src/preagg/{PreAggregator.js → PreAggregator.ts} +164 -145
- package/src/preagg/{preagg-columns.js → preagg-columns.ts} +27 -24
- package/src/preagg/{sufficient-statistics.js → sufficient-statistics.ts} +160 -110
- package/src/types.ts +24 -9
- package/src/util/{AsyncDispatch.js → AsyncDispatch.ts} +62 -43
- package/src/util/{cache.js → cache.ts} +25 -15
- package/src/util/decode-ipc.ts +15 -0
- package/src/util/{distinct.js → distinct.ts} +3 -3
- package/src/util/{field-info.js → field-info.ts} +30 -31
- package/src/util/{hash.js → hash.ts} +4 -4
- package/src/util/is-activatable.ts +11 -0
- package/src/util/is-arrow-table.ts +12 -0
- package/src/util/{js-type.js → js-type.ts} +7 -5
- package/src/util/{priority-queue.js → priority-queue.ts} +32 -20
- package/src/util/{query-result.js → query-result.ts} +24 -17
- package/src/util/synchronizer.ts +56 -0
- package/src/util/throttle.ts +59 -0
- package/src/util/to-data-columns.ts +65 -0
- package/src/util/void-logger.ts +23 -0
- package/src/Coordinator.js +0 -313
- package/src/Selection.js +0 -380
- package/src/SelectionClause.js +0 -159
- package/src/connectors/rest.js +0 -38
- package/src/index-types.ts +0 -5
- package/src/make-client.js +0 -101
- package/src/util/is-activatable.js +0 -8
- package/src/util/is-arrow-table.js +0 -10
- package/src/util/selection-types.ts +0 -137
- package/src/util/synchronizer.js +0 -47
- package/src/util/throttle.js +0 -54
- package/src/util/to-data-columns.js +0 -60
- package/src/util/void-logger.js +0 -13
- package/tsconfig.json +0 -9
- package/vitest.config.ts +0 -3
|
@@ -1,23 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import { Query, and, asNode, ceil, collectColumns, createTable, float64, floor, isBetween, int32, mul, round, scaleTransform, sub, isSelectQuery, isAggregateExpression, ColumnNameRefNode } from '@uwdata/mosaic-sql';
|
|
9
|
-
import { preaggColumns } from './preagg-columns.js';
|
|
1
|
+
import { ExprNode, ScaleOptions, SelectQuery, Query, ExprValue, MaybeArray, FunctionNode, BetweenOpNode, AndNode } from '@uwdata/mosaic-sql';
|
|
2
|
+
import type { Coordinator } from '../Coordinator.js';
|
|
3
|
+
import type { MosaicClient } from '../MosaicClient.js';
|
|
4
|
+
import type { Selection } from '../Selection.js';
|
|
5
|
+
import type { BinMethod, ClauseSource, IntervalMetadata, SelectionClause } from '../SelectionClause.js';
|
|
6
|
+
import { Query as QueryBuilder, and, asNode, ceil, collectColumns, createTable, float64, floor, isBetween, int32, mul, round, scaleTransform, sub, isSelectQuery, isAggregateExpression, ColumnNameRefNode } from '@uwdata/mosaic-sql';
|
|
7
|
+
import { preaggColumns, PreAggColumnsResult } from './preagg-columns.js';
|
|
10
8
|
import { fnv_hash } from '../util/hash.js';
|
|
11
9
|
|
|
12
10
|
const Skip = { skip: true, result: null };
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
export interface PreAggregateOptions {
|
|
13
|
+
/** Database schema (namespace) in which to write pre-aggregated materialized views (default 'mosaic'). */
|
|
14
|
+
schema?: string;
|
|
15
|
+
/** Flag to enable or disable the pre-aggregation. This flag can be updated later via the `enabled` property. */
|
|
16
|
+
enabled?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type ActivePredicate = (p?: ExprNode) => MaybeArray<ExprNode> | undefined;
|
|
20
|
+
|
|
21
|
+
interface ActiveColumnsResult {
|
|
22
|
+
source: ClauseSource | null;
|
|
23
|
+
columns?: Record<string, ExprNode>;
|
|
24
|
+
predicate?: ActivePredicate;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface PreAggregateInfoOptions {
|
|
28
|
+
table: string;
|
|
29
|
+
create: string;
|
|
30
|
+
active: ActiveColumnsResult;
|
|
31
|
+
select: SelectQuery;
|
|
32
|
+
}
|
|
21
33
|
|
|
22
34
|
/**
|
|
23
35
|
* Build and query optimized pre-aggregated materaialized views, for fast
|
|
@@ -39,16 +51,21 @@ const Skip = { skip: true, result: null };
|
|
|
39
51
|
* should be used with care.
|
|
40
52
|
*/
|
|
41
53
|
export class PreAggregator {
|
|
54
|
+
public entries: Map<MosaicClient, PreAggregateInfo | typeof Skip | null>;
|
|
55
|
+
private active: ActiveColumnsResult | null;
|
|
56
|
+
private mc: Coordinator;
|
|
57
|
+
private _schema: string;
|
|
58
|
+
private _enabled: boolean;
|
|
59
|
+
|
|
42
60
|
/**
|
|
43
61
|
* Create a new manager of materialized views of pre-aggregated data.
|
|
44
|
-
* @param
|
|
45
|
-
* @param
|
|
62
|
+
* @param coordinator A Mosaic coordinator.
|
|
63
|
+
* @param options Pre-aggregation options.
|
|
46
64
|
*/
|
|
47
|
-
constructor(coordinator, {
|
|
65
|
+
constructor(coordinator: Coordinator, {
|
|
48
66
|
schema = 'mosaic',
|
|
49
67
|
enabled = true
|
|
50
|
-
} = {}) {
|
|
51
|
-
/** @type {Map<MosaicClient, PreAggregateInfo | Skip | null>} */
|
|
68
|
+
}: PreAggregateOptions = {}) {
|
|
52
69
|
this.entries = new Map();
|
|
53
70
|
this.active = null;
|
|
54
71
|
this.mc = coordinator;
|
|
@@ -61,9 +78,9 @@ export class PreAggregator {
|
|
|
61
78
|
* cleared and subsequent request calls will return null until re-enabled.
|
|
62
79
|
* This method has no effect on any pre-aggregated tables already in the
|
|
63
80
|
* database.
|
|
64
|
-
* @param
|
|
81
|
+
* @param state The enabled state to set.
|
|
65
82
|
*/
|
|
66
|
-
set enabled(state) {
|
|
83
|
+
set enabled(state: boolean) {
|
|
67
84
|
if (this._enabled !== state) {
|
|
68
85
|
if (!state) this.clear();
|
|
69
86
|
this._enabled = state;
|
|
@@ -72,9 +89,9 @@ export class PreAggregator {
|
|
|
72
89
|
|
|
73
90
|
/**
|
|
74
91
|
* Get the enabled state of this manager.
|
|
75
|
-
* @returns
|
|
92
|
+
* @returns The current enabled state.
|
|
76
93
|
*/
|
|
77
|
-
get enabled() {
|
|
94
|
+
get enabled(): boolean {
|
|
78
95
|
return this._enabled;
|
|
79
96
|
}
|
|
80
97
|
|
|
@@ -83,9 +100,9 @@ export class PreAggregator {
|
|
|
83
100
|
* Upon changes, any local state is cleared. This method does _not_ drop any
|
|
84
101
|
* existing materialized views, use `dropSchema` before changing the schema
|
|
85
102
|
* to also remove existing materalized views in the database.
|
|
86
|
-
* @param
|
|
103
|
+
* @param schema The schema name to set.
|
|
87
104
|
*/
|
|
88
|
-
set schema(schema) {
|
|
105
|
+
set schema(schema: string) {
|
|
89
106
|
if (this._schema !== schema) {
|
|
90
107
|
this.clear();
|
|
91
108
|
this._schema = schema;
|
|
@@ -94,9 +111,9 @@ export class PreAggregator {
|
|
|
94
111
|
|
|
95
112
|
/**
|
|
96
113
|
* Get the database schema used for pre-aggregated materialized view tables.
|
|
97
|
-
* @returns
|
|
114
|
+
* @returns The current schema name.
|
|
98
115
|
*/
|
|
99
|
-
get schema() {
|
|
116
|
+
get schema(): string {
|
|
100
117
|
return this._schema;
|
|
101
118
|
}
|
|
102
119
|
|
|
@@ -109,7 +126,7 @@ export class PreAggregator {
|
|
|
109
126
|
* the schema will be repopulated by future pre-aggregation requests.
|
|
110
127
|
* @returns A query result promise.
|
|
111
128
|
*/
|
|
112
|
-
dropSchema() {
|
|
129
|
+
dropSchema(): Promise<unknown> {
|
|
113
130
|
this.clear();
|
|
114
131
|
return this.mc.exec(`DROP SCHEMA IF EXISTS "${this.schema}" CASCADE`);
|
|
115
132
|
}
|
|
@@ -120,7 +137,7 @@ export class PreAggregator {
|
|
|
120
137
|
* views. Use `dropSchema` to remove existing materialized view tables from
|
|
121
138
|
* the database.
|
|
122
139
|
*/
|
|
123
|
-
clear() {
|
|
140
|
+
clear(): void {
|
|
124
141
|
this.entries.clear();
|
|
125
142
|
this.active = null;
|
|
126
143
|
}
|
|
@@ -130,14 +147,14 @@ export class PreAggregator {
|
|
|
130
147
|
* client-selection pair, or null if the client has unstable filters.
|
|
131
148
|
* This method has multiple possible side effects, including materialized
|
|
132
149
|
* view creation and updating internal caches.
|
|
133
|
-
* @param
|
|
134
|
-
* @param
|
|
135
|
-
* @param
|
|
150
|
+
* @param client A Mosaic client.
|
|
151
|
+
* @param selection A Mosaic selection to filter the client by.
|
|
152
|
+
* @param activeClause A representative active selection
|
|
136
153
|
* clause for which to generate materialized views of pre-aggregates.
|
|
137
|
-
* @returns
|
|
154
|
+
* @returns Information and query generator
|
|
138
155
|
* for pre-aggregated tables, or null if the client has unstable filters.
|
|
139
156
|
*/
|
|
140
|
-
request(client, selection, activeClause) {
|
|
157
|
+
request(client: MosaicClient, selection: Selection, activeClause: SelectionClause | null): PreAggregateInfo | typeof Skip | null {
|
|
141
158
|
// if not enabled, do nothing
|
|
142
159
|
if (!this.enabled || activeClause == null) return null;
|
|
143
160
|
|
|
@@ -175,13 +192,13 @@ export class PreAggregator {
|
|
|
175
192
|
|
|
176
193
|
// if we have cached pre-aggregate info, return that
|
|
177
194
|
if (entries.has(client)) {
|
|
178
|
-
return entries.get(client)
|
|
195
|
+
return entries.get(client)!;
|
|
179
196
|
}
|
|
180
197
|
|
|
181
198
|
// get non-active materialized view columns
|
|
182
199
|
const preaggCols = preaggColumns(client);
|
|
183
200
|
|
|
184
|
-
let info;
|
|
201
|
+
let info: PreAggregateInfo | typeof Skip | null;
|
|
185
202
|
if (!preaggCols) {
|
|
186
203
|
// if client is not indexable, record null info
|
|
187
204
|
info = null;
|
|
@@ -191,12 +208,15 @@ export class PreAggregator {
|
|
|
191
208
|
} else {
|
|
192
209
|
// generate materialized view table
|
|
193
210
|
const filter = selection.remove(source).predicate(client);
|
|
194
|
-
info = preaggregateInfo(
|
|
211
|
+
info = preaggregateInfo(
|
|
212
|
+
client.query(filter) as SelectQuery,
|
|
213
|
+
active, preaggCols, schema
|
|
214
|
+
);
|
|
195
215
|
info.result = mc.exec([
|
|
196
216
|
`CREATE SCHEMA IF NOT EXISTS ${schema}`,
|
|
197
217
|
createTable(info.table, info.create, { temp: false })
|
|
198
218
|
]);
|
|
199
|
-
info.result.catch(e => mc.logger().error(e));
|
|
219
|
+
info.result.catch((e: Error) => mc.logger().error(e));
|
|
200
220
|
}
|
|
201
221
|
|
|
202
222
|
entries.set(client, info);
|
|
@@ -210,107 +230,121 @@ export class PreAggregator {
|
|
|
210
230
|
* function for the active dimensions of a pre-aggregated materialized view.
|
|
211
231
|
* If the active clause is not indexable or is missing metadata, this method
|
|
212
232
|
* returns an object with a null source property.
|
|
213
|
-
* @param
|
|
233
|
+
* @param clause The active selection clause to analyze.
|
|
214
234
|
*/
|
|
215
|
-
function activeColumns(clause) {
|
|
235
|
+
function activeColumns(clause: SelectionClause): ActiveColumnsResult {
|
|
216
236
|
const { source, meta } = clause;
|
|
217
237
|
const clausePred = clause.predicate;
|
|
218
|
-
const clauseCols = collectColumns(clausePred).map(c => c.column);
|
|
219
|
-
let predicate;
|
|
220
|
-
let columns;
|
|
238
|
+
const clauseCols = collectColumns(clausePred!).map(c => c.column);
|
|
239
|
+
let predicate: ActivePredicate | undefined;
|
|
240
|
+
let columns: Record<string, ExprNode> | undefined;
|
|
221
241
|
|
|
222
242
|
if (!meta || !clauseCols) {
|
|
223
243
|
return { source: null, columns, predicate };
|
|
224
244
|
}
|
|
225
245
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
if (type === 'point') {
|
|
230
|
-
predicate = x => x;
|
|
231
|
-
columns = Object.fromEntries(
|
|
232
|
-
clauseCols.map(col => [`${col}`, asNode(col)])
|
|
233
|
-
);
|
|
234
|
-
} else if (type === 'interval' && scales) {
|
|
235
|
-
// determine pixel-level binning
|
|
236
|
-
const bins = scales.map(s => binInterval(s, pixelSize, bin));
|
|
237
|
-
|
|
238
|
-
if (bins.some(b => !b)) {
|
|
239
|
-
// bail if a scale type is unsupported
|
|
240
|
-
} else if (bins.length === 1) {
|
|
241
|
-
// selection clause predicate has type BetweenOpNode
|
|
242
|
-
// single interval selection
|
|
243
|
-
predicate = p => p ? isBetween('active0', p.extent.map(bins[0])) : [];
|
|
244
|
-
// @ts-ignore
|
|
245
|
-
columns = { active0: bins[0](clausePred.expr) };
|
|
246
|
-
} else {
|
|
247
|
-
// selection clause predicate has type AndNode<BetweenOpNode>
|
|
248
|
-
// multiple interval selection
|
|
249
|
-
predicate = p => p
|
|
250
|
-
? and(p.clauses.map(
|
|
251
|
-
(c, i) => isBetween(`active${i}`, c.extent.map(bins[i]))
|
|
252
|
-
))
|
|
253
|
-
: [];
|
|
246
|
+
switch (meta.type) {
|
|
247
|
+
case 'point':
|
|
248
|
+
predicate = x => x;
|
|
254
249
|
columns = Object.fromEntries(
|
|
255
|
-
|
|
256
|
-
clausePred.clauses.map((p, i) => [`active${i}`, bins[i](p.expr)])
|
|
250
|
+
clauseCols.map(col => [`${col}`, asNode(col)])
|
|
257
251
|
);
|
|
252
|
+
break;
|
|
253
|
+
case 'interval': {
|
|
254
|
+
const { scales, bin, pixelSize = 1 } = (meta as IntervalMetadata);
|
|
255
|
+
if (!scales) break;
|
|
256
|
+
|
|
257
|
+
// determine pixel-level binning
|
|
258
|
+
const bins = scales.map((s: ScaleOptions) => binInterval(s, pixelSize, bin));
|
|
259
|
+
if (bins.some(b => !b)) {
|
|
260
|
+
// bail if a scale type is unsupported
|
|
261
|
+
} else if (bins.length === 1) {
|
|
262
|
+
// selection clause predicate has type BetweenOpNode
|
|
263
|
+
// single interval selection
|
|
264
|
+
predicate = (p?: ExprNode) => p
|
|
265
|
+
? isBetween('active0', (p as BetweenOpNode).extent?.map(bins[0]!))
|
|
266
|
+
: [];
|
|
267
|
+
columns = { active0: bins[0]!((clausePred as BetweenOpNode).expr) };
|
|
268
|
+
} else {
|
|
269
|
+
// selection clause predicate has type AndNode<BetweenOpNode>
|
|
270
|
+
// multiple interval selection
|
|
271
|
+
predicate = (p?: ExprNode) => p
|
|
272
|
+
? and((p as AndNode<BetweenOpNode>).clauses.map(
|
|
273
|
+
(c, i) => isBetween(`active${i}`, c.extent?.map(bins[i]!))
|
|
274
|
+
))
|
|
275
|
+
: [];
|
|
276
|
+
columns = Object.fromEntries(
|
|
277
|
+
(clausePred as AndNode<BetweenOpNode>).clauses.map(
|
|
278
|
+
(p, i) => [`active${i}`, bins[i]!(p.expr)]
|
|
279
|
+
)
|
|
280
|
+
);
|
|
281
|
+
}
|
|
258
282
|
}
|
|
259
283
|
}
|
|
260
284
|
|
|
261
285
|
return { source: columns ? source : null, columns, predicate };
|
|
262
286
|
}
|
|
263
287
|
|
|
264
|
-
const BIN = { ceil, round };
|
|
288
|
+
const BIN: Record<string, (expr: ExprValue) => FunctionNode> = { ceil, round };
|
|
265
289
|
|
|
266
290
|
/**
|
|
267
291
|
* Returns a bin function generator to discretize a selection interval domain.
|
|
268
|
-
* @param
|
|
292
|
+
* @param scale A scale that maps domain values to the output range
|
|
269
293
|
* (typically pixels).
|
|
270
|
-
* @param
|
|
294
|
+
* @param pixelSize The interactive pixel size. This value indicates
|
|
271
295
|
* the bin step size and may be greater than an actual screen pixel.
|
|
272
|
-
* @param
|
|
273
|
-
*
|
|
274
|
-
* @returns {(value: any) => ExprNode} A bin function generator.
|
|
296
|
+
* @param bin The binning method to apply, one of `floor`, `ceil', or `round`.
|
|
297
|
+
* @returns A bin function generator.
|
|
275
298
|
*/
|
|
276
|
-
function binInterval(
|
|
277
|
-
|
|
299
|
+
function binInterval(
|
|
300
|
+
scale: ScaleOptions,
|
|
301
|
+
pixelSize: number,
|
|
302
|
+
bin?: BinMethod
|
|
303
|
+
): ((value: ExprValue) => ExprNode) | undefined {
|
|
304
|
+
const { type, domain, range, apply, sqlApply } = scaleTransform(scale)!;
|
|
278
305
|
if (!apply) return; // unsupported scale type
|
|
279
306
|
const binFn = BIN[`${bin}`.toLowerCase()] || floor;
|
|
280
|
-
const
|
|
281
|
-
const
|
|
307
|
+
const dom = domain!.map(x => Number(x));
|
|
308
|
+
const lo = apply(Math.min(...dom));
|
|
309
|
+
const hi = apply(Math.max(...dom));
|
|
282
310
|
const s = (type === 'identity'
|
|
283
311
|
? 1
|
|
284
|
-
: Math.abs(range[1] - range[0]) / (hi - lo)) / pixelSize;
|
|
312
|
+
: Math.abs(range![1] - range![0]) / (hi - lo)) / pixelSize;
|
|
285
313
|
const scalar = s === 1
|
|
286
|
-
? x => x
|
|
287
|
-
: x => mul(float64(s), x);
|
|
314
|
+
? (x: ExprValue) => x
|
|
315
|
+
: (x: ExprValue) => mul(float64(s), x);
|
|
288
316
|
const diff = lo === 0
|
|
289
|
-
? x => x
|
|
290
|
-
: x => sub(x, float64(lo));
|
|
317
|
+
? (x: ExprValue) => x
|
|
318
|
+
: (x: ExprValue) => sub(x, float64(lo));
|
|
291
319
|
return value => int32(binFn(scalar(diff(sqlApply(value)))));
|
|
292
320
|
}
|
|
293
321
|
|
|
294
322
|
/**
|
|
295
323
|
* Generate pre-aggregate query information.
|
|
296
|
-
* @param
|
|
297
|
-
* @param
|
|
298
|
-
* @param
|
|
299
|
-
* @
|
|
324
|
+
* @param clientQuery The original client query.
|
|
325
|
+
* @param active Active (selected) columns.
|
|
326
|
+
* @param preaggCols Pre-aggregation columns.
|
|
327
|
+
* @param schema Database schema name.
|
|
328
|
+
* @returns Pre-aggregation information.
|
|
300
329
|
*/
|
|
301
|
-
function preaggregateInfo(
|
|
330
|
+
function preaggregateInfo(
|
|
331
|
+
clientQuery: SelectQuery,
|
|
332
|
+
active: ActiveColumnsResult,
|
|
333
|
+
preaggCols: PreAggColumnsResult,
|
|
334
|
+
schema: string
|
|
335
|
+
): PreAggregateInfo {
|
|
302
336
|
const { group, output, preagg } = preaggCols;
|
|
303
337
|
const { columns } = active;
|
|
304
338
|
|
|
305
339
|
// build materialized view construction query
|
|
306
340
|
const query = clientQuery
|
|
307
341
|
.setSelect({ ...preagg, ...columns })
|
|
308
|
-
.groupby(Object.keys(columns));
|
|
342
|
+
.groupby(Object.keys(columns!));
|
|
309
343
|
|
|
310
344
|
// ensure active clause columns are selected by subqueries
|
|
311
345
|
const [subq] = query.subqueries;
|
|
312
346
|
if (subq) {
|
|
313
|
-
const cols = Object.values(columns)
|
|
347
|
+
const cols = Object.values(columns!)
|
|
314
348
|
.flatMap(c => collectColumns(c).map(c => c.column));
|
|
315
349
|
subqueryPushdown(subq, cols);
|
|
316
350
|
}
|
|
@@ -327,7 +361,7 @@ function preaggregateInfo(clientQuery, active, preaggCols, schema) {
|
|
|
327
361
|
const table = `${schema}.preagg_${id}`;
|
|
328
362
|
|
|
329
363
|
// generate preaggregate select query
|
|
330
|
-
const select =
|
|
364
|
+
const select = QueryBuilder
|
|
331
365
|
.select(group, output)
|
|
332
366
|
.from(table)
|
|
333
367
|
.groupby(group)
|
|
@@ -339,12 +373,12 @@ function preaggregateInfo(clientQuery, active, preaggCols, schema) {
|
|
|
339
373
|
|
|
340
374
|
/**
|
|
341
375
|
* Push column selections down to subqueries.
|
|
342
|
-
* @param
|
|
343
|
-
* @param
|
|
376
|
+
* @param query The (sub)query to push down to.
|
|
377
|
+
* @param cols The column names to push down.
|
|
344
378
|
*/
|
|
345
|
-
function subqueryPushdown(query, cols) {
|
|
346
|
-
const memo = new Set;
|
|
347
|
-
const pushdown = q => {
|
|
379
|
+
function subqueryPushdown(query: Query, cols: string[]): void {
|
|
380
|
+
const memo = new Set();
|
|
381
|
+
const pushdown = (q: Query) => {
|
|
348
382
|
// it is possible to have duplicate subqueries
|
|
349
383
|
// so we memoize and exit early if already seen
|
|
350
384
|
if (memo.has(q)) return;
|
|
@@ -358,8 +392,8 @@ function subqueryPushdown(query, cols) {
|
|
|
358
392
|
// if an aggregation query, we need to push to groupby as well
|
|
359
393
|
// we also deduplicate as the column may already be present
|
|
360
394
|
const set = new Set(
|
|
361
|
-
q._groupby.flatMap(x => x instanceof ColumnNameRefNode ? x.name : []
|
|
362
|
-
)
|
|
395
|
+
q._groupby.flatMap(x => x instanceof ColumnNameRefNode ? [x.name] : [])
|
|
396
|
+
);
|
|
363
397
|
q.groupby(cols.filter(c => !set.has(c)));
|
|
364
398
|
}
|
|
365
399
|
}
|
|
@@ -370,10 +404,10 @@ function subqueryPushdown(query, cols) {
|
|
|
370
404
|
|
|
371
405
|
/**
|
|
372
406
|
* Test if a query performs aggregation.
|
|
373
|
-
* @param
|
|
374
|
-
* @returns
|
|
407
|
+
* @param query Select query to test.
|
|
408
|
+
* @returns True if query performs aggregation.
|
|
375
409
|
*/
|
|
376
|
-
function isAggregateQuery(query) {
|
|
410
|
+
function isAggregateQuery(query: SelectQuery): boolean {
|
|
377
411
|
return query._groupby.length > 0
|
|
378
412
|
|| query._select.some(node => isAggregateExpression(node));
|
|
379
413
|
}
|
|
@@ -385,55 +419,40 @@ function isAggregateQuery(query) {
|
|
|
385
419
|
* active clause and selection state.
|
|
386
420
|
*/
|
|
387
421
|
export class PreAggregateInfo {
|
|
422
|
+
/** The name of the materialized view. */
|
|
423
|
+
table: string;
|
|
424
|
+
/** The SQL query used to generate the materialized view. */
|
|
425
|
+
create: string;
|
|
426
|
+
/** A result promise returned for the materialized view creation query. */
|
|
427
|
+
result: Promise<unknown> | null;
|
|
428
|
+
/** Definitions and predicate function for the active columns,
|
|
429
|
+
* which are dynamically filtered by the active clause. */
|
|
430
|
+
active: ActiveColumnsResult;
|
|
431
|
+
/** Select query (sans where clause) for materialized views. */
|
|
432
|
+
select: SelectQuery;
|
|
433
|
+
/** Boolean flag indicating a client that should be skipped.
|
|
434
|
+
* This value is always false for a created materialized view. */
|
|
435
|
+
skip: boolean;
|
|
436
|
+
|
|
388
437
|
/**
|
|
389
438
|
* Create a new pre-aggregation information instance.
|
|
390
|
-
* @param
|
|
391
|
-
* @param {string} options.table The materialized view table name.
|
|
392
|
-
* @param {string} options.create The table creation query.
|
|
393
|
-
* @param {*} options.active Active column information.
|
|
394
|
-
* @param {SelectQuery} options.select Base query for requesting updates
|
|
395
|
-
* using a pre-aggregated materialized view.
|
|
439
|
+
* @param options Options object.
|
|
396
440
|
*/
|
|
397
|
-
constructor({ table, create, active, select }) {
|
|
398
|
-
/**
|
|
399
|
-
* The name of the materialized view.
|
|
400
|
-
* @type {string}
|
|
401
|
-
*/
|
|
441
|
+
constructor({ table, create, active, select }: PreAggregateInfoOptions) {
|
|
402
442
|
this.table = table;
|
|
403
|
-
/**
|
|
404
|
-
* The SQL query used to generate the materialized view.
|
|
405
|
-
* @type {string}
|
|
406
|
-
*/
|
|
407
443
|
this.create = create;
|
|
408
|
-
/**
|
|
409
|
-
* A result promise returned for the materialized view creation query.
|
|
410
|
-
* @type {Promise | null}
|
|
411
|
-
*/
|
|
412
444
|
this.result = null;
|
|
413
|
-
/**
|
|
414
|
-
* Definitions and predicate function for the active columns,
|
|
415
|
-
* which are dynamically filtered by the active clause.
|
|
416
|
-
*/
|
|
417
445
|
this.active = active;
|
|
418
|
-
/**
|
|
419
|
-
* Select query (sans where clause) for materialized views.
|
|
420
|
-
* @type {SelectQuery}
|
|
421
|
-
*/
|
|
422
446
|
this.select = select;
|
|
423
|
-
/**
|
|
424
|
-
* Boolean flag indicating a client that should be skipped.
|
|
425
|
-
* This value is always false for a created materialized view.
|
|
426
|
-
* @type {boolean}
|
|
427
|
-
*/
|
|
428
447
|
this.skip = false;
|
|
429
448
|
}
|
|
430
449
|
|
|
431
450
|
/**
|
|
432
451
|
* Generate a materialized view query for the given predicate.
|
|
433
|
-
* @param
|
|
434
|
-
* @returns
|
|
452
|
+
* @param predicate The current active clause predicate.
|
|
453
|
+
* @returns A materialized view query.
|
|
435
454
|
*/
|
|
436
|
-
query(predicate) {
|
|
437
|
-
return this.select.clone().where(this.active.predicate(predicate));
|
|
455
|
+
query(predicate: ExprNode): SelectQuery {
|
|
456
|
+
return this.select.clone().where(this.active.predicate!(predicate)!);
|
|
438
457
|
}
|
|
439
|
-
}
|
|
458
|
+
}
|
|
@@ -1,18 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* @import { MosaicClient } from '../MosaicClient.js'
|
|
4
|
-
*/
|
|
1
|
+
import type { AggregateNode, ColumnRefNode, ExprNode, Query, SelectQuery } from '@uwdata/mosaic-sql';
|
|
2
|
+
import type { MosaicClient } from '../MosaicClient.js';
|
|
5
3
|
import { collectAggregates, isAggregateExpression, isSelectQuery, isTableRef, rewrite, sql } from '@uwdata/mosaic-sql';
|
|
6
4
|
import { sufficientStatistics } from './sufficient-statistics.js';
|
|
7
5
|
|
|
6
|
+
export interface PreAggColumnsResult {
|
|
7
|
+
group: string[];
|
|
8
|
+
preagg: Record<string, ExprNode>;
|
|
9
|
+
output: Record<string, ExprNode>;
|
|
10
|
+
}
|
|
11
|
+
|
|
8
12
|
/**
|
|
9
13
|
* Determine pre-aggregation columns for a given Mosaic client.
|
|
10
|
-
* @param
|
|
14
|
+
* @param client The Mosaic client.
|
|
11
15
|
* @returns An object with necessary column data to generate pre-aggregated
|
|
12
16
|
* columns, or null if the client can't be optimized or the client query
|
|
13
17
|
* contains an invalid or unsupported expression.
|
|
14
18
|
*/
|
|
15
|
-
export function preaggColumns(client) {
|
|
19
|
+
export function preaggColumns(client: MosaicClient): PreAggColumnsResult | null {
|
|
16
20
|
if (!client.filterStable) return null;
|
|
17
21
|
const q = client.query();
|
|
18
22
|
|
|
@@ -26,17 +30,13 @@ export function preaggColumns(client) {
|
|
|
26
30
|
});
|
|
27
31
|
if (typeof from !== 'string') return null;
|
|
28
32
|
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
/** @type {Record<string, ExprNode>} */
|
|
34
|
-
const output = {};
|
|
35
|
-
/** @type {string[]} */
|
|
36
|
-
const group = []; // list of grouping dimension columns
|
|
33
|
+
const aggrs = new Map<AggregateNode, ExprNode>();
|
|
34
|
+
const preagg: Record<string, ExprNode> = {};
|
|
35
|
+
const output: Record<string, ExprNode> = {};
|
|
36
|
+
const group: string[] = []; // list of grouping dimension columns
|
|
37
37
|
|
|
38
38
|
// generate a scalar subquery for a global average
|
|
39
|
-
const avg = ref => {
|
|
39
|
+
const avg = (ref: ColumnRefNode) => {
|
|
40
40
|
const name = ref.column;
|
|
41
41
|
const expr = getBase(q, q => q._select.find(c => c.alias === name)?.expr);
|
|
42
42
|
return sql`(SELECT avg(${expr ?? ref}) FROM "${from}")`;
|
|
@@ -46,13 +46,13 @@ export function preaggColumns(client) {
|
|
|
46
46
|
for (const { alias, expr } of q._select) {
|
|
47
47
|
// bail if there is an aggregate we can't analyze
|
|
48
48
|
// a value > 1 indicates an aggregate in verbatim text
|
|
49
|
-
if (isAggregateExpression(expr) > 1) return null;
|
|
49
|
+
if (isAggregateExpression(expr!) > 1) return null;
|
|
50
50
|
|
|
51
|
-
const nodes = collectAggregates(expr);
|
|
51
|
+
const nodes = collectAggregates(expr!);
|
|
52
52
|
if (nodes.length === 0) {
|
|
53
53
|
// if no aggregates, expr is a groupby dimension
|
|
54
54
|
group.push(alias);
|
|
55
|
-
preagg[alias] = expr
|
|
55
|
+
preagg[alias] = expr!;
|
|
56
56
|
} else {
|
|
57
57
|
for (const node of nodes) {
|
|
58
58
|
// bail if distinct aggregate
|
|
@@ -66,7 +66,7 @@ export function preaggColumns(client) {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
// rewrite original select clause to use preaggregates
|
|
69
|
-
output[alias] = rewrite(expr
|
|
69
|
+
output[alias] = rewrite(expr!, aggrs)!;
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
|
|
@@ -80,14 +80,17 @@ export function preaggColumns(client) {
|
|
|
80
80
|
* Identify a shared base (source) query and extract a value from it.
|
|
81
81
|
* This method is used to find a shared base table name or extract
|
|
82
82
|
* the original column name within a base table.
|
|
83
|
-
* @param
|
|
84
|
-
* @param
|
|
83
|
+
* @param query The input query.
|
|
84
|
+
* @param get A getter function to extract
|
|
85
85
|
* a value from a base query.
|
|
86
|
-
* @returns
|
|
86
|
+
* @returns the base query value, or
|
|
87
87
|
* `undefined` if there is no source table, or `NaN` if the
|
|
88
88
|
* query operates over multiple source tables.
|
|
89
89
|
*/
|
|
90
|
-
function getBase(
|
|
90
|
+
function getBase<T>(
|
|
91
|
+
query: Query,
|
|
92
|
+
get: (q: SelectQuery) => T
|
|
93
|
+
): T | number | undefined {
|
|
91
94
|
const subq = query.subqueries;
|
|
92
95
|
|
|
93
96
|
// select query
|
|
@@ -103,4 +106,4 @@ function getBase(query, get) {
|
|
|
103
106
|
if (value !== base) return NaN;
|
|
104
107
|
}
|
|
105
108
|
return base;
|
|
106
|
-
}
|
|
109
|
+
}
|