diff --git a/packages/cubejs-schema-compiler/test/integration/postgres/calendars.test.ts b/packages/cubejs-schema-compiler/test/integration/postgres/calendars.test.ts index 21292548cf4de..ba2f99d4aa878 100644 --- a/packages/cubejs-schema-compiler/test/integration/postgres/calendars.test.ts +++ b/packages/cubejs-schema-compiler/test/integration/postgres/calendars.test.ts @@ -211,6 +211,39 @@ cubes: type: time primary_key: true + granularities: + - name: year + sql: "{CUBE}.retail_year_begin_date" + + - name: quarter + sql: "{CUBE}.retail_quarter_year" + +# - name: month +# sql: "{CUBE}.retail_month_begin_date" + + - name: week + sql: "{CUBE}.retail_week_begin_date" + + # Casually defining custom granularities should also work. + # While maybe not very sound from a business standpoint, + # such definition should be allowed in this data model + - name: fortnight + interval: 2 week + origin: "2025-01-01" + + time_shift: + - interval: 1 month + type: prior + sql: "{CUBE}.retail_date_prev_month" + + - interval: 1 quarter + type: prior + sql: "{CUBE}.retail_date_prev_quarter" + + - interval: 1 year + type: prior + sql: "{CUBE}.retail_date_prev_year" + ##### Retail Dates #### - name: retail_date sql: retail_date @@ -305,149 +338,87 @@ cubes: ); } - it('Count by retail year', async () => runQueryTest({ - measures: ['calendar_orders.count'], - timeDimensions: [{ - dimension: 'custom_calendar.retail_date', - granularity: 'year', - dateRange: ['2025-02-02', '2026-02-01'] - }], - order: [{ id: 'custom_calendar.retail_date' }] - }, [ - { - calendar_orders__count: '37', - custom_calendar__retail_date_year: '2025-02-02T00:00:00.000Z', - } - ])); - - it('Count by retail month', async () => runQueryTest({ - measures: ['calendar_orders.count'], - timeDimensions: [{ - dimension: 'custom_calendar.retail_date', - granularity: 'month', - dateRange: ['2025-02-02', '2026-02-01'] - }], - order: [{ id: 'custom_calendar.retail_date' }] - }, [ - { - calendar_orders__count: '3', - custom_calendar__retail_date_month: '2025-02-01T00:00:00.000Z', - }, - { - calendar_orders__count: '3', - custom_calendar__retail_date_month: '2025-03-01T00:00:00.000Z', - }, - { - calendar_orders__count: '3', - custom_calendar__retail_date_month: '2025-04-01T00:00:00.000Z', - }, - { - calendar_orders__count: '3', - custom_calendar__retail_date_month: '2025-05-01T00:00:00.000Z', - }, - { - calendar_orders__count: '4', - custom_calendar__retail_date_month: '2025-06-01T00:00:00.000Z', - }, - { - calendar_orders__count: '4', - custom_calendar__retail_date_month: '2025-07-01T00:00:00.000Z', - }, - { - calendar_orders__count: '4', - custom_calendar__retail_date_month: '2025-08-01T00:00:00.000Z', - }, - { - calendar_orders__count: '4', - custom_calendar__retail_date_month: '2025-09-01T00:00:00.000Z', - }, - { - calendar_orders__count: '3', - custom_calendar__retail_date_month: '2025-10-01T00:00:00.000Z', - }, - { - calendar_orders__count: '3', - custom_calendar__retail_date_month: '2025-11-01T00:00:00.000Z', - }, - { - calendar_orders__count: '3', - custom_calendar__retail_date_month: '2025-12-01T00:00:00.000Z', - }, - ])); - - it('Count by retail week', async () => runQueryTest({ - measures: ['calendar_orders.count'], - timeDimensions: [{ - dimension: 'custom_calendar.retail_date', - granularity: 'week', - dateRange: ['2025-02-02', '2025-04-01'] - }], - order: [{ id: 'custom_calendar.retail_date' }] - }, [ - { - calendar_orders__count: '1', - custom_calendar__retail_date_week: '2025-02-02T00:00:00.000Z', - }, - { - calendar_orders__count: '1', - custom_calendar__retail_date_week: '2025-02-09T00:00:00.000Z', - }, - { - calendar_orders__count: '1', - custom_calendar__retail_date_week: '2025-02-16T00:00:00.000Z', - }, - { - calendar_orders__count: '1', - custom_calendar__retail_date_week: '2025-02-23T00:00:00.000Z', - }, - { - calendar_orders__count: '1', - custom_calendar__retail_date_week: '2025-03-09T00:00:00.000Z', - }, - { - calendar_orders__count: '1', - custom_calendar__retail_date_week: '2025-03-16T00:00:00.000Z', - }, - { - calendar_orders__count: '1', - custom_calendar__retail_date_week: '2025-03-30T00:00:00.000Z', - }, - ])); - - it('Count by fortnight custom granularity', async () => runQueryTest({ - measures: ['calendar_orders.count'], - timeDimensions: [{ - dimension: 'custom_calendar.retail_date', - granularity: 'fortnight', - dateRange: ['2025-02-02', '2025-04-01'] - }], - order: [{ id: 'custom_calendar.retail_date' }] - }, [ - { - calendar_orders__count: '1', - custom_calendar__retail_date_fortnight: '2025-01-29T00:00:00.000Z', // Notice it starts on 2025-01-29, not 2025-02-01 - }, - { - calendar_orders__count: '2', - custom_calendar__retail_date_fortnight: '2025-02-12T00:00:00.000Z', - }, - { - calendar_orders__count: '2', - custom_calendar__retail_date_fortnight: '2025-02-26T00:00:00.000Z', - }, - { - calendar_orders__count: '1', - custom_calendar__retail_date_fortnight: '2025-03-12T00:00:00.000Z', - }, - { - calendar_orders__count: '1', - custom_calendar__retail_date_fortnight: '2025-03-26T00:00:00.000Z', - }, - ])); + describe('Common queries to calendar cube', () => { + it('Value of time-shift custom granularity non-pk time dimension', async () => runQueryTest({ + dimensions: ['custom_calendar.retail_date'], + timeDimensions: [{ + dimension: 'custom_calendar.retail_date', + dateRange: ['2025-02-02', '2025-02-06'] + }], + order: [{ id: 'custom_calendar.retail_date' }] + }, [ + { + custom_calendar__retail_date: '2025-02-02T00:00:00.000Z', + }, + { + custom_calendar__retail_date: '2025-02-03T00:00:00.000Z', + }, + { + custom_calendar__retail_date: '2025-02-04T00:00:00.000Z', + }, + { + custom_calendar__retail_date: '2025-02-05T00:00:00.000Z', + }, + { + custom_calendar__retail_date: '2025-02-06T00:00:00.000Z', + }, + ])); - describe('Time-shifts', () => { - it('Count shifted by retail year (custom shift + custom granularity)', async () => runQueryTest({ - measures: ['calendar_orders.count', 'calendar_orders.count_shifted_calendar_y'], + it('Year granularity of time-shift custom granularity non-pk time dimension', async () => runQueryTest({ + timeDimensions: [{ + dimension: 'custom_calendar.retail_date', + granularity: 'year', + dateRange: ['2025-02-02', '2025-02-06'] + }], + order: [{ id: 'custom_calendar.retail_date' }] + }, [ + { + custom_calendar__retail_date_year: '2025-02-02T00:00:00.000Z', + }, + ])); + + it('Value of time-shift custom granularity pk time dimension', async () => runQueryTest({ + dimensions: ['custom_calendar.date_val'], + timeDimensions: [{ + dimension: 'custom_calendar.date_val', + dateRange: ['2025-02-02', '2025-02-06'] + }], + order: [{ id: 'custom_calendar.date_val' }] + }, [ + { + custom_calendar__date_val: '2025-02-02T00:00:00.000Z', + }, + { + custom_calendar__date_val: '2025-02-03T00:00:00.000Z', + }, + { + custom_calendar__date_val: '2025-02-04T00:00:00.000Z', + }, + { + custom_calendar__date_val: '2025-02-05T00:00:00.000Z', + }, + { + custom_calendar__date_val: '2025-02-06T00:00:00.000Z', + }, + ])); + + it('Year granularity of time-shift custom granularity pk time dimension', async () => runQueryTest({ + timeDimensions: [{ + dimension: 'custom_calendar.date_val', + granularity: 'year', + dateRange: ['2025-02-02', '2025-02-06'] + }], + order: [{ id: 'custom_calendar.date_val' }] + }, [ + { + custom_calendar__date_val_year: '2025-02-02T00:00:00.000Z', + }, + ])); + }); + + describe('Custom granularities', () => { + it('Count by retail year', async () => runQueryTest({ + measures: ['calendar_orders.count'], timeDimensions: [{ dimension: 'custom_calendar.retail_date', granularity: 'year', @@ -457,13 +428,12 @@ cubes: }, [ { calendar_orders__count: '37', - calendar_orders__count_shifted_calendar_y: '39', custom_calendar__retail_date_year: '2025-02-02T00:00:00.000Z', - }, + } ])); - it('Count shifted by retail month (custom shift common granularity)', async () => runQueryTest({ - measures: ['calendar_orders.count', 'calendar_orders.count_shifted_calendar_m'], + it('Count by retail month', async () => runQueryTest({ + measures: ['calendar_orders.count'], timeDimensions: [{ dimension: 'custom_calendar.retail_date', granularity: 'month', @@ -473,190 +443,360 @@ cubes: }, [ { calendar_orders__count: '3', - calendar_orders__count_shifted_calendar_m: '3', custom_calendar__retail_date_month: '2025-02-01T00:00:00.000Z', }, { calendar_orders__count: '3', - calendar_orders__count_shifted_calendar_m: '4', custom_calendar__retail_date_month: '2025-03-01T00:00:00.000Z', }, { calendar_orders__count: '3', - calendar_orders__count_shifted_calendar_m: '2', custom_calendar__retail_date_month: '2025-04-01T00:00:00.000Z', }, { calendar_orders__count: '3', - calendar_orders__count_shifted_calendar_m: '2', custom_calendar__retail_date_month: '2025-05-01T00:00:00.000Z', }, { calendar_orders__count: '4', - calendar_orders__count_shifted_calendar_m: '3', custom_calendar__retail_date_month: '2025-06-01T00:00:00.000Z', }, { calendar_orders__count: '4', - calendar_orders__count_shifted_calendar_m: '4', custom_calendar__retail_date_month: '2025-07-01T00:00:00.000Z', }, { calendar_orders__count: '4', - calendar_orders__count_shifted_calendar_m: '4', custom_calendar__retail_date_month: '2025-08-01T00:00:00.000Z', }, { calendar_orders__count: '4', - calendar_orders__count_shifted_calendar_m: '3', custom_calendar__retail_date_month: '2025-09-01T00:00:00.000Z', }, { calendar_orders__count: '3', - calendar_orders__count_shifted_calendar_m: '4', custom_calendar__retail_date_month: '2025-10-01T00:00:00.000Z', }, { calendar_orders__count: '3', - calendar_orders__count_shifted_calendar_m: '3', custom_calendar__retail_date_month: '2025-11-01T00:00:00.000Z', }, { calendar_orders__count: '3', - calendar_orders__count_shifted_calendar_m: '3', custom_calendar__retail_date_month: '2025-12-01T00:00:00.000Z', }, ])); - it('Count shifted by retail week (common shift custom granularity)', async () => runQueryTest({ - measures: ['calendar_orders.count', 'calendar_orders.count_shifted_calendar_w'], + it('Count by retail week', async () => runQueryTest({ + measures: ['calendar_orders.count'], timeDimensions: [{ dimension: 'custom_calendar.retail_date', granularity: 'week', - dateRange: ['2025-02-02', '2026-02-01'] + dateRange: ['2025-02-02', '2025-04-01'] }], order: [{ id: 'custom_calendar.retail_date' }] }, [ { calendar_orders__count: '1', - calendar_orders__count_shifted_calendar_w: '1', - custom_calendar__retail_date_week: '2025-02-09T00:00:00.000Z', - }, - { - calendar_orders__count: '1', - calendar_orders__count_shifted_calendar_w: '1', - custom_calendar__retail_date_week: '2025-02-16T00:00:00.000Z', - }, - { - calendar_orders__count: '1', - calendar_orders__count_shifted_calendar_w: '1', - custom_calendar__retail_date_week: '2025-02-23T00:00:00.000Z', - }, - { - calendar_orders__count: '1', - calendar_orders__count_shifted_calendar_w: '1', - custom_calendar__retail_date_week: '2025-03-16T00:00:00.000Z', + custom_calendar__retail_date_week: '2025-02-02T00:00:00.000Z', }, { calendar_orders__count: '1', - calendar_orders__count_shifted_calendar_w: '1', - custom_calendar__retail_date_week: '2025-04-06T00:00:00.000Z', - }, - { - calendar_orders__count: '1', - calendar_orders__count_shifted_calendar_w: '1', - custom_calendar__retail_date_week: '2025-04-13T00:00:00.000Z', + custom_calendar__retail_date_week: '2025-02-09T00:00:00.000Z', }, { calendar_orders__count: '1', - calendar_orders__count_shifted_calendar_w: '1', - custom_calendar__retail_date_week: '2025-05-11T00:00:00.000Z', + custom_calendar__retail_date_week: '2025-02-16T00:00:00.000Z', }, { calendar_orders__count: '1', - calendar_orders__count_shifted_calendar_w: '1', - custom_calendar__retail_date_week: '2025-05-18T00:00:00.000Z', + custom_calendar__retail_date_week: '2025-02-23T00:00:00.000Z', }, { calendar_orders__count: '1', - calendar_orders__count_shifted_calendar_w: '1', - custom_calendar__retail_date_week: '2025-06-08T00:00:00.000Z', + custom_calendar__retail_date_week: '2025-03-09T00:00:00.000Z', }, { calendar_orders__count: '1', - calendar_orders__count_shifted_calendar_w: '1', - custom_calendar__retail_date_week: '2025-06-15T00:00:00.000Z', + custom_calendar__retail_date_week: '2025-03-16T00:00:00.000Z', }, { calendar_orders__count: '1', - calendar_orders__count_shifted_calendar_w: '1', - custom_calendar__retail_date_week: '2025-06-22T00:00:00.000Z', + custom_calendar__retail_date_week: '2025-03-30T00:00:00.000Z', }, + ])); + + it('Count by fortnight custom granularity', async () => runQueryTest({ + measures: ['calendar_orders.count'], + timeDimensions: [{ + dimension: 'custom_calendar.retail_date', + granularity: 'fortnight', + dateRange: ['2025-02-02', '2025-04-01'] + }], + order: [{ id: 'custom_calendar.retail_date' }] + }, [ { calendar_orders__count: '1', - calendar_orders__count_shifted_calendar_w: '1', - custom_calendar__retail_date_week: '2025-06-29T00:00:00.000Z', + custom_calendar__retail_date_fortnight: '2025-01-29T00:00:00.000Z', // Notice it starts on 2025-01-29, not 2025-02-01 }, { calendar_orders__count: '2', - calendar_orders__count_shifted_calendar_w: '1', - custom_calendar__retail_date_week: '2025-07-20T00:00:00.000Z', - }, - { - calendar_orders__count: '1', - calendar_orders__count_shifted_calendar_w: '2', - custom_calendar__retail_date_week: '2025-07-27T00:00:00.000Z', - }, - { - calendar_orders__count: '1', - calendar_orders__count_shifted_calendar_w: '1', - custom_calendar__retail_date_week: '2025-08-03T00:00:00.000Z', - }, - { - calendar_orders__count: '1', - calendar_orders__count_shifted_calendar_w: '1', - custom_calendar__retail_date_week: '2025-08-10T00:00:00.000Z', - }, - { - calendar_orders__count: '1', - calendar_orders__count_shifted_calendar_w: '1', - custom_calendar__retail_date_week: '2025-08-17T00:00:00.000Z', + custom_calendar__retail_date_fortnight: '2025-02-12T00:00:00.000Z', }, { calendar_orders__count: '2', - calendar_orders__count_shifted_calendar_w: '1', - custom_calendar__retail_date_week: '2025-09-07T00:00:00.000Z', - }, - { - calendar_orders__count: '1', - calendar_orders__count_shifted_calendar_w: '2', - custom_calendar__retail_date_week: '2025-09-14T00:00:00.000Z', + custom_calendar__retail_date_fortnight: '2025-02-26T00:00:00.000Z', }, { calendar_orders__count: '1', - calendar_orders__count_shifted_calendar_w: '1', - custom_calendar__retail_date_week: '2025-10-12T00:00:00.000Z', + custom_calendar__retail_date_fortnight: '2025-03-12T00:00:00.000Z', }, { calendar_orders__count: '1', - calendar_orders__count_shifted_calendar_w: '1', - custom_calendar__retail_date_week: '2025-10-19T00:00:00.000Z', - }, - { - calendar_orders__count: '1', - calendar_orders__count_shifted_calendar_w: '1', - custom_calendar__retail_date_week: '2025-11-23T00:00:00.000Z', - }, - { - calendar_orders__count: '1', - calendar_orders__count_shifted_calendar_w: '1', - custom_calendar__retail_date_week: '2025-11-30T00:00:00.000Z', - }, - { - calendar_orders__count: '1', - calendar_orders__count_shifted_calendar_w: '1', - custom_calendar__retail_date_week: '2025-12-21T00:00:00.000Z', + custom_calendar__retail_date_fortnight: '2025-03-26T00:00:00.000Z', }, ])); }); + + describe('Time-shifts', () => { + describe('Non-PK dimension time-shifts', () => { + it('Count shifted by retail year (custom shift + custom granularity)', async () => runQueryTest({ + measures: ['calendar_orders.count', 'calendar_orders.count_shifted_calendar_y'], + timeDimensions: [{ + dimension: 'custom_calendar.retail_date', + granularity: 'year', + dateRange: ['2025-02-02', '2026-02-01'] + }], + order: [{ id: 'custom_calendar.retail_date' }] + }, [ + { + calendar_orders__count: '37', + calendar_orders__count_shifted_calendar_y: '39', + custom_calendar__retail_date_year: '2025-02-02T00:00:00.000Z', + }, + ])); + + it('Count shifted by retail month (custom shift + common granularity)', async () => runQueryTest({ + measures: ['calendar_orders.count', 'calendar_orders.count_shifted_calendar_m'], + timeDimensions: [{ + dimension: 'custom_calendar.retail_date', + granularity: 'month', + dateRange: ['2025-02-02', '2026-02-01'] + }], + order: [{ id: 'custom_calendar.retail_date' }] + }, [ + { + calendar_orders__count: '3', + calendar_orders__count_shifted_calendar_m: '3', + custom_calendar__retail_date_month: '2025-02-01T00:00:00.000Z', + }, + { + calendar_orders__count: '3', + calendar_orders__count_shifted_calendar_m: '4', + custom_calendar__retail_date_month: '2025-03-01T00:00:00.000Z', + }, + { + calendar_orders__count: '3', + calendar_orders__count_shifted_calendar_m: '2', + custom_calendar__retail_date_month: '2025-04-01T00:00:00.000Z', + }, + { + calendar_orders__count: '3', + calendar_orders__count_shifted_calendar_m: '2', + custom_calendar__retail_date_month: '2025-05-01T00:00:00.000Z', + }, + { + calendar_orders__count: '4', + calendar_orders__count_shifted_calendar_m: '3', + custom_calendar__retail_date_month: '2025-06-01T00:00:00.000Z', + }, + { + calendar_orders__count: '4', + calendar_orders__count_shifted_calendar_m: '4', + custom_calendar__retail_date_month: '2025-07-01T00:00:00.000Z', + }, + { + calendar_orders__count: '4', + calendar_orders__count_shifted_calendar_m: '4', + custom_calendar__retail_date_month: '2025-08-01T00:00:00.000Z', + }, + { + calendar_orders__count: '4', + calendar_orders__count_shifted_calendar_m: '3', + custom_calendar__retail_date_month: '2025-09-01T00:00:00.000Z', + }, + { + calendar_orders__count: '3', + calendar_orders__count_shifted_calendar_m: '4', + custom_calendar__retail_date_month: '2025-10-01T00:00:00.000Z', + }, + { + calendar_orders__count: '3', + calendar_orders__count_shifted_calendar_m: '3', + custom_calendar__retail_date_month: '2025-11-01T00:00:00.000Z', + }, + { + calendar_orders__count: '3', + calendar_orders__count_shifted_calendar_m: '3', + custom_calendar__retail_date_month: '2025-12-01T00:00:00.000Z', + }, + ])); + + it('Count shifted by retail week (common shift + custom granularity)', async () => runQueryTest({ + measures: ['calendar_orders.count', 'calendar_orders.count_shifted_calendar_w'], + timeDimensions: [{ + dimension: 'custom_calendar.retail_date', + granularity: 'week', + dateRange: ['2025-02-02', '2025-04-12'] + }], + order: [{ id: 'custom_calendar.retail_date' }] + }, [ + { + calendar_orders__count: '1', + calendar_orders__count_shifted_calendar_w: '1', + custom_calendar__retail_date_week: '2025-02-09T00:00:00.000Z', + }, + { + calendar_orders__count: '1', + calendar_orders__count_shifted_calendar_w: '1', + custom_calendar__retail_date_week: '2025-02-16T00:00:00.000Z', + }, + { + calendar_orders__count: '1', + calendar_orders__count_shifted_calendar_w: '1', + custom_calendar__retail_date_week: '2025-02-23T00:00:00.000Z', + }, + { + calendar_orders__count: '1', + calendar_orders__count_shifted_calendar_w: '1', + custom_calendar__retail_date_week: '2025-03-16T00:00:00.000Z', + }, + { + calendar_orders__count: '1', + calendar_orders__count_shifted_calendar_w: '1', + custom_calendar__retail_date_week: '2025-04-06T00:00:00.000Z', + }, + ])); + }); + + describe('PK dimension time-shifts', () => { + it.skip('Count shifted by retail year (custom shift + custom granularity)1', async () => runQueryTest({ + measures: ['calendar_orders.count', 'calendar_orders.count_shifted_calendar_y'], + timeDimensions: [{ + dimension: 'custom_calendar.date_val', + granularity: 'year', + dateRange: ['2025-02-02', '2026-02-01'] + }], + order: [{ id: 'custom_calendar.date_val' }] + }, [ + { + calendar_orders__count: '37', + calendar_orders__count_shifted_calendar_y: '39', + custom_calendar__date_val_year: '2025-02-02T00:00:00.000Z', + }, + ])); + + it.skip('Count shifted by retail month (custom shift + common granularity)', async () => runQueryTest({ + measures: ['calendar_orders.count', 'calendar_orders.count_shifted_calendar_m'], + timeDimensions: [{ + dimension: 'custom_calendar.date_val', + granularity: 'month', + dateRange: ['2025-02-02', '2026-02-01'] + }], + order: [{ id: 'custom_calendar.date_val' }] + }, [ + { + calendar_orders__count: '3', + calendar_orders__count_shifted_calendar_m: '3', + custom_calendar__date_val_month: '2025-02-01T00:00:00.000Z', + }, + { + calendar_orders__count: '3', + calendar_orders__count_shifted_calendar_m: '4', + custom_calendar__date_val_month: '2025-03-01T00:00:00.000Z', + }, + { + calendar_orders__count: '3', + calendar_orders__count_shifted_calendar_m: '2', + custom_calendar__date_val_month: '2025-04-01T00:00:00.000Z', + }, + { + calendar_orders__count: '3', + calendar_orders__count_shifted_calendar_m: '2', + custom_calendar__date_val_month: '2025-05-01T00:00:00.000Z', + }, + { + calendar_orders__count: '4', + calendar_orders__count_shifted_calendar_m: '3', + custom_calendar__date_val_month: '2025-06-01T00:00:00.000Z', + }, + { + calendar_orders__count: '4', + calendar_orders__count_shifted_calendar_m: '4', + custom_calendar__date_val_month: '2025-07-01T00:00:00.000Z', + }, + { + calendar_orders__count: '4', + calendar_orders__count_shifted_calendar_m: '4', + custom_calendar__date_val_month: '2025-08-01T00:00:00.000Z', + }, + { + calendar_orders__count: '4', + calendar_orders__count_shifted_calendar_m: '3', + custom_calendar__date_val_month: '2025-09-01T00:00:00.000Z', + }, + { + calendar_orders__count: '3', + calendar_orders__count_shifted_calendar_m: '4', + custom_calendar__date_val_month: '2025-10-01T00:00:00.000Z', + }, + { + calendar_orders__count: '3', + calendar_orders__count_shifted_calendar_m: '3', + custom_calendar__date_val_month: '2025-11-01T00:00:00.000Z', + }, + { + calendar_orders__count: '3', + calendar_orders__count_shifted_calendar_m: '3', + custom_calendar__date_val_month: '2025-12-01T00:00:00.000Z', + }, + ])); + + it.skip('Count shifted by retail week (common shift + custom granularity)', async () => runQueryTest({ + measures: ['calendar_orders.count', 'calendar_orders.count_shifted_calendar_w'], + timeDimensions: [{ + dimension: 'custom_calendar.date_val', + granularity: 'week', + dateRange: ['2025-02-02', '2026-02-01'] + }], + order: [{ id: 'custom_calendar.date_val' }] + }, [ + { + calendar_orders__count: '1', + calendar_orders__count_shifted_calendar_w: '1', + custom_calendar__date_val_week: '2025-02-09T00:00:00.000Z', + }, + { + calendar_orders__count: '1', + calendar_orders__count_shifted_calendar_w: '1', + custom_calendar__date_val_week: '2025-02-16T00:00:00.000Z', + }, + { + calendar_orders__count: '1', + calendar_orders__count_shifted_calendar_w: '1', + custom_calendar__date_val_week: '2025-02-23T00:00:00.000Z', + }, + { + calendar_orders__count: '1', + calendar_orders__count_shifted_calendar_w: '1', + custom_calendar__date_val_week: '2025-03-16T00:00:00.000Z', + }, + { + calendar_orders__count: '1', + calendar_orders__count_shifted_calendar_w: '1', + custom_calendar__date_val_week: '2025-04-06T00:00:00.000Z', + }, + ])); + }); + }); }); diff --git a/rust/cubeorchestrator/src/transport.rs b/rust/cubeorchestrator/src/transport.rs index 90901cbcb5160..f262c3ae9243a 100644 --- a/rust/cubeorchestrator/src/transport.rs +++ b/rust/cubeorchestrator/src/transport.rs @@ -159,7 +159,8 @@ pub type MembersMap = HashMap; pub struct GranularityMeta { pub name: String, pub title: String, - pub interval: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub interval: Option, #[serde(skip_serializing_if = "Option::is_none")] pub offset: Option, #[serde(skip_serializing_if = "Option::is_none")] diff --git a/rust/cubesqlplanner/cubesqlplanner/src/physical_plan_builder/builder.rs b/rust/cubesqlplanner/cubesqlplanner/src/physical_plan_builder/builder.rs index 0d739f697e215..d585a7f98b4ab 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/physical_plan_builder/builder.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/physical_plan_builder/builder.rs @@ -47,10 +47,10 @@ impl PhysicalPlanBuilderContext { dimension.calendar_time_shift_for_interval(&shift.interval) { return Either::Right((dim_key.clone(), cts.clone())); - } else if let Some(calendar_pk) = dimension.time_shift_pk() { + } else if let Some(calendar_pk) = dimension.time_shift_pk_full_name() { let mut shift = shift.clone(); shift.interval = shift.interval.inverse(); - return Either::Left((calendar_pk.full_name(), shift.clone())); + return Either::Left((calendar_pk, shift.clone())); } } Either::Left((key.clone(), shift.clone())) diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/dimension_symbol.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/dimension_symbol.rs index f9e35b6cbe85a..6d15a6a0f4db0 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/dimension_symbol.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/dimension_symbol.rs @@ -42,7 +42,10 @@ pub struct DimensionSymbol { is_reference: bool, // Symbol is a direct reference to another symbol without any calculations is_view: bool, time_shift: Vec, - time_shift_pk: Option>, + time_shift_pk_full_name: Option, + is_self_time_shift_pk: bool, // If the dimension itself is a primary key and has time shifts, + // we can not reevaluate itself again while processing time shifts + // to avoid infinite recursion. So we raise this flag instead. } impl DimensionSymbol { @@ -57,7 +60,8 @@ impl DimensionSymbol { case: Option, definition: Rc, time_shift: Vec, - time_shift_pk: Option>, + time_shift_pk_full_name: Option, + is_self_time_shift_pk: bool, ) -> Rc { Rc::new(Self { cube_name, @@ -70,7 +74,8 @@ impl DimensionSymbol { case, is_view, time_shift, - time_shift_pk, + time_shift_pk_full_name, + is_self_time_shift_pk, }) } @@ -112,8 +117,8 @@ impl DimensionSymbol { &self.time_shift } - pub fn time_shift_pk(&self) -> Option> { - self.time_shift_pk.clone() + pub fn time_shift_pk_full_name(&self) -> Option { + self.time_shift_pk_full_name.clone() } pub fn full_name(&self) -> String { @@ -234,8 +239,10 @@ impl DimensionSymbol { .iter() .find(|shift| shift.interval == *interval) { - if let Some(pk) = &self.time_shift_pk { - return Some((pk.full_name(), ts.clone())); + if let Some(pk) = &self.time_shift_pk_full_name { + return Some((pk.clone(), ts.clone())); + } else if self.is_self_time_shift_pk { + return Some((self.full_name(), ts.clone())); } } None @@ -383,6 +390,7 @@ impl SymbolFactory for DimensionSymbolFactory { let cube = cube_evaluator.cube_from_path(cube_name.clone())?; let is_view = cube.static_data().is_view.unwrap_or(false); let is_calendar = cube.static_data().is_calendar.unwrap_or(false); + let mut is_self_time_shift_pk = false; // If the cube is a calendar, we need to find the primary key member // so that we can use it for time shifts processing. @@ -395,33 +403,17 @@ impl SymbolFactory for DimensionSymbolFactory { .unwrap_or_else(|| vec![]); if pk_members.iter().any(|pk| **pk == name) { - // To avoid evaluation loop. - None - } else { - let pk_member = pk_members - .into_iter() - .map(|primary_key| -> Result<_, CubeError> { - let key_dimension_name = format!("{}.{}", cube_name, primary_key); - let pk_member = compiler.add_dimension_evaluator(key_dimension_name)?; - - Ok(pk_member) - }) - .collect::, _>>()? - .into_iter() - .filter(|pk_member| { - if let Ok(pk_dimension) = pk_member.as_dimension() { - // TODO: What if calendar cube is joined via non-time dimension? - if pk_dimension.dimension_type() == "time" { - return true; - } - } - false - }) - .collect::>() - .first() - .cloned(); - pk_member + is_self_time_shift_pk = true; + } + + if pk_members.len() > 1 { + return Err(CubeError::user(format!( + "Cube '{}' has multiple primary keys, but only one is allowed for calendar cubes", + cube_name + ))); } + + pk_members.first().map(|pk| format!("{}.{}", cube_name, pk)) } else { None }; @@ -450,6 +442,7 @@ impl SymbolFactory for DimensionSymbolFactory { definition, time_shift, time_shift_pk, + is_self_time_shift_pk, ))) } }