@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.
- package/README.md +3 -1
- package/dist/mosaic-core.js +11613 -10856
- package/dist/mosaic-core.min.js +7 -7
- package/dist/types/Coordinator.d.ts +169 -0
- package/dist/types/MosaicClient.d.ts +94 -0
- package/dist/types/Param.d.ts +47 -0
- package/dist/types/QueryConsolidator.d.ts +9 -0
- package/dist/types/QueryManager.d.ts +64 -0
- package/dist/types/Selection.d.ts +224 -0
- package/dist/types/SelectionClause.d.ts +105 -0
- package/dist/types/connectors/rest.d.ts +17 -0
- package/dist/types/connectors/socket.d.ts +18 -0
- package/dist/types/connectors/wasm.d.ts +16 -0
- package/dist/types/index.d.ts +25 -0
- package/dist/types/preagg/PreAggregator.d.ts +178 -0
- package/dist/types/preagg/preagg-columns.d.ts +14 -0
- package/dist/types/preagg/sufficient-statistics.d.ts +13 -0
- package/dist/types/util/AsyncDispatch.d.ts +100 -0
- package/dist/types/util/cache.d.ts +13 -0
- package/dist/types/util/decode-ipc.d.ts +7 -0
- package/dist/types/util/distinct.d.ts +2 -0
- package/dist/types/util/field-info.d.ts +13 -0
- package/dist/types/util/hash.d.ts +1 -0
- package/dist/types/util/is-arrow-table.d.ts +8 -0
- package/dist/types/util/js-type.d.ts +1 -0
- package/dist/types/util/priority-queue.d.ts +37 -0
- package/dist/types/util/query-result.d.ts +44 -0
- package/dist/types/util/selection-types.d.ts +114 -0
- package/dist/types/util/synchronizer.d.ts +29 -0
- package/dist/types/util/throttle.d.ts +11 -0
- package/dist/types/util/to-data-columns.d.ts +29 -0
- package/dist/types/util/void-logger.d.ts +7 -0
- package/jsconfig.json +11 -0
- package/package.json +10 -8
- package/src/Coordinator.js +14 -14
- package/src/MosaicClient.js +5 -4
- package/src/QueryConsolidator.js +22 -33
- package/src/QueryManager.js +76 -45
- package/src/Selection.js +8 -5
- package/src/SelectionClause.js +20 -23
- package/src/connectors/rest.js +3 -1
- package/src/connectors/socket.js +3 -1
- package/src/connectors/wasm.js +1 -1
- package/src/index.js +13 -0
- package/src/preagg/PreAggregator.js +407 -0
- package/src/preagg/preagg-columns.js +103 -0
- package/src/preagg/sufficient-statistics.js +439 -0
- package/src/util/field-info.js +16 -5
- package/src/util/hash.js +1 -1
- package/src/util/query-result.js +44 -2
- package/src/util/selection-types.ts +3 -3
- package/src/util/throttle.js +11 -9
- package/src/util/void-logger.js +6 -5
- package/tsconfig.json +11 -0
- package/src/DataCubeIndexer.js +0 -378
- package/src/util/index-columns.js +0 -537
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { ExprNode } from '@uwdata/mosaic-sql';
|
|
2
|
+
import { MosaicClient } from '../MosaicClient.js';
|
|
3
|
+
/**
|
|
4
|
+
* Selection clause metadata to guide possible query optimizations.
|
|
5
|
+
* Sub-interfaces provide more information about the specifics of a
|
|
6
|
+
* given selection based on the selection type.
|
|
7
|
+
*/
|
|
8
|
+
export interface ClauseMetadata {
|
|
9
|
+
/**
|
|
10
|
+
* The selection type, such as `'point'`, `'interval'`, or `'match'`.
|
|
11
|
+
*/
|
|
12
|
+
type: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Selection clause metadata indicating selection of one or more discrete
|
|
16
|
+
* point values, typically based on equality or is distinctiveness checks.
|
|
17
|
+
*/
|
|
18
|
+
export interface PointMetadata extends ClauseMetadata {
|
|
19
|
+
type: 'point';
|
|
20
|
+
}
|
|
21
|
+
/** Text search matching methods. */
|
|
22
|
+
export type MatchMethod = 'contains' | 'prefix' | 'suffix' | 'regexp' | (string & {});
|
|
23
|
+
/**
|
|
24
|
+
* Selection clause metadata indicating text search matching.
|
|
25
|
+
*/
|
|
26
|
+
export interface MatchMetadata extends ClauseMetadata {
|
|
27
|
+
type: MatchMethod;
|
|
28
|
+
/** The text search matching method used. */
|
|
29
|
+
method?: 'contains' | 'prefix' | 'suffix' | 'regexp' | (string & {});
|
|
30
|
+
}
|
|
31
|
+
/** Quantitative scale types. */
|
|
32
|
+
export type ScaleType = 'identity' | 'linear' | 'log' | 'sqrt' | 'pow' | 'symlog' | 'time' | 'utc';
|
|
33
|
+
/** A data value interval extent. */
|
|
34
|
+
export type Extent = [number, number] | [Date, Date];
|
|
35
|
+
/**
|
|
36
|
+
* Descriptor for a scale that maps a data domain to screen pixels.
|
|
37
|
+
*/
|
|
38
|
+
export interface Scale {
|
|
39
|
+
/** The scale type, such as `'linear'`, `'log'`, etc. */
|
|
40
|
+
type: ScaleType;
|
|
41
|
+
/** The scale domain, as an array of start and end data values. */
|
|
42
|
+
domain: Extent;
|
|
43
|
+
/**
|
|
44
|
+
* The scale range, as an array of start and end screen pixels.
|
|
45
|
+
* The range may be omitted for *identity* scales.
|
|
46
|
+
*/
|
|
47
|
+
range?: [number, number];
|
|
48
|
+
/** The base of the logarithm. For `'log'` scales only. */
|
|
49
|
+
base?: number;
|
|
50
|
+
/** The constant parameter. For `'symlog'` scales only. */
|
|
51
|
+
constant?: number;
|
|
52
|
+
/** The exponent parameter. For `'pow'` scales only. */
|
|
53
|
+
exponent?: number;
|
|
54
|
+
}
|
|
55
|
+
/** A binning method name. */
|
|
56
|
+
export type BinMethod = 'floor' | 'ceil' | 'round';
|
|
57
|
+
/**
|
|
58
|
+
* Selection clause metadata for one or more selected intervals. This
|
|
59
|
+
* metadata can be used to determine appropriate data-space binning
|
|
60
|
+
* schemes that correspond to pixel-level bins in screen space.
|
|
61
|
+
*/
|
|
62
|
+
export interface IntervalMetadata extends ClauseMetadata {
|
|
63
|
+
type: 'interval';
|
|
64
|
+
/**
|
|
65
|
+
* The interactive pixel size used by the generating component.
|
|
66
|
+
* Values larger than one indicate intervals that "snap-to" values
|
|
67
|
+
* greater than a single pixel. If unspecified, assumed to be `1`.
|
|
68
|
+
*/
|
|
69
|
+
pixelSize?: number;
|
|
70
|
+
/**
|
|
71
|
+
* An array of one or more scale descriptors that describe the
|
|
72
|
+
* mapping from data values to screen pixels.
|
|
73
|
+
*/
|
|
74
|
+
scales?: Scale[];
|
|
75
|
+
/**
|
|
76
|
+
* A hint for the binning method to use when discretizing the
|
|
77
|
+
* interval domain. If unspecified, the default is `'floor'`.
|
|
78
|
+
*/
|
|
79
|
+
bin?: BinMethod;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* A selection clause representing filtering criteria
|
|
83
|
+
* to apply within a Mosiac Selection.
|
|
84
|
+
*/
|
|
85
|
+
export interface SelectionClause {
|
|
86
|
+
/**
|
|
87
|
+
* A unique identifier (according to object equality) for the source
|
|
88
|
+
* component that generated this clause. In many cases, this is a
|
|
89
|
+
* reference to the originating component itself.
|
|
90
|
+
*/
|
|
91
|
+
source: any;
|
|
92
|
+
/**
|
|
93
|
+
* A set of Mosaic clients associated with this clause that should not
|
|
94
|
+
* be updated when this clause is applied in a cross-filtering context.
|
|
95
|
+
*/
|
|
96
|
+
clients?: Set<MosaicClient>;
|
|
97
|
+
/**
|
|
98
|
+
* A selected value associated with this clause. For example, for a 1D
|
|
99
|
+
* interval selection clause the value may be a [lo, hi] array.
|
|
100
|
+
*/
|
|
101
|
+
value: any;
|
|
102
|
+
/**
|
|
103
|
+
* A predicate SQL expression suitable for use in a query WHERE clause.
|
|
104
|
+
* The predicate should apply filtering criteria consistent with this
|
|
105
|
+
* clause's *value* property.
|
|
106
|
+
*/
|
|
107
|
+
predicate: ExprNode | null;
|
|
108
|
+
/**
|
|
109
|
+
* Optional clause metadata that varies based on the selection type.
|
|
110
|
+
* The metadata can be used to optimize selection queries, for example
|
|
111
|
+
* by creating materialized views of pre-aggregated data when applicable.
|
|
112
|
+
*/
|
|
113
|
+
meta?: ClauseMetadata;
|
|
114
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a new synchronizer instance to aid synchronization
|
|
3
|
+
* of updates on multiple pending operations.
|
|
4
|
+
*/
|
|
5
|
+
export function synchronizer(): {
|
|
6
|
+
/**
|
|
7
|
+
* Mark an item as pending.
|
|
8
|
+
* @param {*} item An item to synchronize on.
|
|
9
|
+
*/
|
|
10
|
+
pending(item: any): void;
|
|
11
|
+
/**
|
|
12
|
+
* Mark a pending item as ready, indicating it is
|
|
13
|
+
* ready for a synchronized update.
|
|
14
|
+
* @param {*} item An item to synchronize on.
|
|
15
|
+
* @returns {boolean} True if the synchronizer is ready to
|
|
16
|
+
* resolve, false otherwise.
|
|
17
|
+
*/
|
|
18
|
+
ready(item: any): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Resolve the current synchronization cycle, causing the synchronize
|
|
21
|
+
* promise to resolve and thereby trigger downstream updates.
|
|
22
|
+
*/
|
|
23
|
+
resolve(): void;
|
|
24
|
+
/**
|
|
25
|
+
* The promise for the current synchronization cycle.
|
|
26
|
+
* @return {Promise} The synchronization promise.
|
|
27
|
+
*/
|
|
28
|
+
readonly promise: Promise<any>;
|
|
29
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Throttle invocations of a callback function. The callback must return
|
|
3
|
+
* a Promise. Upon repeated invocation, the callback will not be invoked
|
|
4
|
+
* until a prior Promise resolves. If multiple invocations occurs while
|
|
5
|
+
* waiting, only the most recent invocation will be pending.
|
|
6
|
+
* @param {(event: *) => Promise} callback The callback function.
|
|
7
|
+
* @param {boolean} [debounce=true] Flag indicating if invocations
|
|
8
|
+
* should also be debounced within the current animation frame.
|
|
9
|
+
* @returns A new function that throttles access to the callback.
|
|
10
|
+
*/
|
|
11
|
+
export function throttle(callback: (event: any) => Promise<any>, debounce?: boolean): (event: any) => void;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Array | Int8Array | Uint8Array | Uint8ClampedArray
|
|
3
|
+
* | Int16Array | Uint16Array | Int32Array | Uint32Array
|
|
4
|
+
* | Float32Array | Float64Array
|
|
5
|
+
* } Arrayish - an Array or TypedArray
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {
|
|
9
|
+
* | { numRows: number, columns: Record<string,Arrayish> }
|
|
10
|
+
* | { numRows: number, values: Arrayish; }
|
|
11
|
+
* } DataColumns
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Convert input data to a set of column arrays.
|
|
15
|
+
* @param {any} data The input data.
|
|
16
|
+
* @returns {DataColumns} An object with named column arrays.
|
|
17
|
+
*/
|
|
18
|
+
export function toDataColumns(data: any): DataColumns;
|
|
19
|
+
/**
|
|
20
|
+
* - an Array or TypedArray
|
|
21
|
+
*/
|
|
22
|
+
export type Arrayish = any[] | Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array;
|
|
23
|
+
export type DataColumns = any | {
|
|
24
|
+
numRows: number;
|
|
25
|
+
columns: Record<string, Arrayish>;
|
|
26
|
+
} | {
|
|
27
|
+
numRows: number;
|
|
28
|
+
values: Arrayish;
|
|
29
|
+
};
|
package/jsconfig.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uwdata/mosaic-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "Scalable and extensible linked data views.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mosaic",
|
|
@@ -16,24 +16,26 @@
|
|
|
16
16
|
"module": "src/index.js",
|
|
17
17
|
"jsdelivr": "dist/mosaic-core.min.js",
|
|
18
18
|
"unpkg": "dist/mosaic-core.min.js",
|
|
19
|
+
"types": "dist/types/index.d.ts",
|
|
19
20
|
"repository": {
|
|
20
21
|
"type": "git",
|
|
21
22
|
"url": "https://github.com/uwdata/mosaic.git"
|
|
22
23
|
},
|
|
23
24
|
"scripts": {
|
|
24
25
|
"prebuild": "rimraf dist && mkdir dist",
|
|
25
|
-
"build": "node ../../esbuild.js mosaic-core",
|
|
26
|
+
"build": "npm run types && node ../../esbuild.js mosaic-core",
|
|
27
|
+
"types": "tsc",
|
|
26
28
|
"lint": "eslint src test",
|
|
27
|
-
"test": "vitest run
|
|
29
|
+
"test": "vitest run && tsc -p jsconfig.json",
|
|
28
30
|
"prepublishOnly": "npm run test && npm run lint && npm run build"
|
|
29
31
|
},
|
|
30
32
|
"dependencies": {
|
|
31
|
-
"@duckdb/duckdb-wasm": "^1.
|
|
32
|
-
"@uwdata/flechette": "^1.
|
|
33
|
-
"@uwdata/mosaic-sql": "^0.
|
|
33
|
+
"@duckdb/duckdb-wasm": "^1.29.0",
|
|
34
|
+
"@uwdata/flechette": "^1.1.1",
|
|
35
|
+
"@uwdata/mosaic-sql": "^0.12.1"
|
|
34
36
|
},
|
|
35
37
|
"devDependencies": {
|
|
36
|
-
"@uwdata/mosaic-duckdb": "^0.
|
|
38
|
+
"@uwdata/mosaic-duckdb": "^0.12.1"
|
|
37
39
|
},
|
|
38
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "fe3a7c34352da54ec36a1ebf557846f46a649782"
|
|
39
41
|
}
|
package/src/Coordinator.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { socketConnector } from './connectors/socket.js';
|
|
2
|
-
import {
|
|
3
|
-
import { MosaicClient } from './MosaicClient.js';
|
|
4
|
-
import { QueryManager, Priority } from './QueryManager.js';
|
|
2
|
+
import { PreAggregator } from './preagg/PreAggregator.js';
|
|
5
3
|
import { queryFieldInfo } from './util/field-info.js';
|
|
6
4
|
import { QueryResult } from './util/query-result.js';
|
|
7
5
|
import { voidLogger } from './util/void-logger.js';
|
|
6
|
+
import { MosaicClient } from './MosaicClient.js';
|
|
7
|
+
import { QueryManager, Priority } from './QueryManager.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* The singleton Coordinator instance.
|
|
@@ -33,15 +33,15 @@ export function coordinator(instance) {
|
|
|
33
33
|
/**
|
|
34
34
|
* A Mosaic Coordinator manages all database communication for clients and
|
|
35
35
|
* handles selection updates. The Coordinator also performs optimizations
|
|
36
|
-
* including query caching, consolidation, and
|
|
36
|
+
* including query caching, consolidation, and pre-aggregation.
|
|
37
37
|
* @param {*} [db] Database connector. Defaults to a web socket connection.
|
|
38
38
|
* @param {object} [options] Coordinator options.
|
|
39
39
|
* @param {*} [options.logger=console] The logger to use, defaults to `console`.
|
|
40
40
|
* @param {*} [options.manager] The query manager to use.
|
|
41
41
|
* @param {boolean} [options.cache=true] Boolean flag to enable/disable query caching.
|
|
42
42
|
* @param {boolean} [options.consolidate=true] Boolean flag to enable/disable query consolidation.
|
|
43
|
-
* @param {import('./
|
|
44
|
-
*
|
|
43
|
+
* @param {import('./preagg/PreAggregator.js').PreAggregateOptions} [options.preagg]
|
|
44
|
+
* Options for the Pre-aggregator.
|
|
45
45
|
*/
|
|
46
46
|
export class Coordinator {
|
|
47
47
|
constructor(db = socketConnector(), {
|
|
@@ -49,7 +49,7 @@ export class Coordinator {
|
|
|
49
49
|
manager = new QueryManager(),
|
|
50
50
|
cache = true,
|
|
51
51
|
consolidate = true,
|
|
52
|
-
|
|
52
|
+
preagg = {}
|
|
53
53
|
} = {}) {
|
|
54
54
|
/** @type {QueryManager} */
|
|
55
55
|
this.manager = manager;
|
|
@@ -58,7 +58,7 @@ export class Coordinator {
|
|
|
58
58
|
this.databaseConnector(db);
|
|
59
59
|
this.logger(logger);
|
|
60
60
|
this.clear();
|
|
61
|
-
this.
|
|
61
|
+
this.preaggregator = new PreAggregator(this, preagg);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
/**
|
|
@@ -208,12 +208,12 @@ export class Coordinator {
|
|
|
208
208
|
/**
|
|
209
209
|
* Issue a query request for a client. If the query is null or undefined,
|
|
210
210
|
* the client is simply updated. Otherwise `updateClient` is called. As a
|
|
211
|
-
* side effect, this method clears the current
|
|
211
|
+
* side effect, this method clears the current preaggregator state.
|
|
212
212
|
* @param {MosaicClient} client The client to update.
|
|
213
213
|
* @param {QueryType | null} [query] The query to issue.
|
|
214
214
|
*/
|
|
215
215
|
requestQuery(client, query) {
|
|
216
|
-
this.
|
|
216
|
+
this.preaggregator.clear();
|
|
217
217
|
return query
|
|
218
218
|
? this.updateClient(client, query)
|
|
219
219
|
: Promise.resolve(client.update());
|
|
@@ -307,10 +307,10 @@ function connectSelection(mc, selection, client) {
|
|
|
307
307
|
* selection clause representative of the activation.
|
|
308
308
|
*/
|
|
309
309
|
function activateSelection(mc, selection, clause) {
|
|
310
|
-
const {
|
|
310
|
+
const { preaggregator, filterGroups } = mc;
|
|
311
311
|
const { clients } = filterGroups.get(selection);
|
|
312
312
|
for (const client of clients) {
|
|
313
|
-
|
|
313
|
+
preaggregator.request(client, selection, clause);
|
|
314
314
|
}
|
|
315
315
|
}
|
|
316
316
|
|
|
@@ -322,11 +322,11 @@ function activateSelection(mc, selection, clause) {
|
|
|
322
322
|
* @returns {Promise} A Promise that resolves when the update completes.
|
|
323
323
|
*/
|
|
324
324
|
function updateSelection(mc, selection) {
|
|
325
|
-
const {
|
|
325
|
+
const { preaggregator, filterGroups } = mc;
|
|
326
326
|
const { clients } = filterGroups.get(selection);
|
|
327
327
|
const { active } = selection;
|
|
328
328
|
return Promise.allSettled(Array.from(clients, client => {
|
|
329
|
-
const info =
|
|
329
|
+
const info = preaggregator.request(client, selection, active);
|
|
330
330
|
const filter = info ? null : selection.predicate(client);
|
|
331
331
|
|
|
332
332
|
// skip due to cross-filtering
|
package/src/MosaicClient.js
CHANGED
|
@@ -38,11 +38,12 @@ export class MosaicClient {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
|
-
* Return a boolean indicating if the client query can be
|
|
42
|
-
*
|
|
43
|
-
* groupby domain of the client
|
|
41
|
+
* Return a boolean indicating if the client query can be sped up with
|
|
42
|
+
* materialized views of pre-aggregated data. Should return true if changes to
|
|
43
|
+
* the filterBy selection does not change the groupby domain of the client
|
|
44
|
+
* query.
|
|
44
45
|
*/
|
|
45
|
-
get
|
|
46
|
+
get filterStable() {
|
|
46
47
|
return true;
|
|
47
48
|
}
|
|
48
49
|
|
package/src/QueryConsolidator.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
83
|
+
if (isSelectQuery(query) && !cache.get(sql)) {
|
|
85
84
|
if (
|
|
86
|
-
|
|
87
|
-
query.
|
|
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,25 +91,21 @@ function consolidationKey(query, cache) {
|
|
|
94
91
|
}
|
|
95
92
|
|
|
96
93
|
// create a derived query stripped of selections
|
|
97
|
-
const q = query.clone()
|
|
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.
|
|
103
|
-
// @ts-ignore
|
|
99
|
+
const groupby = query._groupby;
|
|
104
100
|
if (groupby.length) {
|
|
105
|
-
const map = {}; // expression map (
|
|
106
|
-
|
|
107
|
-
|
|
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));
|
|
110
104
|
}
|
|
111
|
-
|
|
112
|
-
else if (query.select().some(({ expr }) => expr.aggregate)) {
|
|
105
|
+
else if (query._select.some(e => isAggregateExpression(e.expr))) {
|
|
113
106
|
// if query is an ungrouped aggregate, add an explicit groupby to
|
|
114
107
|
// prevent improper consolidation with non-aggregate queries
|
|
115
|
-
q
|
|
108
|
+
q.setGroupby('ALL');
|
|
116
109
|
}
|
|
117
110
|
|
|
118
111
|
// key is just the transformed query as SQL
|
|
@@ -127,17 +120,15 @@ function consolidationKey(query, cache) {
|
|
|
127
120
|
* Issue queries, consolidating where possible.
|
|
128
121
|
* @param {*} group Array of bundled query entries
|
|
129
122
|
* @param {*} enqueue Add entry to query queue
|
|
130
|
-
* @param {*} record Query recorder function
|
|
131
123
|
*/
|
|
132
|
-
function consolidate(group, enqueue
|
|
124
|
+
function consolidate(group, enqueue) {
|
|
133
125
|
if (shouldConsolidate(group)) {
|
|
134
126
|
// issue a single consolidated query
|
|
135
127
|
enqueue({
|
|
136
128
|
request: {
|
|
137
129
|
type: 'arrow',
|
|
138
130
|
cache: false,
|
|
139
|
-
|
|
140
|
-
query: (group.query = consolidatedQuery(group, record))
|
|
131
|
+
query: (group.query = consolidatedQuery(group))
|
|
141
132
|
},
|
|
142
133
|
result: (group.result = new QueryResult())
|
|
143
134
|
});
|
|
@@ -170,10 +161,9 @@ function shouldConsolidate(group) {
|
|
|
170
161
|
/**
|
|
171
162
|
* Create a consolidated query for a group.
|
|
172
163
|
* @param {*} group Array of bundled query entries
|
|
173
|
-
* @param {*} record Query recorder function
|
|
174
164
|
* @returns A consolidated Query instance
|
|
175
165
|
*/
|
|
176
|
-
function consolidatedQuery(group
|
|
166
|
+
function consolidatedQuery(group) {
|
|
177
167
|
const maps = group.maps = [];
|
|
178
168
|
const fields = new Map;
|
|
179
169
|
|
|
@@ -182,30 +172,29 @@ function consolidatedQuery(group, record) {
|
|
|
182
172
|
const { query } = item.entry.request;
|
|
183
173
|
const fieldMap = [];
|
|
184
174
|
maps.push(fieldMap);
|
|
185
|
-
for (const {
|
|
175
|
+
for (const { alias, expr } of query._select) {
|
|
186
176
|
const e = `${expr}`;
|
|
187
177
|
if (!fields.has(e)) {
|
|
188
178
|
fields.set(e, [`col${fields.size}`, expr]);
|
|
189
179
|
}
|
|
190
180
|
const [name] = fields.get(e);
|
|
191
|
-
fieldMap.push([name,
|
|
181
|
+
fieldMap.push([name, alias]);
|
|
192
182
|
}
|
|
193
|
-
record(`${query}`);
|
|
194
183
|
}
|
|
195
184
|
|
|
196
185
|
// use a cloned query as a starting point
|
|
197
186
|
const query = group[0].entry.request.query.clone();
|
|
198
187
|
|
|
199
188
|
// update group by statement as needed
|
|
200
|
-
const groupby = query.
|
|
189
|
+
const groupby = query._groupby;
|
|
201
190
|
if (groupby.length) {
|
|
202
191
|
const map = {};
|
|
203
192
|
group.maps[0].forEach(([name, as]) => map[as] = name);
|
|
204
|
-
query
|
|
193
|
+
query.setGroupby(groupby.map(e => (isColumnRef(e) && map[e.column]) || e));
|
|
205
194
|
}
|
|
206
195
|
|
|
207
196
|
// update select statement and return
|
|
208
|
-
return query
|
|
197
|
+
return query.setSelect(Array.from(fields.values()));
|
|
209
198
|
}
|
|
210
199
|
|
|
211
200
|
/**
|