Skip to content

Commit 9f93140

Browse files
committed
move JoinGraph to ts
1 parent b23bab8 commit 9f93140

File tree

2 files changed

+156
-49
lines changed

2 files changed

+156
-49
lines changed

packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -857,7 +857,7 @@ export class CubeSymbols {
857857
}
858858
}
859859

860-
protected funcArguments(func: Function): string[] {
860+
public funcArguments(func: Function): string[] {
861861
const funcDefinition = func.toString();
862862
if (!this.funcArgumentsValues[funcDefinition]) {
863863
const match = funcDefinition.match(FunctionRegex);

packages/cubejs-schema-compiler/src/compiler/JoinGraph.js renamed to packages/cubejs-schema-compiler/src/compiler/JoinGraph.ts

Lines changed: 155 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,52 +2,123 @@ import R from 'ramda';
22
import Graph from 'node-dijkstra';
33
import { UserError } from './UserError';
44

5+
import type { CubeValidator } from './CubeValidator';
6+
import type { CubeEvaluator, MeasureDefinition } from './CubeEvaluator';
7+
import type { CubeDefinition, JoinDefinition } from './CubeSymbols';
8+
import type { ErrorReporter } from './ErrorReporter';
9+
10+
type JoinEdge = {
11+
join: JoinDefinition,
12+
from: string,
13+
to: string,
14+
originalFrom: string,
15+
originalTo: string,
16+
};
17+
18+
type JoinTreeJoins = JoinEdge[];
19+
20+
type JoinTree = {
21+
root: string,
22+
joins: JoinTreeJoins,
23+
};
24+
25+
export type FinishedJoinTree = JoinTree & {
26+
multiplicationFactor: Record<string, boolean>,
27+
};
28+
29+
export type JoinHint = string | string[];
30+
31+
export type JoinHints = JoinHint[];
32+
533
export class JoinGraph {
6-
/**
7-
* @param {import('./CubeValidator').CubeValidator} cubeValidator
8-
* @param {import('./CubeEvaluator').CubeEvaluator} cubeEvaluator
9-
*/
10-
constructor(cubeValidator, cubeEvaluator) {
34+
private readonly cubeValidator: CubeValidator;
35+
36+
private readonly cubeEvaluator: CubeEvaluator;
37+
38+
// source node -> destination node -> weight
39+
private nodes: Record<string, Record<string, 1>>;
40+
41+
// source node -> destination node -> weight
42+
private undirectedNodes: Record<string, Record<string, 1>>;
43+
44+
private edges: Record<string, JoinEdge>;
45+
46+
private builtJoins: Record<string, FinishedJoinTree>;
47+
48+
private graph: Graph | null;
49+
50+
private cachedConnectedComponents: Record<string, number> | null;
51+
52+
public constructor(cubeValidator: CubeValidator, cubeEvaluator: CubeEvaluator) {
1153
this.cubeValidator = cubeValidator;
1254
this.cubeEvaluator = cubeEvaluator;
1355
this.nodes = {};
56+
this.undirectedNodes = {};
1457
this.edges = {};
1558
this.builtJoins = {};
59+
this.cachedConnectedComponents = null;
1660
}
1761

18-
compile(cubes, errorReporter) {
19-
this.edges = R.compose(
62+
public compile(cubes: unknown, errorReporter: ErrorReporter): void {
63+
this.edges = R.compose<
64+
Array<CubeDefinition>,
65+
Array<CubeDefinition>,
66+
Array<[string, JoinEdge][]>,
67+
Array<[string, JoinEdge]>,
68+
Record<string, JoinEdge>
69+
>(
2070
R.fromPairs,
2171
R.unnest,
22-
R.map(v => this.buildJoinEdges(v, errorReporter.inContext(`${v.name} cube`))),
72+
R.map((v: CubeDefinition): [string, JoinEdge][] => this.buildJoinEdges(v, errorReporter.inContext(`${v.name} cube`))),
2373
R.filter(this.cubeValidator.isCubeValid.bind(this.cubeValidator))
2474
)(this.cubeEvaluator.cubeList);
25-
this.nodes = R.compose(
75+
76+
// This requires @types/[email protected] or newer
77+
// @ts-ignore
78+
this.nodes = R.compose<
79+
Record<string, JoinEdge>,
80+
Array<[string, JoinEdge]>,
81+
Array<JoinEdge>,
82+
Record<string, Array<JoinEdge> | undefined>,
83+
Record<string, Record<string, 1>>
84+
>(
85+
// This requires @types/[email protected] or newer
86+
// @ts-ignore
2687
R.map(groupedByFrom => R.fromPairs(groupedByFrom.map(join => [join.to, 1]))),
27-
R.groupBy(join => join.from),
88+
R.groupBy((join: JoinEdge) => join.from),
2889
R.map(v => v[1]),
2990
R.toPairs
91+
// @ts-ignore
3092
)(this.edges);
93+
94+
// @ts-ignore
3195
this.undirectedNodes = R.compose(
96+
// @ts-ignore
3297
R.map(groupedByFrom => R.fromPairs(groupedByFrom.map(join => [join.from, 1]))),
98+
// @ts-ignore
3399
R.groupBy(join => join.to),
34100
R.unnest,
101+
// @ts-ignore
35102
R.map(v => [v[1], { from: v[1].to, to: v[1].from }]),
36103
R.toPairs
104+
// @ts-ignore
37105
)(this.edges);
106+
38107
this.graph = new Graph(this.nodes);
39108
}
40109

41-
buildJoinEdges(cube, errorReporter) {
110+
protected buildJoinEdges(cube: CubeDefinition, errorReporter: ErrorReporter): Array<[string, JoinEdge]> {
111+
// @ts-ignore
42112
return R.compose(
113+
// @ts-ignore
43114
R.filter(R.identity),
44-
R.map(join => {
45-
const multipliedMeasures = R.compose(
46-
R.filter(
47-
m => m.sql && this.cubeEvaluator.funcArguments(m.sql).length === 0 && m.sql() === 'count(*)' ||
115+
R.map((join: [string, JoinEdge]) => {
116+
const multipliedMeasures: ((m: Record<string, MeasureDefinition>) => MeasureDefinition[]) = R.compose(
117+
R.filter<MeasureDefinition>(
118+
(m: MeasureDefinition): boolean => m.sql && this.cubeEvaluator.funcArguments(m.sql).length === 0 && m.sql() === 'count(*)' ||
48119
['sum', 'avg', 'count', 'number'].indexOf(m.type) !== -1
49120
),
50-
R.values
121+
R.values as (input: Record<string, MeasureDefinition>) => MeasureDefinition[]
51122
);
52123
const joinRequired =
53124
(v) => `primary key for '${v}' is required when join is defined in order to make aggregates work properly`;
@@ -66,7 +137,7 @@ export class JoinGraph {
66137
return join;
67138
}),
68139
R.unnest,
69-
R.map(join => [
140+
R.map((join: [string, JoinDefinition]): [[string, JoinEdge]] => [
70141
[`${cube.name}-${join[0]}`, {
71142
join: join[1],
72143
from: cube.name,
@@ -75,44 +146,65 @@ export class JoinGraph {
75146
originalTo: join[0]
76147
}]
77148
]),
149+
// @ts-ignore
78150
R.filter(R.identity),
79-
R.map(join => {
151+
R.map((join: [string, JoinDefinition]) => {
80152
if (!this.cubeEvaluator.cubeExists(join[0])) {
81153
errorReporter.error(`Cube ${join[0]} doesn't exist`);
82154
return undefined;
83155
}
84156
return join;
85157
}),
158+
// @ts-ignore
86159
R.toPairs
160+
// @ts-ignore
87161
)(cube.joins || {});
88162
}
89163

90-
buildJoinNode(cube) {
91-
return R.compose(
164+
protected buildJoinNode(cube: CubeDefinition): Record<string, 1> {
165+
return R.compose<
166+
Record<string, JoinDefinition>,
167+
Array<[string, JoinDefinition]>,
168+
Array<[string, 1]>,
169+
Record<string, 1>
170+
>(
92171
R.fromPairs,
93172
R.map(v => [v[0], 1]),
94173
R.toPairs
95174
)(cube.joins || {});
96175
}
97176

98-
buildJoin(cubesToJoin) {
177+
public buildJoin(cubesToJoin: JoinHints): FinishedJoinTree | null {
99178
if (!cubesToJoin.length) {
100179
return null;
101180
}
102181
const key = JSON.stringify(cubesToJoin);
103182
if (!this.builtJoins[key]) {
104-
const join = R.pipe(
183+
const join = R.pipe<
184+
JoinHints,
185+
Array<JoinTree | null>,
186+
Array<JoinTree>,
187+
Array<JoinTree>
188+
>(
105189
R.map(
106-
cube => this.buildJoinTreeForRoot(cube, R.without([cube], cubesToJoin))
190+
(cube: JoinHint): JoinTree | null => this.buildJoinTreeForRoot(cube, R.without([cube], cubesToJoin))
107191
),
192+
// @ts-ignore
108193
R.filter(R.identity),
109-
R.sortBy(joinTree => joinTree.joins.length)
194+
R.sortBy((joinTree: JoinTree) => joinTree.joins.length)
195+
// @ts-ignore
110196
)(cubesToJoin)[0];
197+
111198
if (!join) {
112199
throw new UserError(`Can't find join path to join ${cubesToJoin.map(v => `'${v}'`).join(', ')}`);
113200
}
201+
114202
this.builtJoins[key] = Object.assign(join, {
115-
multiplicationFactor: R.compose(
203+
multiplicationFactor: R.compose<
204+
JoinHints,
205+
Array<[string, boolean]>,
206+
Record<string, boolean>
207+
>(
116208
R.fromPairs,
117209
R.map(v => [this.cubeFromPath(v), this.findMultiplicationFactorFor(this.cubeFromPath(v), join.joins)])
118210
)(cubesToJoin)
@@ -121,19 +213,19 @@ export class JoinGraph {
121213
return this.builtJoins[key];
122214
}
123215

124-
cubeFromPath(cubePath) {
216+
protected cubeFromPath(cubePath) {
125217
if (Array.isArray(cubePath)) {
126218
return cubePath[cubePath.length - 1];
127219
}
128220
return cubePath;
129221
}
130222

131-
buildJoinTreeForRoot(root, cubesToJoin) {
223+
protected buildJoinTreeForRoot(root: JoinHint, cubesToJoin: JoinHints): JoinTree | null {
132224
const self = this;
133225
if (Array.isArray(root)) {
134226
const [newRoot, ...additionalToJoin] = root;
135227
if (additionalToJoin.length > 0) {
136-
cubesToJoin = [additionalToJoin].concat(cubesToJoin);
228+
cubesToJoin = [additionalToJoin, ...cubesToJoin];
137229
}
138230
root = newRoot;
139231
}
@@ -157,39 +249,54 @@ export class JoinGraph {
157249
nodesJoined[toJoin] = true;
158250
return { cubes: path, joins: foundJoins };
159251
});
160-
}).reduce((a, b) => a.concat(b), []).reduce((joined, res) => {
161-
if (!res || !joined) {
162-
return null;
163-
}
164-
const indexedPairs = R.compose(
165-
R.addIndex(R.map)((j, i) => [i + joined.joins.length, j])
166-
);
167-
return {
168-
joins: joined.joins.concat(indexedPairs(res.joins))
169-
};
170-
}, { joins: [] });
252+
}).reduce((a, b) => a.concat(b), [])
253+
// @ts-ignore
254+
.reduce((joined, res) => {
255+
if (!res || !joined) {
256+
return null;
257+
}
258+
const indexedPairs = R.compose<
259+
Array<JoinEdge>,
260+
Array<[number, JoinEdge]>
261+
>(
262+
R.addIndex(R.map)((j, i) => [i + joined.joins.length, j])
263+
);
264+
return {
265+
joins: [...joined.joins, ...indexedPairs(res.joins)],
266+
};
267+
}, { joins: [] });
171268

172269
if (!result) {
173270
return null;
174271
}
175272

176-
const pairsSortedByIndex =
177-
R.compose(R.uniq, R.map(indexToJoin => indexToJoin[1]), R.sortBy(indexToJoin => indexToJoin[0]));
273+
const pairsSortedByIndex: (joins: [number, JoinEdge][]) => JoinEdge[] =
274+
R.compose<
275+
Array<[number, JoinEdge]>,
276+
Array<[number, JoinEdge]>,
277+
Array<JoinEdge>,
278+
Array<JoinEdge>
279+
>(
280+
R.uniq,
281+
R.map(([_, join]: [number, JoinEdge]) => join),
282+
R.sortBy(([index]: [number, JoinEdge]) => index)
283+
);
178284
return {
285+
// @ts-ignore
179286
joins: pairsSortedByIndex(result.joins),
180287
root
181288
};
182289
}
183290

184-
findMultiplicationFactorFor(cube, joins) {
291+
protected findMultiplicationFactorFor(cube: string, joins: JoinTreeJoins): boolean {
185292
const visited = {};
186293
const self = this;
187-
function findIfMultipliedRecursive(currentCube) {
294+
function findIfMultipliedRecursive(currentCube: string) {
188295
if (visited[currentCube]) {
189296
return false;
190297
}
191298
visited[currentCube] = true;
192-
function nextNode(nextJoin) {
299+
function nextNode(nextJoin: JoinEdge): string {
193300
return nextJoin.from === currentCube ? nextJoin.to : nextJoin.from;
194301
}
195302
const nextJoins = joins.filter(j => j.from === currentCube || j.to === currentCube);
@@ -205,16 +312,16 @@ export class JoinGraph {
205312
return findIfMultipliedRecursive(cube);
206313
}
207314

208-
checkIfCubeMultiplied(cube, join) {
315+
protected checkIfCubeMultiplied(cube: string, join: JoinEdge): boolean {
209316
return join.from === cube && join.join.relationship === 'hasMany' ||
210317
join.to === cube && join.join.relationship === 'belongsTo';
211318
}
212319

213-
joinsByPath(path) {
320+
protected joinsByPath(path: string[]): JoinEdge[] {
214321
return R.range(0, path.length - 1).map(i => this.edges[`${path[i]}-${path[i + 1]}`]);
215322
}
216323

217-
connectedComponents() {
324+
public connectedComponents(): Record<string, number> {
218325
if (!this.cachedConnectedComponents) {
219326
let componentId = 1;
220327
const components = {};
@@ -227,7 +334,7 @@ export class JoinGraph {
227334
return this.cachedConnectedComponents;
228335
}
229336

230-
findConnectedComponent(componentId, node, components) {
337+
protected findConnectedComponent(componentId: number, node: string, components: Record<string, number>): void {
231338
if (!components[node]) {
232339
components[node] = componentId;
233340
R.toPairs(this.undirectedNodes[node])

0 commit comments

Comments
 (0)