Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/content/product/_meta.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export default {
"introduction": "Introduction",
"getting-started": "Getting started",
"configuration": "Data Sources",
"configuration": "Configuration",
"data-modeling": "Data modeling",
"exploration": "Explore & Analyze",
"presentation": "Present & Share",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,18 @@ from running.
- [Monitoring integrations][ref-monitoring] are also suspended, which prevents
the export of metrics and logs.

When a deployment is resumed from auto-suspension:
When a deployment is [resumed](#resuming-a-suspended-deployment) from auto-suspension:

- [Data model][ref-data-model] compilation would need to be done from scratch.
It applies to all tenants in case [multitenancy][ref-multitenancy] is set up.
Consequently, one or more queries served after a deployment is resumed from
Consequently, one or more requests served after a deployment is resumed from
auto-suspension are likely to have suboptimal performance.
- [Refresh worker][ref-refresh-worker] would need to refresh all
pre-aggregations that became stale during the suspension, competing for the
query queue with API instances and compromising the end-user experience.
- Until the deployment is fully resumed, the requests will be served by transient,
on-demand API instances with limited performance. There are no guarantees for the
[version][ref-cube-version] of Cube these API instances will be running.

## Configuration

Expand Down Expand Up @@ -120,4 +123,5 @@ response times to be significantly longer than usual.
[ref-multitenancy]: /product/configuration/multitenancy
[self-effects]: #effects-on-experience
[ref-refresh-worker]: /product/deployment#refresh-worker
[ref-sls]: /product/apis-integrations/semantic-layer-sync#on-schedule
[ref-sls]: /product/apis-integrations/semantic-layer-sync#on-schedule
[ref-cube-version]: /product/administration/deployment/deployments#cube-version
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
"postcss": "^8.2.8",
"prettier": "^2.0.5",
"rimraf": "^3.0.2",
"rollup": "2.53.1",
"rollup": "2.80.0",
"rollup-plugin-peer-deps-external": "^2.2.4",
"rollup-plugin-tsconfig-paths": "^1.5.2",
"typescript": "~5.2.2"
Expand Down
38 changes: 25 additions & 13 deletions packages/cubejs-client-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ export type LoadMethodOptions = {
* Function that receives `ProgressResult` on each `Continue wait` message.
*/
progressCallback?(result: ProgressResult): void;
/**
* Server-side cache policy for query execution. Does not control client-side caching.
*/
cache?: CacheMode;
/**
* AbortSignal to cancel requests
*/
Expand Down Expand Up @@ -113,10 +117,6 @@ export type CubeSqlOptions = LoadMethodOptions & {
* Query timeout in milliseconds
*/
timeout?: number;
/**
* Cache mode for query execution
*/
cache?: CacheMode;
};

export type CubeSqlSchemaColumn = {
Expand Down Expand Up @@ -577,13 +577,20 @@ class CubeApi {
*/
public load<QueryType extends DeeplyReadonly<Query | Query[]>>(query: QueryType, options?: LoadMethodOptions, callback?: CallableFunction, responseFormat: ResponseFormat = 'default') {
[query, options] = this.prepareQueryOptions(query, options, responseFormat);

const params: Record<string, unknown> = {
query,
queryType: 'multi',
signal: options?.signal,
baseRequestId: options?.baseRequestId,
};

if (options?.cache) {
params.cache = options.cache;
}

return this.loadMethod(
() => this.request('load', {
query,
queryType: 'multi',
signal: options?.signal,
baseRequestId: options?.baseRequestId,
}),
() => this.request('load', params),
(response: any) => this.loadResponseInternal(response, options),
options,
callback
Expand Down Expand Up @@ -726,14 +733,19 @@ class CubeApi {
public cubeSql(sqlQuery: string, options?: CubeSqlOptions, callback?: LoadMethodCallback<CubeSqlResult>): Promise<CubeSqlResult> | UnsubscribeObj {
return this.loadMethod(
() => {
const request = this.request('cubesql', {
const cubesqlParams: Record<string, unknown> = {
query: sqlQuery,
cache: options?.cache,
method: 'POST',
signal: options?.signal,
fetchTimeout: options?.timeout,
baseRequestId: options?.baseRequestId,
});
};

if (options?.cache) {
cubesqlParams.cache = options.cache;
}

const request = this.request('cubesql', cubesqlParams);

return request;
},
Expand Down
40 changes: 40 additions & 0 deletions packages/cubejs-client-core/test/CubeApi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,46 @@ describe('CubeApi Load', () => {
expect(res.rawData()).toEqual(DescriptiveQueryResponse.results[0].data);
});

test('simple query + { cache: "no-cache" }', async () => {
const requestSpy = jest.spyOn(HttpTransport.prototype, 'request').mockImplementation(() => ({
subscribe: (cb) => Promise.resolve(cb({
status: 200,
text: () => Promise.resolve(JSON.stringify(DescriptiveQueryResponse)),
json: () => Promise.resolve(DescriptiveQueryResponse)
} as any,
async () => undefined as any))
}));

const cubeApi = new CubeApi('token', {
apiUrl: 'http://localhost:4000/cubejs-api/v1',
});

const res = await cubeApi.load(DescriptiveQueryRequest as Query, { cache: 'no-cache' });
expect(res).toBeInstanceOf(ResultSet);
expect(requestSpy).toHaveBeenCalled();
expect(requestSpy.mock.calls[0]?.[1]?.cache).toBe('no-cache');
});

test('simple query + { cache: "must-revalidate" }', async () => {
const requestSpy = jest.spyOn(HttpTransport.prototype, 'request').mockImplementation(() => ({
subscribe: (cb) => Promise.resolve(cb({
status: 200,
text: () => Promise.resolve(JSON.stringify(DescriptiveQueryResponse)),
json: () => Promise.resolve(DescriptiveQueryResponse)
} as any,
async () => undefined as any))
}));

const cubeApi = new CubeApi('token', {
apiUrl: 'http://localhost:4000/cubejs-api/v1',
});

const res = await cubeApi.load(DescriptiveQueryRequest as Query, { cache: 'must-revalidate' });
expect(res).toBeInstanceOf(ResultSet);
expect(requestSpy).toHaveBeenCalled();
expect(requestSpy.mock.calls[0]?.[1]?.cache).toBe('must-revalidate');
});

test('2 queries + compact response format', async () => {
// Create a spy on the request method
jest.spyOn(HttpTransport.prototype, 'request').mockImplementation(() => ({
Expand Down
9 changes: 9 additions & 0 deletions packages/cubejs-client-react/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ declare module '@cubejs-client/react' {
BinaryOperator,
DeeplyReadonly,
QueryRecordType,
CacheMode,
} from '@cubejs-client/core';

type CubeProviderOptions = {
Expand Down Expand Up @@ -139,6 +140,10 @@ declare module '@cubejs-client/react' {
* `CubeApi` instance to use
*/
cubeApi?: CubeApi;
/**
* Server-side cache policy for query execution. Does not control client-side caching.
*/
cache?: CacheMode;
/**
* Output of this function will be rendered by the `QueryRenderer`
*/
Expand Down Expand Up @@ -477,6 +482,10 @@ declare module '@cubejs-client/react' {
* If enabled, all members of the 'number' type will be automatically converted to numerical values on the client side
*/
castNumerics?: boolean;
/**
* Server-side cache policy for query execution. Does not control client-side caching.
*/
cache?: CacheMode;
};

type UseCubeQueryResult<TQuery, TData> = {
Expand Down
23 changes: 17 additions & 6 deletions packages/cubejs-client-react/src/QueryRenderer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ export default class QueryRenderer extends React.Component {
queries: null,
loadSql: null,
updateOnlyOnStateChange: false,
resetResultSetOnChange: true
resetResultSetOnChange: true,
cache: null,
};

// @deprecated use `isQueryPresent` from `@cubejs-client/core`
Expand Down Expand Up @@ -71,7 +72,7 @@ export default class QueryRenderer extends React.Component {
}

load(query) {
const { resetResultSetOnChange } = this.props;
const { resetResultSetOnChange, cache } = this.props;
this.setState({
isLoading: true,
error: null,
Expand All @@ -81,6 +82,12 @@ export default class QueryRenderer extends React.Component {
const { loadSql } = this.props;
const cubeApi = this.cubeApi();

const loadOptions = {
mutexObj: this.mutexObj,
mutexKey: 'query',
...(cache ? { cache } : {}),
};

if (query && isQueryPresent(query)) {
if (loadSql === 'only') {
cubeApi.sql(query, { mutexObj: this.mutexObj, mutexKey: 'sql' })
Expand All @@ -93,7 +100,7 @@ export default class QueryRenderer extends React.Component {
} else if (loadSql) {
Promise.all([
cubeApi.sql(query, { mutexObj: this.mutexObj, mutexKey: 'sql' }),
cubeApi.load(query, { mutexObj: this.mutexObj, mutexKey: 'query' })
cubeApi.load(query, loadOptions)
]).then(([sqlQuery, resultSet]) => this.setState({
sqlQuery, resultSet, error: null, isLoading: false
}))
Expand All @@ -103,7 +110,7 @@ export default class QueryRenderer extends React.Component {
isLoading: false
}));
} else {
cubeApi.load(query, { mutexObj: this.mutexObj, mutexKey: 'query' })
cubeApi.load(query, loadOptions)
.then(resultSet => this.setState({ resultSet, error: null, isLoading: false }))
.catch(error => this.setState({
...(resetResultSetOnChange ? { resultSet: null } : {}),
Expand All @@ -116,15 +123,19 @@ export default class QueryRenderer extends React.Component {

loadQueries(queries) {
const cubeApi = this.cubeApi();
const { resetResultSetOnChange } = this.props;
const { resetResultSetOnChange, cache } = this.props;
this.setState({
isLoading: true,
...(resetResultSetOnChange ? { resultSet: null } : {}),
error: null
});

const resultPromises = Promise.all(toPairs(queries).map(
([name, query]) => cubeApi.load(query, { mutexObj: this.mutexObj, mutexKey: name }).then(r => [name, r])
([name, query]) => cubeApi.load(query, {
mutexObj: this.mutexObj,
mutexKey: name,
...(cache ? { cache } : {}),
}).then(r => [name, r])
));

resultPromises
Expand Down
4 changes: 3 additions & 1 deletion packages/cubejs-client-react/src/hooks/cube-query.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ export function useCubeQuery(query, options = {}) {
mutexObj: mutexRef.current,
mutexKey: 'query',
progressCallback,
castNumerics: Boolean(typeof options.castNumerics === 'boolean' ? options.castNumerics : context?.options?.castNumerics)
castNumerics: Boolean(typeof options.castNumerics === 'boolean' ? options.castNumerics : context?.options?.castNumerics),
...(options.cache ? { cache: options.cache } : {}),
});

setResultSet(response);
Expand Down Expand Up @@ -85,6 +86,7 @@ export function useCubeQuery(query, options = {}) {
mutexObj: mutexRef.current,
mutexKey: 'query',
progressCallback,
...(options.cache ? { cache: options.cache } : {}),
},
(e, result) => {
if (e) {
Expand Down
Loading
Loading