Skip to content

Commit 8e9c90d

Browse files
authored
chore(tesseract): Refactoring of multistage time-shifts (#9418)
1 parent aea3a6c commit 8e9c90d

File tree

13 files changed

+287
-163
lines changed

13 files changed

+287
-163
lines changed

packages/cubejs-schema-compiler/test/integration/postgres/member-expression.test.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,6 @@ cubes:
203203
204204
views:
205205
- name: customers_view
206-
207206
cubes:
208207
- join_path: customers
209208
includes:
@@ -384,4 +383,26 @@ views:
384383
// Skipping because it works only in Tesseract
385384
});
386385
}
386+
387+
if (getEnv('nativeSqlPlanner')) {
388+
it('multi stage duplicated time shift over view and origin cube', async () => runQueryTest({
389+
measures: [
390+
'orders_view.cagr_1_y'
391+
],
392+
timeDimensions: [
393+
{
394+
dimension: 'orders_view.date',
395+
dateRange: ['2022-01-01', '2022-10-31'],
396+
},
397+
],
398+
399+
timezone: 'America/Los_Angeles'
400+
},
401+
402+
[{ orders_view__cagr_1_y: '-0.90000000000000000000' }]));
403+
} else {
404+
it.skip('member expression multi stage with time dimension segment', () => {
405+
// Skipping because it works only in Tesseract
406+
});
407+
}
387408
});

rust/cubesqlplanner/cubesqlplanner/src/planner/base_measure.rs

Lines changed: 6 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,21 @@
11
use super::query_tools::QueryTools;
2-
use super::sql_evaluator::{MemberExpressionSymbol, MemberSymbol, SqlCall};
2+
use super::sql_evaluator::{MeasureTimeShift, MemberExpressionSymbol, MemberSymbol, SqlCall};
33
use super::{evaluate_with_context, BaseMember, BaseMemberHelper, VisitorContext};
44
use crate::cube_bridge::measure_definition::{
55
MeasureDefinition, RollingWindow, TimeShiftReference,
66
};
77
use crate::planner::sql_templates::PlanSqlTemplates;
88
use cubenativeutils::CubeError;
9-
use lazy_static::lazy_static;
10-
use regex::Regex;
119
use std::fmt::{Debug, Formatter};
1210
use std::rc::Rc;
1311

14-
#[derive(Clone, Debug)]
15-
pub struct MeasureTimeShift {
16-
pub interval: String,
17-
pub time_dimension: String,
18-
}
19-
20-
lazy_static! {
21-
static ref INTERVAL_MATCH_RE: Regex =
22-
Regex::new(r"^(-?\d+) (second|minute|hour|day|week|month|quarter|year)s?$").unwrap();
23-
}
24-
impl MeasureTimeShift {
25-
pub fn try_from_reference(reference: &TimeShiftReference) -> Result<Self, CubeError> {
26-
let parsed_interval =
27-
if let Some(captures) = INTERVAL_MATCH_RE.captures(&reference.interval) {
28-
let duration = if let Some(duration) = captures.get(1) {
29-
duration.as_str().parse::<i64>().ok()
30-
} else {
31-
None
32-
};
33-
let granularity = if let Some(granularity) = captures.get(2) {
34-
Some(granularity.as_str().to_owned())
35-
} else {
36-
None
37-
};
38-
if let Some((duration, granularity)) = duration.zip(granularity) {
39-
Some((duration, granularity))
40-
} else {
41-
None
42-
}
43-
} else {
44-
None
45-
};
46-
if let Some((duration, granularity)) = parsed_interval {
47-
let duration = if reference.shift_type.as_ref().unwrap_or(&format!("prior")) == "next" {
48-
duration * (-1)
49-
} else {
50-
duration
51-
};
52-
53-
Ok(Self {
54-
interval: format!("{duration} {granularity}"),
55-
time_dimension: reference.time_dimension.clone(),
56-
})
57-
} else {
58-
Err(CubeError::user(format!(
59-
"Invalid interval: {}",
60-
reference.interval
61-
)))
62-
}
63-
}
64-
}
65-
6612
pub struct BaseMeasure {
6713
measure: String,
6814
query_tools: Rc<QueryTools>,
6915
member_evaluator: Rc<MemberSymbol>,
7016
definition: Option<Rc<dyn MeasureDefinition>>,
7117
#[allow(dead_code)]
7218
member_expression_definition: Option<String>,
73-
time_shifts: Vec<MeasureTimeShift>,
7419
cube_name: String,
7520
name: String,
7621
default_alias: String,
@@ -80,7 +25,6 @@ impl Debug for BaseMeasure {
8025
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
8126
f.debug_struct("BaseMeasure")
8227
.field("measure", &self.measure)
83-
.field("time_shifts", &self.time_shifts)
8428
.field("default_alias", &self.default_alias)
8529
.finish()
8630
}
@@ -132,7 +76,6 @@ impl BaseMeasure {
13276
) -> Result<Option<Rc<Self>>, CubeError> {
13377
let res = match evaluation_node.as_ref() {
13478
MemberSymbol::Measure(s) => {
135-
let time_shifts = Self::parse_time_shifts(&s.definition())?;
13679
let default_alias = BaseMemberHelper::default_alias(
13780
&s.cube_name(),
13881
&s.name(),
@@ -147,7 +90,6 @@ impl BaseMeasure {
14790
member_expression_definition: None,
14891
cube_name: s.cube_name().clone(),
14992
name: s.name().clone(),
150-
time_shifts,
15193
default_alias,
15294
}))
15395
}
@@ -166,7 +108,6 @@ impl BaseMeasure {
166108
name,
167109
member_expression_definition,
168110
default_alias,
169-
time_shifts: vec![],
170111
}))
171112
}
172113
_ => None,
@@ -212,7 +153,6 @@ impl BaseMeasure {
212153
name,
213154
member_expression_definition,
214155
default_alias,
215-
time_shifts: vec![],
216156
}))
217157
}
218158

