Skip to content

Commit 133857e

Browse files
committed
feat: Partitioned originalSql support
1 parent 6b253c0 commit 133857e

File tree

4 files changed

+98
-44
lines changed

4 files changed

+98
-44
lines changed

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

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -827,7 +827,7 @@ class BaseQuery {
827827
if (this.options.collectOriginalSqlPreAggregations) {
828828
this.options.collectOriginalSqlPreAggregations.push(foundPreAggregation);
829829
}
830-
return this.preAggregationTableName(cube, foundPreAggregation.preAggregationName);
830+
return this.preAggregations.originalSqlPreAggregationTable(foundPreAggregation);
831831
}
832832
const evaluatedSql = this.evaluateSql(cube, this.cubeEvaluator.cubeFromPath(cube).sql);
833833
const selectAsterisk = evaluatedSql.match(/^\s*select\s+\*\s+from\s+([a-zA-Z0-9_\-`".]+)\s*$/i);
@@ -1521,24 +1521,23 @@ class BaseQuery {
15211521
} else if (preAggregation.type === 'rollup') {
15221522
return this.preAggregations.rollupPreAggregationQuery(cube, preAggregation).buildSqlAndParams();
15231523
} else if (preAggregation.type === 'originalSql') {
1524-
return [
1525-
this.evaluateSymbolSqlWithContext(
1526-
() => this.evaluateSql(cube, this.cubeEvaluator.cubeFromPath(cube).sql),
1527-
{ preAggregationQuery: true, originalSqlPreAggregation: true }
1528-
),
1529-
[]
1530-
];
1524+
const originalSqlPreAggregationQuery = this.preAggregations.originalSqlPreAggregationQuery(cube, preAggregation);
1525+
return this.paramAllocator.buildSqlAndParams(originalSqlPreAggregationQuery.evaluateSymbolSqlWithContext(
1526+
() => originalSqlPreAggregationQuery.evaluateSql(cube, this.cubeEvaluator.cubeFromPath(cube).sql),
1527+
{ preAggregationQuery: true }
1528+
));
15311529
}
15321530
throw new UserError(`Unknown pre-aggregation type '${preAggregation.type}' in '${cube}'`);
15331531
}
15341532

1533+
// eslint-disable-next-line consistent-return
15351534
preAggregationQueryForSqlEvaluation(cube, preAggregation) {
15361535
if (preAggregation.type === 'autoRollup') {
15371536
return this.preAggregations.autoRollupPreAggregationQuery(cube, preAggregation);
15381537
} else if (preAggregation.type === 'rollup') {
15391538
return this.preAggregations.rollupPreAggregationQuery(cube, preAggregation);
15401539
} else if (preAggregation.type === 'originalSql') {
1541-
return this;
1540+
return this.preAggregations.originalSqlPreAggregationQuery(cube, preAggregation);
15421541
}
15431542
}
15441543

@@ -1675,7 +1674,7 @@ class BaseQuery {
16751674
const propValue = target[name];
16761675
const methods = (paramValue) => ({
16771676
filter: (column) => {
1678-
if (paramValue && !this.safeEvaluateSymbolContext().originalSqlPreAggregation) {
1677+
if (paramValue) {
16791678
const value = Array.isArray(paramValue) ?
16801679
paramValue.map(this.paramAllocator.allocateParam.bind(this.paramAllocator)) :
16811680
this.paramAllocator.allocateParam(paramValue);
@@ -1720,8 +1719,7 @@ class BaseQuery {
17201719
if (
17211720
filter &&
17221721
filter.filterParams() &&
1723-
filter.filterParams().length &&
1724-
!this.safeEvaluateSymbolContext().originalSqlPreAggregation
1722+
filter.filterParams().length
17251723
) {
17261724
if (typeof column === "function") {
17271725
return column.apply(

packages/cubejs-schema-compiler/adapter/PreAggregations.js

Lines changed: 83 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,24 @@ class PreAggregations {
2323
if (preAggregationForQuery) {
2424
if (preAggregationForQuery.preAggregation.useOriginalSqlPreAggregations) {
2525
const { preAggregations, result } =
26-
this.collectOriginalSqlPreAggregations(() =>
27-
this.preAggregationDescriptionsFor(preAggregationForQuery.cube, preAggregationForQuery)
26+
this.collectOriginalSqlPreAggregations(
27+
() => this.preAggregationDescriptionsFor(preAggregationForQuery.cube, preAggregationForQuery)
2828
);
29-
return R.unnest(preAggregations.map(p => this.preAggregationDescriptionsFor(p.cube, p))).concat(result);
29+
30+
const queryForEval = this.query.preAggregationQueryForSqlEvaluation(
31+
preAggregationForQuery.cube,
32+
preAggregationForQuery.preAggregation
33+
);
34+
35+
// TODO consider recursive pre-aggregation descriptions instead of duplication of sub query logic
36+
return R.unnest(preAggregations.map(
37+
p => this.preAggregationDescriptionsFor(p.cube, p).concat(
38+
R.unnest(
39+
queryForEval.subQueryDimensions.map(d => queryForEval.subQueryDescription(d).subQuery)
40+
.map(q => q.preAggregations.preAggregationDescriptionsFor(p.cube, p))
41+
)
42+
)
43+
)).concat(result);
3044
}
3145
return this.preAggregationDescriptionsFor(preAggregationForQuery.cube, preAggregationForQuery);
3246
}
@@ -44,20 +58,30 @@ class PreAggregations {
4458
}
4559

4660
preAggregationCubes() {
47-
const join = this.query.join;
61+
const { join } = this.query;
4862
return join.joins.map(j => j.originalTo).concat([join.root]);
4963
}
5064

5165
preAggregationDescriptionsFor(cube, foundPreAggregation) {
52-
if (foundPreAggregation.preAggregation.partitionGranularity && this.query.timeDimensions.length) {
66+
if (this.canPartitionsBeUsed(foundPreAggregation)) {
5367
const { dimension, partitionDimension } = this.partitionDimension(foundPreAggregation);
54-
return partitionDimension.timeSeries().map(range =>
55-
this.preAggregationDescriptionFor(cube, this.addPartitionRangeTo(foundPreAggregation, dimension, range))
68+
return partitionDimension.timeSeries().map(
69+
range => this.preAggregationDescriptionFor(
70+
cube, this.addPartitionRangeTo(foundPreAggregation, dimension, range)
71+
)
5672
);
5773
}
5874
return [this.preAggregationDescriptionFor(cube, foundPreAggregation)];
5975
}
6076

77+
canPartitionsBeUsed(foundPreAggregation) {
78+
return foundPreAggregation.preAggregation.partitionGranularity &&
79+
this.query.timeDimensions.length &&
80+
foundPreAggregation.references.timeDimensions &&
81+
foundPreAggregation.references.timeDimensions.length &&
82+
this.query.timeDimensions.find(td => td.dimension === foundPreAggregation.references.timeDimensions[0].dimension);
83+
}
84+
6185
addPartitionRangeTo(foundPreAggregation, dimension, range) {
6286
return Object.assign({}, foundPreAggregation, {
6387
preAggregation: Object.assign({}, foundPreAggregation.preAggregation, {
@@ -70,7 +94,7 @@ class PreAggregations {
7094
}
7195

7296
partitionDimension(foundPreAggregation) {
73-
const dimension = this.query.timeDimensions[0].dimension;
97+
const { dimension } = this.query.timeDimensions[0];
7498
const partitionDimension = this.query.newTimeDimension({
7599
dimension,
76100
granularity: this.castGranularity(foundPreAggregation.preAggregation.partitionGranularity),
@@ -124,14 +148,16 @@ class PreAggregations {
124148
const preAggregates = this.query.cubeEvaluator.preAggregationsForCube(cube);
125149
const originalSqlPreAggregations = R.pipe(
126150
R.toPairs,
151+
// eslint-disable-next-line no-unused-vars
127152
R.filter(([k, a]) => a.type === 'originalSql')
128153
)(preAggregates);
129154
if (originalSqlPreAggregations.length) {
130155
const [preAggregationName, preAggregation] = originalSqlPreAggregations[0];
131156
return {
132157
preAggregationName,
133158
preAggregation,
134-
cube
159+
cube,
160+
references: this.evaluateAllReferences(cube, preAggregation)
135161
};
136162
}
137163
return null;
@@ -198,11 +224,9 @@ class PreAggregations {
198224

199225
canUsePreAggregationAndCheckIfRefValid(query) {
200226
const transformedQuery = PreAggregations.transformQueryToCanUseForm(query);
201-
return (refs) => {
202-
return PreAggregations.canUsePreAggregationForTransformedQueryFn(
203-
transformedQuery, refs
204-
);
205-
};
227+
return (refs) => PreAggregations.canUsePreAggregationForTransformedQueryFn(
228+
transformedQuery, refs
229+
);
206230
}
207231

208232
checkAutoRollupPreAggregationValid(refs) {
@@ -275,6 +299,7 @@ class PreAggregations {
275299
);
276300
}
277301

302+
// eslint-disable-next-line no-unused-vars
278303
getCubeLattice(cube, preAggregationName, preAggregation) {
279304
throw new UserError('Auto rollups supported only in Enterprise version');
280305
}
@@ -294,6 +319,7 @@ class PreAggregations {
294319
) {
295320
return R.pipe(
296321
R.toPairs,
322+
// eslint-disable-next-line no-unused-vars
297323
R.filter(([k, a]) => a.type === 'autoRollup'),
298324
R.map(([preAggregationName, preAggregation]) => {
299325
const cubeLattice = this.getCubeLattice(cube, preAggregationName, preAggregation);
@@ -305,7 +331,8 @@ class PreAggregations {
305331
preAggregation
306332
),
307333
cube,
308-
canUsePreAggregation: true
334+
canUsePreAggregation: true,
335+
references: optimalPreAggregation
309336
};
310337
})
311338
)(preAggregations);
@@ -339,20 +366,25 @@ class PreAggregations {
339366
findRollupPreAggregationsForCube(cube, canUsePreAggregation, preAggregations) {
340367
return R.pipe(
341368
R.toPairs,
369+
// eslint-disable-next-line no-unused-vars
342370
R.filter(([k, a]) => a.type === 'rollup'),
343-
R.map(([preAggregationName, preAggregation]) => ({
344-
preAggregationName,
345-
preAggregation,
346-
cube,
347-
canUsePreAggregation: canUsePreAggregation(this.evaluateAllReferences(cube, preAggregation))
348-
}))
371+
R.map(([preAggregationName, preAggregation]) => {
372+
const references = this.evaluateAllReferences(cube, preAggregation);
373+
return {
374+
preAggregationName,
375+
preAggregation,
376+
cube,
377+
canUsePreAggregation: canUsePreAggregation(references),
378+
references
379+
};
380+
})
349381
)(preAggregations);
350382
}
351383

352384
rollupMatchResultDescriptions() {
353385
return this.rollupMatchResults().map(p => ({
354386
...this.preAggregationDescriptionFor(p.cube, p),
355-
references: this.evaluateAllReferences(p.cube, p.preAggregation),
387+
references: p.references,
356388
canUsePreAggregation: p.canUsePreAggregation
357389
}));
358390
}
@@ -382,6 +414,15 @@ class PreAggregations {
382414
return { preAggregations, result };
383415
}
384416

417+
originalSqlPreAggregationQuery(cube, aggregation) {
418+
return this.query.newSubQuery({
419+
rowLimit: null,
420+
timeDimensions: aggregation.partitionTimeDimensions,
421+
preAggregationQuery: true,
422+
collectOriginalSqlPreAggregations: this.query.safeEvaluateSymbolContext().collectOriginalSqlPreAggregations
423+
});
424+
}
425+
385426
rollupPreAggregationQuery(cube, aggregation) {
386427
const references = this.evaluateAllReferences(cube, aggregation);
387428
return this.query.newSubQuery({
@@ -421,27 +462,39 @@ class PreAggregations {
421462
}
422463

423464
autoRollupNameSuffix(cube, aggregation) {
465+
// eslint-disable-next-line prefer-template
424466
return '_' + aggregation.dimensions.concat(
425467
aggregation.timeDimensions.map(d => `${d.dimension}${d.granularity.substring(0, 1)}`)
426468
).map(s => {
427469
const path = s.split('.');
428470
return `${path[0][0]}${path[1]}`;
429-
}).map(s => s.replace(/_/g, '')).join("_").replace(/[.]/g, '').toLowerCase();
471+
}).map(s => s.replace(/_/g, '')).join("_")
472+
.replace(/[.]/g, '')
473+
.toLowerCase();
430474
}
431475

432476
evaluateAllReferences(cube, aggregation) {
433477
return this.query.cubeEvaluator.evaluatePreAggregationReferences(cube, aggregation);
434478
}
435479

480+
originalSqlPreAggregationTable(preAggregation) {
481+
return this.canPartitionsBeUsed(preAggregation) ?
482+
this.partitionUnion(preAggregation, true) :
483+
this.query.preAggregationTableName(
484+
preAggregation.cube,
485+
preAggregation.preAggregationName
486+
);
487+
}
488+
436489
rollupPreAggregation(preAggregationForQuery) {
437-
const table = preAggregationForQuery.preAggregation.partitionGranularity && this.query.timeDimensions.length ?
490+
const table = this.canPartitionsBeUsed(preAggregationForQuery) ?
438491
this.partitionUnion(preAggregationForQuery) :
439492
this.query.preAggregationTableName(
440493
preAggregationForQuery.cube,
441494
preAggregationForQuery.preAggregationName
442495
);
443-
let segmentFilters = this.query.segments.map(s =>
444-
this.query.newFilter({ dimension: s.segment, operator: 'equals', values: [true] })
496+
const segmentFilters = this.query.segments.map(
497+
s => this.query.newFilter({ dimension: s.segment, operator: 'equals', values: [true] })
445498
);
446499
const filters =
447500
segmentFilters
@@ -465,12 +518,12 @@ class PreAggregations {
465518
R.fromPairs
466519
)(preAggregationForQuery.preAggregation.type === 'autoRollup' ?
467520
preAggregationForQuery.preAggregation.measures :
468-
this.evaluateAllReferences(preAggregationForQuery.cube, preAggregationForQuery.preAggregation).measures
469-
);
521+
this.evaluateAllReferences(preAggregationForQuery.cube, preAggregationForQuery.preAggregation).measures);
470522

471523
const rollupGranularity = this.castGranularity(preAggregationForQuery.preAggregation.granularity) || 'day';
472524

473525
return this.query.evaluateSymbolSqlWithContext(
526+
// eslint-disable-next-line prefer-template
474527
() => `SELECT ${this.query.baseSelect()} FROM ${table} ${this.query.baseWhere(filters)}` +
475528
this.query.groupByClause() +
476529
this.query.baseHaving(this.query.measureFilters) +
@@ -484,7 +537,7 @@ class PreAggregations {
484537
);
485538
}
486539

487-
partitionUnion(preAggregationForQuery) {
540+
partitionUnion(preAggregationForQuery, withoutAlias) {
488541
const { dimension, partitionDimension } = this.partitionDimension(preAggregationForQuery);
489542

490543
const union = partitionDimension.timeSeries().map(range => {
@@ -495,7 +548,7 @@ class PreAggregations {
495548
preAggregation.preAggregation
496549
);
497550
}).map(table => `SELECT * FROM ${table}`).join(" UNION ALL ");
498-
return `(${union}) as partition_union`;
551+
return `(${union})${withoutAlias ? '' : ' as partition_union'}`;
499552
}
500553
}
501554

packages/cubejs-schema-compiler/compiler/CubeValidator.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,8 @@ const cubeSchema = Joi.object().keys({
179179
maxPreAggregations: Joi.number()
180180
})),
181181
Joi.object().keys(Object.assign({}, BasePreAggregation, {
182-
type: Joi.any().valid('originalSql').required()
182+
type: Joi.any().valid('originalSql').required(),
183+
timeDimensionReference: Joi.func()
183184
})),
184185
Joi.object().keys(Object.assign({}, BasePreAggregation, {
185186
type: Joi.any().valid('rollup').required(),

packages/cubejs-schema-compiler/test/PreAggregationsTest.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,9 @@ describe('PreAggregations', function test() {
9292
source: {
9393
columns: ['source', 'created_at']
9494
}
95-
}
95+
},
96+
partitionGranularity: 'month',
97+
timeDimensionReference: createdAt
9698
},
9799
googleRollup: {
98100
type: 'rollup',

0 commit comments

Comments
 (0)