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; 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..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 @@ -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,149 @@ SELECT 1 AS revenue, cast('2024-01-01' AS timestamp) as time UNION ALL } ])); + 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' + }, [ + { + 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 + }); + } + + 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' + }, [ + { + 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', + }, + ])); + } 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: [ 'visitors.countRollingWeekToDate' 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/filter/base_filter.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/filter/base_filter.rs index 0a5628e0293fd..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::QueryDateTimeHelper; use crate::planner::{evaluate_with_context, FiltersContext, VisitorContext}; +use crate::planner::{Granularity, GranularityHelper, QueryDateTimeHelper}; use cubenativeutils::CubeError; use std::rc::Rc; @@ -188,13 +188,47 @@ impl BaseFilter { filters_context, &member_type, )?, - FilterOperator::ToDateRollingWindowDateRange => self - .to_date_rolling_window_date_range( + 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 +573,11 @@ 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) 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 }; 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) + } } 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..8d5ce117d4d7c 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,14 @@ 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 +68,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 +95,7 @@ impl SqlInterval { pub fn inverse(&self) -> Self { Self::new( -self.year, + -self.quarter, -self.month, -self.week, -self.day, @@ -102,6 +111,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 +125,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 +146,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 +162,7 @@ impl Neg for SqlInterval { fn neg(self) -> SqlInterval { SqlInterval::new( -self.year, + -self.quarter, -self.month, -self.week, -self.day, @@ -175,6 +188,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 +205,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) ); } }