Skip to content

Commit fd94b02

Browse files
authored
feat(tesseract): Support for rolling window without date range (#9364)
1 parent 8c4566c commit fd94b02

File tree

18 files changed

+786
-356
lines changed

18 files changed

+786
-356
lines changed

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

Lines changed: 41 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,34 @@
66
* @license Apache-2.0
77
*/
88

9-
import R from 'ramda';
109
import cronParser from 'cron-parser';
11-
import moment from 'moment-timezone';
1210
import inflection from 'inflection';
11+
import moment from 'moment-timezone';
12+
import R from 'ramda';
1313

14+
import {
15+
buildSqlAndParams as nativeBuildSqlAndParams,
16+
} from '@cubejs-backend/native';
1417
import {
1518
FROM_PARTITION_RANGE,
1619
MAX_SOURCE_ROW_LIMIT,
17-
localTimestampToUtc,
1820
QueryAlias,
1921
getEnv,
22+
localTimestampToUtc,
2023
timeSeries as timeSeriesBase
2124
} from '@cubejs-backend/shared';
22-
import {
23-
buildSqlAndParams as nativeBuildSqlAndParams,
24-
} from '@cubejs-backend/native';
2525

2626
import { UserError } from '../compiler/UserError';
27-
import { BaseMeasure } from './BaseMeasure';
27+
import { SqlParser } from '../parser/SqlParser';
2828
import { BaseDimension } from './BaseDimension';
29-
import { BaseSegment } from './BaseSegment';
3029
import { BaseFilter } from './BaseFilter';
3130
import { BaseGroupFilter } from './BaseGroupFilter';
31+
import { BaseMeasure } from './BaseMeasure';
32+
import { BaseSegment } from './BaseSegment';
3233
import { BaseTimeDimension } from './BaseTimeDimension';
34+
import { Granularity } from './Granularity';
3335
import { ParamAllocator } from './ParamAllocator';
3436
import { PreAggregations } from './PreAggregations';
35-
import { SqlParser } from '../parser/SqlParser';
36-
import { Granularity } from './Granularity';
3737

3838
const DEFAULT_PREAGGREGATIONS_SCHEMA = 'stb_pre_aggregations';
3939

@@ -571,12 +571,9 @@ export class BaseQuery {
571571
* @returns {string}
572572
*/
573573
countAllQuery(sql) {
574-
return `select count(*) ${
575-
this.escapeColumnName(QueryAlias.TOTAL_COUNT)
576-
} from (\n${
577-
sql
578-
}\n) ${
579-
this.escapeColumnName(QueryAlias.ORIGINAL_QUERY)
574+
return `select count(*) ${this.escapeColumnName(QueryAlias.TOTAL_COUNT)
575+
} from (\n${sql
576+
}\n) ${this.escapeColumnName(QueryAlias.ORIGINAL_QUERY)
580577
}`;
581578
}
582579

@@ -967,8 +964,7 @@ export class BaseQuery {
967964
).concat(
968965
R.map(
969966
([multiplied, measure]) => this.withCubeAliasPrefix(
970-
`${
971-
this.aliasName(measure.measure.replace('.', '_'))
967+
`${this.aliasName(measure.measure.replace('.', '_'))
972968
}_cumulative`,
973969
() => this.overTimeSeriesQuery(
974970
multiplied
@@ -983,7 +979,7 @@ export class BaseQuery {
983979
),
984980
)
985981
)(cumulativeMeasures)
986-
// TODO SELECT *
982+
// TODO SELECT *
987983
).concat(multiStageMembers.map(m => `SELECT * FROM ${m.alias}`));
988984
}
989985

@@ -1956,10 +1952,9 @@ export class BaseQuery {
19561952
)
19571953
), inlineWhereConditions);
19581954

1959-
return `SELECT ${this.selectAllDimensionsAndMeasures(measures)} FROM ${
1960-
query
1955+
return `SELECT ${this.selectAllDimensionsAndMeasures(measures)} FROM ${query
19611956
} ${this.baseWhere(filters.concat(inlineWhereConditions))}` +
1962-
(!this.safeEvaluateSymbolContext().ungrouped && this.groupByClause() || '');
1957+
(!this.safeEvaluateSymbolContext().ungrouped && this.groupByClause() || '');
19631958
}
19641959

19651960
/**
@@ -2011,14 +2006,11 @@ export class BaseQuery {
20112006
.join(', ');
20122007

20132008
const primaryKeyJoinConditions = primaryKeyDimensions.map((pkd) => (
2014-
`${
2015-
this.escapeColumnName(QueryAlias.AGG_SUB_QUERY_KEYS)
2016-
}.${
2017-
pkd.aliasName()
2018-
} = ${
2019-
shouldBuildJoinForMeasureSelect
2020-
? `${this.cubeAlias(keyCubeName)}.${pkd.aliasName()}`
2021-
: this.dimensionSql(pkd)
2009+
`${this.escapeColumnName(QueryAlias.AGG_SUB_QUERY_KEYS)
2010+
}.${pkd.aliasName()
2011+
} = ${shouldBuildJoinForMeasureSelect
2012+
? `${this.cubeAlias(keyCubeName)}.${pkd.aliasName()}`
2013+
: this.dimensionSql(pkd)
20222014
}`
20232015
)).join(' AND ');
20242016

