@cubejs-backend/schema-compiler 0.35.10 → 0.35.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -195,13 +195,19 @@ class BaseQuery {
195
195
  memberToAlias: this.options.memberToAlias,
196
196
  expressionParams: this.options.expressionParams,
197
197
  convertTzForRawTimeDimension: this.options.convertTzForRawTimeDimension,
198
+ from: this.options.from,
199
+ postAggregateQuery: this.options.postAggregateQuery,
200
+ postAggregateDimensions: this.options.postAggregateDimensions,
198
201
  });
202
+ this.from = this.options.from;
203
+ this.postAggregateQuery = this.options.postAggregateQuery;
199
204
  this.timezone = this.options.timezone;
200
205
  this.rowLimit = this.options.rowLimit;
201
206
  this.offset = this.options.offset;
202
207
  this.preAggregations = this.newPreAggregations();
203
208
  this.measures = (this.options.measures || []).map(this.newMeasure.bind(this));
204
209
  this.dimensions = (this.options.dimensions || []).map(this.newDimension.bind(this));
210
+ this.postAggregateDimensions = (this.options.postAggregateDimensions || []).map(this.newDimension.bind(this));
205
211
  this.segments = (this.options.segments || []).map(this.newSegment.bind(this));
206
212
  this.order = this.options.order || [];
207
213
  const filters = this.extractFiltersAsTree(this.options.filters || []);
