Skip to content

Commit 3168f53

Browse files
authored
feat(tesseract): Switch dimensions and case measures (#9951)
1 parent 0fde75d commit 3168f53

File tree

131 files changed

+8862
-3964
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

131 files changed

+8862
-3964
lines changed

packages/cubejs-backend-native/Cargo.lock

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3232,6 +3232,9 @@ export class BaseQuery {
32323232
}
32333233
if (symbol.case) {
32343234
return this.renderDimensionCase(symbol, cubeName);
3235+
} else if (symbol.type === 'switch') {
3236+
// Dimension of type switch is not supported in BaseQuery, return an empty string to make dependency resolution work.
3237+
return '';
32353238
} else if (symbol.type === 'geo') {
32363239
return this.concatStringsSql([
32373240
this.autoPrefixAndEvaluateSql(cubeName, symbol.latitude.sql, isMemberExpr),
@@ -4210,7 +4213,17 @@ export class BaseQuery {
42104213
time_series_get_range: 'SELECT {{ max_expr }} as {{ quoted_max_name }},\n' +
42114214
'{{ min_expr }} as {{ quoted_min_name }}\n' +
42124215
'FROM {{ from_prepared }}\n' +
4213-
'{% if filter %}WHERE {{ filter }}{% endif %}'
4216+
'{% if filter %}WHERE {{ filter }}{% endif %}',
4217+
calc_groups_join: '{% if original_sql %}{{ original_sql }}\n{% endif %}' +
4218+
'{% for group in groups %}' +
4219+
'{% if original_sql or not loop.first %}CROSS JOIN\n{% endif %}' +
4220+
'(\n' +
4221+
'{% for value in group.values %}' +
4222+
'SELECT {{ value }} as {{ group.name }}' +
4223+
'{% if not loop.last %} UNION ALL\n{% endif %}' +
4224+
'{% endfor %}' +
4225+
') AS {{ group.alias }}\n' +
4226+
'{% endfor %}'
42144227
},
42154228
expressions: {
42164229
column_reference: '{% if table_name %}{{ table_name }}.{% endif %}{{ name }}',

packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,9 @@ export class CubeEvaluator extends CubeSymbols {
547547
let aliasMember;
548548

549549
const member = members[memberName];
550+
if (member.type === 'switch' || member.multiStage) {
551+
ownedByCube = false;
552+
}
550553
if (member.sql && !member.subQuery) {
551554
const funcArgs = this.funcArguments(member.sql);
552555
const { cubeReferencesUsed, evaluatedSql, pathReferencesUsed } = this.collectUsedCubeReferences(cube.name, member.sql);

packages/cubejs-schema-compiler/src/compiler/CubeValidator.ts

Lines changed: 81 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,11 @@ const BaseDimensionWithoutSubQuery = {
132132
enableSuggestions: Joi.boolean().strict(),
133133
format: formatSchema,
134134
meta: Joi.any(),
135+
values: Joi.when('type', {
136+
is: 'switch',
137+
then: Joi.array().items(Joi.string()),
138+
otherwise: Joi.forbidden()
139+
}),
135140
granularities: Joi.when('type', {
136141
is: 'time',
137142
then: Joi.object().pattern(identifierRegex,
@@ -583,13 +588,50 @@ const timeShiftItemOptional = Joi.object({
583588
.xor('name', 'interval')
584589
.and('interval', 'type');
585590

591+
const CaseSchema = Joi.object().keys({
592+
when: Joi.array().items(Joi.object().keys({
593+
sql: Joi.func().required(),
594+
label: Joi.alternatives([
595+
Joi.string(),
596+
Joi.object().keys({
597+
sql: Joi.func().required()
598+
})
599+
])
600+
})),
601+
else: Joi.object().keys({
602+
label: Joi.alternatives([
603+
Joi.string(),
604+
Joi.object().keys({
605+
sql: Joi.func().required()
606+
})
607+
])
608+
})
609+
}).required();
610+
611+
const SwitchCaseSchema = Joi.object().keys({
612+
switch: Joi.func().required(),
613+
when: Joi.array().items(Joi.object().keys({
614+
value: Joi.string().required(),
615+
sql: Joi.func().required()
616+
})),
617+
else: Joi.object().keys({
618+
sql: Joi.func().required()
619+
})
620+
}).required();
621+
622+
const CaseVariants = Joi.alternatives().try(
623+
CaseSchema,
624+
SwitchCaseSchema
625+
);
626+
586627
const MeasuresSchema = Joi.object().pattern(identifierRegex, Joi.alternatives().conditional(Joi.ref('.multiStage'), [
587628
{
588629
is: true,
589630
then: inherit(BaseMeasure, {
590631
multiStage: Joi.boolean().strict(),
591632
type: multiStageMeasureType.required(),
592633
sql: Joi.func(), // TODO .required(),
634+
case: CaseVariants,
593635
groupBy: Joi.func(),
594636
reduceBy: Joi.func(),
595637
addGroupBy: Joi.func(),
@@ -647,53 +689,45 @@ const CalendarTimeShiftItem = Joi.alternatives().try(
647689
})
648690
);
649691

650-
const DimensionsSchema = Joi.object().pattern(identifierRegex, Joi.alternatives().try(
651-
inherit(BaseDimensionWithoutSubQuery, {
652-
case: Joi.object().keys({
653-
when: Joi.array().items(Joi.object().keys({
654-
sql: Joi.func().required(),
655-
label: Joi.alternatives([
656-
Joi.string(),
657-
Joi.object().keys({
658-
sql: Joi.func().required()
659-
})
660-
])
661-
})),
662-
else: Joi.object().keys({
663-
label: Joi.alternatives([
664-
Joi.string(),
665-
Joi.object().keys({
666-
sql: Joi.func().required()
667-
})
668-
])
669-
})
670-
}).required()
671-
}),
672-
inherit(BaseDimensionWithoutSubQuery, {
673-
latitude: Joi.object().keys({
674-
sql: Joi.func().required()
675-
}).required(),
676-
longitude: Joi.object().keys({
677-
sql: Joi.func().required()
678-
}).required()
679-
}),
680-
inherit(BaseDimension, {
681-
sql: Joi.func().required(),
682-
}),
683-
inherit(BaseDimension, {
684-
multiStage: Joi.boolean().valid(true),
685-
type: Joi.any().valid('number').required(),
686-
sql: Joi.func().required(),
687-
addGroupBy: Joi.func(),
688-
}),
689-
// TODO should be valid only for calendar cubes, but this requires significant refactoring
690-
// of all schemas. Left for the future when we'll switch to zod.
691-
inherit(BaseDimensionWithoutSubQuery, {
692-
type: Joi.any().valid('time').required(),
693-
sql: Joi.func().required(),
694-
timeShift: Joi.array().items(CalendarTimeShiftItem),
695-
})
696-
));
692+
const SwitchDimension = Joi.object({
693+
type: Joi.string().valid('switch').required(),
694+
values: Joi.array().items(Joi.string()).min(1).required()
695+
});
696+
697+
const DimensionsSchema = Joi.object().pattern(identifierRegex, Joi.alternatives().conditional(Joi.ref('.type'), {
698+
is: 'switch',
699+
then: SwitchDimension,
700+
otherwise: Joi.alternatives().try(
701+
inherit(BaseDimensionWithoutSubQuery, {
702+
case: CaseVariants.required(),
703+
multiStage: Joi.boolean().strict(),
704+
}),
705+
inherit(BaseDimensionWithoutSubQuery, {
706+
latitude: Joi.object().keys({
707+
sql: Joi.func().required()
708+
}).required(),
709+
longitude: Joi.object().keys({
710+
sql: Joi.func().required()
711+
}).required()
712+
}),
713+
inherit(BaseDimension, {
714+
sql: Joi.func().required(),
715+
}),
716+
inherit(BaseDimension, {
717+
multiStage: Joi.boolean().valid(true),
718+
type: Joi.any().valid('number').required(),
719+
sql: Joi.func().required(),
720+
addGroupBy: Joi.func(),
721+
}),
722+
// TODO should be valid only for calendar cubes, but this requires significant refactoring
723+
// of all schemas. Left for the future when we'll switch to zod.
724+
inherit(BaseDimensionWithoutSubQuery, {
725+
type: Joi.any().valid('time').required(),
726+
sql: Joi.func().required(),
727+
timeShift: Joi.array().items(CalendarTimeShiftItem),
728+
})
729+
)
730+
}));
697731

698732
const SegmentsSchema = Joi.object().pattern(identifierRegex, Joi.object().keys({
699733
aliases: Joi.array().items(Joi.string()),

packages/cubejs-schema-compiler/src/compiler/transpilers/CubePropContextTranspiler.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export const transpiledFieldsPatterns: Array<RegExp> = [
1919
/^measures\.[_a-zA-Z][_a-zA-Z0-9]*\.(orderBy|order_by)\.[0-9]+\.sql$/,
2020
/^measures\.[_a-zA-Z][_a-zA-Z0-9]*\.(timeShift|time_shift)\.[0-9]+\.(timeDimension|time_dimension)$/,
2121
/^measures\.[_a-zA-Z][_a-zA-Z0-9]*\.(reduceBy|reduce_by|groupBy|group_by|addGroupBy|add_group_by)$/,
22+
/^(measures|dimensions)\.[_a-zA-Z][_a-zA-Z0-9]*\.case\.switch$/,
2223
/^dimensions\.[_a-zA-Z][_a-zA-Z0-9]*\.(reduceBy|reduce_by|groupBy|group_by|addGroupBy|add_group_by)$/,
2324
/^(preAggregations|pre_aggregations)\.[_a-zA-Z][_a-zA-Z0-9]*\.indexes\.[_a-zA-Z][_a-zA-Z0-9]*\.columns$/,
2425
/^(preAggregations|pre_aggregations)\.[_a-zA-Z][_a-zA-Z0-9]*\.(timeDimensionReference|timeDimension|time_dimension|segments|dimensions|measures|rollups|segmentReferences|dimensionReferences|measureReferences|rollupReferences)$/,

0 commit comments

Comments
 (0)