diff --git a/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js b/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js index 8d293558f7cba..5330a964d7dc8 100644 --- a/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js +++ b/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js @@ -2731,7 +2731,7 @@ class BaseQuery { static emptyParametrizedContextSymbols(cubeEvaluator, allocateParam) { return { - filterParams: BaseQuery.filterProxyFromAllFilters({}, cubeEvaluator, allocateParam), + filterParams: BaseQuery.filterProxyFromAllFilters(null, cubeEvaluator, allocateParam), sqlUtils: { convertTz: (field) => field, }, @@ -2792,14 +2792,16 @@ class BaseQuery { if (name === '_objectWithResolvedProperties') { return true; } - const cubeName = cubeEvaluator.cubeNameFromPath(name); + // allFilters is null whenever it's used to test if the member is owned by cube so it should always render to `1 = 1` + // and do not check cube validity as it's part of compilation step. + const cubeName = allFilters && cubeEvaluator.cubeNameFromPath(name); return new Proxy({ cube: cubeName }, { get: (cubeNameObj, propertyName) => { const filters = - allFilters.filter(f => f.dimension === cubeEvaluator.pathFromArray([cubeNameObj.cube, propertyName])); + allFilters?.filter(f => f.dimension === cubeEvaluator.pathFromArray([cubeNameObj.cube, propertyName])); return { filter: (column) => { - if (!filters.length) { + if (!filters || !filters.length) { return '1 = 1'; } diff --git a/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.js b/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.js index 09b2073527cf3..e7053525e198c 100644 --- a/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.js +++ b/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.js @@ -101,6 +101,8 @@ export class CubeEvaluator extends CubeSymbols { if (member.sql && !member.subQuery) { const funcArgs = this.funcArguments(member.sql); const cubeReferences = this.collectUsedCubeReferences(cube.name, member.sql); + // We won't check for FILTER_PARAMS here as it shouldn't affect ownership and it should obey the same reference rules. + // To affect ownership FILTER_PARAMS can be declared as `${FILTER_PARAMS.Foo.bar.filter(`${Foo.bar}`)}`. if (funcArgs.length > 0 && cubeReferences.length === 0) { ownedByCube = false; } diff --git a/packages/cubejs-schema-compiler/test/integration/postgres/cube-views.test.ts b/packages/cubejs-schema-compiler/test/integration/postgres/cube-views.test.ts index be92fa3b6119b..2a571deae3b8b 100644 --- a/packages/cubejs-schema-compiler/test/integration/postgres/cube-views.test.ts +++ b/packages/cubejs-schema-compiler/test/integration/postgres/cube-views.test.ts @@ -12,6 +12,10 @@ cube(\`Orders\`, { UNION ALL SELECT 2 as id, 2 as product_id, 'completed' as status, '2022-01-02T00:00:00.000Z'::timestamptz as created_at \`, + + refreshKey: { + sql: \`SELECT MAX(created_at) FROM \${Orders.sql()} orders WHERE \${FILTER_PARAMS.Orders.createdAt.filter('created_at')}\` + }, preAggregations: { countByProductName: { @@ -65,11 +69,22 @@ cube(\`Orders\`, { type: \`time\` }, + productId: { + sql: \`product_id\`, + type: \`number\`, + }, + productAndCategory: { sql: \`\${Products.name} || '_' || \${Products.ProductCategories.name}\`, type: \`string\` }, }, + + segments: { + potatoOnly: { + sql: \`\${CUBE}.product_id = 2 AND \${FILTER_PARAMS.Orders.productId.filter(\`\${CUBE.productId}\`)}\`, + }, + }, dataSource: \`default\` }); @@ -217,6 +232,8 @@ view(\`OrdersView\`, { console.log(query.buildSqlAndParams()); + console.log(query.cacheKeyQueries()); + const res = await dbRunner.evaluateQueryWithPreAggregations(query); console.log(JSON.stringify(res)); @@ -328,4 +345,18 @@ view(\`OrdersView\`, { }, { orders_view__product_category: null, }])); + + it('segment with filter params', async () => runQueryTest({ + measures: ['Orders.count'], + segments: [ + 'Orders.potatoOnly' + ], + filters: [{ + member: 'Orders.productId', + operator: 'equals', + values: ['2'], + }] + }, [{ + orders__count: '1', + }])); });