diff --git a/packages/cubejs-schema-compiler/test/integration/postgres/sql-generation.test.ts b/packages/cubejs-schema-compiler/test/integration/postgres/sql-generation.test.ts index 088f83ed341de..ba5a2e1a06569 100644 --- a/packages/cubejs-schema-compiler/test/integration/postgres/sql-generation.test.ts +++ b/packages/cubejs-schema-compiler/test/integration/postgres/sql-generation.test.ts @@ -152,6 +152,15 @@ describe('SQL Generation', () => { granularity: 'quarter' } }, + revenue_qtd_proxy: { + type: 'sum', + sql: \`\${revenue}\`, + multi_stage: true, + rollingWindow: { + type: 'to_date', + granularity: 'quarter' + } + }, revenue_day_ago: { multi_stage: true, type: 'sum', @@ -162,6 +171,15 @@ describe('SQL Generation', () => { type: 'prior', }] }, + revenueRollingDayAgo: { + type: 'sum', + sql: \`\${revenue_day_ago}\`, + multi_stage: true, + rollingWindow: { + trailing: '2 day', + offset: 'start' + } + }, revenue_day_ago_no_td: { multi_stage: true, type: 'sum', @@ -1026,6 +1044,38 @@ SELECT 1 AS revenue, cast('2024-01-01' AS timestamp) as time UNION ALL { visitors__created_at_day: '2017-01-10T00:00:00.000Z', visitors__revenue_rolling: null } ])); + if (getEnv('nativeSqlPlanner')) { + it('rolling day ago', async () => runQueryTest({ + measures: [ + 'visitors.revenueRollingDayAgo' + ], + timeDimensions: [{ + dimension: 'visitors.created_at', + granularity: 'day', + dateRange: ['2017-01-01', '2017-01-10'] + }], + order: [{ + id: 'visitors.created_at' + }], + timezone: 'America/Los_Angeles' + }, [ + { visitors__created_at_day: '2017-01-01T00:00:00.000Z', visitors__revenue_rolling_day_ago: null }, + { visitors__created_at_day: '2017-01-02T00:00:00.000Z', visitors__revenue_rolling_day_ago: null }, + { visitors__created_at_day: '2017-01-03T00:00:00.000Z', visitors__revenue_rolling_day_ago: null }, + { visitors__created_at_day: '2017-01-04T00:00:00.000Z', visitors__revenue_rolling_day_ago: '100' }, + { visitors__created_at_day: '2017-01-05T00:00:00.000Z', visitors__revenue_rolling_day_ago: '100' }, + { visitors__created_at_day: '2017-01-06T00:00:00.000Z', visitors__revenue_rolling_day_ago: '200' }, + { visitors__created_at_day: '2017-01-07T00:00:00.000Z', visitors__revenue_rolling_day_ago: '500' }, + { visitors__created_at_day: '2017-01-08T00:00:00.000Z', visitors__revenue_rolling_day_ago: '1200' }, + { visitors__created_at_day: '2017-01-09T00:00:00.000Z', visitors__revenue_rolling_day_ago: '900' }, + { visitors__created_at_day: '2017-01-10T00:00:00.000Z', visitors__revenue_rolling_day_ago: null } + ])); + } else { + it.skip('rolling count without date range', () => { + // Skipping because it works only in Tesseract + }); + } + it('rolling multiplied', async () => runQueryTest({ measures: [ 'visitors.revenueRolling', @@ -1474,6 +1524,34 @@ SELECT 1 AS revenue, cast('2024-01-01' AS timestamp) as time UNION ALL { visitors__created_at_day: '2017-01-10T00:00:00.000Z', visitors__revenue_qtd: '1500' } ])); + if (getEnv('nativeSqlPlanner')) { + it('rolling qtd proxy', async () => runQueryTest({ + measures: [ + 'visitors.revenue_qtd_proxy' + ], + timeDimensions: [{ + dimension: 'visitors.created_at', + granularity: 'day', + dateRange: ['2017-01-05', '2017-01-10'] + }], + order: [{ + id: 'visitors.created_at' + }], + timezone: 'America/Los_Angeles' + }, [ + { visitors__created_at_day: '2017-01-05T00:00:00.000Z', visitors__revenue_qtd_proxy: '600' }, + { visitors__created_at_day: '2017-01-06T00:00:00.000Z', visitors__revenue_qtd_proxy: '1500' }, + { visitors__created_at_day: '2017-01-07T00:00:00.000Z', visitors__revenue_qtd_proxy: '1500' }, + { visitors__created_at_day: '2017-01-08T00:00:00.000Z', visitors__revenue_qtd_proxy: '1500' }, + { visitors__created_at_day: '2017-01-09T00:00:00.000Z', visitors__revenue_qtd_proxy: '1500' }, + { visitors__created_at_day: '2017-01-10T00:00:00.000Z', visitors__revenue_qtd_proxy: '1500' } + ])); + } else { + it.skip('rolling qtd proxy', () => { + // Skipping because it works only in Tesseract + }); + } + it('CAGR', async () => runQueryTest({ measures: [ 'visitors.revenue', diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/mod.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/mod.rs index 9b7b08824f538..eec5b18057113 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/mod.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/mod.rs @@ -3,11 +3,9 @@ mod member; mod member_query_planner; mod multi_stage_query_planner; mod query_description; -mod rolling_window_planner; pub use applied_state::*; pub use member::*; pub use member_query_planner::MultiStageMemberQueryPlanner; pub use multi_stage_query_planner::MultiStageQueryPlanner; pub use query_description::MultiStageQueryDescription; -pub use rolling_window_planner::RollingWindowPlanner; diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/multi_stage_query_planner.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/multi_stage_query_planner.rs index 0763627e85b85..aac6c384b39b5 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/multi_stage_query_planner.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/multi_stage_query_planner.rs @@ -1,8 +1,9 @@ use super::{ MultiStageAppliedState, MultiStageInodeMember, MultiStageInodeMemberType, MultiStageLeafMemberType, MultiStageMember, MultiStageMemberQueryPlanner, MultiStageMemberType, - MultiStageQueryDescription, RollingWindowPlanner, + MultiStageQueryDescription, RollingWindowDescription, TimeSeriesDescription, }; +use crate::cube_bridge::measure_definition::RollingWindow; use crate::logical_plan::*; use crate::planner::query_tools::QueryTools; use crate::planner::sql_evaluator::collectors::has_multi_stage_members; @@ -10,7 +11,7 @@ use crate::planner::sql_evaluator::collectors::member_childs; use crate::planner::sql_evaluator::MemberSymbol; use crate::planner::BaseMember; use crate::planner::QueryProperties; -use crate::planner::{BaseDimension, BaseMeasure}; +use crate::planner::{BaseDimension, BaseMeasure, BaseTimeDimension, GranularityHelper}; use cubenativeutils::CubeError; use itertools::Itertools; use std::rc::Rc; @@ -18,16 +19,11 @@ use std::rc::Rc; pub struct MultiStageQueryPlanner { query_tools: Rc, query_properties: Rc, - rolling_window_planner: RollingWindowPlanner, } impl MultiStageQueryPlanner { pub fn new(query_tools: Rc, query_properties: Rc) -> Self { Self { - rolling_window_planner: RollingWindowPlanner::new( - query_tools.clone(), - query_properties.clone(), - ), query_tools, query_properties, } @@ -180,15 +176,14 @@ impl MultiStageQueryPlanner { return Ok(exists.clone()); }; - if let Some(rolling_window_query) = self.rolling_window_planner.try_plan_rolling_window( - member.clone(), - state.clone(), - descriptions, - )? { + if let Some(rolling_window_query) = + self.try_plan_rolling_window(member.clone(), state.clone(), descriptions)? + { return Ok(rolling_window_query); } let childs = member_childs(&member, true)?; + let has_multi_stage_members = has_multi_stage_members(&member, false)?; let description = if childs.is_empty() || !has_multi_stage_members { if has_multi_stage_members { @@ -266,4 +261,319 @@ impl MultiStageQueryPlanner { descriptions.push(description.clone()); Ok(description) } + + pub fn try_plan_rolling_window( + &self, + member: Rc, + state: Rc, + descriptions: &mut Vec>, + ) -> Result>, CubeError> { + if let Ok(measure) = member.as_measure() { + if measure.is_cumulative() { + let rolling_window = if let Some(rolling_window) = measure.rolling_window() { + rolling_window.clone() + } else { + RollingWindow { + trailing: None, + leading: None, + offset: None, + rolling_type: None, + granularity: None, + } + }; + + if !measure.is_multi_stage() { + let childs = member_childs(&member, true)?; + let measures = childs + .iter() + .filter(|s| s.as_measure().is_ok()) + .collect_vec(); + if !measures.is_empty() { + return Err(CubeError::user( + format!("Measure {} references another measures ({}). In this case, {} must have multi_stage: true defined", + member.full_name(), + measures.into_iter().map(|m| m.full_name()).join(", "), + member.full_name(), + ), + )); + } + } + + let ungrouped = measure.is_rolling_window() && !measure.is_addictive(); + + let mut time_dimensions = self.query_properties.time_dimensions().clone(); + for dim in self.query_properties.dimension_symbols() { + let dim = dim.resolve_reference_chain(); + if let Ok(time_dimension_symbol) = dim.as_time_dimension() { + let time_dimension = BaseTimeDimension::try_new_from_td_symbol( + self.query_tools.clone(), + time_dimension_symbol, + )?; + time_dimensions.push(time_dimension); + } + } + + if time_dimensions.is_empty() { + let rolling_base = self.add_rolling_window_base( + member.clone(), + state.clone(), + ungrouped, + descriptions, + )?; + return Ok(Some(rolling_base)); + } + let uniq_time_dimensions = time_dimensions + .iter() + .unique_by(|a| (a.cube_name(), a.name(), a.get_date_range())) + .collect_vec(); + if uniq_time_dimensions.len() != 1 { + return Err(CubeError::internal( + "Rolling window requires one time dimension and equal date ranges" + .to_string(), + )); + } + + let time_dimension = + GranularityHelper::find_dimension_with_min_granularity(&time_dimensions)?; + + let (base_rolling_state, base_time_dimension) = self.make_rolling_base_state( + time_dimension.clone(), + &rolling_window, + state.clone(), + )?; + let base_member = MemberSymbol::new_measure(measure.new_unrolling()); + + let time_series = + self.add_time_series(time_dimension.clone(), state.clone(), descriptions)?; + + let rolling_base = if !measure.is_multi_stage() { + self.add_rolling_window_base( + base_member, + base_rolling_state, + ungrouped, + descriptions, + )? + } else { + self.make_queries_descriptions(base_member, base_rolling_state, descriptions)? + }; + + let input = vec![time_series, rolling_base]; + + let alias = format!("cte_{}", descriptions.len()); + + let rolling_window_descr = if measure.is_running_total() { + RollingWindowDescription::new_running_total(time_dimension, base_time_dimension) + } else if let Some(granularity) = + self.get_to_date_rolling_granularity(&rolling_window)? + { + RollingWindowDescription::new_to_date( + time_dimension, + base_time_dimension, + granularity, + ) + } else { + RollingWindowDescription::new_regular( + time_dimension, + base_time_dimension, + rolling_window.trailing.clone(), + rolling_window.leading.clone(), + rolling_window.offset.clone().unwrap_or("end".to_string()), + ) + }; + + let inode_member = MultiStageInodeMember::new( + MultiStageInodeMemberType::RollingWindow(rolling_window_descr), + vec![], + vec![], + None, + None, + ); + + let description = MultiStageQueryDescription::new( + MultiStageMember::new( + MultiStageMemberType::Inode(inode_member), + member, + self.query_properties.ungrouped(), + false, + ), + state.clone(), + input, + alias.clone(), + ); + descriptions.push(description.clone()); + Ok(Some(description)) + } else { + Ok(None) + } + } else { + Ok(None) + } + } + + fn add_time_series_get_range_query( + &self, + time_dimension: Rc, + state: Rc, + descriptions: &mut Vec>, + ) -> Result, CubeError> { + let description = if let Some(description) = descriptions + .iter() + .find(|d| d.alias() == "time_series_get_range") + { + description.clone() + } else { + let time_series_get_range_node = MultiStageQueryDescription::new( + MultiStageMember::new( + MultiStageMemberType::Leaf(MultiStageLeafMemberType::TimeSeriesGetRange( + time_dimension.clone(), + )), + time_dimension.member_evaluator(), + true, + false, + ), + state.clone(), + vec![], + "time_series_get_range".to_string(), + ); + descriptions.push(time_series_get_range_node.clone()); + time_series_get_range_node + }; + Ok(description) + } + + fn add_time_series( + &self, + time_dimension: Rc, + state: Rc, + descriptions: &mut Vec>, + ) -> Result, CubeError> { + let description = if let Some(description) = + descriptions.iter().find(|d| d.alias() == "time_series") + { + description.clone() + } else { + let get_range_query_description = if time_dimension.get_date_range().is_some() { + None + } else { + Some(self.add_time_series_get_range_query( + time_dimension.clone(), + state.clone(), + descriptions, + )?) + }; + let time_series_node = MultiStageQueryDescription::new( + MultiStageMember::new( + MultiStageMemberType::Leaf(MultiStageLeafMemberType::TimeSeries(Rc::new( + TimeSeriesDescription { + time_dimension: time_dimension.clone(), + date_range_cte: get_range_query_description.map(|d| d.alias().clone()), + }, + ))), + time_dimension.member_evaluator(), + true, + false, + ), + state.clone(), + vec![], + "time_series".to_string(), + ); + descriptions.push(time_series_node.clone()); + time_series_node + }; + Ok(description) + } + + fn add_rolling_window_base( + &self, + member: Rc, + state: Rc, + ungrouped: bool, + descriptions: &mut Vec>, + ) -> Result, CubeError> { + let alias = format!("cte_{}", descriptions.len()); + let description = MultiStageQueryDescription::new( + MultiStageMember::new( + MultiStageMemberType::Leaf(MultiStageLeafMemberType::Measure), + member, + self.query_properties.ungrouped() || ungrouped, + true, + ), + state, + vec![], + alias.clone(), + ); + descriptions.push(description.clone()); + Ok(description) + } + + fn get_to_date_rolling_granularity( + &self, + rolling_window: &RollingWindow, + ) -> Result, CubeError> { + let is_to_date = rolling_window + .rolling_type + .as_ref() + .is_some_and(|tp| tp == "to_date"); + + if is_to_date { + if let Some(granularity) = &rolling_window.granularity { + Ok(Some(granularity.clone())) + } else { + Err(CubeError::user(format!( + "Granularity required for to_date rolling window" + ))) + } + } else { + Ok(None) + } + } + + fn make_rolling_base_state( + &self, + time_dimension: Rc, + rolling_window: &RollingWindow, + state: Rc, + ) -> Result<(Rc, Rc), CubeError> { + let time_dimension_base_name = time_dimension.base_dimension().full_name(); + let mut new_state = state.clone_state(); + let trailing_granularity = + GranularityHelper::granularity_from_interval(&rolling_window.trailing); + let leading_granularity = + GranularityHelper::granularity_from_interval(&rolling_window.leading); + let window_granularity = + GranularityHelper::min_granularity(&trailing_granularity, &leading_granularity)?; + let result_granularity = GranularityHelper::min_granularity( + &window_granularity, + &time_dimension.resolved_granularity()?, + )?; + + let new_time_dimension = time_dimension.change_granularity(result_granularity.clone())?; + //We keep only one time_dimension in the leaf query because, even if time_dimension values have different granularity, in the leaf query we need to group by the lowest granularity. + new_state.set_time_dimensions(vec![new_time_dimension.clone()]); + + let dimensions = new_state + .dimensions() + .clone() + .into_iter() + .filter(|d| { + d.member_evaluator() + .resolve_reference_chain() + .as_time_dimension() + .is_err() + }) + .collect_vec(); + new_state.set_dimensions(dimensions); + + if let Some(granularity) = self.get_to_date_rolling_granularity(rolling_window)? { + new_state.replace_to_date_date_range_filter(&time_dimension_base_name, &granularity); + } else { + new_state.replace_regular_date_range_filter( + &time_dimension_base_name, + rolling_window.trailing.clone(), + rolling_window.leading.clone(), + ); + } + + Ok((Rc::new(new_state), new_time_dimension)) + } } diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/rolling_window_planner.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/rolling_window_planner.rs deleted file mode 100644 index 6f68ed97e2b67..0000000000000 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/rolling_window_planner.rs +++ /dev/null @@ -1,319 +0,0 @@ -use super::{ - MultiStageAppliedState, MultiStageInodeMember, MultiStageInodeMemberType, - MultiStageLeafMemberType, MultiStageMember, MultiStageMemberType, MultiStageQueryDescription, - RollingWindowDescription, TimeSeriesDescription, -}; -use crate::cube_bridge::measure_definition::RollingWindow; -use crate::planner::query_tools::QueryTools; -use crate::planner::sql_evaluator::MemberSymbol; -use crate::planner::BaseMeasure; -use crate::planner::{BaseMember, BaseTimeDimension, GranularityHelper, QueryProperties}; -use cubenativeutils::CubeError; -use itertools::Itertools; -use std::rc::Rc; - -pub struct RollingWindowPlanner { - query_tools: Rc, - query_properties: Rc, -} - -impl RollingWindowPlanner { - pub fn new(query_tools: Rc, query_properties: Rc) -> Self { - Self { - query_tools, - query_properties, - } - } - - pub fn try_plan_rolling_window( - &self, - member: Rc, - state: Rc, - descriptions: &mut Vec>, - ) -> Result>, CubeError> { - if let Some(measure) = BaseMeasure::try_new(member.clone(), self.query_tools.clone())? { - if measure.is_cumulative() { - let rolling_window = if let Some(rolling_window) = measure.rolling_window() { - rolling_window.clone() - } else { - RollingWindow { - trailing: None, - leading: None, - offset: None, - rolling_type: None, - granularity: None, - } - }; - let ungrouped = match member.as_ref() { - MemberSymbol::Measure(measure_symbol) => { - measure_symbol.is_rolling_window() && !measure_symbol.is_addictive() - } - _ => false, - }; - let mut time_dimensions = self.query_properties.time_dimensions().clone(); - for dim in self.query_properties.dimension_symbols() { - let dim = dim.resolve_reference_chain(); - if let Ok(time_dimension_symbol) = dim.as_time_dimension() { - let time_dimension = BaseTimeDimension::try_new_from_td_symbol( - self.query_tools.clone(), - time_dimension_symbol, - )?; - time_dimensions.push(time_dimension); - } - } - - if time_dimensions.is_empty() { - let rolling_base = self.add_rolling_window_base( - member.clone(), - state.clone(), - ungrouped, - descriptions, - )?; - return Ok(Some(rolling_base)); - } - let uniq_time_dimensions = time_dimensions - .iter() - .unique_by(|a| (a.cube_name(), a.name(), a.get_date_range())) - .collect_vec(); - if uniq_time_dimensions.len() != 1 { - return Err(CubeError::internal( - "Rolling window requires one time dimension and equal date ranges" - .to_string(), - )); - } - - let time_dimension = - GranularityHelper::find_dimension_with_min_granularity(&time_dimensions)?; - - let (base_rolling_state, base_time_dimension) = self.make_rolling_base_state( - time_dimension.clone(), - &rolling_window, - state.clone(), - )?; - let input = vec![ - self.add_time_series(time_dimension.clone(), state.clone(), descriptions)?, - self.add_rolling_window_base( - member.clone(), - base_rolling_state, - ungrouped, - descriptions, - )?, - ]; - - let alias = format!("cte_{}", descriptions.len()); - - let rolling_window_descr = if measure.is_running_total() { - RollingWindowDescription::new_running_total(time_dimension, base_time_dimension) - } else if let Some(granularity) = - self.get_to_date_rolling_granularity(&rolling_window)? - { - RollingWindowDescription::new_to_date( - time_dimension, - base_time_dimension, - granularity, - ) - } else { - RollingWindowDescription::new_regular( - time_dimension, - base_time_dimension, - rolling_window.trailing.clone(), - rolling_window.leading.clone(), - rolling_window.offset.clone().unwrap_or("end".to_string()), - ) - }; - - let inode_member = MultiStageInodeMember::new( - MultiStageInodeMemberType::RollingWindow(rolling_window_descr), - vec![], - vec![], - None, - None, - ); - - let description = MultiStageQueryDescription::new( - MultiStageMember::new( - MultiStageMemberType::Inode(inode_member), - member, - self.query_properties.ungrouped(), - false, - ), - state.clone(), - input, - alias.clone(), - ); - descriptions.push(description.clone()); - Ok(Some(description)) - } else { - Ok(None) - } - } else { - Ok(None) - } - } - fn add_time_series_get_range_query( - &self, - time_dimension: Rc, - state: Rc, - descriptions: &mut Vec>, - ) -> Result, CubeError> { - let description = if let Some(description) = descriptions - .iter() - .find(|d| d.alias() == "time_series_get_range") - { - description.clone() - } else { - let time_series_get_range_node = MultiStageQueryDescription::new( - MultiStageMember::new( - MultiStageMemberType::Leaf(MultiStageLeafMemberType::TimeSeriesGetRange( - time_dimension.clone(), - )), - time_dimension.member_evaluator(), - true, - false, - ), - state.clone(), - vec![], - "time_series_get_range".to_string(), - ); - descriptions.push(time_series_get_range_node.clone()); - time_series_get_range_node - }; - Ok(description) - } - - fn add_time_series( - &self, - time_dimension: Rc, - state: Rc, - descriptions: &mut Vec>, - ) -> Result, CubeError> { - let description = if let Some(description) = - descriptions.iter().find(|d| d.alias() == "time_series") - { - description.clone() - } else { - let get_range_query_description = if time_dimension.get_date_range().is_some() { - None - } else { - Some(self.add_time_series_get_range_query( - time_dimension.clone(), - state.clone(), - descriptions, - )?) - }; - let time_series_node = MultiStageQueryDescription::new( - MultiStageMember::new( - MultiStageMemberType::Leaf(MultiStageLeafMemberType::TimeSeries(Rc::new( - TimeSeriesDescription { - time_dimension: time_dimension.clone(), - date_range_cte: get_range_query_description.map(|d| d.alias().clone()), - }, - ))), - time_dimension.member_evaluator(), - true, - false, - ), - state.clone(), - vec![], - "time_series".to_string(), - ); - descriptions.push(time_series_node.clone()); - time_series_node - }; - Ok(description) - } - - fn add_rolling_window_base( - &self, - member: Rc, - state: Rc, - ungrouped: bool, - descriptions: &mut Vec>, - ) -> Result, CubeError> { - let alias = format!("cte_{}", descriptions.len()); - let description = MultiStageQueryDescription::new( - MultiStageMember::new( - MultiStageMemberType::Leaf(MultiStageLeafMemberType::Measure), - member, - self.query_properties.ungrouped() || ungrouped, - true, - ), - state, - vec![], - alias.clone(), - ); - descriptions.push(description.clone()); - Ok(description) - } - - fn get_to_date_rolling_granularity( - &self, - rolling_window: &RollingWindow, - ) -> Result, CubeError> { - let is_to_date = rolling_window - .rolling_type - .as_ref() - .is_some_and(|tp| tp == "to_date"); - - if is_to_date { - if let Some(granularity) = &rolling_window.granularity { - Ok(Some(granularity.clone())) - } else { - Err(CubeError::user(format!( - "Granularity required for to_date rolling window" - ))) - } - } else { - Ok(None) - } - } - - fn make_rolling_base_state( - &self, - time_dimension: Rc, - rolling_window: &RollingWindow, - state: Rc, - ) -> Result<(Rc, Rc), CubeError> { - let time_dimension_base_name = time_dimension.base_dimension().full_name(); - let mut new_state = state.clone_state(); - let trailing_granularity = - GranularityHelper::granularity_from_interval(&rolling_window.trailing); - let leading_granularity = - GranularityHelper::granularity_from_interval(&rolling_window.leading); - let window_granularity = - GranularityHelper::min_granularity(&trailing_granularity, &leading_granularity)?; - let result_granularity = GranularityHelper::min_granularity( - &window_granularity, - &time_dimension.resolved_granularity()?, - )?; - - let new_time_dimension = time_dimension.change_granularity(result_granularity.clone())?; - //We keep only one time_dimension in the leaf query because, even if time_dimension values have different granularity, in the leaf query we need to group by the lowest granularity. - new_state.set_time_dimensions(vec![new_time_dimension.clone()]); - - let dimensions = new_state - .dimensions() - .clone() - .into_iter() - .filter(|d| { - d.member_evaluator() - .resolve_reference_chain() - .as_time_dimension() - .is_err() - }) - .collect_vec(); - new_state.set_dimensions(dimensions); - - if let Some(granularity) = self.get_to_date_rolling_granularity(rolling_window)? { - new_state.replace_to_date_date_range_filter(&time_dimension_base_name, &granularity); - } else { - new_state.replace_regular_date_range_filter( - &time_dimension_base_name, - rolling_window.trailing.clone(), - rolling_window.leading.clone(), - ); - } - - Ok((Rc::new(new_state), new_time_dimension)) - } -} diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/measure_symbol.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/measure_symbol.rs index 00e32afb884a7..0f49e175cb153 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/measure_symbol.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/measure_symbol.rs @@ -121,6 +121,38 @@ impl MeasureSymbol { }) } + pub fn new_unrolling(&self) -> Rc { + if self.is_rolling_window() { + let measure_type = if self.is_multi_stage { + format!("number") + } else { + self.measure_type.clone() + }; + Rc::new(Self { + cube_name: self.cube_name.clone(), + name: self.name.clone(), + owned_by_cube: self.owned_by_cube, + measure_type, + rolling_window: None, + is_multi_stage: false, + is_reference: false, + is_view: self.is_view, + measure_filters: self.measure_filters.clone(), + measure_drill_filters: self.measure_drill_filters.clone(), + time_shift: self.time_shift.clone(), + measure_order_by: self.measure_order_by.clone(), + reduce_by: self.reduce_by.clone(), + add_group_by: self.add_group_by.clone(), + group_by: self.group_by.clone(), + member_sql: self.member_sql.clone(), + pk_sqls: self.pk_sqls.clone(), + is_splitted_source: self.is_splitted_source, + }) + } else { + Rc::new(self.clone()) + } + } + pub fn new_patched( &self, new_measure_type: Option,