@@ -24,6 +24,7 @@ import {
24
24
timeSeriesFromCustomInterval
25
25
} from '@cubejs-backend/shared' ;
26
26
27
+ import { CubeSymbols } from "../compiler/CubeSymbols" ;
27
28
import { UserError } from '../compiler/UserError' ;
28
29
import { SqlParser } from '../parser/SqlParser' ;
29
30
import { BaseDimension } from './BaseDimension' ;
@@ -307,16 +308,16 @@ export class BaseQuery {
307
308
}
308
309
309
310
prebuildJoin ( ) {
310
- if ( this . useNativeSqlPlanner ) {
311
- // Tesseract doesn't require join to be prebuilt and there's a case where single join can't be built for multi-fact query
312
- // But we need this join for a fallback when using pre-aggregations. So we’ll try to obtain the join but ignore any errors (which may occur if the query is a multi-fact one).
313
- try {
314
- this . join = this . joinGraph . buildJoin ( this . allJoinHints ) ;
315
- } catch ( e ) {
316
- // Ignore
317
- }
318
- } else {
311
+ try {
312
+ // TODO allJoinHints should contain join hints form pre-agg
319
313
this . join = this . joinGraph . buildJoin ( this . allJoinHints ) ;
314
+ } catch ( e ) {
315
+ if ( this . useNativeSqlPlanner ) {
316
+ // Tesseract doesn't require join to be prebuilt and there's a case where single join can't be built for multi-fact query
317
+ // But we need this join for a fallback when using pre-aggregations. So we’ll try to obtain the join but ignore any errors (which may occur if the query is a multi-fact one).
318
+ } else {
319
+ throw e ;
320
+ }
320
321
}
321
322
}
322
323
@@ -363,6 +364,10 @@ export class BaseQuery {
363
364
return this . collectedCubeNames ;
364
365
}
365
366
367
+ /**
368
+ *
369
+ * @returns {Array<Array<string>> }
370
+ */
366
371
get allJoinHints ( ) {
367
372
if ( ! this . collectedJoinHints ) {
368
373
this . collectedJoinHints = this . collectJoinHints ( ) ;
@@ -1203,7 +1208,16 @@ export class BaseQuery {
1203
1208
1204
1209
collectAllMultiStageMembers ( allMemberChildren ) {
1205
1210
const allMembers = R . uniq ( R . flatten ( Object . keys ( allMemberChildren ) . map ( k => [ k ] . concat ( allMemberChildren [ k ] ) ) ) ) ;
1206
- return R . fromPairs ( allMembers . map ( m => ( [ m , this . memberInstanceByPath ( m ) . isMultiStage ( ) ] ) ) ) ;
1211
+ return R . fromPairs ( allMembers . map ( m => {
1212
+ // When `m` is coming from `collectAllMemberChildren`, it can contain `granularities.customGranularityName` in path
1213
+ // And it would mess up with join hints detection
1214
+ const trimmedPath = this
1215
+ . cubeEvaluator
1216
+ . parsePathAnyType ( m )
1217
+ . slice ( 0 , 2 )
1218
+ . join ( '.' ) ;
1219
+ return [ m , this . memberInstanceByPath ( trimmedPath ) . isMultiStage ( ) ] ;
1220
+ } ) ) ;
1207
1221
}
1208
1222
1209
1223
memberInstanceByPath ( m ) {
@@ -1398,7 +1412,7 @@ export class BaseQuery {
1398
1412
// TODO condition should something else instead of rank
1399
1413
multiStageQuery : ! ! withQuery . measures . find ( d => {
1400
1414
const { type } = this . newMeasure ( d ) . definition ( ) ;
1401
- return type === 'rank' || BaseQuery . isCalculatedMeasureType ( type ) ;
1415
+ return type === 'rank' || CubeSymbols . isCalculatedMeasureType ( type ) ;
1402
1416
} ) ,
1403
1417
disableExternalPreAggregations : true ,
1404
1418
} ;
@@ -1992,7 +2006,7 @@ export class BaseQuery {
1992
2006
) ;
1993
2007
1994
2008
if ( shouldBuildJoinForMeasureSelect ) {
1995
- const joinHints = this . collectFrom ( measures , this . collectJoinHintsFor . bind ( this ) , 'collectJoinHintsFor' ) ;
2009
+ const joinHints = this . collectJoinHintsFromMembers ( measures ) ;
1996
2010
const measuresJoin = this . joinGraph . buildJoin ( joinHints ) ;
1997
2011
if ( measuresJoin . multiplicationFactor [ keyCubeName ] ) {
1998
2012
throw new UserError (
@@ -2046,6 +2060,11 @@ export class BaseQuery {
2046
2060
( ! this . safeEvaluateSymbolContext ( ) . ungrouped && this . aggregateSubQueryGroupByClause ( ) || '' ) ;
2047
2061
}
2048
2062
2063
+ /**
2064
+ * @param {Array<BaseMeasure> } measures
2065
+ * @param {string } keyCubeName
2066
+ * @returns {boolean }
2067
+ */
2049
2068
checkShouldBuildJoinForMeasureSelect ( measures , keyCubeName ) {
2050
2069
// When member expression references view, it would have to collect join hints from view
2051
2070
// Consider join A->B, as many-to-one, so B is multiplied and A is not, and member expression like SUM(AB_view.dimB)
@@ -2067,7 +2086,11 @@ export class BaseQuery {
2067
2086
. filter ( member => member . definition ( ) . ownedByCube ) ;
2068
2087
2069
2088
const cubes = this . collectFrom ( nonViewMembers , this . collectCubeNamesFor . bind ( this ) , 'collectCubeNamesFor' ) ;
2070
- const joinHints = this . collectFrom ( nonViewMembers , this . collectJoinHintsFor . bind ( this ) , 'collectJoinHintsFor' ) ;
2089
+ // Not using `collectJoinHintsFromMembers([measure])` because it would collect too many join hints from view
2090
+ const joinHints = [
2091
+ measure . joinHint ,
2092
+ ...this . collectJoinHintsFromMembers ( nonViewMembers ) ,
2093
+ ] ;
2071
2094
if ( R . any ( cubeName => keyCubeName !== cubeName , cubes ) ) {
2072
2095
const measuresJoin = this . joinGraph . buildJoin ( joinHints ) ;
2073
2096
if ( measuresJoin . multiplicationFactor [ keyCubeName ] ) {
@@ -2186,12 +2209,29 @@ export class BaseQuery {
2186
2209
) ;
2187
2210
}
2188
2211
2212
+ /**
2213
+ *
2214
+ * @param {boolean } [excludeTimeDimensions=false]
2215
+ * @returns {Array<Array<string>> }
2216
+ */
2189
2217
collectJoinHints ( excludeTimeDimensions = false ) {
2190
- return this . collectFromMembers (
2191
- excludeTimeDimensions ,
2192
- this . collectJoinHintsFor . bind ( this ) ,
2193
- 'collectJoinHintsFor'
2194
- ) ;
2218
+ const membersToCollectFrom = this . allMembersConcat ( excludeTimeDimensions )
2219
+ . concat ( this . join ? this . join . joins . map ( j => ( {
2220
+ getMembers : ( ) => [ {
2221
+ path : ( ) => null ,
2222
+ cube : ( ) => this . cubeEvaluator . cubeFromPath ( j . originalFrom ) ,
2223
+ definition : ( ) => j . join ,
2224
+ } ]
2225
+ } ) ) : [ ] ) ;
2226
+
2227
+ return this . collectJoinHintsFromMembers ( membersToCollectFrom ) ;
2228
+ }
2229
+
2230
+ collectJoinHintsFromMembers ( members ) {
2231
+ return [
2232
+ ...members . map ( m => m . joinHint ) . filter ( h => h ?. length > 0 ) ,
2233
+ ...this . collectFrom ( members , this . collectJoinHintsFor . bind ( this ) , 'collectJoinHintsFromMembers' ) ,
2234
+ ] ;
2195
2235
}
2196
2236
2197
2237
collectFromMembers ( excludeTimeDimensions , fn , methodName ) {
@@ -2206,6 +2246,11 @@ export class BaseQuery {
2206
2246
return this . collectFrom ( membersToCollectFrom , fn , methodName ) ;
2207
2247
}
2208
2248
2249
+ /**
2250
+ *
2251
+ * @param {boolean } excludeTimeDimensions
2252
+ * @returns {Array<BaseMeasure | BaseDimension | BaseSegment> }
2253
+ */
2209
2254
allMembersConcat ( excludeTimeDimensions ) {
2210
2255
return this . measures
2211
2256
. concat ( this . dimensions )
@@ -2902,7 +2947,7 @@ export class BaseQuery {
2902
2947
funDef = this . countDistinctApprox ( evaluateSql ) ;
2903
2948
} else if ( symbol . type === 'countDistinct' || symbol . type === 'count' && ! symbol . sql && multiplied ) {
2904
2949
funDef = `count(distinct ${ evaluateSql } )` ;
2905
- } else if ( BaseQuery . isCalculatedMeasureType ( symbol . type ) ) {
2950
+ } else if ( CubeSymbols . isCalculatedMeasureType ( symbol . type ) ) {
2906
2951
// TODO calculated measure type will be ungrouped
2907
2952
// if (this.multiStageDimensions.length !== this.dimensions.length) {
2908
2953
// throw new UserError(`Calculated measure '${measurePath}' uses group_by or reduce_by context modifiers while it isn't allowed`);
@@ -2928,23 +2973,12 @@ export class BaseQuery {
2928
2973
return this . primaryKeyCount ( cubeName , true ) ;
2929
2974
}
2930
2975
}
2931
- if ( BaseQuery . isCalculatedMeasureType ( symbol . type ) ) {
2976
+ if ( CubeSymbols . isCalculatedMeasureType ( symbol . type ) ) {
2932
2977
return evaluateSql ;
2933
2978
}
2934
2979
return `${ symbol . type } (${ evaluateSql } )` ;
2935
2980
}
2936
2981
2937
- static isCalculatedMeasureType ( type ) {
2938
- return type === 'number' || type === 'string' || type === 'time' || type === 'boolean' ;
2939
- }
2940
-
2941
- /**
2942
- TODO: support type qualifiers on min and max
2943
- */
2944
- static toMemberDataType ( type ) {
2945
- return this . isCalculatedMeasureType ( type ) ? type : 'number' ;
2946
- }
2947
-
2948
2982
aggregateOnGroupedColumn ( symbol , evaluateSql , topLevelMerge , measurePath ) {
2949
2983
const cumulativeMeasureFilters = ( this . safeEvaluateSymbolContext ( ) . cumulativeMeasureFilters || { } ) [ measurePath ] ;
2950
2984
if ( cumulativeMeasureFilters ) {
@@ -4051,7 +4085,7 @@ export class BaseQuery {
4051
4085
sqlUtils : {
4052
4086
convertTz : ( field ) => field ,
4053
4087
} ,
4054
- securityContext : BaseQuery . contextSymbolsProxyFrom ( { } , allocateParam ) ,
4088
+ securityContext : CubeSymbols . contextSymbolsProxyFrom ( { } , allocateParam ) ,
4055
4089
} ;
4056
4090
}
4057
4091
@@ -4066,41 +4100,7 @@ export class BaseQuery {
4066
4100
}
4067
4101
4068
4102
contextSymbolsProxy ( symbols ) {
4069
- return BaseQuery . contextSymbolsProxyFrom ( symbols , this . paramAllocator . allocateParam . bind ( this . paramAllocator ) ) ;
4070
- }
4071
-
4072
- static contextSymbolsProxyFrom ( symbols , allocateParam ) {
4073
- return new Proxy ( symbols , {
4074
- get : ( target , name ) => {
4075
- const propValue = target [ name ] ;
4076
- const methods = ( paramValue ) => ( {
4077
- filter : ( column ) => {
4078
- if ( paramValue ) {
4079
- const value = Array . isArray ( paramValue ) ?
4080
- paramValue . map ( allocateParam ) :
4081
- allocateParam ( paramValue ) ;
4082
- if ( typeof column === 'function' ) {
4083
- return column ( value ) ;
4084
- } else {
4085
- return `${ column } = ${ value } ` ;
4086
- }
4087
- } else {
4088
- return '1 = 1' ;
4089
- }
4090
- } ,
4091
- requiredFilter : ( column ) => {
4092
- if ( ! paramValue ) {
4093
- throw new UserError ( `Filter for ${ column } is required` ) ;
4094
- }
4095
- return methods ( paramValue ) . filter ( column ) ;
4096
- } ,
4097
- unsafeValue : ( ) => paramValue
4098
- } ) ;
4099
- return methods ( target ) [ name ] ||
4100
- typeof propValue === 'object' && propValue !== null && BaseQuery . contextSymbolsProxyFrom ( propValue , allocateParam ) ||
4101
- methods ( propValue ) ;
4102
- }
4103
- } ) ;
4103
+ return CubeSymbols . contextSymbolsProxyFrom ( symbols , this . paramAllocator . allocateParam . bind ( this . paramAllocator ) ) ;
4104
4104
}
4105
4105
4106
4106
static extractFilterMembers ( filter ) {
@@ -4306,6 +4306,11 @@ export class BaseQuery {
4306
4306
} ) ;
4307
4307
}
4308
4308
4309
+ /**
4310
+ *
4311
+ * @param {boolean } excludeSegments
4312
+ * @returns {Array<BaseMeasure | BaseDimension | BaseSegment> }
4313
+ */
4309
4314
flattenAllMembers ( excludeSegments = false ) {
4310
4315
return R . flatten (
4311
4316
this . measures
@@ -4326,9 +4331,14 @@ export class BaseQuery {
4326
4331
return this . backAliasMembers ( this . flattenAllMembers ( ) ) ;
4327
4332
}
4328
4333
4334
+ /**
4335
+ *
4336
+ * @param {Array<BaseMeasure | BaseDimension | BaseSegment> } members
4337
+ * @returns {Record<string, string> }
4338
+ */
4329
4339
backAliasMembers ( members ) {
4330
4340
const query = this ;
4331
- return members . map (
4341
+ return Object . fromEntries ( members . flatMap (
4332
4342
member => {
4333
4343
const collectedMembers = query . evaluateSymbolSqlWithContext (
4334
4344
( ) => query . collectFrom ( [ member ] , query . collectMemberNamesFor . bind ( query ) , 'collectMemberNamesFor' ) ,
@@ -4343,10 +4353,8 @@ export class BaseQuery {
4343
4353
}
4344
4354
return ! nonAliasSeen ;
4345
4355
} )
4346
- . map ( d => (
4347
- { [ query . cubeEvaluator . byPathAnyType ( d ) . aliasMember ] : memberPath }
4348
- ) ) . reduce ( ( a , b ) => ( { ...a , ...b } ) , { } ) ;
4356
+ . map ( d => [ query . cubeEvaluator . byPathAnyType ( d ) . aliasMember , memberPath ] ) ;
4349
4357
}
4350
- ) . reduce ( ( a , b ) => ( { ... a , ... b } ) , { } ) ;
4358
+ ) ) ;
4351
4359
}
4352
4360
}
0 commit comments