Skip to content

Commit e7d5f5b

Browse files
committed
feat(tesseract): Support for rolling window without date range
1 parent abe830c commit e7d5f5b

File tree

19 files changed

+801
-376
lines changed

19 files changed

+801
-376
lines changed

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

Lines changed: 45 additions & 50 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,13 +571,10 @@ 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)
580-
}`;
574+
return `select count(*) ${this.escapeColumnName(QueryAlias.TOTAL_COUNT)
575+
} from (\n${sql
576+
}\n) ${this.escapeColumnName(QueryAlias.ORIGINAL_QUERY)
577+
}`;
581578
}
582579

583580
regularAndTimeSeriesRollupQuery(regularMeasures, multipliedMeasures, cumulativeMeasures, preAggregationForQuery) {
@@ -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

@@ -1891,10 +1887,9 @@ export class BaseQuery {
18911887
)
18921888
), inlineWhereConditions);
18931889

1894-
return `SELECT ${this.selectAllDimensionsAndMeasures(measures)} FROM ${
1895-
query
1896-
} ${this.baseWhere(filters.concat(inlineWhereConditions))}` +
1897-
(!this.safeEvaluateSymbolContext().ungrouped && this.groupByClause() || '');
1890+
return `SELECT ${this.selectAllDimensionsAndMeasures(measures)} FROM ${query
1891+
} ${this.baseWhere(filters.concat(inlineWhereConditions))}` +
1892+
(!this.safeEvaluateSymbolContext().ungrouped && this.groupByClause() || '');
18981893
}
18991894

19001895
/**
@@ -1946,14 +1941,11 @@ export class BaseQuery {
19461941
.join(', ');
19471942

19481943
const primaryKeyJoinConditions = primaryKeyDimensions.map((pkd) => (
1949-
`${
1950-
this.escapeColumnName(QueryAlias.AGG_SUB_QUERY_KEYS)
1951-
}.${
1952-
pkd.aliasName()
1953-
} = ${
1954-
shouldBuildJoinForMeasureSelect
1955-
? `${this.cubeAlias(keyCubeName)}.${pkd.aliasName()}`
1956-
: this.dimensionSql(pkd)
1944+
`${this.escapeColumnName(QueryAlias.AGG_SUB_QUERY_KEYS)
1945+
}.${pkd.aliasName()
1946+
} = ${shouldBuildJoinForMeasureSelect
1947+
? `${this.cubeAlias(keyCubeName)}.${pkd.aliasName()}`
1948+
: this.dimensionSql(pkd)
19571949
}`
19581950
)).join(' AND ');
19591951

@@ -2026,9 +2018,8 @@ export class BaseQuery {
20262018
'collectSubQueryDimensionsFor'
20272019
)
20282020
), inlineWhereConditions);
2029-
return `SELECT DISTINCT ${this.keysSelect(primaryKeyDimensions)} FROM ${
2030-
query
2031-
} ${this.baseWhere(filters.concat(inlineWhereConditions))}`;
2021+
return `SELECT DISTINCT ${this.keysSelect(primaryKeyDimensions)} FROM ${query
2022+
} ${this.baseWhere(filters.concat(inlineWhereConditions))}`;
20322023
}
20332024

20342025
keysSelect(primaryKeyDimensions) {
@@ -3337,7 +3328,7 @@ export class BaseQuery {
33373328
}
33383329
throw new UserError('Output schema is only supported for rollup pre-aggregations');
33393330
},
3340-
{ inputProps: { }, cache: this.queryCache }
3331+
{ inputProps: {}, cache: this.queryCache }
33413332
);
33423333
}
33433334

