Skip to content

Commit cd7490e

Browse files
authored
fix(schema-compiler): Fix incorrect sql generation for view queries referencing measures from joined cubes (#8991)
* fix(schema-compiler): Fix incorrect sql generation for view queries referencing measures from joined cubes * add tests
1 parent 0483c9d commit cd7490e

File tree

4 files changed

+136
-2
lines changed

4 files changed

+136
-2
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1521,13 +1521,13 @@ export class BaseQuery {
15211521
this.queryCache
15221522
);
15231523
if (m.expressionName && !collectedMeasures.length && !m.isMemberExpression) {
1524-
throw new UserError(`Subquery dimension ${m.expressionName} should reference at least one measure`);
1524+
throw new UserError(`Subquery measure ${m.expressionName} should reference at least one member`);
15251525
}
15261526
if (!collectedMeasures.length && m.isMemberExpression && m.query.allCubeNames.length > 1 && m.measureSql() === 'COUNT(*)') {
15271527
const cubeName = m.expressionCubeName ? `\`${m.expressionCubeName}\` ` : '';
15281528
throw new UserError(`The query contains \`COUNT(*)\` expression but cube/view ${cubeName}is missing \`count\` measure`);
15291529
}
1530-
return [m.measure, collectedMeasures];
1530+
return [typeof m.measure === 'string' ? m.measure : `${m.measure.cubeName}.${m.measure.name}`, collectedMeasures];
15311531
}));
15321532
}
15331533

