Skip to content

Commit 6f1e3ae

Browse files
committed
implement pre-agg matching using pre-agg join subgraphs
1 parent 7777f8a commit 6f1e3ae

File tree

1 file changed

+71
-35
lines changed

1 file changed

+71
-35
lines changed

packages/cubejs-schema-compiler/src/adapter/PreAggregations.ts

Lines changed: 71 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,16 @@ export class PreAggregations {
639639
transformedQuery.allBackAliasMembers[r] || r
640640
));
641641

642+
const buildPath = (cube: string): string[] => {
643+
const path = [cube];
644+
const parentMap = transformedQuery.joinsMap;
645+
while (parentMap[cube]) {
646+
cube = parentMap[cube];
647+
path.push(cube);
648+
}
649+
return path.reverse();
650+
};
651+
642652
/**
643653
* Determine whether pre-aggregation can be used or not.
644654
*/
@@ -647,8 +657,32 @@ export class PreAggregations {
647657
const qryTimeDimensions = references.allowNonStrictDateRangeMatch
648658
? transformedQuery.timeDimensions
649659
: transformedQuery.sortedTimeDimensions;
650-
const backAliasMeasures = backAlias(references.measures);
651-
const backAliasDimensions = backAlias(references.dimensions);
660+
661+
let dimsToMatch: string[];
662+
let measToMatch: string[];
663+
664+
if (references.rollups.length > 0) {
665+
// In 'rollupJoin' / 'rollupLambda' pre-aggregations fullName members will be empty, because there are
666+
// no connections in the joinTree between cubes from different datasources
667+
// but joinGraph of the query has all the connections, necessary for serving the query,
668+
// so we use this information to complete the full paths of members from the root of the query
669+
// up to the pre-agg cube.
670+
// We use references from the underlying pre-aggregations, filtered with members existing in the root
671+
// pre-aggregation itself.
672+
673+
dimsToMatch = references.rollupsReferences
674+
.flatMap(rolRef => rolRef.fullNameDimensions)
675+
.filter(d => references.dimensions.some(rd => d.endsWith(rd)));
676+
measToMatch = references.rollupsReferences
677+
.flatMap(rolRef => rolRef.fullNameMeasures)
678+
.filter(m => references.measures.some(rm => m.endsWith(rm)));
679+
} else {
680+
dimsToMatch = references.fullNameDimensions;
681+
measToMatch = references.fullNameMeasures;
682+
}
683+
684+
const backAliasMeasures = backAlias(measToMatch);
685+
const backAliasDimensions = backAlias(dimsToMatch);
652686
return ((
653687
transformedQuery.hasNoTimeDimensionsWithoutGranularity
654688
) && (
@@ -741,7 +775,6 @@ export class PreAggregations {
741775

742776
let dimsToMatch: string[];
743777
let timeDimsToMatch: PreAggregationTimeDimensionReference[];
744-
let dimensionsMatch: (dimensions: string[], doBackAlias: boolean) => boolean;
745778

746779
if (references.rollups.length > 0) {
747780
// In 'rollupJoin' / 'rollupLambda' pre-aggregations fullName members will be empty, because there are
@@ -754,44 +787,28 @@ export class PreAggregations {
754787

755788
dimsToMatch = references.rollupsReferences
756789
.flatMap(rolRef => rolRef.fullNameDimensions)
757-
.filter(d => references.dimensions.some(rd => d.endsWith(rd)));
758-
timeDimsToMatch = references.rollupsReferences
759-
.flatMap(rolRef => rolRef.fullNameTimeDimensions)
760-
.filter(d => references.timeDimensions.some(rd => d.dimension.endsWith(rd.dimension)));
761-
762-
const buildPath = (cube: string): string[] => {
763-
const path = [cube];
764-
const parentMap = transformedQuery.joinsMap;
765-
while (parentMap[cube]) {
766-
cube = parentMap[cube];
767-
path.push(cube);
768-
}
769-
return path.reverse();
770-
};
771-
772-
dimensionsMatch = (dimensions, doBackAlias) => {
773-
let target = doBackAlias ? backAlias(dimsToMatch) : dimsToMatch;
774-
target = target.map(dim => {
775-
const [cube, ...restPath] = dim.split('.');
790+
.filter(d => references.dimensions.some(rd => d.endsWith(rd)))
791+
.map(d => {
792+
const [cube, ...restPath] = d.split('.');
776793
if (cube === transformedQuery.joinGraphRoot) {
777-
return dim;
794+
return d;
778795
}
779796
const path = buildPath(cube);
780797
return `${path.join('.')}.${restPath.join('.')}`;
781798
});
782-
783-
return dimensions.every(d => target.includes(d));
784-
};
799+
timeDimsToMatch = references.rollupsReferences
800+
.flatMap(rolRef => rolRef.fullNameTimeDimensions)
801+
.filter(d => references.timeDimensions.some(rd => d.dimension.endsWith(rd.dimension)));
785802
} else {
786803
dimsToMatch = references.fullNameDimensions;
787804
timeDimsToMatch = references.fullNameTimeDimensions;
788-
789-
dimensionsMatch = (dimensions, doBackAlias) => {
790-
const target = doBackAlias ? backAlias(dimsToMatch) : dimsToMatch;
791-
return dimensions.every(d => target.includes(d));
792-
};
793805
}
794806

807+
const dimensionsMatch = (dimensions, doBackAlias) => {
808+
const target = doBackAlias ? backAlias(dimsToMatch) : dimsToMatch;
809+
return dimensions.every(d => target.includes(d));
810+
};
811+
795812
const timeDimensionsMatch = (timeDimensionsList, doBackAlias) => R.allPass(
796813
timeDimensionsList.map(
797814
tds => R.anyPass(tds.map((td: [string, string]) => {
@@ -1005,10 +1022,28 @@ export class PreAggregations {
10051022
return this.query.cacheValue(
10061023
['buildRollupJoin', JSON.stringify(preAggObj), JSON.stringify(preAggObjsToJoin)],
10071024
() => {
1025+
// It's important to build join graph not only using the pre-agg members, but also
1026+
// taking into account all explicit underlying rollup pre-aggregation joins, because
1027+
// otherwise the built join tree might differ from the actual pre-aggregation.
1028+
const preAggJoinsJoinHints = preAggObj.references.rollupsReferences.map(r => {
1029+
if (!r.joinTree) {
1030+
return [];
1031+
}
1032+
1033+
const hints: (string | string[])[]= [r.joinTree.root];
1034+
1035+
for (const j of r.joinTree.joins) {
1036+
hints.push([j.from, j.to]);
1037+
}
1038+
1039+
return hints;
1040+
}).flat();
10081041
const targetJoins = this.resolveJoinMembers(
1009-
// TODO join hints?
1010-
this.query.joinGraph.buildJoin(this.cubesFromPreAggregation(preAggObj))
1042+
this.query.joinGraph.buildJoin(
1043+
preAggJoinsJoinHints.concat(this.cubesFromPreAggregation(preAggObj))
1044+
)
10111045
);
1046+
// const targetJoins = this.resolveJoinMembers(this.query.joinGraph.buildJoin(this.cubesFromPreAggregation(preAggObj)));
10121047
const existingJoins = R.unnest(preAggObjsToJoin.map(
10131048
// TODO join hints?
10141049
p => this.resolveJoinMembers(this.query.joinGraph.buildJoin(this.cubesFromPreAggregation(p)))
@@ -1114,11 +1149,12 @@ export class PreAggregations {
11141149
preAggregationsToJoin.forEach(preAgg => {
11151150
references.rollupsReferences.push(preAgg.references);
11161151
});
1152+
const canUsePreAggregationResult = canUsePreAggregation(references);
11171153
return {
11181154
...preAggObj,
1119-
canUsePreAggregation: canUsePreAggregation(references),
1155+
canUsePreAggregation: canUsePreAggregationResult,
11201156
preAggregationsToJoin,
1121-
rollupJoin: this.buildRollupJoin(preAggObj, preAggregationsToJoin)
1157+
rollupJoin: canUsePreAggregationResult ? this.buildRollupJoin(preAggObj, preAggregationsToJoin) : null,
11221158
};
11231159
} else if (preAggregation.type === 'rollupLambda') {
11241160
// TODO evaluation optimizations. Should be cached or moved to compile time.

0 commit comments

Comments
 (0)