@@ -387,16 +387,16 @@ export class BaseQuery {
387387 }
388388
389389 /**
390- * Is used by native
391390 * This function follows the same logic as in this.collectJoinHints()
392- * @private
391+ * skipQueryJoinMap is used by PreAggregations to build join tree without user's query all members map
392+ * @public
393393 * @param {Array<(Array<string> | string)> } hints
394+ * @param { boolean } skipQueryJoinMap
394395 * @return {import('../compiler/JoinGraph').FinishedJoinTree }
395396 */
396- joinTreeForHints ( hints ) {
397- const explicitJoinHintMembers = new Set ( hints . filter ( j => Array . isArray ( j ) ) . flat ( ) ) ;
398- const queryJoinMaps = this . queryJoinMap ( ) ;
399- const newCollectedHints = [ ] ;
397+ joinTreeForHints ( hints , skipQueryJoinMap = false ) {
398+ const queryJoinMaps = skipQueryJoinMap ? { } : this . queryJoinMap ( ) ;
399+ let newCollectedHints = [ ] ;
400400
401401 const constructJH = ( ) => R . uniq ( this . enrichHintsWithJoinMap ( [
402402 ...newCollectedHints ,
@@ -421,16 +421,20 @@ export class BaseQuery {
421421 const iterationCollectedHints = joinMembersJoinHints . filter ( j => ! allJoinHintsFlatten . has ( j ) ) ;
422422 newJoinHintsCollectedCnt = iterationCollectedHints . length ;
423423 cnt ++ ;
424- if ( newJoin ) {
425- newCollectedHints . push ( ...joinMembersJoinHints . filter ( j => ! explicitJoinHintMembers . has ( j ) ) ) ;
424+ if ( newJoin && newJoin . joins . length > 0 ) {
425+ // Even if there is no join tree changes, we still
426+ // push correctly ordered join hints, collected from the resolving of members of join tree
427+ // upfront the all existing query members. This ensures the correct cube join order
428+ // with transitive joins even if they are already presented among query members.
429+ newCollectedHints = this . enrichedJoinHintsFromJoinTree ( newJoin , joinMembersJoinHints ) ;
426430 }
427431 } while ( newJoin ?. joins . length > 0 && ! this . isJoinTreesEqual ( prevJoin , newJoin ) && cnt < 10000 && newJoinHintsCollectedCnt > 0 ) ;
428432
429433 if ( cnt >= 10000 ) {
430434 throw new UserError ( 'Can not construct joins for the query, potential loop detected' ) ;
431435 }
432436
433- return newJoin ;
437+ return this . joinGraph . buildJoin ( constructJH ( ) ) ;
434438 }
435439
436440 cacheValue ( key , fn , { contextPropNames, inputProps, cache } = { } ) {
@@ -505,6 +509,34 @@ export class BaseQuery {
505509 return joinMaps ;
506510 }
507511
512+ /**
513+ * @private
514+ * @param { import('../compiler/JoinGraph').FinishedJoinTree } joinTree
515+ * @param { string[] } joinHints
516+ * @return { string[][] }
517+ */
518+ enrichedJoinHintsFromJoinTree ( joinTree , joinHints ) {
519+ const joinsMap = { } ;
520+
521+ for ( const j of joinTree . joins ) {
522+ joinsMap [ j . to ] = j . from ;
523+ }
524+
525+ return joinHints . map ( jh => {
526+ let cubeName = jh ;
527+ const path = [ cubeName ] ;
528+ while ( joinsMap [ cubeName ] ) {
529+ cubeName = joinsMap [ cubeName ] ;
530+ path . push ( cubeName ) ;
531+ }
532+
533+ if ( path . length === 1 ) {
534+ return path [ 0 ] ;
535+ }
536+ return path . reverse ( ) ;
537+ } ) ;
538+ }
539+
508540 /**
509541 * @private
510542 * @param { (string|string[])[] } hints
@@ -2666,10 +2698,9 @@ export class BaseQuery {
26662698 */
26672699 collectJoinHints ( excludeTimeDimensions = false ) {
26682700 const allMembersJoinHints = this . collectJoinHintsFromMembers ( this . allMembersConcat ( excludeTimeDimensions ) ) ;
2669- const explicitJoinHintMembers = new Set ( allMembersJoinHints . filter ( j => Array . isArray ( j ) ) . flat ( ) ) ;
26702701 const queryJoinMaps = this . queryJoinMap ( ) ;
26712702 const customSubQueryJoinHints = this . collectJoinHintsFromMembers ( this . joinMembersFromCustomSubQuery ( ) ) ;
2672- const newCollectedHints = [ ] ;
2703+ let newCollectedHints = [ ] ;
26732704
26742705 // One cube may join the other cube via transitive joined cubes,
26752706 // members from which are referenced in the join `on` clauses.
@@ -2703,8 +2734,12 @@ export class BaseQuery {
27032734 const iterationCollectedHints = joinMembersJoinHints . filter ( j => ! allJoinHintsFlatten . has ( j ) ) ;
27042735 newJoinHintsCollectedCnt = iterationCollectedHints . length ;
27052736 cnt ++ ;
2706- if ( newJoin ) {
2707- newCollectedHints . push ( ...joinMembersJoinHints . filter ( j => ! explicitJoinHintMembers . has ( j ) ) ) ;
2737+ if ( newJoin && newJoin . joins . length > 0 ) {
2738+ // Even if there is no join tree changes, we still
2739+ // push correctly ordered join hints, collected from the resolving of members of join tree
2740+ // upfront the all existing query members. This ensures the correct cube join order
2741+ // with transitive joins even if they are already presented among query members.
2742+ newCollectedHints = this . enrichedJoinHintsFromJoinTree ( newJoin , joinMembersJoinHints ) ;
27082743 }
27092744 } while ( newJoin ?. joins . length > 0 && ! this . isJoinTreesEqual ( prevJoin , newJoin ) && cnt < 10000 && newJoinHintsCollectedCnt > 0 ) ;
27102745
@@ -2745,9 +2780,15 @@ export class BaseQuery {
27452780 }
27462781
27472782 collectJoinHintsFromMembers ( members ) {
2783+ // Extract cube names from members to make cache key member-cubes-specific
2784+ const memberCubes = members
2785+ . map ( m => m . cube ?. ( ) ?. name )
2786+ . filter ( Boolean )
2787+ . sort ( ) ;
2788+
27482789 return [
27492790 ...members . map ( m => m . joinHint ) . filter ( h => h ?. length > 0 ) ,
2750- ...this . collectFrom ( members , this . collectJoinHintsFor . bind ( this ) , 'collectJoinHintsFromMembers' ) ,
2791+ ...this . collectFrom ( members , this . collectJoinHintsFor . bind ( this ) , [ 'collectJoinHintsFromMembers' , ... memberCubes ] ) ,
27512792 ] ;
27522793 }
27532794
0 commit comments