@uwdata/mosaic-core 0.10.0 → 0.12.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 (61) hide show
  1. package/README.md +1 -1
  2. package/dist/mosaic-core.js +12960 -21458
  3. package/dist/mosaic-core.min.js +7 -16
  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 +66 -41
  36. package/src/MosaicClient.js +14 -4
  37. package/src/QueryConsolidator.js +32 -39
  38. package/src/QueryManager.js +85 -48
  39. package/src/Selection.js +49 -15
  40. package/src/SelectionClause.js +19 -22
  41. package/src/connectors/rest.js +6 -4
  42. package/src/connectors/socket.js +7 -4
  43. package/src/connectors/wasm.js +20 -4
  44. package/src/index.js +16 -8
  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/decode-ipc.js +11 -0
  49. package/src/util/field-info.js +19 -16
  50. package/src/util/hash.js +1 -1
  51. package/src/util/is-arrow-table.js +10 -0
  52. package/src/util/priority-queue.js +75 -76
  53. package/src/util/query-result.js +44 -2
  54. package/src/util/selection-types.ts +3 -3
  55. package/src/util/throttle.js +21 -9
  56. package/src/util/to-data-columns.js +4 -15
  57. package/src/util/void-logger.js +6 -5
  58. package/tsconfig.json +11 -0
  59. package/src/DataCubeIndexer.js +0 -320
  60. package/src/util/convert-arrow.js +0 -145
  61. package/src/util/index-columns.js +0 -540
@@ -1,4 +1,4 @@
1
- import { Query, Ref, isDescribeQuery } from '@uwdata/mosaic-sql';
1
+ import { DescribeQuery, isAggregateExpression, isColumnRef, isDescribeQuery, isSelectQuery, Query } from '@uwdata/mosaic-sql';
2
2
  import { QueryResult } from './util/query-result.js';
3
3
 
