@uwdata/mosaic-core 0.11.0 → 0.12.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.
Files changed (56) hide show
  1. package/README.md +3 -1
  2. package/dist/mosaic-core.js +11613 -10856
  3. package/dist/mosaic-core.min.js +7 -7
  4. package/dist/types/Coordinator.d.ts +169 -0
  5. package/dist/types/MosaicClient.d.ts +94 -0
  6. package/dist/types/Param.d.ts +47 -0
  7. package/dist/types/QueryConsolidator.d.ts +9 -0
  8. package/dist/types/QueryManager.d.ts +64 -0
  9. package/dist/types/Selection.d.ts +224 -0
  10. package/dist/types/SelectionClause.d.ts +105 -0
  11. package/dist/types/connectors/rest.d.ts +17 -0
  12. package/dist/types/connectors/socket.d.ts +18 -0
  13. package/dist/types/connectors/wasm.d.ts +16 -0
  14. package/dist/types/index.d.ts +25 -0
  15. package/dist/types/preagg/PreAggregator.d.ts +178 -0
  16. package/dist/types/preagg/preagg-columns.d.ts +14 -0
  17. package/dist/types/preagg/sufficient-statistics.d.ts +13 -0
  18. package/dist/types/util/AsyncDispatch.d.ts +100 -0
  19. package/dist/types/util/cache.d.ts +13 -0
  20. package/dist/types/util/decode-ipc.d.ts +7 -0
  21. package/dist/types/util/distinct.d.ts +2 -0
  22. package/dist/types/util/field-info.d.ts +13 -0
  23. package/dist/types/util/hash.d.ts +1 -0
  24. package/dist/types/util/is-arrow-table.d.ts +8 -0
  25. package/dist/types/util/js-type.d.ts +1 -0
  26. package/dist/types/util/priority-queue.d.ts +37 -0
  27. package/dist/types/util/query-result.d.ts +44 -0
  28. package/dist/types/util/selection-types.d.ts +114 -0
  29. package/dist/types/util/synchronizer.d.ts +29 -0
  30. package/dist/types/util/throttle.d.ts +11 -0
  31. package/dist/types/util/to-data-columns.d.ts +29 -0
  32. package/dist/types/util/void-logger.d.ts +7 -0
  33. package/jsconfig.json +11 -0
  34. package/package.json +10 -8
  35. package/src/Coordinator.js +14 -14
  36. package/src/MosaicClient.js +5 -4
  37. package/src/QueryConsolidator.js +22 -33
  38. package/src/QueryManager.js +76 -45
  39. package/src/Selection.js +8 -5
  40. package/src/SelectionClause.js +20 -23
  41. package/src/connectors/rest.js +3 -1
  42. package/src/connectors/socket.js +3 -1
  43. package/src/connectors/wasm.js +1 -1
  44. package/src/index.js +13 -0
  45. package/src/preagg/PreAggregator.js +407 -0
  46. package/src/preagg/preagg-columns.js +103 -0
  47. package/src/preagg/sufficient-statistics.js +439 -0
  48. package/src/util/field-info.js +16 -5
  49. package/src/util/hash.js +1 -1
  50. package/src/util/query-result.js +44 -2
  51. package/src/util/selection-types.ts +3 -3
  52. package/src/util/throttle.js +11 -9
  53. package/src/util/void-logger.js +6 -5
  54. package/tsconfig.json +11 -0
  55. package/src/DataCubeIndexer.js +0 -378
  56. package/src/util/index-columns.js +0 -537
@@ -1,56 +1,85 @@
1
1
  import { consolidator } from './QueryConsolidator.js';
2
2
  import { lruCache, voidCache } from './util/cache.js';
3
3
  import { PriorityQueue } from './util/priority-queue.js';
4
- import { QueryResult } from './util/query-result.js';
4
+ import { QueryResult, QueryState } from './util/query-result.js';
5
+ import { voidLogger } from './util/void-logger.js';
5
6
 
6
- export const Priority = { High: 0, Normal: 1, Low: 2 };
7
+ export const Priority = Object.freeze({ High: 0, Normal: 1, Low: 2 });
7
8
 
