Skip to content

Commit c0466fd

Browse files
authored
fix(schema-compiler): propagate FILTER_PARAMS from view to inner cube's SELECT (#8466)
* chore(schema-compiler): Move backAliasMembers*() from PreAggreggations to BaseQuery * chore(schema-compiler): remove Ramda from BaseGroupFilter * chore(schema-compiler): improve getSqlGenerator() by removing duplicate calls to getDbType() * chore(schema-compiler): improvement in extractFilterMembers * chore(schema-compiler): Improvement in evaluateSymbolSql() * fix(schema-compiler): For views, propagate FILTER_PARAMS to inner cube SELECT * chore(schema-compiler): add tests for FILTER_PARAMS propagation * chore(schema-compiler): fix yaml-compiler tests * fix(schema-compiler): fix backalias resolution in FILTER_PARAMS to exclude segments
1 parent 33b9342 commit c0466fd

File tree

6 files changed

+193
-114
lines changed

6 files changed

+193
-114
lines changed

packages/cubejs-schema-compiler/src/adapter/BaseGroupFilter.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import R from 'ramda';
2-
31
export class BaseGroupFilter {
42
protected readonly values: any;
53

@@ -31,7 +29,7 @@ export class BaseGroupFilter {
3129
return null;
3230
}
3331
return `(${sql})`;
34-
}).filter(R.identity).join(` ${this.operator.toUpperCase()} `);
32+
}).filter(x => x).join(` ${this.operator.toUpperCase()} `);
3533

