Skip to content

Commit c164aab

Browse files
authored
fix(schema-compiler): resolve time dimension granularity columns in pre-aggregation indexes (#10894)
1 parent 9b454f8 commit c164aab

7 files changed

Lines changed: 388 additions & 13 deletions

File tree

docs-mintlify/reference/data-modeling/pre-aggregations.mdx

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1571,9 +1571,8 @@ cube(`orders`, {
15711571

15721572
</CodeGroup>
15731573

1574-
In the case that you want to reference the time dimension in the index,
1575-
the column name as a string in the following format can be used:
1576-
`<cube name>__<time dimension name>_<granularity>`:
1574+
To reference a time dimension column with its granularity in the index,
1575+
use the `CUBE.<time_dimension>.<granularity>` syntax:
15771576

15781577
<CodeGroup>
15791578

@@ -1594,8 +1593,7 @@ cubes:
15941593
- name: category_created_at_index
15951594
columns:
15961595
- CUBE.category
1597-
# special syntax needed
1598-
- "{'orders__created_at_day'}"
1596+
- CUBE.created_at.day
15991597
# ...
16001598
```
16011599

@@ -1613,6 +1611,53 @@ cube(`orders`, {
16131611
],
16141612
time_dimension: CUBE.created_at,
16151613
granularity: `day`,
1614+
indexes: {
1615+
category_created_at_index: {
1616+
columns: [
1617+
CUBE.category,
1618+
CUBE.created_at.day
1619+
]
1620+
}
1621+
}
1622+
}
1623+
},
1624+
1625+
// ...
1626+
})
1627+
```
1628+
1629+
</CodeGroup>
1630+
1631+
Alternatively, you can reference the time dimension column as a string
1632+
using the `<cube_name>__<time_dimension_name>_<granularity>` format:
1633+
1634+
<CodeGroup>
1635+
1636+
```yaml title="YAML"
1637+
cubes:
1638+
- name: orders
1639+
# ...
1640+
1641+
pre_aggregations:
1642+
- name: category_and_date
1643+
# ...
1644+
1645+
indexes:
1646+
- name: category_created_at_index
1647+
columns:
1648+
- CUBE.category
1649+
- "{'orders__created_at_day'}"
1650+
# ...
1651+
```
1652+
1653+
```javascript title="JavaScript"
1654+
cube(`orders`, {
1655+
// ...
1656+
1657+
pre_aggregations: {
1658+
category_and_date: {
1659+
// ...
1660+
16161661
indexes: {
16171662
category_created_at_index: {
16181663
columns: [

docs/content/product/data-modeling/reference/pre-aggregations.mdx

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1571,9 +1571,8 @@ cubes:
15711571

15721572
</CodeTabs>
15731573

1574-
In the case that you want to reference the time dimension in the index,
1575-
the column name as a string in the following format can be used:
1576-
`<cube name>__<time dimension name>_<granularity>`:
1574+
To reference a time dimension column with its granularity in the index,
1575+
use the `CUBE.<time_dimension>.<granularity>` syntax:
15771576

15781577
<CodeTabs>
15791578

@@ -1595,7 +1594,7 @@ cube(`orders`, {
15951594
category_created_at_index: {
15961595
columns: [
15971596
CUBE.category,
1598-
`orders__created_at_day`
1597+
CUBE.created_at.day
15991598
]
16001599
}
16011600
}
@@ -1623,7 +1622,53 @@ cubes:
16231622
- name: category_created_at_index
16241623
columns:
16251624
- CUBE.category
1626-
# special syntax needed
1625+
- CUBE.created_at.day
1626+
# ...
1627+
```
1628+
1629+
</CodeTabs>
1630+
1631+
Alternatively, you can reference the time dimension column as a string
1632+
using the `<cube_name>__<time_dimension_name>_<granularity>` format:
1633+
1634+
<CodeTabs>
1635+
1636+
```javascript
1637+
cube(`orders`, {
1638+
// ...
1639+
1640+
pre_aggregations: {
1641+
category_and_date: {
1642+
// ...
1643+
1644+
indexes: {
1645+
category_created_at_index: {
1646+
columns: [
1647+
CUBE.category,
1648+
`orders__created_at_day`
1649+
]
1650+
}
1651+
}
1652+
}
1653+
},
1654+
1655+
// ...
1656+
})
1657+
```
1658+
1659+
```yaml
1660+
cubes:
1661+
- name: orders
1662+
# ...
1663+
1664+
pre_aggregations:
1665+
- name: category_and_date
1666+
# ...
1667+
1668+
indexes:
1669+
- name: category_created_at_index
1670+
columns:
1671+
- CUBE.category
16271672
- "{'orders__created_at_day'}"
16281673
# ...
16291674
```

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4273,6 +4273,17 @@ export class BaseQuery {
42734273
this.cubeEvaluator.isSegment(path)
42744274
)
42754275
) {
4276+
if (path.length === 3 && this.cubeEvaluator.isDimension(path.slice(0, 2))) {
4277+
const dimensionDef = this.cubeEvaluator.dimensionByPath(path.slice(0, 2));
4278+
if (dimensionDef.type === 'time' &&
4279+
this.cubeEvaluator.resolveGranularity([path[0], path[1], 'granularities', path[2]])) {
4280+
const td = this.newTimeDimension({
4281+
dimension: `${path[0]}.${path[1]}`,
4282+
granularity: path[2],
4283+
});
4284+
return td.unescapedAliasName();
4285+
}
4286+
}
42764287
return this.aliasName(column);
42774288
} else {
42784289
return column;

packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -832,15 +832,15 @@ export class CubeEvaluator extends CubeSymbols {
832832
return this.isInstanceOfType('segments', path);
833833
}
834834

835-
public measureByPath(measurePath: string): MeasureDefinition {
835+
public measureByPath(measurePath: string | string[]): MeasureDefinition {
836836
return this.byPath('measures', measurePath) as MeasureDefinition;
837837
}
838838

839-
public dimensionByPath(dimensionPath: string): DimensionDefinition {
839+
public dimensionByPath(dimensionPath: string | string[]): DimensionDefinition {
840840
return this.byPath('dimensions', dimensionPath) as DimensionDefinition;
841841
}
842842

843-
public segmentByPath(segmentPath: string): SegmentDefinition {
843+
public segmentByPath(segmentPath: string | string[]): SegmentDefinition {
844844
return this.byPath('segments', segmentPath) as SegmentDefinition;
845845
}
846846

packages/cubejs-schema-compiler/test/unit/pre-aggregations.test.ts

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,150 @@ describe('pre-aggregations', () => {
364364
expect(indexesSql[1].indexName).toEqual('orders_indexes_orders_by_day_with_day_by_status_agg_index');
365365
});
366366

367+
it('pre-aggregation index with time dimension granularity column', async () => {
368+
const { compiler, cubeEvaluator, joinGraph } = prepareJsCompiler(
369+
`
370+
cube('Orders', {
371+
sql: \`SELECT * FROM orders\`,
372+
373+
measures: {
374+
count: {
375+
type: 'count',
376+
},
377+
},
378+
379+
dimensions: {
380+
created_at: {
381+
sql: \`created_at\`,
382+
type: 'time',
383+
},
384+
status: {
385+
sql: \`status\`,
386+
type: 'string',
387+
},
388+
},
389+
390+
preAggregations: {
391+
ordersByHour: {
392+
measures: [CUBE.count],
393+
dimensions: [CUBE.status],
394+
timeDimension: CUBE.created_at,
395+
granularity: 'hour',
396+
indexes: {
397+
time_index: {
398+
columns: [CUBE.created_at.hour],
399+
},
400+
time_and_status_index: {
401+
columns: [CUBE.created_at.hour, CUBE.status],
402+
},
403+
},
404+
},
405+
},
406+
});
407+
`
408+
);
409+
410+
await compiler.compile();
411+
412+
const query = new PostgresQuery({ joinGraph, cubeEvaluator, compiler }, {
413+
measures: ['Orders.count'],
414+
timeDimensions: [{
415+
dimension: 'Orders.created_at',
416+
granularity: 'hour',
417+
dateRange: ['2023-01-01', '2023-01-10']
418+
}],
419+
dimensions: ['Orders.status']
420+
});
421+
422+
const preAggregationsDescription: any = query.preAggregations?.preAggregationsDescription();
423+
expect(preAggregationsDescription.length).toBeGreaterThan(0);
424+
425+
const { indexesSql, createTableIndexes } = preAggregationsDescription[0];
426+
expect(indexesSql.length).toEqual(2);
427+
428+
const [timeIndexSql] = indexesSql[0].sql;
429+
expect(timeIndexSql).toContain('"orders__created_at_hour"');
430+
expect(timeIndexSql).not.toContain('"orders__created_at__hour"');
431+
432+
const [timeAndStatusIndexSql] = indexesSql[1].sql;
433+
expect(timeAndStatusIndexSql).toContain('"orders__created_at_hour"');
434+
expect(timeAndStatusIndexSql).toContain('"orders__status"');
435+
436+
expect(createTableIndexes[0].columns).toContain('"orders__created_at_hour"');
437+
expect(createTableIndexes[1].columns).toContain('"orders__created_at_hour"');
438+
expect(createTableIndexes[1].columns).toContain('"orders__status"');
439+
});
440+
441+
it('pre-aggregation index with time dimension granularity column (YAML)', async () => {
442+
const { compiler, cubeEvaluator, joinGraph } = prepareYamlCompiler(
443+
createSchemaYaml({
444+
cubes: [
445+
{
446+
name: 'orders',
447+
sql_table: 'orders',
448+
measures: [{
449+
name: 'count',
450+
type: 'count',
451+
}],
452+
dimensions: [
453+
{
454+
name: 'created_at',
455+
sql: 'created_at',
456+
type: 'time',
457+
},
458+
{
459+
name: 'status',
460+
sql: 'status',
461+
type: 'string',
462+
}
463+
],
464+
preAggregations: [
465+
{
466+
name: 'orders_by_hour',
467+
measures: ['count'],
468+
dimensions: ['status'],
469+
timeDimension: 'created_at',
470+
granularity: 'hour',
471+
indexes: [
472+
{
473+
name: 'time_granularity_index',
474+
columns: ['created_at.hour', 'status']
475+
}
476+
]
477+
}
478+
]
479+
},
480+
]
481+
})
482+
);
483+
484+
await compiler.compile();
485+
486+
const query = new PostgresQuery({ joinGraph, cubeEvaluator, compiler }, {
487+
measures: ['orders.count'],
488+
timeDimensions: [{
489+
dimension: 'orders.created_at',
490+
granularity: 'hour',
491+
dateRange: ['2023-01-01', '2023-01-10']
492+
}],
493+
dimensions: ['orders.status']
494+
});
495+
496+
const preAggregationsDescription: any = query.preAggregations?.preAggregationsDescription();
497+
expect(preAggregationsDescription.length).toBeGreaterThan(0);
498+
499+
const { indexesSql, createTableIndexes } = preAggregationsDescription[0];
500+
expect(indexesSql.length).toEqual(1);
501+
502+
const [indexSql] = indexesSql[0].sql;
503+
expect(indexSql).toContain('"orders__created_at_hour"');
504+
expect(indexSql).not.toContain('"orders__created_at__hour"');
505+
expect(indexSql).toContain('"orders__status"');
506+
507+
expect(createTableIndexes[0].columns).toContain('"orders__created_at_hour"');
508+
expect(createTableIndexes[0].columns).toContain('"orders__status"');
509+
});
510+
367511
it('pre-aggregation with FILTER_PARAMS', async () => {
368512
const { compiler, cubeEvaluator, joinGraph } = prepareYamlCompiler(
369513
createSchemaYaml({

0 commit comments

Comments
 (0)