From 6d4a7e9fa482afbfa7712e2d437206b6ba07a4b8 Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Wed, 16 Jul 2025 18:11:19 +0300 Subject: [PATCH 01/15] add @types/node-dijkstra --- packages/cubejs-schema-compiler/package.json | 1 + yarn.lock | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/packages/cubejs-schema-compiler/package.json b/packages/cubejs-schema-compiler/package.json index ae739f7426c75..b6d176309eeaf 100644 --- a/packages/cubejs-schema-compiler/package.json +++ b/packages/cubejs-schema-compiler/package.json @@ -68,6 +68,7 @@ "@types/inflection": "^1.5.28", "@types/jest": "^29", "@types/node": "^20", + "@types/node-dijkstra": "^2.5.6", "@types/ramda": "^0.27.34", "@types/sqlstring": "^2.3.0", "@types/syntax-error": "^1.4.1", diff --git a/yarn.lock b/yarn.lock index 4678fb93ad1e2..945c02a67e211 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7848,6 +7848,11 @@ dependencies: "@types/node" "*" +"@types/node-dijkstra@^2.5.6": + version "2.5.6" + resolved "https://registry.yarnpkg.com/@types/node-dijkstra/-/node-dijkstra-2.5.6.tgz#df4621e50df10b2e98229796ab1c2a3ca74b65b8" + integrity sha512-+n0D+FdGuCLsKoH7fwX3iWfkKSAG0e4z1F96UG5gAnlE2V/1AZO6LuPLzHQd1MC2fZJDcE2cpTS2Ln1lywgUvw== + "@types/node-fetch@^2.5.7", "@types/node-fetch@^2.5.8": version "2.6.12" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.12.tgz#8ab5c3ef8330f13100a7479e2cd56d3386830a03" From f2011e6e45465885700c8c72ec71cbd9df2e56f2 Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Wed, 16 Jul 2025 18:23:51 +0300 Subject: [PATCH 02/15] some ramda fixes --- packages/cubejs-schema-compiler/src/adapter/BaseFilter.ts | 4 ++-- .../cubejs-schema-compiler/src/adapter/PreAggregations.ts | 2 +- .../src/compiler/CubeToMetaTransformer.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/cubejs-schema-compiler/src/adapter/BaseFilter.ts b/packages/cubejs-schema-compiler/src/adapter/BaseFilter.ts index f656b3249affd..f4fea50f91cb2 100644 --- a/packages/cubejs-schema-compiler/src/adapter/BaseFilter.ts +++ b/packages/cubejs-schema-compiler/src/adapter/BaseFilter.ts @@ -1,6 +1,6 @@ import inlection from 'inflection'; import moment from 'moment-timezone'; -import { contains, join, map } from 'ramda'; +import { includes, join, map } from 'ramda'; import { FROM_PARTITION_RANGE, TO_PARTITION_RANGE } from '@cubejs-backend/shared'; import { BaseDimension } from './BaseDimension'; @@ -134,7 +134,7 @@ export class BaseFilter extends BaseDimension { } public isDateOperator(): boolean { - return contains(this.camelizeOperator, DATE_OPERATORS); + return includes(this.camelizeOperator, DATE_OPERATORS); } public valuesArray() { diff --git a/packages/cubejs-schema-compiler/src/adapter/PreAggregations.ts b/packages/cubejs-schema-compiler/src/adapter/PreAggregations.ts index c856f675d2c83..e1ca2a94a4294 100644 --- a/packages/cubejs-schema-compiler/src/adapter/PreAggregations.ts +++ b/packages/cubejs-schema-compiler/src/adapter/PreAggregations.ts @@ -767,7 +767,7 @@ export class PreAggregations { if (td[1] === '*') { return R.any((tdtc: [string, string]) => tdtc[0] === td[0]); // need to match the dimension at least } else { - return R.contains(td); + return R.includes(td); } })) ) diff --git a/packages/cubejs-schema-compiler/src/compiler/CubeToMetaTransformer.js b/packages/cubejs-schema-compiler/src/compiler/CubeToMetaTransformer.js index b6a706feddaa3..740da78e43576 100644 --- a/packages/cubejs-schema-compiler/src/compiler/CubeToMetaTransformer.js +++ b/packages/cubejs-schema-compiler/src/compiler/CubeToMetaTransformer.js @@ -172,7 +172,7 @@ export class CubeToMetaTransformer { // As for now context works on the cubes level return R.filter( - (query) => R.contains(query.config.name, context.contextMembers) + (query) => R.includes(query.config.name, context.contextMembers) )(this.queries); } From beb442896abb793e292b0ebb26dd58fc3c9169d0 Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Wed, 16 Jul 2025 18:24:14 +0300 Subject: [PATCH 03/15] more types in base member classes --- .../src/adapter/BaseDimension.ts | 8 +++++- .../src/adapter/BaseMeasure.ts | 28 +++++++++++-------- .../src/adapter/BaseSegment.ts | 12 ++++++-- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/packages/cubejs-schema-compiler/src/adapter/BaseDimension.ts b/packages/cubejs-schema-compiler/src/adapter/BaseDimension.ts index 34e8fde294ed4..9459eece31627 100644 --- a/packages/cubejs-schema-compiler/src/adapter/BaseDimension.ts +++ b/packages/cubejs-schema-compiler/src/adapter/BaseDimension.ts @@ -142,6 +142,12 @@ export class BaseDimension { if (this.expression) { return `expr:${this.expressionName}`; } - return this.query.cubeEvaluator.pathFromArray(this.path() as string[]); + + const path = this.path(); + if (path === null) { + // Sanity check, this should not actually happen because we checked this.expression earlier + throw new Error('Unexpected null path'); + } + return this.query.cubeEvaluator.pathFromArray(path); } } diff --git a/packages/cubejs-schema-compiler/src/adapter/BaseMeasure.ts b/packages/cubejs-schema-compiler/src/adapter/BaseMeasure.ts index dff05deb9ff5f..091e1dede408f 100644 --- a/packages/cubejs-schema-compiler/src/adapter/BaseMeasure.ts +++ b/packages/cubejs-schema-compiler/src/adapter/BaseMeasure.ts @@ -206,32 +206,32 @@ export class BaseMeasure { return this.measureDefinition(); } - public aliasName() { + public aliasName(): string { return this.query.escapeColumnName(this.unescapedAliasName()); } - public unescapedAliasName() { + public unescapedAliasName(): string { if (this.expression) { return this.query.aliasName(this.expressionName); } return this.query.aliasName(this.measure); } - public isCumulative() { + public isCumulative(): boolean { if (this.expression) { // TODO return false; } return BaseMeasure.isCumulative(this.measureDefinition()); } - public isMultiStage() { + public isMultiStage(): boolean { if (this.expression) { // TODO return false; } return this.definition().multiStage; } - public isAdditive() { + public isAdditive(): boolean { if (this.expression) { // TODO return false; } @@ -243,7 +243,7 @@ export class BaseMeasure { definition.type === 'min' || definition.type === 'max'; } - public static isCumulative(definition) { + public static isCumulative(definition): boolean { return definition.type === 'runningTotal' || !!definition.rollingWindow; } @@ -294,7 +294,7 @@ export class BaseMeasure { return this.query.minGranularity(granularityA, granularityB); } - public granularityFromInterval(interval: string) { + public granularityFromInterval(interval: string): string | undefined { if (!interval) { return undefined; } @@ -312,7 +312,7 @@ export class BaseMeasure { return undefined; } - public shouldUngroupForCumulative() { + public shouldUngroupForCumulative(): boolean { return this.measureDefinition().rollingWindow && !this.isAdditive(); } @@ -320,17 +320,23 @@ export class BaseMeasure { return this.measureDefinition().sql; } - public path() { + public path(): string[] | null { if (this.expression) { return null; } return this.query.cubeEvaluator.parsePath('measures', this.measure); } - public expressionPath() { + public expressionPath(): string { if (this.expression) { return `expr:${this.expression.expressionName}`; } - return this.query.cubeEvaluator.pathFromArray(this.path() as string[]); + + const path = this.path(); + if (path === null) { + // Sanity check, this should not actually happen because we checked this.expression earlier + throw new Error('Unexpected null path'); + } + return this.query.cubeEvaluator.pathFromArray(path); } } diff --git a/packages/cubejs-schema-compiler/src/adapter/BaseSegment.ts b/packages/cubejs-schema-compiler/src/adapter/BaseSegment.ts index 854eb8da1c8f2..18343505bdf08 100644 --- a/packages/cubejs-schema-compiler/src/adapter/BaseSegment.ts +++ b/packages/cubejs-schema-compiler/src/adapter/BaseSegment.ts @@ -81,17 +81,23 @@ export class BaseSegment { return this.segmentDefinition().sql; } - public path() { + public path(): string[] | null { if (this.expression) { return null; } return this.query.cubeEvaluator.parsePath('segments', this.segment); } - public expressionPath() { + public expressionPath(): string { if (this.expression) { return `expr:${this.expression.expressionName}`; } - return this.query.cubeEvaluator.pathFromArray(this.path() as string[]); + + const path = this.path(); + if (path === null) { + // Sanity check, this should not actually happen because we checked this.expression earlier + throw new Error('Unexpected null path'); + } + return this.query.cubeEvaluator.pathFromArray(path); } } From 573189f65cbc57e69c8db273d3ec7306fad58dd5 Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Wed, 16 Jul 2025 18:32:30 +0300 Subject: [PATCH 04/15] more types --- .../cubejs-schema-compiler/src/compiler/CubeSymbols.ts | 2 +- .../cubejs-schema-compiler/src/compiler/CubeValidator.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts b/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts index 3542a08be6534..1c510bda47ddb 100644 --- a/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts +++ b/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts @@ -10,7 +10,7 @@ import type { ErrorReporter } from './ErrorReporter'; export type ToString = { toString(): string }; -interface CubeDefinition { +export interface CubeDefinition { name: string; extends?: (...args: Array) => { __cubeName: string }; sql?: string | (() => string); diff --git a/packages/cubejs-schema-compiler/src/compiler/CubeValidator.ts b/packages/cubejs-schema-compiler/src/compiler/CubeValidator.ts index f9af9edab58f3..32540d4864ac6 100644 --- a/packages/cubejs-schema-compiler/src/compiler/CubeValidator.ts +++ b/packages/cubejs-schema-compiler/src/compiler/CubeValidator.ts @@ -1,7 +1,7 @@ import Joi from 'joi'; import cronParser from 'cron-parser'; -import type { CubeSymbols } from './CubeSymbols'; +import type { CubeSymbols, CubeDefinition } from './CubeSymbols'; import type { ErrorReporter } from './ErrorReporter'; /* ***************************** @@ -952,13 +952,13 @@ export class CubeValidator { if (result.error != null) { errorReporter.error(formatErrorMessage(result.error), result.error); } else { - this.validCubes[cube.name] = true; + this.validCubes.set(cube.name, true); } return result; } - public isCubeValid(cube) { - return this.validCubes[cube.name] || cube.isSplitView; + public isCubeValid(cube: CubeDefinition): boolean { + return this.validCubes.get(cube.name) ?? cube.isSplitView ?? false; } } From e47e39c9729f0e918fef73df7ae54a57adbfb75c Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Wed, 16 Jul 2025 19:37:17 +0300 Subject: [PATCH 05/15] more types and EvaluatedCube type!! --- .../src/adapter/BaseQuery.js | 62 +++++- .../src/adapter/PreAggregations.ts | 22 +- .../src/compiler/CubeEvaluator.ts | 191 ++++++++++++------ 3 files changed, 201 insertions(+), 74 deletions(-) diff --git a/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js b/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js index 43b270f34b5a9..d9d21e3dacba1 100644 --- a/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js +++ b/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js @@ -2166,6 +2166,12 @@ export class BaseQuery { )); } + /** + * + * @param {string} cube + * @param {boolean} [isLeftJoinCondition] + * @returns {[string, string, string?]} + */ rewriteInlineCubeSql(cube, isLeftJoinCondition) { const sql = this.cubeSql(cube); const cubeAlias = this.cubeAlias(cube); @@ -2190,7 +2196,7 @@ export class BaseQuery { joinQuery(join, subQueryDimensions) { const subQueryDimensionsByCube = R.groupBy(d => this.cubeEvaluator.cubeNameFromPath(d), subQueryDimensions); - const joins = join.joins.map( + const joins = join.joins.flatMap( j => { const [cubeSql, cubeAlias, conditions] = this.rewriteInlineCubeSql(j.originalTo, true); return [{ @@ -2200,7 +2206,7 @@ export class BaseQuery { // TODO handle the case when sub query referenced by a foreign cube on other side of a join }].concat((subQueryDimensionsByCube[j.originalTo] || []).map(d => this.subQueryJoin(d))); } - ).reduce((a, b) => a.concat(b), []); + ); const [cubeSql, cubeAlias] = this.rewriteInlineCubeSql(join.root); @@ -2273,6 +2279,11 @@ export class BaseQuery { return this.filtersWithoutSubQueriesValue; } + /** + * + * @param {string} dimension + * @returns {{ prefix: string, subQuery: this, cubeName: string }} + */ subQueryDescription(dimension) { const symbol = this.cubeEvaluator.dimensionByPath(dimension); const [cubeName, name] = this.cubeEvaluator.parsePath('dimensions', dimension); @@ -2317,6 +2328,12 @@ export class BaseQuery { return { prefix, subQuery, cubeName }; } + /** + * + * @param {string} cubeName + * @param {string} name + * @returns {string} + */ subQueryName(cubeName, name) { return `${cubeName}_${name}_subquery`; } @@ -2520,6 +2537,9 @@ export class BaseQuery { ); } + /** + * @param {string} cube + */ cubeSql(cube) { const foundPreAggregation = this.preAggregations.findPreAggregationToUseForCube(cube); if (foundPreAggregation && @@ -2630,6 +2650,13 @@ export class BaseQuery { ]; } + /** + * @template T + * @param {boolean} excludeTimeDimensions + * @param {(t: () => void) => T} fn + * @param {string | Array} methodName + * @returns {T} + */ collectFromMembers(excludeTimeDimensions, fn, methodName) { const membersToCollectFrom = this.allMembersConcat(excludeTimeDimensions) .concat(this.join ? this.join.joins.map(j => ({ @@ -2656,6 +2683,14 @@ export class BaseQuery { .concat(excludeTimeDimensions ? [] : this.timeDimensions); } + /** + * @template T + * @param {Array} membersToCollectFrom + * @param {(t: () => void) => T} fn + * @param {string | Array} methodName + * @param {unknown} [cache] + * @returns {T} + */ collectFrom(membersToCollectFrom, fn, methodName, cache) { const methodCacheKey = Array.isArray(methodName) ? methodName : [methodName]; return R.pipe( @@ -2677,6 +2712,11 @@ export class BaseQuery { ); } + /** + * + * @param {() => void} fn + * @returns {Array} + */ collectSubQueryDimensionsFor(fn) { const context = { subQueryDimensions: [] }; this.evaluateSymbolSqlWithContext( @@ -3239,6 +3279,11 @@ export class BaseQuery { return strings.join(' || '); } + /** + * + * @param {string} cubeName + * @returns {Array} + */ primaryKeyNames(cubeName) { const primaryKeys = this.cubeEvaluator.primaryKeys[cubeName]; if (!primaryKeys || !primaryKeys.length) { @@ -3374,6 +3419,12 @@ export class BaseQuery { )(context.leafMeasures); } + /** + * @template T + * @param {() => T} fn + * @param {unknown} context + * @returns {T} + */ evaluateSymbolSqlWithContext(fn, context) { const oldContext = this.evaluateSymbolContext; this.evaluateSymbolContext = oldContext ? Object.assign({}, oldContext, context) : context; @@ -3596,6 +3647,11 @@ export class BaseQuery { .map(s => `(${s})`).join(' AND '); } + /** + * @param {string} primaryKeyName + * @param {string} cubeName + * @returns {unknown} + */ primaryKeySql(primaryKeyName, cubeName) { const primaryKeyDimension = this.cubeEvaluator.dimensionByPath([cubeName, primaryKeyName]); return this.evaluateSymbolSql( @@ -3745,7 +3801,7 @@ export class BaseQuery { /** * * @param options - * @returns {BaseQuery} + * @returns {this} */ newSubQuery(options) { const QueryClass = this.constructor; diff --git a/packages/cubejs-schema-compiler/src/adapter/PreAggregations.ts b/packages/cubejs-schema-compiler/src/adapter/PreAggregations.ts index e1ca2a94a4294..d7b07c95f1f84 100644 --- a/packages/cubejs-schema-compiler/src/adapter/PreAggregations.ts +++ b/packages/cubejs-schema-compiler/src/adapter/PreAggregations.ts @@ -49,6 +49,10 @@ export type PreAggregationForQuery = { sqlAlias?: string; }; +export type PreAggregationForQueryWithTableName = PreAggregationForQuery & { + tableName: string; +}; + export type PreAggregationForCube = { preAggregationName: string; cube: string; @@ -125,7 +129,7 @@ export class PreAggregations { !isInPreAggregationQuery || isInPreAggregationQuery && this.query.options.useOriginalSqlPreAggregationsInPreAggregation) { return R.pipe( - R.map(cube => { + R.map((cube: string) => { const { preAggregations } = this.collectOriginalSqlPreAggregations(() => this.query.cubeSql(cube)); return R.unnest(preAggregations.map(p => this.preAggregationDescriptionsFor(p))); }), @@ -138,6 +142,10 @@ export class PreAggregations { private preAggregationCubes(): string[] { const { join } = this.query; + if (!join) { + // This can happen with Tesseract, or when there's no cubes to join + throw new Error('Unexpected missing join tree for query'); + } return join.joins.map(j => j.originalTo).concat([join.root]); } @@ -945,14 +953,12 @@ export class PreAggregations { private findRollupPreAggregationsForCube(cube: string, canUsePreAggregation: CanUsePreAggregationFn, preAggregations: PreAggregationDefinitions): PreAggregationForQuery[] { return R.pipe( R.toPairs, - // eslint-disable-next-line no-unused-vars - // eslint-disable-next-line @typescript-eslint/no-unused-vars - R.filter(([k, a]) => a.type === 'rollup' || a.type === 'rollupJoin' || a.type === 'rollupLambda'), + R.filter(([_k, a]) => a.type === 'rollup' || a.type === 'rollupJoin' || a.type === 'rollupLambda'), R.map(([preAggregationName, preAggregation]) => this.evaluatedPreAggregationObj(cube, preAggregationName, preAggregation, canUsePreAggregation)) )(preAggregations); } - public getRollupPreAggregationByName(cube, preAggregationName) { + public getRollupPreAggregationByName(cube, preAggregationName): PreAggregationForQueryWithTableName | {} { const canUsePreAggregation = () => true; const preAggregation = R.pipe( R.toPairs, @@ -1075,7 +1081,7 @@ export class PreAggregations { return this.evaluatedPreAggregationObj( joinCube, joinPreAggregationName, - this.query.cubeEvaluator.byPath('preAggregations', name), + this.query.cubeEvaluator.byPath('preAggregations', name) as PreAggregationDefinitionExtended, canUsePreAggregation ); } @@ -1093,7 +1099,7 @@ export class PreAggregations { return this.evaluatedPreAggregationObj( referencedCube, referencedPreAggregation, - this.query.cubeEvaluator.byPath('preAggregations', name), + this.query.cubeEvaluator.byPath('preAggregations', name) as PreAggregationDefinitionExtended, canUsePreAggregation ); } @@ -1311,7 +1317,7 @@ export class PreAggregations { if (aggregation.type === 'rollupLambda') { if (references.rollups.length > 0) { const [firstLambdaCube] = this.query.cubeEvaluator.parsePath('preAggregations', references.rollups[0]); - const firstLambdaPreAggregation = this.query.cubeEvaluator.byPath('preAggregations', references.rollups[0]); + const firstLambdaPreAggregation = this.query.cubeEvaluator.byPath('preAggregations', references.rollups[0]) as PreAggregationDefinitionExtended; const firstLambdaReferences = this.query.cubeEvaluator.evaluatePreAggregationReferences(firstLambdaCube, firstLambdaPreAggregation); if (references.measures.length === 0 && diff --git a/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts b/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts index 7bc21e26bb455..ab19ff51b95a3 100644 --- a/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts +++ b/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts @@ -3,28 +3,28 @@ import R from 'ramda'; import { CubeSymbols, type ToString } from './CubeSymbols'; import { UserError } from './UserError'; -import { BaseQuery } from '../adapter'; +import { BaseQuery, PreAggregationDefinitionExtended } from '../adapter'; import type { CubeValidator } from './CubeValidator'; import type { ErrorReporter } from './ErrorReporter'; export type SegmentDefinition = { - type: string, - sql: Function, - primaryKey?: true, - ownedByCube: boolean, - fieldType?: string, + type: string; + sql(): string; + primaryKey?: true; + ownedByCube: boolean; + fieldType?: string; // TODO should we have it here? - multiStage?: boolean, + multiStage?: boolean; }; export type DimensionDefinition = { - type: string, - sql: Function, - primaryKey?: true, - ownedByCube: boolean, - fieldType?: string, - multiStage?: boolean, - shiftInterval?: string, + type: string; + sql(): string; + primaryKey?: true; + ownedByCube: boolean; + fieldType?: string; + multiStage?: boolean; + shiftInterval?: string; }; export type TimeShiftDefinition = { @@ -42,30 +42,30 @@ export type TimeShiftDefinitionReference = { }; export type MeasureDefinition = { - type: string, - sql: Function, - ownedByCube: boolean, + type: string; + sql(): string; + ownedByCube: boolean; rollingWindow?: any filters?: any - primaryKey?: true, - drillFilters?: any, - multiStage?: boolean, - groupBy?: (...args: Array) => Array, - reduceBy?: (...args: Array) => Array, - addGroupBy?: (...args: Array) => Array, - timeShift?: TimeShiftDefinition[], - groupByReferences?: string[], - reduceByReferences?: string[], - addGroupByReferences?: string[], - timeShiftReferences?: TimeShiftDefinitionReference[], - patchedFrom?: { cubeName: string, name: string }, + primaryKey?: true; + drillFilters?: any; + multiStage?: boolean; + groupBy?: (...args: Array) => Array; + reduceBy?: (...args: Array) => Array; + addGroupBy?: (...args: Array) => Array; + timeShift?: TimeShiftDefinition[]; + groupByReferences?: string[]; + reduceByReferences?: string[]; + addGroupByReferences?: string[]; + timeShiftReferences?: TimeShiftDefinitionReference[]; + patchedFrom?: { cubeName: string; name: string }; }; export type PreAggregationFilters = { - dataSources?: string[], - cubes?: string[], - preAggregationIds?: string[], - scheduled?: boolean, + dataSources?: string[]; + cubes?: string[]; + preAggregationIds?: string[]; + scheduled?: boolean; }; export type EveryInterval = string; @@ -94,24 +94,27 @@ export type CubeRefreshKey = | CubeRefreshKeyImmutableVariant; export type PreAggregationDefinition = { - type: 'autoRollup' | 'originalSql' | 'rollupJoin' | 'rollupLambda' | 'rollup', - allowNonStrictDateRangeMatch?: boolean, - useOriginalSqlPreAggregations?: boolean, - timeDimensionReference?: () => ToString, - granularity: string, - timeDimensionReferences: Array<{ dimension: () => ToString, granularity: string }>, - dimensionReferences: () => Array, - segmentReferences: () => Array, - measureReferences: () => Array, - rollupReferences: () => Array, - indexes?: Record, - refreshKey?: CubeRefreshKey, - scheduledRefresh: boolean, - external: boolean, + type: 'autoRollup' | 'originalSql' | 'rollupJoin' | 'rollupLambda' | 'rollup'; + allowNonStrictDateRangeMatch?: boolean; + useOriginalSqlPreAggregations?: boolean; + timeDimensionReference?: () => ToString; + granularity: string; + timeDimensionReferences: Array<{ dimension: () => ToString, granularity: string }>; + dimensionReferences: () => Array; + segmentReferences: () => Array; + measureReferences: () => Array; + rollupReferences: () => Array; + indexes?: Record; + refreshKey?: CubeRefreshKey; + scheduledRefresh: boolean; + external: boolean; + ownedByCube?: boolean; // To be compatible with measures and dimensions }; export type PreAggregationDefinitions = Record; +export type PreAggregationDefinitionsExtended = Record; + export type PreAggregationTimeDimensionReference = { dimension: string, granularity: string, @@ -137,10 +140,69 @@ export type PreAggregationInfo = { indexesReferences: unknown, }; +export type EvaluatedCubeDimensions = Record; +export type EvaluatedCubeMeasures = Record; +export type EvaluatedCubeSegments = Record; +export type EvaluatedJoins = Record; + +export type EvaluatedHierarchy = { + name: string; + title?: string; + levels: string[]; + aliasMember?: string; + [key: string]: any; +}; + +export type Filter = + | { + member: string; + memberReference?: string; + [key: string]: any; + } + | { + and?: Filter[]; + or?: Filter[]; + [key: string]: any; + }; + +export type AccessPolicy = { + rowLevel?: { + filters: Filter[]; + }; + memberLevel?: { + includes?: string | string[]; + excludes?: string | string[]; + includesMembers?: string[]; + excludesMembers?: string[]; + }; +}; + +export type EvaluatedFolder = { + name: string; + includes: (EvaluatedFolder | DimensionDefinition | MeasureDefinition)[]; + type: 'folder'; + [key: string]: any; +}; + +export type EvaluatedCube = { + measures: EvaluatedCubeMeasures; + dimensions: EvaluatedCubeDimensions; + segments: EvaluatedCubeSegments; + joins: EvaluatedJoins; + hierarchies: unknown; + evaluatedHierarchies: EvaluatedHierarchy[]; + preAggregations: Record; + dataSource?: string; + folders: EvaluatedFolder[]; + sql: unknown; + sqlTable: unknown; + accessPolicy?: AccessPolicy[]; +}; + export class CubeEvaluator extends CubeSymbols { - public evaluatedCubes: Record = {}; + public evaluatedCubes: Record = {}; - public primaryKeys: Record = {}; + public primaryKeys: Record = {}; public byFileName: Record = {}; @@ -559,7 +621,7 @@ export class CubeEvaluator extends CubeSymbols { return this.byFileName[fileName] || []; } - public timeDimensionPathsForCube(cube: any) { + public timeDimensionPathsForCube(cube: string): string[] { return R.compose( R.map(dimName => `${cube}.${dimName}`), R.keys, @@ -569,18 +631,18 @@ export class CubeEvaluator extends CubeSymbols { )(this.evaluatedCubes[cube].dimensions || {}); } - public measuresForCube(cube) { + public measuresForCube(cube: string): EvaluatedCubeMeasures { return this.cubeFromPath(cube).measures || {}; } - public timeDimensionsForCube(cube) { + public timeDimensionsForCube(cube: string): EvaluatedCubeDimensions { return R.filter( (d: any) => d.type === 'time', this.cubeFromPath(cube).dimensions || {} ); } - public preAggregationsForCube(path: string): Record { + public preAggregationsForCube(path: string): Record { return this.cubeFromPath(path).preAggregations || {}; } @@ -689,22 +751,22 @@ export class CubeEvaluator extends CubeSymbols { } public measureByPath(measurePath: string): MeasureDefinition { - return this.byPath('measures', measurePath); + return this.byPath('measures', measurePath) as MeasureDefinition; } public dimensionByPath(dimensionPath: string): DimensionDefinition { - return this.byPath('dimensions', dimensionPath); + return this.byPath('dimensions', dimensionPath) as DimensionDefinition; } public segmentByPath(segmentPath: string): SegmentDefinition { - return this.byPath('segments', segmentPath); + return this.byPath('segments', segmentPath) as SegmentDefinition; } - public cubeExists(cube) { + public cubeExists(cube: string): boolean { return !!this.evaluatedCubes[cube]; } - public cubeFromPath(path: string) { + public cubeFromPath(path: string): EvaluatedCube { return this.evaluatedCubes[this.cubeNameFromPath(path)]; } @@ -738,7 +800,7 @@ export class CubeEvaluator extends CubeSymbols { throw new UserError(`Can't resolve member '${Array.isArray(path) ? path.join('.') : path}'`); } - public byPath(type: 'measures' | 'dimensions' | 'segments' | 'preAggregations', path: string | string[]) { + public byPath(type: T, path: string | string[]): EvaluatedCube[T][string] { if (!type) { throw new Error(`Type can't be undefined for '${path}'`); } @@ -748,19 +810,22 @@ export class CubeEvaluator extends CubeSymbols { } const cubeAndName = Array.isArray(path) ? path : path.split('.'); - if (!this.evaluatedCubes[cubeAndName[0]]) { + const cube = this.evaluatedCubes[cubeAndName[0]]; + if (cube === undefined) { throw new UserError(`Cube '${cubeAndName[0]}' not found for path '${path}'`); } - if (!this.evaluatedCubes[cubeAndName[0]][type]) { + const typeMembers = cube[type]; + if (typeMembers === undefined) { throw new UserError(`${type} not defined for path '${path}'`); } - if (!this.evaluatedCubes[cubeAndName[0]][type][cubeAndName[1]]) { + const member = typeMembers[cubeAndName[1]]; + if (member === undefined) { throw new UserError(`'${cubeAndName[1]}' not found for path '${path}'`); } - return this.evaluatedCubes[cubeAndName[0]][type][cubeAndName[1]]; + return member as EvaluatedCube[T][string]; } public parsePath(type: 'measures' | 'dimensions' | 'segments' | 'preAggregations', path: string): string[] { From d421dbbb99bfba96a4a5e32bb7bb53ab184ee485 Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Thu, 17 Jul 2025 12:37:10 +0300 Subject: [PATCH 06/15] more types --- .../src/compiler/CubeEvaluator.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts b/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts index ab19ff51b95a3..9c658bf80285b 100644 --- a/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts +++ b/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts @@ -145,9 +145,16 @@ export type EvaluatedCubeMeasures = Record; export type EvaluatedCubeSegments = Record; export type EvaluatedJoins = Record; +export type HierarchyDefinition = { + title?: string; + public?: boolean; + levels?: (...args: any[]) => string[]; +}; + export type EvaluatedHierarchy = { name: string; title?: string; + public?: boolean; levels: string[]; aliasMember?: string; [key: string]: any; @@ -189,13 +196,13 @@ export type EvaluatedCube = { dimensions: EvaluatedCubeDimensions; segments: EvaluatedCubeSegments; joins: EvaluatedJoins; - hierarchies: unknown; + hierarchies: Record; evaluatedHierarchies: EvaluatedHierarchy[]; preAggregations: Record; dataSource?: string; folders: EvaluatedFolder[]; - sql: unknown; - sqlTable: unknown; + sql?: (...args: any[]) => string; + sqlTable?: (...args: any[]) => string; accessPolicy?: AccessPolicy[]; }; @@ -204,7 +211,7 @@ export class CubeEvaluator extends CubeSymbols { public primaryKeys: Record = {}; - public byFileName: Record = {}; + public byFileName: Record = {}; private isRbacEnabledCache: boolean | null = null; From 46d8d270795d5dae672123759689028e00345322 Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Thu, 17 Jul 2025 13:08:12 +0300 Subject: [PATCH 07/15] more types in CubeSymbols --- .../src/compiler/CubeEvaluator.ts | 6 +-- .../src/compiler/CubeSymbols.ts | 53 +++++++++++++++---- 2 files changed, 46 insertions(+), 13 deletions(-) diff --git a/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts b/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts index 9c658bf80285b..5de27f38792eb 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, type ToString } from './CubeSymbols'; +import { CubeDefinitionExtended, CubeSymbols, type ToString } from './CubeSymbols'; import { UserError } from './UserError'; import { BaseQuery, PreAggregationDefinitionExtended } from '../adapter'; import type { CubeValidator } from './CubeValidator'; @@ -211,7 +211,7 @@ export class CubeEvaluator extends CubeSymbols { public primaryKeys: Record = {}; - public byFileName: Record = {}; + public byFileName: Record = {}; private isRbacEnabledCache: boolean | null = null; @@ -237,7 +237,7 @@ export class CubeEvaluator extends CubeSymbols { this.evaluatedCubes[cube.name] = this.prepareCube(cube, errorReporter); } - this.byFileName = R.groupBy(v => v.fileName, validCubes); + this.byFileName = R.groupBy(v => v.fileName || v.name, validCubes); this.primaryKeys = R.fromPairs( validCubes.map((v) => { const primaryKeyNamesToSymbols = R.compose( diff --git a/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts b/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts index 1c510bda47ddb..7174809a4343e 100644 --- a/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts +++ b/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts @@ -10,13 +10,29 @@ import type { ErrorReporter } from './ErrorReporter'; export type ToString = { toString(): string }; +export type GranularityDefinition = { + sql?: (...args: any[]) => string; + title?: string; + interval?: string; + offset?: string; + origin?: string; +}; + +export type TimeshiftDefinition = { + interval?: string; + type?: string; + name?: string; + timeDimension?: (...args: any[]) => string; +}; + export interface CubeDefinition { name: string; extends?: (...args: Array) => { __cubeName: string }; - sql?: string | (() => string); + sql?: string | ((...args: any[]) => string); // eslint-disable-next-line camelcase - sql_table?: string | (() => string); - sqlTable?: string | (() => string); + sql_table?: string | ((...args: any[]) => string); + sqlTable?: string | ((...args: any[]) => string); + dataSource?: string; measures?: Record; dimensions?: Record; segments?: Record; @@ -34,9 +50,10 @@ export interface CubeDefinition { calendar?: boolean; isSplitView?: boolean; includedMembers?: any[]; + fileName?: string; } -interface CubeDefinitionExtended extends CubeDefinition { +export interface CubeDefinitionExtended extends CubeDefinition { allDefinitions: (type: string) => Record; rawFolders: () => any[]; rawCubes: () => any[]; @@ -46,6 +63,22 @@ interface SplitViews { [key: string]: any; } +export type CubeSymbolDefinition = { + type?: string; + sql?: (...args: any[]) => string; + primaryKey?: boolean; + granularities?: Record; + timeShift?: TimeshiftDefinition[]; + format?: string; +}; + +export interface CubeSymbolsBase { + cubeName: () => string; + cubeObj: () => CubeDefinitionExtended; +} + +export type CubeSymbolsDefinition = CubeSymbolsBase & Record; + const FunctionRegex = /function\s+\w+\(([A-Za-z0-9_,]*)|\(([\s\S]*?)\)\s*=>|\(?(\w+)\)?\s*=>/; export const CONTEXT_SYMBOLS = { SECURITY_CONTEXT: 'securityContext', @@ -61,15 +94,15 @@ export const CONTEXT_SYMBOLS = { export const CURRENT_CUBE_CONSTANTS = ['CUBE', 'TABLE']; export class CubeSymbols { - public symbols: Record; + public symbols: Record; - private builtCubes: Record; + private builtCubes: Record; private cubeDefinitions: Record; private funcArgumentsValues: Record; - public cubeList: any[]; + public cubeList: CubeDefinitionExtended[]; private readonly evaluateViews: boolean; @@ -959,7 +992,7 @@ export class CubeSymbols { ), }; } - if (cube[propertyName]) { + if (cube[propertyName as string]) { return this.cubeReferenceProxy(cubeName, joinHints, propertyName); } if (self.symbols[propertyName]) { @@ -1022,9 +1055,9 @@ export class CubeSymbols { if (propertyName === '_objectWithResolvedProperties') { return true; } - if (cube[propertyName]) { + if (cube[propertyName as string]) { const index = depsResolveFn(propertyName, parentIndex); - if (cube[propertyName].type === 'time') { + if (cube[propertyName as string].type === 'time') { return this.timeDimDependenciesProxy(index); } From e91da9ab0de964d4a485394adcece3562face181 Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Thu, 17 Jul 2025 13:39:04 +0300 Subject: [PATCH 08/15] more types in CubeSymbols --- .../src/adapter/PreAggregations.ts | 4 +- .../src/compiler/CubeEvaluator.ts | 59 ++---------- .../src/compiler/CubeSymbols.ts | 96 ++++++++++++++++--- .../test/unit/cube-validator.test.ts | 12 ++- 4 files changed, 99 insertions(+), 72 deletions(-) diff --git a/packages/cubejs-schema-compiler/src/adapter/PreAggregations.ts b/packages/cubejs-schema-compiler/src/adapter/PreAggregations.ts index d7b07c95f1f84..3da6eb54d3004 100644 --- a/packages/cubejs-schema-compiler/src/adapter/PreAggregations.ts +++ b/packages/cubejs-schema-compiler/src/adapter/PreAggregations.ts @@ -1,10 +1,10 @@ import R from 'ramda'; -import { CubeSymbols } from '../compiler/CubeSymbols'; +import { CubeSymbols, PreAggregationDefinition } from '../compiler/CubeSymbols'; import { UserError } from '../compiler/UserError'; import { BaseQuery } from './BaseQuery'; import { - PreAggregationDefinition, PreAggregationDefinitions, + PreAggregationDefinitions, PreAggregationReferences, PreAggregationTimeDimensionReference } from '../compiler/CubeEvaluator'; diff --git a/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts b/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts index 5de27f38792eb..c525b13b414f0 100644 --- a/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts +++ b/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts @@ -1,7 +1,13 @@ /* eslint-disable no-restricted-syntax */ import R from 'ramda'; -import { CubeDefinitionExtended, CubeSymbols, type ToString } from './CubeSymbols'; +import { + CubeDefinitionExtended, + CubeSymbols, + HierarchyDefinition, + PreAggregationDefinition, PreAggregationDefinitionRollup, + type ToString +} from './CubeSymbols'; import { UserError } from './UserError'; import { BaseQuery, PreAggregationDefinitionExtended } from '../adapter'; import type { CubeValidator } from './CubeValidator'; @@ -68,49 +74,6 @@ export type PreAggregationFilters = { scheduled?: boolean; }; -export type EveryInterval = string; -type EveryCronInterval = string; -type EveryCronTimeZone = string; - -export type CubeRefreshKeySqlVariant = { - sql: () => string; - every?: EveryInterval; -}; - -export type CubeRefreshKeyEveryVariant = { - every: EveryInterval | EveryCronInterval; - timezone?: EveryCronTimeZone; - incremental?: boolean; - updateWindow?: EveryInterval; -}; - -export type CubeRefreshKeyImmutableVariant = { - immutable: true; -}; - -export type CubeRefreshKey = - | CubeRefreshKeySqlVariant - | CubeRefreshKeyEveryVariant - | CubeRefreshKeyImmutableVariant; - -export type PreAggregationDefinition = { - type: 'autoRollup' | 'originalSql' | 'rollupJoin' | 'rollupLambda' | 'rollup'; - allowNonStrictDateRangeMatch?: boolean; - useOriginalSqlPreAggregations?: boolean; - timeDimensionReference?: () => ToString; - granularity: string; - timeDimensionReferences: Array<{ dimension: () => ToString, granularity: string }>; - dimensionReferences: () => Array; - segmentReferences: () => Array; - measureReferences: () => Array; - rollupReferences: () => Array; - indexes?: Record; - refreshKey?: CubeRefreshKey; - scheduledRefresh: boolean; - external: boolean; - ownedByCube?: boolean; // To be compatible with measures and dimensions -}; - export type PreAggregationDefinitions = Record; export type PreAggregationDefinitionsExtended = Record; @@ -145,12 +108,6 @@ export type EvaluatedCubeMeasures = Record; export type EvaluatedCubeSegments = Record; export type EvaluatedJoins = Record; -export type HierarchyDefinition = { - title?: string; - public?: boolean; - levels?: (...args: any[]) => string[]; -}; - export type EvaluatedHierarchy = { name: string; title?: string; @@ -901,7 +858,7 @@ export class CubeEvaluator extends CubeSymbols { return this.evaluateReferences(cube, rollupReferences, { originalSorting: true }); } - public evaluatePreAggregationReferences(cube: string, aggregation: PreAggregationDefinition): PreAggregationReferences { + public evaluatePreAggregationReferences(cube: string, aggregation: PreAggregationDefinitionRollup): PreAggregationReferences { const timeDimensions: Array = []; if (aggregation.timeDimensionReference) { diff --git a/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts b/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts index 7174809a4343e..f7d7a2124819c 100644 --- a/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts +++ b/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts @@ -7,6 +7,7 @@ import { DynamicReference } from './DynamicReference'; import { camelizeCube } from './utils'; import type { ErrorReporter } from './ErrorReporter'; +import { PreAggregationDefinitionExtended } from '../adapter'; export type ToString = { toString(): string }; @@ -25,6 +26,80 @@ export type TimeshiftDefinition = { timeDimension?: (...args: any[]) => string; }; +export type CubeSymbolDefinition = { + type?: string; + sql?: (...args: any[]) => string; + primaryKey?: boolean; + granularities?: Record; + timeShift?: TimeshiftDefinition[]; + format?: string; +}; + +export type HierarchyDefinition = { + title?: string; + public?: boolean; + levels?: (...args: any[]) => string[]; +}; + +export type EveryInterval = string; +type EveryCronInterval = string; +type EveryCronTimeZone = string; + +export type CubeRefreshKeySqlVariant = { + sql: () => string; + every?: EveryInterval; +}; + +export type CubeRefreshKeyEveryVariant = { + every: EveryInterval | EveryCronInterval; + timezone?: EveryCronTimeZone; + incremental?: boolean; + updateWindow?: EveryInterval; +}; + +export type CubeRefreshKeyImmutableVariant = { + immutable: true; +}; + +export type CubeRefreshKey = + | CubeRefreshKeySqlVariant + | CubeRefreshKeyEveryVariant + | CubeRefreshKeyImmutableVariant; + +type BasePreAggregationDefinition = { + allowNonStrictDateRangeMatch?: boolean; + useOriginalSqlPreAggregations?: boolean; + timeDimensionReference?: (...args: any[]) => ToString; + indexes?: Record; + refreshKey?: CubeRefreshKey; + ownedByCube?: boolean; +}; + +export type PreAggregationDefinitionOriginalSql = BasePreAggregationDefinition & { + type: 'originalSql'; + partitionGranularity?: string; + // eslint-disable-next-line camelcase + partition_granularity?: string; + // eslint-disable-next-line camelcase + time_dimension?: (...args: any[]) => ToString; +}; + +export type PreAggregationDefinitionRollup = BasePreAggregationDefinition & { + type: 'autoRollup' | 'rollupJoin' | 'rollupLambda' | 'rollup'; + granularity: string; + timeDimensionReferences: Array<{ dimension: () => ToString; granularity: string }>; + dimensionReferences: (...args: any[]) => ToString[]; + segmentReferences: (...args: any[]) => ToString[]; + measureReferences: (...args: any[]) => ToString[]; + rollupReferences: (...args: any[]) => ToString[]; + scheduledRefresh: boolean; + external: boolean; +}; + +// PreAggregationDefinition is widely used in the codebase, but it's assumed to be rollup, +// originalSql is not refreshed and so on. +export type PreAggregationDefinition = PreAggregationDefinitionRollup; + export interface CubeDefinition { name: string; extends?: (...args: Array) => { __cubeName: string }; @@ -33,13 +108,13 @@ export interface CubeDefinition { sql_table?: string | ((...args: any[]) => string); sqlTable?: string | ((...args: any[]) => string); dataSource?: string; - measures?: Record; - dimensions?: Record; - segments?: Record; - hierarchies?: Record; - preAggregations?: Record; + measures?: Record; + dimensions?: Record; + segments?: Record; + hierarchies?: Record; + preAggregations?: Record; // eslint-disable-next-line camelcase - pre_aggregations?: Record; + pre_aggregations?: Record; joins?: Record; accessPolicy?: any[]; folders?: any[]; @@ -63,15 +138,6 @@ interface SplitViews { [key: string]: any; } -export type CubeSymbolDefinition = { - type?: string; - sql?: (...args: any[]) => string; - primaryKey?: boolean; - granularities?: Record; - timeShift?: TimeshiftDefinition[]; - format?: string; -}; - export interface CubeSymbolsBase { cubeName: () => string; cubeObj: () => CubeDefinitionExtended; diff --git a/packages/cubejs-schema-compiler/test/unit/cube-validator.test.ts b/packages/cubejs-schema-compiler/test/unit/cube-validator.test.ts index 19b8d84ef2c9f..8cb7943d61e12 100644 --- a/packages/cubejs-schema-compiler/test/unit/cube-validator.test.ts +++ b/packages/cubejs-schema-compiler/test/unit/cube-validator.test.ts @@ -1,5 +1,9 @@ import { CubeValidator, functionFieldsPatterns } from '../../src/compiler/CubeValidator'; -import { CubeSymbols } from '../../src/compiler/CubeSymbols'; +import { + CubeRefreshKey, + CubeSymbols, + PreAggregationDefinitionOriginalSql +} from '../../src/compiler/CubeSymbols'; import { ErrorReporter } from '../../src/compiler/ErrorReporter'; describe('Cube Validation', () => { @@ -696,10 +700,10 @@ describe('Cube Validation', () => { type: 'originalSql', time_dimension: () => 'createdAt', partition_granularity: 'day', - refresh_key: { + refreshKey: { sql: () => 'SELECT MAX(created_at) FROM orders', - }, - } + } satisfies CubeRefreshKey, + } satisfies PreAggregationDefinitionOriginalSql }, data_source: 'default', rewrite_queries: true, From 5308d21a43c223a925baf00057898d3a2ecc3ce2 Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Thu, 17 Jul 2025 14:08:54 +0300 Subject: [PATCH 09/15] fix types in QueryOrchestrator --- .../src/orchestrator/QueryOrchestrator.ts | 26 ++++++------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/packages/cubejs-query-orchestrator/src/orchestrator/QueryOrchestrator.ts b/packages/cubejs-query-orchestrator/src/orchestrator/QueryOrchestrator.ts index 46e55da79519f..a9c9340d8bc08 100644 --- a/packages/cubejs-query-orchestrator/src/orchestrator/QueryOrchestrator.ts +++ b/packages/cubejs-query-orchestrator/src/orchestrator/QueryOrchestrator.ts @@ -3,7 +3,7 @@ import R from 'ramda'; import { getEnv } from '@cubejs-backend/shared'; import { CubeStoreDriver } from '@cubejs-backend/cubestore-driver'; -import { QueryCache, QueryBody, TempTable } from './QueryCache'; +import { QueryCache, QueryBody, TempTable, PreAggTableToTempTable } from './QueryCache'; import { PreAggregations, PreAggregationDescription, getLastUpdatedAtTimestamp } from './PreAggregations'; import { DriverFactory, DriverFactoryByDataSource } from './DriverFactory'; import { LocalQueueEventsBus } from './LocalQueueEventsBus'; @@ -224,28 +224,18 @@ export class QueryOrchestrator { }; } - const usedPreAggregations = R.pipe( + const usedPreAggregations = R.pipe< + PreAggTableToTempTable[], + Record, + Record + >( R.fromPairs, - R.map((pa: TempTable) => ({ + R.mapObjIndexed((pa: TempTable) => ({ targetTableName: pa.targetTableName, refreshKeyValues: pa.refreshKeyValues, lastUpdatedAt: pa.lastUpdatedAt, })), - )( - preAggregationsTablesToTempTables as unknown as [ - number, // TODO: we actually have a string here - { - buildRangeEnd: string, - lastUpdatedAt: number, - queryKey: unknown, - refreshKeyValues: [{ - 'refresh_key': string, - }][], - targetTableName: string, - type: string, - }, - ][] - ); + )(preAggregationsTablesToTempTables); if (this.rollupOnlyMode && Object.keys(usedPreAggregations).length === 0) { throw new Error( From 6e1a9ca9c1906a9a6411c9976d504126b17d6420 Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Thu, 17 Jul 2025 14:09:11 +0300 Subject: [PATCH 10/15] more types in CubeSymbols & Evaluator --- .../src/compiler/CubeEvaluator.ts | 21 +++++++------------ .../src/compiler/CubeSymbols.ts | 21 ++++++++++--------- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts b/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts index c525b13b414f0..3d2e2e3a993ea 100644 --- a/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts +++ b/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts @@ -4,7 +4,7 @@ import R from 'ramda'; import { CubeDefinitionExtended, CubeSymbols, - HierarchyDefinition, + HierarchyDefinition, JoinDefinition, PreAggregationDefinition, PreAggregationDefinitionRollup, type ToString } from './CubeSymbols'; @@ -76,8 +76,6 @@ export type PreAggregationFilters = { export type PreAggregationDefinitions = Record; -export type PreAggregationDefinitionsExtended = Record; - export type PreAggregationTimeDimensionReference = { dimension: string, granularity: string, @@ -103,11 +101,6 @@ export type PreAggregationInfo = { indexesReferences: unknown, }; -export type EvaluatedCubeDimensions = Record; -export type EvaluatedCubeMeasures = Record; -export type EvaluatedCubeSegments = Record; -export type EvaluatedJoins = Record; - export type EvaluatedHierarchy = { name: string; title?: string; @@ -149,10 +142,10 @@ export type EvaluatedFolder = { }; export type EvaluatedCube = { - measures: EvaluatedCubeMeasures; - dimensions: EvaluatedCubeDimensions; - segments: EvaluatedCubeSegments; - joins: EvaluatedJoins; + measures: Record; + dimensions: Record; + segments: Record; + joins: Record; hierarchies: Record; evaluatedHierarchies: EvaluatedHierarchy[]; preAggregations: Record; @@ -595,11 +588,11 @@ export class CubeEvaluator extends CubeSymbols { )(this.evaluatedCubes[cube].dimensions || {}); } - public measuresForCube(cube: string): EvaluatedCubeMeasures { + public measuresForCube(cube: string): Record { return this.cubeFromPath(cube).measures || {}; } - public timeDimensionsForCube(cube: string): EvaluatedCubeDimensions { + public timeDimensionsForCube(cube: string): Record { return R.filter( (d: any) => d.type === 'time', this.cubeFromPath(cube).dimensions || {} diff --git a/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts b/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts index f7d7a2124819c..d50d521d9d0a8 100644 --- a/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts +++ b/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts @@ -7,7 +7,6 @@ import { DynamicReference } from './DynamicReference'; import { camelizeCube } from './utils'; import type { ErrorReporter } from './ErrorReporter'; -import { PreAggregationDefinitionExtended } from '../adapter'; export type ToString = { toString(): string }; @@ -100,6 +99,11 @@ export type PreAggregationDefinitionRollup = BasePreAggregationDefinition & { // originalSql is not refreshed and so on. export type PreAggregationDefinition = PreAggregationDefinitionRollup; +export type JoinDefinition = { + relationship: string, + sql: (...args: any[]) => string, +}; + export interface CubeDefinition { name: string; extends?: (...args: Array) => { __cubeName: string }; @@ -115,7 +119,7 @@ export interface CubeDefinition { preAggregations?: Record; // eslint-disable-next-line camelcase pre_aggregations?: Record; - joins?: Record; + joins?: Record; accessPolicy?: any[]; folders?: any[]; includes?: any; @@ -184,13 +188,10 @@ export class CubeSymbols { } public compile(cubes: CubeDefinition[], errorReporter: ErrorReporter) { - // @ts-ignore - this.cubeDefinitions = R.pipe( - // @ts-ignore - R.map((c: CubeDefinition) => [c.name, c]), - R.fromPairs - // @ts-ignore - )(cubes); + this.cubeDefinitions = Object.fromEntries( + cubes.map((c): [string, CubeDefinition] => [c.name, c]) + ); + this.cubeList = cubes.map(c => (c.name ? this.getCubeDefinition(c.name) : this.createCube(c))); // TODO support actual dependency sorting to allow using views inside views const sortedByDependency = R.pipe( @@ -856,7 +857,7 @@ export class CubeSymbols { } } - protected funcArguments(func) { + protected funcArguments(func: Function): string[] { const funcDefinition = func.toString(); if (!this.funcArgumentsValues[funcDefinition]) { const match = funcDefinition.match(FunctionRegex); From b23bab8c92a15749f7ed600242402e54253e3962 Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Thu, 17 Jul 2025 14:13:40 +0300 Subject: [PATCH 11/15] fix types in ScaffoldingSchema --- .../src/scaffolding/ScaffoldingSchema.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/cubejs-schema-compiler/src/scaffolding/ScaffoldingSchema.ts b/packages/cubejs-schema-compiler/src/scaffolding/ScaffoldingSchema.ts index 77c7fa42a0e8e..0c91881b2cc57 100644 --- a/packages/cubejs-schema-compiler/src/scaffolding/ScaffoldingSchema.ts +++ b/packages/cubejs-schema-compiler/src/scaffolding/ScaffoldingSchema.ts @@ -32,7 +32,7 @@ export type JoinRelationship = 'hasOne' | 'hasMany' | 'belongsTo'; type ColumnsToJoin = { cubeToJoin: string; columnToJoin: string; - tableName: string; + tableName: TableName; }; export type CubeDescriptorMember = { @@ -112,7 +112,7 @@ export type DatabaseSchema = Record; type TableData = { schema: string, table: string, - tableName: string; + tableName: TableName; tableDefinition: ColumnData[], }; @@ -122,7 +122,7 @@ type ScaffoldingSchemaOptions = { }; export class ScaffoldingSchema { - private tableNamesToTables: { [key: string]: TableData[] } = {}; + private tableNamesToTables: Record = {}; public constructor( private readonly dbSchema: DatabaseSchema, @@ -198,7 +198,7 @@ export class ScaffoldingSchema { tableNames.map(tableName => { const [schema, table] = this.parseTableName(tableName); const tableDefinition = this.resolveTableDefinition(tableName); - const definition = { + const definition: TableData = { schema, table, tableDefinition, tableName }; const tableizeName = inflection.tableize(this.fixCase(table)); @@ -237,7 +237,7 @@ export class ScaffoldingSchema { }; } - protected parseTableName(tableName: TableName) { + protected parseTableName(tableName: TableName): [string, string] { let schemaAndTable; if (Array.isArray(tableName)) { schemaAndTable = tableName; From 9f9314022c09b70848f232f405b78abec00558d5 Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Thu, 17 Jul 2025 15:21:41 +0300 Subject: [PATCH 12/15] move JoinGraph to ts --- .../src/compiler/CubeSymbols.ts | 2 +- .../compiler/{JoinGraph.js => JoinGraph.ts} | 203 +++++++++++++----- 2 files changed, 156 insertions(+), 49 deletions(-) rename packages/cubejs-schema-compiler/src/compiler/{JoinGraph.js => JoinGraph.ts} (51%) diff --git a/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts b/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts index d50d521d9d0a8..8e108f8df3bfb 100644 --- a/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts +++ b/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts @@ -857,7 +857,7 @@ export class CubeSymbols { } } - protected funcArguments(func: Function): string[] { + public funcArguments(func: Function): string[] { const funcDefinition = func.toString(); if (!this.funcArgumentsValues[funcDefinition]) { const match = funcDefinition.match(FunctionRegex); diff --git a/packages/cubejs-schema-compiler/src/compiler/JoinGraph.js b/packages/cubejs-schema-compiler/src/compiler/JoinGraph.ts similarity index 51% rename from packages/cubejs-schema-compiler/src/compiler/JoinGraph.js rename to packages/cubejs-schema-compiler/src/compiler/JoinGraph.ts index 179ca2ac5ceba..4f73cebd19bdb 100644 --- a/packages/cubejs-schema-compiler/src/compiler/JoinGraph.js +++ b/packages/cubejs-schema-compiler/src/compiler/JoinGraph.ts @@ -2,52 +2,123 @@ import R from 'ramda'; import Graph from 'node-dijkstra'; import { UserError } from './UserError'; +import type { CubeValidator } from './CubeValidator'; +import type { CubeEvaluator, MeasureDefinition } from './CubeEvaluator'; +import type { CubeDefinition, JoinDefinition } from './CubeSymbols'; +import type { ErrorReporter } from './ErrorReporter'; + +type JoinEdge = { + join: JoinDefinition, + from: string, + to: string, + originalFrom: string, + originalTo: string, +}; + +type JoinTreeJoins = JoinEdge[]; + +type JoinTree = { + root: string, + joins: JoinTreeJoins, +}; + +export type FinishedJoinTree = JoinTree & { + multiplicationFactor: Record, +}; + +export type JoinHint = string | string[]; + +export type JoinHints = JoinHint[]; + export class JoinGraph { - /** - * @param {import('./CubeValidator').CubeValidator} cubeValidator - * @param {import('./CubeEvaluator').CubeEvaluator} cubeEvaluator - */ - constructor(cubeValidator, cubeEvaluator) { + private readonly cubeValidator: CubeValidator; + + private readonly cubeEvaluator: CubeEvaluator; + + // source node -> destination node -> weight + private nodes: Record>; + + // source node -> destination node -> weight + private undirectedNodes: Record>; + + private edges: Record; + + private builtJoins: Record; + + private graph: Graph | null; + + private cachedConnectedComponents: Record | null; + + public constructor(cubeValidator: CubeValidator, cubeEvaluator: CubeEvaluator) { this.cubeValidator = cubeValidator; this.cubeEvaluator = cubeEvaluator; this.nodes = {}; + this.undirectedNodes = {}; this.edges = {}; this.builtJoins = {}; + this.cachedConnectedComponents = null; } - compile(cubes, errorReporter) { - this.edges = R.compose( + public compile(cubes: unknown, errorReporter: ErrorReporter): void { + this.edges = R.compose< + Array, + Array, + Array<[string, JoinEdge][]>, + Array<[string, JoinEdge]>, + Record + >( R.fromPairs, R.unnest, - R.map(v => this.buildJoinEdges(v, errorReporter.inContext(`${v.name} cube`))), + R.map((v: CubeDefinition): [string, JoinEdge][] => this.buildJoinEdges(v, errorReporter.inContext(`${v.name} cube`))), R.filter(this.cubeValidator.isCubeValid.bind(this.cubeValidator)) )(this.cubeEvaluator.cubeList); - this.nodes = R.compose( + + // This requires @types/ramda@0.29 or newer + // @ts-ignore + this.nodes = R.compose< + Record, + Array<[string, JoinEdge]>, + Array, + Record | undefined>, + Record> + >( + // This requires @types/ramda@0.29 or newer + // @ts-ignore R.map(groupedByFrom => R.fromPairs(groupedByFrom.map(join => [join.to, 1]))), - R.groupBy(join => join.from), + R.groupBy((join: JoinEdge) => join.from), R.map(v => v[1]), R.toPairs + // @ts-ignore )(this.edges); + + // @ts-ignore this.undirectedNodes = R.compose( + // @ts-ignore R.map(groupedByFrom => R.fromPairs(groupedByFrom.map(join => [join.from, 1]))), + // @ts-ignore R.groupBy(join => join.to), R.unnest, + // @ts-ignore R.map(v => [v[1], { from: v[1].to, to: v[1].from }]), R.toPairs + // @ts-ignore )(this.edges); + this.graph = new Graph(this.nodes); } - buildJoinEdges(cube, errorReporter) { + protected buildJoinEdges(cube: CubeDefinition, errorReporter: ErrorReporter): Array<[string, JoinEdge]> { + // @ts-ignore return R.compose( + // @ts-ignore R.filter(R.identity), - R.map(join => { - const multipliedMeasures = R.compose( - R.filter( - m => m.sql && this.cubeEvaluator.funcArguments(m.sql).length === 0 && m.sql() === 'count(*)' || + R.map((join: [string, JoinEdge]) => { + const multipliedMeasures: ((m: Record) => MeasureDefinition[]) = R.compose( + R.filter( + (m: MeasureDefinition): boolean => m.sql && this.cubeEvaluator.funcArguments(m.sql).length === 0 && m.sql() === 'count(*)' || ['sum', 'avg', 'count', 'number'].indexOf(m.type) !== -1 ), - R.values + R.values as (input: Record) => MeasureDefinition[] ); const joinRequired = (v) => `primary key for '${v}' is required when join is defined in order to make aggregates work properly`; @@ -66,7 +137,7 @@ export class JoinGraph { return join; }), R.unnest, - R.map(join => [ + R.map((join: [string, JoinDefinition]): [[string, JoinEdge]] => [ [`${cube.name}-${join[0]}`, { join: join[1], from: cube.name, @@ -75,44 +146,65 @@ export class JoinGraph { originalTo: join[0] }] ]), + // @ts-ignore R.filter(R.identity), - R.map(join => { + R.map((join: [string, JoinDefinition]) => { if (!this.cubeEvaluator.cubeExists(join[0])) { errorReporter.error(`Cube ${join[0]} doesn't exist`); return undefined; } return join; }), + // @ts-ignore R.toPairs + // @ts-ignore )(cube.joins || {}); } - buildJoinNode(cube) { - return R.compose( + protected buildJoinNode(cube: CubeDefinition): Record { + return R.compose< + Record, + Array<[string, JoinDefinition]>, + Array<[string, 1]>, + Record + >( R.fromPairs, R.map(v => [v[0], 1]), R.toPairs )(cube.joins || {}); } - buildJoin(cubesToJoin) { + public buildJoin(cubesToJoin: JoinHints): FinishedJoinTree | null { if (!cubesToJoin.length) { return null; } const key = JSON.stringify(cubesToJoin); if (!this.builtJoins[key]) { - const join = R.pipe( + const join = R.pipe< + JoinHints, + Array, + Array, + Array + >( R.map( - cube => this.buildJoinTreeForRoot(cube, R.without([cube], cubesToJoin)) + (cube: JoinHint): JoinTree | null => this.buildJoinTreeForRoot(cube, R.without([cube], cubesToJoin)) ), + // @ts-ignore R.filter(R.identity), - R.sortBy(joinTree => joinTree.joins.length) + R.sortBy((joinTree: JoinTree) => joinTree.joins.length) + // @ts-ignore )(cubesToJoin)[0]; + if (!join) { throw new UserError(`Can't find join path to join ${cubesToJoin.map(v => `'${v}'`).join(', ')}`); } + this.builtJoins[key] = Object.assign(join, { - multiplicationFactor: R.compose( + multiplicationFactor: R.compose< + JoinHints, + Array<[string, boolean]>, + Record + >( R.fromPairs, R.map(v => [this.cubeFromPath(v), this.findMultiplicationFactorFor(this.cubeFromPath(v), join.joins)]) )(cubesToJoin) @@ -121,19 +213,19 @@ export class JoinGraph { return this.builtJoins[key]; } - cubeFromPath(cubePath) { + protected cubeFromPath(cubePath) { if (Array.isArray(cubePath)) { return cubePath[cubePath.length - 1]; } return cubePath; } - buildJoinTreeForRoot(root, cubesToJoin) { + protected buildJoinTreeForRoot(root: JoinHint, cubesToJoin: JoinHints): JoinTree | null { const self = this; if (Array.isArray(root)) { const [newRoot, ...additionalToJoin] = root; if (additionalToJoin.length > 0) { - cubesToJoin = [additionalToJoin].concat(cubesToJoin); + cubesToJoin = [additionalToJoin, ...cubesToJoin]; } root = newRoot; } @@ -157,39 +249,54 @@ export class JoinGraph { nodesJoined[toJoin] = true; return { cubes: path, joins: foundJoins }; }); - }).reduce((a, b) => a.concat(b), []).reduce((joined, res) => { - if (!res || !joined) { - return null; - } - const indexedPairs = R.compose( - R.addIndex(R.map)((j, i) => [i + joined.joins.length, j]) - ); - return { - joins: joined.joins.concat(indexedPairs(res.joins)) - }; - }, { joins: [] }); + }).reduce((a, b) => a.concat(b), []) + // @ts-ignore + .reduce((joined, res) => { + if (!res || !joined) { + return null; + } + const indexedPairs = R.compose< + Array, + Array<[number, JoinEdge]> + >( + R.addIndex(R.map)((j, i) => [i + joined.joins.length, j]) + ); + return { + joins: [...joined.joins, ...indexedPairs(res.joins)], + }; + }, { joins: [] }); if (!result) { return null; } - const pairsSortedByIndex = - R.compose(R.uniq, R.map(indexToJoin => indexToJoin[1]), R.sortBy(indexToJoin => indexToJoin[0])); + const pairsSortedByIndex: (joins: [number, JoinEdge][]) => JoinEdge[] = + R.compose< + Array<[number, JoinEdge]>, + Array<[number, JoinEdge]>, + Array, + Array + >( + R.uniq, + R.map(([_, join]: [number, JoinEdge]) => join), + R.sortBy(([index]: [number, JoinEdge]) => index) + ); return { + // @ts-ignore joins: pairsSortedByIndex(result.joins), root }; } - findMultiplicationFactorFor(cube, joins) { + protected findMultiplicationFactorFor(cube: string, joins: JoinTreeJoins): boolean { const visited = {}; const self = this; - function findIfMultipliedRecursive(currentCube) { + function findIfMultipliedRecursive(currentCube: string) { if (visited[currentCube]) { return false; } visited[currentCube] = true; - function nextNode(nextJoin) { + function nextNode(nextJoin: JoinEdge): string { return nextJoin.from === currentCube ? nextJoin.to : nextJoin.from; } const nextJoins = joins.filter(j => j.from === currentCube || j.to === currentCube); @@ -205,16 +312,16 @@ export class JoinGraph { return findIfMultipliedRecursive(cube); } - checkIfCubeMultiplied(cube, join) { + protected checkIfCubeMultiplied(cube: string, join: JoinEdge): boolean { return join.from === cube && join.join.relationship === 'hasMany' || join.to === cube && join.join.relationship === 'belongsTo'; } - joinsByPath(path) { + protected joinsByPath(path: string[]): JoinEdge[] { return R.range(0, path.length - 1).map(i => this.edges[`${path[i]}-${path[i + 1]}`]); } - connectedComponents() { + public connectedComponents(): Record { if (!this.cachedConnectedComponents) { let componentId = 1; const components = {}; @@ -227,7 +334,7 @@ export class JoinGraph { return this.cachedConnectedComponents; } - findConnectedComponent(componentId, node, components) { + protected findConnectedComponent(componentId: number, node: string, components: Record): void { if (!components[node]) { components[node] = componentId; R.toPairs(this.undirectedNodes[node]) From c39c598e3ed336f8a8b5b952f776972c8d52137b Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Thu, 17 Jul 2025 15:32:41 +0300 Subject: [PATCH 13/15] more types in BaseQuery --- .../src/adapter/BaseQuery.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js b/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js index d9d21e3dacba1..483efe6e3e44d 100644 --- a/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js +++ b/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js @@ -130,6 +130,11 @@ export class BaseQuery { /** @type {import('./BaseTimeDimension').BaseTimeDimension[]} */ timeDimensions; + /** + * @type {import('../compiler/JoinGraph').FinishedJoinTree} + */ + join; + /** * BaseQuery class constructor. * @param {Compilers|*} compilers @@ -2194,6 +2199,11 @@ export class BaseQuery { } } + /** + * @param {import('../compiler/JoinGraph').FinishedJoinTree} join + * @param {Array} subQueryDimensions + * @returns {string} + */ joinQuery(join, subQueryDimensions) { const subQueryDimensionsByCube = R.groupBy(d => this.cubeEvaluator.cubeNameFromPath(d), subQueryDimensions); const joins = join.joins.flatMap( @@ -2218,6 +2228,10 @@ export class BaseQuery { ]); } + /** + * @param {JoinChain} toJoin + * @returns {string} + */ joinSql(toJoin) { const [root, ...rest] = toJoin; const joins = rest.map( From 8d4396dbc16870af435419b94981e024dd4bbdfb Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Thu, 17 Jul 2025 15:42:19 +0300 Subject: [PATCH 14/15] fix --- packages/cubejs-schema-compiler/src/compiler/JoinGraph.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/cubejs-schema-compiler/src/compiler/JoinGraph.ts b/packages/cubejs-schema-compiler/src/compiler/JoinGraph.ts index 4f73cebd19bdb..86fdc5cd46941 100644 --- a/packages/cubejs-schema-compiler/src/compiler/JoinGraph.ts +++ b/packages/cubejs-schema-compiler/src/compiler/JoinGraph.ts @@ -57,6 +57,7 @@ export class JoinGraph { this.edges = {}; this.builtJoins = {}; this.cachedConnectedComponents = null; + this.graph = null; } public compile(cubes: unknown, errorReporter: ErrorReporter): void { @@ -240,11 +241,11 @@ export class JoinGraph { prevNode = toJoin; return { joins: [] }; } - const path = this.graph.path(prevNode, toJoin); + const path = this.graph.path(prevNode, toJoin) as string[] | null; if (!path) { return null; } - const foundJoins = self.joinsByPath(path); + const foundJoins = self.joinsByPath(path!); prevNode = toJoin; nodesJoined[toJoin] = true; return { cubes: path, joins: foundJoins }; From 8419982723e69dc9b0838e7a3d3f41a02346cc1d Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Thu, 17 Jul 2025 16:11:59 +0300 Subject: [PATCH 15/15] fix --- .../src/compiler/JoinGraph.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/cubejs-schema-compiler/src/compiler/JoinGraph.ts b/packages/cubejs-schema-compiler/src/compiler/JoinGraph.ts index 86fdc5cd46941..179450a0cfed9 100644 --- a/packages/cubejs-schema-compiler/src/compiler/JoinGraph.ts +++ b/packages/cubejs-schema-compiler/src/compiler/JoinGraph.ts @@ -223,6 +223,13 @@ export class JoinGraph { protected buildJoinTreeForRoot(root: JoinHint, cubesToJoin: JoinHints): JoinTree | null { const self = this; + + const { graph } = this; + if (graph === null) { + // JoinGraph was not compiled + return null; + } + if (Array.isArray(root)) { const [newRoot, ...additionalToJoin] = root; if (additionalToJoin.length > 0) { @@ -241,11 +248,17 @@ export class JoinGraph { prevNode = toJoin; return { joins: [] }; } - const path = this.graph.path(prevNode, toJoin) as string[] | null; + + const path = graph.path(prevNode, toJoin); if (!path) { return null; } - const foundJoins = self.joinsByPath(path!); + if (!Array.isArray(path)) { + // Unexpected object return from graph, it should do so only when path cost was requested + return null; + } + + const foundJoins = self.joinsByPath(path); prevNode = toJoin; nodesJoined[toJoin] = true; return { cubes: path, joins: foundJoins }; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy