@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.
Files changed (178) hide show
  1. package/LICENSE +47 -0
  2. package/README.md +0 -1
  3. package/dist/src/Coordinator.d.ts +159 -0
  4. package/dist/src/Coordinator.d.ts.map +1 -0
  5. package/dist/src/Coordinator.js +250 -0
  6. package/dist/src/Coordinator.js.map +1 -0
  7. package/dist/src/MosaicClient.d.ts +138 -0
  8. package/dist/src/MosaicClient.d.ts.map +1 -0
  9. package/dist/src/MosaicClient.js +214 -0
  10. package/dist/src/MosaicClient.js.map +1 -0
  11. package/dist/src/Param.d.ts +56 -0
  12. package/dist/src/Param.d.ts.map +1 -0
  13. package/dist/src/Param.js +89 -0
  14. package/dist/src/Param.js.map +1 -0
  15. package/dist/src/QueryConsolidator.d.ts +11 -0
  16. package/dist/src/QueryConsolidator.d.ts.map +1 -0
  17. package/dist/src/QueryConsolidator.js +249 -0
  18. package/dist/src/QueryConsolidator.js.map +1 -0
  19. package/dist/src/QueryManager.d.ts +77 -0
  20. package/dist/src/QueryManager.d.ts.map +1 -0
  21. package/dist/src/QueryManager.js +174 -0
  22. package/dist/src/QueryManager.js.map +1 -0
  23. package/dist/src/Selection.d.ts +222 -0
  24. package/dist/src/Selection.d.ts.map +1 -0
  25. package/dist/src/Selection.js +322 -0
  26. package/dist/src/Selection.js.map +1 -0
  27. package/dist/src/SelectionClause.d.ts +222 -0
  28. package/dist/src/SelectionClause.d.ts.map +1 -0
  29. package/dist/src/SelectionClause.js +168 -0
  30. package/dist/src/SelectionClause.js.map +1 -0
  31. package/dist/src/connectors/Connector.d.ts +26 -0
  32. package/dist/src/connectors/Connector.d.ts.map +1 -0
  33. package/dist/src/connectors/Connector.js +2 -0
  34. package/dist/src/connectors/Connector.js.map +1 -0
  35. package/dist/src/connectors/rest.d.ts +24 -0
  36. package/dist/src/connectors/rest.d.ts.map +1 -0
  37. package/dist/src/connectors/rest.js +37 -0
  38. package/dist/src/connectors/rest.js.map +1 -0
  39. package/dist/src/connectors/socket.d.ts +40 -0
  40. package/dist/src/connectors/socket.d.ts.map +1 -0
  41. package/dist/src/connectors/socket.js +115 -0
  42. package/dist/src/connectors/socket.js.map +1 -0
  43. package/dist/src/connectors/wasm.d.ts +56 -0
  44. package/dist/src/connectors/wasm.d.ts.map +1 -0
  45. package/dist/src/connectors/wasm.js +116 -0
  46. package/dist/src/connectors/wasm.js.map +1 -0
  47. package/dist/src/index.d.ts +28 -0
  48. package/dist/src/index.d.ts.map +1 -0
  49. package/{src → dist/src}/index.js +8 -11
  50. package/dist/src/index.js.map +1 -0
  51. package/dist/src/make-client.d.ts +33 -0
  52. package/dist/src/make-client.d.ts.map +1 -0
  53. package/dist/src/make-client.js +52 -0
  54. package/dist/src/make-client.js.map +1 -0
  55. package/dist/src/preagg/PreAggregator.d.ts +150 -0
  56. package/dist/src/preagg/PreAggregator.d.ts.map +1 -0
  57. package/dist/src/preagg/PreAggregator.js +382 -0
  58. package/dist/src/preagg/PreAggregator.js.map +1 -0
  59. package/dist/src/preagg/preagg-columns.d.ts +16 -0
  60. package/dist/src/preagg/preagg-columns.d.ts.map +1 -0
  61. package/dist/src/preagg/preagg-columns.js +95 -0
  62. package/dist/src/preagg/preagg-columns.js.map +1 -0
  63. package/dist/src/preagg/sufficient-statistics.d.ts +14 -0
  64. package/dist/src/preagg/sufficient-statistics.d.ts.map +1 -0
  65. package/dist/src/preagg/sufficient-statistics.js +446 -0
  66. package/dist/src/preagg/sufficient-statistics.js.map +1 -0
  67. package/dist/src/types.d.ts +77 -0
  68. package/dist/src/types.d.ts.map +1 -0
  69. package/dist/src/types.js +2 -0
  70. package/dist/src/types.js.map +1 -0
  71. package/dist/src/util/AsyncDispatch.d.ts +121 -0
  72. package/dist/src/util/AsyncDispatch.d.ts.map +1 -0
  73. package/dist/src/util/AsyncDispatch.js +188 -0
  74. package/dist/src/util/AsyncDispatch.js.map +1 -0
  75. package/dist/src/util/cache.d.ts +19 -0
  76. package/dist/src/util/cache.d.ts.map +1 -0
  77. package/dist/src/util/cache.js +66 -0
  78. package/dist/src/util/cache.js.map +1 -0
  79. package/dist/src/util/decode-ipc.d.ts +12 -0
  80. package/dist/src/util/decode-ipc.d.ts.map +1 -0
  81. package/{src → dist/src}/util/decode-ipc.js +5 -6
  82. package/dist/src/util/decode-ipc.js.map +1 -0
  83. package/dist/src/util/distinct.d.ts +3 -0
  84. package/dist/src/util/distinct.d.ts.map +1 -0
  85. package/dist/src/util/distinct.js +16 -0
  86. package/dist/src/util/distinct.js.map +1 -0
  87. package/dist/src/util/field-info.d.ts +26 -0
  88. package/dist/src/util/field-info.d.ts.map +1 -0
  89. package/dist/src/util/field-info.js +91 -0
  90. package/dist/src/util/field-info.js.map +1 -0
  91. package/dist/src/util/hash.d.ts +2 -0
  92. package/dist/src/util/hash.d.ts.map +1 -0
  93. package/dist/src/util/hash.js +26 -0
  94. package/dist/src/util/hash.js.map +1 -0
  95. package/dist/src/util/is-activatable.d.ts +8 -0
  96. package/dist/src/util/is-activatable.d.ts.map +1 -0
  97. package/dist/src/util/is-activatable.js +10 -0
  98. package/dist/src/util/is-activatable.js.map +1 -0
  99. package/dist/src/util/is-arrow-table.d.ts +9 -0
  100. package/dist/src/util/is-arrow-table.d.ts.map +1 -0
  101. package/dist/src/util/is-arrow-table.js +11 -0
  102. package/dist/src/util/is-arrow-table.js.map +1 -0
  103. package/dist/src/util/js-type.d.ts +9 -0
  104. package/dist/src/util/js-type.d.ts.map +1 -0
  105. package/dist/src/util/js-type.js +59 -0
  106. package/dist/src/util/js-type.js.map +1 -0
  107. package/dist/src/util/priority-queue.d.ts +35 -0
  108. package/dist/src/util/priority-queue.d.ts.map +1 -0
  109. package/dist/src/util/priority-queue.js +81 -0
  110. package/dist/src/util/priority-queue.js.map +1 -0
  111. package/dist/src/util/query-result.d.ts +47 -0
  112. package/dist/src/util/query-result.d.ts.map +1 -0
  113. package/dist/src/util/query-result.js +83 -0
  114. package/dist/src/util/query-result.js.map +1 -0
  115. package/dist/src/util/synchronizer.d.ts +36 -0
  116. package/dist/src/util/synchronizer.d.ts.map +1 -0
  117. package/dist/src/util/synchronizer.js +52 -0
  118. package/dist/src/util/synchronizer.js.map +1 -0
  119. package/dist/src/util/throttle.d.ts +12 -0
  120. package/dist/src/util/throttle.d.ts.map +1 -0
  121. package/dist/src/util/throttle.js +51 -0
  122. package/dist/src/util/throttle.js.map +1 -0
  123. package/dist/src/util/to-data-columns.d.ts +22 -0
  124. package/dist/src/util/to-data-columns.d.ts.map +1 -0
  125. package/dist/src/util/to-data-columns.js +51 -0
  126. package/dist/src/util/to-data-columns.js.map +1 -0
  127. package/dist/src/util/void-logger.d.ts +13 -0
  128. package/dist/src/util/void-logger.d.ts.map +1 -0
  129. package/dist/src/util/void-logger.js +13 -0
  130. package/dist/src/util/void-logger.js.map +1 -0
  131. package/package.json +17 -11
  132. package/src/Coordinator.ts +396 -0
  133. package/src/{MosaicClient.js → MosaicClient.ts} +50 -43
  134. package/src/{Param.js → Param.ts} +29 -28
  135. package/src/{QueryConsolidator.js → QueryConsolidator.ts} +85 -62
  136. package/src/{QueryManager.js → QueryManager.ts} +61 -54
  137. package/src/Selection.ts +391 -0
  138. package/src/SelectionClause.ts +357 -0
  139. package/src/connectors/Connector.ts +6 -6
  140. package/src/connectors/rest.ts +56 -0
  141. package/src/connectors/{socket.js → socket.ts} +53 -42
  142. package/src/connectors/{wasm.js → wasm.ts} +52 -63
  143. package/src/index.ts +42 -0
  144. package/src/make-client.ts +93 -0
  145. package/src/preagg/{PreAggregator.js → PreAggregator.ts} +164 -145
  146. package/src/preagg/{preagg-columns.js → preagg-columns.ts} +27 -24
  147. package/src/preagg/{sufficient-statistics.js → sufficient-statistics.ts} +160 -110
  148. package/src/types.ts +24 -9
  149. package/src/util/{AsyncDispatch.js → AsyncDispatch.ts} +62 -43
  150. package/src/util/{cache.js → cache.ts} +25 -15
  151. package/src/util/decode-ipc.ts +15 -0
  152. package/src/util/{distinct.js → distinct.ts} +3 -3
  153. package/src/util/{field-info.js → field-info.ts} +30 -31
  154. package/src/util/{hash.js → hash.ts} +4 -4
  155. package/src/util/is-activatable.ts +11 -0
  156. package/src/util/is-arrow-table.ts +12 -0
  157. package/src/util/{js-type.js → js-type.ts} +7 -5
  158. package/src/util/{priority-queue.js → priority-queue.ts} +32 -20
  159. package/src/util/{query-result.js → query-result.ts} +24 -17
  160. package/src/util/synchronizer.ts +56 -0
  161. package/src/util/throttle.ts +59 -0
  162. package/src/util/to-data-columns.ts +65 -0
  163. package/src/util/void-logger.ts +23 -0
  164. package/src/Coordinator.js +0 -313
  165. package/src/Selection.js +0 -380
  166. package/src/SelectionClause.js +0 -159
  167. package/src/connectors/rest.js +0 -38
  168. package/src/index-types.ts +0 -5
  169. package/src/make-client.js +0 -101
  170. package/src/util/is-activatable.js +0 -8
  171. package/src/util/is-arrow-table.js +0 -10
  172. package/src/util/selection-types.ts +0 -137
  173. package/src/util/synchronizer.js +0 -47
  174. package/src/util/throttle.js +0 -54
  175. package/src/util/to-data-columns.js +0 -60
  176. package/src/util/void-logger.js +0 -13
  177. package/tsconfig.json +0 -9
  178. package/vitest.config.ts +0 -3