@@ -405,6 +411,10 @@ class BaseQuery {
405
411
  buildParamAnnotatedSql() {
406
412
  let sql;
407
413
  let preAggForQuery;
414
+ // TODO Most probably should be called later than here but avoids errors during pre-aggregation match for now
415
+ if (this.from) {
416
+ return this.simpleQuery();
417
+ }
408
418
  if (!this.options.preAggregationQuery) {
409
419
  preAggForQuery =
410
420
  this.preAggregations.findPreAggregationForQuery();
@@ -413,7 +423,7 @@ class BaseQuery {
413
423
  }
414
424
  }
415
425
  if (preAggForQuery) {
416
- const { multipliedMeasures, regularMeasures, cumulativeMeasures, } = this.fullKeyQueryAggregateMeasures();
426
+ const { multipliedMeasures, regularMeasures, cumulativeMeasures, withQueries, postAggregateMembers, } = this.fullKeyQueryAggregateMeasures();
417
427
  if (cumulativeMeasures.length === 0) {
418
428
  sql = this.preAggregations.rollupPreAggregation(preAggForQuery, this.measures, true);
419
429
  }
@@ -564,6 +574,9 @@ class BaseQuery {
564
574
  // eslint-disable-next-line prefer-template
565
575
  const inlineWhereConditions = [];
566
576
  const commonQuery = this.rewriteInlineWhere(() => this.commonQuery(), inlineWhereConditions);
577
+ if (this.postAggregateQuery) {
578
+ return `${commonQuery} ${this.baseWhere(this.allFilters.concat(inlineWhereConditions))}`;
579
+ }
567
580
  return `${commonQuery} ${this.baseWhere(this.allFilters.concat(inlineWhereConditions))}` +
568
581
  this.groupByClause() +
569
582
  this.baseHaving(this.measureFilters) +
@@ -575,10 +588,14 @@ class BaseQuery {
575
588
  * @returns {string}
576
589
  */
577
590
  fullKeyQueryAggregate() {
578
- const { multipliedMeasures, regularMeasures, cumulativeMeasures, } = this.fullKeyQueryAggregateMeasures();
579
- if (!multipliedMeasures.length && !cumulativeMeasures.length) {
591
+ if (this.from) {
592
+ return this.simpleQuery();
593
+ }
594
+ const { multipliedMeasures, regularMeasures, cumulativeMeasures, withQueries, postAggregateMembers, } = this.fullKeyQueryAggregateMeasures();
595
+ if (!multipliedMeasures.length && !cumulativeMeasures.length && !postAggregateMembers.length) {
580
596
  return this.simpleQuery();
581
597
  }
598
+ const renderedWithQueries = withQueries.map(q => this.renderWithQuery(q));
582
599
  let toJoin;
583
600
  if (this.options.preAggregationQuery) {
584
601
  const allRegular = regularMeasures.concat(cumulativeMeasures
@@ -600,7 +617,9 @@ class BaseQuery {
600
617
  .concat(ramda_1.default.pipe(ramda_1.default.groupBy(m => m.cube().name), ramda_1.default.toPairs, ramda_1.default.map(([keyCubeName, measures]) => this
601
618
  .withCubeAliasPrefix(`${this.aliasName(keyCubeName)}_key`, () => this.aggregateSubQuery(keyCubeName, measures))))(multipliedMeasures)).concat(ramda_1.default.map(([multiplied, measure]) => this.withCubeAliasPrefix(`${this.aliasName(measure.measure.replace('.', '_'))}_cumulative`, () => this.overTimeSeriesQuery(multiplied
602
619
  ? (measures, filters) => this.aggregateSubQuery(measures[0].cube().name, measures, filters)
603
- : this.regularMeasuresSubQuery.bind(this), measure, false)))(cumulativeMeasures));
620
+ : this.regularMeasuresSubQuery.bind(this), measure, false)))(cumulativeMeasures)
621
+ // TODO SELECT *
622
+ ).concat(postAggregateMembers.map(m => `SELECT * FROM ${m.alias}`));
604
623
  }
605
624
  // Move regular measures to multiplied ones if there're same
606
625
  // cubes to calculate. Most of the times it'll be much faster to
@@ -631,36 +650,196 @@ class BaseQuery {
631
650
  toJoin = ramda_1.default.pipe(ramda_1.default.groupBy(m => m.cube().name), ramda_1.default.toPairs, ramda_1.default.map(([keyCubeName, measures]) => this.withCubeAliasPrefix(`${keyCubeName}_key`, () => this.aggregateSubQuery(keyCubeName, measures))))(measuresList);
632
651
  }
633
652
  }
634
- return this.joinFullKeyQueryAggregate(multipliedMeasures, regularMeasures, cumulativeMeasures, toJoin);
653
+ const postAggregateMeasures = ramda_1.default.flatten(postAggregateMembers.map(m => m.measures)).map(m => this.newMeasure(m));
654
+ return this.withQueries(this.joinFullKeyQueryAggregate(
655
+ // TODO separate param?
656
+ multipliedMeasures.concat(postAggregateMeasures), regularMeasures, cumulativeMeasures, toJoin), renderedWithQueries);
635
657
  }
636
658
  joinFullKeyQueryAggregate(multipliedMeasures, regularMeasures, cumulativeMeasures, toJoin) {
659
+ return this.outerMeasuresJoinFullKeyQueryAggregate(multipliedMeasures.concat(regularMeasures).concat(cumulativeMeasures.map(([multiplied, measure]) => measure)), this.measures, toJoin);
660
+ }
661
+ outerMeasuresJoinFullKeyQueryAggregate(innerMembers, outerMembers, toJoin) {
637
662
  const renderedReferenceContext = {
638
- renderedReference: ramda_1.default.pipe(ramda_1.default.map(m => [m.measure, m.aliasName()]), ramda_1.default.fromPairs)(multipliedMeasures.concat(regularMeasures).concat(cumulativeMeasures.map(([multiplied, measure]) => measure))),
663
+ renderedReference: ramda_1.default.pipe(ramda_1.default.map(m => [m.measure || m.dimension, m.aliasName()]), ramda_1.default.fromPairs)(innerMembers),
639
664
  };
640
665
  const join = ramda_1.default.drop(1, toJoin)
641
666
  .map((q, i) => (this.dimensionAliasNames().length ?
642
- `INNER JOIN (${q}) as q_${i + 1} ON ${this.dimensionsJoinCondition(`q_${i}`, `q_${i + 1}`)}` :
643
- `, (${q}) as q_${i + 1}`)).join('\n');
644
- const columnsToSelect = this.evaluateSymbolSqlWithContext(() => this.dimensionColumns('q_0').concat(this.measures.map(m => m.selectColumns())).join(', '), renderedReferenceContext);
645
- const queryHasNoRemapping = this.evaluateSymbolSqlWithContext(() => this.dimensionsForSelect().concat(this.measures).every(r => r.hasNoRemapping()), renderedReferenceContext);
667
+ `INNER JOIN ${this.wrapInParenthesis((q))} as q_${i + 1} ON ${this.dimensionsJoinCondition(`q_${i}`, `q_${i + 1}`)}` :
668
+ `, ${this.wrapInParenthesis(q)} as q_${i + 1}`)).join('\n');
669
+ const columnsToSelect = this.evaluateSymbolSqlWithContext(() => this.dimensionColumns('q_0').concat(outerMembers.map(m => m.selectColumns())).join(', '), renderedReferenceContext);
670
+ const queryHasNoRemapping = this.evaluateSymbolSqlWithContext(() => this.dimensionsForSelect().concat(outerMembers).every(r => r.hasNoRemapping()), renderedReferenceContext);
646
671
  const havingFilters = this.evaluateSymbolSqlWithContext(() => this.baseWhere(this.measureFilters), renderedReferenceContext);
647
672
  // TODO all having filters should be pushed down
648
673
  // subQuery dimensions can introduce projection remapping
649
674
  if (toJoin.length === 1 &&
650
675
  this.measureFilters.length === 0 &&
651
- this.measures.filter(m => m.expression).length === 0 &&
676
+ outerMembers.filter(m => m.expression).length === 0 &&
652
677
  queryHasNoRemapping) {
653
678
  return `${toJoin[0].replace(/^SELECT/, `SELECT ${this.topLimit()}`)} ${this.orderBy()}${this.groupByDimensionLimit()}`;
654
679
  }
655
- return `SELECT ${this.topLimit()}${columnsToSelect} FROM (${toJoin[0]}) as q_0 ${join}${havingFilters}${this.orderBy()}${this.groupByDimensionLimit()}`;
680
+ return `SELECT ${this.topLimit()}${columnsToSelect} FROM ${this.wrapInParenthesis(toJoin[0])} as q_0 ${join}${havingFilters}${this.orderBy()}${this.groupByDimensionLimit()}`;
681
+ }
682
+ wrapInParenthesis(select) {
683
+ return select.trim().match(/^SELECT/ig) ? `(${select})` : select;
684
+ }
685
+ withQueries(select, withQueries) {
686
+ if (!withQueries || !withQueries.length) {
687
+ return select;
688
+ }
689
+ // TODO escape alias
690
+ return `WITH\n${withQueries.map(q => `${q.alias} AS (${q.query})`).join(',\n')}\n${select}`;
656
691
  }
657
692
  fullKeyQueryAggregateMeasures(context) {
658
693
  const measureToHierarchy = this.collectRootMeasureToHieararchy(context);
659
- const measuresToRender = (multiplied, cumulative) => ramda_1.default.pipe(ramda_1.default.values, ramda_1.default.flatten, ramda_1.default.filter(m => m.multiplied === multiplied && this.newMeasure(m.measure).isCumulative() === cumulative), ramda_1.default.map(m => m.measure), ramda_1.default.uniq, ramda_1.default.map(m => this.newMeasure(m)));
694
+ const allMemberChildren = this.collectAllMemberChildren(context);
695
+ const measuresToRender = (multiplied, cumulative) => ramda_1.default.pipe(ramda_1.default.values, ramda_1.default.flatten, ramda_1.default.filter(m => m.multiplied === multiplied && this.newMeasure(m.measure).isCumulative() === cumulative && !m.postAggregate), ramda_1.default.map(m => m.measure), ramda_1.default.uniq, ramda_1.default.map(m => this.newMeasure(m)));
660
696
  const multipliedMeasures = measuresToRender(true, false)(measureToHierarchy);
661
697
  const regularMeasures = measuresToRender(false, false)(measureToHierarchy);
662
698
  const cumulativeMeasures = ramda_1.default.pipe(ramda_1.default.map(multiplied => ramda_1.default.xprod([multiplied], measuresToRender(multiplied, true)(measureToHierarchy))), ramda_1.default.unnest)([false, true]);
663
- return { multipliedMeasures, regularMeasures, cumulativeMeasures };
699
+ const withQueries = [];
700
+ const postAggregateMembers = (this.allMembersConcat(false))
701
+ .filter(m => m.definition && m.definition()?.postAggregate)
702
+ .map(m => m.measure || m.dimension)
703
+ .map(m => this.postAggregateWithQueries(m, { dimensions: this.dimensions.map(d => d.dimension), postAggregateDimensions: this.dimensions.map(d => d.dimension), filters: this.filters }, allMemberChildren, withQueries));
704
+ const usedWithQueries = {};
705
+ postAggregateMembers.forEach(m => this.collectUsedWithQueries(usedWithQueries, m));
706
+ return {
707
+ multipliedMeasures,
708
+ regularMeasures,
709
+ cumulativeMeasures,
710
+ postAggregateMembers,
711
+ withQueries: withQueries.filter(q => usedWithQueries[q.alias])
712
+ };
713
+ }
714
+ collectAllMemberChildren(context) {
715
+ return this.collectFromMembers(false, (fn) => {
716
+ const memberChildren = {};
717
+ this.evaluateSymbolSqlWithContext(fn, { ...context, memberChildren });
718
+ return memberChildren;
719
+ }, context ? ['collectAllMemberChildren', JSON.stringify(context)] : 'collectAllMemberChildren').reduce((a, b) => ({ ...a, ...b }), {});
720
+ }
721
+ postAggregateWithQueries(member, queryContext, memberChildren, withQueries) {
722
+ let memberFrom = memberChildren[member]
723
+ ?.map(child => this.postAggregateWithQueries(child, this.childrenPostAggregateContext(member, queryContext), memberChildren, withQueries));
724
+ const unionFromDimensions = memberFrom ? ramda_1.default.uniq(ramda_1.default.flatten(memberFrom.map(f => f.dimensions))) : queryContext.dimensions;
725
+ const unionDimensionsContext = { ...queryContext, dimensions: unionFromDimensions.filter(d => !this.newDimension(d).isPostAggregate()) };
726
+ // TODO is calling postAggregateWithQueries twice optimal? If so make sure to keep only used CTE
727
+ memberFrom = memberChildren[member] &&
728
+ ramda_1.default.uniqBy(f => f.alias, memberChildren[member].map(child => this.postAggregateWithQueries(child, this.childrenPostAggregateContext(member, unionDimensionsContext), memberChildren, withQueries)));
729
+ const selfContext = this.selfPostAggregateContext(member, queryContext, unionDimensionsContext);
730
+ const subQuery = {
731
+ ...selfContext,
732
+ ...(this.cubeEvaluator.isMeasure(member) ? { measures: [member] } : { measures: [], dimensions: ramda_1.default.uniq(selfContext.dimensions.concat(member)) }),
733
+ memberFrom,
734
+ };
735
+ if (!memberFrom) {
736
+ const postAggregateMember = subQuery.measures.find(m => this.newMeasure(m).isPostAggregate()) || subQuery.dimensions.find(m => this.newDimension(m).isPostAggregate());
737
+ if (postAggregateMember) {
738
+ throw new Error(`Post aggregate member '${postAggregateMember}' lacks FROM clause in sub query: ${JSON.stringify(subQuery)}`);
739
+ }
740
+ }
741
+ const foundWith = withQueries.find(({ alias, ...q }) => ramda_1.default.equals(subQuery, q));
742
+ if (foundWith) {
743
+ return foundWith;
744
+ }
745
+ subQuery.alias = `cte_${withQueries.length}`;
746
+ withQueries.push(subQuery);
747
+ return subQuery;
748
+ }
749
+ collectUsedWithQueries(usedQueries, member) {
750
+ usedQueries[member.alias] = true;
751
+ member.memberFrom?.forEach(m => this.collectUsedWithQueries(usedQueries, m));
752
+ }
753
+ childrenPostAggregateContext(memberPath, queryContext) {
754
+ let member;
755
+ if (this.cubeEvaluator.isMeasure(memberPath)) {
756
+ member = this.newMeasure(memberPath);
757
+ }
758
+ else if (this.cubeEvaluator.isDimension(memberPath)) {
759
+ member = this.newDimension(memberPath);
760
+ }
761
+ const memberDef = member.definition();
762
+ // TODO can addGroupBy replaced by something else?
763
+ if (memberDef.addGroupByReferences) {
764
+ queryContext = { ...queryContext, dimensions: ramda_1.default.uniq(queryContext.dimensions.concat(memberDef.addGroupByReferences)) };
765
+ }
766
+ return queryContext;
767
+ }
768
+ selfPostAggregateContext(memberPath, queryContext, unionDimensionsContext) {
769
+ let member;
770
+ if (this.cubeEvaluator.isMeasure(memberPath)) {
771
+ member = this.newMeasure(memberPath);
772
+ }
773
+ else if (this.cubeEvaluator.isDimension(memberPath)) {
774
+ member = this.newDimension(memberPath);
775
+ // TODO is it right place to replace context?
776
+ // if (member.definition().type === 'rank') {
777
+ // queryContext = unionDimensionsContext;
778
+ // }
779
+ }
780
+ const memberDef = member.definition();
781
+ if (memberDef.reduceByReferences) {
782
+ queryContext = {
783
+ ...queryContext,
784
+ postAggregateDimensions: ramda_1.default.difference(queryContext.postAggregateDimensions, memberDef.reduceByReferences),
785
+ // dimensions: R.uniq(queryContext.dimensions.concat(memberDef.reduceByReferences))
786
+ };
787
+ }
788
+ if (memberDef.groupByReferences) {
789
+ queryContext = {
790
+ ...queryContext,
791
+ postAggregateDimensions: ramda_1.default.intersection(queryContext.postAggregateDimensions, memberDef.groupByReferences)
792
+ };
793
+ }
794
+ return queryContext;
795
+ }
796
+ renderWithQuery(withQuery) {
797
+ const fromMeasures = withQuery.memberFrom && ramda_1.default.uniq(ramda_1.default.flatten(withQuery.memberFrom.map(f => f.measures)));
798
+ // TODO get rid of this postAggregate filter
799
+ const fromDimensions = withQuery.memberFrom && ramda_1.default.uniq(ramda_1.default.flatten(withQuery.memberFrom.map(f => f.dimensions)));
800
+ const renderedReferenceContext = {
801
+ renderedReference: withQuery.memberFrom && ramda_1.default.fromPairs(ramda_1.default.unnest(withQuery.memberFrom.map(from => from.measures.map(m => {
802
+ const measure = this.newMeasure(m);
803
+ return [m, measure.aliasName()];
804
+ }).concat(from.dimensions.map(m => {
805
+ const member = this.newDimension(m);
806
+ return [m, member.aliasName()];
807
+ })))))
808
+ };
809
+ const fromSubQuery = fromMeasures && this.newSubQuery({
810
+ measures: fromMeasures,
811
+ // TODO get rid of this postAggregate filter
812
+ dimensions: fromDimensions,
813
+ postAggregateDimensions: withQuery.postAggregateDimensions,
814
+ filters: withQuery.filters,
815
+ // TODO do we need it?
816
+ postAggregateQuery: true // !!fromDimensions.find(d => this.newDimension(d).isPostAggregate())
817
+ });
818
+ const measures = fromSubQuery && fromMeasures.map(m => fromSubQuery.newMeasure(m));
819
+ // TODO get rid of this postAggregate filter
820
+ const postAggregateDimensions = fromSubQuery && fromDimensions.map(m => fromSubQuery.newDimension(m)).filter(d => d.isPostAggregate());
821
+ const membersToSelect = measures?.concat(postAggregateDimensions);
822
+ const select = fromSubQuery && fromSubQuery.outerMeasuresJoinFullKeyQueryAggregate(membersToSelect, membersToSelect, withQuery.memberFrom.map(f => f.alias));
823
+ const fromSql = select && this.wrapInParenthesis(select);
824
+ const subQuery = this.newSubQuery({
825
+ measures: withQuery.measures,
826
+ dimensions: withQuery.dimensions,
827
+ postAggregateDimensions: withQuery.postAggregateDimensions,
828
+ filters: withQuery.filters,
829
+ from: fromSql && {
830
+ sql: fromSql,
831
+ alias: `${withQuery.alias}_join`,
832
+ },
833
+ // TODO condition should something else instead of rank
834
+ postAggregateQuery: !!withQuery.measures.find(d => {
835
+ const { type } = this.newMeasure(d).definition();
836
+ return type === 'rank' || type === 'number';
837
+ }),
838
+ });
839
+ return {
840
+ query: subQuery.evaluateSymbolSqlWithContext(() => subQuery.buildParamAnnotatedSql(), renderedReferenceContext),
841
+ alias: withQuery.alias
842
+ };
664
843
  }
665
844
  dimensionsJoinCondition(leftAlias, rightAlias) {
666
845
  const dimensionAliases = this.dimensionAliasNames();
@@ -807,7 +986,7 @@ class BaseQuery {
807
986
  }));