3634
if (!r.length) {
3735
return null;

packages/cubejs-schema-compiler/src/adapter/BaseQuery.js

Lines changed: 105 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2180,15 +2180,16 @@ export class BaseQuery {
21802180
const memberPathArray = [cubeName, name];
21812181
const memberPath = this.cubeEvaluator.pathFromArray(memberPathArray);
21822182
let type = memberExpressionType;
2183-
if (!type && this.cubeEvaluator.isMeasure(memberPathArray)) {
2184-
type = 'measure';
2185-
}
2186-
if (!type && this.cubeEvaluator.isDimension(memberPathArray)) {
2187-
type = 'dimension';
2188-
}
2189-
if (!type && this.cubeEvaluator.isSegment(memberPathArray)) {
2190-
type = 'segment';
2183+
if (!type) {
2184+
if (this.cubeEvaluator.isMeasure(memberPathArray)) {
2185+
type = 'measure';
2186+
} else if (this.cubeEvaluator.isDimension(memberPathArray)) {
2187+
type = 'dimension';
2188+
} else if (this.cubeEvaluator.isSegment(memberPathArray)) {
2189+
type = 'segment';
2190+
}
21912191
}
2192+
21922193
const parentMember = this.safeEvaluateSymbolContext().currentMember;
21932194
if (this.safeEvaluateSymbolContext().memberChildren && parentMember) {
21942195
this.safeEvaluateSymbolContext().memberChildren[parentMember] = this.safeEvaluateSymbolContext().memberChildren[parentMember] || [];
@@ -2358,7 +2359,7 @@ export class BaseQuery {
23582359

23592360
/**
23602361
* Evaluate escaped SQL-alias for cube or cube's property
2361-
* (measure, dimention).
2362+
* (measure, dimension).
23622363
* @param {string} cubeName
23632364
* @returns string
23642365
*/
@@ -3529,25 +3530,29 @@ export class BaseQuery {
35293530
static extractFilterMembers(filter) {
35303531
if (filter.operator === 'and' || filter.operator === 'or') {
35313532
return filter.values.map(f => BaseQuery.extractFilterMembers(f)).reduce((a, b) => ((a && b) ? { ...a, ...b } : null), {});
3532-
} else if (filter.measure || filter.dimension) {
3533+
} else if (filter.measure) {
3534+
return {
3535+
[filter.measure]: true
3536+
};
3537+
} else if (filter.dimension) {
35333538
return {
3534-
[filter.measure || filter.dimension]: true
3539+
[filter.dimension]: true
35353540
};
35363541
} else {
35373542
return null;
35383543
}
35393544
}
35403545

3541-
static findAndSubTreeForFilterGroup(filter, groupMembers, newGroupFilter) {
3546+
static findAndSubTreeForFilterGroup(filter, groupMembers, newGroupFilter, aliases) {
35423547
if ((filter.operator === 'and' || filter.operator === 'or') && !filter.values?.length) {
35433548
return null;
35443549
}
35453550
const filterMembers = BaseQuery.extractFilterMembers(filter);
3546-
if (filterMembers && Object.keys(filterMembers).every(m => groupMembers.indexOf(m) !== -1)) {
3551+
if (filterMembers && Object.keys(filterMembers).every(m => (groupMembers.indexOf(m) !== -1 || aliases.indexOf(m) !== -1))) {
35473552
return filter;
35483553
}
35493554
if (filter.operator === 'and') {
3550-
const result = filter.values.map(f => BaseQuery.findAndSubTreeForFilterGroup(f, groupMembers, newGroupFilter)).filter(f => !!f);
3555+
const result = filter.values.map(f => BaseQuery.findAndSubTreeForFilterGroup(f, groupMembers, newGroupFilter, aliases)).filter(f => !!f);
35513556
if (!result.length) {
35523557
return null;
35533558
}
@@ -3572,21 +3577,30 @@ export class BaseQuery {
35723577
);
35733578
}
35743579

3575-
static renderFilterParams(filter, filterParamArgs, allocateParam, newGroupFilter) {
3580+
static renderFilterParams(filter, filterParamArgs, allocateParam, newGroupFilter, aliases) {
35763581
if (!filter) {
35773582
return '1 = 1';
35783583
}
35793584

35803585
if (filter.operator === 'and' || filter.operator === 'or') {
35813586
const values = filter.values
3582-
.map(f => BaseQuery.renderFilterParams(f, filterParamArgs, allocateParam, newGroupFilter))
3587+
.map(f => BaseQuery.renderFilterParams(f, filterParamArgs, allocateParam, newGroupFilter, aliases))
35833588
.map(v => ({ filterToWhere: () => v }));
35843589

35853590
return newGroupFilter({ operator: filter.operator, values }).filterToWhere();
35863591
}
35873592

3588-
const filterParams = filter && filter.filterParams();
3589-
const filterParamArg = filterParamArgs.filter(p => p.__member() === filter.measure || p.__member() === filter.dimension)[0];
3593+
const filterParams = filter.filterParams();
3594+
const filterParamArg = filterParamArgs.filter(p => {
3595+
const member = p.__member();
3596+
return member === filter.measure ||
3597+
member === filter.dimension ||
3598+
(aliases[member] && (
3599+
aliases[member] === filter.measure ||
3600+
aliases[member] === filter.dimension
3601+
));
3602+
})[0];
3603+
35903604
if (!filterParamArg) {
35913605
throw new Error(`FILTER_PARAMS arg not found for ${filter.measure || filter.dimension}`);
35923606
}
@@ -3619,15 +3633,25 @@ export class BaseQuery {
36193633
return f.__member();
36203634
});
36213635

3622-
const filter = BaseQuery.findAndSubTreeForFilterGroup(newGroupFilter({ operator: 'and', values: allFilters }), groupMembers, newGroupFilter);
3636+
const aliases = allFilters ?
3637+
allFilters
3638+
.map(v => (v.query ? v.query.allBackAliasMembersExceptSegments() : {}))
3639+
.reduce((a, b) => ({ ...a, ...b }), {})
3640+
: {};
3641+
const filter = BaseQuery.findAndSubTreeForFilterGroup(
3642+
newGroupFilter({ operator: 'and', values: allFilters }),
3643+
groupMembers,
3644+
newGroupFilter,
3645+
Object.values(aliases)
3646+
);
36233647

3624-
return `(${BaseQuery.renderFilterParams(filter, filterParamArgs, allocateParam, newGroupFilter)})`;
3648+
return `(${BaseQuery.renderFilterParams(filter, filterParamArgs, allocateParam, newGroupFilter, aliases)})`;
36253649
};
36263650
}
36273651

36283652
static filterProxyFromAllFilters(allFilters, cubeEvaluator, allocateParam, newGroupFilter) {
36293653
return new Proxy({}, {
3630-
get: (target, name) => {
3654+
get: (_target, name) => {
36313655
if (name === '_objectWithResolvedProperties') {
36323656
return true;
36333657
}
@@ -3644,17 +3668,75 @@ export class BaseQuery {
36443668
return cubeEvaluator.pathFromArray([cubeNameObj.cube, propertyName]);
36453669
},
36463670
toString() {
3671+
// Segments should be excluded because they are evaluated separately in cubeReferenceProxy
3672+
// In other case this falls into the recursive loop/stack exceeded caused by:
3673+
// collectFrom() -> traverseSymbol() -> evaluateSymbolSql() ->
3674+
// evaluateSql() -> resolveSymbolsCall() -> cubeReferenceProxy->toString() ->
3675+
// evaluateSymbolSql() -> evaluateSql()... -> and got here again
3676+
const aliases = allFilters ?
3677+
allFilters
3678+
.map(v => (v.query ? v.query.allBackAliasMembersExceptSegments() : {}))
3679+
.reduce((a, b) => ({ ...a, ...b }), {})
3680+
: {};
3681+
// Filtering aliases that somehow relate to this cube
3682+
const filteredAliases = Object.entries(aliases)
3683+
.filter(([key, value]) => key.startsWith(cubeNameObj.cube) || value.startsWith(cubeNameObj.cube))
3684+
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {});
36473685
const filter = BaseQuery.findAndSubTreeForFilterGroup(
36483686
newGroupFilter({ operator: 'and', values: allFilters }),
36493687
[cubeEvaluator.pathFromArray([cubeNameObj.cube, propertyName])],
3650-
newGroupFilter
3688+
newGroupFilter,
3689+
Object.values(filteredAliases)
36513690
);
3652-
return `(${BaseQuery.renderFilterParams(filter, [this], allocateParam, newGroupFilter)})`;
3691+
3692+
return `(${BaseQuery.renderFilterParams(filter, [this], allocateParam, newGroupFilter, aliases)})`;
36533693
}
36543694
})
36553695
})
36563696
});
36573697
}
36583698
});
36593699
}
3700+
3701+
flattenAllMembers(excludeSegments = false) {
3702+
return R.flatten(
3703+
this.measures
3704+
.concat(this.dimensions)
3705+
.concat(excludeSegments ? [] : this.segments)
3706+
.concat(this.filters)
3707+
.concat(this.measureFilters)
3708+
.concat(this.timeDimensions)
3709+
.map(m => m.getMembers()),
3710+
);
3711+
}
3712+
3713+
allBackAliasMembersExceptSegments() {
3714+
return this.backAliasMembers(this.flattenAllMembers(true));
3715+
}
3716+
3717+
allBackAliasMembers() {
3718+
return this.backAliasMembers(this.flattenAllMembers());
3719+
}
3720+
3721+
backAliasMembers(members) {
3722+
const query = this;
3723+
return members.map(
3724+
member => {
3725+
const collectedMembers = query
3726+
.collectFrom([member], query.collectMemberNamesFor.bind(query), 'collectMemberNamesFor');
3727+
const memberPath = member.expressionPath();
3728+
let nonAliasSeen = false;
3729+
return collectedMembers
3730+
.filter(d => {
3731+
if (!query.cubeEvaluator.byPathAnyType(d).aliasMember) {
3732+
nonAliasSeen = true;
3733+
}
3734+
return !nonAliasSeen;
3735+
})
3736+
.map(d => (
3737+
{ [query.cubeEvaluator.byPathAnyType(d).aliasMember]: memberPath }
3738+
)).reduce((a, b) => ({ ...a, ...b }), {});
3739+
}
3740+
).reduce((a, b) => ({ ...a, ...b }), {});
3741+
}
36603742
}

packages/cubejs-schema-compiler/src/adapter/PreAggregations.js

Lines changed: 2 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ export class PreAggregations {
157157
const queryForSqlEvaluation = this.query.preAggregationQueryForSqlEvaluation(cube, preAggregation);
158158
const partitionInvalidateKeyQueries = queryForSqlEvaluation.partitionInvalidateKeyQueries && queryForSqlEvaluation.partitionInvalidateKeyQueries(cube, preAggregation);
159159

160-
const allBackAliasMembers = PreAggregations.allBackAliasMembers(this.query);
160+
const allBackAliasMembers = this.query.allBackAliasMembers();
161161

162162
const matchedTimeDimension = preAggregation.partitionGranularity && !this.hasCumulativeMeasures &&
163163
this.query.timeDimensions.find(td => {
@@ -292,7 +292,7 @@ export class PreAggregations {
292292
static transformQueryToCanUseForm(query) {
293293
const flattenDimensionMembers = this.flattenDimensionMembers(query);
294294
const sortedDimensions = this.squashDimensions(query, flattenDimensionMembers);
295-
const allBackAliasMembers = this.allBackAliasMembers(query);
295+
const allBackAliasMembers = query.allBackAliasMembers();
296296
const measures = query.measures.concat(query.measureFilters);
297297
const measurePaths = R.uniq(this.flattenMembers(measures).map(m => m.expressionPath()));
298298
const collectLeafMeasures = query.collectLeafMeasures.bind(query);
@@ -426,31 +426,6 @@ export class PreAggregations {
426426
);
427427
}
428428

429-
static backAliasMembers(query, members) {
430-
return members.map(
431-
member => {
432-
const collectedMembers = query
433-
.collectFrom([member], query.collectMemberNamesFor.bind(query), 'collectMemberNamesFor');
434-
const memberPath = member.expressionPath();
435-
let nonAliasSeen = false;
436-
return collectedMembers
437-
.filter(d => {
438-
if (!query.cubeEvaluator.byPathAnyType(d).aliasMember) {
439-
nonAliasSeen = true;
440-
}
441-
return !nonAliasSeen;
442-
})
443-
.map(d => (
444-
{ [query.cubeEvaluator.byPathAnyType(d).aliasMember]: memberPath }
445-
)).reduce((a, b) => ({ ...a, ...b }), {});
446-
}
447-
).reduce((a, b) => ({ ...a, ...b }), {});
448-
}
449-
450-
static allBackAliasMembers(query) {
451-
return this.backAliasMembers(query, this.flattenAllMembers(query));
452-
}
453-
454429
static sortTimeDimensionsWithRollupGranularity(timeDimensions) {
455430
return timeDimensions && R.sortBy(
456431
R.prop(0),
@@ -750,18 +725,6 @@ export class PreAggregations {
750725
);
751726
}
752727

753-
static flattenAllMembers(query) {
754-
return R.flatten(
755-
query.measures
756-
.concat(query.dimensions)
757-
.concat(query.segments)
758-
.concat(query.filters)
759-
.concat(query.measureFilters)
760-
.concat(query.timeDimensions)
761-
.map(m => m.getMembers()),
762-
);
763-
}
764-
765728
// eslint-disable-next-line no-unused-vars
766729
// eslint-disable-next-line @typescript-eslint/no-unused-vars
767730
getCubeLattice(cube, preAggregationName, preAggregation) {

0 commit comments

Comments
 (0)