@@ -1,23 +1,35 @@
1
- /**
2
- * @import { ExprNode, SelectQuery } from '@uwdata/mosaic-sql'
3
- * @import { Coordinator } from '../Coordinator.js'
4
- * @import { MosaicClient } from '../MosaicClient.js'
5
- * @import { Selection } from '../Selection.js'
6
- * @import { BinMethod, Scale, SelectionClause } from '../util/selection-types.js'
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
- * @typedef {object} PreAggregateOptions
16
- * @property {string} [schema] Database schema (namespace) in which to write
17
- * pre-aggregated materialzied views (default 'mosaic').
18
- * @property {boolean} [options.enabled=true] Flag to enable or disable the
19
- * pre-aggregation. This flag can be updated later via the `enabled` property.
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 {Coordinator} coordinator A Mosaic coordinator.
45
- * @param {PreAggregateOptions} [options] Pre-aggregation options.
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 {boolean} [state] The enabled state to set.
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 {boolean} The current enabled state.
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 {string} [schema] The schema name to set.
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 {string} The current schema name.
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 {MosaicClient} client A Mosaic client.
134
- * @param {Selection} selection A Mosaic selection to filter the client by.
135
- * @param {SelectionClause} activeClause A representative active selection
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 {PreAggregateInfo | Skip | null} Information and query generator
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(client.query(filter), active, preaggCols, schema);
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 {SelectionClause} clause The active selection clause to analyze.
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
- // @ts-ignore
227
- const { type, scales, bin, pixelSize = 1 } = meta;
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
- // @ts-ignore
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 {Scale} scale A scale that maps domain values to the output range
292
+ * @param scale A scale that maps domain values to the output range
269
293
  * (typically pixels).
270
- * @param {number} pixelSize The interactive pixel size. This value indicates
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 {BinMethod} bin The binning method to apply, one of `floor`,
273
- * `ceil', or `round`.
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(scale, pixelSize, bin) {
277
- const { type, domain, range, apply, sqlApply } = scaleTransform(scale);
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 lo = apply(Math.min(...domain));
281
- const hi = apply(Math.max(...domain));
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 {SelectQuery} clientQuery The original client query.
297
- * @param {ReturnType<activeColumns>} active Active (selected) columns.
298
- * @param {ReturnType<preaggColumns>} preaggCols Pre-aggregation columns.
299
- * @returns {PreAggregateInfo}
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(clientQuery, active, preaggCols, schema) {
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 = Query
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 {Query} query The (sub)query to push down to.
343
- * @param {string[]} cols The column names to push down.
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 {SelectQuery} query
374
- * @returns {boolean}
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 {object} options Options object.
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 {ExprNode} predicate The current active clause predicate.
434
- * @returns {SelectQuery} A materialized view query.
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
- * @import { AggregateNode, ExprNode, Query, SelectQuery } from '@uwdata/mosaic-sql'
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 {MosaicClient} client The Mosaic client.
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
- /** @type {Map<AggregateNode, ExprNode>} */
30
- const aggrs = new Map;
31
- /** @type {Record<string, ExprNode>} */
32
- const preagg = {};
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, aggrs);
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 {Query} query The input query.
84
- * @param {(q: SelectQuery) => any} get A getter function to extract
83
+ * @param query The input query.
84
+ * @param get A getter function to extract
85
85
  * a value from a base query.
86
- * @returns {string | undefined | NaN} the base query value, or
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(query, get) {
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
+ }