Skip to content

Commit 4a67346

Browse files
committed
feat: Show used pre-aggregations and match rollup results in Playground
1 parent 868941f commit 4a67346

File tree

11 files changed

+309
-103
lines changed

11 files changed

+309
-103
lines changed

packages/cubejs-api-gateway/index.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,10 @@ class ApiGateway {
274274
try {
275275
query = this.parseQueryParam(query);
276276
const normalizedQuery = await this.queryTransformer(normalizeQuery(query), context);
277-
const sqlQuery = await this.getCompilerApi(context).getSql(coerceForSqlQuery(normalizedQuery, context));
277+
const sqlQuery = await this.getCompilerApi(context).getSql(
278+
coerceForSqlQuery(normalizedQuery, context),
279+
{ includeDebugInfo: process.env.NODE_ENV !== 'production' }
280+
);
278281
res({
279282
sql: sqlQuery
280283
});
@@ -323,7 +326,10 @@ class ApiGateway {
323326
query: normalizedQuery,
324327
data: transformData(aliasToMemberNameMap, flattenAnnotation, response.data),
325328
lastRefreshTime: response.lastRefreshTime && response.lastRefreshTime.toISOString(),
326-
refreshKeyValues: process.env.NODE_ENV === 'production' ? undefined : response.refreshKeyValues,
329+
...(process.env.NODE_ENV === 'production' ? undefined : {
330+
refreshKeyValues: response.refreshKeyValues,
331+
usedPreAggregations: response.usedPreAggregations
332+
}),
327333
annotation
328334
});
329335
} catch (e) {

packages/cubejs-playground/src/ChartContainer.jsx

Lines changed: 3 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { QueryRenderer } from '@cubejs-client/react';
1111
import sqlFormatter from "sql-formatter";
1212
import PropTypes from 'prop-types';
1313
import PrismCode from './PrismCode';
14+
import CachePane from './components/CachePane';
1415
import { playgroundAction } from './events';
1516

1617
export const frameworks = [{
@@ -296,47 +297,9 @@ class ChartContainer extends React.Component {
296297
);
297298
} else if (showCode === 'cache') {
298299
return (
299-
<QueryRenderer
300-
loadSql
301-
query={{ ...query, renewQuery: true }}
300+
<CachePane
301+
query={query}
302302
cubejsApi={cubejsApi}
303-
render={
304-
({ sqlQuery, resultSet: rs }) => (
305-
<div>
306-
<h3>
307-
Last Refresh Time:
308-
{rs && rs.loadResponse.lastRefreshTime}
309-
</h3>
310-
<Table
311-
loading={!sqlQuery}
312-
pagination={false}
313-
columns={[
314-
{
315-
title: 'Refresh Key SQL',
316-
key: 'refreshKey',
317-
render: (text, record) => <PrismCode code={sqlFormatter.format(record[0])} />,
318-
},
319-
{
320-
title: 'Value',
321-
key: 'value',
322-
render: (text, record) => (
323-
<PrismCode
324-
code={
325-
rs && rs.loadResponse.refreshKeyValues
326-
&& JSON.stringify(rs.loadResponse.refreshKeyValues[
327-
sqlQuery.sqlQuery.sql.cacheKeyQueries.queries.indexOf(record)
328-
], null, 2)
329-
}
330-
/>
331-
),
332-
}
333-
]}
334-
dataSource={
335-
sqlQuery && sqlQuery.sqlQuery.sql && sqlQuery.sqlQuery.sql.cacheKeyQueries.queries
336-
}
337-
/>
338-
</div>
339-
)}
340303
/>
341304
);
342305
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import React from 'react';
2+
import {
3+
Table, Icon, Tabs
4+
} from 'antd';
5+
import { QueryRenderer } from '@cubejs-client/react';
6+
import sqlFormatter from "sql-formatter";
7+
import PropTypes from 'prop-types';
8+
import PrismCode from '../PrismCode';
9+
10+
const CachePane = ({ query, cubejsApi }) => (
11+
<QueryRenderer
12+
loadSql
13+
query={{ ...query, renewQuery: true }}
14+
cubejsApi={cubejsApi}
15+
render={
16+
({ sqlQuery, resultSet: rs }) => (
17+
<Tabs
18+
defaultActiveKey="refreshKeys"
19+
tabBarExtraContent={(
20+
<span>
21+
Last Refresh Time:&nbsp;
22+
<b>{rs && rs.loadResponse.lastRefreshTime}</b>
23+
</span>
24+
)}
25+
>
26+
<Tabs.TabPane tab="Refresh Keys" key="refreshKeys">
27+
<Table
28+
loading={!sqlQuery}
29+
pagination={false}
30+
scroll={{ x: true }}
31+
columns={[
32+
{
33+
title: 'Refresh Key SQL',
34+
key: 'refreshKey',
35+
render: (text, record) => <PrismCode code={sqlFormatter.format(record[0])} />,
36+
},
37+
{
38+
title: 'Value',
39+
key: 'value',
40+
render: (text, record) => (
41+
<PrismCode
42+
code={
43+
rs && rs.loadResponse.refreshKeyValues
44+
&& JSON.stringify(rs.loadResponse.refreshKeyValues[
45+
sqlQuery.sqlQuery.sql.cacheKeyQueries.queries.indexOf(record)
46+
], null, 2)
47+
}
48+
/>
49+
),
50+
}
51+
]}
52+
dataSource={
53+
sqlQuery && sqlQuery.sqlQuery.sql && sqlQuery.sqlQuery.sql.cacheKeyQueries.queries
54+
}
55+
/>
56+
</Tabs.TabPane>
57+
<Tabs.TabPane tab="Pre-aggregations" key="preAggregations">
58+
<Table
59+
loading={!sqlQuery}
60+
pagination={false}
61+
scroll={{ x: true }}
62+
columns={[
63+
{
64+
title: 'Table Name',
65+
key: 'tableName',
66+
dataIndex: 'tableName',
67+
render: (text) => <b>{text}</b>
68+
},
69+
{
70+
title: 'Refresh Key SQL',
71+
key: 'refreshKey',
72+
dataIndex: 'invalidateKeyQueries',
73+
render: (refreshKeyQueries) => refreshKeyQueries
74+
.map(q => <PrismCode key={q[0]} code={sqlFormatter.format(q[0])} />),
75+
},
76+
{
77+
title: 'Refresh Key Value',
78+
key: 'value',
79+
render: (text, record) => rs && rs.loadResponse.usedPreAggregations
80+
&& rs.loadResponse.usedPreAggregations[record.tableName].refreshKeyValues.map(k => (
81+
<PrismCode
82+
key={JSON.stringify(k)}
83+
code={JSON.stringify(k, null, 2)}
84+
/>
85+
)),
86+
}
87+
]}
88+
dataSource={
89+
sqlQuery && sqlQuery.sqlQuery.sql && sqlQuery.sqlQuery.sql.preAggregations
90+
}
91+
/>
92+
</Tabs.TabPane>
93+
<Tabs.TabPane tab="Rollup Match Results" key="rollupMatchResults">
94+
<Table
95+
loading={!sqlQuery}
96+
pagination={false}
97+
scroll={{ x: true }}
98+
columns={[
99+
{
100+
title: 'Rollup Table Name',
101+
key: 'tableName',
102+
dataIndex: 'tableName',
103+
render: (text) => <b>{text}</b>
104+
},
105+
{
106+
title: 'Rollup Definition',
107+
key: 'rollup',
108+
dataIndex: 'references',
109+
render: (text) => <PrismCode code={JSON.stringify(text, null, 2)} />,
110+
},
111+
{
112+
title: 'Can Be Used',
113+
key: 'canUsePreAggregation',
114+
dataIndex: 'canUsePreAggregation',
115+
render: (text) => (
116+
text ? <Icon type="check" style={{ color: '#52c41a', fontSize: '2em' }}/>
117+
: <Icon type="close" style={{ color: '#c2371b', fontSize: '2em' }}/>
118+
),
119+
}
120+
]}
121+
dataSource={
122+
sqlQuery && sqlQuery.sqlQuery.sql && sqlQuery.sqlQuery.sql.rollupMatchResults
123+
}
124+
/>
125+
</Tabs.TabPane>
126+
</Tabs>
127+
)}
128+
/>
129+
);
130+
131+
CachePane.propTypes = {
132+
query: PropTypes.object.isRequired,
133+
cubejsApi: PropTypes.object.isRequired
134+
};
135+
136+
export default CachePane;

packages/cubejs-playground/src/prism.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ pre[class*="language-"] {
5555

5656
:not(pre) > code[class*="language-"],
5757
pre[class*="language-"] {
58-
background: #fff;
58+
background: none;
5959
}
6060

6161
/* Inline code */

packages/cubejs-query-orchestrator/orchestrator/PreAggregations.js

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -187,15 +187,15 @@ class PreAggregationLoader {
187187
return this.loadPreAggregationWithKeys();
188188
}
189189
} else {
190-
return this.loadPreAggregationWithKeys();
190+
return {
191+
targetTableName: await this.loadPreAggregationWithKeys(),
192+
refreshKeyValues: await this.getInvalidationKeyValues()
193+
};
191194
}
192195
}
193196

194197
async loadPreAggregationWithKeys() {
195-
const invalidationKeys = await Promise.all(
196-
(this.preAggregation.invalidateKeyQueries || [])
197-
.map(keyQuery => this.loadCache.keyQueryResult(keyQuery))
198-
);
198+
const invalidationKeys = await this.getInvalidationKeyValues();
199199
const contentVersion = version([this.preAggregation.loadSql, invalidationKeys]);
200200
const structureVersion = version(this.preAggregation.loadSql);
201201

@@ -275,6 +275,13 @@ class PreAggregationLoader {
275275
return this.targetTableName(versionEntry);
276276
}
277277

278+
getInvalidationKeyValues() {
279+
return Promise.all(
280+
(this.preAggregation.invalidateKeyQueries || [])
281+
.map(keyQuery => this.loadCache.keyQueryResult(keyQuery))
282+
);
283+
}
284+
278285
scheduleRefresh(invalidationKeys, newVersionEntry) {
279286
this.logger('Refreshing pre-aggregation content', { preAggregation: this.preAggregation });
280287
this.executeInQueue(invalidationKeys, 0, newVersionEntry)
@@ -429,9 +436,10 @@ class PreAggregations {
429436
loadCache,
430437
{ waitForRenew: queryBody.renewQuery }
431438
);
432-
const preAggregationPromise = () => loader.loadPreAggregation().then(async tempTableName => {
433-
await this.addTableUsed(tempTableName);
434-
return [p.tableName, tempTableName];
439+
const preAggregationPromise = () => loader.loadPreAggregation().then(async targetTableName => {
440+
const usedPreAggregation = typeof targetTableName === 'string' ? { targetTableName } : targetTableName;
441+
await this.addTableUsed(usedPreAggregation.targetTableName);
442+
return [p.tableName, usedPreAggregation];
435443
});
436444
return preAggregationPromise().then(res => preAggregationsTablesToTempTables.concat([res]));
437445
}).reduce((promise, fn) => promise.then(fn), Promise.resolve([]));

packages/cubejs-query-orchestrator/orchestrator/QueryCache.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ class QueryCache {
9090
static replacePreAggregationTableNames(queryAndParams, preAggregationsTablesToTempTables) {
9191
const [keyQuery, params] = Array.isArray(queryAndParams) ? queryAndParams : [queryAndParams, []];
9292
const replacedKeqQuery = preAggregationsTablesToTempTables.reduce(
93-
(query, [tableName, tempTable]) => QueryCache.replaceAll(tableName, tempTable, query),
93+
(query, [tableName, { targetTableName }]) => QueryCache.replaceAll(tableName, targetTableName, query),
9494
keyQuery
9595
);
9696
return Array.isArray(queryAndParams) ? [replacedKeqQuery, params] : replacedKeqQuery;

packages/cubejs-query-orchestrator/orchestrator/QueryOrchestrator.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
const R = require('ramda');
12
const QueryCache = require('./QueryCache');
23
const PreAggregations = require('./PreAggregations');
34

@@ -33,9 +34,15 @@ class QueryOrchestrator {
3334

3435
async fetchQuery(queryBody) {
3536
return this.preAggregations.loadAllPreAggregationsIfNeeded(queryBody)
36-
.then(preAggregationsTablesToTempTables => this.queryCache.cachedQueryResult(
37-
queryBody, preAggregationsTablesToTempTables
38-
));
37+
.then(async preAggregationsTablesToTempTables => {
38+
const result = await this.queryCache.cachedQueryResult(
39+
queryBody, preAggregationsTablesToTempTables
40+
);
41+
return {
42+
...result,
43+
usedPreAggregations: R.fromPairs(preAggregationsTablesToTempTables)
44+
};
45+
});
3946
}
4047

4148
async queryStage(queryBody) {
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/* globals describe, it, should */
2+
const QueryOrchestrator = require('../orchestrator/QueryOrchestrator');
3+
4+
class MockDriver {
5+
constructor() {
6+
this.tables = [];
7+
}
8+
9+
async query(query) {
10+
return [query];
11+
}
12+
13+
async getTablesQuery(schema) {
14+
return this.tables.map(t => ({ table_name: t.replace(`${schema}.`, '') }));
15+
}
16+
17+
async createSchemaIfNotExists(schema) {
18+
this.schema = schema;
19+
return null;
20+
}
21+
22+
async loadPreAggregationIntoTable(preAggregationTableName) {
23+
this.tables.push(preAggregationTableName);
24+
}
25+
}
26+
27+
describe('QueryOrchestrator', () => {
28+
const mockDriver = new MockDriver();
29+
const queryOrchestrator = new QueryOrchestrator(
30+
'TEST', async () => mockDriver, (msg, params) => console.log(msg, params)
31+
);
32+
33+
it('basic', async () => {
34+
const query = {
35+
query: "SELECT \"orders__created_at_week\" \"orders__created_at_week\", sum(\"orders__count\") \"orders__count\" FROM (SELECT * FROM stb_pre_aggregations.orders_number_and_count20191101) as partition_union WHERE (\"orders__created_at_week\" >= ($1::timestamptz::timestamptz AT TIME ZONE 'UTC') AND \"orders__created_at_week\" <= ($2::timestamptz::timestamptz AT TIME ZONE 'UTC')) GROUP BY 1 ORDER BY 1 ASC LIMIT 10000",
36+
values: ["2019-11-01T00:00:00Z", "2019-11-30T23:59:59Z"],
37+
cacheKeyQueries: {
38+
renewalThreshold: 21600,
39+
queries: [["SELECT date_trunc('hour', (NOW()::timestamptz AT TIME ZONE 'UTC')) as current_hour", []]]
40+
},
41+
preAggregations: [{
42+
preAggregationsSchema: "stb_pre_aggregations",
43+
tableName: "stb_pre_aggregations.orders_number_and_count20191101",
44+
loadSql: ["CREATE TABLE stb_pre_aggregations.orders_number_and_count20191101 AS SELECT\n date_trunc('week', (\"orders\".created_at::timestamptz AT TIME ZONE 'UTC')) \"orders__created_at_week\", count(\"orders\".id) \"orders__count\", sum(\"orders\".number) \"orders__number\"\n FROM\n public.orders AS \"orders\"\n WHERE (\"orders\".created_at >= $1::timestamptz AND \"orders\".created_at <= $2::timestamptz) GROUP BY 1", ["2019-11-01T00:00:00Z", "2019-11-30T23:59:59Z"]],
45+
invalidateKeyQueries: [["SELECT date_trunc('hour', (NOW()::timestamptz AT TIME ZONE 'UTC')) as current_hour", []]]
46+
}],
47+
renewQuery: true
48+
};
49+
const result = await queryOrchestrator.fetchQuery(query);
50+
console.log(result.data[0]);
51+
should(result.data[0]).match(/orders_number_and_count20191101_kjypcoio_5yftl5il/);
52+
});
53+
});

packages/cubejs-query-orchestrator/test/QueryQueueTest.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
/* globals describe,it */
12
const QueryQueue = require('../orchestrator/QueryQueue');
23
const should = require('should');
3-
const redis = require('redis');
44

55
const QueryQueueTest = (name, options) => {
66
describe(`QueryQueue${name}`, function () {
@@ -16,7 +16,7 @@ const QueryQueueTest = (name, options) => {
1616
const result = query.result + delayCount;
1717
delayCount += 1;
1818
await setCancelHandler(result);
19-
return await delayFn(result, query.delay);
19+
return delayFn(result, query.delay);
2020
}
2121
},
2222
cancelHandlers: {
@@ -57,6 +57,7 @@ const QueryQueueTest = (name, options) => {
5757
await queue.executeInQueue('delay', query, { delay: 3000, result: '1' });
5858
} catch (e) {
5959
if (e.message === 'Continue wait') {
60+
// eslint-disable-next-line no-continue
6061
continue;
6162
}
6263
errorString = e.toString();
@@ -89,7 +90,7 @@ const QueryQueueTest = (name, options) => {
8990

9091
it('orphaned', async () => {
9192
for (let i = 1; i <= 4; i++) {
92-
await queue.executeInQueue('delay', `11` + i, { delay: 50, result: '' + i }, 0);
93+
await queue.executeInQueue('delay', `11` + i, { delay: 50, result: `${i}` }, 0);
9394
}
9495
cancelledQuery = null;
9596
delayCount = 0;

0 commit comments

Comments
 (0)