packages/cubejs-testing/birdbox-fixtures/postgresql/schema/Orders.js

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,40 @@ cube(`Orders`, {
1010
UNION ALL
1111
select 5 as id, 600 as amount, 'shipped' status, '2024-01-05'::timestamptz created_at
1212
`,
13+
joins: {
14+
OrderItems: {
15+
relationship: 'one_to_many',
16+
sql: `${CUBE.id} = ${OrderItems.order_id}`
17+
},
18+
},
1319
measures: {
1420
count: {
1521
type: `count`,
1622
},
23+
orderCount: {
24+
type: `count_distinct`,
25+
sql: `CASE WHEN ${Orders.status} = 'shipped' THEN ${CUBE}.id END`
26+
},
27+
netCollectionCompleted: {
28+
type: `sum`,
29+
sql: `CASE WHEN ${Orders.status} = 'shipped' THEN ${CUBE}.amount END`
30+
},
31+
arpu: {
32+
type: `number`,
33+
sql: `1.0 * ${netCollectionCompleted} / ${orderCount}`
34+
},
35+
refundRate: {
36+
type: `number`,
37+
sql: `1.0 * ${refundOrdersCount} / ${overallOrders}`
38+
},
39+
refundOrdersCount: {
40+
type: `count_distinct`,
41+
sql: `CASE WHEN ${Orders.status} = 'refunded' THEN ${CUBE}.id END`
42+
},
43+
overallOrders: {
44+
type: `count_distinct`,
45+
sql: `CASE WHEN ${Orders.status} != 'cancelled' THEN ${CUBE}.id END`
46+
},
1747
totalAmount: {
1848
sql: `amount`,
1949
type: `sum`,
@@ -96,10 +126,75 @@ cube(`Orders`, {
96126
},
97127
});
98128

129+
cube(`OrderItems`, {
130+
sql: `
131+
select 1 as id, 1 as order_id, 'Phone' AS name, 'Electronics' AS type, '2024-01-01'::timestamptz created_at
132+
UNION ALL
133+
select 2 as id, 2 as order_id, 'Keyboard' AS name, 'Electronics' AS type, '2024-01-02'::timestamptz created_at
134+
UNION ALL
135+
select 3 as id, 3 as order_id, 'Glass' AS name, 'Home' AS type, '2024-01-03'::timestamptz created_at
136+
UNION ALL
137+
select 4 as id, 4 as order_id, 'Lamp' AS name, 'Home' AS type, '2024-01-04'::timestamptz created_at
138+
UNION ALL
139+
select 5 as id, 5 as order_id, 'Pen' AS name, 'Office' AS type, '2024-01-05'::timestamptz created_at
140+
`,
141+
measures: {
142+
count: {
143+
type: `count`,
144+
},
145+
},
146+
dimensions: {
147+
id: {
148+
sql: `id`,
149+
type: `number`,
150+
primaryKey: true,
151+
public: true,
152+
},
153+
154+
order_id: {
155+
sql: `order_id`,
156+
type: `number`,
157+
public: true,
158+
},
159+
160+
name: {
161+
sql: `name`,
162+
type: `string`,
163+
public: true,
164+
},
165+
166+
type: {
167+
sql: `type`,
168+
type: `string`,
169+
public: true,
170+
},
171+
172+
createdAt: {
173+
sql: `created_at`,
174+
type: `time`,
175+
public: true,
176+
}
177+
},
178+
});
179+
99180
view(`OrdersView`, {
100181
cubes: [{
101182
joinPath: Orders,
102183
includes: `*`,
103184
excludes: [`toRemove`]
104185
}]
105186
});
187+
188+
view(`OrdersItemsPrefixView`, {
189+
cubes: [{
190+
joinPath: Orders,
191+
includes: `*`,
192+
excludes: [`toRemove`],
193+
prefix: true
194+
},
195+
{
196+
joinPath: Orders.OrderItems,
197+
includes: `*`,
198+
prefix: true
199+
}]
200+
});

packages/cubejs-testing/test/__snapshots__/smoke-cubesql.test.ts.snap

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,17 @@ Array [
111111
]
112112
`;
113113

114+
exports[`SQL API Postgres (Data) query views with deep joins: query-view-deep-joins 1`] = `
115+
Array [
116+
Object {
117+
"Calculation_1055547778125863": 2024-01-01T00:00:00.000Z,
118+
"Orders_arpu": null,
119+
"Orders_netCollectionCompleted": null,
120+
"Orders_refundRate": 0,
121+
},
122+
]
123+
`;
124+
114125
exports[`SQL API Postgres (Data) query with intervals (SQL PUSH DOWN): timestamps 1`] = `
115126
Array [
116127
Object {

packages/cubejs-testing/test/smoke-cubesql.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,5 +471,33 @@ describe('SQL API', () => {
471471
const res = await connection.query(query);
472472
expect(res.rows).toMatchSnapshot('timestamps');
473473
});
474+
475+
test('query views with deep joins', async () => {
476+
const query = `
477+
SELECT
478+
CAST(
479+
DATE_TRUNC(
480+
'MONTH',
481+
CAST(
482+
CAST("OrdersItemsPrefixView"."Orders_createdAt" AS DATE) AS TIMESTAMP
483+
)
484+
) AS DATE
485+
) AS "Calculation_1055547778125863",
486+
SUM("OrdersItemsPrefixView"."Orders_arpu") AS "Orders_arpu",
487+
SUM("OrdersItemsPrefixView"."Orders_refundRate") AS "Orders_refundRate",
488+
SUM("OrdersItemsPrefixView"."Orders_netCollectionCompleted") AS "Orders_netCollectionCompleted"
489+
FROM
490+
OrdersItemsPrefixView
491+
WHERE
492+
OrdersItemsPrefixView.Orders_createdAt >= '2024-01-01T00:00:00.000'
493+
AND OrdersItemsPrefixView.Orders_createdAt <= '2024-12-31T23:59:59.999'
494+
AND (OrdersItemsPrefixView.Orders_status IN ('shipped', 'processed'))
495+
AND (OrdersItemsPrefixView.OrderItems_type IN ('Electronics', 'Home'))
496+
GROUP BY 1
497+
`;
498+
499+
const res = await connection.query(query);
500+
expect(res.rows).toMatchSnapshot('query-view-deep-joins');
501+
});
474502
});
475503
});

0 commit comments

Comments
 (0)