@@ -2110,8 +2102,7 @@ export class BaseQuery {
21102102
'collectSubQueryDimensionsFor'
21112103
)
21122104
), inlineWhereConditions);
2113-
return `SELECT DISTINCT ${this.keysSelect(primaryKeyDimensions)} FROM ${
2114-
query
2105+
return `SELECT DISTINCT ${this.keysSelect(primaryKeyDimensions)} FROM ${query
21152106
} ${this.baseWhere(filters.concat(inlineWhereConditions))}`;
21162107
}
21172108

@@ -3425,7 +3416,7 @@ export class BaseQuery {
34253416
}
34263417
throw new UserError('Output schema is only supported for rollup pre-aggregations');
34273418
},
3428-
{ inputProps: { }, cache: this.queryCache }
3419+
{ inputProps: {}, cache: this.queryCache }
34293420
);
34303421
}
34313422

@@ -3545,14 +3536,18 @@ export class BaseQuery {
35453536
join: '{{ join_type }} JOIN {{ source }} ON {{ condition }}',
35463537
cte: '{{ alias }} AS ({{ query | indent(2, true) }})',
35473538
time_series_select: 'SELECT date_from::timestamp AS "date_from",\n' +
3548-
'date_to::timestamp AS "date_to" \n' +
3549-
'FROM(\n' +
3550-
' VALUES ' +
3551-
'{% for time_item in seria %}' +
3552-
'(\'{{ time_item | join(\'\\\', \\\'\') }}\')' +
3553-
'{% if not loop.last %}, {% endif %}' +
3554-
'{% endfor %}' +
3555-
') AS dates (date_from, date_to)'
3539+
'date_to::timestamp AS "date_to" \n' +
3540+
'FROM(\n' +
3541+
' VALUES ' +
3542+
'{% for time_item in seria %}' +
3543+
'(\'{{ time_item | join(\'\\\', \\\'\') }}\')' +
3544+
'{% if not loop.last %}, {% endif %}' +
3545+
'{% endfor %}' +
3546+
') AS dates (date_from, date_to)',
3547+
time_series_get_range: 'SELECT {{ max_expr }} as {{ quoted_max_name }},\n' +
3548+
'{{ min_expr }} as {{ quoted_min_name }}\n' +
3549+
'FROM {{ from_prepared }}\n' +
3550+
'{% if filter %}WHERE {{ filter }}{% endif %}'
35563551
},
35573552
expressions: {
35583553
column_reference: '{% if table_name %}{{ table_name }}.{% endif %}{{ name }}',
@@ -4166,11 +4161,11 @@ export class BaseQuery {
41664161
const filterParamArg = filterParamArgs.filter(p => {
41674162
const member = p.__member();
41684163
return member === filter.measure ||
4169-
member === filter.dimension ||
4170-
(aliases[member] && (
4171-
aliases[member] === filter.measure ||
4172-
aliases[member] === filter.dimension
4173-
));
4164+
member === filter.dimension ||
4165+
(aliases[member] && (
4166+
aliases[member] === filter.measure ||
4167+
aliases[member] === filter.dimension
4168+
));
41744169
})[0];
41754170

41764171
if (!filterParamArg) {

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ export class PostgresQuery extends BaseQuery {
8282
templates.types.double = 'DOUBLE PRECISION';
8383
templates.types.binary = 'BYTEA';
8484
templates.operators.is_not_distinct_from = 'IS NOT DISTINCT FROM';
85+
templates.statements.generated_time_series_select = 'SELECT date_from AS "date_from",\n' +
86+
'date_from + interval \'{{ granularity }}\' - interval \'1 millisecond\' AS "date_to" \n' +
87+
'FROM generate_series({{ start }}::timestamp, {{ end }}:: timestamp, \'{{ granularity }}\'::interval) "date_from" ';
8588
return templates;
8689
}
8790

packages/cubejs-schema-compiler/test/integration/postgres/sql-generation.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,13 @@ describe('SQL Generation', () => {
9090
offset: 'start'
9191
}
9292
},
93+
countRollingThreeMonth: {
94+
type: 'count',
95+
rollingWindow: {
96+
trailing: '3 month',
97+
offset: 'end'
98+
}
99+
},
93100
countRollingUnbounded: {
94101
type: 'count',
95102
rollingWindow: {
@@ -1088,6 +1095,34 @@ SELECT 1 AS revenue, cast('2024-01-01' AS timestamp) as time UNION ALL
10881095
{ visitors__created_at_day: '2017-01-10T00:00:00.000Z', visitors__count_rolling: null }
10891096
]));
10901097

1098+
if (getEnv('nativeSqlPlanner')) {
1099+
it('rolling count without date range', async () => {
1100+
await runQueryTest({
1101+
measures: [
1102+
'visitors.countRollingThreeMonth'
1103+
],
1104+
timeDimensions: [{
1105+
dimension: 'visitors.created_at',
1106+
granularity: 'month',
1107+
}],
1108+
order: [{
1109+
id: 'visitors.created_at'
1110+
}],
1111+
timezone: 'America/Los_Angeles'
1112+
}, [
1113+
{ visitors__created_at_month: '2016-09-01T00:00:00.000Z', visitors__count_rolling_three_month: '1' },
1114+
{ visitors__created_at_month: '2016-10-01T00:00:00.000Z', visitors__count_rolling_three_month: '1' },
1115+
{ visitors__created_at_month: '2016-11-01T00:00:00.000Z', visitors__count_rolling_three_month: '1' },
1116+
{ visitors__created_at_month: '2016-12-01T00:00:00.000Z', visitors__count_rolling_three_month: null },
1117+
{ visitors__created_at_month: '2017-01-01T00:00:00.000Z', visitors__count_rolling_three_month: '5' },
1118+
]);
1119+
});
1120+
} else {
1121+
it.skip('rolling count without date range', () => {
1122+
// Skipping because it works only in Tesseract
1123+
});
1124+
}
1125+
10911126
it('rolling qtd', async () => runQueryTest({
10921127
measures: [
10931128
'visitors.revenue_qtd'

rust/cubesqlplanner/cubesqlplanner/src/plan/builder/select.rs

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,28 @@ impl SelectBuilder {
7575
.add_column(SchemaColumn::new(alias.clone(), Some(member.full_name())));
7676
}
7777

78+
pub fn add_projection_function_expression(
79+
&mut self,
80+
function: &str,
81+
args: Vec<Rc<dyn BaseMember>>,
82+
alias: String,
83+
) {
84+
let expr = Expr::Function(FunctionExpression {
85+
function: function.to_string(),
86+
arguments: args
87+
.into_iter()
88+
.map(|r| Expr::Member(MemberExpression::new(r.clone())))
89+
.collect(),
90+
});
91+
let aliased_expr = AliasedExpr {
92+
expr,
93+
alias: alias.clone(),
94+
};
95+
96+
self.projection_columns.push(aliased_expr);
97+
self.result_schema
98+
.add_column(SchemaColumn::new(alias.clone(), None));
99+
}
78100
pub fn add_projection_coalesce_member(
79101
&mut self,
80102
member: &Rc<dyn BaseMember>,
@@ -147,16 +169,16 @@ impl SelectBuilder {
147169
self.ctes = ctes;
148170
}
149171

150-
fn make_cube_references(&self) -> HashMap<String, String> {
172+
pub fn make_cube_references(from: Rc<From>) -> HashMap<String, String> {
151173
let mut refs = HashMap::new();
152-
match &self.from.source {
174+
match &from.source {
153175
crate::plan::FromSource::Single(source) => {
154-
self.add_cube_reference_if_needed(source, &mut refs)
176+
Self::add_cube_reference_if_needed(source, &mut refs)
155177
}
156178
crate::plan::FromSource::Join(join) => {
157-
self.add_cube_reference_if_needed(&join.root, &mut refs);
179+
Self::add_cube_reference_if_needed(&join.root, &mut refs);
158180
for join_item in join.joins.iter() {
159-
self.add_cube_reference_if_needed(&join_item.from, &mut refs);
181+
Self::add_cube_reference_if_needed(&join_item.from, &mut refs);
160182
}
161183
}
162184
crate::plan::FromSource::Empty => {}
@@ -165,7 +187,6 @@ impl SelectBuilder {
165187
}
166188

167189
fn add_cube_reference_if_needed(
168-
&self,
169190
source: &SingleAliasedSource,
170191
refs: &mut HashMap<String, String>,
171192
) {
@@ -194,7 +215,7 @@ impl SelectBuilder {
194215
}
195216

196217
pub fn build(self, mut nodes_factory: SqlNodesFactory) -> Select {
197-
let cube_references = self.make_cube_references();
218+
let cube_references = Self::make_cube_references(self.from.clone());
198219
nodes_factory.set_cube_name_references(cube_references);
199220
let schema = if self.projection_columns.is_empty() {
200221
self.make_asteriks_schema()

rust/cubesqlplanner/cubesqlplanner/src/plan/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,5 @@ pub use order::OrderBy;
2121
pub use query_plan::QueryPlan;
2222
pub use schema::{QualifiedColumnName, Schema, SchemaColumn};
2323
pub use select::{AliasedExpr, Select};
24-
pub use time_series::TimeSeries;
24+
pub use time_series::{TimeSeries, TimeSeriesDateRange};
2525
pub use union::Union;

0 commit comments

Comments
 (0)