Skip to content

Commit fb668d4

Browse files
committed
fix(schema-compiler): Fix rendered references for preaggregations with join paths
1 parent d214ced commit fb668d4

File tree

2 files changed

+152
-52
lines changed

2 files changed

+152
-52
lines changed

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

Lines changed: 45 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1370,21 +1370,24 @@ export class PreAggregations {
13701370
* @returns {Record<string, string>}
13711371
*/
13721372
measuresRenderedReference(preAggregationForQuery) {
1373-
return R.pipe(
1374-
R.map(path => {
1373+
const measures = this.rollupMeasures(preAggregationForQuery);
1374+
1375+
return Object.fromEntries(measures
1376+
.flatMap(path => {
13751377
const measure = this.query.newMeasure(path);
1376-
return [
1378+
const memberPath = this.query.cubeEvaluator.pathFromArray(measure.path());
1379+
const column = this.query.ungrouped ? measure.aliasName() : (this.query.aggregateOnGroupedColumn(
1380+
measure.measureDefinition(),
1381+
measure.aliasName(),
1382+
!this.query.safeEvaluateSymbolContext().overTimeSeriesAggregate,
13771383
path,
1378-
this.query.ungrouped ? measure.aliasName() : (this.query.aggregateOnGroupedColumn(
1379-
measure.measureDefinition(),
1380-
measure.aliasName(),
1381-
!this.query.safeEvaluateSymbolContext().overTimeSeriesAggregate,
1382-
path,
1383-
) || `sum(${measure.aliasName()})`),
1384+
) || `sum(${measure.aliasName()})`);
1385+
// Return both full join path and measure path
1386+
return [
1387+
[path, column],
1388+
[memberPath, column],
13841389
];
1385-
}),
1386-
R.fromPairs,
1387-
)(this.rollupMeasures(preAggregationForQuery));
1390+
}));
13881391
}
13891392

13901393
/**
@@ -1393,16 +1396,19 @@ export class PreAggregations {
13931396
* @returns {Record<string, string>}
13941397
*/
13951398
measureAliasesRenderedReference(preAggregationForQuery) {
1396-
return R.pipe(
1397-
R.map(path => {
1399+
const measures = this.rollupMeasures(preAggregationForQuery);
1400+
1401+
return Object.fromEntries(measures
1402+
.flatMap(path => {
13981403
const measure = this.query.newMeasure(path);
1404+
const memberPath = this.query.cubeEvaluator.pathFromArray(measure.path());
1405+
const alias = measure.aliasName();
1406+
// Return both full join path and measure path
13991407
return [
1400-
path,
1401-
measure.aliasName(),
1408+
[path, alias],
1409+
[memberPath, alias],
14021410
];
1403-
}),
1404-
R.fromPairs,
1405-
)(this.rollupMeasures(preAggregationForQuery));
1411+
}));
14061412
}
14071413

14081414
/**
@@ -1411,16 +1417,19 @@ export class PreAggregations {
14111417
* @returns {Record<string, string>}
14121418
*/
14131419
dimensionsRenderedReference(preAggregationForQuery) {
1414-
return R.pipe(
1415-
R.map(path => {
1420+
const dimensions = this.rollupDimensions(preAggregationForQuery);
1421+
1422+
return Object.fromEntries(dimensions
1423+
.flatMap(path => {
14161424
const dimension = this.query.newDimension(path);
1425+
const column = this.query.escapeColumnName(dimension.unescapedAliasName());
1426+
const memberPath = this.query.cubeEvaluator.pathFromArray(dimension.path());
1427+
// Return both full join path and dimension path
14171428
return [
1418-
path,
1419-
this.query.escapeColumnName(dimension.unescapedAliasName()),
1429+
[path, column],
1430+
[memberPath, column],
14201431
];
1421-
}),
1422-
R.fromPairs,
1423-
)(this.rollupDimensions(preAggregationForQuery));
1432+
}));
14241433
}
14251434

14261435
/**
@@ -1430,16 +1439,19 @@ export class PreAggregations {
14301439
* @returns {Record<string, string>}
14311440
*/
14321441
timeDimensionsRenderedReference(rollupGranularity, preAggregationForQuery) {
1433-
return R.pipe(
1434-
R.map((td) => {
1442+
const timeDimensions = this.rollupTimeDimensions(preAggregationForQuery);
1443+
1444+
return Object.fromEntries(timeDimensions
1445+
.flatMap(td => {
14351446
const timeDimension = this.query.newTimeDimension(td);
1447+
const column = this.query.escapeColumnName(timeDimension.unescapedAliasName(rollupGranularity));
1448+
const memberPath = this.query.cubeEvaluator.pathFromArray(timeDimension.path());
1449+
// Return both full join path and dimension path
14361450
return [
1437-
td.dimension,
1438-
this.query.escapeColumnName(timeDimension.unescapedAliasName(rollupGranularity)),
1451+
[td.dimension, column],
1452+
[memberPath, column],
14391453
];
1440-
}),
1441-
R.fromPairs,
1442-
)(this.rollupTimeDimensions(preAggregationForQuery));
1454+
}));
14431455
}
14441456

14451457
/**

packages/cubejs-schema-compiler/test/integration/postgres/multiple-join-paths.test.ts

Lines changed: 107 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { prepareJsCompiler } from '../../unit/PrepareCompiler';
33
import { DataSchemaCompiler } from '../../../src/compiler/DataSchemaCompiler';
44
import { JoinGraph } from '../../../src/compiler/JoinGraph';
55
import { CubeEvaluator } from '../../../src/compiler/CubeEvaluator';
6+
import { testWithPreAggregation } from './pre-aggregation-utils';
67

78
describe('Multiple join paths', () => {
89
jest.setTimeout(200000);
@@ -20,7 +21,8 @@ describe('Multiple join paths', () => {
2021
// └-->F-------┘
2122
// View, pre-aggregations and all interesting parts should use ADEX path
2223
// It should NOT be the shortest one from A to X (that's AFX), nor first in join edges declaration (that's ABCX)
23-
// All join conditions would be essentially `FALSE`, but with different syntax, to be able to test SQL generation
24+
// All join conditions would be essentially `TRUE` for ADEX joins and `FALSE` for everything else
25+
// But they would use different syntax, to be able to test SQL generation
2426
// Also, there should be only one way to cover cubes A and D with joins: A->D join
2527

2628
// TODO in this model queries like [A.a_id, X.x_id] become ambiguous, probably we want to handle this better
@@ -37,7 +39,7 @@ describe('Multiple join paths', () => {
3739
},
3840
D: {
3941
relationship: 'many_to_one',
40-
sql: "'A' = 'D'",
42+
sql: "'A' = 'D' OR TRUE",
4143
},
4244
F: {
4345
relationship: 'many_to_one',
@@ -78,7 +80,6 @@ describe('Multiple join paths', () => {
7880
a_id,
7981
A.D.d_id,
8082
A.D.d_name_for_join_paths,
81-
// D.d_id,
8283
A.D.E.X.x_id,
8384
],
8485
measures: [
@@ -93,6 +94,19 @@ describe('Multiple join paths', () => {
9394
granularity: 'day',
9495
},
9596
97+
adex_cumulative_with_join_paths: {
98+
type: 'rollup',
99+
dimensions: [
100+
a_id,
101+
A.D.E.X.x_id,
102+
],
103+
measures: [
104+
A.D.E.X.x_cumulative_sum,
105+
],
106+
timeDimension: A.D.E.X.x_time,
107+
granularity: 'day',
108+
},
109+
96110
ad_without_join_paths: {
97111
type: 'rollup',
98112
dimensions: [
@@ -183,7 +197,7 @@ describe('Multiple join paths', () => {
183197
joins: {
184198
E: {
185199
relationship: 'many_to_one',
186-
sql: "'D' = 'E'",
200+
sql: "'D' = 'E' OR TRUE",
187201
},
188202
},
189203
@@ -228,7 +242,7 @@ describe('Multiple join paths', () => {
228242
joins: {
229243
X: {
230244
relationship: 'many_to_one',
231-
sql: "'E' = 'X'",
245+
sql: "'E' = 'X' OR TRUE",
232246
},
233247
},
234248
@@ -319,6 +333,13 @@ describe('Multiple join paths', () => {
319333
sql: 'x_value',
320334
type: 'sum',
321335
},
336+
x_cumulative_sum: {
337+
sql: 'x_value',
338+
type: 'sum',
339+
rolling_window: {
340+
trailing: 'unbounded',
341+
},
342+
},
322343
},
323344
324345
segments: {
@@ -515,33 +536,100 @@ describe('Multiple join paths', () => {
515536
expect(preAggregation).toBeDefined();
516537
});
517538

518-
const preAggregationIds = [
519-
'A.adex_with_join_paths',
520-
'A.ad_without_join_paths',
539+
function makeReferenceQueryFor(preAggregationId: string, withDateRange: boolean = false): PostgresQuery {
540+
const preAggregations = cubeEvaluator.preAggregations({});
541+
542+
const preAggregation = preAggregations
543+
.find(p => p.id === preAggregationId);
544+
if (preAggregation === undefined) {
545+
throw expect(preAggregation).toBeDefined();
546+
}
547+
548+
if (withDateRange) {
549+
preAggregation.references.timeDimensions = preAggregation.references.timeDimensions.map(td => ({
550+
...td,
551+
dateRange: ['1970-01-01', '1970-01-02'],
552+
}));
553+
}
554+
555+
return new PostgresQuery({ joinGraph, cubeEvaluator, compiler }, {
556+
...preAggregation.references,
557+
preAggregationId: preAggregation.id,
558+
preAggregationsSchema: '',
559+
timezone: 'UTC',
560+
});
561+
}
562+
563+
const preAggregationTests = [
564+
{
565+
preAggregationId: 'A.adex_with_join_paths',
566+
addTimeRange: false,
567+
expectedData: [
568+
{
569+
a__a_id: 1,
570+
a__a_seg: false,
571+
a__a_sum: '100',
572+
d__d_id: 1,
573+
d__d_name_for_join_paths: 'foo',
574+
d__d_seg: false,
575+
x__x_id: 1,
576+
x__x_seg: false,
577+
x__x_time_day: '1970-01-01T00:00:00.000Z',
578+
},
579+
],
580+
},
581+
{
582+
preAggregationId: 'A.adex_cumulative_with_join_paths',
583+
addTimeRange: true,
584+
expectedData: [],
585+
},
586+
{
587+
preAggregationId: 'A.ad_without_join_paths',
588+
addTimeRange: false,
589+
expectedData: [
590+
{
591+
a__a_id: 1,
592+
a__a_seg: false,
593+
a__a_sum: '100',
594+
d__d_id: 1,
595+
d__d_name_for_no_join_paths: 'foo',
596+
d__d_seg: false,
597+
d__d_time_day: '1970-01-01T00:00:00.000Z',
598+
},
599+
],
600+
},
521601
];
522-
for (const preAggregationId of preAggregationIds) {
602+
for (const { preAggregationId, addTimeRange, expectedData } of preAggregationTests) {
523603
// eslint-disable-next-line no-loop-func
524604
it(`pre-aggregation ${preAggregationId} should match its own references`, async () => {
525-
const preAggregations = cubeEvaluator.preAggregations({});
605+
// Always not using range, because reference query would have no range to start from
606+
// but should match pre-aggregation anyway
607+
const query = makeReferenceQueryFor(preAggregationId);
526608

527-
const preAggregation = preAggregations
528-
.find(p => p.id === preAggregationId);
529-
if (preAggregation === undefined) {
530-
throw expect(preAggregation).toBeDefined();
609+
const preAggregationsDescription: any = query.preAggregations?.preAggregationsDescription();
610+
const preAggregationFromQuery = preAggregationsDescription.find(p => p.preAggregationId === preAggregationId);
611+
if (preAggregationFromQuery === undefined) {
612+
throw expect(preAggregationFromQuery).toBeDefined();
531613
}
532614

533-
const query = new PostgresQuery({ joinGraph, cubeEvaluator, compiler }, {
534-
...preAggregation.references,
535-
preAggregationId: preAggregation.id,
536-
});
615+
expect(preAggregationFromQuery.preAggregationId).toBe(preAggregationId);
616+
});
617+
618+
// eslint-disable-next-line no-loop-func
619+
it(`pre-aggregation ${preAggregationId} reference query should be executable`, async () => {
620+
// Adding date range for rolling window measure
621+
const query = makeReferenceQueryFor(preAggregationId, addTimeRange);
537622

538623
const preAggregationsDescription: any = query.preAggregations?.preAggregationsDescription();
539-
const preAggregationFromQuery = preAggregationsDescription.find(p => p.preAggregationId === preAggregation.id);
624+
const preAggregationFromQuery = preAggregationsDescription.find(p => p.preAggregationId === preAggregationId);
540625
if (preAggregationFromQuery === undefined) {
541626
throw expect(preAggregationFromQuery).toBeDefined();
542627
}
543628

544629
expect(preAggregationFromQuery.preAggregationId).toBe(preAggregationId);
630+
631+
const res = await testWithPreAggregation(preAggregationFromQuery, query);
632+
expect(res).toEqual(expectedData);
545633
});
546634
}
547635
});

0 commit comments

Comments
 (0)