808
987
  }
809
988
  query() {
810
- return this.joinQuery(this.join, this.collectFromMembers(false, this.collectSubQueryDimensionsFor.bind(this), 'collectSubQueryDimensionsFor'));
989
+ return this.from && this.joinSql([this.from]) || this.joinQuery(this.join, this.collectFromMembers(false, this.collectSubQueryDimensionsFor.bind(this), 'collectSubQueryDimensionsFor'));
811
990
  }
812
991
  rewriteInlineCubeSql(cube, isLeftJoinCondition) {
813
992
  const sql = this.cubeSql(cube);
@@ -1048,12 +1227,7 @@ class BaseQuery {
1048
1227
  return this.collectFromMembers(excludeTimeDimensions, this.collectJoinHintsFor.bind(this), 'collectJoinHintsFor');
1049
1228
  }
1050
1229
  collectFromMembers(excludeTimeDimensions, fn, methodName) {
1051
- const membersToCollectFrom = this.measures
1052
- .concat(this.dimensions)
1053
- .concat(this.segments)
1054
- .concat(this.filters)
1055
- .concat(this.measureFilters)
1056
- .concat(excludeTimeDimensions ? [] : this.timeDimensions)
1230
+ const membersToCollectFrom = this.allMembersConcat(excludeTimeDimensions)
1057
1231
  .concat(this.join ? this.join.joins.map(j => ({
1058
1232
  getMembers: () => [{
1059
1233
  path: () => null,
@@ -1063,6 +1237,14 @@ class BaseQuery {
1063
1237
  })) : []);
1064
1238
  return this.collectFrom(membersToCollectFrom, fn, methodName);
1065
1239
  }
1240
+ allMembersConcat(excludeTimeDimensions) {
1241
+ return this.measures
1242
+ .concat(this.dimensions)
1243
+ .concat(this.segments)
1244
+ .concat(this.filters)
1245
+ .concat(this.measureFilters)
1246
+ .concat(excludeTimeDimensions ? [] : this.timeDimensions);
1247
+ }
1066
1248
  collectFrom(membersToCollectFrom, fn, methodName, cache) {
1067
1249
  const methodCacheKey = Array.isArray(methodName) ? methodName : [methodName];
1068
1250
  return ramda_1.default.pipe(ramda_1.default.map(f => f.getMembers()), ramda_1.default.flatten, ramda_1.default.map(s => ((cache || this.compilerCache).cache(['collectFrom'].concat(methodCacheKey).concat(s.path() ? [s.path().join('.')] : [s.cube().name, s.expression?.toString() || s.expressionName || s.definition().sql]), () => fn(() => this.traverseSymbol(s))))), ramda_1.default.unnest, ramda_1.default.uniq, ramda_1.default.filter(ramda_1.default.identity))(membersToCollectFrom);
@@ -1249,75 +1431,100 @@ class BaseQuery {
1249
1431
  if (!type && this.cubeEvaluator.isSegment(memberPathArray)) {
1250
1432
  type = 'segment';
1251
1433
  }
1252
- if (type === 'measure') {
1253
- let parentMeasure;
1254
- if (this.safeEvaluateSymbolContext().compositeCubeMeasures ||
1255
- this.safeEvaluateSymbolContext().leafMeasures) {
1256
- parentMeasure = this.safeEvaluateSymbolContext().currentMeasure;
1257
- if (this.safeEvaluateSymbolContext().compositeCubeMeasures) {
1258
- if (parentMeasure && !memberExpressionType &&
1259
- (this.cubeEvaluator.cubeNameFromPath(parentMeasure) !== cubeName ||
1260
- this.newMeasure(this.cubeEvaluator.pathFromArray(memberPathArray)).isCumulative())) {
1261
- this.safeEvaluateSymbolContext().compositeCubeMeasures[parentMeasure] = true;
1434
+ const parentMember = this.safeEvaluateSymbolContext().currentMember;
1435
+ if (this.safeEvaluateSymbolContext().memberChildren && parentMember) {
1436
+ this.safeEvaluateSymbolContext().memberChildren[parentMember] = this.safeEvaluateSymbolContext().memberChildren[parentMember] || [];
1437
+ if (this.safeEvaluateSymbolContext().memberChildren[parentMember].indexOf(memberPath) === -1) {
1438
+ this.safeEvaluateSymbolContext().memberChildren[parentMember].push(memberPath);
1439
+ }
1440
+ }
1441
+ this.safeEvaluateSymbolContext().currentMember = memberPath;
1442
+ try {
1443
+ if (type === 'measure') {
1444
+ let parentMeasure;
1445
+ if (this.safeEvaluateSymbolContext().compositeCubeMeasures ||
1446
+ this.safeEvaluateSymbolContext().leafMeasures) {
1447
+ parentMeasure = this.safeEvaluateSymbolContext().currentMeasure;
1448
+ if (this.safeEvaluateSymbolContext().compositeCubeMeasures) {
1449
+ if (parentMeasure && !memberExpressionType &&
1450
+ (this.cubeEvaluator.cubeNameFromPath(parentMeasure) !== cubeName ||
1451
+ this.newMeasure(memberPath).isCumulative())) {
1452
+ this.safeEvaluateSymbolContext().compositeCubeMeasures[parentMeasure] = true;
1453
+ }
1262
1454
  }
1263
- }
1264
- this.safeEvaluateSymbolContext().currentMeasure = this.cubeEvaluator.pathFromArray(memberPathArray);
1265
- if (this.safeEvaluateSymbolContext().leafMeasures) {
1266
- if (parentMeasure) {
1267
- this.safeEvaluateSymbolContext().leafMeasures[parentMeasure] = false;
1455
+ this.safeEvaluateSymbolContext().currentMeasure = memberPath;
1456
+ if (this.safeEvaluateSymbolContext().leafMeasures) {
1457
+ if (parentMeasure) {
1458
+ this.safeEvaluateSymbolContext().leafMeasures[parentMeasure] = false;
1459
+ }
1460
+ this.safeEvaluateSymbolContext().leafMeasures[this.safeEvaluateSymbolContext().currentMeasure] = true;
1268
1461
  }
1269
- this.safeEvaluateSymbolContext().leafMeasures[this.safeEvaluateSymbolContext().currentMeasure] = true;
1270
1462
  }
1271
- }
1272
- const primaryKeys = this.cubeEvaluator.primaryKeys[cubeName];
1273
- const result = this.renderSqlMeasure(name, this.applyMeasureFilters(this.autoPrefixWithCubeName(cubeName, symbol.sql && this.evaluateSql(cubeName, symbol.sql) ||
1274
- primaryKeys.length && (primaryKeys.length > 1 ?
1275
- this.concatStringsSql(primaryKeys.map((pk) => this.castToString(this.primaryKeySql(pk, cubeName))))
1276
- : this.primaryKeySql(primaryKeys[0], cubeName)) || '*'), symbol, cubeName), symbol, cubeName, parentMeasure);
1277
- if (this.safeEvaluateSymbolContext().compositeCubeMeasures ||
1278
- this.safeEvaluateSymbolContext().leafMeasures) {
1279
- this.safeEvaluateSymbolContext().currentMeasure = parentMeasure;
1280
- }
1281
- return result;
1282
- }
1283
- else if (type === 'dimension') {
1284
- if ((this.safeEvaluateSymbolContext().renderedReference || {})[memberPath]) {
1285
- return this.evaluateSymbolContext.renderedReference[memberPath];
1286
- }
1287
- if (symbol.subQuery) {
1288
- if (this.safeEvaluateSymbolContext().subQueryDimensions) {
1289
- this.safeEvaluateSymbolContext().subQueryDimensions.push(memberPath);
1463
+ const primaryKeys = this.cubeEvaluator.primaryKeys[cubeName];
1464
+ const orderBySql = (symbol.orderBy || []).map(o => ({ sql: this.evaluateSql(cubeName, o.sql), dir: o.dir }));
1465
+ let sql;
1466
+ if (symbol.type !== 'rank') {
1467
+ sql = symbol.sql && this.evaluateSql(cubeName, symbol.sql) ||
1468
+ primaryKeys.length && (primaryKeys.length > 1 ?
1469
+ this.concatStringsSql(primaryKeys.map((pk) => this.castToString(this.primaryKeySql(pk, cubeName))))
1470
+ : this.primaryKeySql(primaryKeys[0], cubeName)) || '*';
1290
1471
  }
1291
- return this.escapeColumnName(this.aliasName(memberPath));
1292
- }
1293
- if (symbol.case) {
1294
- return this.renderDimensionCase(symbol, cubeName);
1472
+ const result = this.renderSqlMeasure(name, sql && this.applyMeasureFilters(this.autoPrefixWithCubeName(cubeName, sql), symbol, cubeName), symbol, cubeName, parentMeasure, orderBySql);
1473
+ if (this.safeEvaluateSymbolContext().compositeCubeMeasures ||
1474
+ this.safeEvaluateSymbolContext().leafMeasures) {
1475
+ this.safeEvaluateSymbolContext().currentMeasure = parentMeasure;
1476
+ }
1477
+ return result;
1295
1478
  }
1296
- else if (symbol.type === 'geo') {
1297
- return this.concatStringsSql([
1298
- this.autoPrefixAndEvaluateSql(cubeName, symbol.latitude.sql),
1299
- '\',\'',
1300
- this.autoPrefixAndEvaluateSql(cubeName, symbol.longitude.sql)
1301
- ]);
1479
+ else if (type === 'dimension') {
1480
+ if ((this.safeEvaluateSymbolContext().renderedReference || {})[memberPath]) {
1481
+ return this.evaluateSymbolContext.renderedReference[memberPath];
1482
+ }
1483
+ // if (symbol.postAggregate) {
1484
+ // const orderBySql = (symbol.orderBy || []).map(o => ({ sql: this.evaluateSql(cubeName, o.sql), dir: o.dir }));
1485
+ // const partitionBy = this.postAggregateDimensions.length ? `PARTITION BY ${this.postAggregateDimensions.map(d => d.dimensionSql()).join(', ')} ` : '';
1486
+ // if (symbol.type === 'rank') {
1487
+ // return `${symbol.type}() OVER (${partitionBy}ORDER BY ${orderBySql.map(o => `${o.sql} ${o.dir}`).join(', ')})`;
1488
+ // }
1489
+ // }
1490
+ if (symbol.subQuery) {
1491
+ if (this.safeEvaluateSymbolContext().subQueryDimensions) {
1492
+ this.safeEvaluateSymbolContext().subQueryDimensions.push(memberPath);
1493
+ }
1494
+ return this.escapeColumnName(this.aliasName(memberPath));
1495
+ }
1496
+ if (symbol.case) {
1497
+ return this.renderDimensionCase(symbol, cubeName);
1498
+ }
1499
+ else if (symbol.type === 'geo') {
1500
+ return this.concatStringsSql([
1501
+ this.autoPrefixAndEvaluateSql(cubeName, symbol.latitude.sql),
1502
+ '\',\'',
1503
+ this.autoPrefixAndEvaluateSql(cubeName, symbol.longitude.sql)
1504
+ ]);
1505
+ }
1506
+ else {
1507
+ let res = this.autoPrefixAndEvaluateSql(cubeName, symbol.sql);
1508
+ if (this.safeEvaluateSymbolContext().convertTzForRawTimeDimension &&
1509
+ !memberExpressionType &&
1510
+ symbol.type === 'time' &&
1511
+ this.cubeEvaluator.byPathAnyType(memberPathArray).ownedByCube) {
1512
+ res = this.convertTz(res);
1513
+ }
1514
+ return res;
1515
+ }
1302
1516
  }
1303
- else {
1304
- let res = this.autoPrefixAndEvaluateSql(cubeName, symbol.sql);
1305
- if (this.safeEvaluateSymbolContext().convertTzForRawTimeDimension &&
1306
- !memberExpressionType &&
1307
- symbol.type === 'time' &&
1308
- this.cubeEvaluator.byPathAnyType(memberPathArray).ownedByCube) {
1309
- res = this.convertTz(res);
1517
+ else if (type === 'segment') {
1518
+ if ((this.safeEvaluateSymbolContext().renderedReference || {})[memberPath]) {
1519
+ return this.evaluateSymbolContext.renderedReference[memberPath];
1310
1520
  }
1311
- return res;
1521
+ return this.autoPrefixWithCubeName(cubeName, this.evaluateSql(cubeName, symbol.sql));
1312
1522
  }
1523
+ return this.evaluateSql(cubeName, symbol.sql);
1313
1524
  }
1314
- else if (type === 'segment') {
1315
- if ((this.safeEvaluateSymbolContext().renderedReference || {})[memberPath]) {
1316
- return this.evaluateSymbolContext.renderedReference[memberPath];
1317
- }
1318
- return this.autoPrefixWithCubeName(cubeName, this.evaluateSql(cubeName, symbol.sql));
1525
+ finally {
1526
+ this.safeEvaluateSymbolContext().currentMember = parentMember;
1319
1527
  }
1320
- return this.evaluateSql(cubeName, symbol.sql);
1321
1528
  }
1322
1529
  autoPrefixAndEvaluateSql(cubeName, sql) {
1323
1530
  return this.autoPrefixWithCubeName(cubeName, this.evaluateSql(cubeName, sql));
@@ -1415,7 +1622,7 @@ class BaseQuery {
1415
1622
  this.evaluateSymbolContext = oldContext;
1416
1623
  }
1417
1624
  }
1418
- renderSqlMeasure(name, evaluateSql, symbol, cubeName, parentMeasure) {
1625
+ renderSqlMeasure(name, evaluateSql, symbol, cubeName, parentMeasure, orderBySql) {
1419
1626
  const multiplied = this.multipliedJoinRowResult(cubeName);
1420
1627
  const measurePath = `${cubeName}.${name}`;
1421
1628
  let resultMultiplied = multiplied;
@@ -1427,10 +1634,10 @@ class BaseQuery {
1427
1634
  if (parentMeasure &&
1428
1635
  (this.safeEvaluateSymbolContext().foundCompositeCubeMeasures || {})[parentMeasure] &&
1429
1636
  !(this.safeEvaluateSymbolContext().foundCompositeCubeMeasures || {})[measurePath]) {
1430
- this.safeEvaluateSymbolContext().measuresToRender.push({ multiplied: resultMultiplied, measure: measurePath });
1637
+ this.safeEvaluateSymbolContext().measuresToRender.push({ multiplied: resultMultiplied, measure: measurePath, postAggregate: symbol.postAggregate });
1431
1638
  }
1432
1639
  if (this.safeEvaluateSymbolContext().foundCompositeCubeMeasures && !parentMeasure) {
1433
- this.safeEvaluateSymbolContext().rootMeasure.value = { multiplied: resultMultiplied, measure: measurePath };
1640
+ this.safeEvaluateSymbolContext().rootMeasure.value = { multiplied: resultMultiplied, measure: measurePath, postAggregate: symbol.postAggregate };
1434
1641
  }
1435
1642
  if (((this.evaluateSymbolContext || {}).renderedReference || {})[measurePath]) {
1436
1643
  return this.evaluateSymbolContext.renderedReference[measurePath];
@@ -1459,6 +1666,32 @@ class BaseQuery {
1459
1666
  return onGroupedColumn;
1460
1667
  }
1461
1668
  }
1669
+ if (symbol.postAggregate) {
1670
+ const partitionBy = this.postAggregateDimensions.length ? `PARTITION BY ${this.postAggregateDimensions.map(d => d.dimensionSql()).join(', ')} ` : '';
1671
+ if (symbol.type === 'rank') {
1672
+ return `${symbol.type}() OVER (${partitionBy}ORDER BY ${orderBySql.map(o => `${o.sql} ${o.dir}`).join(', ')})`;
1673
+ }
1674
+ if (!ramda_1.default.equals(this.postAggregateDimensions.map(d => d.expressionPath()), this.dimensions.map(d => d.expressionPath()))) {
1675
+ let funDef;
1676
+ if (symbol.type === 'countDistinctApprox') {
1677
+ funDef = this.countDistinctApprox(evaluateSql);
1678
+ }
1679
+ else if (symbol.type === 'countDistinct' || symbol.type === 'count' && !symbol.sql && multiplied) {
1680
+ funDef = `count(distinct ${evaluateSql})`;
1681
+ }
1682
+ else if (BaseQuery.isCalculatedMeasureType(symbol.type)) {
1683
+ // TODO calculated measure type will be ungrouped
1684
+ // if (this.postAggregateDimensions.length !== this.dimensions.length) {
1685
+ // throw new UserError(`Calculated measure '${measurePath}' uses group_by or reduce_by context modifiers while it isn't allowed`);
1686
+ // }
1687
+ return evaluateSql;
1688
+ }
1689
+ else {
1690
+ funDef = `${symbol.type}(${symbol.type}(${evaluateSql}))`;
1691
+ }
1692
+ return `${funDef} OVER(${partitionBy})`;
1693
+ }
1694
+ }
1462
1695
  if (symbol.type === 'countDistinctApprox') {
1463
1696
  return this.safeEvaluateSymbolContext().overTimeSeriesAggregate || this.options.preAggregationQuery ?
1464
1697
  this.hllInit(evaluateSql) :