Skip to content

Commit be35010

Browse files
committed
implement join maps
1 parent 481afc4 commit be35010

File tree

2 files changed

+70
-10
lines changed

2 files changed

+70
-10
lines changed

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

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -436,12 +436,14 @@ export class BaseQuery {
436436
get allJoinHints() {
437437
if (!this.collectedJoinHints) {
438438
const allMembersJoinHints = this.collectJoinHintsFromMembers(this.allMembersConcat(false));
439+
const queryJoinMaps = this.queryJoinMap();
439440
const customSubQueryJoinHints = this.collectJoinHintsFromMembers(this.joinMembersFromCustomSubQuery());
440-
const allJoinHints = [
441+
const allJoinHints = this.enrichHintsWithJoinMap([
441442
...this.queryLevelJoinHints,
442443
...allMembersJoinHints,
443444
...customSubQueryJoinHints,
444-
];
445+
],
446+
queryJoinMaps);
445447

446448
const tempJoin = this.joinGraph.buildJoin(allJoinHints);
447449

@@ -454,17 +456,63 @@ export class BaseQuery {
454456
const allJoinHintsFlatten = new Set(allJoinHints.flat());
455457
const newCollectedHints = joinMembersJoinHints.filter(j => !allJoinHintsFlatten.has(j));
456458

457-
this.collectedJoinHints = [
459+
this.collectedJoinHints = this.enrichHintsWithJoinMap([
458460
...this.queryLevelJoinHints,
459461
tempJoin.root,
460462
...newCollectedHints,
461463
...allMembersJoinHints,
462464
...customSubQueryJoinHints,
463-
];
465+
],
466+
queryJoinMaps);
464467
}
465468
return this.collectedJoinHints;
466469
}
467470

471+
/**
472+
* @private
473+
* @return { Record<string, string[][]>}
474+
*/
475+
queryJoinMap() {
476+
const queryMembers = this.allMembersConcat(false);
477+
const joinMaps = {};
478+
479+
for (const member of queryMembers) {
480+
const memberCube = member.cube?.();
481+
if (memberCube?.isView && !joinMaps[memberCube.name]) {
482+
joinMaps[memberCube.name] = memberCube.joinMap;
483+
}
484+
}
485+
486+
return joinMaps;
487+
}
488+
489+
/**
490+
* @private
491+
* @param { (string|string[])[] } hints
492+
* @param { Record<string, string[][]>} joinMap
493+
* @return {(string|string[])[]}
494+
*/
495+
enrichHintsWithJoinMap(hints, joinMap) {
496+
// Potentially, if joins between views would take place, we need to distinguish
497+
// join maps on per view basis.
498+
const allPaths = Object.values(joinMap).flat();
499+
500+
return hints.map(hint => {
501+
if (Array.isArray(hint)) {
502+
return hint;
503+
}
504+
505+
for (const path of allPaths) {
506+
const hintIndex = path.indexOf(hint);
507+
if (hintIndex !== -1) {
508+
return path.slice(0, hintIndex + 1);
509+
}
510+
}
511+
512+
return hint;
513+
});
514+
}
515+
468516
get dataSource() {
469517
const dataSources = R.uniq(this.allCubeNames.map(c => this.cubeDataSource(c)));
470518
if (dataSources.length > 1 && !this.externalPreAggregationQuery()) {

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

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,12 @@ export type AccessPolicyDefinition = {
133133
};
134134
};
135135

136+
export type ViewIncludedMember = {
137+
type: string;
138+
memberPath: string;
139+
name: string;
140+
};
141+
136142
export interface CubeDefinition {
137143
name: string;
138144
extends?: (...args: Array<unknown>) => { __cubeName: string };
@@ -159,7 +165,8 @@ export interface CubeDefinition {
159165
isView?: boolean;
160166
calendar?: boolean;
161167
isSplitView?: boolean;
162-
includedMembers?: any[];
168+
includedMembers?: ViewIncludedMember[];
169+
joinMap?: string[][];
163170
fileName?: string;
164171
}
165172

@@ -562,6 +569,8 @@ export class CubeSymbols implements TranspilerSymbolResolver {
562569
// `hierarchies` must be processed first
563570
const types = ['hierarchies', 'measures', 'dimensions', 'segments'];
564571

572+
const joinMap: string[][] = [];
573+
565574
for (const type of types) {
566575
let cubeIncludes: any[] = [];
567576

@@ -573,6 +582,11 @@ export class CubeSymbols implements TranspilerSymbolResolver {
573582
const split = fullPath.split('.');
574583
const cubeRef = split[split.length - 1];
575584

585+
// No need to keep a simple direct cube joins in join map
586+
if (split.length > 1) {
587+
joinMap.push(split);
588+
}
589+
576590
if (it.includes === '*') {
577591
return it;
578592
}
@@ -614,11 +628,7 @@ export class CubeSymbols implements TranspilerSymbolResolver {
614628
existing.map(({ type: t, memberPath, name }) => `${t}|${memberPath}|${name}`)
615629
);
616630

617-
const additions: {
618-
type: string;
619-
memberPath: string;
620-
name: string;
621-
}[] = [];
631+
const additions: ViewIncludedMember[] = [];
622632

623633
for (const { member, name } of cubeIncludes) {
624634
const parts = member.split('.');
@@ -636,6 +646,8 @@ export class CubeSymbols implements TranspilerSymbolResolver {
636646
}
637647
}
638648

649+
cube.joinMap = joinMap;
650+
639651
[...memberSets.allMembers].filter(it => !memberSets.resolvedMembers.has(it)).forEach(it => {
640652
errorReporter.error(`Member '${it}' is included in '${cube.name}' but not defined in any cube`);
641653
});

0 commit comments

Comments
 (0)