8
9
  export class QueryManager {
9
- constructor() {
10
+ constructor(
11
+ maxConcurrentRequests = 32
12
+ ) {
10
13
  this.queue = new PriorityQueue(3);
11
14
  this.db = null;
12
15
  this.clientCache = null;
13
- this._logger = null;
16
+ this._logger = voidLogger();
14
17
  this._logQueries = false;
15
- this.recorders = [];
16
- this.pending = null;
17
18
  this._consolidate = null;
19
+ /**
20
+ * Requests pending with the query manager.
21
+ *
22
+ * @type {QueryResult[]}
23
+ */
24
+ this.pendingResults = [];
25
+ this.maxConcurrentRequests = maxConcurrentRequests;
26
+ this.pendingExec = false;
18
27
  }
19
28
 
20
29
  next() {
21
- if (this.pending || this.queue.isEmpty()) return;
30
+ if (this.queue.isEmpty() || this.pendingResults.length > this.maxConcurrentRequests || this.pendingExec) {
31
+ return;
32
+ }
33
+
22
34
  const { request, result } = this.queue.next();
23
- this.pending = this.submit(request, result);
24
- this.pending.finally(() => { this.pending = null; this.next(); });
35
+
36
+ this.pendingResults.push(result);
37
+ if (request.type === 'exec') this.pendingExec = true;
38
+
39
+ this.submit(request, result).finally(() => {
40
+ // return from the queue all requests that are ready
41
+ while (this.pendingResults.length && this.pendingResults[0].state !== QueryState.pending) {
42
+ const result = this.pendingResults.shift();
43
+ if (result.state === QueryState.ready) {
44
+ result.fulfill();
45
+ } else if (result.state === QueryState.done) {
46
+ this._logger.warn('Found resolved query in pending results.');
47
+ }
48
+ }
49
+ if (request.type === 'exec') this.pendingExec = false;
50
+ this.next();
51
+ });
25
52
  }
26
53
 
54
+ /**
55
+ * Add an entry to the query queue with a priority.
56
+ * @param {object} entry The entry to add.
57
+ * @param {*} [entry.request] The query request.
58
+ * @param {QueryResult} [entry.result] The query result.
59
+ * @param {number} priority The query priority, defaults to `Priority.Normal`.
60
+ */
27
61
  enqueue(entry, priority = Priority.Normal) {
28
62
  this.queue.insert(entry, priority);
29
63
  this.next();
30
64
  }
31
65
 
32
- recordQuery(sql) {
33
- if (this.recorders.length && sql) {
34
- this.recorders.forEach(rec => rec.add(sql));
35
- }
36
- }
37
-
66
+ /**
67
+ * Submit the query to the connector.
68
+ * @param {*} request The request.
69
+ * @param {QueryResult} result The query result.
70
+ */
38
71
  async submit(request, result) {
39
72
  try {
40
- const { query, type, cache = false, record = true, options } = request;
73
+ const { query, type, cache = false, options } = request;
41
74
  const sql = query ? `${query}` : null;
42
75
 
43
- // update recorders
44
- if (record) {
45
- this.recordQuery(sql);
46
- }
47
-
48
76
  // check query cache
49
77
  if (cache) {
50
78
  const cached = this.clientCache.get(sql);
51
79
  if (cached) {
80
+ const data = await cached;
52
81
  this._logger.debug('Cache');
53
- result.fulfill(cached);
82
+ result.ready(data);
54
83
  return;
55
84
  }
56
85
  }
@@ -60,10 +89,16 @@ export class QueryManager {
60
89
  if (this._logQueries) {
61
90
  this._logger.debug('Query', { type, sql, ...options });
62
91
  }
63
- const data = await this.db.query({ type, sql, ...options });
92
+
93
+ const promise = this.db.query({ type, sql, ...options });
94
+ if (cache) this.clientCache.set(sql, promise);
95
+
96
+ const data = await promise;
97
+
64
98
  if (cache) this.clientCache.set(sql, data);
99
+
65
100
  this._logger.debug(`Request: ${(performance.now() - t0).toFixed(1)}`);
66
- result.fulfill(data);
101
+ result.ready(type === 'exec' ? null : data);
67
102
  } catch (err) {
68
103
  result.reject(err);
69
104
  }
@@ -89,12 +124,18 @@ export class QueryManager {
89
124
 
90
125
  consolidate(flag) {
91
126
  if (flag && !this._consolidate) {
92
- this._consolidate = consolidator(this.enqueue.bind(this), this.clientCache, this.recordQuery.bind(this));
127
+ this._consolidate = consolidator(this.enqueue.bind(this), this.clientCache);
93
128
  } else if (!flag && this._consolidate) {
94
129
  this._consolidate = null;
95
130
  }
96
131
  }
97
132
 
133
+ /**
134
+ * Request a query result.
135
+ * @param {*} request The request.
136
+ * @param {number} priority The query priority, defaults to `Priority.Normal`.
137
+ * @returns {QueryResult} A query result promise.
138
+ */
98
139
  request(request, priority = Priority.Normal) {
99
140
  const result = new QueryResult();
100
141
  const entry = { request, result };
@@ -116,6 +157,12 @@ export class QueryManager {
116
157
  }
117
158
  return false;
118
159
  });
160
+
161
+ for (const result of this.pendingResults) {
162
+ if (set.has(result)) {
163
+ result.reject('Canceled');
164
+ }
165
+ }
119
166
  }
120
167
  }
121
168
 
@@ -124,26 +171,10 @@ export class QueryManager {
124
171
  result.reject('Cleared');
125
172
  return true;
126
173
  });
127
- }
128
174
 
