diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/filter.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/filter.rs index 5a4fd2c8a85e7..246b84a91208c 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/filter.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/filter.rs @@ -1,5 +1,5 @@ use super::pretty_print::*; -use crate::plan::{Filter, FilterItem}; +use crate::planner::filter::{Filter, FilterItem}; use itertools::Itertools; #[derive(Default)] diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/optimizers/common/helper.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/optimizers/common/helper.rs index ee6abebb8e98b..e67204efe650d 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/optimizers/common/helper.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/optimizers/common/helper.rs @@ -1,7 +1,7 @@ use itertools::Itertools; use crate::logical_plan::*; -use crate::plan::FilterItem; +use crate::planner::filter::FilterItem; use crate::planner::sql_evaluator::MemberSymbol; use std::rc::Rc; diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/optimizers/pre_aggregation/dimension_matcher.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/optimizers/pre_aggregation/dimension_matcher.rs index 2b2a52140b2b8..2199b126e0dc5 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/optimizers/pre_aggregation/dimension_matcher.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/optimizers/pre_aggregation/dimension_matcher.rs @@ -1,7 +1,5 @@ use super::CompiledPreAggregation; -use crate::plan::filter::FilterGroupOperator; -use crate::plan::FilterItem; -use crate::planner::filter::BaseFilter; +use crate::planner::filter::{BaseFilter, FilterGroupOperator, FilterItem}; use crate::planner::query_tools::QueryTools; use crate::planner::sql_evaluator::DimensionSymbol; use crate::planner::sql_evaluator::MemberSymbol; diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/optimizers/pre_aggregation/optimizer.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/optimizers/pre_aggregation/optimizer.rs index 0e8dd83aeb087..f5e1d36110a10 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/optimizers/pre_aggregation/optimizer.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/optimizers/pre_aggregation/optimizer.rs @@ -2,7 +2,7 @@ use super::PreAggregationsCompiler; use super::*; use crate::logical_plan::visitor::{LogicalPlanRewriter, NodeRewriteResult}; use crate::logical_plan::*; -use crate::plan::FilterItem; +use crate::planner::filter::FilterItem; use crate::planner::filter::FilterOp; use crate::planner::join_hints::JoinHints; use crate::planner::multi_fact_join_groups::{MeasuresJoinHints, MultiFactJoinGroups}; diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/pretty_print.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/pretty_print.rs index c43991e7b5e51..fced9061d212b 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/pretty_print.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/logical_plan/pretty_print.rs @@ -1,4 +1,4 @@ -use crate::plan::FilterItem; +use crate::planner::filter::FilterItem; use crate::planner::sql_evaluator::MemberSymbol; use std::rc::Rc; diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/builder/select.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/builder/select.rs index aa8d38b6a9753..533358a4b17d0 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/builder/select.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/builder/select.rs @@ -1,7 +1,8 @@ use crate::plan::{ - AliasedExpr, Cte, Expr, Filter, From, FromSource, MemberExpression, OrderBy, - QualifiedColumnName, Schema, SchemaColumn, Select, SingleAliasedSource, SingleSource, + AliasedExpr, Cte, Expr, From, FromSource, MemberExpression, OrderBy, QualifiedColumnName, + Schema, SchemaColumn, Select, SingleAliasedSource, SingleSource, }; +use crate::planner::filter::Filter; use crate::plan::expression::FunctionExpression; use crate::planner::query_tools::QueryTools; diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/base_filter.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/base_filter.rs new file mode 100644 index 0000000000000..595d555137125 --- /dev/null +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/base_filter.rs @@ -0,0 +1,38 @@ +use super::ToSql; +use crate::planner::filter::typed_filter::resolve_base_symbol; +use crate::planner::filter::BaseFilter; +use crate::planner::query_tools::QueryTools; +use crate::planner::sql_evaluator::sql_nodes::SqlNode; +use crate::planner::sql_evaluator::SqlEvaluatorVisitor; +use crate::planner::sql_templates::PlanSqlTemplates; +use crate::planner::FiltersContext; +use cubenativeutils::CubeError; +use std::rc::Rc; + +impl ToSql for BaseFilter { + fn to_sql( + &self, + visitor: &SqlEvaluatorVisitor, + node_processor: Rc, + query_tools: Rc, + templates: &PlanSqlTemplates, + filters_ctx: &FiltersContext, + ) -> Result { + if !filters_ctx.filter_params_columns.is_empty() { + let symbol_to_match = + resolve_base_symbol(self.raw_member_evaluator_ref()).resolve_reference_chain(); + if let Some(filter_params_column) = filters_ctx + .filter_params_columns + .get(&symbol_to_match.full_name()) + { + return self.typed_filter().to_sql_for_filter_params( + filter_params_column, + templates, + filters_ctx, + ); + } + } + self.typed_filter() + .to_sql(visitor, node_processor, query_tools, templates, filters_ctx) + } +} diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/base_segment.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/base_segment.rs new file mode 100644 index 0000000000000..fbc85c84f09ea --- /dev/null +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/base_segment.rs @@ -0,0 +1,22 @@ +use super::ToSql; +use crate::planner::filter::BaseSegment; +use crate::planner::query_tools::QueryTools; +use crate::planner::sql_evaluator::sql_nodes::SqlNode; +use crate::planner::sql_evaluator::SqlEvaluatorVisitor; +use crate::planner::sql_templates::PlanSqlTemplates; +use crate::planner::FiltersContext; +use cubenativeutils::CubeError; +use std::rc::Rc; + +impl ToSql for BaseSegment { + fn to_sql( + &self, + visitor: &SqlEvaluatorVisitor, + node_processor: Rc, + _query_tools: Rc, + templates: &PlanSqlTemplates, + _filters_ctx: &FiltersContext, + ) -> Result { + visitor.apply(&self.member_evaluator(), node_processor, templates) + } +} diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/filter.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/filter.rs new file mode 100644 index 0000000000000..d2797b51e620b --- /dev/null +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/filter.rs @@ -0,0 +1,86 @@ +use super::ToSql; +use crate::planner::filter::{Filter, FilterItem}; +use crate::planner::query_tools::QueryTools; +use crate::planner::sql_evaluator::sql_nodes::SqlNode; +use crate::planner::sql_evaluator::SqlEvaluatorVisitor; +use crate::planner::sql_templates::PlanSqlTemplates; +use crate::planner::FiltersContext; +use cubenativeutils::CubeError; +use std::rc::Rc; + +impl ToSql for FilterItem { + fn to_sql( + &self, + visitor: &SqlEvaluatorVisitor, + node_processor: Rc, + query_tools: Rc, + templates: &PlanSqlTemplates, + filters_ctx: &FiltersContext, + ) -> Result { + let res = match self { + FilterItem::Group(group) => { + let operator = format!(" {} ", group.operator.to_string()); + let items_sql = group + .items + .iter() + .map(|itm| { + itm.to_sql( + visitor, + node_processor.clone(), + query_tools.clone(), + templates, + filters_ctx, + ) + }) + .collect::, _>>()? + .into_iter() + .filter(|itm| !itm.is_empty()) + .collect::>(); + if items_sql.is_empty() { + "".to_string() + } else { + let result = items_sql.join(&operator); + format!("({})", result) + } + } + FilterItem::Item(item) => { + let sql = + item.to_sql(visitor, node_processor, query_tools, templates, filters_ctx)?; + format!("({})", sql) + } + FilterItem::Segment(item) => { + let sql = + item.to_sql(visitor, node_processor, query_tools, templates, filters_ctx)?; + format!("({})", sql) + } + }; + Ok(res) + } +} + +impl ToSql for Filter { + fn to_sql( + &self, + visitor: &SqlEvaluatorVisitor, + node_processor: Rc, + query_tools: Rc, + templates: &PlanSqlTemplates, + filters_ctx: &FiltersContext, + ) -> Result { + let res = self + .items + .iter() + .map(|itm| { + itm.to_sql( + visitor, + node_processor.clone(), + query_tools.clone(), + templates, + filters_ctx, + ) + }) + .collect::, _>>()? + .join(" AND "); + Ok(res) + } +} diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/mod.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/mod.rs new file mode 100644 index 0000000000000..c11cbd42ac7cc --- /dev/null +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/mod.rs @@ -0,0 +1,10 @@ +pub mod base_filter; +pub mod base_segment; +pub mod filter; +pub(crate) mod operators; +pub mod render_filter; +pub mod to_sql; +pub mod typed_filter; + +pub use render_filter::{render_filter, render_filter_item}; +pub use to_sql::ToSql; diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/comparison.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/comparison.rs new file mode 100644 index 0000000000000..084b748dbe81a --- /dev/null +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/comparison.rs @@ -0,0 +1,15 @@ +use super::{FilterOperationSql, FilterSqlContext}; +use crate::planner::filter::operators::comparison::{ComparisonKind, ComparisonOp}; +use cubenativeutils::CubeError; + +impl FilterOperationSql for ComparisonOp { + fn to_sql(&self, ctx: &FilterSqlContext) -> Result { + let param = ctx.allocate_and_cast(&self.value, &self.member_type)?; + match self.kind { + ComparisonKind::Gt => ctx.plan_templates.gt(ctx.member_sql.to_string(), param), + ComparisonKind::Gte => ctx.plan_templates.gte(ctx.member_sql.to_string(), param), + ComparisonKind::Lt => ctx.plan_templates.lt(ctx.member_sql.to_string(), param), + ComparisonKind::Lte => ctx.plan_templates.lte(ctx.member_sql.to_string(), param), + } + } +} diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/date_range.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/date_range.rs new file mode 100644 index 0000000000000..e9d53c851642c --- /dev/null +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/date_range.rs @@ -0,0 +1,22 @@ +use super::{FilterOperationSql, FilterSqlContext}; +use crate::planner::filter::operators::date_range::{DateRangeKind, DateRangeOp}; +use cubenativeutils::CubeError; + +impl FilterOperationSql for DateRangeOp { + fn to_sql(&self, ctx: &FilterSqlContext) -> Result { + let from_param = ctx.format_and_allocate_from_date(&self.from)?; + let to_param = ctx.format_and_allocate_to_date(&self.to)?; + match self.kind { + DateRangeKind::InRange => ctx.plan_templates.time_range_filter( + ctx.member_sql.to_string(), + from_param, + to_param, + ), + DateRangeKind::NotInRange => ctx.plan_templates.time_not_in_range_filter( + ctx.member_sql.to_string(), + from_param, + to_param, + ), + } + } +} diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/date_single.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/date_single.rs new file mode 100644 index 0000000000000..803f27cd65816 --- /dev/null +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/date_single.rs @@ -0,0 +1,34 @@ +use super::{FilterOperationSql, FilterSqlContext}; +use crate::planner::filter::operators::date_single::{DateSingleKind, DateSingleOp}; +use cubenativeutils::CubeError; + +impl FilterOperationSql for DateSingleOp { + fn to_sql(&self, ctx: &FilterSqlContext) -> Result { + match self.kind { + DateSingleKind::Before | DateSingleKind::AfterOrOn => { + let param = ctx.format_and_allocate_from_date(&self.value)?; + match self.kind { + DateSingleKind::Before => { + ctx.plan_templates.lt(ctx.member_sql.to_string(), param) + } + DateSingleKind::AfterOrOn => { + ctx.plan_templates.gte(ctx.member_sql.to_string(), param) + } + _ => unreachable!(), + } + } + DateSingleKind::BeforeOrOn | DateSingleKind::After => { + let param = ctx.format_and_allocate_to_date(&self.value)?; + match self.kind { + DateSingleKind::BeforeOrOn => { + ctx.plan_templates.lte(ctx.member_sql.to_string(), param) + } + DateSingleKind::After => { + ctx.plan_templates.gt(ctx.member_sql.to_string(), param) + } + _ => unreachable!(), + } + } + } + } +} diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/equality.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/equality.rs new file mode 100644 index 0000000000000..52ed6682cc146 --- /dev/null +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/equality.rs @@ -0,0 +1,18 @@ +use super::{FilterOperationSql, FilterSqlContext}; +use crate::planner::filter::operators::equality::EqualityOp; +use cubenativeutils::CubeError; + +impl FilterOperationSql for EqualityOp { + fn to_sql(&self, ctx: &FilterSqlContext) -> Result { + let param = ctx.allocate_and_cast(&self.value, &self.member_type)?; + // For negated (notEquals), add OR IS NULL check when value is not null + let need_null_check = self.negated; + if self.negated { + ctx.plan_templates + .not_equals(ctx.member_sql.to_string(), param, need_null_check) + } else { + ctx.plan_templates + .equals(ctx.member_sql.to_string(), param, false) + } + } +} diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/filter_sql_context.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/filter_sql_context.rs similarity index 100% rename from rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/filter_sql_context.rs rename to rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/filter_sql_context.rs diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/in_list.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/in_list.rs new file mode 100644 index 0000000000000..4532fbada975f --- /dev/null +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/in_list.rs @@ -0,0 +1,19 @@ +use super::{FilterOperationSql, FilterSqlContext}; +use crate::planner::filter::operators::in_list::InListOp; +use cubenativeutils::CubeError; + +impl FilterOperationSql for InListOp { + fn to_sql(&self, ctx: &FilterSqlContext) -> Result { + let has_null = self.values.iter().any(|v| v.is_none()); + let need_null_check = if self.negated { !has_null } else { has_null }; + let allocated = ctx.allocate_and_cast_values(&self.values, &self.member_type)?; + + if self.negated { + ctx.plan_templates + .not_in_where(ctx.member_sql.to_string(), allocated, need_null_check) + } else { + ctx.plan_templates + .in_where(ctx.member_sql.to_string(), allocated, need_null_check) + } + } +} diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/like.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/like.rs new file mode 100644 index 0000000000000..1a1d84fe0a41e --- /dev/null +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/like.rs @@ -0,0 +1,48 @@ +use super::{FilterOperationSql, FilterSqlContext}; +use crate::planner::filter::operators::like::LikeOp; +use cubenativeutils::CubeError; + +impl FilterOperationSql for LikeOp { + fn to_sql(&self, ctx: &FilterSqlContext) -> Result { + let allocated = ctx.allocate_and_cast_values( + &self + .values + .iter() + .map(|v| Some(v.clone())) + .collect::>(), + &self.member_type, + )?; + + let like_parts = allocated + .into_iter() + .map(|v| { + ctx.plan_templates.ilike( + ctx.member_sql, + &v, + self.start_wild, + self.end_wild, + self.negated, + ) + }) + .collect::, _>>()?; + + let logical_symbol = if self.negated { " AND " } else { " OR " }; + let need_null_check = if self.negated { + !self.has_null + } else { + self.has_null + }; + let null_check = if need_null_check { + ctx.plan_templates + .or_is_null_check(ctx.member_sql.to_string())? + } else { + "".to_string() + }; + + Ok(format!( + "({}){}", + like_parts.join(logical_symbol), + null_check + )) + } +} diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/measure_filter.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/measure_filter.rs new file mode 100644 index 0000000000000..422dba31941e5 --- /dev/null +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/measure_filter.rs @@ -0,0 +1,48 @@ +use crate::planner::filter::operators::measure_filter::MeasureFilterOp; +use crate::planner::query_tools::QueryTools; +use crate::planner::sql_evaluator::sql_nodes::SqlNode; +use crate::planner::sql_evaluator::{MemberSymbol, SqlEvaluatorVisitor}; +use crate::planner::sql_templates::PlanSqlTemplates; +use cubenativeutils::CubeError; +use std::rc::Rc; + +impl MeasureFilterOp { + pub fn to_sql( + &self, + member_evaluator: &Rc, + visitor: &SqlEvaluatorVisitor, + node_processor: Rc, + query_tools: Rc, + plan_templates: &PlanSqlTemplates, + ) -> Result { + match member_evaluator.as_ref() { + MemberSymbol::Measure(measure_symbol) => { + if measure_symbol.measure_filters().is_empty() + && measure_symbol.measure_drill_filters().is_empty() + { + plan_templates.always_true() + } else { + let parts = measure_symbol + .measure_filters() + .iter() + .chain(measure_symbol.measure_drill_filters().iter()) + .map(|filter| -> Result { + Ok(format!( + "({})", + filter.eval( + visitor, + node_processor.clone(), + query_tools.clone(), + plan_templates, + )? + )) + }) + .collect::, _>>()?; + + Ok(parts.join(" AND ")) + } + } + _ => plan_templates.always_true(), + } + } +} diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/mod.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/mod.rs new file mode 100644 index 0000000000000..6aade5734170f --- /dev/null +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/mod.rs @@ -0,0 +1,13 @@ +pub mod comparison; +pub mod date_range; +pub mod date_single; +pub mod equality; +pub mod filter_sql_context; +pub mod in_list; +pub mod like; +pub mod measure_filter; +pub mod nullability; +pub mod rolling_window; +pub mod to_date_rolling_window; + +pub use filter_sql_context::{FilterOperationSql, FilterSqlContext}; diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/nullability.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/nullability.rs new file mode 100644 index 0000000000000..5ee721955a062 --- /dev/null +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/nullability.rs @@ -0,0 +1,13 @@ +use super::{FilterOperationSql, FilterSqlContext}; +use crate::planner::filter::operators::nullability::NullabilityOp; +use cubenativeutils::CubeError; + +impl FilterOperationSql for NullabilityOp { + fn to_sql(&self, ctx: &FilterSqlContext) -> Result { + if self.negated { + ctx.plan_templates.not_set_where(ctx.member_sql.to_string()) + } else { + ctx.plan_templates.set_where(ctx.member_sql.to_string()) + } + } +} diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/rolling_window.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/rolling_window.rs new file mode 100644 index 0000000000000..ce85dd7470cea --- /dev/null +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/rolling_window.rs @@ -0,0 +1,24 @@ +use super::{FilterOperationSql, FilterSqlContext}; +use crate::planner::filter::operators::rolling_window::RegularRollingWindowOp; +use cubenativeutils::CubeError; + +impl FilterOperationSql for RegularRollingWindowOp { + fn to_sql(&self, ctx: &FilterSqlContext) -> Result { + let (from, to) = ctx.date_range_from_time_series()?; + + let from = ctx.extend_date_range_bound(from, &self.trailing, true)?; + let to = ctx.extend_date_range_bound(to, &self.leading, false)?; + + let date_field = ctx.convert_tz(ctx.member_sql)?; + + match (&from, &to) { + (Some(from), Some(to)) => { + ctx.plan_templates + .time_range_filter(date_field, from.clone(), to.clone()) + } + (Some(from), None) => ctx.plan_templates.gte(date_field, from.clone()), + (None, Some(to)) => ctx.plan_templates.lte(date_field, to.clone()), + (None, None) => ctx.plan_templates.always_true(), + } + } +} diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/to_date_rolling_window.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/to_date_rolling_window.rs new file mode 100644 index 0000000000000..e3a1b2f5883f6 --- /dev/null +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/operators/to_date_rolling_window.rs @@ -0,0 +1,16 @@ +use super::{FilterOperationSql, FilterSqlContext}; +use crate::planner::filter::operators::to_date_rolling_window::ToDateRollingWindowOp; +use cubenativeutils::CubeError; + +impl FilterOperationSql for ToDateRollingWindowOp { + fn to_sql(&self, ctx: &FilterSqlContext) -> Result { + let (from, to) = ctx.date_range_from_time_series()?; + + let from = self + .granularity + .apply_to_input_sql(ctx.plan_templates, from)?; + + let date_field = ctx.convert_tz(ctx.member_sql)?; + ctx.plan_templates.time_range_filter(date_field, from, to) + } +} diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/render_filter.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/render_filter.rs new file mode 100644 index 0000000000000..dac2cd2359e66 --- /dev/null +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/render_filter.rs @@ -0,0 +1,35 @@ +use super::ToSql; +use crate::planner::filter::{Filter, FilterItem}; +use crate::planner::sql_templates::PlanSqlTemplates; +use crate::planner::VisitorContext; +use cubenativeutils::CubeError; + +pub fn render_filter( + context: &VisitorContext, + filter: &Filter, + templates: &PlanSqlTemplates, +) -> Result { + let visitor = context.make_visitor(context.query_tools()); + filter.to_sql( + &visitor, + context.node_processor(), + context.query_tools(), + templates, + context.filters_context(), + ) +} + +pub fn render_filter_item( + context: &VisitorContext, + item: &FilterItem, + templates: &PlanSqlTemplates, +) -> Result { + let visitor = context.make_visitor(context.query_tools()); + item.to_sql( + &visitor, + context.node_processor(), + context.query_tools(), + templates, + context.filters_context(), + ) +} diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/to_sql.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/to_sql.rs new file mode 100644 index 0000000000000..49200524860f5 --- /dev/null +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/to_sql.rs @@ -0,0 +1,18 @@ +use crate::planner::query_tools::QueryTools; +use crate::planner::sql_evaluator::sql_nodes::SqlNode; +use crate::planner::sql_evaluator::SqlEvaluatorVisitor; +use crate::planner::sql_templates::PlanSqlTemplates; +use crate::planner::FiltersContext; +use cubenativeutils::CubeError; +use std::rc::Rc; + +pub trait ToSql { + fn to_sql( + &self, + visitor: &SqlEvaluatorVisitor, + node_processor: Rc, + query_tools: Rc, + templates: &PlanSqlTemplates, + filters_ctx: &FiltersContext, + ) -> Result; +} diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/typed_filter.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/typed_filter.rs new file mode 100644 index 0000000000000..7e44e431cf2ad --- /dev/null +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter/typed_filter.rs @@ -0,0 +1,118 @@ +use super::operators::{FilterOperationSql, FilterSqlContext}; +use super::ToSql; +use crate::cube_bridge::member_sql::FilterParamsColumn; +use crate::planner::filter::typed_filter::{resolve_base_symbol, FilterOp, TypedFilter}; +use crate::planner::query_tools::QueryTools; +use crate::planner::sql_evaluator::sql_nodes::SqlNode; +use crate::planner::sql_evaluator::SqlEvaluatorVisitor; +use crate::planner::sql_templates::PlanSqlTemplates; +use crate::planner::FiltersContext; +use cubenativeutils::CubeError; +use std::rc::Rc; + +impl ToSql for TypedFilter { + fn to_sql( + &self, + visitor: &SqlEvaluatorVisitor, + node_processor: Rc, + query_tools: Rc, + templates: &PlanSqlTemplates, + filters_ctx: &FiltersContext, + ) -> Result { + if let FilterOp::MeasureFilter(op) = self.operation() { + return op.to_sql( + self.member_evaluator(), + visitor, + node_processor, + query_tools, + templates, + ); + } + + let resolved = resolve_base_symbol(self.member_evaluator()); + let member_sql = visitor.apply_for_filter(&resolved, node_processor, templates)?; + + let ctx = FilterSqlContext { + member_sql: &member_sql, + query_tools: self.query_tools(), + plan_templates: templates, + use_db_time_zone: !filters_ctx.use_local_tz, + use_raw_values: self.use_raw_values(), + }; + + dispatch_to_sql(self.operation(), &ctx) + } +} + +impl TypedFilter { + pub fn to_sql_for_filter_params( + &self, + column: &FilterParamsColumn, + plan_templates: &PlanSqlTemplates, + filters_context: &FiltersContext, + ) -> Result { + let use_db_time_zone = !filters_context.use_local_tz; + + match column { + FilterParamsColumn::String(column_sql) => { + let ctx = FilterSqlContext { + member_sql: column_sql, + query_tools: self.query_tools(), + plan_templates, + use_db_time_zone, + use_raw_values: self.use_raw_values(), + }; + dispatch_to_sql(self.operation(), &ctx) + } + FilterParamsColumn::Callback(callback) => { + let args = match self.operation() { + FilterOp::DateRange(_) | FilterOp::DateSingle(_) => { + let ctx = FilterSqlContext { + member_sql: "", + query_tools: self.query_tools(), + plan_templates, + use_db_time_zone, + use_raw_values: self.use_raw_values(), + }; + let from = self + .values() + .first() + .and_then(|v| v.as_ref()) + .map(|v| ctx.format_and_allocate_from_date(v)) + .transpose()?; + let to = self + .values() + .get(1) + .and_then(|v| v.as_ref()) + .map(|v| ctx.format_and_allocate_to_date(v)) + .transpose()?; + [from, to].into_iter().flatten().collect() + } + _ => self + .values() + .iter() + .filter_map(|v| v.as_ref().map(|v| self.query_tools().allocate_param(v))) + .collect::>(), + }; + callback.call(&args) + } + } + } +} + +fn dispatch_to_sql(op: &FilterOp, ctx: &FilterSqlContext) -> Result { + match op { + FilterOp::Comparison(op) => op.to_sql(ctx), + FilterOp::DateRange(op) => op.to_sql(ctx), + FilterOp::DateSingle(op) => op.to_sql(ctx), + FilterOp::Equality(op) => op.to_sql(ctx), + FilterOp::InList(op) => op.to_sql(ctx), + FilterOp::Like(op) => op.to_sql(ctx), + FilterOp::MeasureFilter(_) => { + unreachable!("MeasureFilter is handled in TypedFilter::to_sql") + } + FilterOp::Nullability(op) => op.to_sql(ctx), + FilterOp::RegularRollingWindow(op) => op.to_sql(ctx), + FilterOp::ToDateRollingWindow(op) => op.to_sql(ctx), + } +} diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/mod.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/mod.rs index 517cc9e751ae6..a84bb2d74c880 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/mod.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/mod.rs @@ -3,7 +3,6 @@ pub mod calc_groups_join; pub mod cte; pub mod expression; pub mod filter; -pub mod filter_debug; pub mod from; pub mod join; pub mod order; @@ -17,7 +16,6 @@ pub use builder::{JoinBuilder, SelectBuilder}; pub use calc_groups_join::*; pub use cte::Cte; pub use expression::{Expr, MemberExpression}; -pub use filter::{Filter, FilterGroup, FilterItem}; pub use from::{From, FromSource, SingleAliasedSource, SingleSource}; pub use join::{ Join, JoinCondition, JoinItem, RegularRollingWindowJoinCondition, RollingTotalJoinCondition, diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/select.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/select.rs index ef0dd3cc4749f..917c0de7251d1 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/select.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/select.rs @@ -1,4 +1,5 @@ -use super::{Cte, Expr, Filter, From, OrderBy, Schema}; +use super::{Cte, Expr, From, OrderBy, Schema}; +use crate::planner::filter::Filter; use crate::planner::sql_templates::PlanSqlTemplates; use crate::planner::sql_templates::{ TemplateGroupByColumn, TemplateOrderByColumn, TemplateProjectionColumn, @@ -69,7 +70,11 @@ impl Select { }; let where_condition = if let Some(filter) = &self.filter { - Some(filter.to_sql(templates, self.context.clone())?) + Some(crate::plan::filter::render_filter( + &self.context, + filter, + templates, + )?) } else { None }; @@ -85,7 +90,11 @@ impl Select { .collect::, _>>()?; let having = if let Some(having) = &self.having { - Some(having.to_sql(templates, self.context.clone())?) + Some(crate::plan::filter::render_filter( + &self.context, + having, + templates, + )?) } else { None }; diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/base_filter.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/base_filter.rs index 7015af24cdbd2..af92b95b718ec 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/base_filter.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/base_filter.rs @@ -1,8 +1,6 @@ use super::filter_operator::FilterOperator; use super::typed_filter::{resolve_base_symbol, TypedFilter}; use crate::planner::sql_evaluator::MemberSymbol; -use crate::planner::sql_templates::PlanSqlTemplates; -use crate::planner::VisitorContext; use cubenativeutils::CubeError; use itertools::Itertools; use std::rc::Rc; @@ -71,6 +69,10 @@ impl BaseFilter { self.typed_filter.member_evaluator().clone() } + pub fn raw_member_evaluator_ref(&self) -> &Rc { + self.typed_filter.member_evaluator() + } + pub fn with_member_evaluator( &self, member_evaluator: Rc, @@ -113,6 +115,10 @@ impl BaseFilter { self.typed_filter.use_raw_values() } + pub fn typed_filter(&self) -> &TypedFilter { + &self.typed_filter + } + pub fn member_name(&self) -> String { self.member_evaluator().full_name() } @@ -138,27 +144,4 @@ impl BaseFilter { None } } - - pub fn to_sql( - &self, - context: Rc, - plan_templates: &PlanSqlTemplates, - ) -> Result { - let filters_context = context.filters_context(); - if !filters_context.filter_params_columns.is_empty() { - let symbol_to_match = - resolve_base_symbol(self.typed_filter.member_evaluator()).resolve_reference_chain(); - if let Some(filter_params_column) = filters_context - .filter_params_columns - .get(&symbol_to_match.full_name()) - { - return self.typed_filter.to_sql_for_filter_params( - filter_params_column, - plan_templates, - filters_context, - ); - } - } - self.typed_filter.to_sql(context, plan_templates) - } } diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/base_segment.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/base_segment.rs index 4d5198db52b44..8a58dfa101508 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/base_segment.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/base_segment.rs @@ -1,8 +1,6 @@ use crate::planner::sql_evaluator::{ CubeTableSymbol, MemberExpressionExpression, MemberExpressionSymbol, MemberSymbol, SqlCall, }; -use crate::planner::sql_templates::PlanSqlTemplates; -use crate::planner::{evaluate_with_context, VisitorContext}; use cubenativeutils::CubeError; use std::rc::Rc; @@ -46,14 +44,6 @@ impl BaseSegment { name, })) } - pub fn to_sql( - &self, - context: Rc, - plan_templates: &PlanSqlTemplates, - ) -> Result { - evaluate_with_context(&self.member_evaluator, context, plan_templates) - } - pub fn full_name(&self) -> String { self.full_name.clone() } diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/compiler.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/compiler.rs index c00a61eedd2d9..57d78a2072899 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/compiler.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/compiler.rs @@ -1,7 +1,7 @@ use super::base_filter::{BaseFilter, FilterType}; use super::FilterOperator; use crate::cube_bridge::base_query_options::FilterItem as NativeFilterItem; -use crate::plan::filter::{FilterGroup, FilterGroupOperator, FilterItem}; +use crate::planner::filter::{FilterGroup, FilterGroupOperator, FilterItem}; use crate::planner::query_tools::QueryTools; use crate::planner::sql_evaluator::{Compiler, MemberSymbol}; use cubenativeutils::CubeError; diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter_debug.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/filter_debug.rs similarity index 95% rename from rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter_debug.rs rename to rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/filter_debug.rs index 024b683d8e77b..e20735bfc2565 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter_debug.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/filter_debug.rs @@ -1,5 +1,4 @@ -use super::filter::{Filter, FilterGroup, FilterGroupOperator, FilterItem}; -use crate::planner::filter::BaseFilter; +use crate::planner::filter::{BaseFilter, Filter, FilterGroup, FilterGroupOperator, FilterItem}; use crate::planner::sql_evaluator::DebugSql; impl DebugSql for BaseFilter { diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/mod.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/mod.rs index a567c9df0a5db..b27b1c8efbf27 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/mod.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/mod.rs @@ -1,12 +1,15 @@ pub mod base_filter; pub mod base_segment; pub mod compiler; +pub mod filter_debug; pub mod filter_operator; pub(crate) mod operators; +pub mod tree; pub mod typed_filter; pub use base_filter::BaseFilter; pub use base_segment::BaseSegment; pub use filter_operator::FilterOperator; pub use operators::date_range::DateRangeOp; +pub use tree::{Filter, FilterGroup, FilterGroupOperator, FilterItem}; pub use typed_filter::{resolve_base_symbol, FilterOp}; diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/comparison.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/comparison.rs index fc077fb09fdec..8b24cc443f251 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/comparison.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/comparison.rs @@ -1,6 +1,3 @@ -use super::{FilterOperationSql, FilterSqlContext}; -use cubenativeutils::CubeError; - #[derive(Clone, Debug)] pub enum ComparisonKind { Gt, @@ -11,9 +8,9 @@ pub enum ComparisonKind { #[derive(Clone, Debug)] pub struct ComparisonOp { - kind: ComparisonKind, - value: String, - member_type: Option, + pub(crate) kind: ComparisonKind, + pub(crate) value: String, + pub(crate) member_type: Option, } impl ComparisonOp { @@ -25,15 +22,3 @@ impl ComparisonOp { } } } - -impl FilterOperationSql for ComparisonOp { - fn to_sql(&self, ctx: &FilterSqlContext) -> Result { - let param = ctx.allocate_and_cast(&self.value, &self.member_type)?; - match self.kind { - ComparisonKind::Gt => ctx.plan_templates.gt(ctx.member_sql.to_string(), param), - ComparisonKind::Gte => ctx.plan_templates.gte(ctx.member_sql.to_string(), param), - ComparisonKind::Lt => ctx.plan_templates.lt(ctx.member_sql.to_string(), param), - ComparisonKind::Lte => ctx.plan_templates.lte(ctx.member_sql.to_string(), param), - } - } -} diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/date_range.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/date_range.rs index 93d359a32b0be..53db04f09d895 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/date_range.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/date_range.rs @@ -1,4 +1,3 @@ -use super::{FilterOperationSql, FilterSqlContext}; use crate::planner::time_dimension::QueryDateTimeHelper; use cubenativeutils::CubeError; @@ -10,9 +9,9 @@ pub enum DateRangeKind { #[derive(Clone, Debug)] pub struct DateRangeOp { - kind: DateRangeKind, - from: String, - to: String, + pub(crate) kind: DateRangeKind, + pub(crate) from: String, + pub(crate) to: String, } impl DateRangeOp { @@ -26,22 +25,3 @@ impl DateRangeOp { Ok((from, to)) } } - -impl FilterOperationSql for DateRangeOp { - fn to_sql(&self, ctx: &FilterSqlContext) -> Result { - let from_param = ctx.format_and_allocate_from_date(&self.from)?; - let to_param = ctx.format_and_allocate_to_date(&self.to)?; - match self.kind { - DateRangeKind::InRange => ctx.plan_templates.time_range_filter( - ctx.member_sql.to_string(), - from_param, - to_param, - ), - DateRangeKind::NotInRange => ctx.plan_templates.time_not_in_range_filter( - ctx.member_sql.to_string(), - from_param, - to_param, - ), - } - } -} diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/date_single.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/date_single.rs index 76b2c735f3b06..8b162057f5282 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/date_single.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/date_single.rs @@ -1,6 +1,3 @@ -use super::{FilterOperationSql, FilterSqlContext}; -use cubenativeutils::CubeError; - #[derive(Clone, Debug)] pub enum DateSingleKind { Before, @@ -11,8 +8,8 @@ pub enum DateSingleKind { #[derive(Clone, Debug)] pub struct DateSingleOp { - kind: DateSingleKind, - value: String, + pub(crate) kind: DateSingleKind, + pub(crate) value: String, } impl DateSingleOp { @@ -20,34 +17,3 @@ impl DateSingleOp { Self { kind, value } } } - -impl FilterOperationSql for DateSingleOp { - fn to_sql(&self, ctx: &FilterSqlContext) -> Result { - match self.kind { - DateSingleKind::Before | DateSingleKind::AfterOrOn => { - let param = ctx.format_and_allocate_from_date(&self.value)?; - match self.kind { - DateSingleKind::Before => { - ctx.plan_templates.lt(ctx.member_sql.to_string(), param) - } - DateSingleKind::AfterOrOn => { - ctx.plan_templates.gte(ctx.member_sql.to_string(), param) - } - _ => unreachable!(), - } - } - DateSingleKind::BeforeOrOn | DateSingleKind::After => { - let param = ctx.format_and_allocate_to_date(&self.value)?; - match self.kind { - DateSingleKind::BeforeOrOn => { - ctx.plan_templates.lte(ctx.member_sql.to_string(), param) - } - DateSingleKind::After => { - ctx.plan_templates.gt(ctx.member_sql.to_string(), param) - } - _ => unreachable!(), - } - } - } - } -} diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/equality.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/equality.rs index 8e2accc1ab080..04f1ae22fd663 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/equality.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/equality.rs @@ -1,11 +1,8 @@ -use super::{FilterOperationSql, FilterSqlContext}; -use cubenativeutils::CubeError; - #[derive(Clone, Debug)] pub struct EqualityOp { - negated: bool, - value: String, - member_type: Option, + pub(crate) negated: bool, + pub(crate) value: String, + pub(crate) member_type: Option, } impl EqualityOp { @@ -17,18 +14,3 @@ impl EqualityOp { } } } - -impl FilterOperationSql for EqualityOp { - fn to_sql(&self, ctx: &FilterSqlContext) -> Result { - let param = ctx.allocate_and_cast(&self.value, &self.member_type)?; - // For negated (notEquals), add OR IS NULL check when value is not null - let need_null_check = self.negated; - if self.negated { - ctx.plan_templates - .not_equals(ctx.member_sql.to_string(), param, need_null_check) - } else { - ctx.plan_templates - .equals(ctx.member_sql.to_string(), param, false) - } - } -} diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/in_list.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/in_list.rs index 73d85e37cccef..50c6eb20d05d9 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/in_list.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/in_list.rs @@ -1,11 +1,8 @@ -use super::{FilterOperationSql, FilterSqlContext}; -use cubenativeutils::CubeError; - #[derive(Clone, Debug)] pub struct InListOp { - negated: bool, - values: Vec>, - member_type: Option, + pub(crate) negated: bool, + pub(crate) values: Vec>, + pub(crate) member_type: Option, } impl InListOp { @@ -17,19 +14,3 @@ impl InListOp { } } } - -impl FilterOperationSql for InListOp { - fn to_sql(&self, ctx: &FilterSqlContext) -> Result { - let has_null = self.values.iter().any(|v| v.is_none()); - let need_null_check = if self.negated { !has_null } else { has_null }; - let allocated = ctx.allocate_and_cast_values(&self.values, &self.member_type)?; - - if self.negated { - ctx.plan_templates - .not_in_where(ctx.member_sql.to_string(), allocated, need_null_check) - } else { - ctx.plan_templates - .in_where(ctx.member_sql.to_string(), allocated, need_null_check) - } - } -} diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/like.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/like.rs index e41f0426300d7..2a424fa6c7728 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/like.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/like.rs @@ -1,14 +1,11 @@ -use super::{FilterOperationSql, FilterSqlContext}; -use cubenativeutils::CubeError; - #[derive(Clone, Debug)] pub struct LikeOp { - negated: bool, - start_wild: bool, - end_wild: bool, - values: Vec, - has_null: bool, - member_type: Option, + pub(crate) negated: bool, + pub(crate) start_wild: bool, + pub(crate) end_wild: bool, + pub(crate) values: Vec, + pub(crate) has_null: bool, + pub(crate) member_type: Option, } impl LikeOp { @@ -30,48 +27,3 @@ impl LikeOp { } } } - -impl FilterOperationSql for LikeOp { - fn to_sql(&self, ctx: &FilterSqlContext) -> Result { - let allocated = ctx.allocate_and_cast_values( - &self - .values - .iter() - .map(|v| Some(v.clone())) - .collect::>(), - &self.member_type, - )?; - - let like_parts = allocated - .into_iter() - .map(|v| { - ctx.plan_templates.ilike( - ctx.member_sql, - &v, - self.start_wild, - self.end_wild, - self.negated, - ) - }) - .collect::, _>>()?; - - let logical_symbol = if self.negated { " AND " } else { " OR " }; - let need_null_check = if self.negated { - !self.has_null - } else { - self.has_null - }; - let null_check = if need_null_check { - ctx.plan_templates - .or_is_null_check(ctx.member_sql.to_string())? - } else { - "".to_string() - }; - - Ok(format!( - "({}){}", - like_parts.join(logical_symbol), - null_check - )) - } -} diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/measure_filter.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/measure_filter.rs index 7caadb6689e68..7c6a34d365811 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/measure_filter.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/measure_filter.rs @@ -1,10 +1,3 @@ -use crate::planner::query_tools::QueryTools; -use crate::planner::sql_evaluator::MemberSymbol; -use crate::planner::sql_templates::PlanSqlTemplates; -use crate::planner::VisitorContext; -use cubenativeutils::CubeError; -use std::rc::Rc; - #[derive(Clone, Debug)] pub struct MeasureFilterOp; @@ -12,45 +5,4 @@ impl MeasureFilterOp { pub fn new() -> Self { Self } - - pub fn to_sql( - &self, - member_evaluator: &Rc, - query_tools: &Rc, - context: &Rc, - plan_templates: &PlanSqlTemplates, - ) -> Result { - match member_evaluator.as_ref() { - MemberSymbol::Measure(measure_symbol) => { - if measure_symbol.measure_filters().is_empty() - && measure_symbol.measure_drill_filters().is_empty() - { - plan_templates.always_true() - } else { - let visitor = context.make_visitor(query_tools.clone()); - let node_processor = context.node_processor(); - - let parts = measure_symbol - .measure_filters() - .iter() - .chain(measure_symbol.measure_drill_filters().iter()) - .map(|filter| -> Result { - Ok(format!( - "({})", - filter.eval( - &visitor, - node_processor.clone(), - query_tools.clone(), - plan_templates, - )? - )) - }) - .collect::, _>>()?; - - Ok(parts.join(" AND ")) - } - } - _ => plan_templates.always_true(), - } - } } diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/mod.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/mod.rs index 4bc72d28e7521..27d2bd1abe875 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/mod.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/mod.rs @@ -2,12 +2,9 @@ pub mod comparison; pub mod date_range; pub mod date_single; pub mod equality; -mod filter_sql_context; pub mod in_list; pub mod like; pub mod measure_filter; pub mod nullability; pub mod rolling_window; pub mod to_date_rolling_window; - -pub use filter_sql_context::{FilterOperationSql, FilterSqlContext}; diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/nullability.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/nullability.rs index 8a3b19234bf6d..45a7a3285ef71 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/nullability.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/nullability.rs @@ -1,9 +1,6 @@ -use super::{FilterOperationSql, FilterSqlContext}; -use cubenativeutils::CubeError; - #[derive(Clone, Debug)] pub struct NullabilityOp { - negated: bool, + pub(crate) negated: bool, } impl NullabilityOp { @@ -11,13 +8,3 @@ impl NullabilityOp { Self { negated } } } - -impl FilterOperationSql for NullabilityOp { - fn to_sql(&self, ctx: &FilterSqlContext) -> Result { - if self.negated { - ctx.plan_templates.not_set_where(ctx.member_sql.to_string()) - } else { - ctx.plan_templates.set_where(ctx.member_sql.to_string()) - } - } -} diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/rolling_window.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/rolling_window.rs index 1d2f6731fbb32..b03568d9c32f7 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/rolling_window.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/rolling_window.rs @@ -1,10 +1,7 @@ -use super::{FilterOperationSql, FilterSqlContext}; -use cubenativeutils::CubeError; - #[derive(Clone, Debug)] pub struct RegularRollingWindowOp { - trailing: Option, - leading: Option, + pub(crate) trailing: Option, + pub(crate) leading: Option, } impl RegularRollingWindowOp { @@ -12,24 +9,3 @@ impl RegularRollingWindowOp { Self { trailing, leading } } } - -impl FilterOperationSql for RegularRollingWindowOp { - fn to_sql(&self, ctx: &FilterSqlContext) -> Result { - let (from, to) = ctx.date_range_from_time_series()?; - - let from = ctx.extend_date_range_bound(from, &self.trailing, true)?; - let to = ctx.extend_date_range_bound(to, &self.leading, false)?; - - let date_field = ctx.convert_tz(ctx.member_sql)?; - - match (&from, &to) { - (Some(from), Some(to)) => { - ctx.plan_templates - .time_range_filter(date_field, from.clone(), to.clone()) - } - (Some(from), None) => ctx.plan_templates.gte(date_field, from.clone()), - (None, Some(to)) => ctx.plan_templates.lte(date_field, to.clone()), - (None, None) => ctx.plan_templates.always_true(), - } - } -} diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/to_date_rolling_window.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/to_date_rolling_window.rs index f827433f1bc49..2541847f45e4c 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/to_date_rolling_window.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/operators/to_date_rolling_window.rs @@ -1,10 +1,8 @@ -use super::{FilterOperationSql, FilterSqlContext}; use crate::planner::Granularity; -use cubenativeutils::CubeError; #[derive(Clone)] pub struct ToDateRollingWindowOp { - granularity: Granularity, + pub(crate) granularity: Granularity, } impl std::fmt::Debug for ToDateRollingWindowOp { @@ -20,16 +18,3 @@ impl ToDateRollingWindowOp { Self { granularity } } } - -impl FilterOperationSql for ToDateRollingWindowOp { - fn to_sql(&self, ctx: &FilterSqlContext) -> Result { - let (from, to) = ctx.date_range_from_time_series()?; - - let from = self - .granularity - .apply_to_input_sql(ctx.plan_templates, from)?; - - let date_field = ctx.convert_tz(ctx.member_sql)?; - ctx.plan_templates.time_range_filter(date_field, from, to) - } -} diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/tree.rs similarity index 85% rename from rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter.rs rename to rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/tree.rs index b6cfff118436f..1722078c9ba0a 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/plan/filter.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/tree.rs @@ -1,8 +1,5 @@ -use crate::planner::filter::{BaseFilter, BaseSegment}; +use super::{BaseFilter, BaseSegment}; use crate::planner::sql_evaluator::MemberSymbol; -use crate::planner::sql_templates::PlanSqlTemplates; -use crate::planner::VisitorContext; -use cubenativeutils::CubeError; use std::fmt; use std::rc::Rc; @@ -75,41 +72,6 @@ impl Filter { } impl FilterItem { - pub fn to_sql( - &self, - templates: &PlanSqlTemplates, - context: Rc, - ) -> Result { - let res = match self { - FilterItem::Group(group) => { - let operator = format!(" {} ", group.operator.to_string()); - let items_sql = group - .items - .iter() - .map(|itm| itm.to_sql(templates, context.clone())) - .collect::, _>>()? - .into_iter() - .filter(|itm| !itm.is_empty()) - .collect::>(); - if items_sql.is_empty() { - "".to_string() - } else { - let result = items_sql.join(&operator); - format!("({})", result) - } - } - FilterItem::Item(item) => { - let sql = item.to_sql(context.clone(), templates)?; - format!("({})", sql) - } - FilterItem::Segment(item) => { - let sql = item.to_sql(context.clone(), templates)?; - format!("({})", sql) - } - }; - Ok(res) - } - pub fn all_member_evaluators(&self) -> Vec> { let mut result = Vec::new(); self.find_all_member_evaluators(&mut result); @@ -315,19 +277,3 @@ impl FilterItem { } } } - -impl Filter { - pub fn to_sql( - &self, - templates: &PlanSqlTemplates, - context: Rc, - ) -> Result { - let res = self - .items - .iter() - .map(|itm| itm.to_sql(templates, context.clone())) - .collect::, _>>()? - .join(" AND "); - Ok(res) - } -} diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/typed_filter.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/typed_filter.rs index f7d3951a3e84e..3f17e008b2b0a 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/typed_filter.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/filter/typed_filter.rs @@ -1,9 +1,5 @@ -use crate::cube_bridge::member_sql::FilterParamsColumn; use crate::planner::query_tools::QueryTools; use crate::planner::sql_evaluator::MemberSymbol; -use crate::planner::sql_templates::PlanSqlTemplates; -use crate::planner::visitor_context::evaluate_filter_with_context; -use crate::planner::{FiltersContext, VisitorContext}; use cubenativeutils::CubeError; use std::rc::Rc; @@ -18,7 +14,6 @@ use super::operators::measure_filter::MeasureFilterOp; use super::operators::nullability::NullabilityOp; use super::operators::rolling_window::RegularRollingWindowOp; use super::operators::to_date_rolling_window::ToDateRollingWindowOp; -use super::operators::{FilterOperationSql, FilterSqlContext}; use super::FilterOperator; use crate::planner::GranularityHelper; @@ -98,106 +93,6 @@ impl TypedFilter { pub fn use_raw_values(&self) -> bool { self.use_raw_values } - - pub fn to_sql( - &self, - context: Rc, - plan_templates: &PlanSqlTemplates, - ) -> Result { - if let FilterOp::MeasureFilter(op) = &self.op { - return op.to_sql( - &self.member_evaluator, - &self.query_tools, - &context, - plan_templates, - ); - } - - let resolved = resolve_base_symbol(&self.member_evaluator); - let member_sql = evaluate_filter_with_context(&resolved, context.clone(), plan_templates)?; - - let filters_context = context.filters_context(); - let ctx = FilterSqlContext { - member_sql: &member_sql, - query_tools: &self.query_tools, - plan_templates, - use_db_time_zone: !filters_context.use_local_tz, - use_raw_values: self.use_raw_values, - }; - - self.dispatch_to_sql(&ctx) - } - - pub fn to_sql_for_filter_params( - &self, - column: &FilterParamsColumn, - plan_templates: &PlanSqlTemplates, - filters_context: &FiltersContext, - ) -> Result { - let use_db_time_zone = !filters_context.use_local_tz; - - match column { - FilterParamsColumn::String(column_sql) => { - let ctx = FilterSqlContext { - member_sql: column_sql, - query_tools: &self.query_tools, - plan_templates, - use_db_time_zone, - use_raw_values: self.use_raw_values, - }; - self.dispatch_to_sql(&ctx) - } - FilterParamsColumn::Callback(callback) => { - let args = match &self.op { - FilterOp::DateRange(_) | FilterOp::DateSingle(_) => { - let ctx = FilterSqlContext { - member_sql: "", - query_tools: &self.query_tools, - plan_templates, - use_db_time_zone, - use_raw_values: self.use_raw_values, - }; - let from = self - .values - .first() - .and_then(|v| v.as_ref()) - .map(|v| ctx.format_and_allocate_from_date(v)) - .transpose()?; - let to = self - .values - .get(1) - .and_then(|v| v.as_ref()) - .map(|v| ctx.format_and_allocate_to_date(v)) - .transpose()?; - [from, to].into_iter().flatten().collect() - } - _ => self - .values - .iter() - .filter_map(|v| v.as_ref().map(|v| self.query_tools.allocate_param(v))) - .collect::>(), - }; - callback.call(&args) - } - } - } - - fn dispatch_to_sql(&self, ctx: &FilterSqlContext) -> Result { - match &self.op { - FilterOp::Comparison(op) => op.to_sql(ctx), - FilterOp::DateRange(op) => op.to_sql(ctx), - FilterOp::DateSingle(op) => op.to_sql(ctx), - FilterOp::Equality(op) => op.to_sql(ctx), - FilterOp::InList(op) => op.to_sql(ctx), - FilterOp::Like(op) => op.to_sql(ctx), - FilterOp::MeasureFilter(_) => { - unreachable!("MeasureFilter is handled in TypedFilter::to_sql") - } - FilterOp::Nullability(op) => op.to_sql(ctx), - FilterOp::RegularRollingWindow(op) => op.to_sql(ctx), - FilterOp::ToDateRollingWindow(op) => op.to_sql(ctx), - } - } } #[derive(Default)] diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/multi_fact_join_groups.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/multi_fact_join_groups.rs index 3d6e3756b9296..912a83b686102 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/multi_fact_join_groups.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/multi_fact_join_groups.rs @@ -1,5 +1,5 @@ use crate::cube_bridge::join_definition::JoinDefinition; -use crate::plan::FilterItem; +use crate::planner::filter::FilterItem; use crate::planner::join_hints::JoinHints; use crate::planner::query_tools::JoinKey; use crate::planner::query_tools::QueryTools; diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/dimension_subquery_planner.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/dimension_subquery_planner.rs index efce0efc61108..bd16dbf11589e 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/dimension_subquery_planner.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/dimension_subquery_planner.rs @@ -1,6 +1,7 @@ use super::{CommonUtils, QueryPlanner}; use crate::logical_plan::{pretty_print_rc, DimensionSubQuery}; -use crate::plan::{FilterItem, QualifiedColumnName}; +use crate::plan::QualifiedColumnName; +use crate::planner::filter::FilterItem; use crate::planner::join_hints::JoinHints; use crate::planner::query_tools::QueryTools; use crate::planner::sql_evaluator::collectors::collect_sub_query_dimensions; diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/applied_state.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/applied_state.rs index 4d289893cc3b5..6f75139851fbf 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/applied_state.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/applied_state.rs @@ -1,5 +1,5 @@ -use crate::plan::{FilterGroup, FilterItem}; use crate::planner::filter::FilterOperator; +use crate::planner::filter::{FilterGroup, FilterItem}; use crate::planner::planners::multi_stage::time_shift_state::TimeShiftState; use crate::planner::sql_evaluator::collectors::has_multi_stage_members; use crate::planner::sql_evaluator::{DimensionTimeShift, MeasureTimeShifts, MemberSymbol}; diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/multi_stage_query_planner.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/multi_stage_query_planner.rs index 6ae27098f4c42..139be537921f2 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/multi_stage_query_planner.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/planners/multi_stage/multi_stage_query_planner.rs @@ -5,9 +5,9 @@ use super::{ }; use crate::cube_bridge::measure_definition::RollingWindow; use crate::logical_plan::*; -use crate::plan::FilterItem; use crate::planner::filter::base_filter::FilterType; use crate::planner::filter::BaseFilter; +use crate::planner::filter::FilterItem; use crate::planner::filter::FilterOperator; use crate::planner::query_tools::QueryTools; use crate::planner::sql_evaluator::apply_static_filter_to_symbol; diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/query_properties.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/query_properties.rs index 08412f472b9a4..9df44a84a758e 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/query_properties.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/query_properties.rs @@ -13,7 +13,7 @@ use super::sql_evaluator::MemberSymbol; use crate::cube_bridge::base_query_options::BaseQueryOptions; use crate::cube_bridge::join_definition::JoinDefinition; use crate::cube_bridge::options_member::OptionsMember; -use crate::plan::{Filter, FilterItem}; +use crate::planner::filter::{Filter, FilterItem}; use crate::planner::multi_fact_join_groups::{MeasuresJoinHints, MultiFactJoinGroups}; use crate::planner::sql_evaluator::collectors::{ collect_multiplied_measures, has_multi_stage_members, diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/query_tools.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/query_tools.rs index 79d69376998af..842b282e47db5 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/query_tools.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/query_tools.rs @@ -1,6 +1,6 @@ use super::sql_evaluator::Compiler; use super::ParamsAllocator; -use crate::cube_bridge::base_query_options::{FilterItem, MaskedMemberItem}; +use crate::cube_bridge::base_query_options::MaskedMemberItem; use crate::cube_bridge::base_tools::BaseTools; use crate::cube_bridge::evaluator::CubeEvaluator; use crate::cube_bridge::join_definition::JoinDefinition; @@ -8,6 +8,8 @@ use crate::cube_bridge::join_graph::JoinGraph; use crate::cube_bridge::join_item::JoinItemStatic; use crate::cube_bridge::security_context::SecurityContext; use crate::cube_bridge::sql_templates_render::SqlTemplatesRender; +use crate::planner::filter::compiler::FilterCompiler; +use crate::planner::filter::{FilterGroup, FilterGroupOperator, FilterItem}; use crate::planner::join_hints::JoinHints; use crate::planner::sql_templates::PlanSqlTemplates; use chrono_tz::Tz; @@ -33,7 +35,10 @@ pub struct QueryTools { timezone: Tz, convert_tz_for_raw_time_dimension: bool, masked_members: HashSet, - member_mask_filters: HashMap, + // Compiled mask filters keyed by member full path. Populated in try_new + // after the QueryTools Rc is constructed (FilterCompiler requires it), + // then never mutated again — RefCell only carries the construction phase. + member_mask_filters: RefCell>, } impl QueryTools { @@ -63,17 +68,18 @@ impl QueryTools { timezone.clone(), member_to_alias, ))); + + // Phase 1: collect masked member names eagerly; mask filters are + // compiled in Phase 2 below (FilterCompiler requires Rc, + // which doesn't exist yet at this point). let mut masked_set = HashSet::new(); - let mut mask_filters = HashMap::new(); - if let Some(items) = masked_members { + if let Some(items) = &masked_members { for item in items { masked_set.insert(item.member.clone()); - if let Some(filter) = item.filter { - mask_filters.insert(item.member, filter); - } } } - Ok(Rc::new(Self { + + let result = Rc::new(Self { cube_evaluator, base_tools, join_graph, @@ -83,16 +89,55 @@ impl QueryTools { timezone, convert_tz_for_raw_time_dimension, masked_members: masked_set, - member_mask_filters: mask_filters, - })) + member_mask_filters: RefCell::new(HashMap::new()), + }); + + // Phase 2: compile mask filters once now that Rc exists. + // After this, member_mask_filters is treated as immutable for the + // lifetime of QueryTools. + if let Some(items) = masked_members { + Self::compile_mask_filters(&result, items)?; + } + + Ok(result) + } + + fn compile_mask_filters( + this: &Rc, + items: Vec, + ) -> Result<(), CubeError> { + let mut compiled = HashMap::new(); + for item in items { + let Some(native_filter) = item.filter else { + continue; + }; + let mut compiler = this.evaluator_compiler.borrow_mut(); + let mut filter_compiler = FilterCompiler::new(&mut compiler, this.clone()); + filter_compiler.add_item(&native_filter)?; + let (dimension_filters, _, _) = filter_compiler.extract_result(); + if dimension_filters.is_empty() { + continue; + } + let filter_item = if dimension_filters.len() == 1 { + dimension_filters.into_iter().next().unwrap() + } else { + FilterItem::Group(Rc::new(FilterGroup::new( + FilterGroupOperator::And, + dimension_filters, + ))) + }; + compiled.insert(item.member, filter_item); + } + *this.member_mask_filters.borrow_mut() = compiled; + Ok(()) } pub fn is_member_masked(&self, member_path: &str) -> bool { self.masked_members.contains(member_path) } - pub fn member_mask_filter(&self, member_path: &str) -> Option<&FilterItem> { - self.member_mask_filters.get(member_path) + pub fn member_mask_filter(&self, member_path: &str) -> Option { + self.member_mask_filters.borrow().get(member_path).cloned() } pub fn cube_evaluator(&self) -> &Rc { diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/references_builder.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/references_builder.rs index 7d3ee2705859b..33d321922125b 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/references_builder.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/references_builder.rs @@ -1,7 +1,7 @@ use crate::plan::{ - CalcGroupsJoin, Filter, FilterItem, From, FromSource, Join, QualifiedColumnName, - SingleAliasedSource, SingleSource, + CalcGroupsJoin, From, FromSource, Join, QualifiedColumnName, SingleAliasedSource, SingleSource, }; +use crate::planner::filter::{Filter, FilterItem}; use crate::planner::sql_evaluator::sql_nodes::RenderReferences; use cubenativeutils::CubeError; use std::rc::Rc; diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_call.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_call.rs index ad2aca6b1e2d8..7377f0adcba5e 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_call.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_call.rs @@ -394,12 +394,12 @@ impl SqlCall { .insert(itm.filter_symbol_name.clone(), itm.column.clone()); } - let context = Rc::new(VisitorContext::new_for_filter_params( + let context = VisitorContext::new_for_filter_params( query_tools.clone(), &SqlNodesFactory::new(), filter_params_columns, - )); - return subtree.to_sql(templates, context); + ); + return crate::plan::filter::render_filter_item(&context, &subtree, templates); } } } diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_nodes/masked.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_nodes/masked.rs index 781151f89a71f..e685f20480473 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_nodes/masked.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_nodes/masked.rs @@ -1,12 +1,10 @@ use super::SqlNode; -use crate::cube_bridge::base_query_options::FilterItem as NativeFilterItem; -use crate::plan::filter::FilterItem; -use crate::planner::filter::compiler::FilterCompiler; +use crate::plan::filter::ToSql; use crate::planner::query_tools::QueryTools; use crate::planner::sql_evaluator::MemberSymbol; use crate::planner::sql_evaluator::SqlEvaluatorVisitor; use crate::planner::sql_templates::PlanSqlTemplates; -use crate::planner::VisitorContext; +use crate::planner::FiltersContext; use cubenativeutils::CubeError; use std::any::Any; use std::rc::Rc; @@ -44,7 +42,7 @@ impl MaskedSqlNode { return Ok(None); } - let mask_filter = query_tools.member_mask_filter(&full_name).cloned(); + let mask_filter = query_tools.member_mask_filter(&full_name); let masked_sql = if let Some(mask_call) = node.mask_sql() { if self.ungrouped { @@ -64,64 +62,37 @@ impl MaskedSqlNode { "(NULL)".to_string() }; - if let Some(filter_item) = mask_filter { - let original_sql = self.input.to_sql( - visitor, - node, - query_tools.clone(), - node_processor, - templates, - )?; - let filter_sql = - self.compile_filter_to_sql(&filter_item, query_tools.clone(), templates)?; - if let Some(filter_sql) = filter_sql { - Ok(Some(templates.case( - None, - vec![(filter_sql, original_sql)], - Some(masked_sql), - )?)) - } else { - Ok(Some(masked_sql)) - } - } else { - Ok(Some(masked_sql)) - } - } - - fn compile_filter_to_sql( - &self, - native_filter: &NativeFilterItem, - query_tools: Rc, - templates: &PlanSqlTemplates, - ) -> Result, CubeError> { - let filter_item = { - let mut compiler = query_tools.evaluator_compiler().borrow_mut(); - let mut filter_compiler = FilterCompiler::new(&mut compiler, query_tools.clone()); - filter_compiler.add_item(native_filter)?; - let (dimension_filters, _, _) = filter_compiler.extract_result(); - if dimension_filters.is_empty() { - return Ok(None); - } - if dimension_filters.len() == 1 { - dimension_filters.into_iter().next().unwrap() - } else { - FilterItem::Group(Rc::new(crate::plan::filter::FilterGroup::new( - crate::plan::filter::FilterGroupOperator::And, - dimension_filters, - ))) - } + let Some(filter_item) = mask_filter else { + return Ok(Some(masked_sql)); }; - // TODO: support FILTER_PARAMS in mask filter SQL by passing - // proper FiltersContext with filter_params_columns - let context = Rc::new(VisitorContext::new_with_node_processor( + + let original_sql = self.input.to_sql( + visitor, + node, query_tools.clone(), + node_processor, + templates, + )?; + // TODO: support FILTER_PARAMS in mask filter SQL by passing + // proper FiltersContext with filter_params_columns. + // Use self.input as node_processor so member references inside the filter + // resolve through the unmasked chain — prevents recursion through MaskedSqlNode + // when the filter member is itself masked. + let filter_sql = filter_item.to_sql( + visitor, self.input.clone(), - )); - let sql = filter_item.to_sql(templates, context)?; - if sql.is_empty() { - Ok(None) + query_tools, + templates, + &FiltersContext::default(), + )?; + if filter_sql.is_empty() { + Ok(Some(masked_sql)) } else { - Ok(Some(sql)) + Ok(Some(templates.case( + None, + vec![(filter_sql, original_sql)], + Some(masked_sql), + )?)) } } } diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_visitor.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_visitor.rs index 7182b0a10963d..9c72766ad547b 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_visitor.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/sql_visitor.rs @@ -1,7 +1,7 @@ use super::sql_nodes::SqlNode; use super::CubeRefEvaluator; use super::MemberSymbol; -use crate::plan::Filter; +use crate::planner::filter::Filter; use crate::planner::query_tools::QueryTools; use crate::planner::sql_evaluator::sql_call::CubeRef; use crate::planner::sql_templates::PlanSqlTemplates; @@ -71,6 +71,18 @@ impl SqlEvaluatorVisitor { Ok(result) } + /// Evaluate a member symbol in filter-mode: timezone conversion is suppressed + /// because filter values are already normalized to the database timezone. + pub fn apply_for_filter( + &self, + node: &Rc, + node_processor: Rc, + templates: &PlanSqlTemplates, + ) -> Result { + self.with_ignore_tz_convert() + .apply(node, node_processor, templates) + } + pub fn ignore_tz_convert(&self) -> bool { self.ignore_tz_convert } diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/common/case.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/common/case.rs index eaaf7bcdd966c..147e4cefcec06 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/common/case.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/common/case.rs @@ -1,4 +1,4 @@ -use crate::plan::FilterItem; +use crate::planner::filter::FilterItem; use crate::{ cube_bridge::{ case_switch_definition::CaseSwitchDefinition as NativeCaseSwitchDefinition, diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/common/static_filter.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/common/static_filter.rs index f3f9c909c7366..e8b7a95e8fca7 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/common/static_filter.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/sql_evaluator/symbols/common/static_filter.rs @@ -1,9 +1,7 @@ use cubenativeutils::CubeError; -use crate::{ - plan::{filter::FilterGroupOperator, Filter, FilterGroup, FilterItem}, - planner::sql_evaluator::MemberSymbol, -}; +use crate::planner::filter::{Filter, FilterGroup, FilterGroupOperator, FilterItem}; +use crate::planner::sql_evaluator::MemberSymbol; use std::rc::Rc; pub fn find_value_restriction( diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/visitor_context.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/visitor_context.rs index 91a007e140797..5c5c84773047e 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/visitor_context.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/planner/visitor_context.rs @@ -2,7 +2,7 @@ use super::query_tools::QueryTools; use super::sql_evaluator::sql_nodes::{SqlNode, SqlNodesFactory}; use super::sql_evaluator::{CubeRefEvaluator, MemberSymbol, SqlCall}; use crate::cube_bridge::member_sql::FilterParamsColumn; -use crate::plan::Filter; +use crate::planner::filter::Filter; use crate::planner::sql_evaluator::SqlEvaluatorVisitor; use crate::planner::sql_templates::PlanSqlTemplates; use cubenativeutils::CubeError; @@ -60,19 +60,6 @@ impl VisitorContext { } } - pub fn new_with_node_processor( - query_tools: Rc, - node_processor: Rc, - ) -> Self { - Self { - query_tools, - node_processor, - cube_ref_evaluator: Rc::new(CubeRefEvaluator::new(HashMap::new(), HashMap::new())), - all_filters: None, - filters_context: FiltersContext::default(), - } - } - pub fn make_visitor(&self, query_tools: Rc) -> SqlEvaluatorVisitor { SqlEvaluatorVisitor::new( query_tools, @@ -105,19 +92,6 @@ pub fn evaluate_with_context( visitor.apply(node, node_processor, templates) } -pub fn evaluate_filter_with_context( - node: &Rc, - context: Rc, - templates: &PlanSqlTemplates, -) -> Result { - let visitor = context - .make_visitor(context.query_tools()) - .with_ignore_tz_convert(); - let node_processor = context.node_processor(); - - visitor.apply(node, node_processor, templates) -} - pub fn evaluate_sql_call_with_context( sql_call: &Rc, context: Rc, diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/test_fixtures/test_utils/test_context.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/test_fixtures/test_utils/test_context.rs index d3be16a4ae35c..f28c178c8bbf9 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/test_fixtures/test_utils/test_context.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/test_fixtures/test_utils/test_context.rs @@ -3,8 +3,9 @@ use crate::cube_bridge::join_hints::JoinHintItem; use crate::logical_plan::PreAggregationUsage; #[cfg(feature = "integration-postgres")] use crate::logical_plan::{PreAggregation, PreAggregationSource, PreAggregationTable}; -use crate::plan::Filter; +use crate::plan::filter::ToSql; use crate::planner::filter::base_segment::BaseSegment; +use crate::planner::filter::Filter; use crate::planner::query_tools::QueryTools; use crate::planner::sql_evaluator::sql_nodes::SqlNodesFactory; use crate::planner::sql_evaluator::{MemberSymbol, SqlEvaluatorVisitor, TimeDimensionSymbol}; @@ -649,7 +650,7 @@ impl TestContext { let driver_tools = base_tools.driver_tools(false)?; let templates = PlanSqlTemplates::try_new(driver_tools, false)?; - let sql = filter.to_sql(&templates, context)?; + let sql = crate::plan::filter::render_filter(&context, &filter, &templates)?; let params = self.query_tools.get_allocated_params(); Ok((sql, params)) } @@ -668,7 +669,14 @@ impl TestContext { let driver_tools = base_tools.driver_tools(false)?; let templates = PlanSqlTemplates::try_new(driver_tools, false)?; - let sql = base_filter.to_sql(context, &templates)?; + let visitor = context.make_visitor(self.query_tools.clone()); + let sql = base_filter.to_sql( + &visitor, + context.node_processor(), + self.query_tools.clone(), + &templates, + context.filters_context(), + )?; let params = self.query_tools.get_allocated_params(); Ok((sql, params)) } diff --git a/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/utils/debug.rs b/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/utils/debug.rs index 18975f9008f88..bb9bc1abf52d7 100644 --- a/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/utils/debug.rs +++ b/rust/cube/cubesqlplanner/cubesqlplanner/src/tests/utils/debug.rs @@ -1,6 +1,6 @@ -use crate::plan::filter::{FilterGroup, FilterGroupOperator}; -use crate::plan::FilterItem; -use crate::planner::filter::{BaseFilter, FilterOperator}; +use crate::planner::filter::{ + BaseFilter, FilterGroup, FilterGroupOperator, FilterItem, FilterOperator, +}; use crate::planner::sql_evaluator::DebugSql; use crate::test_fixtures::cube_bridge::MockSchema; use crate::test_fixtures::test_utils::TestContext;