diff --git a/src/query/service/src/pipelines/builders/builder_union_all.rs b/src/query/service/src/pipelines/builders/builder_union_all.rs index 757347d1f7c70..ba2fee9af90c6 100644 --- a/src/query/service/src/pipelines/builders/builder_union_all.rs +++ b/src/query/service/src/pipelines/builders/builder_union_all.rs @@ -42,7 +42,9 @@ impl PipelineBuilder { self.main_pipeline.extend_sinks(left_sinks); self.main_pipeline.extend_sinks(right_sinks); - match self.ctx.get_settings().get_enable_parallel_union_all()? { + let enable_parallel_union_all = self.ctx.get_settings().get_enable_parallel_union_all()? + || self.ctx.get_settings().get_grouping_sets_to_union()?; + match enable_parallel_union_all { true => self.main_pipeline.resize(outputs, false), false => self.main_pipeline.sequence_group(sequence_groups, outputs), } diff --git a/src/query/settings/src/settings_default.rs b/src/query/settings/src/settings_default.rs index 318ebb43dc198..b50da92880468 100644 --- a/src/query/settings/src/settings_default.rs +++ b/src/query/settings/src/settings_default.rs @@ -513,6 +513,13 @@ impl DefaultSettings { scope: SettingScope::Both, range: Some(SettingRange::Numeric(0..=1)), }), + ("grouping_sets_to_union", DefaultSettingValue { + value: UserSettingValue::UInt64(0), + desc: "Enables grouping sets to union.", + mode: SettingMode::Both, + scope: SettingScope::Both, + range: Some(SettingRange::Numeric(0..=1)), + }), ("storage_fetch_part_num", DefaultSettingValue { value: UserSettingValue::UInt64(2), desc: "Sets the number of partitions that are fetched in parallel from storage during query execution.", diff --git a/src/query/settings/src/settings_getter_setter.rs b/src/query/settings/src/settings_getter_setter.rs index e5a888c05e4fc..a3e54c3bab828 100644 --- a/src/query/settings/src/settings_getter_setter.rs +++ b/src/query/settings/src/settings_getter_setter.rs @@ -516,6 +516,10 @@ impl Settings { self.try_get_string("group_by_shuffle_mode") } + pub fn get_grouping_sets_to_union(&self) -> Result { + Ok(self.try_get_u64("grouping_sets_to_union")? == 1) + } + pub fn get_efficiently_memory_group_by(&self) -> Result { Ok(self.try_get_u64("efficiently_memory_group_by")? == 1) } diff --git a/src/query/sql/src/executor/physical_plans/physical_aggregate_final.rs b/src/query/sql/src/executor/physical_plans/physical_aggregate_final.rs index d3511fbb8cdaa..1c62800a40e6d 100644 --- a/src/query/sql/src/executor/physical_plans/physical_aggregate_final.rs +++ b/src/query/sql/src/executor/physical_plans/physical_aggregate_final.rs @@ -232,13 +232,13 @@ impl PhysicalPlanBuilder { settings.get_enable_experimental_aggregate_hashtable()?; if let Some(grouping_sets) = agg.grouping_sets.as_ref() { - assert_eq!(grouping_sets.dup_group_items.len(), group_items.len() - 1); // ignore `_grouping_id`. - // If the aggregation function argument if a group item, - // we cannot use the group item directly. - // It's because the group item will be wrapped with nullable and fill dummy NULLs (in `AggregateExpand` plan), - // which will cause panic while executing aggregation function. - // To avoid the panic, we will duplicate (`Arc::clone`) original group item columns in `AggregateExpand`, - // we should use these columns instead. + // ignore `_grouping_id`. + // If the aggregation function argument if a group item, + // we cannot use the group item directly. + // It's because the group item will be wrapped with nullable and fill dummy NULLs (in `AggregateExpand` plan), + // which will cause panic while executing aggregation function. + // To avoid the panic, we will duplicate (`Arc::clone`) original group item columns in `AggregateExpand`, + // we should use these columns instead. for func in agg_funcs.iter_mut() { for arg in func.arg_indices.iter_mut() { if let Some(pos) = group_items.iter().position(|g| g == arg) { @@ -480,7 +480,6 @@ impl PhysicalPlanBuilder { if let Some(grouping_sets) = agg.grouping_sets.as_ref() { // The argument types are wrapped nullable due to `AggregateExpand` plan. We should recover them to original types. - assert_eq!(grouping_sets.dup_group_items.len(), group_items.len() - 1); // ignore `_grouping_id`. for func in agg_funcs.iter_mut() { for (arg, ty) in func.arg_indices.iter_mut().zip(func.sig.args.iter_mut()) { if let Some(pos) = group_items.iter().position(|g| g == arg) { diff --git a/src/query/sql/src/planner/binder/aggregate.rs b/src/query/sql/src/planner/binder/aggregate.rs index 8f4bac849bb4a..fea0d22d07d2a 100644 --- a/src/query/sql/src/planner/binder/aggregate.rs +++ b/src/query/sql/src/planner/binder/aggregate.rs @@ -771,6 +771,7 @@ impl Binder { ); dup_group_items.push((dummy.index, *dummy.data_type)); } + // Add a virtual column `_grouping_id` to group items. let grouping_id_column = self.create_derived_column_binding( "_grouping_id".to_string(), @@ -783,14 +784,16 @@ impl Binder { column: grouping_id_column.clone(), }; - agg_info.group_items_map.insert( - bound_grouping_id_col.clone().into(), - agg_info.group_items.len(), - ); - agg_info.group_items.push(ScalarItem { - index: grouping_id_column.index, - scalar: bound_grouping_id_col.into(), - }); + if !self.ctx.get_settings().get_grouping_sets_to_union()? { + agg_info.group_items_map.insert( + bound_grouping_id_col.clone().into(), + agg_info.group_items.len(), + ); + agg_info.group_items.push(ScalarItem { + index: grouping_id_column.index, + scalar: bound_grouping_id_col.into(), + }); + } let grouping_sets_info = GroupingSetsInfo { grouping_id_column, diff --git a/src/query/sql/src/planner/optimizer/optimizer_context.rs b/src/query/sql/src/planner/optimizer/optimizer_context.rs index 35d682b55901f..28e0e9071c26d 100644 --- a/src/query/sql/src/planner/optimizer/optimizer_context.rs +++ b/src/query/sql/src/planner/optimizer/optimizer_context.rs @@ -19,6 +19,7 @@ use databend_common_catalog::table_context::TableContext; use educe::Educe; use parking_lot::RwLock; +use crate::optimizer::optimizers::rule::RuleID; use crate::planner::QueryExecutor; use crate::MetadataRef; @@ -152,6 +153,13 @@ impl OptimizerContext { /// Check if an optimizer or rule is disabled based on optimizer_skip_list setting pub fn is_optimizer_disabled(self: &Arc, name: &str) -> bool { let settings = self.get_table_ctx().get_settings(); + + if !settings.get_grouping_sets_to_union().unwrap_or_default() + && name == RuleID::GroupingSetsToUnion.to_string() + { + return true; + } + match settings.get_optimizer_skip_list() { Ok(skip_list) if !skip_list.is_empty() => { let name_lower = name.to_lowercase(); diff --git a/src/query/sql/src/planner/optimizer/optimizers/rule/agg_rules/mod.rs b/src/query/sql/src/planner/optimizer/optimizers/rule/agg_rules/mod.rs index b1a25d3218ada..715343c80c6af 100644 --- a/src/query/sql/src/planner/optimizer/optimizers/rule/agg_rules/mod.rs +++ b/src/query/sql/src/planner/optimizer/optimizers/rule/agg_rules/mod.rs @@ -15,6 +15,7 @@ mod agg_index; mod rule_eager_aggregation; mod rule_fold_count_aggregate; +mod rule_grouping_sets_to_union; mod rule_push_down_filter_aggregate; mod rule_push_down_limit_aggregate; mod rule_split_aggregate; @@ -22,6 +23,7 @@ mod rule_try_apply_agg_index; pub use rule_eager_aggregation::RuleEagerAggregation; pub use rule_fold_count_aggregate::RuleFoldCountAggregate; +pub use rule_grouping_sets_to_union::RuleGroupingSetsToUnion; pub use rule_push_down_filter_aggregate::RulePushDownFilterAggregate; pub use rule_push_down_limit_aggregate::RulePushDownRankLimitAggregate; pub use rule_split_aggregate::RuleSplitAggregate; diff --git a/src/query/sql/src/planner/optimizer/optimizers/rule/agg_rules/rule_grouping_sets_to_union.rs b/src/query/sql/src/planner/optimizer/optimizers/rule/agg_rules/rule_grouping_sets_to_union.rs new file mode 100644 index 0000000000000..301b5823907eb --- /dev/null +++ b/src/query/sql/src/planner/optimizer/optimizers/rule/agg_rules/rule_grouping_sets_to_union.rs @@ -0,0 +1,258 @@ +// Copyright 2021 Datafuse Labs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::hash::DefaultHasher; +use std::hash::Hash; +use std::hash::Hasher; +use std::sync::Arc; + +use databend_common_exception::Result; +use databend_common_expression::types::NumberScalar; +use databend_common_expression::Scalar; + +use crate::optimizer::ir::Matcher; +use crate::optimizer::ir::RelExpr; +use crate::optimizer::ir::SExpr; +use crate::optimizer::optimizers::rule::Rule; +use crate::optimizer::optimizers::rule::RuleID; +use crate::optimizer::optimizers::rule::TransformResult; +use crate::plans::walk_expr_mut; +use crate::plans::Aggregate; +use crate::plans::AggregateMode; +use crate::plans::CastExpr; +use crate::plans::ConstantExpr; +use crate::plans::EvalScalar; +use crate::plans::MaterializeCTERef; +use crate::plans::MaterializedCTE; +use crate::plans::RelOp; +use crate::plans::Sequence; +use crate::plans::UnionAll; +use crate::plans::VisitorMut; +use crate::IndexType; +use crate::ScalarExpr; + +// TODO +const ID: RuleID = RuleID::GroupingSetsToUnion; +// Split `Grouping Sets` into `Union All` of `Group by` +// Eg: +// select number % 10 AS a, number % 3 AS b, number % 4 AS c +// from numbers(100000000) +// group by grouping sets((a,b),(a,c)); + +// INTO: + +// select number % 10 AS a, number % 3 AS b, number % 4 AS c +// from numbers(100000000) +// group by a,b +// union all +// select number % 10 AS a, number % 3 AS b, number % 4 AS c +// from numbers(100000000) +// group by a,c +// +pub struct RuleGroupingSetsToUnion { + id: RuleID, + matchers: Vec, +} + +impl RuleGroupingSetsToUnion { + pub fn new() -> Self { + Self { + id: ID, + // Aggregate + // \ + // * + matchers: vec![Matcher::MatchOp { + op_type: RelOp::EvalScalar, + children: vec![Matcher::MatchOp { + op_type: RelOp::Aggregate, + children: vec![Matcher::Leaf], + }], + }], + } + } +} + +// Must go before `RuleSplitAggregate` +impl Rule for RuleGroupingSetsToUnion { + fn id(&self) -> RuleID { + self.id + } + + fn apply(&self, s_expr: &SExpr, state: &mut TransformResult) -> Result<()> { + let eval_scalar: EvalScalar = s_expr.plan().clone().try_into()?; + let agg: Aggregate = s_expr.child(0)?.plan().clone().try_into()?; + if agg.mode != AggregateMode::Initial { + return Ok(()); + } + + let agg_input = s_expr.child(0)?.child(0)?; + let agg_input_columns: Vec = RelExpr::with_s_expr(agg_input) + .derive_relational_prop()? + .output_columns + .iter() + .cloned() + .collect(); + + if let Some(grouping_sets) = &agg.grouping_sets { + if !grouping_sets.sets.is_empty() { + let mut children = Vec::with_capacity(grouping_sets.sets.len()); + + let mut hasher = DefaultHasher::new(); + agg.grouping_sets.hash(&mut hasher); + let hash = hasher.finish(); + let temp_cte_name = format!("cte_groupingsets_{hash}"); + + let cte_materialized_sexpr = SExpr::create_unary( + MaterializedCTE::new(temp_cte_name.clone(), None, Some(1)), + agg_input.clone(), + ); + + let cte_consumer = SExpr::create_leaf(MaterializeCTERef { + cte_name: temp_cte_name, + output_columns: agg_input_columns.clone(), + def: agg_input.clone(), + }); + + let mask = (1 << grouping_sets.dup_group_items.len()) - 1; + let group_bys = agg + .group_items + .iter() + .map(|i| { + agg_input_columns + .iter() + .position(|t| *t == i.index) + .unwrap() + }) + .collect::>(); + + for set in &grouping_sets.sets { + let mut id = 0; + + // For element in `group_bys`, + // if it is in current grouping set: set 0, else: set 1. (1 represents it will be NULL in grouping) + // Example: GROUP BY GROUPING SETS ((a, b), (a), (b), ()) + // group_bys: [a, b] + // grouping_sets: [[0, 1], [0], [1], []] + // grouping_ids: 00, 01, 10, 11 + + for g in set { + let i = group_bys.iter().position(|t| *t == *g).unwrap(); + id |= 1 << i; + } + let grouping_id = !id & mask; + + let mut eval_scalar = eval_scalar.clone(); + let mut agg = agg.clone(); + agg.grouping_sets = None; + + let null_group_ids: Vec = agg + .group_items + .iter() + .map(|i| i.index) + .filter(|index| !set.contains(index)) + .clone() + .collect(); + + agg.group_items.retain(|x| set.contains(&x.index)); + let group_ids: Vec = + agg.group_items.iter().map(|i| i.index).collect(); + + let mut visitor = ReplaceColumnForGroupingSetsVisitor { + group_indexes: group_ids, + exclude_group_indexes: null_group_ids, + grouping_id_index: grouping_sets.grouping_id_index, + grouping_id_value: grouping_id, + }; + + for scalar in eval_scalar.items.iter_mut() { + visitor.visit(&mut scalar.scalar)?; + } + + let agg_plan = SExpr::create_unary(agg, cte_consumer.clone()); + let eval_plan = SExpr::create_unary(eval_scalar, agg_plan); + children.push(eval_plan); + } + + // fold children into result + let mut result = children.first().unwrap().clone(); + for other in children.into_iter().skip(1) { + let left_outputs: Vec<(IndexType, Option)> = + eval_scalar.items.iter().map(|x| (x.index, None)).collect(); + let right_outputs = left_outputs.clone(); + + let union_plan = UnionAll { + left_outputs, + right_outputs, + cte_scan_names: vec![], + output_indexes: eval_scalar.items.iter().map(|x| x.index).collect(), + }; + result = SExpr::create_binary(Arc::new(union_plan.into()), result, other); + } + result = SExpr::create_binary(Sequence, cte_materialized_sexpr, result); + state.add_result(result); + return Ok(()); + } + } + Ok(()) + } + + fn matchers(&self) -> &[Matcher] { + &self.matchers + } +} + +impl Default for RuleGroupingSetsToUnion { + fn default() -> Self { + Self::new() + } +} + +struct ReplaceColumnForGroupingSetsVisitor { + group_indexes: Vec, + exclude_group_indexes: Vec, + grouping_id_index: IndexType, + grouping_id_value: u32, +} + +impl VisitorMut<'_> for ReplaceColumnForGroupingSetsVisitor { + fn visit(&mut self, expr: &mut ScalarExpr) -> Result<()> { + let old = expr.clone(); + + if let ScalarExpr::BoundColumnRef(col) = expr { + if self.group_indexes.contains(&col.column.index) { + *expr = ScalarExpr::CastExpr(CastExpr { + argument: Box::new(old), + is_try: true, + target_type: Box::new(col.column.data_type.wrap_nullable()), + span: col.span, + }); + } else if self.exclude_group_indexes.contains(&col.column.index) { + *expr = ScalarExpr::TypedConstantExpr( + ConstantExpr { + value: Scalar::Null, + span: col.span, + }, + col.column.data_type.wrap_nullable(), + ); + } else if self.grouping_id_index == col.column.index { + *expr = ScalarExpr::ConstantExpr(ConstantExpr { + value: Scalar::Number(NumberScalar::UInt32(self.grouping_id_value)), + span: col.span, + }); + } + return Ok(()); + } + walk_expr_mut(self, expr) + } +} diff --git a/src/query/sql/src/planner/optimizer/optimizers/rule/agg_rules/rule_push_down_limit_aggregate.rs b/src/query/sql/src/planner/optimizer/optimizers/rule/agg_rules/rule_push_down_limit_aggregate.rs index bf2f1c1eb05ab..4917f45285697 100644 --- a/src/query/sql/src/planner/optimizer/optimizers/rule/agg_rules/rule_push_down_limit_aggregate.rs +++ b/src/query/sql/src/planner/optimizer/optimizers/rule/agg_rules/rule_push_down_limit_aggregate.rs @@ -47,7 +47,7 @@ pub struct RulePushDownRankLimitAggregate { impl RulePushDownRankLimitAggregate { pub fn new(max_limit: usize) -> Self { Self { - id: RuleID::RulePushDownRankLimitAggregate, + id: RuleID::PushDownRankLimitAggregate, matchers: vec![ Matcher::MatchOp { op_type: RelOp::Limit, diff --git a/src/query/sql/src/planner/optimizer/optimizers/rule/factory.rs b/src/query/sql/src/planner/optimizer/optimizers/rule/factory.rs index f941c88dc4d9b..913befc8e8002 100644 --- a/src/query/sql/src/planner/optimizer/optimizers/rule/factory.rs +++ b/src/query/sql/src/planner/optimizer/optimizers/rule/factory.rs @@ -25,6 +25,7 @@ use crate::optimizer::optimizers::rule::RuleEliminateSort; use crate::optimizer::optimizers::rule::RuleEliminateUnion; use crate::optimizer::optimizers::rule::RuleFilterNulls; use crate::optimizer::optimizers::rule::RuleFoldCountAggregate; +use crate::optimizer::optimizers::rule::RuleGroupingSetsToUnion; use crate::optimizer::optimizers::rule::RuleID; use crate::optimizer::optimizers::rule::RuleLeftExchangeJoin; use crate::optimizer::optimizers::rule::RuleMergeEvalScalar; @@ -89,7 +90,7 @@ impl RuleFactory { RuleID::PushDownLimitWindow => Ok(Box::new(RulePushDownLimitWindow::new( ctx.get_max_push_down_limit(), ))), - RuleID::RulePushDownRankLimitAggregate => Ok(Box::new( + RuleID::PushDownRankLimitAggregate => Ok(Box::new( RulePushDownRankLimitAggregate::new(ctx.get_max_push_down_limit()), )), RuleID::PushDownFilterAggregate => Ok(Box::new(RulePushDownFilterAggregate::new())), @@ -101,6 +102,7 @@ impl RuleFactory { RuleID::MergeEvalScalar => Ok(Box::new(RuleMergeEvalScalar::new())), RuleID::MergeFilter => Ok(Box::new(RuleMergeFilter::new())), RuleID::NormalizeScalarFilter => Ok(Box::new(RuleNormalizeScalarFilter::new())), + RuleID::GroupingSetsToUnion => Ok(Box::new(RuleGroupingSetsToUnion::new())), RuleID::SplitAggregate => Ok(Box::new(RuleSplitAggregate::new())), RuleID::FoldCountAggregate => Ok(Box::new(RuleFoldCountAggregate::new())), RuleID::CommuteJoin => Ok(Box::new(RuleCommuteJoin::new())), diff --git a/src/query/sql/src/planner/optimizer/optimizers/rule/rule.rs b/src/query/sql/src/planner/optimizer/optimizers/rule/rule.rs index f6af7ad45b861..eabd76d1e3402 100644 --- a/src/query/sql/src/planner/optimizer/optimizers/rule/rule.rs +++ b/src/query/sql/src/planner/optimizer/optimizers/rule/rule.rs @@ -49,7 +49,7 @@ pub static DEFAULT_REWRITE_RULES: LazyLock> = LazyLock::new(|| { RuleID::PushDownLimitEvalScalar, RuleID::PushDownLimitSort, RuleID::PushDownLimitWindow, - RuleID::RulePushDownRankLimitAggregate, + RuleID::PushDownRankLimitAggregate, RuleID::PushDownLimitOuterJoin, RuleID::PushDownLimitScan, RuleID::SemiToInnerJoin, @@ -58,6 +58,7 @@ pub static DEFAULT_REWRITE_RULES: LazyLock> = LazyLock::new(|| { RuleID::PushDownFilterScan, RuleID::PushDownPrewhere, /* PushDownPrwhere should be after all rules except PushDownFilterScan */ RuleID::PushDownSortScan, // PushDownSortScan should be after PushDownPrewhere + RuleID::GroupingSetsToUnion, ] }); @@ -102,7 +103,7 @@ pub enum RuleID { PushDownLimitEvalScalar, PushDownLimitSort, PushDownLimitWindow, - RulePushDownRankLimitAggregate, + PushDownRankLimitAggregate, PushDownLimitScan, PushDownSortEvalScalar, PushDownSortScan, @@ -112,6 +113,7 @@ pub enum RuleID { EliminateSort, MergeEvalScalar, MergeFilter, + GroupingSetsToUnion, SplitAggregate, FoldCountAggregate, PushDownPrewhere, @@ -142,7 +144,7 @@ impl Display for RuleID { RuleID::PushDownLimitOuterJoin => write!(f, "PushDownLimitOuterJoin"), RuleID::PushDownLimitEvalScalar => write!(f, "PushDownLimitEvalScalar"), RuleID::PushDownLimitSort => write!(f, "PushDownLimitSort"), - RuleID::RulePushDownRankLimitAggregate => write!(f, "RulePushDownRankLimitAggregate"), + RuleID::PushDownRankLimitAggregate => write!(f, "PushDownRankLimitAggregate"), RuleID::PushDownFilterAggregate => write!(f, "PushDownFilterAggregate"), RuleID::PushDownLimitScan => write!(f, "PushDownLimitScan"), RuleID::PushDownSortScan => write!(f, "PushDownSortScan"), @@ -156,6 +158,7 @@ impl Display for RuleID { RuleID::MergeEvalScalar => write!(f, "MergeEvalScalar"), RuleID::MergeFilter => write!(f, "MergeFilter"), RuleID::NormalizeScalarFilter => write!(f, "NormalizeScalarFilter"), + RuleID::GroupingSetsToUnion => write!(f, "GroupingSetsToUnion"), RuleID::SplitAggregate => write!(f, "SplitAggregate"), RuleID::FoldCountAggregate => write!(f, "FoldCountAggregate"), RuleID::PushDownPrewhere => write!(f, "PushDownPrewhere"), diff --git a/src/query/sql/src/planner/optimizer/optimizers/rule/scalar_rules/rule_eliminate_eval_scalar.rs b/src/query/sql/src/planner/optimizer/optimizers/rule/scalar_rules/rule_eliminate_eval_scalar.rs index e04f2058f70ce..1acb7d127ccb2 100644 --- a/src/query/sql/src/planner/optimizer/optimizers/rule/scalar_rules/rule_eliminate_eval_scalar.rs +++ b/src/query/sql/src/planner/optimizer/optimizers/rule/scalar_rules/rule_eliminate_eval_scalar.rs @@ -25,6 +25,7 @@ use crate::plans::Operator; use crate::plans::RelOp; use crate::ColumnSet; use crate::MetadataRef; +use crate::ScalarExpr; pub struct RuleEliminateEvalScalar { id: RuleID, @@ -73,7 +74,32 @@ impl Rule for RuleEliminateEvalScalar { .clone(); let eval_scalar_output_cols: ColumnSet = eval_scalar.items.iter().map(|x| x.index).collect(); + if eval_scalar_output_cols.is_subset(&child_output_cols) { + // check if there's f(#x) as #x, if so we can't eliminate the eval scalar + for item in eval_scalar.items { + match item.scalar { + ScalarExpr::FunctionCall(func) => { + if func.arguments.len() == 1 { + if let ScalarExpr::BoundColumnRef(bound_column_ref) = &func.arguments[0] + { + if bound_column_ref.column.index == item.index { + return Ok(()); + } + } + } + } + ScalarExpr::CastExpr(cast) => { + if let ScalarExpr::BoundColumnRef(bound_column_ref) = cast.argument.as_ref() + { + if bound_column_ref.column.index == item.index { + return Ok(()); + } + } + } + _ => {} + } + } state.add_result(s_expr.child(0)?.clone()); return Ok(()); } diff --git a/tests/sqllogictests/suites/duckdb/sql/aggregate/group/group_by_grouping_sets_union_all.test b/tests/sqllogictests/suites/duckdb/sql/aggregate/group/group_by_grouping_sets_union_all.test new file mode 100644 index 0000000000000..c815d16d84465 --- /dev/null +++ b/tests/sqllogictests/suites/duckdb/sql/aggregate/group/group_by_grouping_sets_union_all.test @@ -0,0 +1,7 @@ +statement ok +set grouping_sets_to_union = 1; + +include ./group_by_grouping_sets.test + +statement ok +unset grouping_sets_to_union; diff --git a/tests/sqllogictests/suites/mode/standalone/explain/explain_grouping_sets.test b/tests/sqllogictests/suites/mode/standalone/explain/explain_grouping_sets.test index 1573e610ea0f1..6961b77298dfd 100644 --- a/tests/sqllogictests/suites/mode/standalone/explain/explain_grouping_sets.test +++ b/tests/sqllogictests/suites/mode/standalone/explain/explain_grouping_sets.test @@ -65,3 +65,164 @@ EvalScalar ├── partitions scanned: 1 ├── push downs: [filters: [], limit: NONE] └── estimated rows: 1.00 + + +statement ok +set grouping_sets_to_union = 1; + +query T +explain select number % 2 as a, number % 3 as b, number % 5 as c from numbers(1) group by cube(a, b, c); +---- +Sequence +├── MaterializedCTE: cte_groupingsets_16366510952463710337 +│ └── EvalScalar +│ ├── output columns: [numbers.number (#0), a (#1), b (#2), c (#3)] +│ ├── expressions: [numbers.number (#0) % 2, numbers.number (#0) % 3, numbers.number (#0) % 5] +│ ├── estimated rows: 1.00 +│ └── TableScan +│ ├── table: default.system.numbers +│ ├── output columns: [number (#0)] +│ ├── read rows: 1 +│ ├── read size: < 1 KiB +│ ├── partitions total: 1 +│ ├── partitions scanned: 1 +│ ├── push downs: [filters: [], limit: NONE] +│ └── estimated rows: 1.00 +└── UnionAll + ├── output columns: [a (#8), b (#9), c (#10)] + ├── estimated rows: 8.00 + ├── UnionAll + │ ├── output columns: [a (#8), b (#9), c (#10)] + │ ├── estimated rows: 7.00 + │ ├── UnionAll + │ │ ├── output columns: [a (#8), b (#9), c (#10)] + │ │ ├── estimated rows: 6.00 + │ │ ├── UnionAll + │ │ │ ├── output columns: [a (#8), b (#9), c (#10)] + │ │ │ ├── estimated rows: 5.00 + │ │ │ ├── UnionAll + │ │ │ │ ├── output columns: [a (#8), b (#9), c (#10)] + │ │ │ │ ├── estimated rows: 4.00 + │ │ │ │ ├── UnionAll + │ │ │ │ │ ├── output columns: [a (#8), b (#9), c (#10)] + │ │ │ │ │ ├── estimated rows: 3.00 + │ │ │ │ │ ├── UnionAll + │ │ │ │ │ │ ├── output columns: [a (#8), b (#9), c (#10)] + │ │ │ │ │ │ ├── estimated rows: 2.00 + │ │ │ │ │ │ ├── EvalScalar + │ │ │ │ │ │ │ ├── output columns: [a (#8), b (#9), c (#10)] + │ │ │ │ │ │ │ ├── expressions: [NULL, NULL, NULL] + │ │ │ │ │ │ │ ├── estimated rows: 1.00 + │ │ │ │ │ │ │ └── DummyTableScan + │ │ │ │ │ │ └── EvalScalar + │ │ │ │ │ │ ├── output columns: [a (#8), b (#9), c (#10)] + │ │ │ │ │ │ ├── expressions: [TRY_CAST(group_item (#1) AS UInt8 NULL), NULL, NULL] + │ │ │ │ │ │ ├── estimated rows: 1.00 + │ │ │ │ │ │ └── AggregateFinal + │ │ │ │ │ │ ├── output columns: [a (#1)] + │ │ │ │ │ │ ├── group by: [a] + │ │ │ │ │ │ ├── aggregate functions: [] + │ │ │ │ │ │ ├── estimated rows: 1.00 + │ │ │ │ │ │ └── AggregatePartial + │ │ │ │ │ │ ├── group by: [a] + │ │ │ │ │ │ ├── aggregate functions: [] + │ │ │ │ │ │ ├── estimated rows: 1.00 + │ │ │ │ │ │ └── MaterializeCTERef + │ │ │ │ │ │ ├── cte_name: cte_groupingsets_16366510952463710337 + │ │ │ │ │ │ └── cte_schema: [number (#0), a (#1), b (#2), c (#3)] + │ │ │ │ │ └── EvalScalar + │ │ │ │ │ ├── output columns: [a (#8), b (#9), c (#10)] + │ │ │ │ │ ├── expressions: [NULL, TRY_CAST(group_item (#2) AS UInt8 NULL), NULL] + │ │ │ │ │ ├── estimated rows: 1.00 + │ │ │ │ │ └── AggregateFinal + │ │ │ │ │ ├── output columns: [b (#2)] + │ │ │ │ │ ├── group by: [b] + │ │ │ │ │ ├── aggregate functions: [] + │ │ │ │ │ ├── estimated rows: 1.00 + │ │ │ │ │ └── AggregatePartial + │ │ │ │ │ ├── group by: [b] + │ │ │ │ │ ├── aggregate functions: [] + │ │ │ │ │ ├── estimated rows: 1.00 + │ │ │ │ │ └── MaterializeCTERef + │ │ │ │ │ ├── cte_name: cte_groupingsets_16366510952463710337 + │ │ │ │ │ └── cte_schema: [number (#0), a (#1), b (#2), c (#3)] + │ │ │ │ └── EvalScalar + │ │ │ │ ├── output columns: [a (#8), b (#9), c (#10)] + │ │ │ │ ├── expressions: [NULL, NULL, TRY_CAST(group_item (#3) AS UInt8 NULL)] + │ │ │ │ ├── estimated rows: 1.00 + │ │ │ │ └── AggregateFinal + │ │ │ │ ├── output columns: [c (#3)] + │ │ │ │ ├── group by: [c] + │ │ │ │ ├── aggregate functions: [] + │ │ │ │ ├── estimated rows: 1.00 + │ │ │ │ └── AggregatePartial + │ │ │ │ ├── group by: [c] + │ │ │ │ ├── aggregate functions: [] + │ │ │ │ ├── estimated rows: 1.00 + │ │ │ │ └── MaterializeCTERef + │ │ │ │ ├── cte_name: cte_groupingsets_16366510952463710337 + │ │ │ │ └── cte_schema: [number (#0), a (#1), b (#2), c (#3)] + │ │ │ └── EvalScalar + │ │ │ ├── output columns: [a (#8), b (#9), c (#10)] + │ │ │ ├── expressions: [TRY_CAST(group_item (#1) AS UInt8 NULL), TRY_CAST(group_item (#2) AS UInt8 NULL), NULL] + │ │ │ ├── estimated rows: 1.00 + │ │ │ └── AggregateFinal + │ │ │ ├── output columns: [a (#1), b (#2)] + │ │ │ ├── group by: [a, b] + │ │ │ ├── aggregate functions: [] + │ │ │ ├── estimated rows: 1.00 + │ │ │ └── AggregatePartial + │ │ │ ├── group by: [a, b] + │ │ │ ├── aggregate functions: [] + │ │ │ ├── estimated rows: 1.00 + │ │ │ └── MaterializeCTERef + │ │ │ ├── cte_name: cte_groupingsets_16366510952463710337 + │ │ │ └── cte_schema: [number (#0), a (#1), b (#2), c (#3)] + │ │ └── EvalScalar + │ │ ├── output columns: [a (#8), b (#9), c (#10)] + │ │ ├── expressions: [TRY_CAST(group_item (#1) AS UInt8 NULL), NULL, TRY_CAST(group_item (#3) AS UInt8 NULL)] + │ │ ├── estimated rows: 1.00 + │ │ └── AggregateFinal + │ │ ├── output columns: [a (#1), c (#3)] + │ │ ├── group by: [a, c] + │ │ ├── aggregate functions: [] + │ │ ├── estimated rows: 1.00 + │ │ └── AggregatePartial + │ │ ├── group by: [a, c] + │ │ ├── aggregate functions: [] + │ │ ├── estimated rows: 1.00 + │ │ └── MaterializeCTERef + │ │ ├── cte_name: cte_groupingsets_16366510952463710337 + │ │ └── cte_schema: [number (#0), a (#1), b (#2), c (#3)] + │ └── EvalScalar + │ ├── output columns: [a (#8), b (#9), c (#10)] + │ ├── expressions: [NULL, TRY_CAST(group_item (#2) AS UInt8 NULL), TRY_CAST(group_item (#3) AS UInt8 NULL)] + │ ├── estimated rows: 1.00 + │ └── AggregateFinal + │ ├── output columns: [b (#2), c (#3)] + │ ├── group by: [b, c] + │ ├── aggregate functions: [] + │ ├── estimated rows: 1.00 + │ └── AggregatePartial + │ ├── group by: [b, c] + │ ├── aggregate functions: [] + │ ├── estimated rows: 1.00 + │ └── MaterializeCTERef + │ ├── cte_name: cte_groupingsets_16366510952463710337 + │ └── cte_schema: [number (#0), a (#1), b (#2), c (#3)] + └── EvalScalar + ├── output columns: [a (#8), b (#9), c (#10)] + ├── expressions: [TRY_CAST(group_item (#1) AS UInt8 NULL), TRY_CAST(group_item (#2) AS UInt8 NULL), TRY_CAST(group_item (#3) AS UInt8 NULL)] + ├── estimated rows: 1.00 + └── AggregateFinal + ├── output columns: [a (#1), b (#2), c (#3)] + ├── group by: [a, b, c] + ├── aggregate functions: [] + ├── estimated rows: 1.00 + └── AggregatePartial + ├── group by: [a, b, c] + ├── aggregate functions: [] + ├── estimated rows: 1.00 + └── MaterializeCTERef + ├── cte_name: cte_groupingsets_16366510952463710337 + └── cte_schema: [number (#0), a (#1), b (#2), c (#3)]