@@ -4,7 +4,7 @@ import { camelize } from 'inflection';
44
55import { UserError } from './UserError' ;
66import { DynamicReference } from './DynamicReference' ;
7- import { camelizeCube } from './utils' ;
7+ import { camelizeCube , topologicalSort } from './utils' ;
88import { BaseQuery } from '../adapter' ;
99
1010import type { ErrorReporter } from './ErrorReporter' ;
@@ -34,7 +34,7 @@ interface SplitViews {
3434const FunctionRegex = / f u n c t i o n \s + \w + \( ( [ A - Z a - z 0 - 9 _ , ] * ) | \( ( [ \s \S ] * ?) \) \s * = > | \( ? ( \w + ) \) ? \s * = > / ;
3535export const CONTEXT_SYMBOLS = {
3636 SECURITY_CONTEXT : 'securityContext' ,
37- // SECURITY_CONTEXT has been deprecated, however security_context (lowecase )
37+ // SECURITY_CONTEXT has been deprecated, however security_context (lowercase )
3838 // is allowed in RBAC policies for query-time attribute matching
3939 security_context : 'securityContext' ,
4040 securityContext : 'securityContext' ,
@@ -45,6 +45,22 @@ export const CONTEXT_SYMBOLS = {
4545
4646export const CURRENT_CUBE_CONSTANTS = [ 'CUBE' , 'TABLE' ] ;
4747
48+ export type CubeDef = any ;
49+
50+ export type GraphNode = {
51+ cubeDef : CubeDef ;
52+ name : string ;
53+ } ;
54+
55+ export type ReferenceNode = {
56+ name : string ;
57+ } ;
58+
59+ /**
60+ * Treat it as GraphNode depends on ReferenceNode
61+ */
62+ export type GraphEdge = [ GraphNode , ReferenceNode ?] ;
63+
4864export class CubeSymbols {
4965 public symbols : Record < string | symbol , any > ;
5066
@@ -60,7 +76,9 @@ export class CubeSymbols {
6076
6177 private resolveSymbolsCallContext : any ;
6278
63- private readonly duplicateCheckerFn : ( cube : any , memberType : string , memberName : string ) => boolean ;
79+ private readonly viewDuplicateCheckerFn : ( cube : any , memberType : string , memberName : string ) => boolean ;
80+
81+ private readonly cubeDuplicateNamesCheckerFn : ( cube : any ) => string [ ] ;
6482
6583 public constructor ( evaluateViews = false ) {
6684 this . symbols = { } ;
@@ -70,26 +88,22 @@ export class CubeSymbols {
7088 this . cubeList = [ ] ;
7189 this . evaluateViews = evaluateViews ;
7290
73- if ( getEnv ( 'caseInsensitiveDuplicateCheck' ) ) {
74- this . duplicateCheckerFn = this . duplicateCheckerCaseInsensitive ;
91+ if ( getEnv ( 'caseInsensitiveDuplicatesCheck' ) ) {
92+ this . cubeDuplicateNamesCheckerFn = this . cubeDuplicateNamesCheckerCaseInsensitive ;
93+ this . viewDuplicateCheckerFn = this . viewDuplicateCheckerCaseInsensitive ;
7594 } else {
76- this . duplicateCheckerFn = this . duplicateCheckerCaseSensitive ;
95+ this . cubeDuplicateNamesCheckerFn = this . cubeDuplicateNamesCheckerCaseSensitive ;
96+ this . viewDuplicateCheckerFn = this . viewDuplicateCheckerCaseSensitive ;
7797 }
7898 }
7999
80100 public compile ( cubes : CubeDefinition [ ] , errorReporter : ErrorReporter ) {
81- // @ts -ignore
82- this . cubeDefinitions = R . pipe (
83- // @ts -ignore
84- R . map ( ( c : CubeDefinition ) => [ c . name , c ] ) ,
85- R . fromPairs
86- // @ts -ignore
87- ) ( cubes ) ;
88- this . cubeList = cubes . map ( c => ( c . name ? this . getCubeDefinition ( c . name ) : this . createCube ( c ) ) ) ;
89- // TODO support actual dependency sorting to allow using views inside views
90- const sortedByDependency = R . pipe (
91- R . sortBy ( ( c : CubeDefinition ) => ! ! c . isView ) ,
92- ) ( cubes ) ;
101+ this . cubeDefinitions = Object . fromEntries ( cubes . map ( ( c ) => [ c . name , c ] ) ) ;
102+ this . cubeList = cubes . map ( c => this . getCubeDefinition ( c . name ) ) ;
103+
104+ // Sorting matters only for views evaluation
105+ const sortedByDependency = this . evaluateViews ? topologicalSort ( this . prepareDepsGraph ( cubes ) ) : cubes ;
106+
93107 for ( const cube of sortedByDependency ) {
94108 const splitViews : SplitViews = { } ;
95109 this . symbols [ cube . name ] = this . transform ( cube . name , errorReporter . inContext ( `${ cube . name } cube` ) , splitViews ) ;
@@ -101,6 +115,35 @@ export class CubeSymbols {
101115 }
102116 }
103117
118+ private prepareDepsGraph ( cubes : CubeDefinition [ ] ) : GraphEdge [ ] {
119+ const graph = new Map < string , GraphEdge > ( ) ;
120+
121+ for ( const cube of cubes ) {
122+ if ( ! cube . isView ) { // Cubes are independent
123+ graph . set ( `${ cube . name } -none` , [ { cubeDef : cube , name : cube . name } ] ) ;
124+ } else {
125+ cube . cubes ?. forEach ( c => {
126+ const jp = c . joinPath || c . join_path ;
127+ if ( jp ) {
128+ // It's enough to ref the very first level, as everything else will be evaluated on its own
129+ const cubeJoinPath = this . funcArguments ( jp ) [ 0 ] ; // View is not camelized yet
130+ graph . set ( `${ cube . name } -${ cubeJoinPath } ` , [ { cubeDef : cube , name : cube . name } , { name : cubeJoinPath } ] ) ;
131+ }
132+ } ) ;
133+
134+ // Legacy-style includes
135+ if ( typeof cube . includes === 'function' ) {
136+ const refs = this . funcArguments ( cube . includes ) ;
137+ refs . forEach ( ref => {
138+ graph . set ( `${ cube . name } -${ ref } ` , [ { cubeDef : cube , name : cube . name } , { name : ref } ] ) ;
139+ } ) ;
140+ }
141+ }
142+ }
143+
144+ return Array . from ( graph . values ( ) ) ;
145+ }
146+
104147 public getCubeDefinition ( cubeName : string ) {
105148 if ( ! this . builtCubes [ cubeName ] ) {
106149 const cubeDefinition = this . cubeDefinitions [ cubeName ] ;
@@ -201,31 +244,50 @@ export class CubeSymbols {
201244 this . prepareIncludes ( cube , errorReporter , splitViews ) ;
202245 }
203246
204- const duplicateNames = R . compose (
205- R . map ( ( nameToDefinitions : any ) => nameToDefinitions [ 0 ] ) ,
247+ const duplicateNames = this . cubeDuplicateNamesCheckerFn ( cube ) ;
248+
249+ if ( duplicateNames . length > 0 ) {
250+ errorReporter . error ( `${ duplicateNames . join ( ', ' ) } defined more than once` ) ;
251+ }
252+
253+ return {
254+ cubeName : ( ) => cube . name ,
255+ cubeObj : ( ) => cube ,
256+ ...cube . measures || { } ,
257+ ...cube . dimensions || { } ,
258+ ...cube . segments || { } ,
259+ ...cube . preAggregations || { }
260+ } ;
261+ }
262+
263+ private cubeDuplicateNamesCheckerCaseSensitive ( cube : any ) : string [ ] {
264+ // @ts -ignore
265+ return R . compose (
266+ R . map ( ( [ name ] ) => name ) ,
206267 R . toPairs ,
207268 R . filter ( ( definitionsByName : any ) => definitionsByName . length > 1 ) ,
208- R . groupBy ( ( nameToDefinition : any ) => nameToDefinition [ 0 ] ) ,
269+ R . groupBy ( ( [ name ] : any ) => name ) ,
209270 R . unnest ,
210271 R . map ( R . toPairs ) ,
211272 // @ts -ignore
212273 R . filter ( ( v : any ) => ! ! v )
213274 // @ts -ignore
214275 ) ( [ cube . measures , cube . dimensions , cube . segments , cube . preAggregations , cube . hierarchies ] ) ;
276+ }
215277
278+ private cubeDuplicateNamesCheckerCaseInsensitive ( cube : any ) : string [ ] {
216279 // @ts -ignore
217- if ( duplicateNames . length > 0 ) {
280+ return R . compose (
281+ R . map ( ( [ name ] ) => name ) ,
282+ R . toPairs ,
283+ R . filter ( ( definitionsByName : any ) => definitionsByName . length > 1 ) ,
284+ R . groupBy ( ( [ name ] : any ) => name . toLowerCase ( ) ) ,
285+ R . unnest ,
286+ R . map ( R . toPairs ) ,
218287 // @ts -ignore
219- errorReporter . error ( `${ duplicateNames . join ( ', ' ) } defined more than once` ) ;
220- }
221-
222- return Object . assign (
223- { cubeName : ( ) => cube . name , cubeObj : ( ) => cube } ,
224- cube . measures || { } ,
225- cube . dimensions || { } ,
226- cube . segments || { } ,
227- cube . preAggregations || { }
228- ) ;
288+ R . filter ( ( v : any ) => ! ! v )
289+ // @ts -ignore
290+ ) ( [ cube . measures , cube . dimensions , cube . segments , cube . preAggregations , cube . hierarchies ] ) ;
229291 }
230292
231293 private camelCaseTypes ( obj : Object ) {
@@ -369,19 +431,19 @@ export class CubeSymbols {
369431
370432 protected applyIncludeMembers ( includeMembers : any [ ] , cube : CubeDefinition , type : string , errorReporter : ErrorReporter ) {
371433 for ( const [ memberName , memberDefinition ] of includeMembers ) {
372- if ( this . duplicateCheckerFn ( cube , type , memberName ) ) {
434+ if ( this . viewDuplicateCheckerFn ( cube , type , memberName ) ) {
373435 errorReporter . error ( `Included member '${ memberName } ' conflicts with existing member of '${ cube . name } '. Please consider excluding this member or assigning it an alias.` ) ;
374436 } else if ( type !== 'hierarchies' ) {
375437 cube [ type ] [ memberName ] = memberDefinition ;
376438 }
377439 }
378440 }
379441
380- private duplicateCheckerCaseSensitive ( cube : any , memberType : string , memberName : string ) : boolean {
442+ private viewDuplicateCheckerCaseSensitive ( cube : any , memberType : string , memberName : string ) : boolean {
381443 return cube [ memberType ] [ memberName ] ;
382444 }
383445
384- private duplicateCheckerCaseInsensitive ( cube : any , memberType : string , memberName : string ) : boolean {
446+ private viewDuplicateCheckerCaseInsensitive ( cube : any , memberType : string , memberName : string ) : boolean {
385447 return Object . keys ( cube [ memberType ] ) . map ( v => v . toLowerCase ( ) ) . includes ( memberName . toLowerCase ( ) ) ;
386448 }
387449
0 commit comments