4
4
  function wait(callback) {
@@ -13,10 +13,9 @@ function wait(callback) {
13
13
  * Create a consolidator to combine structurally compatible queries.
14
14
  * @param {*} enqueue Query manager enqueue method
15
15
  * @param {*} cache Client-side query cache (sql -> data)
16
- * @param {*} record Query recorder function
17
16
  * @returns A consolidator object
18
17
  */
19
- export function consolidator(enqueue, cache, record) {
18
+ export function consolidator(enqueue, cache) {
20
19
  let pending = [];
21
20
  let id = 0;
22
21
 
@@ -28,7 +27,7 @@ export function consolidator(enqueue, cache, record) {
28
27
 
29
28
  // build and issue consolidated queries
30
29
  for (const group of groups) {
31
- consolidate(group, enqueue, record);
30
+ consolidate(group, enqueue);
32
31
  processResults(group, cache);
33
32
  }
34
33
  }
@@ -75,18 +74,16 @@ function entryGroups(entries, cache) {
75
74
  * Queries with matching keys are conosolidation-compatible.
76
75
  * If a query is found in the cache, it is exempted from consolidation,
77
76
  * which is indicated by returning the precise query SQL as the key.
78
- * @param {*} query The input query.
77
+ * @param {Query | DescribeQuery} query The input query.
79
78
  * @param {*} cache The query cache (sql -> data).
80
79
  * @returns a key string
81
80
  */
82
81
  function consolidationKey(query, cache) {
83
82
  const sql = `${query}`;
84
- if (query instanceof Query && !cache.get(sql)) {
83
+ if (isSelectQuery(query) && !cache.get(sql)) {
85
84
  if (
86
- // @ts-ignore
87
- query.orderby().length || query.where().length ||
88
- // @ts-ignore
89
- query.qualify().length || query.having().length
85
+ query._orderby.length || query._where.length ||
86
+ query._qualify.length || query._having.length
90
87
  ) {
91
88
  // do not try to analyze if query includes clauses
92
89
  // that may refer to *derived* columns we can't resolve
@@ -94,19 +91,21 @@ function consolidationKey(query, cache) {
94
91
  }
95
92
 
96
93
  // create a derived query stripped of selections
97
- const q = query.clone().$select('*');
94
+ const q = query.clone().setSelect('*');
98
95
 
99
96
  // check group by criteria for compatibility
100
97
  // queries may refer to *derived* columns as group by criteria
101
98
  // we resolve these against the true grouping expressions
102
- const groupby = query.groupby();
103
- // @ts-ignore
99
+ const groupby = query._groupby;
104
100
  if (groupby.length) {
105
- const map = {}; // expression map (as -> expr)
106
- // @ts-ignore
107
- query.select().forEach(({ as, expr }) => map[as] = expr);
108
- // @ts-ignore
109
- q.$groupby(groupby.map(e => (e instanceof Ref && map[e.column]) || e));
101
+ const map = {}; // expression map (alias -> expr)
102
+ query._select.forEach(({ alias, expr }) => map[alias] = expr);
103
+ q.setGroupby(groupby.map(e => (isColumnRef(e) && map[e.column]) || e));
104
+ }
105
+ else if (query._select.some(e => isAggregateExpression(e.expr))) {
106
+ // if query is an ungrouped aggregate, add an explicit groupby to
107
+ // prevent improper consolidation with non-aggregate queries
108
+ q.setGroupby('ALL');
110
109
  }
111
110
 
112
111
  // key is just the transformed query as SQL
@@ -121,17 +120,15 @@ function consolidationKey(query, cache) {
121
120
  * Issue queries, consolidating where possible.
122
121
  * @param {*} group Array of bundled query entries
123
122
  * @param {*} enqueue Add entry to query queue
124
- * @param {*} record Query recorder function
125
123
  */
126
- function consolidate(group, enqueue, record) {
124
+ function consolidate(group, enqueue) {
127
125
  if (shouldConsolidate(group)) {
128
126
  // issue a single consolidated query
129
127
  enqueue({
130
128
  request: {
131
129
  type: 'arrow',
132
130
  cache: false,
133
- record: false,
134
- query: (group.query = consolidatedQuery(group, record))
131
+ query: (group.query = consolidatedQuery(group))
135
132
  },
136
133
  result: (group.result = new QueryResult())
137
134
  });
@@ -164,10 +161,9 @@ function shouldConsolidate(group) {
164
161
  /**
165
162
  * Create a consolidated query for a group.
166
163
  * @param {*} group Array of bundled query entries
167
- * @param {*} record Query recorder function
168
164
  * @returns A consolidated Query instance
169
165
  */
170
- function consolidatedQuery(group, record) {
166
+ function consolidatedQuery(group) {
171
167
  const maps = group.maps = [];
172
168
  const fields = new Map;
173
169
 
@@ -176,30 +172,29 @@ function consolidatedQuery(group, record) {
176
172
  const { query } = item.entry.request;
177
173
  const fieldMap = [];
178
174
  maps.push(fieldMap);
179
- for (const { as, expr } of query.select()) {
175
+ for (const { alias, expr } of query._select) {
180
176
  const e = `${expr}`;
181
177
  if (!fields.has(e)) {
182
178
  fields.set(e, [`col${fields.size}`, expr]);
183
179
  }
184
180
  const [name] = fields.get(e);
185
- fieldMap.push([name, as]);
181
+ fieldMap.push([name, alias]);
186
182
  }
187
- record(`${query}`);
188
183
  }
189
184
 
190
185
  // use a cloned query as a starting point
191
186
  const query = group[0].entry.request.query.clone();
192
187
 
193
188
  // update group by statement as needed
194
- const groupby = query.groupby();
189
+ const groupby = query._groupby;
195
190
  if (groupby.length) {
196
191
  const map = {};
197
192
  group.maps[0].forEach(([name, as]) => map[as] = name);
198
- query.$groupby(groupby.map(e => (e instanceof Ref && map[e.column]) || e));
193
+ query.setGroupby(groupby.map(e => (isColumnRef(e) && map[e.column]) || e));
199
194
  }
200
195
 
201
196
  // update select statement and return
202
- return query.$select(Array.from(fields.values()));
197
+ return query.setSelect(Array.from(fields.values()));
203
198
  }
204
199
 
205
200
  /**
@@ -244,22 +239,20 @@ async function processResults(group, cache) {
244
239
 
245
240
  /**
246
241
  * Project a consolidated result to a client result
247
- * @param {*} data Consolidated query result, as an Apache Arrow Table
248
- * @param {*} map Column name map as [source, target] pairs
242
+ * @param {import('@uwdata/flechette').Table} data
243
+ * Consolidated query result, as an Arrow Table
244
+ * @param {[string, string][]} map Column name map as [source, target] pairs
249
245
  * @returns the projected Apache Arrow table
250
246
  */
251
247
  function projectResult(data, map) {
252
- const cols = {};
253
- for (const [name, as] of map) {
254
- cols[as] = data.getChild(name);
255
- }
256
- return new data.constructor(cols);
248
+ return data.select(map.map(x => x[0]), map.map(x => x[1]));
257
249
  }
258
250
 
259
251
  /**
260
252
  * Filter a consolidated describe query result to a client result
261
- * @param {*} data Consolidated query result
262
- * @param {*} map Column name map as [source, target] pairs
253
+ * @param {import('@uwdata/flechette').Table} data
254
+ * Consolidated query result, as an Arrow Table
255
+ * @param {[string, string][]} map Column name map as [source, target] pairs
263
256
  * @returns the filtered table data
264
257
  */
265
258
  function filterResult(data, map) {
@@ -1,56 +1,85 @@
1
1
  import { consolidator } from './QueryConsolidator.js';
2
2
  import { lruCache, voidCache } from './util/cache.js';
3
- import { priorityQueue } from './util/priority-queue.js';
4
- import { QueryResult } from './util/query-result.js';
3
+ import { PriorityQueue } from './util/priority-queue.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
- this.queue = priorityQueue(3);
10
+ constructor(
11
+ maxConcurrentRequests = 32
12
+ ) {
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 };
@@ -109,7 +150,19 @@ export class QueryManager {
109
150
  cancel(requests) {
110
151
  const set = new Set(requests);
111
152
  if (set.size) {
112
- this.queue.remove(({ result }) => set.has(result));
153
+ this.queue.remove(({ result }) => {
154
+ if (set.has(result)) {
155
+ result.reject('Canceled');
156
+ return true;
157
+ }
158
+ return false;
159
+ });
160
+
161
+ for (const result of this.pendingResults) {
162
+ if (set.has(result)) {
163
+ result.reject('Canceled');
164
+ }
165
+ }
113
166
  }
114
167
  }
115
168
 
@@ -118,26 +171,10 @@ export class QueryManager {
118
171
  result.reject('Cleared');
119
172
  return true;
120
173
  });
121
- }
122
174
 
123
- record() {
124
- let state = [];
125
- const recorder = {
126
- add(query) {
127
- state.push(query);
128
- },
129
- reset() {
130
- state = [];
131
- },
132
- snapshot() {
133
- return state.slice();
134
- },
135
- stop() {
136
- this.recorders = this.recorders.filter(x => x !== recorder);
137
- return state;
138
- }
139
- };
140
- this.recorders.push(recorder);
141
- return recorder;
175
+ for (const result of this.pendingResults) {
176
+ result.reject('Cleared');
177
+ }
178
+ this.pendingResults = [];
142
179
  }
143
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.
@@ -10,6 +11,13 @@ export function isSelection(x) {
10
11
  return x instanceof Selection;
11
12
  }
12
13
 
14
+ function create(options, include) {
15
+ return new Selection(
16
+ new SelectionResolver(options),
17
+ include ? [include].flat() : include
18
+ );
19
+ }
20
+
13
21
  /**
14
22
  * Represents a dynamic set of query filter predicates.
15
23
  */
@@ -25,10 +33,13 @@ export class Selection extends Param {
25
33
  * @param {boolean} [options.empty=false] Boolean flag indicating if a lack
26
34
  * of clauses should correspond to an empty selection with no records. This
27
35
  * setting determines the default selection state.
36
+ * @param {Selection|Selection[]} [options.include] Upstream selections whose
37
+ * clauses should be included as part of the new selection. Any clauses
38
+ * published to upstream selections will be relayed to the new selection.
28
39
  * @returns {Selection} The new Selection instance.
29
40
  */
30
- static intersect({ cross = false, empty = false } = {}) {
31
- return new Selection(new SelectionResolver({ cross, empty }));
41
+ static intersect({ cross = false, empty = false, include = [] } = {}) {
42
+ return create({ cross, empty }, include);
32
43
  }
33
44
 
34
45
  /**
@@ -41,10 +52,13 @@ export class Selection extends Param {
41
52
  * @param {boolean} [options.empty=false] Boolean flag indicating if a lack
42
53
  * of clauses should correspond to an empty selection with no records. This
43
54
  * setting determines the default selection state.
55
+ * @param {Selection|Selection[]} [options.include] Upstream selections whose
56
+ * clauses should be included as part of the new selection. Any clauses
57
+ * published to upstream selections will be relayed to the new selection.
44
58
  * @returns {Selection} The new Selection instance.
45
59
  */
46
- static union({ cross = false, empty = false } = {}) {
47
- return new Selection(new SelectionResolver({ cross, empty, union: true }));
60
+ static union({ cross = false, empty = false, include = [] } = {}) {
61
+ return create({ cross, empty, union: true }, include);
48
62
  }
49
63
 
50
64
  /**
@@ -57,10 +71,13 @@ export class Selection extends Param {
57
71
  * @param {boolean} [options.empty=false] Boolean flag indicating if a lack
58
72
  * of clauses should correspond to an empty selection with no records. This
59
73
  * setting determines the default selection state.
74
+ * @param {Selection|Selection[]} [options.include] Upstream selections whose
75
+ * clauses should be included as part of the new selection. Any clauses
76
+ * published to upstream selections will be relayed to the new selection.
60
77
  * @returns {Selection} The new Selection instance.
61
78
  */
62
- static single({ cross = false, empty = false } = {}) {
63
- return new Selection(new SelectionResolver({ cross, empty, single: true }));
79
+ static single({ cross = false, empty = false, include = [] } = {}) {
80
+ return create({ cross, empty, single: true }, include);
64
81
  }
65
82
 
66
83
  /**
@@ -70,21 +87,34 @@ export class Selection extends Param {
70
87
  * @param {boolean} [options.empty=false] Boolean flag indicating if a lack
71
88
  * of clauses should correspond to an empty selection with no records. This
72
89
  * setting determines the default selection state.
90
+ * @param {Selection|Selection[]} [options.include] Upstream selections whose
91
+ * clauses should be included as part of the new selection. Any clauses
92
+ * published to upstream selections will be relayed to the new selection.
73
93
  * @returns {Selection} The new Selection instance.
74
94
  */
75
- static crossfilter({ empty = false } = {}) {
76
- return new Selection(new SelectionResolver({ cross: true, empty }));
95
+ static crossfilter({ empty = false, include = [] } = {}) {
96
+ return create({ cross: true, empty }, include);
77
97
  }
78
98
 
79
99
  /**
80
100
  * Create a new Selection instance.
81
- * @param {SelectionResolver} resolver The selection resolution
101
+ * @param {SelectionResolver} [resolver] The selection resolution
82
102
  * strategy to apply.
103
+ * @param {Selection[]} [include] Upstream selections whose clauses
104
+ * should be included as part of this selection. Any clauses published
105
+ * to these upstream selections will be relayed to this selection.
83
106
  */
84
- constructor(resolver = new SelectionResolver()) {
107
+ constructor(resolver = new SelectionResolver(), include = []) {
85
108
  super([]);
86
109
  this._resolved = this._value;
87
110
  this._resolver = resolver;
111
+ /** @type {Set<Selection>} */
112
+ this._relay = new Set;
113
+ if (Array.isArray(include)) {
114
+ for (const sel of include) {
115
+ sel._relay.add(this);
116
+ }
117
+ }
88
118
  }
89
119
 
90
120
  /**
@@ -161,6 +191,7 @@ export class Selection extends Param {
161
191
  */
162
192
  activate(clause) {
163
193
  this.emit('activate', clause);
194
+ this._relay.forEach(sel => sel.activate(clause));
164
195
  }
165
196
 
166
197
  /**
@@ -173,6 +204,7 @@ export class Selection extends Param {
173
204
  // this ensures consistent clause state across unemitted event values
174
205
  this._resolved = this._resolver.resolve(this._resolved, clause, true);
175
206
  this._resolved.active = clause;
207
+ this._relay.forEach(sel => sel.update(clause));
176
208
  return super.update(this._resolved);
177
209
  }
178
210
 
@@ -284,9 +316,11 @@ export class SelectionResolver {
284
316
 
285
317
  /**
286
318
  * Return a selection query predicate for the given client.
287
- * @param {*[]} clauseList An array of selection clauses.
288
- * @param {*} active The current active selection clause.
289
- * @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.
290
324
  * @returns {*} The query predicate for filtering client data,
291
325
  * based on the current state of this selection.
292
326
  */
@@ -294,7 +328,7 @@ export class SelectionResolver {
294
328
  const { empty, union } = this;
295
329
 
296
330
  if (empty && !clauseList.length) {
297
- return ['FALSE'];
331
+ return [literal(false)];
298
332
  }
299
333
 
300
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,7 +24,7 @@ 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
29
  ? isNotDistinct(field, literal(value))
34
30
  : null;
@@ -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 };
@@ -1,12 +1,14 @@
1
- import { tableFromIPC } from 'apache-arrow';
1
+ import { decodeIPC } from '../util/decode-ipc.js';
2
2
 
3
3
  export function restConnector(uri = 'http://localhost:3000/') {
4
4
  return {
5
5
  /**
6
6
  * Query the DuckDB server.
7
7
  * @param {object} query
8
- * @param {'exec' | 'arrow' | 'json'} [query.type] The query type: 'exec', 'arrow', or 'json'.
9
- * @param {string} query.sql A SQL query string.
8
+ * @param {'exec' | 'arrow' | 'json' | 'create-bundle' | 'load-bundle'} [query.type] The query type.
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) {
@@ -20,7 +22,7 @@ export function restConnector(uri = 'http://localhost:3000/') {
20
22
  });
21
23
 
22
24
  return query.type === 'exec' ? req
23
- : query.type === 'arrow' ? tableFromIPC(req)
25
+ : query.type === 'arrow' ? decodeIPC(await (await req).arrayBuffer())
24
26
  : (await req).json();
25
27
  }
26
28
  };