From 339399d4119de1b3dcc0ca174badf5dd2d52a896 Mon Sep 17 00:00:00 2001 From: Mikhail Cheshkov Date: Thu, 27 Mar 2025 18:09:14 +0100 Subject: [PATCH 1/4] refactor(schema-compiler): More types in CubeEvaluator --- .../src/compiler/CubeEvaluator.ts | 47 ++++++++++++++++--- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts b/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts index c58823b4ac9c6..c6f2a3f4c5f07 100644 --- a/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts +++ b/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts @@ -65,6 +65,40 @@ export type PreAggregationFilters = { scheduled?: boolean, }; +type PreAggregationDefinition = { + allowNonStrictDateRangeMatch?: boolean, + timeDimensionReference?: () => { toString(): string }, + granularity: string, + timeDimensionReferences: Array<{ dimension: () => { toString(): string }, granularity: string }>, + dimensionReferences: () => Array<{ toString(): string }>, + segmentReferences: () => Array<{ toString(): string }>, + measureReferences: () => Array<{ toString(): string }>, + rollupReferences: () => Array<{ toString(): string }>, +}; + +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({ From bb4c2705eff22b4729d459bb4332de5fd7808f2c Mon Sep 17 00:00:00 2001 From: Mikhail Cheshkov Date: Fri, 4 Apr 2025 15:35:57 +0200 Subject: [PATCH 2/4] refactor(schema-compiler): Port DynamicReference to TypeScript --- .../cubejs-schema-compiler/src/compiler/DynamicReference.js | 6 ------ .../cubejs-schema-compiler/src/compiler/DynamicReference.ts | 4 ++++ 2 files changed, 4 insertions(+), 6 deletions(-) delete mode 100644 packages/cubejs-schema-compiler/src/compiler/DynamicReference.js create mode 100644 packages/cubejs-schema-compiler/src/compiler/DynamicReference.ts 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) { + } +} From 381630e92bc3102ed52b1f194e2f031a88a1b282 Mon Sep 17 00:00:00 2001 From: Mikhail Cheshkov Date: Thu, 27 Mar 2025 18:10:13 +0100 Subject: [PATCH 3/4] refactor(schema-compiler): More types in CubeSymbols --- .../src/compiler/CubeEvaluator.ts | 8 ++-- .../src/compiler/CubeSymbols.ts | 43 +++++++++++++------ 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts b/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts index c6f2a3f4c5f07..7d5e2b71e0281 100644 --- a/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts +++ b/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts @@ -28,7 +28,7 @@ export type DimensionDefinition = { }; export type TimeShiftDefinition = { - timeDimension: Function, + timeDimension: (...args: Array) => { toString(): string }, 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<{ toString(): string }>, + reduceBy?: (...args: Array) => Array<{ toString(): string }>, + addGroupBy?: (...args: Array) => Array<{ toString(): string }>, timeShift?: TimeShiftDefinition[], groupByReferences?: string[], reduceByReferences?: string[], diff --git a/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts b/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts index 5c00edacdf94d..a859486677924 100644 --- a/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts +++ b/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts @@ -11,7 +11,7 @@ import type { ErrorReporter } from './ErrorReporter'; interface CubeDefinition { name: string; - extends?: string; + extends?: (...args: Array) => { __cubeName: string }; measures?: Record; dimensions?: Record; segments?: Record; @@ -292,7 +292,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(): string }, { collectJoinHints: true }); const split = fullPath.split('.'); const cubeRef = split[split.length - 1]; @@ -332,7 +333,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<{ toString(): string }>, { originalSorting: true }); levels.forEach((level) => autoIncludeMembers.add(level)); } @@ -370,7 +372,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(): string }, { collectJoinHints: true }); const split = fullPath.split('.'); const cubeReference = split[split.length - 1]; const cubeName = cubeInclude.alias || cubeReference; @@ -453,7 +456,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<{ toString(): string }>, cubeName: string, type: string) { const references = this.evaluateReferences(cubeName, referencesFn); return R.unnest(references.map((ref: string) => { const path = ref.split('.'); @@ -550,7 +553,11 @@ 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<{ toString(): string }> ? Array : T extends { toString(): string } ? string : string | Array { const cubeEvaluator = this; const fullPath = (joinHints, path) => { @@ -561,7 +568,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 +589,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 { From d6d638c1c38b4b781ab16f03d29578a5675ca615 Mon Sep 17 00:00:00 2001 From: Mikhail Cheshkov Date: Tue, 8 Apr 2025 14:28:55 +0200 Subject: [PATCH 4/4] refactor(schema-compiler): Extract ToString type --- .../src/compiler/CubeEvaluator.ts | 22 +++++++++---------- .../src/compiler/CubeSymbols.ts | 17 ++++++++------ 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts b/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts index 7d5e2b71e0281..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: (...args: Array) => { toString(): string }, + timeDimension: (...args: Array) => ToString, interval: string, type: 'next' | 'prior', }; @@ -48,9 +48,9 @@ export type MeasureDefinition = { primaryKey?: true, drillFilters?: any, multiStage?: boolean, - groupBy?: (...args: Array) => Array<{ toString(): string }>, - reduceBy?: (...args: Array) => Array<{ toString(): string }>, - addGroupBy?: (...args: Array) => Array<{ toString(): string }>, + groupBy?: (...args: Array) => Array, + reduceBy?: (...args: Array) => Array, + addGroupBy?: (...args: Array) => Array, timeShift?: TimeShiftDefinition[], groupByReferences?: string[], reduceByReferences?: string[], @@ -67,13 +67,13 @@ export type PreAggregationFilters = { type PreAggregationDefinition = { allowNonStrictDateRangeMatch?: boolean, - timeDimensionReference?: () => { toString(): string }, + timeDimensionReference?: () => ToString, granularity: string, - timeDimensionReferences: Array<{ dimension: () => { toString(): string }, granularity: string }>, - dimensionReferences: () => Array<{ toString(): string }>, - segmentReferences: () => Array<{ toString(): string }>, - measureReferences: () => Array<{ toString(): string }>, - rollupReferences: () => Array<{ toString(): string }>, + timeDimensionReferences: Array<{ dimension: () => ToString, granularity: string }>, + dimensionReferences: () => Array, + segmentReferences: () => Array, + measureReferences: () => Array, + rollupReferences: () => Array, }; type PreAggregationTimeDimensionReference = { diff --git a/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts b/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts index a859486677924..dffa59b9e017c 100644 --- a/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts +++ b/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts @@ -9,6 +9,8 @@ import { BaseQuery } from '../adapter'; import type { ErrorReporter } from './ErrorReporter'; +export type ToString = { toString(): string }; + interface CubeDefinition { name: string; extends?: (...args: Array) => { __cubeName: string }; @@ -293,7 +295,7 @@ export class CubeSymbols { // Extend `includes` with members from hierarchies that should be auto-included const cubes = type === 'dimensions' ? cube.cubes.map((it) => { // TODO recheck `it.joinPath` typing - const fullPath = this.evaluateReferences(null, it.joinPath as () => { toString(): string }, { collectJoinHints: true }); + const fullPath = this.evaluateReferences(null, it.joinPath as () => ToString, { collectJoinHints: true }); const split = fullPath.split('.'); const cubeRef = split[split.length - 1]; @@ -334,7 +336,7 @@ export class CubeSymbols { if (hierarchy) { // TODO recheck `this.getResolvedMember(...).levels` typing - const levels = this.evaluateReferences(cubeName, this.getResolvedMember('hierarchies', cubeName, hierarchyName).levels as () => Array<{ toString(): string }>, { originalSorting: true }); + const levels = this.evaluateReferences(cubeName, this.getResolvedMember('hierarchies', cubeName, hierarchyName).levels as () => Array, { originalSorting: true }); levels.forEach((level) => autoIncludeMembers.add(level)); } @@ -373,7 +375,7 @@ export class CubeSymbols { protected membersFromCubes(parentCube: CubeDefinition, cubes: any[], type: string, errorReporter: ErrorReporter, splitViews: SplitViews, memberSets: any) { return R.unnest(cubes.map(cubeInclude => { // TODO recheck `cubeInclude.joinPath` typing - const fullPath = this.evaluateReferences(null, cubeInclude.joinPath as () => { toString(): string }, { collectJoinHints: true }); + 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; @@ -456,7 +458,7 @@ export class CubeSymbols { return includes.filter(include => !excludesMap.has(include.member)); } - protected membersFromIncludeExclude(referencesFn: (...args: Array) => Array<{ toString(): string }>, 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('.'); @@ -553,11 +555,12 @@ export class CubeSymbols { return res; } - protected evaluateReferences>( + protected evaluateReferences>( cube: string | null, referencesFn: (...args: Array) => T, - options: { collectJoinHints?: boolean, originalSorting?: boolean } = {}): - T extends Array<{ toString(): string }> ? Array : T extends { toString(): string } ? string : string | Array { + options: { collectJoinHints?: boolean, originalSorting?: boolean } = {} + ): + T extends Array ? Array : T extends ToString ? string : string | Array { const cubeEvaluator = this; const fullPath = (joinHints, path) => {