Skip to content

Commit 4e5e122

Browse files
committed
implement pre-agg matching using pre-agg join subgraphs
1 parent e33be90 commit 4e5e122

File tree

1 file changed

+57
-37
lines changed

1 file changed

+57
-37
lines changed

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

Lines changed: 57 additions & 37 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,12 @@ export class PreAggregations {
10051022
return this.query.cacheValue(
10061023
['buildRollupJoin', JSON.stringify(preAggObj), JSON.stringify(preAggObjsToJoin)],
10071024
() => {
1008-
const targetJoins = this.resolveJoinMembers(
1009-
// TODO join hints?
1010-
this.query.joinGraph.buildJoin(this.cubesFromPreAggregation(preAggObj))
1011-
);
1025+
// If we got here - it means that the query members were matched with pre-agg taking into account
1026+
// query join graph and members full names. So no need to collect and construct the same graph again
1027+
// Moreover, for 'rollupJoin' pre-aggs this may lead to incorrect join graph construction,
1028+
// as it is being built using only flat hints from members, but not the pre-aggregation joins.
1029+
const targetJoins = this.resolveJoinMembers(this.query.join);
1030+
// const targetJoins = this.resolveJoinMembers(this.query.joinGraph.buildJoin(this.cubesFromPreAggregation(preAggObj)));
10121031
const existingJoins = R.unnest(preAggObjsToJoin.map(
10131032
// TODO join hints?
10141033
p => this.resolveJoinMembers(this.query.joinGraph.buildJoin(this.cubesFromPreAggregation(p)))
@@ -1114,11 +1133,12 @@ export class PreAggregations {
11141133
preAggregationsToJoin.forEach(preAgg => {
11151134
references.rollupsReferences.push(preAgg.references);
11161135
});
1136+
const canUsePreAggregationResult = canUsePreAggregation(references);
11171137
return {
11181138
...preAggObj,
1119-
canUsePreAggregation: canUsePreAggregation(references),
1139+
canUsePreAggregation: canUsePreAggregationResult,
11201140
preAggregationsToJoin,
1121-
rollupJoin: this.buildRollupJoin(preAggObj, preAggregationsToJoin)
1141+
rollupJoin: canUsePreAggregationResult ? this.buildRollupJoin(preAggObj, preAggregationsToJoin) : null,
11221142
};
11231143
} else if (preAggregation.type === 'rollupLambda') {
11241144
// TODO evaluation optimizations. Should be cached or moved to compile time.

0 commit comments

Comments
 (0)