Skip to content

Commit 25125fa

Browse files
committed
chore(tesseract): Fix for multi stage with sub queries
1 parent 7248b7d commit 25125fa

File tree

2 files changed

+204
-0
lines changed

2 files changed

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

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)