Skip to content

Commit b36ba74

Browse files
authored
Merge branch 'master' into drill-down-custom-granularities
2 parents 77528ec + 635b45b commit b36ba74

File tree

9 files changed

+1053
-114
lines changed

9 files changed

+1053
-114
lines changed

packages/cubejs-schema-compiler/src/adapter/BaseQuery.js

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)