From 905e87366806e27c14f34eb4f822b46436ea3db9 Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Fri, 31 Jan 2025 13:20:08 +0200 Subject: [PATCH] =?UTF-8?q?cached=20granularityHierarchies()=20for=C2=A0qu?= =?UTF-8?q?ery=20timezone?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/adapter/BaseQuery.js | 46 ++++++++++++++++++- .../src/adapter/PreAggregations.js | 39 +++++----------- .../src/compiler/CubeEvaluator.ts | 13 ++++-- 3 files changed, 65 insertions(+), 33 deletions(-) diff --git a/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js b/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js index f7154b203e0e6..47018933422ff 100644 --- a/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js +++ b/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js @@ -26,6 +26,7 @@ import { BaseTimeDimension } from './BaseTimeDimension'; import { ParamAllocator } from './ParamAllocator'; import { PreAggregations } from './PreAggregations'; import { SqlParser } from '../parser/SqlParser'; +import { Granularity } from './Granularity'; const DEFAULT_PREAGGREGATIONS_SCHEMA = 'stb_pre_aggregations'; @@ -1379,7 +1380,47 @@ export class BaseQuery { } granularityHierarchies() { - return R.fromPairs(Object.keys(standardGranularitiesParents).map(k => [k, this.granularityParentHierarchy(k)])); + return this.cacheValue( + // If time dimension custom granularity in data model is defined without + // timezone information they are treated in query timezone. + // Because of that it's not possible to correctly precalculate + // granularities hierarchies on startup as they are specific for each timezone. + ['granularityHierarchies', this.timezone], + () => R.reduce( + (hierarchies, cube) => R.reduce( + (acc, [tdName, td]) => { + const dimensionKey = `${cube}.${tdName}`; + + // constructing standard granularities for time dimension + const standardEntries = R.fromPairs( + R.keys(standardGranularitiesParents).map(gr => [ + `${dimensionKey}.${gr}`, + standardGranularitiesParents[gr], + ]), + ); + + // If we have custom granularities in time dimension + const customEntries = td.granularities + ? R.fromPairs( + R.keys(td.granularities).map(granularityName => { + const grObj = new Granularity(this, { dimension: dimensionKey, granularity: granularityName }); + return [ + `${dimensionKey}.${granularityName}`, + [granularityName, ...standardGranularitiesParents[grObj.minGranularity()]], + ]; + }), + ) + : {}; + + return R.mergeRight(acc, standardEntries, customEntries); + }, + hierarchies, + R.toPairs(this.cubeEvaluator.timeDimensionsForCube(cube)), + ), + {}, + R.keys(this.cubeEvaluator.evaluatedCubes), + ), + ); } granularityParentHierarchy(granularity) { @@ -1646,7 +1687,8 @@ export class BaseQuery { /** * - * @param {{sql: string, on: {cubeName: string, expression: Function}, joinType: 'LEFT' | 'INNER', alias: string}} customJoin + * @param {{sql: string, on: {cubeName: string, expression: Function}, joinType: 'LEFT' | 'INNER', alias: string}} + * customJoin * @returns {JoinItem} */ customSubQueryJoin(customJoin) { diff --git a/packages/cubejs-schema-compiler/src/adapter/PreAggregations.js b/packages/cubejs-schema-compiler/src/adapter/PreAggregations.js index 64dd556e3ee2c..815c5d373b793 100644 --- a/packages/cubejs-schema-compiler/src/adapter/PreAggregations.js +++ b/packages/cubejs-schema-compiler/src/adapter/PreAggregations.js @@ -439,14 +439,14 @@ export class PreAggregations { static sortTimeDimensionsWithRollupGranularity(timeDimensions) { return timeDimensions && R.sortBy( R.prop(0), - timeDimensions.map(d => [d.expressionPath(), d.rollupGranularity(), d.granularityObj?.minGranularity()]) + timeDimensions.map(d => [d.expressionPath(), d.rollupGranularity()]) ) || []; } static timeDimensionsAsIs(timeDimensions) { return timeDimensions && R.sortBy( R.prop(0), - timeDimensions.map(d => [d.expressionPath(), d.resolvedGranularity(), d.granularityObj?.minGranularity()]), + timeDimensions.map(d => [d.expressionPath(), d.resolvedGranularity()]), ) || []; } @@ -536,9 +536,7 @@ export class PreAggregations { backAlias(references.sortedTimeDimensions || sortTimeDimensions(references.timeDimensions)); const qryTimeDimensions = references.allowNonStrictDateRangeMatch ? transformedQuery.timeDimensions - : transformedQuery.sortedTimeDimensions.map(t => t.slice(0, 2)); - // slices above/below are used to exclude granularity on 3rd position returned from sortTimeDimensionsWithRollupGranularity() - const qryDimensions = transformedQuery.timeDimensions.map(t => t.slice(0, 2)); + : transformedQuery.sortedTimeDimensions; const backAliasMeasures = backAlias(references.measures); const backAliasSortedDimensions = backAlias(references.sortedDimensions || references.dimensions); const backAliasDimensions = backAlias(references.dimensions); @@ -550,7 +548,7 @@ export class PreAggregations { R.equals(qryTimeDimensions, refTimeDimensions) ) && ( transformedQuery.isAdditive || - R.equals(qryDimensions, refTimeDimensions) + R.equals(transformedQuery.timeDimensions, refTimeDimensions) ) && ( filterDimensionsSingleValueEqual && references.dimensions.length === filterDimensionsSingleValueEqual.size && @@ -566,28 +564,13 @@ export class PreAggregations { /** * Expand granularity into array of granularity hierarchy. - * resolvedGranularity might be a custom granularity name, in this case - * we insert it into all expanded hierarchies using its minimal granularity passed - * as minGranularity param - * @param {string} resolvedGranularity - * @param {string} minGranularity + * @param {string} granularity Granularity full path in the form of `cube.timeDimension.granularity` * @returns {Array} */ - const expandGranularity = (resolvedGranularity, minGranularity) => { - if (!resolvedGranularity) { - return []; - } - - let predefinedHierarchy = transformedQuery.granularityHierarchies[resolvedGranularity]; - - if (predefinedHierarchy) { - return predefinedHierarchy; - } - - predefinedHierarchy = transformedQuery.granularityHierarchies[minGranularity]; - // Custom granularity should be the first in list for exact match hit - return [resolvedGranularity, ...predefinedHierarchy]; - }; + const expandGranularity = (granularity) => ( + transformedQuery.granularityHierarchies[granularity] || + [granularity] + ); /** * Determine whether time dimensions match to the window granularity or not. @@ -619,8 +602,8 @@ export class PreAggregations { * @returns {Array>} */ const expandTimeDimension = (timeDimension) => { - const [dimension, resolvedGranularity, minGranularity] = timeDimension; - return expandGranularity(resolvedGranularity, minGranularity) + const [dimension, resolvedGranularity] = timeDimension; + return expandGranularity(`${dimension}.${resolvedGranularity}`) .map((newGranularity) => [dimension, newGranularity]); }; diff --git a/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts b/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts index b5bf5bbb29535..56dbd50f12d1c 100644 --- a/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts +++ b/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts @@ -448,8 +448,8 @@ export class CubeEvaluator extends CubeSymbols { public timeDimensionPathsForCube(cube: any) { return R.compose( - R.map(nameToDefinition => `${cube}.${nameToDefinition[0]}`), - R.toPairs, + R.map(dimName => `${cube}.${dimName}`), + R.keys, // @ts-ignore R.filter((d: any) => d.type === 'time') // @ts-ignore @@ -460,12 +460,19 @@ export class CubeEvaluator extends CubeSymbols { return this.cubeFromPath(cube).measures || {}; } + public timeDimensionsForCube(cube) { + return R.filter( + (d: any) => d.type === 'time', + this.cubeFromPath(cube).dimensions || {} + ); + } + public preAggregationsForCube(path: string) { return this.cubeFromPath(path).preAggregations || {}; } /** - * Returns pre-aggregations filtered by the spcified selector. + * Returns pre-aggregations filtered by the specified selector. * @param {{ * scheduled: boolean, * dataSource: Array,