@@ -2,52 +2,123 @@ import R from 'ramda';
22import Graph from 'node-dijkstra' ;
33import { 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+
533export 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