129
- record() {
130
- let state = [];
131
- const recorder = {
132
- add(query) {
133
- state.push(query);
134
- },
135
- reset() {
136
- state = [];
137
- },
138
- snapshot() {
139
- return state.slice();
140
- },
141
- stop() {
142
- this.recorders = this.recorders.filter(x => x !== recorder);
143
- return state;
144
- }
145
- };
146
- this.recorders.push(recorder);
147
- return recorder;
175
+ for (const result of this.pendingResults) {
176
+ result.reject('Cleared');
177
+ }
178
+ this.pendingResults = [];
148
179
  }
149
180
  }
package/src/Selection.js CHANGED
@@ -1,5 +1,6 @@
1
- import { or } from '@uwdata/mosaic-sql';
1
+ import { literal, or } from '@uwdata/mosaic-sql';
2
2
  import { Param } from './Param.js';
3
+ import { MosaicClient } from './MosaicClient.js';
3
4
 
4
5
  /**
5
6
  * Test if a value is a Selection instance.
@@ -315,9 +316,11 @@ export class SelectionResolver {
315
316
 
316
317
  /**
317
318
  * Return a selection query predicate for the given client.
318
- * @param {*[]} clauseList An array of selection clauses.
319
- * @param {*} active The current active selection clause.
320
- * @param {*} client The client whose data may be filtered.
319
+ * @param {import('./util/selection-types.js').SelectionClause[]} clauseList
320
+ * An array of selection clauses.
321
+ * @param {import('./util/selection-types.js').SelectionClause} active
322
+ * The current active selection clause.
323
+ * @param {MosaicClient} client The client whose data may be filtered.
321
324
  * @returns {*} The query predicate for filtering client data,
322
325
  * based on the current state of this selection.
323
326
  */
