Skip to content

Commit c19fb8a

Browse files
authored
chore(tesseract): Fix for multi stage with sub queries (#9292)
1 parent 7248b7d commit c19fb8a

File tree

2 files changed

+229
-0
lines changed

2 files changed

+229
-0
lines changed
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
import {
2+
getEnv,
3+
} from '@cubejs-backend/shared';
4+
import { PostgresQuery } from '../../../src/adapter/PostgresQuery';
5+
import { prepareYamlCompiler } from '../../unit/PrepareCompiler';
6+
import { dbRunner } from './PostgresDBRunner';
7+
8+
describe('Multi-Stage', () => {
9+
jest.setTimeout(200000);
10+
11+
const { compiler, joinGraph, cubeEvaluator } = prepareYamlCompiler(`
12+
cubes:
13+
- name: orders
14+
sql: >
15+
SELECT 9 as ID, 'completed' as STATUS, '2022-01-12T20:00:00.000Z'::timestamptz as CREATED_AT
16+
union all
17+
SELECT 10 as ID, 'completed' as STATUS, '2023-01-12T20:00:00.000Z'::timestamptz as CREATED_AT
18+
union all
19+
SELECT 11 as ID, 'completed' as STATUS, '2024-01-14T20:00:00.000Z'::timestamptz as CREATED_AT
20+
union all
21+
SELECT 12 as ID, 'completed' as STATUS, '2024-02-14T20:00:00.000Z'::timestamptz as CREATED_AT
22+
union all
23+
SELECT 13 as ID, 'completed' as STATUS, '2025-03-14T20:00:00.000Z'::timestamptz as CREATED_AT
24+
joins:
25+
- name: line_items
26+
sql: "{CUBE}.ID = {line_items}.order_id"
27+
relationship: many_to_one
28+
29+
dimensions:
30+
- name: id
31+
sql: ID
32+
type: number
33+
primary_key: true
34+
35+
- name: status
36+
sql: STATUS
37+
type: string
38+
39+
- name: date
40+
sql: CREATED_AT
41+
type: time
42+
43+
- name: amount
44+
sql: '{line_items.total_amount}'
45+
type: number
46+
sub_query: true
47+
48+
measures:
49+
- name: count
50+
type: count
51+
52+
- name: completed_count
53+
type: count
54+
filters:
55+
- sql: "{CUBE}.STATUS = 'completed'"
56+
57+
- name: returned_count
58+
type: count
59+
filters:
60+
- sql: "{CUBE}.STATUS = 'returned'"
61+
62+
- name: return_rate
63+
type: number
64+
sql: "({returned_count} / NULLIF({completed_count}, 0)) * 100.0"
65+
description: "Percentage of returned orders out of completed, exclude just placed orders."
66+
format: percent
67+
68+
- name: total_amount
69+
sql: '{CUBE.amount}'
70+
type: sum
71+
72+
- name: revenue
73+
sql: "CASE WHEN {CUBE}.status = 'completed' THEN {CUBE.amount} END"
74+
type: sum
75+
format: currency
76+
77+
- name: average_order_value
78+
sql: '{CUBE.amount}'
79+
type: avg
80+
81+
- name: revenue_1_y_ago
82+
sql: "{revenue}"
83+
multi_stage: true
84+
type: number
85+
format: currency
86+
time_shift:
87+
- time_dimension: date
88+
interval: 1 year
89+
type: prior
90+
- time_dimension: orders_view.date
91+
interval: 1 year
92+
type: prior
93+
94+
- name: cagr_1_y
95+
sql: "(({revenue} / {revenue_1_y_ago}) - 1)"
96+
type: number
97+
format: percent
98+
description: "Annual CAGR, year over year growth in revenue"
99+
100+
- name: line_items
101+
sql: >
102+
SELECT 9 as ID, '2024-01-12T20:00:00.000Z'::timestamptz as CREATED_AT, 9 as ORDER_ID, 11 as PRODUCT_ID
103+
union all
104+
SELECT 10 as ID, '2024-01-12T20:00:00.000Z'::timestamptz as CREATED_AT, 10 as ORDER_ID, 10 as PRODUCT_ID
105+
union all
106+
SELECT 11 as ID, '2024-01-12T20:00:00.000Z'::timestamptz as CREATED_AT, 10 as ORDER_ID, 11 as PRODUCT_ID
107+
union all
108+
SELECT 12 as ID, '2024-01-12T20:00:00.000Z'::timestamptz as CREATED_AT, 11 as ORDER_ID, 10 as PRODUCT_ID
109+
union all
110+
SELECT 13 as ID, '2024-01-12T20:00:00.000Z'::timestamptz as CREATED_AT, 11 as ORDER_ID, 10 as PRODUCT_ID
111+
union all
112+
SELECT 14 as ID, '2024-01-12T20:00:00.000Z'::timestamptz as CREATED_AT, 12 as ORDER_ID, 10 as PRODUCT_ID
113+
union all
114+
SELECT 15 as ID, '2024-01-12T20:00:00.000Z'::timestamptz as CREATED_AT, 13 as ORDER_ID, 11 as PRODUCT_ID
115+
public: false
116+
117+
joins:
118+
- name: products
119+
sql: "{CUBE}.PRODUCT_ID = {products}.ID"
120+
relationship: many_to_one
121+
122+
dimensions:
123+
- name: id
124+
sql: ID
125+
type: number
126+
primary_key: true
127+
128+
- name: created_at
129+
sql: CREATED_AT
130+
type: time
131+
132+
- name: price
133+
sql: "{products.price}"
134+
type: number
135+
136+
measures:
137+
- name: count
138+
type: count
139+
140+
- name: total_amount
141+
sql: "{price}"
142+
type: sum
143+
144+
- name: products
145+
sql: >
146+
SELECT 10 as ID, 'some category' as PRODUCT_CATEGORY, 'some name' as NAME, 10 as PRICE
147+
union all
148+
SELECT 11 as ID, 'some category' as PRODUCT_CATEGORY, 'some name' as NAME, 5 as PRICE
149+
public: false
150+
description: >
151+
Products and categories in our e-commerce store.
152+
153+
dimensions:
154+
- name: id
155+
sql: ID
156+
type: number
157+
primary_key: true
158+
159+
- name: product_category
160+
sql: PRODUCT_CATEGORY
161+
type: string
162+
163+
- name: name
164+
sql: NAME
165+
type: string
166+
167+
- name: price
168+
sql: PRICE
169+
type: number
170+
171+
measures:
172+
- name: count
173+
type: count
174+
views:
175+
- name: orders_view
176+
177+
cubes:
178+
- join_path: orders
179+
includes:
180+
- date
181+
- revenue
182+
- cagr_1_y
183+
- return_rate
184+
185+
- join_path: line_items.products
186+
prefix: true
187+
includes:
188+
- product_category
189+
190+
`);
191+
192+
async function runQueryTest(q, expectedResult) {
193+
if (!getEnv('nativeSqlPlanner')) {
194+
return;
195+
}
196+
await compiler.compile();
197+
const query = new PostgresQuery({ joinGraph, cubeEvaluator, compiler }, q);
198+
199+
console.log(query.buildSqlAndParams());
200+
201+
const res = await dbRunner.testQuery(query.buildSqlAndParams());
202+
console.log(JSON.stringify(res));
203+
204+
expect(res).toEqual(
205+
expectedResult
206+
);
207+
}
208+
209+
it('multi stage over sub query', async () => runQueryTest({
210+
measures: ['orders.revenue', 'orders.revenue_1_y_ago', 'orders.cagr_1_y'],
211+
timeDimensions: [
212+
{
213+
dimension: 'orders.date',
214+
granularity: 'year'
215+
}
216+
],
217+
timezone: 'UTC'
218+
}, [
219+
220+
{ orders__date_year: '2023-01-01T00:00:00.000Z',
221+
orders__revenue: '15',
222+
orders__revenue_1_y_ago: '5',
223+
orders__cagr_1_y: '2.0000000000000000' },
224+
{ orders__date_year: '2024-01-01T00:00:00.000Z', orders__revenue: '30', orders__revenue_1_y_ago: '15', orders__cagr_1_y: '1.0000000000000000' },
225+
{ orders__date_year: '2025-01-01T00:00:00.000Z', orders__revenue: '5', orders__revenue_1_y_ago: '30', orders__cagr_1_y: '-0.83333333333333333333' }]));
226+
});

rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/collectors/sub_query_dimensions.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ impl SubQueryDimensionsCollector {
2020

2121
pub fn extract_result(self) -> Vec<Rc<MemberSymbol>> {
2222
self.sub_query_dimensions
23+
.into_iter()
24+
.unique_by(|m| m.full_name())
25+
.collect()
2326
}
2427

2528
fn check_dim_has_measures(&self, dim: &DimensionSymbol) -> bool {

0 commit comments

Comments
 (0)