Skip to content

Commit 655b70c

Browse files
committed
fix(schema-compiler): Fix rendered references for preaggregations with join paths
1 parent 7666e1e commit 655b70c

File tree

2 files changed

+161
-69
lines changed

2 files changed

+161
-69
lines changed

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

Lines changed: 45 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1443,76 +1443,72 @@ export class PreAggregations {
14431443
}
14441444

14451445
private measuresRenderedReference(preAggregationForQuery: PreAggregationForQuery): Record<string, string> {
1446-
return R.pipe<
1447-
string[],
1448-
Array<[string, string]>,
1449-
Record<string, string>
1450-
>(
1451-
R.map((path: string): [string, string] => {
1446+
const measures = this.rollupMeasures(preAggregationForQuery);
1447+
1448+
return Object.fromEntries(measures
1449+
.flatMap(path => {
14521450
const measure = this.query.newMeasure(path);
1453-
return [
1451+
const memberPath = this.query.cubeEvaluator.pathFromArray(measure.path());
1452+
const column = this.query.ungrouped ? measure.aliasName() : (this.query.aggregateOnGroupedColumn(
1453+
measure.measureDefinition(),
1454+
measure.aliasName(),
1455+
!this.query.safeEvaluateSymbolContext().overTimeSeriesAggregate,
14541456
path,
1455-
this.query.ungrouped ? measure.aliasName() : (this.query.aggregateOnGroupedColumn(
1456-
measure.measureDefinition(),
1457-
measure.aliasName(),
1458-
!this.query.safeEvaluateSymbolContext().overTimeSeriesAggregate,
1459-
path,
1460-
) || `sum(${measure.aliasName()})`),
1457+
) || `sum(${measure.aliasName()})`);
1458+
// Return both full join path and measure path
1459+
return [
1460+
[path, column],
1461+
[memberPath, column],
14611462
];
1462-
}),
1463-
R.fromPairs,
1464-
)(this.rollupMeasures(preAggregationForQuery));
1463+
}));
14651464
}
14661465

14671466
private measureAliasesRenderedReference(preAggregationForQuery: PreAggregationForQuery): Record<string, string> {
1468-
return R.pipe<
1469-
string[],
1470-
Array<[string, string]>,
1471-
Record<string, string>
1472-
>(
1473-
R.map((path: string): [string, string] => {
1467+
const measures = this.rollupMeasures(preAggregationForQuery);
1468+
1469+
return Object.fromEntries(measures
1470+
.flatMap(path => {
14741471
const measure = this.query.newMeasure(path);
1472+
const memberPath = this.query.cubeEvaluator.pathFromArray(measure.path());
1473+
const alias = measure.aliasName();
1474+
// Return both full join path and measure path
14751475
return [
1476-
path,
1477-
measure.aliasName(),
1476+
[path, alias],
1477+
[memberPath, alias],
14781478
];
1479-
}),
1480-
R.fromPairs,
1481-
)(this.rollupMeasures(preAggregationForQuery));
1479+
}));
14821480
}
14831481

14841482
private dimensionsRenderedReference(preAggregationForQuery: PreAggregationForQuery): Record<string, string> {
1485-
return R.pipe<
1486-
string[],
1487-
Array<[string, string]>,
1488-
Record<string, string>
1489-
>(
1490-
R.map((path: string): [string, string] => {
1483+
const dimensions = this.rollupDimensions(preAggregationForQuery);
1484+
1485+
return Object.fromEntries(dimensions
1486+
.flatMap(path => {
14911487
const dimension = this.query.newDimension(path);
1488+
const column = this.query.escapeColumnName(dimension.unescapedAliasName());
1489+
const memberPath = this.query.cubeEvaluator.pathFromArray(dimension.path());
1490+
// Return both full join path and dimension path
14921491
return [
1493-
path,
1494-
this.query.escapeColumnName(dimension.unescapedAliasName()),
1492+
[path, column],
1493+
[memberPath, column],
14951494
];
1496-
}),
1497-
R.fromPairs,
1498-
)(this.rollupDimensions(preAggregationForQuery));
1495+
}));
14991496
}
15001497

15011498
private timeDimensionsRenderedReference(rollupGranularity: string, preAggregationForQuery: PreAggregationForQuery): Record<string, string> {
1502-
return R.pipe<
1503-
PreAggregationTimeDimensionReference[],
1504-
Array<[string, string]>,
1505-
Record<string, string>
1506-
>(
1507-
R.map((td: PreAggregationTimeDimensionReference): [string, string] => {
1499+
const timeDimensions = this.rollupTimeDimensions(preAggregationForQuery);
1500+
1501+
return Object.fromEntries(timeDimensions
1502+
.flatMap(td => {
15081503
const timeDimension = this.query.newTimeDimension(td);
1504+
const column = this.query.escapeColumnName(timeDimension.unescapedAliasName(rollupGranularity));
1505+
const memberPath = this.query.cubeEvaluator.pathFromArray(timeDimension.path());
1506+
// Return both full join path and dimension path
15091507
return [
1510-
td.dimension,
1511-
this.query.escapeColumnName(timeDimension.unescapedAliasName(rollupGranularity)),
1508+
[td.dimension, column],
1509+
[memberPath, column],
15121510
];
1513-
}),
1514-
R.fromPairs,
1515-
)(this.rollupTimeDimensions(preAggregationForQuery));
1511+
}));
15161512
}
15171513

15181514
private rollupMembers<T extends 'measures' | 'dimensions' | 'timeDimensions'>(preAggregationForQuery: PreAggregationForQuery, type: T): PreAggregationReferences[T] {

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

Lines changed: 116 additions & 20 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,108 @@ 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+
preAggregationIds: [preAggregationId]
542+
});
543+
544+
expect(preAggregations.length).toBe(1);
545+
const preAggregation = preAggregations[0];
546+
547+
if (withDateRange) {
548+
preAggregation.references.timeDimensions = preAggregation.references.timeDimensions.map(td => ({
549+
...td,
550+
dateRange: ['1970-01-01', '1970-01-02'],
551+
}));
552+
}
553+
554+
return new PostgresQuery({ joinGraph, cubeEvaluator, compiler }, {
555+
...preAggregation.references,
556+
preAggregationId: preAggregation.id,
557+
preAggregationsSchema: '',
558+
timezone: 'UTC',
559+
});
560+
}
561+
562+
const preAggregationTests = [
563+
{
564+
preAggregationId: 'A.adex_with_join_paths',
565+
addTimeRange: false,
566+
expectedData: [
567+
{
568+
a__a_id: 1,
569+
a__a_seg: false,
570+
a__a_sum: '100',
571+
d__d_id: 1,
572+
d__d_name_for_join_paths: 'foo',
573+
d__d_seg: false,
574+
x__x_id: 1,
575+
x__x_seg: false,
576+
x__x_time_day: '1970-01-01T00:00:00.000Z',
577+
},
578+
],
579+
},
580+
{
581+
preAggregationId: 'A.adex_cumulative_with_join_paths',
582+
addTimeRange: true,
583+
expectedData: [
584+
{
585+
a__a_id: 1,
586+
x__x_cumulative_sum: '100',
587+
x__x_id: 1,
588+
x__x_time_day: '1970-01-01T00:00:00.000Z',
589+
},
590+
{
591+
a__a_id: 1,
592+
x__x_cumulative_sum: '100',
593+
x__x_id: 1,
594+
x__x_time_day: '1970-01-02T00:00:00.000Z',
595+
},
596+
],
597+
},
598+
{
599+
preAggregationId: 'A.ad_without_join_paths',
600+
addTimeRange: false,
601+
expectedData: [
602+
{
603+
a__a_id: 1,
604+
a__a_seg: false,
605+
a__a_sum: '100',
606+
d__d_id: 1,
607+
d__d_name_for_no_join_paths: 'foo',
608+
d__d_seg: false,
609+
d__d_time_day: '1970-01-01T00:00:00.000Z',
610+
},
611+
],
612+
},
521613
];
522-
for (const preAggregationId of preAggregationIds) {
614+
for (const { preAggregationId, addTimeRange, expectedData } of preAggregationTests) {
523615
// eslint-disable-next-line no-loop-func
524616
it(`pre-aggregation ${preAggregationId} should match its own references`, async () => {
525-
const preAggregations = cubeEvaluator.preAggregations({});
617+
// Always not using range, because reference query would have no range to start from
618+
// but should match pre-aggregation anyway
619+
const query = makeReferenceQueryFor(preAggregationId);
526620

527-
const preAggregation = preAggregations
528-
.find(p => p.id === preAggregationId);
529-
if (preAggregation === undefined) {
530-
throw expect(preAggregation).toBeDefined();
621+
const preAggregationsDescription: any = query.preAggregations?.preAggregationsDescription();
622+
const preAggregationFromQuery = preAggregationsDescription.find(p => p.preAggregationId === preAggregationId);
623+
if (preAggregationFromQuery === undefined) {
624+
throw expect(preAggregationFromQuery).toBeDefined();
531625
}
626+
});
532627

533-
const query = new PostgresQuery({ joinGraph, cubeEvaluator, compiler }, {
534-
...preAggregation.references,
535-
preAggregationId: preAggregation.id,
536-
});
628+
// eslint-disable-next-line no-loop-func
629+
it(`pre-aggregation ${preAggregationId} reference query should be executable`, async () => {
630+
// Adding date range for rolling window measure
631+
const query = makeReferenceQueryFor(preAggregationId, addTimeRange);
537632

538633
const preAggregationsDescription: any = query.preAggregations?.preAggregationsDescription();
539-
const preAggregationFromQuery = preAggregationsDescription.find(p => p.preAggregationId === preAggregation.id);
634+
const preAggregationFromQuery = preAggregationsDescription.find(p => p.preAggregationId === preAggregationId);
540635
if (preAggregationFromQuery === undefined) {
541636
throw expect(preAggregationFromQuery).toBeDefined();
542637
}
543638

544-
expect(preAggregationFromQuery.preAggregationId).toBe(preAggregationId);
639+
const res = await testWithPreAggregation(preAggregationFromQuery, query);
640+
expect(res).toEqual(expectedData);
545641
});
546642
}
547643
});

0 commit comments

Comments
 (0)