diff --git a/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts b/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts index c58823b4ac9c6..c97d922f1c514 100644 --- a/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts +++ b/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts @@ -1,7 +1,7 @@ /* eslint-disable no-restricted-syntax */ import R from 'ramda'; -import { CubeSymbols } from './CubeSymbols'; +import { CubeSymbols, type ToString } from './CubeSymbols'; import { UserError } from './UserError'; import { BaseQuery } from '../adapter'; import type { CubeValidator } from './CubeValidator'; @@ -28,7 +28,7 @@ export type DimensionDefinition = { }; export type TimeShiftDefinition = { - timeDimension: Function, + timeDimension: (...args: Array) => ToString, interval: string, type: 'next' | 'prior', }; @@ -48,9 +48,9 @@ export type MeasureDefinition = { primaryKey?: true, drillFilters?: any, multiStage?: boolean, - groupBy?: Function, - reduceBy?: Function, - addGroupBy?: Function, + groupBy?: (...args: Array) => Array, + reduceBy?: (...args: Array) => Array, + addGroupBy?: (...args: Array) => Array, timeShift?: TimeShiftDefinition[], groupByReferences?: string[], reduceByReferences?: string[], @@ -65,6 +65,40 @@ export type PreAggregationFilters = { scheduled?: boolean, }; +type PreAggregationDefinition = { + allowNonStrictDateRangeMatch?: boolean, + timeDimensionReference?: () => ToString, + granularity: string, + timeDimensionReferences: Array<{ dimension: () => ToString, granularity: string }>, + dimensionReferences: () => Array, + segmentReferences: () => Array, + measureReferences: () => Array, + rollupReferences: () => Array, +}; + +type PreAggregationTimeDimensionReference = { + dimension: string, + granularity: string, +}; + +type PreAggregationReferences = { + allowNonStrictDateRangeMatch?: boolean, + dimensions: Array, + measures: Array, + timeDimensions: Array, + rollups: Array, +}; + +type PreAggregationInfo = { + id: string, + preAggregationName: string, + preAggregation: unknown, + cube: string, + references: PreAggregationReferences, + refreshKey: unknown, + indexesReferences: unknown, +}; + export class CubeEvaluator extends CubeSymbols { public evaluatedCubes: Record = {}; @@ -492,7 +526,7 @@ export class CubeEvaluator extends CubeSymbols { /** * Returns pre-aggregations filtered by the specified selector. */ - public preAggregations(filter: PreAggregationFilters) { + public preAggregations(filter: PreAggregationFilters): Array { const { scheduled, dataSources, cubes, preAggregationIds } = filter || {}; const idFactory = ({ cube, preAggregationName }) => `${cube}.${preAggregationName}`; @@ -510,7 +544,7 @@ export class CubeEvaluator extends CubeSymbols { ) ) )) - .map(cube => { + .flatMap(cube => { const preAggregations = this.preAggregationsForCube(cube); return Object.keys(preAggregations) .filter( @@ -547,11 +581,10 @@ export class CubeEvaluator extends CubeSymbols { }, {}) }; }); - }) - .reduce((a, b) => a.concat(b), []); + }); } - public scheduledPreAggregations() { + public scheduledPreAggregations(): Array { return this.preAggregations({ scheduled: true }); } @@ -704,8 +737,8 @@ export class CubeEvaluator extends CubeSymbols { return { cubeReferencesUsed, pathReferencesUsed, evaluatedSql }; } - protected evaluatePreAggregationReferences(cube, aggregation) { - const timeDimensions: any = []; + protected evaluatePreAggregationReferences(cube: string, aggregation: PreAggregationDefinition): PreAggregationReferences { + const timeDimensions: Array = []; if (aggregation.timeDimensionReference) { timeDimensions.push({ diff --git a/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts b/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts index 5c00edacdf94d..dffa59b9e017c 100644 --- a/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts +++ b/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts @@ -9,9 +9,11 @@ import { BaseQuery } from '../adapter'; import type { ErrorReporter } from './ErrorReporter'; +export type ToString = { toString(): string }; + interface CubeDefinition { name: string; - extends?: string; + extends?: (...args: Array) => { __cubeName: string }; measures?: Record; dimensions?: Record; segments?: Record; @@ -292,7 +294,8 @@ export class CubeSymbols { // If the hierarchy is included all members from it should be included as well // Extend `includes` with members from hierarchies that should be auto-included const cubes = type === 'dimensions' ? cube.cubes.map((it) => { - const fullPath = this.evaluateReferences(null, it.joinPath, { collectJoinHints: true }); + // TODO recheck `it.joinPath` typing + const fullPath = this.evaluateReferences(null, it.joinPath as () => ToString, { collectJoinHints: true }); const split = fullPath.split('.'); const cubeRef = split[split.length - 1]; @@ -332,7 +335,8 @@ export class CubeSymbols { const hierarchy = this.getResolvedMember(type, cubeName, hierarchyName); if (hierarchy) { - const levels = this.evaluateReferences(cubeName, this.getResolvedMember('hierarchies', cubeName, hierarchyName).levels, { originalSorting: true }); + // TODO recheck `this.getResolvedMember(...).levels` typing + const levels = this.evaluateReferences(cubeName, this.getResolvedMember('hierarchies', cubeName, hierarchyName).levels as () => Array, { originalSorting: true }); levels.forEach((level) => autoIncludeMembers.add(level)); } @@ -370,7 +374,8 @@ export class CubeSymbols { protected membersFromCubes(parentCube: CubeDefinition, cubes: any[], type: string, errorReporter: ErrorReporter, splitViews: SplitViews, memberSets: any) { return R.unnest(cubes.map(cubeInclude => { - const fullPath = this.evaluateReferences(null, cubeInclude.joinPath, { collectJoinHints: true }); + // TODO recheck `cubeInclude.joinPath` typing + const fullPath = this.evaluateReferences(null, cubeInclude.joinPath as () => ToString, { collectJoinHints: true }); const split = fullPath.split('.'); const cubeReference = split[split.length - 1]; const cubeName = cubeInclude.alias || cubeReference; @@ -453,7 +458,7 @@ export class CubeSymbols { return includes.filter(include => !excludesMap.has(include.member)); } - protected membersFromIncludeExclude(referencesFn: any, cubeName: string, type: string) { + protected membersFromIncludeExclude(referencesFn: (...args: Array) => Array, cubeName: string, type: string) { const references = this.evaluateReferences(cubeName, referencesFn); return R.unnest(references.map((ref: string) => { const path = ref.split('.'); @@ -550,7 +555,12 @@ export class CubeSymbols { return res; } - protected evaluateReferences(cube, referencesFn, options: any = {}) { + protected evaluateReferences>( + cube: string | null, + referencesFn: (...args: Array) => T, + options: { collectJoinHints?: boolean, originalSorting?: boolean } = {} + ): + T extends Array ? Array : T extends ToString ? string : string | Array { const cubeEvaluator = this; const fullPath = (joinHints, path) => { @@ -561,7 +571,7 @@ export class CubeSymbols { } }; - const arrayOrSingle = cubeEvaluator.resolveSymbolsCall(referencesFn, (name) => { + const arrayOrSingle: T = cubeEvaluator.resolveSymbolsCall(referencesFn, (name) => { const referencedCube = cubeEvaluator.symbols[name] && name || cube; const resolvedSymbol = cubeEvaluator.resolveSymbol( @@ -582,25 +592,35 @@ export class CubeSymbols { collectJoinHints: options.collectJoinHints, }); if (!Array.isArray(arrayOrSingle)) { - return arrayOrSingle.toString(); + // arrayOrSingle is of type `T`, and we just checked that it is not an array + // Which means it `T` be an object with `toString`, and result must be `string` + // For any branch of return type that can can contain just an object it's OK to return string + return arrayOrSingle.toString() as any; } - const references = arrayOrSingle.map(p => p.toString()); - return options.originalSorting ? references : R.sortBy(R.identity, references); + const references: Array = arrayOrSingle.map(p => p.toString()); + // arrayOrSingle is of type `T`, and we just checked that it is an array + // Which means that both `T` and result must be arrays + // For any branch of return type that can contain array it's OK to return array + return options.originalSorting ? references : R.sortBy(R.identity, references) as any; } public pathFromArray(array) { return array.join('.'); } - protected resolveSymbolsCall(func, nameResolver, context?: any) { + protected resolveSymbolsCall( + func: (...args: Array) => T | DynamicReference, + nameResolver: (id: string) => unknown, + context?: unknown, + ): T { const oldContext = this.resolveSymbolsCallContext; this.resolveSymbolsCallContext = context; try { // eslint-disable-next-line prefer-spread - let res = func.apply(null, this.funcArguments(func).map((id) => nameResolver(id.trim()))); + const res = func.apply(null, this.funcArguments(func).map((id) => nameResolver(id.trim()))); if (res instanceof DynamicReference) { - res = res.fn.apply(null, res.memberNames.map((id) => nameResolver(id.trim()))); + return res.fn.apply(null, res.memberNames.map((id) => nameResolver(id.trim()))); } return res; } finally { diff --git a/packages/cubejs-schema-compiler/src/compiler/DynamicReference.js b/packages/cubejs-schema-compiler/src/compiler/DynamicReference.js deleted file mode 100644 index 9a3166e91629c..0000000000000 --- a/packages/cubejs-schema-compiler/src/compiler/DynamicReference.js +++ /dev/null @@ -1,6 +0,0 @@ -export class DynamicReference { - constructor(memberNames, fn) { - this.memberNames = memberNames; - this.fn = fn; - } -} diff --git a/packages/cubejs-schema-compiler/src/compiler/DynamicReference.ts b/packages/cubejs-schema-compiler/src/compiler/DynamicReference.ts new file mode 100644 index 0000000000000..c2b4fce377cab --- /dev/null +++ b/packages/cubejs-schema-compiler/src/compiler/DynamicReference.ts @@ -0,0 +1,4 @@ +export class DynamicReference { + public constructor(public memberNames: Array, public fn: (...args: Array) => T) { + } +}