Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Split view support #7308

Merged
merged 1 commit into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 45 additions & 13 deletions packages/cubejs-schema-compiler/src/compiler/CubeSymbols.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,13 @@
R.sortBy(c => !!c.isView),
)(cubes);
for (const cube of sortedByDependency) {
this.symbols[cube.name] = this.transform(cube.name, errorReporter.inContext(`${cube.name} cube`));
const splitViews = {};
this.symbols[cube.name] = this.transform(cube.name, errorReporter.inContext(`${cube.name} cube`), splitViews);
for (const viewName of Object.keys(splitViews)) {
// TODO can we define it when cubeList is defined?
this.cubeList.push(splitViews[viewName]);
this.symbols[viewName] = splitViews[viewName];

Check warning on line 45 in packages/cubejs-schema-compiler/src/compiler/CubeSymbols.js

View check run for this annotation

Codecov / codecov/patch

packages/cubejs-schema-compiler/src/compiler/CubeSymbols.js#L44-L45

Added lines #L44 - L45 were not covered by tests
}
}
}

Expand Down Expand Up @@ -110,7 +116,7 @@
return cubeObject;
}

transform(cubeName, errorReporter) {
transform(cubeName, errorReporter, splitViews) {
const cube = this.getCubeDefinition(cubeName);
const duplicateNames = R.compose(
R.map(nameToDefinitions => nameToDefinitions[0]),
Expand Down Expand Up @@ -138,7 +144,7 @@
}

if (this.evaluateViews) {
this.prepareIncludes(cube, errorReporter);
this.prepareIncludes(cube, errorReporter, splitViews);
}

return Object.assign(
Expand Down Expand Up @@ -209,32 +215,36 @@
/**
* @protected
*/
prepareIncludes(cube, errorReporter) {
prepareIncludes(cube, errorReporter, splitViews) {
if (!cube.includes && !cube.cubes) {
return;
}
const types = ['measures', 'dimensions', 'segments'];
for (const type of types) {
const cubeIncludes = cube.cubes && this.membersFromCubes(cube.cubes, type, errorReporter) || [];
const cubeIncludes = cube.cubes && this.membersFromCubes(cube, cube.cubes, type, errorReporter, splitViews) || [];
const includes = cube.includes && this.membersFromIncludeExclude(cube.includes, cube.name, type) || [];
const excludes = cube.excludes && this.membersFromIncludeExclude(cube.excludes, cube.name, type) || [];
// cube includes will take precedence in case of member clash
const finalIncludes = this.diffByMember(this.diffByMember(includes, cubeIncludes).concat(cubeIncludes), excludes);
const includeMembers = this.generateIncludeMembers(finalIncludes, cube.name, type);
for (const [memberName, memberDefinition] of includeMembers) {
if (cube[type]?.[memberName]) {
errorReporter.error(`Included member '${memberName}' conflicts with existing member of '${cube.name}'. Please consider excluding this member.`);
} else {
cube[type][memberName] = memberDefinition;
}
this.applyIncludeMembers(includeMembers, cube, type, errorReporter);

Check warning on line 230 in packages/cubejs-schema-compiler/src/compiler/CubeSymbols.js

View check run for this annotation

Codecov / codecov/patch

packages/cubejs-schema-compiler/src/compiler/CubeSymbols.js#L230

Added line #L230 was not covered by tests
}
}

applyIncludeMembers(includeMembers, cube, type, errorReporter) {
for (const [memberName, memberDefinition] of includeMembers) {

Check warning on line 235 in packages/cubejs-schema-compiler/src/compiler/CubeSymbols.js

View check run for this annotation

Codecov / codecov/patch

packages/cubejs-schema-compiler/src/compiler/CubeSymbols.js#L234-L235

Added lines #L234 - L235 were not covered by tests
if (cube[type]?.[memberName]) {
errorReporter.error(`Included member '${memberName}' conflicts with existing member of '${cube.name}'. Please consider excluding this member.`);
} else {
cube[type][memberName] = memberDefinition;

Check warning on line 239 in packages/cubejs-schema-compiler/src/compiler/CubeSymbols.js

View check run for this annotation

Codecov / codecov/patch

packages/cubejs-schema-compiler/src/compiler/CubeSymbols.js#L237-L239

Added lines #L237 - L239 were not covered by tests
}
}
}

/**
* @protected
*/
membersFromCubes(cubes, type, errorReporter) {
membersFromCubes(parentCube, cubes, type, errorReporter, splitViews) {

Check warning on line 247 in packages/cubejs-schema-compiler/src/compiler/CubeSymbols.js

View check run for this annotation

Codecov / codecov/patch

packages/cubejs-schema-compiler/src/compiler/CubeSymbols.js#L247

Added line #L247 was not covered by tests
return R.unnest(cubes.map(cubeInclude => {
const fullPath = this.evaluateReferences(null, cubeInclude.joinPath, { collectJoinHints: true });
const split = fullPath.split('.');
Expand Down Expand Up @@ -277,7 +287,29 @@
member: `${cubeReference}.${exclude}`
} : undefined;
});
return this.diffByMember(includes.filter(Boolean), excludes.filter(Boolean));

const finalIncludes = this.diffByMember(includes.filter(Boolean), excludes.filter(Boolean));

Check warning on line 291 in packages/cubejs-schema-compiler/src/compiler/CubeSymbols.js

View check run for this annotation

Codecov / codecov/patch

packages/cubejs-schema-compiler/src/compiler/CubeSymbols.js#L291

Added line #L291 was not covered by tests

if (cubeInclude.split) {
const viewName = `${parentCube.name}_${cubeName}`;
let splitViewDef = splitViews[viewName];

Check warning on line 295 in packages/cubejs-schema-compiler/src/compiler/CubeSymbols.js

View check run for this annotation

Codecov / codecov/patch

packages/cubejs-schema-compiler/src/compiler/CubeSymbols.js#L294-L295

Added lines #L294 - L295 were not covered by tests
if (!splitViewDef) {
splitViews[viewName] = this.createCube({

Check warning on line 297 in packages/cubejs-schema-compiler/src/compiler/CubeSymbols.js

View check run for this annotation

Codecov / codecov/patch

packages/cubejs-schema-compiler/src/compiler/CubeSymbols.js#L297

Added line #L297 was not covered by tests
name: viewName,
isView: true,
// TODO might worth adding to validation as it goes around it right now
isSplitView: true,
});
splitViewDef = splitViews[viewName];

Check warning on line 303 in packages/cubejs-schema-compiler/src/compiler/CubeSymbols.js

View check run for this annotation

Codecov / codecov/patch

packages/cubejs-schema-compiler/src/compiler/CubeSymbols.js#L303

Added line #L303 was not covered by tests
}

const includeMembers = this.generateIncludeMembers(finalIncludes, parentCube.name, type);
this.applyIncludeMembers(includeMembers, splitViewDef, type, errorReporter);

Check warning on line 307 in packages/cubejs-schema-compiler/src/compiler/CubeSymbols.js

View check run for this annotation

Codecov / codecov/patch

packages/cubejs-schema-compiler/src/compiler/CubeSymbols.js#L306-L307

Added lines #L306 - L307 were not covered by tests

return [];
} else {
return finalIncludes;

Check warning on line 311 in packages/cubejs-schema-compiler/src/compiler/CubeSymbols.js

View check run for this annotation

Codecov / codecov/patch

packages/cubejs-schema-compiler/src/compiler/CubeSymbols.js#L309-L311

Added lines #L309 - L311 were not covered by tests
}
}));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,7 @@ const viewSchema = inherit(baseSchema, {
Joi.object().keys({
joinPath: Joi.func().required(),
prefix: Joi.boolean(),
split: Joi.boolean(),
alias: Joi.string(),
includes: Joi.alternatives([
Joi.string().valid('*'),
Expand All @@ -553,6 +554,8 @@ const viewSchema = inherit(baseSchema, {
]))
]).required(),
excludes: Joi.array().items(Joi.string().required()),
}).oxor('split', 'prefix').messages({
'object.oxor': 'Using split together with prefix is not supported'
})
),
});
Expand Down Expand Up @@ -641,6 +644,6 @@ export class CubeValidator {
}

isCubeValid(cube) {
return this.validCubes[cube.name];
return this.validCubes[cube.name] || cube.isSplitView;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,17 @@ view(\`OrdersView\`, {
view(\`OrdersView2\`, {
includes: [Orders.count],
});

view(\`OrdersView3\`, {
cubes: [{
join_path: Orders,
includes: '*'
}, {
join_path: Orders.Products.ProductCategories,
includes: '*',
split: true
}]
});
`);

async function runQueryTest(q: any, expectedResult: any, additionalTest?: (query: BaseQuery) => any) {
Expand Down Expand Up @@ -400,4 +411,13 @@ view(\`OrdersView2\`, {
const cube = metaTransformer.cubes.find(c => c.config.name === 'Orders');
expect(cube.config.measures.filter((({ isVisible }) => isVisible)).length).toBe(0);
});

it('split views', async () => runQueryTest({
measures: ['OrdersView3.count'],
dimensions: ['OrdersView3_ProductCategories.name'],
order: [{ id: 'OrdersView3_ProductCategories.name' }],
}, [{
orders_view3__count: '2',
orders_view3__product_categories__name: 'Groceries',
}]));
});
Loading