@@ -325,7 +328,7 @@ export class SelectionResolver {
325
328
  const { empty, union } = this;
326
329
 
327
330
  if (empty && !clauseList.length) {
328
- return ['FALSE'];
331
+ return [literal(false)];
329
332
  }
330
333
 
331
334
  // do nothing if cross-filtering and client is currently active
@@ -1,7 +1,4 @@
1
- import {
2
- SQLExpression, and, contains, isBetween, isNotDistinct, literal,
3
- or, prefix, regexp_matches, suffix
4
- } from '@uwdata/mosaic-sql';
1
+ import { ExprNode, and, contains, isBetween, isIn, isNotDistinct, literal, or, prefix, regexp_matches, suffix } from '@uwdata/mosaic-sql';
5
2
  import { MosaicClient } from './MosaicClient.js';
6
3
 
7
4
  /**
@@ -10,12 +7,11 @@ import { MosaicClient } from './MosaicClient.js';
10
7
  * @typedef {import('./util/selection-types.js').Extent} Extent
11
8
  * @typedef {import('./util/selection-types.js').MatchMethod} MatchMethod
12
9
  * @typedef {import('./util/selection-types.js').BinMethod} BinMethod
13
- * @typedef {SQLExpression | string} Field
14
10
  */
15
11
 
16
12
  /**
17
13
  * Generate a selection clause for a single selected point value.
18
- * @param {Field} field The table column or expression to select.
14
+ * @param {import('@uwdata/mosaic-sql').ExprValue} field The table column or expression to select.
19
15
  * @param {*} value The selected value.
20
16
  * @param {object} options Additional clause properties.
21
17
  * @param {*} options.source The source component generating this clause.
@@ -28,9 +24,9 @@ export function clausePoint(field, value, {
28
24
  source,
29
25
  clients = source ? new Set([source]) : undefined
30
26
  }) {
31
- /** @type {SQLExpression | null} */
27
+ /** @type {ExprNode | null} */
32
28
  const predicate = value !== undefined
33
- ? isNotDistinct(field, literal(value))
29
+ ? isIn(field, [literal(value)])
34
30
  : null;
35
31
  return {
36
32
  meta: { type: 'point' },
@@ -43,9 +39,9 @@ export function clausePoint(field, value, {
43
39
 
44
40
  /**
45
41
  * Generate a selection clause for multiple selected point values.
46
- * @param {Field[]} fields The table columns or expressions to select.
47
- * @param {any[][]} value The selected values, as an array of arrays where
48
- * each subarray contains values corresponding to each *fields* entry.
42
+ * @param {import('@uwdata/mosaic-sql').ExprValue[]} fields The table columns or expressions to select.
43
+ * @param {any[][] | undefined} value The selected values, as an array of
44
+ * arrays. Each subarray contains values for each *fields* entry.
49
45
  * @param {object} options Additional clause properties.
50
46
  * @param {*} options.source The source component generating this clause.
51
47
  * @param {Set<MosaicClient>} [options.clients] The Mosaic clients associated
@@ -57,14 +53,15 @@ export function clausePoints(fields, value, {
57
53
  source,
58
54
  clients = source ? new Set([source]) : undefined
59
55
  }) {
60
- /** @type {SQLExpression | null} */
56
+ /** @type {ExprNode | null} */
61
57
  let predicate = null;
62
58
  if (value) {
63
- const clauses = value.map(vals => {
64
- const list = vals.map((v, i) => isNotDistinct(fields[i], literal(v)));
65
- return list.length > 1 ? and(list) : list[0];
66
- });
67
- predicate = clauses.length > 1 ? or(clauses) : clauses[0];
59
+ const clauses = value.length && fields.length === 1
60
+ ? [isIn(fields[0], value.map(v => literal(v[0])))]
61
+ : value.map(v => and(v.map((_, i) => isNotDistinct(fields[i], literal(_)))));
62
+ predicate = value.length === 0 ? literal(false)
63
+ : clauses.length > 1 ? or(clauses)
64
+ : clauses[0];
68
65
  }
69
66
  return {
70
67
  meta: { type: 'point' },
@@ -77,7 +74,7 @@ export function clausePoints(fields, value, {
77
74
 
78
75
  /**
79
76
  * Generate a selection clause for a selected 1D interval.
80
- * @param {Field} field The table column or expression to select.
77
+ * @param {import('@uwdata/mosaic-sql').ExprValue} field The table column or expression to select.
81
78
  * @param {Extent} value The selected interval as a [lo, hi] array.
82
79
  * @param {object} options Additional clause properties.
83
80
  * @param {*} options.source The source component generating this clause.
@@ -96,7 +93,7 @@ export function clauseInterval(field, value, {
96
93
  scale,
97
94
  pixelSize = 1
98
95
  }) {
99
- /** @type {SQLExpression | null} */
96
+ /** @type {ExprNode | null} */
100
97
  const predicate = value != null ? isBetween(field, value) : null;
101
98
  /** @type {import('./util/selection-types.js').IntervalMetadata} */
102
99
  const meta = { type: 'interval', scales: scale && [scale], bin, pixelSize };
@@ -105,7 +102,7 @@ export function clauseInterval(field, value, {
105
102
 
106
103
  /**
107
104
  * Generate a selection clause for multiple selected intervals.
108
- * @param {Field[]} fields The table columns or expressions to select.
105
+ * @param {import('@uwdata/mosaic-sql').ExprValue[]} fields The table columns or expressions to select.
109
106
  * @param {Extent[]} value The selected intervals, as an array of extents.
110
107
  * @param {object} options Additional clause properties.
111
108
  * @param {*} options.source The source component generating this clause.
@@ -125,7 +122,7 @@ export function clauseIntervals(fields, value, {
125
122
  scales = [],
126
123
  pixelSize = 1
127
124
  }) {
128
- /** @type {SQLExpression | null} */
125
+ /** @type {ExprNode | null} */
129
126
  const predicate = value != null
130
127
  ? and(fields.map((f, i) => isBetween(f, value[i])))
131
128
  : null;
@@ -138,7 +135,7 @@ const MATCH_METHODS = { contains, prefix, suffix, regexp: regexp_matches };
138
135
 
139
136
  /**
140
137
  * Generate a selection clause for text search matching.
141
- * @param {Field} field The table column or expression to select.
138
+ * @param {import('@uwdata/mosaic-sql').ExprValue} field The table column or expression to select.
142
139
  * @param {string} value The selected text search query string.
143
140
  * @param {object} options Additional clause properties.
144
141
  * @param {*} options.source The source component generating this clause.
@@ -153,7 +150,7 @@ export function clauseMatch(field, value, {
153
150
  source, clients = undefined, method = 'contains'
154
151
  }) {
155
152
  let fn = MATCH_METHODS[method];
156
- /** @type {SQLExpression | null} */
153
+ /** @type {ExprNode | null} */
157
154
  const predicate = value ? fn(field, literal(value)) : null;
158
155
  /** @type {import('./util/selection-types.js').MatchMetadata} */
159
156
  const meta = { type: 'match', method };
@@ -6,7 +6,9 @@ export function restConnector(uri = 'http://localhost:3000/') {
6
6
  * Query the DuckDB server.
7
7
  * @param {object} query
8
8
  * @param {'exec' | 'arrow' | 'json' | 'create-bundle' | 'load-bundle'} [query.type] The query type.
9
- * @param {string} query.sql A SQL query string.
9
+ * @param {string} [query.sql] A SQL query string.
10
+ * @param {string[]} [query.queries] The queries used to create a bundle.
11
+ * @param {string} [query.name] The name of a bundle to create or load.
10
12
  * @returns the query result
11
13
  */
12
14
  async query(query) {
@@ -86,7 +86,9 @@ export function socketConnector(uri = 'ws://localhost:3000/') {
86
86
  * Query the DuckDB server.
87
87
  * @param {object} query
88
88
  * @param {'exec' | 'arrow' | 'json' | 'create-bundle' | 'load-bundle'} [query.type] The query type.
89
- * @param {string} query.sql A SQL query string.
89
+ * @param {string} [query.sql] A SQL query string.
90
+ * @param {string[]} [query.queries] The queries used to create a bundle.
91
+ * @param {string} [query.name] The name of a bundle to create or load.
90
92
  * @returns the query result
91
93
  */
92
94
  query(query) {
@@ -61,7 +61,7 @@ export function wasmConnector(options = {}) {
61
61
  /**
62
62
  * Query the DuckDB-WASM instance.
63
63
  * @param {object} query
64
- * @param {'exec' | 'arrow' | 'json' | 'create-bundle' | 'load-bundle'} [query.type] The query type.
64
+ * @param {'exec' | 'arrow' | 'json'} [query.type] The query type.
65
65
  * @param {string} query.sql A SQL query string.
66
66
  * @returns the query result
67
67
  */
package/src/index.js CHANGED
@@ -22,3 +22,16 @@ export { isArrowTable } from './util/is-arrow-table.js';
22
22
  export { synchronizer } from './util/synchronizer.js';
23
23
  export { throttle } from './util/throttle.js';
24
24
  export { toDataColumns } from './util/to-data-columns.js';
25
+
26
+ /**
27
+ * @typedef {import('./util/selection-types.js').ClauseMetadata} ClauseMetadata
28
+ * @typedef {import('./util/selection-types.js').PointMetadata} PointMetadata
29
+ * @typedef {import('./util/selection-types.js').MatchMethod} MatchMethod
30
+ * @typedef {import('./util/selection-types.js').MatchMetadata} MatchMetadata
31
+ * @typedef {import('./util/selection-types.js').ScaleType} ScaleType
32
+ * @typedef {import('./util/selection-types.js').Extent} Extent
33
+ * @typedef {import('./util/selection-types.js').Scale} Scale
34
+ * @typedef {import('./util/selection-types.js').BinMethod} BinMethod
35
+ * @typedef {import('./util/selection-types.js').IntervalMetadata} IntervalMetadata
36
+ * @typedef {import('./util/selection-types.js').SelectionClause} SelectionClause
37
+ */