@@ -232,19 +172,6 @@ impl BaseMeasure {
232172
Ok(res)
233173
}
234174

235-
fn parse_time_shifts(
236-
definition: &Rc<dyn MeasureDefinition>,
237-
) -> Result<Vec<MeasureTimeShift>, CubeError> {
238-
if let Some(time_shifts) = &definition.static_data().time_shift_references {
239-
time_shifts
240-
.iter()
241-
.map(|t| MeasureTimeShift::try_from_reference(t))
242-
.collect::<Result<Vec<_>, _>>()
243-
} else {
244-
Ok(vec![])
245-
}
246-
}
247-
248175
pub fn member_evaluator(&self) -> &Rc<MemberSymbol> {
249176
&self.member_evaluator
250177
}
@@ -289,8 +216,11 @@ impl BaseMeasure {
289216
.map_or(None, |d| d.static_data().time_shift_references.clone())
290217
}
291218

292-
pub fn time_shifts(&self) -> &Vec<MeasureTimeShift> {
293-
&self.time_shifts
219+
pub fn time_shifts(&self) -> Vec<MeasureTimeShift> {
220+
match self.member_evaluator.as_ref() {
221+
MemberSymbol::Measure(measure_symbol) => measure_symbol.time_shifts().clone(),
222+
_ => vec![],
223+
}
294224
}
295225

296226
pub fn is_multi_stage(&self) -> bool {

rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/applied_state.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::plan::{FilterGroup, FilterItem};
22
use crate::planner::filter::FilterOperator;
3-
use crate::planner::planners::multi_stage::MultiStageTimeShift;
3+
use crate::planner::sql_evaluator::MeasureTimeShift;
44
use crate::planner::{BaseDimension, BaseMember, BaseTimeDimension};
55
use cubenativeutils::CubeError;
66
use itertools::Itertools;
@@ -17,7 +17,7 @@ pub struct MultiStageAppliedState {
1717
dimensions_filters: Vec<FilterItem>,
1818
measures_filters: Vec<FilterItem>,
1919
segments: Vec<FilterItem>,
20-
time_shifts: HashMap<String, String>,
20+
time_shifts: HashMap<String, MeasureTimeShift>,
2121
}
2222

2323
impl MultiStageAppliedState {
@@ -62,14 +62,17 @@ impl MultiStageAppliedState {
6262
.collect_vec();
6363
}
6464

65-
pub fn add_time_shifts(&mut self, time_shifts: Vec<MultiStageTimeShift>) {
65+
pub fn add_time_shifts(&mut self, time_shifts: Vec<MeasureTimeShift>) {
6666
for ts in time_shifts.into_iter() {
67-
self.time_shifts
68-
.insert(ts.time_dimension.clone(), ts.interval.clone());
67+
if let Some(exists) = self.time_shifts.get_mut(&ts.dimension.full_name()) {
68+
exists.interval += ts.interval;
69+
} else {
70+
self.time_shifts.insert(ts.dimension.full_name(), ts);
71+
}
6972
}
7073
}
7174

72-
pub fn time_shifts(&self) -> &HashMap<String, String> {
75+
pub fn time_shifts(&self) -> &HashMap<String, MeasureTimeShift> {
7376
&self.time_shifts
7477
}
7578

rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/member.rs

Lines changed: 4 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,7 @@
1-
use crate::cube_bridge::measure_definition::TimeShiftReference;
2-
use crate::planner::sql_evaluator::MemberSymbol;
1+
use crate::planner::sql_evaluator::{MeasureTimeShift, MemberSymbol};
32
use crate::planner::BaseTimeDimension;
4-
use cubenativeutils::CubeError;
5-
use lazy_static::lazy_static;
6-
use regex::Regex;
73
use std::rc::Rc;
84

9-
#[derive(Clone, Debug)]
10-
pub struct MultiStageTimeShift {
11-
pub interval: String,
12-
pub time_dimension: String,
13-
}
14-
15-
lazy_static! {
16-
static ref INTERVAL_MATCH_RE: Regex =
17-
Regex::new(r"^(-?\d+) (second|minute|hour|day|week|month|quarter|year)s?$").unwrap();
18-
}
19-
impl MultiStageTimeShift {
20-
pub fn try_from_reference(reference: &TimeShiftReference) -> Result<Self, CubeError> {
21-
let parsed_interval =
22-
if let Some(captures) = INTERVAL_MATCH_RE.captures(&reference.interval) {
23-
let duration = if let Some(duration) = captures.get(1) {
24-
duration.as_str().parse::<i64>().ok()
25-
} else {
26-
None
27-
};
28-
let granularity = if let Some(granularity) = captures.get(2) {
29-
Some(granularity.as_str().to_owned())
30-
} else {
31-
None
32-
};
33-
if let Some((duration, granularity)) = duration.zip(granularity) {
34-
Some((duration, granularity))
35-
} else {
36-
None
37-
}
38-
} else {
39-
None
40-
};
41-
if let Some((duration, granularity)) = parsed_interval {
42-
let duration = if reference.shift_type.as_ref().unwrap_or(&format!("prior")) == "next" {
43-
duration * (-1)
44-
} else {
45-
duration
46-
};
47-
48-
Ok(Self {
49-
interval: format!("{duration} {granularity}"),
50-
time_dimension: reference.time_dimension.clone(),
51-
})
52-
} else {
53-
Err(CubeError::user(format!(
54-
"Invalid interval: {}",
55-
reference.interval
56-
)))
57-
}
58-
}
59-
}
60-
615
#[derive(Clone)]
626
pub struct TimeSeriesDescription {
637
pub time_dimension: Rc<BaseTimeDimension>,
@@ -143,7 +87,7 @@ pub struct MultiStageInodeMember {
14387
reduce_by: Vec<String>,
14488
add_group_by: Vec<String>,
14589
group_by: Option<Vec<String>>,
146-
time_shifts: Vec<MultiStageTimeShift>,
90+
time_shifts: Vec<MeasureTimeShift>,
14791
}
14892

14993
impl MultiStageInodeMember {
@@ -152,7 +96,7 @@ impl MultiStageInodeMember {
15296
reduce_by: Vec<String>,
15397
add_group_by: Vec<String>,
15498
group_by: Option<Vec<String>>,
155-
time_shifts: Vec<MultiStageTimeShift>,
99+
time_shifts: Vec<MeasureTimeShift>,
156100
) -> Self {
157101
Self {
158102
inode_type,
@@ -179,7 +123,7 @@ impl MultiStageInodeMember {
179123
&self.group_by
180124
}
181125

182-
pub fn time_shifts(&self) -> &Vec<MultiStageTimeShift> {
126+
pub fn time_shifts(&self) -> &Vec<MeasureTimeShift> {
183127
&self.time_shifts
184128
}
185129
}

rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/multi_stage_query_planner.rs

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use super::{
22
MultiStageAppliedState, MultiStageInodeMember, MultiStageInodeMemberType,
33
MultiStageLeafMemberType, MultiStageMember, MultiStageMemberQueryPlanner, MultiStageMemberType,
4-
MultiStageQueryDescription, MultiStageTimeShift, RollingWindowPlanner,
4+
MultiStageQueryDescription, RollingWindowPlanner,
55
};
66
use crate::plan::{Cte, From, Schema, Select, SelectBuilder};
77
use crate::planner::query_tools::QueryTools;
@@ -124,15 +124,8 @@ impl MultiStageQueryPlanner {
124124
MultiStageInodeMemberType::Calculate
125125
};
126126

127-
let time_shifts = if let Some(refs) = measure.time_shift_references() {
128-
let time_shifts = refs
129-
.iter()
130-
.map(|r| MultiStageTimeShift::try_from_reference(r))
131-
.collect::<Result<Vec<_>, _>>()?;
132-
time_shifts
133-
} else {
134-
vec![]
135-
};
127+
let time_shifts = measure.time_shifts();
128+
136129
let is_ungrupped = match &member_type {
137130
MultiStageInodeMemberType::Rank | MultiStageInodeMemberType::Calculate => true,
138131
_ => self.query_properties.ungrouped(),

0 commit comments

Comments
 (0)