@@ -3457,14 +3448,18 @@ export class BaseQuery {
34573448
join: '{{ join_type }} JOIN {{ source }} ON {{ condition }}',
34583449
cte: '{{ alias }} AS ({{ query | indent(2, true) }})',
34593450
time_series_select: 'SELECT date_from::timestamp AS "date_from",\n' +
3460-
'date_to::timestamp AS "date_to" \n' +
3461-
'FROM(\n' +
3462-
' VALUES ' +
3463-
'{% for time_item in seria %}' +
3464-
'(\'{{ time_item | join(\'\\\', \\\'\') }}\')' +
3465-
'{% if not loop.last %}, {% endif %}' +
3466-
'{% endfor %}' +
3467-
') AS dates (date_from, date_to)'
3451+
'date_to::timestamp AS "date_to" \n' +
3452+
'FROM(\n' +
3453+
' VALUES ' +
3454+
'{% for time_item in seria %}' +
3455+
'(\'{{ time_item | join(\'\\\', \\\'\') }}\')' +
3456+
'{% if not loop.last %}, {% endif %}' +
3457+
'{% endfor %}' +
3458+
') AS dates (date_from, date_to)',
3459+
time_series_get_range: 'SELECT {{ max_expr }} as {{ quoted_max_name }},\n' +
3460+
'{{ min_expr }} as {{ quoted_min_name }}\n' +
3461+
'FROM {{ from_prepared }}\n' +
3462+
'{% if filter %}WHERE {{ filter }}{% endif %}'
34683463
},
34693464
expressions: {
34703465
column_reference: '{% if table_name %}{{ table_name }}.{% endif %}{{ name }}',
@@ -3742,7 +3737,7 @@ export class BaseQuery {
37423737
sql: `${refreshKeyQuery.nowTimestampSql()} < ${updateWindow ?
37433738
refreshKeyQuery.addTimestampInterval(dateTo, updateWindow) :
37443739
dateTo
3745-
}`,
3740+
}`,
37463741
label: originalRefreshKey
37473742
}]);
37483743
}
@@ -4078,11 +4073,11 @@ export class BaseQuery {
40784073
const filterParamArg = filterParamArgs.filter(p => {
40794074
const member = p.__member();
40804075
return member === filter.measure ||
4081-
member === filter.dimension ||
4082-
(aliases[member] && (
4083-
aliases[member] === filter.measure ||
4084-
aliases[member] === filter.dimension
4085-
));
4076+
member === filter.dimension ||
4077+
(aliases[member] && (
4078+
aliases[member] === filter.measure ||
4079+
aliases[member] === filter.dimension
4080+
));
40864081
})[0];
40874082

40884083
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: 43 additions & 13 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: {
@@ -895,7 +902,7 @@ SELECT 1 AS revenue, cast('2024-01-01' AS timestamp) as time UNION ALL
895902
timeDimensions: [
896903
{
897904
dimension: 'visitors.created_at',
898-
granularity: 'three_days',
905+
granularity: 'month',
899906
dateRange: ['2017-01-01', '2017-01-10']
900907
},
901908
{
@@ -1088,6 +1095,29 @@ 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+
it('rolling count without date range', async () => {
1099+
if (!getEnv('nativeSqlPlanner')) return;
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+
10911121
it('rolling qtd', async () => runQueryTest({
10921122
measures: [
10931123
'visitors.revenue_qtd'
@@ -3408,18 +3438,18 @@ SELECT 1 AS revenue, cast('2024-01-01' AS timestamp) as time UNION ALL
34083438
cubeEvaluator: joinedSchemaCompilers.cubeEvaluator,
34093439
compiler: joinedSchemaCompilers.compiler,
34103440
},
3411-
{
3412-
measures: ['B.bval_sum', 'B.count'],
3413-
dimensions: ['B.aid'],
3414-
filters: [{
3415-
member: 'C.did',
3416-
operator: 'lt',
3417-
values: ['10']
3418-
}],
3419-
order: [{
3420-
'B.bval_sum': 'desc'
3421-
}]
3422-
});
3441+
{
3442+
measures: ['B.bval_sum', 'B.count'],
3443+
dimensions: ['B.aid'],
3444+
filters: [{
3445+
member: 'C.did',
3446+
operator: 'lt',
3447+
values: ['10']
3448+
}],
3449+
order: [{
3450+
'B.bval_sum': 'desc'
3451+
}]
3452+
});
34233453
const sql = query.buildSqlAndParams();
34243454
return dbRunner
34253455
.testQuery(sql)

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)