From f98771303f8cf7af451918638006ddbf2360f180 Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Tue, 1 Jul 2025 18:51:43 +0300 Subject: [PATCH 1/9] remove useless comment --- packages/cubejs-schema-compiler/src/adapter/BaseQuery.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js b/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js index 6c50fc2398971..a725d8dc510e9 100644 --- a/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js +++ b/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js @@ -3665,7 +3665,6 @@ export class BaseQuery { * @param {import('./Granularity').Granularity} granularity * @return {string} */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars dimensionTimeGroupedColumn(dimension, granularity) { let dtDate; From deaae68812335dd6cecf717060c820dc1c602dae Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Tue, 1 Jul 2025 18:59:27 +0300 Subject: [PATCH 2/9] implement baseQuery's dimensionTimeGroupedColumn() in Granularity object --- .../src/planner/sql_templates/plan.rs | 17 ++++++------ .../src/planner/time_dimension/granularity.rs | 26 +++++++++++++++++++ 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_templates/plan.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_templates/plan.rs index 69cb7400e9c26..5221a6ddb502e 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_templates/plan.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_templates/plan.rs @@ -62,6 +62,15 @@ impl PlanSqlTemplates { .time_grouped_column(granularity, dimension) } + pub fn date_bin( + &self, + interval: String, + source: String, + origin: String, + ) -> Result { + self.driver_tools.date_bin(interval, source, origin) + } + pub fn timestamp_precision(&self) -> Result { self.driver_tools.timestamp_precision() } @@ -121,14 +130,6 @@ impl PlanSqlTemplates { self.driver_tools.count_distinct_approx(sql) } - pub fn date_bin( - &self, - interval: String, - source: String, - origin: String, - ) -> Result { - self.driver_tools.date_bin(interval, source, origin) - } pub fn alias_name(name: &str) -> String { let res = name .with_boundaries(&[ diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/time_dimension/granularity.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/time_dimension/granularity.rs index 0e5e0d242ff5a..4a480e11be3bb 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/time_dimension/granularity.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/time_dimension/granularity.rs @@ -1,5 +1,6 @@ use super::{GranularityHelper, QueryDateTime, SqlInterval}; use crate::planner::sql_evaluator::SqlCall; +use crate::planner::sql_templates::PlanSqlTemplates; use chrono_tz::Tz; use cubenativeutils::CubeError; use itertools::Itertools; @@ -166,4 +167,29 @@ impl Granularity { fn default_origin(timezone: Tz) -> Result { Ok(QueryDateTime::now(timezone)?.start_of_year()) } + + pub fn apply_to_input_sql( + &self, + templates: &PlanSqlTemplates, + input: String, + ) -> Result { + let res = if self.is_natural_aligned { + if let Some(offset) = &self.granularity_offset { + let mut res = templates.subtract_interval(input.clone(), offset.clone())?; + res = templates.time_grouped_column(self.granularity_from_interval()?, res)?; + res = templates.add_interval(res, offset.clone())?; + res + } else { + templates.time_grouped_column(self.granularity_from_interval()?, input)? + } + } else { + templates.date_bin( + self.granularity_interval.clone(), + input, + self.origin_local_formatted(), + )? + }; + + Ok(res) + } } From 0d605e2a92f06dbb1d29cad56403e813bbe4f101 Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Tue, 1 Jul 2025 18:59:53 +0300 Subject: [PATCH 3/9] Use Granularity obj in ToDateRollingWindowJoinCondition --- .../logical_plan/multistage/rolling_window.rs | 8 ++++-- .../src/physical_plan_builder/builder.rs | 4 +-- .../cubesqlplanner/src/plan/join.rs | 10 +++---- .../multi_stage/member_query_planner.rs | 28 +++++++++++++++++-- .../sql_evaluator/sql_nodes/time_dimension.rs | 25 +---------------- 5 files changed, 40 insertions(+), 35 deletions(-) diff --git a/rust/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/rolling_window.rs b/rust/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/rolling_window.rs index 2d4b5f7699351..9301ec0e77f7f 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/rolling_window.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/logical_plan/multistage/rolling_window.rs @@ -1,6 +1,7 @@ use crate::logical_plan::*; use crate::planner::query_properties::OrderByItem; use crate::planner::sql_evaluator::MemberSymbol; +use crate::planner::Granularity; use std::rc::Rc; pub struct MultiStageRegularRollingWindow { @@ -24,14 +25,17 @@ impl PrettyPrint for MultiStageRegularRollingWindow { } pub struct MultiStageToDateRollingWindow { - pub granularity: String, + pub granularity_obj: Rc, } impl PrettyPrint for MultiStageToDateRollingWindow { fn pretty_print(&self, result: &mut PrettyPrintResult, state: &PrettyPrintState) { result.println("ToDate Rolling Window", state); let state = state.new_level(); - result.println(&format!("granularity: {}", self.granularity), &state); + result.println( + &format!("granularity: {}", self.granularity_obj.granularity()), + &state, + ); } } diff --git a/rust/cubesqlplanner/cubesqlplanner/src/physical_plan_builder/builder.rs b/rust/cubesqlplanner/cubesqlplanner/src/physical_plan_builder/builder.rs index 2923330135c2b..2c66e0977b4a6 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/physical_plan_builder/builder.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/physical_plan_builder/builder.rs @@ -1061,7 +1061,7 @@ impl PhysicalPlanBuilder { MultiStageRollingWindowType::ToDate(to_date_rolling_window) => { JoinCondition::new_to_date_rolling_join( root_alias.clone(), - to_date_rolling_window.granularity.clone(), + to_date_rolling_window.granularity_obj.clone(), Expr::Reference(QualifiedColumnName::new( Some(measure_input_alias.clone()), base_time_dimension_alias, @@ -1092,7 +1092,7 @@ impl PhysicalPlanBuilder { let mut render_references = HashMap::new(); let mut select_builder = SelectBuilder::new(from.clone()); - //We insert render reference for main time dimension (with the some granularity as in time series to avoid unnecessary date_tranc) + //We insert render reference for main time dimension (with some granularity as in time series to avoid unnecessary date_tranc) render_references.insert( time_dimension.full_name(), QualifiedColumnName::new(Some(root_alias.clone()), format!("date_from")), diff --git a/rust/cubesqlplanner/cubesqlplanner/src/plan/join.rs b/rust/cubesqlplanner/cubesqlplanner/src/plan/join.rs index 93c3bfa3a458c..b70927c195357 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/plan/join.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/plan/join.rs @@ -1,7 +1,7 @@ use super::{Expr, SingleAliasedSource}; use crate::planner::query_tools::QueryTools; use crate::planner::sql_templates::PlanSqlTemplates; -use crate::planner::{BaseJoinCondition, VisitorContext}; +use crate::planner::{BaseJoinCondition, Granularity, VisitorContext}; use cubenativeutils::CubeError; use lazy_static::lazy_static; @@ -118,7 +118,7 @@ impl RollingTotalJoinCondition { } pub struct ToDateRollingWindowJoinCondition { time_series_source: String, - granularity: String, + granularity: Rc, time_dimension: Expr, _query_tools: Rc, } @@ -126,7 +126,7 @@ pub struct ToDateRollingWindowJoinCondition { impl ToDateRollingWindowJoinCondition { pub fn new( time_series_source: String, - granularity: String, + granularity: Rc, time_dimension: Expr, query_tools: Rc, ) -> Self { @@ -151,7 +151,7 @@ impl ToDateRollingWindowJoinCondition { templates.column_reference(&Some(self.time_series_source.clone()), "date_from")?; let date_from = templates.rolling_window_expr_timestamp_cast(&date_from)?; let date_to = templates.rolling_window_expr_timestamp_cast(&date_to)?; - let grouped_from = templates.time_grouped_column(self.granularity.clone(), date_from)?; + let grouped_from = self.granularity.apply_to_input_sql(templates, date_from)?; let result = format!("{date_column} >= {grouped_from} and {date_column} <= {date_to}"); Ok(result) } @@ -243,7 +243,7 @@ impl JoinCondition { pub fn new_to_date_rolling_join( time_series_source: String, - granularity: String, + granularity: Rc, time_dimension: Expr, query_tools: Rc, ) -> Self { diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/member_query_planner.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/member_query_planner.rs index 7cad1c4e66f54..a481a23c26dba 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/member_query_planner.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/member_query_planner.rs @@ -6,7 +6,9 @@ use crate::logical_plan::*; use crate::planner::planners::{multi_stage::RollingWindowType, QueryPlanner, SimpleQueryPlanner}; use crate::planner::query_tools::QueryTools; use crate::planner::sql_evaluator::MemberSymbol; -use crate::planner::{BaseDimension, BaseMeasure, BaseMember, BaseMemberHelper, BaseTimeDimension}; +use crate::planner::{ + BaseDimension, BaseMeasure, BaseMember, BaseMemberHelper, BaseTimeDimension, GranularityHelper, +}; use crate::planner::{OrderByItem, QueryProperties}; use cubenativeutils::CubeError; @@ -126,8 +128,30 @@ impl MultiStageMemberQueryPlanner { }) } RollingWindowType::ToDate(to_date_rolling_window) => { + let time_dimension = &rolling_window_desc.time_dimension; + let query_granularity = to_date_rolling_window.granularity.clone(); + + let evaluator_compiler_cell = self.query_tools.evaluator_compiler().clone(); + let mut evaluator_compiler = evaluator_compiler_cell.borrow_mut(); + + let Some(granularity_obj) = GranularityHelper::make_granularity_obj( + self.query_tools.cube_evaluator().clone(), + &mut evaluator_compiler, + self.query_tools.timezone().clone(), + time_dimension.cube_name(), + time_dimension.name(), + Some(query_granularity.clone()), + )? + else { + return Err(CubeError::internal(format!( + "Rolling window granularity '{}' is not found in time dimension '{}'", + query_granularity, + time_dimension.name() + ))); + }; + MultiStageRollingWindowType::ToDate(MultiStageToDateRollingWindow { - granularity: to_date_rolling_window.granularity.clone(), + granularity_obj: Rc::new(granularity_obj), }) } RollingWindowType::RunningTotal => MultiStageRollingWindowType::RunningTotal, diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_nodes/time_dimension.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_nodes/time_dimension.rs index 4376d59887642..b24c59e22ee1a 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_nodes/time_dimension.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_nodes/time_dimension.rs @@ -62,30 +62,7 @@ impl SqlNode for TimeDimensionNode { templates.convert_tz(input_sql)? }; - let res = if granularity_obj.is_natural_aligned() { - if let Some(granularity_offset) = granularity_obj.granularity_offset() { - let dt = templates - .subtract_interval(converted_tz, granularity_offset.clone())?; - let dt = templates.time_grouped_column( - granularity_obj.granularity_from_interval()?, - dt, - )?; - templates.add_interval(dt, granularity_offset.clone())? - } else { - templates.time_grouped_column( - granularity_obj.granularity().clone(), - converted_tz, - )? - } - } else { - templates.date_bin( - granularity_obj.granularity_interval().clone(), - converted_tz, - granularity_obj.origin_local_formatted(), - )? - }; - - res + granularity_obj.apply_to_input_sql(templates, converted_tz)? } else { input_sql }; From 8f97727e5d556e3f565ebe9dc0f811fdbcf6ffcf Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Tue, 1 Jul 2025 19:32:19 +0300 Subject: [PATCH 4/9] add missing quarter to SqlInterval --- .../planner/time_dimension/sql_interval.rs | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/time_dimension/sql_interval.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/time_dimension/sql_interval.rs index ebd130787fd62..3f82f780ff689 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/time_dimension/sql_interval.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/time_dimension/sql_interval.rs @@ -6,6 +6,7 @@ use std::str::FromStr; #[derive(Default, Debug, PartialEq, Clone, Hash, Eq)] pub struct SqlInterval { pub year: i32, + pub quarter: i32, pub month: i32, pub week: i32, pub day: i32, @@ -17,6 +18,7 @@ pub struct SqlInterval { impl SqlInterval { pub fn new( year: i32, + quarter: i32, month: i32, week: i32, day: i32, @@ -26,6 +28,7 @@ impl SqlInterval { ) -> Self { Self { year, + quarter, month, week, day, @@ -48,12 +51,12 @@ impl SqlInterval { "week" } else if self.month != 0 { "month" + } else if self.quarter != 0 { + "quarter" } else if self.year != 0 { "year" } else { - return Err(CubeError::internal(format!( - "Attempt to get granularity from empty SqlInterval" - ))); + return Err(CubeError::internal("Attempt to get granularity from empty SqlInterval".to_string())); }; Ok(res.to_string()) } @@ -63,6 +66,9 @@ impl SqlInterval { if self.year != 0 { res.push(format!("{} year", self.year)); } + if self.quarter != 0 { + res.push(format!("{} quarter", self.quarter)); + } if self.month != 0 { res.push(format!("{} month", self.month)); } @@ -87,6 +93,7 @@ impl SqlInterval { pub fn inverse(&self) -> Self { Self::new( -self.year, + -self.quarter, -self.month, -self.week, -self.day, @@ -102,6 +109,7 @@ impl Add for SqlInterval { fn add(self, other: SqlInterval) -> SqlInterval { SqlInterval::new( self.year + other.year, + self.quarter + other.quarter, self.month + other.month, self.week + other.week, self.day + other.day, @@ -115,6 +123,7 @@ impl Add for SqlInterval { impl AddAssign<&SqlInterval> for SqlInterval { fn add_assign(&mut self, other: &SqlInterval) { self.year += other.year; + self.quarter += other.quarter; self.month += other.month; self.week += other.week; self.day += other.day; @@ -135,6 +144,7 @@ impl Sub for SqlInterval { fn sub(self, other: SqlInterval) -> SqlInterval { SqlInterval::new( self.year - other.year, + self.quarter - other.quarter, self.month - other.month, self.week - other.week, self.day - other.day, @@ -150,6 +160,7 @@ impl Neg for SqlInterval { fn neg(self) -> SqlInterval { SqlInterval::new( -self.year, + -self.quarter, -self.month, -self.week, -self.day, @@ -175,6 +186,7 @@ impl FromStr for SqlInterval { "day" | "days" => result.day = value, "week" | "weeks" => result.week = value, "month" | "months" => result.month = value, + "quarter" | "quarters" => result.quarter = value, "year" | "years" => result.year = value, other => return Err(CubeError::user(format!("Invalid interval unit: {}", other))), } @@ -191,28 +203,28 @@ mod tests { fn test_from_str() { assert_eq!( SqlInterval::from_str("1 second").unwrap(), - SqlInterval::new(0, 0, 0, 0, 0, 0, 1) + SqlInterval::new(0, 0, 0, 0, 0, 0, 0, 1) ); assert_eq!( SqlInterval::from_str("1 year 3 months 4 weeks 2 day 4 hours 2 minutes 1 second") .unwrap(), - SqlInterval::new(1, 3, 4, 2, 4, 2, 1) + SqlInterval::new(1, 0, 3, 4, 2, 4, 2, 1) ); } #[test] fn test_arithmetic() { assert_eq!( - SqlInterval::new(1, 3, 4, 2, 4, 2, 1) + SqlInterval::new(1, 3, 4, 2, 4, 2, 1), - SqlInterval::new(2, 6, 8, 4, 8, 4, 2) + SqlInterval::new(1, 0, 3, 4, 2, 4, 2, 1) + SqlInterval::new(1, 0, 3, 4, 2, 4, 2, 1), + SqlInterval::new(2, 0, 6, 8, 4, 8, 4, 2) ); assert_eq!( - SqlInterval::new(1, 3, 4, 2, 4, 2, 1) - SqlInterval::new(1, 4, 4, 2, 2, 2, 1), - SqlInterval::new(0, -1, 0, 0, 2, 0, 0) + SqlInterval::new(1, 0, 3, 4, 2, 4, 2, 1) - SqlInterval::new(1, 0, 4, 4, 2, 2, 2, 1), + SqlInterval::new(0, 0, -1, 0, 0, 2, 0, 0) ); assert_eq!( - -SqlInterval::new(1, 3, -4, 2, 4, 2, 1), - SqlInterval::new(-1, -3, 4, -2, -4, -2, -1) + -SqlInterval::new(1, 0, 3, -4, 2, 4, 2, 1), + SqlInterval::new(-1, 0, -3, 4, -2, -4, -2, -1) ); } } From 696fcc6a7f3419acf9700e45d3f4c5e5aa4a5fc9 Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Tue, 1 Jul 2025 19:39:12 +0300 Subject: [PATCH 5/9] cargo fmt --- .../cubesqlplanner/src/planner/time_dimension/sql_interval.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/time_dimension/sql_interval.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/time_dimension/sql_interval.rs index 3f82f780ff689..8d5ce117d4d7c 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/time_dimension/sql_interval.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/time_dimension/sql_interval.rs @@ -56,7 +56,9 @@ impl SqlInterval { } else if self.year != 0 { "year" } else { - return Err(CubeError::internal("Attempt to get granularity from empty SqlInterval".to_string())); + return Err(CubeError::internal( + "Attempt to get granularity from empty SqlInterval".to_string(), + )); }; Ok(res.to_string()) } From d5396eadf0a3cbe73e78de5d762b88e65db6e0d1 Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Tue, 1 Jul 2025 20:28:09 +0300 Subject: [PATCH 6/9] add support for custom granularities to base filter --- .../src/planner/filter/base_filter.rs | 69 +++++++++++++------ 1 file changed, 48 insertions(+), 21 deletions(-) diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/filter/base_filter.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/filter/base_filter.rs index 0a5628e0293fd..f9f7f8e5e7e0f 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/filter/base_filter.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/filter/base_filter.rs @@ -3,7 +3,7 @@ use crate::planner::query_tools::QueryTools; use crate::planner::sql_evaluator::MemberSymbol; use crate::planner::sql_templates::PlanSqlTemplates; use crate::planner::sql_templates::TemplateProjectionColumn; -use crate::planner::QueryDateTimeHelper; +use crate::planner::{Granularity, GranularityHelper, QueryDateTimeHelper}; use crate::planner::{evaluate_with_context, FiltersContext, VisitorContext}; use cubenativeutils::CubeError; use std::rc::Rc; @@ -188,13 +188,48 @@ impl BaseFilter { filters_context, &member_type, )?, - FilterOperator::ToDateRollingWindowDateRange => self - .to_date_rolling_window_date_range( - &member_sql, - plan_templates, - filters_context, - &member_type, - )?, + FilterOperator::ToDateRollingWindowDateRange => { + let query_granularity = if self.values.len() >= 3 { + if let Some(granularity) = &self.values[2] { + granularity + } else { + return Err(CubeError::user( + "Granularity required for to_date rolling window".to_string(), + )); + } + } else { + return Err(CubeError::user( + "Granularity required for to_date rolling window".to_string(), + )); + }; + let evaluator_compiler_cell = self.query_tools.evaluator_compiler().clone(); + let mut evaluator_compiler = evaluator_compiler_cell.borrow_mut(); + + let Some(granularity_obj) = GranularityHelper::make_granularity_obj( + self.query_tools.cube_evaluator().clone(), + &mut evaluator_compiler, + self.query_tools.timezone().clone(), + &symbol.cube_name(), + &symbol.name(), + Some(query_granularity.clone()), + )? + else { + return Err(CubeError::internal(format!( + "Rolling window granularity '{}' is not found in time dimension '{}'", + query_granularity, + symbol.name() + ))); + }; + + self + .to_date_rolling_window_date_range( + &member_sql, + plan_templates, + filters_context, + &member_type, + granularity_obj, + )? + }, FilterOperator::In => { self.in_where(&member_sql, plan_templates, filters_context, &member_type)? } @@ -539,22 +574,14 @@ impl BaseFilter { plan_templates: &PlanSqlTemplates, _filters_context: &FiltersContext, _member_type: &Option, + granularity_obj: Granularity, ) -> Result { let (from, to) = self.date_range_from_time_series(plan_templates)?; - let from = if self.values.len() >= 3 { - if let Some(granularity) = &self.values[2] { - plan_templates.time_grouped_column(granularity.clone(), from)? - } else { - return Err(CubeError::user(format!( - "Granularity required for to_date rolling window" - ))); - } - } else { - return Err(CubeError::user(format!( - "Granularity required for to_date rolling window" - ))); - }; + let from = granularity_obj.apply_to_input_sql( + plan_templates, + from.clone(), + )?; let date_field = plan_templates.convert_tz(member_sql.to_string())?; plan_templates.time_range_filter(date_field, from, to) From 961788963b2dfa9c0782e46d8a11457ddcd2c697 Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Tue, 1 Jul 2025 20:28:17 +0300 Subject: [PATCH 7/9] add tests --- .../postgres/sql-generation.test.ts | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) 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..8eee3170bcb5d 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 @@ -144,6 +144,13 @@ describe('SQL Generation', () => { granularity: 'week' } }, + countRollingThreeDaysToDate: { + type: 'count', + rollingWindow: { + type: 'to_date', + granularity: 'three_days' + } + }, revenue_qtd: { type: 'sum', sql: 'amount', @@ -1210,6 +1217,137 @@ SELECT 1 AS revenue, cast('2024-01-01' AS timestamp) as time UNION ALL } ])); + it('custom granularity rolling window to_date with one time dimension with regular granularity', async () => runQueryTest({ + measures: [ + 'visitors.countRollingThreeDaysToDate' + ], + timeDimensions: [ + { + dimension: 'visitors.created_at', + granularity: 'day', + dateRange: ['2017-01-01', '2017-01-10'] + } + ], + order: [{ + id: 'visitors.created_at' + }], + timezone: 'America/Los_Angeles' + }, [ + { + visitors__count_rolling_three_days_to_date: null, + visitors__created_at_day: '2017-01-01T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: '1', + visitors__created_at_day: '2017-01-02T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: '1', + visitors__created_at_day: '2017-01-03T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: '1', + visitors__created_at_day: '2017-01-04T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: '2', + visitors__created_at_day: '2017-01-05T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: '4', + visitors__created_at_day: '2017-01-06T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: null, + visitors__created_at_day: '2017-01-07T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: null, + visitors__created_at_day: '2017-01-08T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: null, + visitors__created_at_day: '2017-01-09T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: null, + visitors__created_at_day: '2017-01-10T00:00:00.000Z', + }, + ])); + + it('custom granularity rolling window to_date with two time dimension granularities one custom one regular', async () => runQueryTest({ + measures: [ + 'visitors.countRollingThreeDaysToDate' + ], + timeDimensions: [ + { + dimension: 'visitors.created_at', + granularity: 'three_days', + dateRange: ['2017-01-01', '2017-01-10'] + }, + { + dimension: 'visitors.created_at', + granularity: 'day', + dateRange: ['2017-01-01', '2017-01-10'] + } + ], + order: [{ + id: 'visitors.created_at' + }], + timezone: 'America/Los_Angeles' + }, [ + { + visitors__count_rolling_three_days_to_date: null, + visitors__created_at_day: '2017-01-01T00:00:00.000Z', + visitors__created_at_three_days: '2017-01-01T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: '1', + visitors__created_at_day: '2017-01-02T00:00:00.000Z', + visitors__created_at_three_days: '2017-01-01T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: '1', + visitors__created_at_day: '2017-01-03T00:00:00.000Z', + visitors__created_at_three_days: '2017-01-01T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: '1', + visitors__created_at_day: '2017-01-04T00:00:00.000Z', + visitors__created_at_three_days: '2017-01-04T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: '2', + visitors__created_at_day: '2017-01-05T00:00:00.000Z', + visitors__created_at_three_days: '2017-01-04T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: '4', + visitors__created_at_day: '2017-01-06T00:00:00.000Z', + visitors__created_at_three_days: '2017-01-04T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: null, + visitors__created_at_day: '2017-01-07T00:00:00.000Z', + visitors__created_at_three_days: '2017-01-07T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: null, + visitors__created_at_day: '2017-01-08T00:00:00.000Z', + visitors__created_at_three_days: '2017-01-07T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: null, + visitors__created_at_day: '2017-01-09T00:00:00.000Z', + visitors__created_at_three_days: '2017-01-07T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: null, + visitors__created_at_day: '2017-01-10T00:00:00.000Z', + visitors__created_at_three_days: '2017-01-10T00:00:00.000Z', + }, + ])); + it('rolling window with same td with and without granularity', async () => runQueryTest({ measures: [ 'visitors.countRollingWeekToDate' From 5100469bcd3bf1c33ec80cae5b4963975896375b Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Tue, 1 Jul 2025 20:30:44 +0300 Subject: [PATCH 8/9] cargo fmt --- .../src/planner/filter/base_filter.rs | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/filter/base_filter.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/filter/base_filter.rs index f9f7f8e5e7e0f..8cab5a19275ea 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/filter/base_filter.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/filter/base_filter.rs @@ -3,8 +3,8 @@ use crate::planner::query_tools::QueryTools; use crate::planner::sql_evaluator::MemberSymbol; use crate::planner::sql_templates::PlanSqlTemplates; use crate::planner::sql_templates::TemplateProjectionColumn; -use crate::planner::{Granularity, GranularityHelper, QueryDateTimeHelper}; use crate::planner::{evaluate_with_context, FiltersContext, VisitorContext}; +use crate::planner::{Granularity, GranularityHelper, QueryDateTimeHelper}; use cubenativeutils::CubeError; use std::rc::Rc; @@ -220,16 +220,15 @@ impl BaseFilter { symbol.name() ))); }; - - self - .to_date_rolling_window_date_range( - &member_sql, - plan_templates, - filters_context, - &member_type, - granularity_obj, - )? - }, + + self.to_date_rolling_window_date_range( + &member_sql, + plan_templates, + filters_context, + &member_type, + granularity_obj, + )? + } FilterOperator::In => { self.in_where(&member_sql, plan_templates, filters_context, &member_type)? } @@ -578,10 +577,7 @@ impl BaseFilter { ) -> Result { let (from, to) = self.date_range_from_time_series(plan_templates)?; - let from = granularity_obj.apply_to_input_sql( - plan_templates, - from.clone(), - )?; + let from = granularity_obj.apply_to_input_sql(plan_templates, from.clone())?; let date_field = plan_templates.convert_tz(member_sql.to_string())?; plan_templates.time_range_filter(date_field, from, to) From 06803a2281a364c5ee46aec2b0c8ba6f0d9ed3b4 Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Tue, 1 Jul 2025 21:27:31 +0300 Subject: [PATCH 9/9] skip tests for basequery --- .../postgres/sql-generation.test.ts | 262 +++++++++--------- 1 file changed, 137 insertions(+), 125 deletions(-) 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 8eee3170bcb5d..813760db0f71f 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 @@ -1217,136 +1217,148 @@ SELECT 1 AS revenue, cast('2024-01-01' AS timestamp) as time UNION ALL } ])); - it('custom granularity rolling window to_date with one time dimension with regular granularity', async () => runQueryTest({ - measures: [ - 'visitors.countRollingThreeDaysToDate' - ], - timeDimensions: [ + if (getEnv('nativeSqlPlanner')) { + it('custom granularity rolling window to_date with one time dimension with regular granularity', async () => runQueryTest({ + measures: [ + 'visitors.countRollingThreeDaysToDate' + ], + timeDimensions: [ + { + dimension: 'visitors.created_at', + granularity: 'day', + dateRange: ['2017-01-01', '2017-01-10'] + } + ], + order: [{ + id: 'visitors.created_at' + }], + timezone: 'America/Los_Angeles' + }, [ { - dimension: 'visitors.created_at', - granularity: 'day', - dateRange: ['2017-01-01', '2017-01-10'] - } - ], - order: [{ - id: 'visitors.created_at' - }], - timezone: 'America/Los_Angeles' - }, [ - { - visitors__count_rolling_three_days_to_date: null, - visitors__created_at_day: '2017-01-01T00:00:00.000Z', - }, - { - visitors__count_rolling_three_days_to_date: '1', - visitors__created_at_day: '2017-01-02T00:00:00.000Z', - }, - { - visitors__count_rolling_three_days_to_date: '1', - visitors__created_at_day: '2017-01-03T00:00:00.000Z', - }, - { - visitors__count_rolling_three_days_to_date: '1', - visitors__created_at_day: '2017-01-04T00:00:00.000Z', - }, - { - visitors__count_rolling_three_days_to_date: '2', - visitors__created_at_day: '2017-01-05T00:00:00.000Z', - }, - { - visitors__count_rolling_three_days_to_date: '4', - visitors__created_at_day: '2017-01-06T00:00:00.000Z', - }, - { - visitors__count_rolling_three_days_to_date: null, - visitors__created_at_day: '2017-01-07T00:00:00.000Z', - }, - { - visitors__count_rolling_three_days_to_date: null, - visitors__created_at_day: '2017-01-08T00:00:00.000Z', - }, - { - visitors__count_rolling_three_days_to_date: null, - visitors__created_at_day: '2017-01-09T00:00:00.000Z', - }, - { - visitors__count_rolling_three_days_to_date: null, - visitors__created_at_day: '2017-01-10T00:00:00.000Z', - }, - ])); + visitors__count_rolling_three_days_to_date: null, + visitors__created_at_day: '2017-01-01T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: '1', + visitors__created_at_day: '2017-01-02T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: '1', + visitors__created_at_day: '2017-01-03T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: '1', + visitors__created_at_day: '2017-01-04T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: '2', + visitors__created_at_day: '2017-01-05T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: '4', + visitors__created_at_day: '2017-01-06T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: null, + visitors__created_at_day: '2017-01-07T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: null, + visitors__created_at_day: '2017-01-08T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: null, + visitors__created_at_day: '2017-01-09T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: null, + visitors__created_at_day: '2017-01-10T00:00:00.000Z', + }, + ])); + } else { + it.skip('NO_BASE_QUERY_SUPPORT: custom granularity rolling window to_date with one time dimension with regular granularity', () => { + // Skipping because it works only in Tesseract + }); + } - it('custom granularity rolling window to_date with two time dimension granularities one custom one regular', async () => runQueryTest({ - measures: [ - 'visitors.countRollingThreeDaysToDate' - ], - timeDimensions: [ + if (getEnv('nativeSqlPlanner')) { + it('custom granularity rolling window to_date with two time dimension granularities one custom one regular', async () => runQueryTest({ + measures: [ + 'visitors.countRollingThreeDaysToDate' + ], + timeDimensions: [ + { + dimension: 'visitors.created_at', + granularity: 'three_days', + dateRange: ['2017-01-01', '2017-01-10'] + }, + { + dimension: 'visitors.created_at', + granularity: 'day', + dateRange: ['2017-01-01', '2017-01-10'] + } + ], + order: [{ + id: 'visitors.created_at' + }], + timezone: 'America/Los_Angeles' + }, [ { - dimension: 'visitors.created_at', - granularity: 'three_days', - dateRange: ['2017-01-01', '2017-01-10'] + visitors__count_rolling_three_days_to_date: null, + visitors__created_at_day: '2017-01-01T00:00:00.000Z', + visitors__created_at_three_days: '2017-01-01T00:00:00.000Z', }, { - dimension: 'visitors.created_at', - granularity: 'day', - dateRange: ['2017-01-01', '2017-01-10'] - } - ], - order: [{ - id: 'visitors.created_at' - }], - timezone: 'America/Los_Angeles' - }, [ - { - visitors__count_rolling_three_days_to_date: null, - visitors__created_at_day: '2017-01-01T00:00:00.000Z', - visitors__created_at_three_days: '2017-01-01T00:00:00.000Z', - }, - { - visitors__count_rolling_three_days_to_date: '1', - visitors__created_at_day: '2017-01-02T00:00:00.000Z', - visitors__created_at_three_days: '2017-01-01T00:00:00.000Z', - }, - { - visitors__count_rolling_three_days_to_date: '1', - visitors__created_at_day: '2017-01-03T00:00:00.000Z', - visitors__created_at_three_days: '2017-01-01T00:00:00.000Z', - }, - { - visitors__count_rolling_three_days_to_date: '1', - visitors__created_at_day: '2017-01-04T00:00:00.000Z', - visitors__created_at_three_days: '2017-01-04T00:00:00.000Z', - }, - { - visitors__count_rolling_three_days_to_date: '2', - visitors__created_at_day: '2017-01-05T00:00:00.000Z', - visitors__created_at_three_days: '2017-01-04T00:00:00.000Z', - }, - { - visitors__count_rolling_three_days_to_date: '4', - visitors__created_at_day: '2017-01-06T00:00:00.000Z', - visitors__created_at_three_days: '2017-01-04T00:00:00.000Z', - }, - { - visitors__count_rolling_three_days_to_date: null, - visitors__created_at_day: '2017-01-07T00:00:00.000Z', - visitors__created_at_three_days: '2017-01-07T00:00:00.000Z', - }, - { - visitors__count_rolling_three_days_to_date: null, - visitors__created_at_day: '2017-01-08T00:00:00.000Z', - visitors__created_at_three_days: '2017-01-07T00:00:00.000Z', - }, - { - visitors__count_rolling_three_days_to_date: null, - visitors__created_at_day: '2017-01-09T00:00:00.000Z', - visitors__created_at_three_days: '2017-01-07T00:00:00.000Z', - }, - { - visitors__count_rolling_three_days_to_date: null, - visitors__created_at_day: '2017-01-10T00:00:00.000Z', - visitors__created_at_three_days: '2017-01-10T00:00:00.000Z', - }, - ])); + visitors__count_rolling_three_days_to_date: '1', + visitors__created_at_day: '2017-01-02T00:00:00.000Z', + visitors__created_at_three_days: '2017-01-01T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: '1', + visitors__created_at_day: '2017-01-03T00:00:00.000Z', + visitors__created_at_three_days: '2017-01-01T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: '1', + visitors__created_at_day: '2017-01-04T00:00:00.000Z', + visitors__created_at_three_days: '2017-01-04T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: '2', + visitors__created_at_day: '2017-01-05T00:00:00.000Z', + visitors__created_at_three_days: '2017-01-04T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: '4', + visitors__created_at_day: '2017-01-06T00:00:00.000Z', + visitors__created_at_three_days: '2017-01-04T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: null, + visitors__created_at_day: '2017-01-07T00:00:00.000Z', + visitors__created_at_three_days: '2017-01-07T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: null, + visitors__created_at_day: '2017-01-08T00:00:00.000Z', + visitors__created_at_three_days: '2017-01-07T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: null, + visitors__created_at_day: '2017-01-09T00:00:00.000Z', + visitors__created_at_three_days: '2017-01-07T00:00:00.000Z', + }, + { + visitors__count_rolling_three_days_to_date: null, + visitors__created_at_day: '2017-01-10T00:00:00.000Z', + visitors__created_at_three_days: '2017-01-10T00:00:00.000Z', + }, + ])); + } else { + it.skip('NO_BASE_QUERY_SUPPORT: custom granularity rolling window to_date with two time dimension granularities one custom one regular', () => { + // Skipping because it works only in Tesseract + }); + } it('rolling window with same td with and without granularity', async () => runQueryTest({ measures: [ pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy