Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/cubejs-schema-compiler/src/adapter/BaseQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -3883,11 +3883,11 @@ export class BaseQuery {
* @returns {BaseQuery}
*/
// eslint-disable-next-line consistent-return
preAggregationQueryForSqlEvaluation(cube, preAggregation) {
preAggregationQueryForSqlEvaluation(cube, preAggregation, context = {}) {
if (preAggregation.type === 'autoRollup') {
return this.preAggregations.autoRollupPreAggregationQuery(cube, preAggregation);
} else if (preAggregation.type === 'rollup') {
return this.preAggregations.rollupPreAggregationQuery(cube, preAggregation);
return this.preAggregations.rollupPreAggregationQuery(cube, preAggregation, context);
} else if (preAggregation.type === 'originalSql') {
return this;
}
Expand Down
44 changes: 38 additions & 6 deletions packages/cubejs-schema-compiler/src/adapter/PreAggregations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ import { BaseSegment } from './BaseSegment';

export type RollupJoin = any;

export type PreAggregationDefinitionExtended = PreAggregationDefinition & {
export type PartitionTimeDimension = {
dimension: string;
dateRange: [string, string];
boundaryDateRange: [string, string];
};

export type PreAggregationDefinitionExtended = PreAggregationDefinition & PreAggregationReferences & {
unionWithSourceData: boolean;
rollupLambdaId: string;
lastRollupLambda: boolean;
Expand All @@ -27,6 +33,7 @@ export type PreAggregationDefinitionExtended = PreAggregationDefinition & {
streamOffset: 'earliest' | 'latest';
uniqueKeyColumns: string;
sqlAlias?: string;
partitionTimeDimensions?: PartitionTimeDimension[];
};

export type PreAggregationForQuery = {
Expand All @@ -49,6 +56,10 @@ export type PreAggregationForCube = {
references: PreAggregationReferences;
};

export type EvaluateReferencesContext = {
inPreAggEvaluation?: boolean;
};

export type BaseMember = BaseDimension | BaseMeasure | BaseFilter | BaseGroupFilter | BaseSegment;

export type CanUsePreAggregationFn = (references: PreAggregationReferences) => boolean;
Expand Down Expand Up @@ -582,12 +593,15 @@ export class PreAggregations {
const dimensionsTrimmed = references
.dimensions
.map(d => CubeSymbols.joinHintFromPath(d).path);
const multipliedMeasuresTrimmed = references
.multipliedMeasures?.map(m => CubeSymbols.joinHintFromPath(m).path) || [];

return {
...references,
dimensions: dimensionsTrimmed,
measures: measuresTrimmed,
timeDimensions: timeDimensionsTrimmed,
multipliedMeasures: multipliedMeasuresTrimmed,
};
}

Expand Down Expand Up @@ -722,6 +736,19 @@ export class PreAggregations {
// TODO remove this in favor of matching with join path
const referencesTrimmed = trimmedReferences(references);

// Even if there are no multiplied measures in the query (because no multiplier dimensions are requested)
// but the same measures are multiplied in the pre-aggregation, we can't use pre-aggregation
// for such queries.
if (referencesTrimmed.multipliedMeasures) {
const backAliasMultipliedMeasures = backAlias(referencesTrimmed.multipliedMeasures);

if (transformedQuery.leafMeasures.some(m => referencesTrimmed.multipliedMeasures?.includes(m)) ||
transformedQuery.measures.some(m => backAliasMultipliedMeasures.includes(m))
) {
return false;
}
}

const dimensionsMatch = (dimensions, doBackAlias) => R.all(
d => (
doBackAlias ?
Expand Down Expand Up @@ -1193,11 +1220,11 @@ export class PreAggregations {
);
}

public rollupPreAggregationQuery(cube: string, aggregation): BaseQuery {
public rollupPreAggregationQuery(cube: string, aggregation: PreAggregationDefinitionExtended, context: EvaluateReferencesContext = {}): BaseQuery {
// `this.evaluateAllReferences` will retain not only members, but their join path as well, and pass join hints
// to subquery. Otherwise, members in subquery would regenerate new join tree from clean state,
// and it can be different from expected by join path in pre-aggregation declaration
const references = this.evaluateAllReferences(cube, aggregation);
const references = this.evaluateAllReferences(cube, aggregation, null, context);
const cubeQuery = this.query.newSubQueryForCube(cube, {});
return this.query.newSubQueryForCube(cube, {
rowLimit: null,
Expand Down Expand Up @@ -1225,7 +1252,7 @@ export class PreAggregations {
});
}

public autoRollupPreAggregationQuery(cube: string, aggregation): BaseQuery {
public autoRollupPreAggregationQuery(cube: string, aggregation: PreAggregationDefinitionExtended): BaseQuery {
return this.query.newSubQueryForCube(
cube,
{
Expand All @@ -1241,7 +1268,7 @@ export class PreAggregations {
);
}

private mergePartitionTimeDimensions(aggregation: PreAggregationReferences, partitionTimeDimensions: BaseTimeDimension[]) {
private mergePartitionTimeDimensions(aggregation: PreAggregationReferences, partitionTimeDimensions?: PartitionTimeDimension[]) {
if (!partitionTimeDimensions) {
return aggregation.timeDimensions;
}
Expand All @@ -1266,13 +1293,18 @@ export class PreAggregations {
.toLowerCase();
}

private evaluateAllReferences(cube: string, aggregation: PreAggregationDefinition, preAggregationName: string | null = null): PreAggregationReferences {
private evaluateAllReferences(cube: string, aggregation: PreAggregationDefinition, preAggregationName: string | null = null, context: EvaluateReferencesContext = {}): PreAggregationReferences {
// TODO build a join tree for all references, so they would always include full join path
// Even for preaggregation references without join path
// It is necessary to be able to match query and preaggregation based on full join tree

const evaluateReferences = () => {
const references = this.query.cubeEvaluator.evaluatePreAggregationReferences(cube, aggregation);
if (!context.inPreAggEvaluation) {
const preAggQuery = this.query.preAggregationQueryForSqlEvaluation(cube, aggregation, { inPreAggEvaluation: true });
const aggregateMeasures = preAggQuery?.fullKeyQueryAggregateMeasures({ hasMultipliedForPreAggregation: true });
references.multipliedMeasures = aggregateMeasures?.multipliedMeasures?.map(m => m.measure);
}
if (aggregation.type === 'rollupLambda') {
if (references.rollups.length > 0) {
const [firstLambdaCube] = this.query.cubeEvaluator.parsePath('preAggregations', references.rollups[0]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export type CubeRefreshKey =
export type PreAggregationDefinition = {
type: 'autoRollup' | 'originalSql' | 'rollupJoin' | 'rollupLambda' | 'rollup',
allowNonStrictDateRangeMatch?: boolean,
useOriginalSqlPreAggregations?: boolean,
timeDimensionReference?: () => ToString,
granularity: string,
timeDimensionReferences: Array<{ dimension: () => ToString, granularity: string }>,
Expand Down Expand Up @@ -121,6 +122,7 @@ export type PreAggregationReferences = {
measures: Array<string>,
timeDimensions: Array<PreAggregationTimeDimensionReference>,
rollups: Array<string>,
multipliedMeasures?: Array<string>,
};

export type PreAggregationInfo = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,6 @@ describe('MSSqlPreAggregations', () => {
timeDimensionReference: createdAt,
granularity: 'day',
},
approx: {
type: 'rollup',
measureReferences: [countDistinctApprox],
timeDimensionReference: createdAt,
granularity: 'day'
},
ratioRollup: {
type: 'rollup',
measureReferences: [checkinsTotal, uniqueSourceCount],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
cubes:
- name: line_items
sql_table: public.line_items

dimensions:
- name: id
sql: id
type: number
primary_key: true

- name: product_id
sql: product_id
type: number

- name: order_id
sql: order_id
type: number

- name: quantity
sql: quantity
type: number

- name: price
sql: price
type: number

- name: orders
sql_table: public.orders
joins:
- name: line_items
sql: "{CUBE}.id = {line_items}.order_id"
relationship: one_to_many
dimensions:
- name: id
sql: id
type: number
primary_key: true
- name: user_id
sql: user_id
type: number
- name: amount
sql: amount
type: number
- name: status
sql: status
type: string
- name: status_new
sql: status || '_new'
type: string
- name: created_at
sql: created_at
type: time
measures:
- name: count
type: count
- name: total_qty
type: sum
sql: amount

pre_aggregations:
- name: pre_agg_with_multiplied_measures
dimensions:
- orders.status
- line_items.product_id
measures:
- orders.count
- orders.total_qty
timeDimension: orders.created_at
granularity: month
Loading
Loading