From f4e54abc9e7372370683fd02792865e097978440 Mon Sep 17 00:00:00 2001 From: Alexandr Romanenko Date: Wed, 2 Apr 2025 22:15:32 +0200 Subject: [PATCH 1/4] chore(tesseract): Refactoring of multistage time-shifts --- .../postgres/member-expression.test.ts | 23 ++++- .../src/planner/base_measure.rs | 83 ++--------------- .../planners/multi_stage/applied_state.rs | 16 ++-- .../planner/planners/multi_stage/member.rs | 64 +------------ .../multi_stage/multi_stage_query_planner.rs | 13 +-- .../collectors/find_owned_by_cube.rs | 61 ++++++++++++ .../planner/sql_evaluator/collectors/mod.rs | 2 + .../src/planner/sql_evaluator/mod.rs | 2 +- .../sql_evaluator/sql_nodes/factory.rs | 5 +- .../sql_evaluator/sql_nodes/time_shift.rs | 9 +- .../sql_evaluator/symbols/measure_symbol.rs | 59 ++++++++++++ .../src/planner/sql_evaluator/symbols/mod.rs | 2 +- .../planner/time_dimension/sql_interval.rs | 92 ++++++++++++++++++- 13 files changed, 270 insertions(+), 161 deletions(-) create mode 100644 rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/collectors/find_owned_by_cube.rs diff --git a/packages/cubejs-schema-compiler/test/integration/postgres/member-expression.test.ts b/packages/cubejs-schema-compiler/test/integration/postgres/member-expression.test.ts index 89249f52aeb0f..df16364c3e15d 100644 --- a/packages/cubejs-schema-compiler/test/integration/postgres/member-expression.test.ts +++ b/packages/cubejs-schema-compiler/test/integration/postgres/member-expression.test.ts @@ -203,7 +203,6 @@ cubes: views: - name: customers_view - cubes: - join_path: customers includes: @@ -384,4 +383,26 @@ views: // Skipping because it works only in Tesseract }); } + + if (getEnv('nativeSqlPlanner')) { + it('multi stage duplicated time shift over view and origin cube', async () => runQueryTest({ + measures: [ + 'orders_view.cagr_1_y' + ], + timeDimensions: [ + { + dimension: 'orders_view.date', + dateRange: ['2022-01-01', '2022-10-31'], + }, + ], + + timezone: 'America/Los_Angeles' + }, + + [{ orders_view__cagr_1_y: '-0.90000000000000000000' }])); + } else { + it.skip('member expression multi stage with time dimension segment', () => { + // Skipping because it works only in Tesseract + }); + } }); diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/base_measure.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/base_measure.rs index d903e5ab49e83..3c13b9b547772 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/base_measure.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/base_measure.rs @@ -1,67 +1,14 @@ use super::query_tools::QueryTools; -use super::sql_evaluator::{MemberExpressionSymbol, MemberSymbol, SqlCall}; +use super::sql_evaluator::{MemberExpressionSymbol, MemberSymbol, SqlCall, MeasureTimeShift}; use super::{evaluate_with_context, BaseMember, BaseMemberHelper, VisitorContext}; use crate::cube_bridge::measure_definition::{ MeasureDefinition, RollingWindow, TimeShiftReference, }; use crate::planner::sql_templates::PlanSqlTemplates; use cubenativeutils::CubeError; -use lazy_static::lazy_static; -use regex::Regex; use std::fmt::{Debug, Formatter}; use std::rc::Rc; -#[derive(Clone, Debug)] -pub struct MeasureTimeShift { - pub interval: String, - pub time_dimension: String, -} - -lazy_static! { - static ref INTERVAL_MATCH_RE: Regex = - Regex::new(r"^(-?\d+) (second|minute|hour|day|week|month|quarter|year)s?$").unwrap(); -} -impl MeasureTimeShift { - pub fn try_from_reference(reference: &TimeShiftReference) -> Result { - let parsed_interval = - if let Some(captures) = INTERVAL_MATCH_RE.captures(&reference.interval) { - let duration = if let Some(duration) = captures.get(1) { - duration.as_str().parse::().ok() - } else { - None - }; - let granularity = if let Some(granularity) = captures.get(2) { - Some(granularity.as_str().to_owned()) - } else { - None - }; - if let Some((duration, granularity)) = duration.zip(granularity) { - Some((duration, granularity)) - } else { - None - } - } else { - None - }; - if let Some((duration, granularity)) = parsed_interval { - let duration = if reference.shift_type.as_ref().unwrap_or(&format!("prior")) == "next" { - duration * (-1) - } else { - duration - }; - - Ok(Self { - interval: format!("{duration} {granularity}"), - time_dimension: reference.time_dimension.clone(), - }) - } else { - Err(CubeError::user(format!( - "Invalid interval: {}", - reference.interval - ))) - } - } -} pub struct BaseMeasure { measure: String, @@ -70,7 +17,6 @@ pub struct BaseMeasure { definition: Option>, #[allow(dead_code)] member_expression_definition: Option, - time_shifts: Vec, cube_name: String, name: String, default_alias: String, @@ -80,7 +26,6 @@ impl Debug for BaseMeasure { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("BaseMeasure") .field("measure", &self.measure) - .field("time_shifts", &self.time_shifts) .field("default_alias", &self.default_alias) .finish() } @@ -132,7 +77,6 @@ impl BaseMeasure { ) -> Result>, CubeError> { let res = match evaluation_node.as_ref() { MemberSymbol::Measure(s) => { - let time_shifts = Self::parse_time_shifts(&s.definition())?; let default_alias = BaseMemberHelper::default_alias( &s.cube_name(), &s.name(), @@ -147,7 +91,6 @@ impl BaseMeasure { member_expression_definition: None, cube_name: s.cube_name().clone(), name: s.name().clone(), - time_shifts, default_alias, })) } @@ -166,7 +109,6 @@ impl BaseMeasure { name, member_expression_definition, default_alias, - time_shifts: vec![], })) } _ => None, @@ -212,7 +154,6 @@ impl BaseMeasure { name, member_expression_definition, default_alias, - time_shifts: vec![], })) } @@ -232,19 +173,6 @@ impl BaseMeasure { Ok(res) } - fn parse_time_shifts( - definition: &Rc, - ) -> Result, CubeError> { - if let Some(time_shifts) = &definition.static_data().time_shift_references { - time_shifts - .iter() - .map(|t| MeasureTimeShift::try_from_reference(t)) - .collect::, _>>() - } else { - Ok(vec![]) - } - } - pub fn member_evaluator(&self) -> &Rc { &self.member_evaluator } @@ -289,8 +217,13 @@ impl BaseMeasure { .map_or(None, |d| d.static_data().time_shift_references.clone()) } - pub fn time_shifts(&self) -> &Vec { - &self.time_shifts + pub fn time_shifts(&self) -> Vec { + match self.member_evaluator.as_ref() { + MemberSymbol::Measure(measure_symbol) => { + measure_symbol.time_shifts().clone() + }, + _ => vec![], + } } pub fn is_multi_stage(&self) -> bool { diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/applied_state.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/applied_state.rs index e39347de43f89..63bf361f419ef 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/applied_state.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/applied_state.rs @@ -1,6 +1,6 @@ use crate::plan::{FilterGroup, FilterItem}; use crate::planner::filter::FilterOperator; -use crate::planner::planners::multi_stage::MultiStageTimeShift; +use crate::planner::sql_evaluator::MeasureTimeShift; use crate::planner::{BaseDimension, BaseMember, BaseTimeDimension}; use cubenativeutils::CubeError; use itertools::Itertools; @@ -17,7 +17,7 @@ pub struct MultiStageAppliedState { dimensions_filters: Vec, measures_filters: Vec, segments: Vec, - time_shifts: HashMap, + time_shifts: HashMap, } impl MultiStageAppliedState { @@ -62,14 +62,18 @@ impl MultiStageAppliedState { .collect_vec(); } - pub fn add_time_shifts(&mut self, time_shifts: Vec) { + pub fn add_time_shifts(&mut self, time_shifts: Vec) { for ts in time_shifts.into_iter() { - self.time_shifts - .insert(ts.time_dimension.clone(), ts.interval.clone()); + if let Some(exists) = self.time_shifts.get_mut(&ts.dimension.full_name()) { + exists.interval += ts.interval; + } else { + self.time_shifts + .insert(ts.dimension.full_name(), ts); + } } } - pub fn time_shifts(&self) -> &HashMap { + pub fn time_shifts(&self) -> &HashMap { &self.time_shifts } diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/member.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/member.rs index 2731bdde32618..dcb39821e1c8c 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/member.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/member.rs @@ -1,63 +1,7 @@ -use crate::cube_bridge::measure_definition::TimeShiftReference; -use crate::planner::sql_evaluator::MemberSymbol; +use crate::planner::sql_evaluator::{MemberSymbol, MeasureTimeShift}; use crate::planner::BaseTimeDimension; -use cubenativeutils::CubeError; -use lazy_static::lazy_static; -use regex::Regex; use std::rc::Rc; -#[derive(Clone, Debug)] -pub struct MultiStageTimeShift { - pub interval: String, - pub time_dimension: String, -} - -lazy_static! { - static ref INTERVAL_MATCH_RE: Regex = - Regex::new(r"^(-?\d+) (second|minute|hour|day|week|month|quarter|year)s?$").unwrap(); -} -impl MultiStageTimeShift { - pub fn try_from_reference(reference: &TimeShiftReference) -> Result { - let parsed_interval = - if let Some(captures) = INTERVAL_MATCH_RE.captures(&reference.interval) { - let duration = if let Some(duration) = captures.get(1) { - duration.as_str().parse::().ok() - } else { - None - }; - let granularity = if let Some(granularity) = captures.get(2) { - Some(granularity.as_str().to_owned()) - } else { - None - }; - if let Some((duration, granularity)) = duration.zip(granularity) { - Some((duration, granularity)) - } else { - None - } - } else { - None - }; - if let Some((duration, granularity)) = parsed_interval { - let duration = if reference.shift_type.as_ref().unwrap_or(&format!("prior")) == "next" { - duration * (-1) - } else { - duration - }; - - Ok(Self { - interval: format!("{duration} {granularity}"), - time_dimension: reference.time_dimension.clone(), - }) - } else { - Err(CubeError::user(format!( - "Invalid interval: {}", - reference.interval - ))) - } - } -} - #[derive(Clone)] pub struct TimeSeriesDescription { pub time_dimension: Rc, @@ -143,7 +87,7 @@ pub struct MultiStageInodeMember { reduce_by: Vec, add_group_by: Vec, group_by: Option>, - time_shifts: Vec, + time_shifts: Vec, } impl MultiStageInodeMember { @@ -152,7 +96,7 @@ impl MultiStageInodeMember { reduce_by: Vec, add_group_by: Vec, group_by: Option>, - time_shifts: Vec, + time_shifts: Vec, ) -> Self { Self { inode_type, @@ -179,7 +123,7 @@ impl MultiStageInodeMember { &self.group_by } - pub fn time_shifts(&self) -> &Vec { + pub fn time_shifts(&self) -> &Vec { &self.time_shifts } } 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 4e0f0fb48ed45..ccc6a6ad36669 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,7 +1,7 @@ use super::{ MultiStageAppliedState, MultiStageInodeMember, MultiStageInodeMemberType, MultiStageLeafMemberType, MultiStageMember, MultiStageMemberQueryPlanner, MultiStageMemberType, - MultiStageQueryDescription, MultiStageTimeShift, RollingWindowPlanner, + MultiStageQueryDescription, RollingWindowPlanner, }; use crate::plan::{Cte, From, Schema, Select, SelectBuilder}; use crate::planner::query_tools::QueryTools; @@ -124,15 +124,8 @@ impl MultiStageQueryPlanner { MultiStageInodeMemberType::Calculate }; - let time_shifts = if let Some(refs) = measure.time_shift_references() { - let time_shifts = refs - .iter() - .map(|r| MultiStageTimeShift::try_from_reference(r)) - .collect::, _>>()?; - time_shifts - } else { - vec![] - }; + let time_shifts = measure.time_shifts(); + let is_ungrupped = match &member_type { MultiStageInodeMemberType::Rank | MultiStageInodeMemberType::Calculate => true, _ => self.query_properties.ungrouped(), diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/collectors/find_owned_by_cube.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/collectors/find_owned_by_cube.rs new file mode 100644 index 0000000000000..ab365d62b3208 --- /dev/null +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/collectors/find_owned_by_cube.rs @@ -0,0 +1,61 @@ +use crate::planner::sql_evaluator::MemberSymbol; +use cubenativeutils::CubeError; +use std::rc::Rc; + +pub struct FindOwnedByCubeChildCollector { +} + +impl FindOwnedByCubeChildCollector { + pub fn new() -> Self { + Self {} + } + + pub fn find(&self, node: &Rc) -> Result, CubeError> { + self.find_impl(node, &node.full_name()) + + } + + fn find_impl(&self, node: &Rc, origin_node_name: &String) -> Result, CubeError> { + match node.as_ref() { + MemberSymbol::Dimension(dimension_symbol) => { + if dimension_symbol.owned_by_cube() { + Ok(node.clone()) + } else { + self.process_deps(&node, origin_node_name) + } + }, + MemberSymbol::TimeDimension(time_dimension_symbol) => { + self.find_impl(time_dimension_symbol.base_symbol(), origin_node_name) + }, + MemberSymbol::Measure(measure_symbol) => { + if measure_symbol.owned_by_cube() { + Ok(node.clone()) + } else { + self.process_deps(&node, origin_node_name) + } + + }, + _ => { + + Err(CubeError::internal(format!("FindOwnedByCubeChild cannot be processed on node {}", node.full_name()))) + } + } + + } + + fn process_deps(&self, node: &Rc, origin_node_name: &String) -> Result, CubeError> { + let deps = node.get_dependencies(); + if deps.len() == 1 { + self.find_impl(&deps[0], origin_node_name) + } else { + Err(CubeError::internal(format!("Cannot find owned by cube child for {}", origin_node_name))) + } + + } +} + + +pub fn find_owned_by_cube_child(node: &Rc) -> Result, CubeError> { + let visitor = FindOwnedByCubeChildCollector::new(); + visitor.find(node) +} \ No newline at end of file diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/collectors/mod.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/collectors/mod.rs index 3315b5dfa2b79..9e28e05d62669 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/collectors/mod.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/collectors/mod.rs @@ -5,6 +5,7 @@ mod join_hints_collector; mod member_childs_collector; mod multiplied_measures_collector; mod sub_query_dimensions; +mod find_owned_by_cube; pub use cube_names_collector::collect_cube_names; pub use has_cumulative_members::{has_cumulative_members, HasCumulativeMembersCollector}; @@ -18,3 +19,4 @@ pub use sub_query_dimensions::{ collect_sub_query_dimensions, collect_sub_query_dimensions_from_members, collect_sub_query_dimensions_from_symbols, }; +pub use find_owned_by_cube::*; diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/mod.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/mod.rs index 8aeaaafa89cd2..9dbd042cd0c41 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/mod.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/mod.rs @@ -17,6 +17,6 @@ pub use symbols::{ CubeNameSymbol, CubeNameSymbolFactory, CubeTableSymbol, CubeTableSymbolFactory, DimensionCaseDefinition, DimensionCaseWhenItem, DimensionSymbol, DimensionSymbolFactory, DimenstionCaseLabel, MeasureSymbol, MeasureSymbolFactory, MemberExpressionSymbol, MemberSymbol, - SymbolFactory, TimeDimensionSymbol, + SymbolFactory, TimeDimensionSymbol, MeasureTimeShift }; pub use visitor::TraversalVisitor; diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_nodes/factory.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_nodes/factory.rs index 3ffcc61e0ed5d..37c2c19ff93b4 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_nodes/factory.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_nodes/factory.rs @@ -4,13 +4,14 @@ use super::{ RenderReferencesSqlNode, RollingWindowNode, RootSqlNode, SqlNode, TimeDimensionNode, TimeShiftSqlNode, UngroupedMeasureSqlNode, UngroupedQueryFinalMeasureSqlNode, }; +use crate::planner::sql_evaluator::MeasureTimeShift; use crate::plan::schema::QualifiedColumnName; use std::collections::{HashMap, HashSet}; use std::rc::Rc; #[derive(Clone)] pub struct SqlNodesFactory { - time_shifts: HashMap, + time_shifts: HashMap, ungrouped: bool, ungrouped_measure: bool, count_approx_as_state: bool, @@ -42,7 +43,7 @@ impl SqlNodesFactory { } } - pub fn set_time_shifts(&mut self, time_shifts: HashMap) { + pub fn set_time_shifts(&mut self, time_shifts: HashMap) { self.time_shifts = time_shifts; } diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_nodes/time_shift.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_nodes/time_shift.rs index e0777823fe5be..fc66c28c8333e 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_nodes/time_shift.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_nodes/time_shift.rs @@ -1,6 +1,6 @@ use super::SqlNode; use crate::planner::query_tools::QueryTools; -use crate::planner::sql_evaluator::MemberSymbol; +use crate::planner::sql_evaluator::{MemberSymbol, MeasureTimeShift}; use crate::planner::sql_evaluator::SqlEvaluatorVisitor; use crate::planner::sql_templates::PlanSqlTemplates; use cubenativeutils::CubeError; @@ -9,16 +9,16 @@ use std::collections::HashMap; use std::rc::Rc; pub struct TimeShiftSqlNode { - shifts: HashMap, + shifts: HashMap, input: Rc, } impl TimeShiftSqlNode { - pub fn new(shifts: HashMap, input: Rc) -> Rc { + pub fn new(shifts: HashMap, input: Rc) -> Rc { Rc::new(Self { shifts, input }) } - pub fn shifts(&self) -> &HashMap { + pub fn shifts(&self) -> &HashMap { &self.shifts } @@ -46,6 +46,7 @@ impl SqlNode for TimeShiftSqlNode { let res = match node.as_ref() { MemberSymbol::Dimension(ev) => { if let Some(shift) = self.shifts.get(&ev.full_name()) { + let shift = shift.interval.to_sql(); format!("({input} + interval '{shift}')") } else { input 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 0b4818df43f01..4cabe42d17c4f 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 @@ -3,12 +3,18 @@ use crate::cube_bridge::evaluator::CubeEvaluator; use crate::cube_bridge::measure_definition::{ MeasureDefinition, RollingWindow, TimeShiftReference, }; +use crate::planner::sql_evaluator::collectors::find_owned_by_cube_child; use crate::cube_bridge::member_sql::MemberSql; use crate::planner::query_tools::QueryTools; use crate::planner::sql_evaluator::{sql_nodes::SqlNode, Compiler, SqlCall, SqlEvaluatorVisitor}; use crate::planner::sql_templates::PlanSqlTemplates; +use crate::planner::SqlInterval; use cubenativeutils::CubeError; +use itertools::Itertools; +use std::collections::HashMap; use std::rc::Rc; +use std::cmp::{PartialEq, Eq}; + #[derive(Clone)] pub struct MeasureOrderBy { @@ -33,6 +39,22 @@ impl MeasureOrderBy { } } +#[derive(Clone, Debug)] +pub struct MeasureTimeShift { + pub interval: SqlInterval, + pub dimension: Rc, +} + +impl PartialEq for MeasureTimeShift { + fn eq(&self, other: &Self) -> bool { + self.interval == other.interval && self.dimension.full_name() == other.dimension.full_name() + } +} + +impl Eq for MeasureTimeShift { + +} + #[derive(Clone)] pub struct MeasureSymbol { cube_name: String, @@ -40,6 +62,7 @@ pub struct MeasureSymbol { definition: Rc, measure_filters: Vec>, measure_drill_filters: Vec>, + time_shifts: Vec, measure_order_by: Vec, member_sql: Option>, pk_sqls: Vec>, @@ -55,6 +78,7 @@ impl MeasureSymbol { definition: Rc, measure_filters: Vec>, measure_drill_filters: Vec>, + time_shifts: Vec, measure_order_by: Vec, ) -> Self { Self { @@ -66,6 +90,7 @@ impl MeasureSymbol { measure_filters, measure_drill_filters, measure_order_by, + time_shifts, is_splitted_source: false, } } @@ -82,6 +107,10 @@ impl MeasureSymbol { &self.pk_sqls } + pub fn time_shifts(&self) -> &Vec { + &self.time_shifts + } + pub fn is_calculated(&self) -> bool { match self.definition.static_data().measure_type.as_str() { "number" | "string" | "time" | "boolean" => true, @@ -354,6 +383,35 @@ impl SymbolFactory for MeasureSymbolFactory { None }; + let time_shifts = if let Some(time_shift_references) = &definition.static_data().time_shift_references { + let mut shifts: HashMap = HashMap::new(); + for shift_ref in time_shift_references.iter() { + let interval = shift_ref.interval.parse::()?; + let interval = if shift_ref.shift_type.as_ref().unwrap_or(&format!("prior")) == "next" { + -interval + } else { + interval + }; + let dimension = compiler.add_dimension_evaluator(shift_ref.time_dimension.clone())?; + let dimension = find_owned_by_cube_child(&dimension)?; + let dimension_name = dimension.full_name(); + if let Some(exists) = shifts.get(&dimension_name) { + if exists.interval != interval { + return Err(CubeError::user(format!("Different time shifts for one dimension {} not allowed", dimension_name))) + } + } else { + shifts.insert(dimension_name, MeasureTimeShift { + interval: interval.clone(), + dimension: dimension.clone() + }); + + }; + } + shifts.into_values().collect_vec() + } else { + vec![] + }; + Ok(MemberSymbol::new_measure(MeasureSymbol::new( cube_name, name, @@ -362,6 +420,7 @@ impl SymbolFactory for MeasureSymbolFactory { definition, measure_filters, measure_drill_filters, + time_shifts, measure_order_by, ))) } diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/mod.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/mod.rs index c05e9d160b911..c29d900d2ed6c 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/mod.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/mod.rs @@ -13,7 +13,7 @@ pub use dimension_symbol::{ DimensionCaseDefinition, DimensionCaseWhenItem, DimensionSymbol, DimensionSymbolFactory, DimenstionCaseLabel, }; -pub use measure_symbol::{MeasureSymbol, MeasureSymbolFactory}; +pub use measure_symbol::{MeasureSymbol, MeasureSymbolFactory, MeasureTimeShift}; pub use member_expression_symbol::MemberExpressionSymbol; pub use member_symbol::MemberSymbol; pub use symbol_factory::SymbolFactory; 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 cda53985fdbc3..2b120f5d0a299 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/time_dimension/sql_interval.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/time_dimension/sql_interval.rs @@ -1,8 +1,9 @@ use cubenativeutils::CubeError; use itertools::Itertools; use std::str::FromStr; +use std::ops::{Add, Sub, Neg, AddAssign}; -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Hash, Eq)] pub struct SqlInterval { pub year: i32, pub month: i32, @@ -57,6 +58,32 @@ impl SqlInterval { Ok(res.to_string()) } + pub fn to_sql(&self) -> String { + let mut res = vec![]; + if self.year != 0 { + res.push(format!("{} YEAR", self.year)); + } + if self.month != 0 { + res.push(format!("{} MONTH", self.month)); + } + if self.week != 0 { + res.push(format!("{} WEEK", self.week)); + } + if self.day != 0 { + res.push(format!("{} DAY", self.day)); + } + if self.hour != 0 { + res.push(format!("{} HOUR", self.hour)); + } + if self.minute != 0 { + res.push(format!("{} MINUTE", self.minute)); + } + if self.second != 0 { + res.push(format!("{} SECOND", self.second)); + } + res.join(" ") + } + pub fn inverse(&self) -> Self { Self::new( -self.year, @@ -70,6 +97,63 @@ impl SqlInterval { } } +impl Add for SqlInterval { + type Output = SqlInterval; + fn add(self, other: SqlInterval) -> SqlInterval { + SqlInterval::new( + self.year + other.year, + self.month + other.month, + self.week + other.week, + self.day + other.day, + self.hour + other.hour, + self.minute + other.minute, + self.second + other.second, + ) + } +} + +impl AddAssign for SqlInterval { + fn add_assign(&mut self, other: SqlInterval) { + self.year += other.year; + self.month += other.month; + self.week += other.week; + self.day += other.day; + self.hour += other.hour; + self.minute += other.minute; + self.second += other.second; + } +} + +impl Sub for SqlInterval { + type Output = SqlInterval; + fn sub(self, other: SqlInterval) -> SqlInterval { + SqlInterval::new( + self.year - other.year, + self.month - other.month, + self.week - other.week, + self.day - other.day, + self.hour - other.hour, + self.minute - other.minute, + self.second - other.second, + ) + } +} + +impl Neg for SqlInterval { + type Output = SqlInterval; + fn neg(self) -> SqlInterval { + SqlInterval::new( + -self.year, + -self.month, + -self.week, + -self.day, + -self.hour, + -self.minute, + -self.second, + ) + } +} + impl Default for SqlInterval { fn default() -> Self { Self { @@ -124,4 +208,10 @@ mod tests { SqlInterval::new(1, 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)); + 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)); + assert_eq!(-SqlInterval::new(1, 3, -4, 2, 4, 2, 1), SqlInterval::new(-1, -3, 4, -2, -4, -2, -1)); + } } From 7659d4705eda7be855f1c537d51d8471a0cb4ea9 Mon Sep 17 00:00:00 2001 From: Alexandr Romanenko Date: Thu, 3 Apr 2025 17:01:36 +0200 Subject: [PATCH 2/4] fix lint --- .../src/planner/base_measure.rs | 7 +- .../planners/multi_stage/applied_state.rs | 3 +- .../planner/planners/multi_stage/member.rs | 2 +- .../collectors/find_owned_by_cube.rs | 39 +++++----- .../planner/sql_evaluator/collectors/mod.rs | 4 +- .../src/planner/sql_evaluator/mod.rs | 4 +- .../sql_evaluator/sql_nodes/factory.rs | 2 +- .../sql_evaluator/sql_nodes/time_shift.rs | 2 +- .../sql_evaluator/symbols/measure_symbol.rs | 73 ++++++++++--------- .../planner/time_dimension/sql_interval.rs | 19 +++-- 10 files changed, 85 insertions(+), 70 deletions(-) diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/base_measure.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/base_measure.rs index 3c13b9b547772..00591bce13405 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/base_measure.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/base_measure.rs @@ -1,5 +1,5 @@ use super::query_tools::QueryTools; -use super::sql_evaluator::{MemberExpressionSymbol, MemberSymbol, SqlCall, MeasureTimeShift}; +use super::sql_evaluator::{MeasureTimeShift, MemberExpressionSymbol, MemberSymbol, SqlCall}; use super::{evaluate_with_context, BaseMember, BaseMemberHelper, VisitorContext}; use crate::cube_bridge::measure_definition::{ MeasureDefinition, RollingWindow, TimeShiftReference, @@ -9,7 +9,6 @@ use cubenativeutils::CubeError; use std::fmt::{Debug, Formatter}; use std::rc::Rc; - pub struct BaseMeasure { measure: String, query_tools: Rc, @@ -219,9 +218,7 @@ impl BaseMeasure { pub fn time_shifts(&self) -> Vec { match self.member_evaluator.as_ref() { - MemberSymbol::Measure(measure_symbol) => { - measure_symbol.time_shifts().clone() - }, + MemberSymbol::Measure(measure_symbol) => measure_symbol.time_shifts().clone(), _ => vec![], } } diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/applied_state.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/applied_state.rs index 63bf361f419ef..80fe8bfdc1d2d 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/applied_state.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/applied_state.rs @@ -67,8 +67,7 @@ impl MultiStageAppliedState { if let Some(exists) = self.time_shifts.get_mut(&ts.dimension.full_name()) { exists.interval += ts.interval; } else { - self.time_shifts - .insert(ts.dimension.full_name(), ts); + self.time_shifts.insert(ts.dimension.full_name(), ts); } } } diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/member.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/member.rs index dcb39821e1c8c..ec882404c7275 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/member.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/member.rs @@ -1,4 +1,4 @@ -use crate::planner::sql_evaluator::{MemberSymbol, MeasureTimeShift}; +use crate::planner::sql_evaluator::{MeasureTimeShift, MemberSymbol}; use crate::planner::BaseTimeDimension; use std::rc::Rc; diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/collectors/find_owned_by_cube.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/collectors/find_owned_by_cube.rs index ab365d62b3208..87e81c85a56cd 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/collectors/find_owned_by_cube.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/collectors/find_owned_by_cube.rs @@ -2,8 +2,7 @@ use crate::planner::sql_evaluator::MemberSymbol; use cubenativeutils::CubeError; use std::rc::Rc; -pub struct FindOwnedByCubeChildCollector { -} +pub struct FindOwnedByCubeChildCollector {} impl FindOwnedByCubeChildCollector { pub fn new() -> Self { @@ -12,10 +11,13 @@ impl FindOwnedByCubeChildCollector { pub fn find(&self, node: &Rc) -> Result, CubeError> { self.find_impl(node, &node.full_name()) - } - fn find_impl(&self, node: &Rc, origin_node_name: &String) -> Result, CubeError> { + fn find_impl( + &self, + node: &Rc, + origin_node_name: &String, + ) -> Result, CubeError> { match node.as_ref() { MemberSymbol::Dimension(dimension_symbol) => { if dimension_symbol.owned_by_cube() { @@ -23,39 +25,42 @@ impl FindOwnedByCubeChildCollector { } else { self.process_deps(&node, origin_node_name) } - }, + } MemberSymbol::TimeDimension(time_dimension_symbol) => { self.find_impl(time_dimension_symbol.base_symbol(), origin_node_name) - }, + } MemberSymbol::Measure(measure_symbol) => { if measure_symbol.owned_by_cube() { Ok(node.clone()) } else { self.process_deps(&node, origin_node_name) } - - }, - _ => { - - Err(CubeError::internal(format!("FindOwnedByCubeChild cannot be processed on node {}", node.full_name()))) } + _ => Err(CubeError::internal(format!( + "FindOwnedByCubeChild cannot be processed on node {}", + node.full_name() + ))), } - } - fn process_deps(&self, node: &Rc, origin_node_name: &String) -> Result, CubeError> { + fn process_deps( + &self, + node: &Rc, + origin_node_name: &String, + ) -> Result, CubeError> { let deps = node.get_dependencies(); if deps.len() == 1 { self.find_impl(&deps[0], origin_node_name) } else { - Err(CubeError::internal(format!("Cannot find owned by cube child for {}", origin_node_name))) + Err(CubeError::internal(format!( + "Cannot find owned by cube child for {}", + origin_node_name + ))) } - } } - pub fn find_owned_by_cube_child(node: &Rc) -> Result, CubeError> { let visitor = FindOwnedByCubeChildCollector::new(); visitor.find(node) -} \ No newline at end of file +} diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/collectors/mod.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/collectors/mod.rs index 9e28e05d62669..a5fe4c0531624 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/collectors/mod.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/collectors/mod.rs @@ -1,13 +1,14 @@ mod cube_names_collector; +mod find_owned_by_cube; mod has_cumulative_members; mod has_multi_stage_members; mod join_hints_collector; mod member_childs_collector; mod multiplied_measures_collector; mod sub_query_dimensions; -mod find_owned_by_cube; pub use cube_names_collector::collect_cube_names; +pub use find_owned_by_cube::*; pub use has_cumulative_members::{has_cumulative_members, HasCumulativeMembersCollector}; pub use has_multi_stage_members::{has_multi_stage_members, HasMultiStageMembersCollector}; pub use join_hints_collector::{ @@ -19,4 +20,3 @@ pub use sub_query_dimensions::{ collect_sub_query_dimensions, collect_sub_query_dimensions_from_members, collect_sub_query_dimensions_from_symbols, }; -pub use find_owned_by_cube::*; diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/mod.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/mod.rs index 9dbd042cd0c41..0a9e167c0746c 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/mod.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/mod.rs @@ -16,7 +16,7 @@ pub use sql_visitor::SqlEvaluatorVisitor; pub use symbols::{ CubeNameSymbol, CubeNameSymbolFactory, CubeTableSymbol, CubeTableSymbolFactory, DimensionCaseDefinition, DimensionCaseWhenItem, DimensionSymbol, DimensionSymbolFactory, - DimenstionCaseLabel, MeasureSymbol, MeasureSymbolFactory, MemberExpressionSymbol, MemberSymbol, - SymbolFactory, TimeDimensionSymbol, MeasureTimeShift + DimenstionCaseLabel, MeasureSymbol, MeasureSymbolFactory, MeasureTimeShift, + MemberExpressionSymbol, MemberSymbol, SymbolFactory, TimeDimensionSymbol, }; pub use visitor::TraversalVisitor; diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_nodes/factory.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_nodes/factory.rs index 37c2c19ff93b4..ecb6062a15d4c 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_nodes/factory.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_nodes/factory.rs @@ -4,8 +4,8 @@ use super::{ RenderReferencesSqlNode, RollingWindowNode, RootSqlNode, SqlNode, TimeDimensionNode, TimeShiftSqlNode, UngroupedMeasureSqlNode, UngroupedQueryFinalMeasureSqlNode, }; -use crate::planner::sql_evaluator::MeasureTimeShift; use crate::plan::schema::QualifiedColumnName; +use crate::planner::sql_evaluator::MeasureTimeShift; use std::collections::{HashMap, HashSet}; use std::rc::Rc; diff --git a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_nodes/time_shift.rs b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_nodes/time_shift.rs index fc66c28c8333e..5a29cff9b43b3 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_nodes/time_shift.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_nodes/time_shift.rs @@ -1,7 +1,7 @@ use super::SqlNode; use crate::planner::query_tools::QueryTools; -use crate::planner::sql_evaluator::{MemberSymbol, MeasureTimeShift}; use crate::planner::sql_evaluator::SqlEvaluatorVisitor; +use crate::planner::sql_evaluator::{MeasureTimeShift, MemberSymbol}; use crate::planner::sql_templates::PlanSqlTemplates; use cubenativeutils::CubeError; use std::any::Any; 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 4cabe42d17c4f..4e8e33bd0b5cc 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 @@ -3,18 +3,17 @@ use crate::cube_bridge::evaluator::CubeEvaluator; use crate::cube_bridge::measure_definition::{ MeasureDefinition, RollingWindow, TimeShiftReference, }; -use crate::planner::sql_evaluator::collectors::find_owned_by_cube_child; use crate::cube_bridge::member_sql::MemberSql; use crate::planner::query_tools::QueryTools; +use crate::planner::sql_evaluator::collectors::find_owned_by_cube_child; use crate::planner::sql_evaluator::{sql_nodes::SqlNode, Compiler, SqlCall, SqlEvaluatorVisitor}; use crate::planner::sql_templates::PlanSqlTemplates; use crate::planner::SqlInterval; use cubenativeutils::CubeError; use itertools::Itertools; +use std::cmp::{Eq, PartialEq}; use std::collections::HashMap; use std::rc::Rc; -use std::cmp::{PartialEq, Eq}; - #[derive(Clone)] pub struct MeasureOrderBy { @@ -51,9 +50,7 @@ impl PartialEq for MeasureTimeShift { } } -impl Eq for MeasureTimeShift { - -} +impl Eq for MeasureTimeShift {} #[derive(Clone)] pub struct MeasureSymbol { @@ -383,34 +380,42 @@ impl SymbolFactory for MeasureSymbolFactory { None }; - let time_shifts = if let Some(time_shift_references) = &definition.static_data().time_shift_references { - let mut shifts: HashMap = HashMap::new(); - for shift_ref in time_shift_references.iter() { - let interval = shift_ref.interval.parse::()?; - let interval = if shift_ref.shift_type.as_ref().unwrap_or(&format!("prior")) == "next" { - -interval - } else { - interval - }; - let dimension = compiler.add_dimension_evaluator(shift_ref.time_dimension.clone())?; - let dimension = find_owned_by_cube_child(&dimension)?; - let dimension_name = dimension.full_name(); - if let Some(exists) = shifts.get(&dimension_name) { - if exists.interval != interval { - return Err(CubeError::user(format!("Different time shifts for one dimension {} not allowed", dimension_name))) - } - } else { - shifts.insert(dimension_name, MeasureTimeShift { - interval: interval.clone(), - dimension: dimension.clone() - }); - - }; - } - shifts.into_values().collect_vec() - } else { - vec![] - }; + let time_shifts = + if let Some(time_shift_references) = &definition.static_data().time_shift_references { + let mut shifts: HashMap = HashMap::new(); + for shift_ref in time_shift_references.iter() { + let interval = shift_ref.interval.parse::()?; + let interval = + if shift_ref.shift_type.as_ref().unwrap_or(&format!("prior")) == "next" { + -interval + } else { + interval + }; + let dimension = + compiler.add_dimension_evaluator(shift_ref.time_dimension.clone())?; + let dimension = find_owned_by_cube_child(&dimension)?; + let dimension_name = dimension.full_name(); + if let Some(exists) = shifts.get(&dimension_name) { + if exists.interval != interval { + return Err(CubeError::user(format!( + "Different time shifts for one dimension {} not allowed", + dimension_name + ))); + } + } else { + shifts.insert( + dimension_name, + MeasureTimeShift { + interval: interval.clone(), + dimension: dimension.clone(), + }, + ); + }; + } + shifts.into_values().collect_vec() + } else { + vec![] + }; Ok(MemberSymbol::new_measure(MeasureSymbol::new( cube_name, 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 2b120f5d0a299..fbe0568b9e5e4 100644 --- a/rust/cubesqlplanner/cubesqlplanner/src/planner/time_dimension/sql_interval.rs +++ b/rust/cubesqlplanner/cubesqlplanner/src/planner/time_dimension/sql_interval.rs @@ -1,7 +1,7 @@ use cubenativeutils::CubeError; use itertools::Itertools; +use std::ops::{Add, AddAssign, Neg, Sub}; use std::str::FromStr; -use std::ops::{Add, Sub, Neg, AddAssign}; #[derive(Debug, PartialEq, Clone, Hash, Eq)] pub struct SqlInterval { @@ -152,7 +152,7 @@ impl Neg for SqlInterval { -self.second, ) } -} +} impl Default for SqlInterval { fn default() -> Self { @@ -210,8 +210,17 @@ mod tests { } #[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)); - 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)); - assert_eq!(-SqlInterval::new(1, 3, -4, 2, 4, 2, 1), SqlInterval::new(-1, -3, 4, -2, -4, -2, -1)); + 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) + ); + 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) + ); + assert_eq!( + -SqlInterval::new(1, 3, -4, 2, 4, 2, 1), + SqlInterval::new(-1, -3, 4, -2, -4, -2, -1) + ); } } From 096e098974a9c5b2d0e241f8e9425876a04d733d Mon Sep 17 00:00:00 2001 From: Alexandr Romanenko Date: Tue, 8 Apr 2025 16:21:46 +0200 Subject: [PATCH 3/4] fix --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 49c9a836951e8..a460d5cf2a5ff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17154,7 +17154,7 @@ generate-object-property@^1.0.0: dependencies: is-property "^1.0.0" -generic-pool@*, generic-pool@^3.8.2: +generic-pool@*, generic-pool@^3.6.0, generic-pool@^3.8.2: version "3.9.0" resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-3.9.0.tgz#36f4a678e963f4fdb8707eab050823abc4e8f5e4" integrity sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g== From 35b11fee6bbcee426e238456edb91b7d88580066 Mon Sep 17 00:00:00 2001 From: Alexandr Romanenko Date: Tue, 8 Apr 2025 18:21:51 +0200 Subject: [PATCH 4/4] fix --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index a460d5cf2a5ff..49c9a836951e8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17154,7 +17154,7 @@ generate-object-property@^1.0.0: dependencies: is-property "^1.0.0" -generic-pool@*, generic-pool@^3.6.0, generic-pool@^3.8.2: +generic-pool@*, generic-pool@^3.8.2: version "3.9.0" resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-3.9.0.tgz#36f4a678e963f4fdb8707eab050823abc4e8f5e4" integrity sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==