From fd08e9a68296fa27b940247335b69c6a46801132 Mon Sep 17 00:00:00 2001 From: Andy Grove Date: Fri, 6 May 2022 10:32:47 -0600 Subject: [PATCH 1/8] Add SQL planner support for ROLLUP and CUBE grouping sets --- .../core/src/datasource/listing/helpers.rs | 1 + datafusion/core/src/logical_plan/builder.rs | 15 +- datafusion/core/src/logical_plan/expr.rs | 38 +- .../core/src/logical_plan/expr_rewriter.rs | 17 + .../core/src/logical_plan/expr_visitor.rs | 14 + datafusion/core/src/logical_plan/mod.rs | 12 +- .../src/optimizer/common_subexpr_eliminate.rs | 28 ++ .../src/optimizer/projection_push_down.rs | 6 +- .../src/optimizer/simplify_expressions.rs | 1 + datafusion/core/src/optimizer/utils.rs | 20 + datafusion/core/src/physical_plan/planner.rs | 32 ++ datafusion/core/src/sql/planner.rs | 348 +++++++++++------- datafusion/core/src/sql/utils.rs | 74 +++- datafusion/core/tests/sql/group_by.rs | 26 ++ datafusion/expr/src/expr.rs | 104 ++++++ datafusion/expr/src/expr_schema.rs | 8 + datafusion/expr/src/logical_plan/plan.rs | 23 +- 17 files changed, 593 insertions(+), 174 deletions(-) diff --git a/datafusion/core/src/datasource/listing/helpers.rs b/datafusion/core/src/datasource/listing/helpers.rs index 9518986a14da..11a91f2eeaa8 100644 --- a/datafusion/core/src/datasource/listing/helpers.rs +++ b/datafusion/core/src/datasource/listing/helpers.rs @@ -96,6 +96,7 @@ impl ExpressionVisitor for ApplicabilityVisitor<'_> { | Expr::InSubquery { .. } | Expr::ScalarSubquery(_) | Expr::GetIndexedField { .. } + | Expr::GroupingSet(_) | Expr::Case { .. } => Recursion::Continue(self), Expr::ScalarFunction { fun, .. } => self.visit_volatility(fun.volatility()), diff --git a/datafusion/core/src/logical_plan/builder.rs b/datafusion/core/src/logical_plan/builder.rs index 1fbb1f5f9dfe..37449a604f95 100644 --- a/datafusion/core/src/logical_plan/builder.rs +++ b/datafusion/core/src/logical_plan/builder.rs @@ -43,7 +43,8 @@ use std::{ sync::Arc, }; -use super::{exprlist_to_fields, Expr, JoinConstraint, JoinType, LogicalPlan, PlanType}; +use super::{Expr, JoinConstraint, JoinType, LogicalPlan, PlanType}; +use crate::logical_plan::expr::exprlist_to_fields; use crate::logical_plan::{ columnize_expr, normalize_col, normalize_cols, provider_as_source, rewrite_sort_cols_by_aggs, Column, CrossJoin, DFField, DFSchema, DFSchemaRef, Limit, @@ -557,7 +558,7 @@ impl LogicalPlanBuilder { expr.extend(missing_exprs); let new_schema = DFSchema::new_with_metadata( - exprlist_to_fields(&expr, input_schema)?, + exprlist_to_fields(&expr, &input)?, input_schema.metadata().clone(), )?; @@ -629,7 +630,7 @@ impl LogicalPlanBuilder { .map(|f| Expr::Column(f.qualified_column())) .collect(); let new_schema = DFSchema::new_with_metadata( - exprlist_to_fields(&new_expr, schema)?, + exprlist_to_fields(&new_expr, &self.plan)?, schema.metadata().clone(), )?; @@ -843,8 +844,7 @@ impl LogicalPlanBuilder { let window_expr = normalize_cols(window_expr, &self.plan)?; let all_expr = window_expr.iter(); validate_unique_names("Windows", all_expr.clone(), self.plan.schema())?; - let mut window_fields: Vec = - exprlist_to_fields(all_expr, self.plan.schema())?; + let mut window_fields: Vec = exprlist_to_fields(all_expr, &self.plan)?; window_fields.extend_from_slice(self.plan.schema().fields()); Ok(Self::from(LogicalPlan::Window(Window { input: Arc::new(self.plan.clone()), @@ -869,7 +869,7 @@ impl LogicalPlanBuilder { let all_expr = group_expr.iter().chain(aggr_expr.iter()); validate_unique_names("Aggregations", all_expr.clone(), self.plan.schema())?; let aggr_schema = DFSchema::new_with_metadata( - exprlist_to_fields(all_expr, self.plan.schema())?, + exprlist_to_fields(all_expr, &self.plan)?, self.plan.schema().metadata().clone(), )?; Ok(Self::from(LogicalPlan::Aggregate(Aggregate { @@ -1126,13 +1126,14 @@ pub fn project_with_alias( } validate_unique_names("Projections", projected_expr.iter(), input_schema)?; let input_schema = DFSchema::new_with_metadata( - exprlist_to_fields(&projected_expr, input_schema)?, + exprlist_to_fields(&projected_expr, &plan)?, plan.schema().metadata().clone(), )?; let schema = match alias { Some(ref alias) => input_schema.replace_qualifier(alias.as_str()), None => input_schema, }; + Ok(LogicalPlan::Projection(Projection { expr: projected_expr, input: Arc::new(plan.clone()), diff --git a/datafusion/core/src/logical_plan/expr.rs b/datafusion/core/src/logical_plan/expr.rs index 673345c69b61..1ebbbafd9831 100644 --- a/datafusion/core/src/logical_plan/expr.rs +++ b/datafusion/core/src/logical_plan/expr.rs @@ -25,11 +25,11 @@ use crate::logical_plan::{DFField, DFSchema}; use arrow::datatypes::DataType; pub use datafusion_common::{Column, ExprSchema}; pub use datafusion_expr::expr_fn::*; -use datafusion_expr::AccumulatorFunctionImplementation; use datafusion_expr::BuiltinScalarFunction; pub use datafusion_expr::Expr; use datafusion_expr::StateTypeFunction; pub use datafusion_expr::{lit, lit_timestamp_nano, Literal}; +use datafusion_expr::{AccumulatorFunctionImplementation, LogicalPlan}; use datafusion_expr::{AggregateUDF, ScalarUDF}; use datafusion_expr::{ ReturnTypeFunction, ScalarFunctionImplementation, Signature, Volatility, @@ -137,6 +137,42 @@ pub fn create_udaf( /// Create field meta-data from an expression, for use in a result set schema pub fn exprlist_to_fields<'a>( + expr: impl IntoIterator, + plan: &LogicalPlan, +) -> Result> { + let exprs: Vec = expr.into_iter().cloned().collect(); + let mut fields = vec![]; + for expr in &exprs { + match expr { + Expr::Column(c) => { + match plan { + LogicalPlan::Aggregate(agg) => { + let group_expr = agg.columns_in_group_expr()?; + if let Some(_) = group_expr.into_iter().find(|x| x == c) { + // fall back to legacy behavior, which has known issues, but at least use valid expressions and schemas + fields.push(expr.to_field(&agg.input.schema())?); + } else { + // fall back to legacy behavior, which has known issues + fields.push(expr.to_field(plan.schema())?); + } + } + _ => { + // fall back to legacy behavior, which has known issues + fields.push(expr.to_field(plan.schema())?); + } + } + } + _ => { + // fall back to legacy behavior, which has known issues + fields.push(expr.to_field(&plan.schema())?); + } + } + } + Ok(fields) +} + +/// Create field meta-data from an expression, for use in a result set schema +pub fn exprlist_to_fields_from_schema<'a>( expr: impl IntoIterator, input_schema: &DFSchema, ) -> Result> { diff --git a/datafusion/core/src/logical_plan/expr_rewriter.rs b/datafusion/core/src/logical_plan/expr_rewriter.rs index 4e94768993d5..1f24556eaa80 100644 --- a/datafusion/core/src/logical_plan/expr_rewriter.rs +++ b/datafusion/core/src/logical_plan/expr_rewriter.rs @@ -24,6 +24,7 @@ use crate::logical_plan::ExprSchemable; use crate::logical_plan::LogicalPlan; use datafusion_common::Column; use datafusion_common::Result; +use datafusion_expr::expr::GroupingSet; use std::collections::HashMap; use std::collections::HashSet; use std::sync::Arc; @@ -215,6 +216,22 @@ impl ExprRewritable for Expr { fun, distinct, }, + Expr::GroupingSet(grouping_set) => match grouping_set { + GroupingSet::Rollup(exprs) => { + Expr::GroupingSet(GroupingSet::Rollup(rewrite_vec(exprs, rewriter)?)) + } + GroupingSet::Cube(exprs) => { + Expr::GroupingSet(GroupingSet::Cube(rewrite_vec(exprs, rewriter)?)) + } + GroupingSet::GroupingSets(lists_of_exprs) => { + Expr::GroupingSet(GroupingSet::GroupingSets( + lists_of_exprs + .iter() + .map(|exprs| rewrite_vec(exprs.clone(), rewriter)) + .collect::>>()?, + )) + } + }, Expr::AggregateUDF { args, fun } => Expr::AggregateUDF { args: rewrite_vec(args, rewriter)?, fun, diff --git a/datafusion/core/src/logical_plan/expr_visitor.rs b/datafusion/core/src/logical_plan/expr_visitor.rs index 7c578da19b75..24acb65bcbab 100644 --- a/datafusion/core/src/logical_plan/expr_visitor.rs +++ b/datafusion/core/src/logical_plan/expr_visitor.rs @@ -19,6 +19,7 @@ use super::Expr; use datafusion_common::Result; +use datafusion_expr::expr::GroupingSet; /// Controls how the visitor recursion should proceed. pub enum Recursion { @@ -103,6 +104,19 @@ impl ExprVisitable for Expr { | Expr::TryCast { expr, .. } | Expr::Sort { expr, .. } | Expr::GetIndexedField { expr, .. } => expr.accept(visitor), + Expr::GroupingSet(GroupingSet::Rollup(exprs)) => exprs + .iter() + .fold(Ok(visitor), |v, e| v.and_then(|v| e.accept(v))), + Expr::GroupingSet(GroupingSet::Cube(exprs)) => exprs + .iter() + .fold(Ok(visitor), |v, e| v.and_then(|v| e.accept(v))), + Expr::GroupingSet(GroupingSet::GroupingSets(lists_of_exprs)) => { + lists_of_exprs.iter().fold(Ok(visitor), |v, exprs| { + v.and_then(|v| { + exprs.iter().fold(Ok(v), |v, e| v.and_then(|v| e.accept(v))) + }) + }) + } Expr::Column(_) | Expr::ScalarVariable(_, _) | Expr::Literal(_) diff --git a/datafusion/core/src/logical_plan/mod.rs b/datafusion/core/src/logical_plan/mod.rs index 55295e22e956..e0968f99eb3d 100644 --- a/datafusion/core/src/logical_plan/mod.rs +++ b/datafusion/core/src/logical_plan/mod.rs @@ -41,12 +41,12 @@ pub use expr::{ avg, bit_length, btrim, call_fn, case, ceil, character_length, chr, coalesce, col, columnize_expr, combine_filters, concat, concat_expr, concat_ws, concat_ws_expr, cos, count, count_distinct, create_udaf, create_udf, date_part, date_trunc, digest, - exists, exp, exprlist_to_fields, floor, in_list, in_subquery, initcap, left, length, - lit, lit_timestamp_nano, ln, log10, log2, lower, lpad, ltrim, max, md5, min, - not_exists, not_in_subquery, now, now_expr, nullif, octet_length, or, power, random, - regexp_match, regexp_replace, repeat, replace, reverse, right, round, rpad, rtrim, - scalar_subquery, sha224, sha256, sha384, sha512, signum, sin, split_part, sqrt, - starts_with, strpos, substr, sum, tan, to_hex, to_timestamp_micros, + exists, exp, exprlist_to_fields_from_schema, floor, in_list, in_subquery, initcap, + left, length, lit, lit_timestamp_nano, ln, log10, log2, lower, lpad, ltrim, max, md5, + min, not_exists, not_in_subquery, now, now_expr, nullif, octet_length, or, power, + random, regexp_match, regexp_replace, repeat, replace, reverse, right, round, rpad, + rtrim, scalar_subquery, sha224, sha256, sha384, sha512, signum, sin, split_part, + sqrt, starts_with, strpos, substr, sum, tan, to_hex, to_timestamp_micros, to_timestamp_millis, to_timestamp_seconds, translate, trim, trunc, unalias, upper, when, Column, Expr, ExprSchema, Literal, }; diff --git a/datafusion/core/src/optimizer/common_subexpr_eliminate.rs b/datafusion/core/src/optimizer/common_subexpr_eliminate.rs index a9983cdf1e08..b4010542ea80 100644 --- a/datafusion/core/src/optimizer/common_subexpr_eliminate.rs +++ b/datafusion/core/src/optimizer/common_subexpr_eliminate.rs @@ -29,6 +29,7 @@ use crate::logical_plan::{ use crate::optimizer::optimizer::OptimizerRule; use crate::optimizer::utils; use arrow::datatypes::DataType; +use datafusion_expr::expr::GroupingSet; use std::collections::{HashMap, HashSet}; use std::sync::Arc; @@ -482,6 +483,33 @@ impl ExprIdentifierVisitor<'_> { desc.push_str("GetIndexedField-"); desc.push_str(&key.to_string()); } + Expr::GroupingSet(grouping_set) => match grouping_set { + GroupingSet::Rollup(exprs) => { + desc.push_str("Rollup"); + for expr in exprs { + desc.push_str("-"); + desc.push_str(&Self::desc_expr(expr)); + } + } + GroupingSet::Cube(exprs) => { + desc.push_str("Cube"); + for expr in exprs { + desc.push_str("-"); + desc.push_str(&Self::desc_expr(expr)); + } + } + GroupingSet::GroupingSets(lists_of_exprs) => { + desc.push_str("GroupingSets"); + for exprs in lists_of_exprs { + desc.push_str("("); + for expr in exprs { + desc.push_str("-"); + desc.push_str(&Self::desc_expr(expr)); + } + desc.push_str(")"); + } + } + }, } desc diff --git a/datafusion/core/src/optimizer/projection_push_down.rs b/datafusion/core/src/optimizer/projection_push_down.rs index 5062082e8643..595045cd4298 100644 --- a/datafusion/core/src/optimizer/projection_push_down.rs +++ b/datafusion/core/src/optimizer/projection_push_down.rs @@ -507,7 +507,8 @@ mod tests { use super::*; use crate::logical_plan::{ - col, exprlist_to_fields, lit, max, min, Expr, JoinType, LogicalPlanBuilder, + col, exprlist_to_fields_from_schema, lit, max, min, Expr, JoinType, + LogicalPlanBuilder, }; use crate::test::*; use arrow::datatypes::DataType; @@ -810,7 +811,8 @@ mod tests { // that the Column references are unqualified (e.g. their // relation is `None`). PlanBuilder resolves the expressions let expr = vec![col("a"), col("b")]; - let projected_fields = exprlist_to_fields(&expr, input_schema).unwrap(); + let projected_fields = + exprlist_to_fields_from_schema(&expr, input_schema).unwrap(); let projected_schema = DFSchema::new_with_metadata( projected_fields, input_schema.metadata().clone(), diff --git a/datafusion/core/src/optimizer/simplify_expressions.rs b/datafusion/core/src/optimizer/simplify_expressions.rs index 4dfbb6eb6543..e9694ebc528c 100644 --- a/datafusion/core/src/optimizer/simplify_expressions.rs +++ b/datafusion/core/src/optimizer/simplify_expressions.rs @@ -380,6 +380,7 @@ impl<'a> ConstEvaluator<'a> { | Expr::ScalarSubquery(_) | Expr::WindowFunction { .. } | Expr::Sort { .. } + | Expr::GroupingSet(_) | Expr::Wildcard | Expr::QualifiedWildcard { .. } => false, Expr::ScalarFunction { fun, .. } => Self::volatility_ok(fun.volatility()), diff --git a/datafusion/core/src/optimizer/utils.rs b/datafusion/core/src/optimizer/utils.rs index 48855df9f8e8..2c56b5f893c3 100644 --- a/datafusion/core/src/optimizer/utils.rs +++ b/datafusion/core/src/optimizer/utils.rs @@ -36,6 +36,7 @@ use crate::{ logical_plan::ExpressionVisitor, }; use datafusion_common::DFSchema; +use datafusion_expr::expr::GroupingSet; use std::{collections::HashSet, sync::Arc}; const CASE_EXPR_MARKER: &str = "__DATAFUSION_CASE_EXPR__"; @@ -83,6 +84,7 @@ impl ExpressionVisitor for ColumnNameVisitor<'_> { | Expr::ScalarUDF { .. } | Expr::WindowFunction { .. } | Expr::AggregateFunction { .. } + | Expr::GroupingSet(_) | Expr::AggregateUDF { .. } | Expr::InList { .. } | Expr::Exists { .. } @@ -323,6 +325,13 @@ pub fn expr_sub_expressions(expr: &Expr) -> Result> { | Expr::ScalarUDF { args, .. } | Expr::AggregateFunction { args, .. } | Expr::AggregateUDF { args, .. } => Ok(args.clone()), + Expr::GroupingSet(grouping_set) => match grouping_set { + GroupingSet::Rollup(exprs) => Ok(exprs.clone()), + GroupingSet::Cube(exprs) => Ok(exprs.clone()), + GroupingSet::GroupingSets(_) => Err(DataFusionError::Plan( + "GroupingSets are not supported yet".to_string(), + )), + }, Expr::WindowFunction { args, partition_by, @@ -458,6 +467,17 @@ pub fn rewrite_expression(expr: &Expr, expressions: &[Expr]) -> Result { fun: fun.clone(), args: expressions.to_vec(), }), + Expr::GroupingSet(grouping_set) => match grouping_set { + GroupingSet::Rollup(_exprs) => { + Ok(Expr::GroupingSet(GroupingSet::Rollup(expressions.to_vec()))) + } + GroupingSet::Cube(_exprs) => { + Ok(Expr::GroupingSet(GroupingSet::Rollup(expressions.to_vec()))) + } + GroupingSet::GroupingSets(_) => Err(DataFusionError::Plan( + "GroupingSets are not supported yet".to_string(), + )), + }, Expr::Case { .. } => { let mut base_expr: Option> = None; let mut when_then: Vec<(Box, Box)> = vec![]; diff --git a/datafusion/core/src/physical_plan/planner.rs b/datafusion/core/src/physical_plan/planner.rs index 85fb7d424fac..f6b3842f243e 100644 --- a/datafusion/core/src/physical_plan/planner.rs +++ b/datafusion/core/src/physical_plan/planner.rs @@ -62,6 +62,7 @@ use arrow::compute::SortOptions; use arrow::datatypes::{Schema, SchemaRef}; use arrow::{compute::can_cast_types, datatypes::DataType}; use async_trait::async_trait; +use datafusion_expr::expr::GroupingSet; use datafusion_physical_expr::expressions::DateIntervalExpr; use futures::future::BoxFuture; use futures::{FutureExt, StreamExt, TryStreamExt}; @@ -174,6 +175,37 @@ fn create_physical_name(e: &Expr, is_first_expr: bool) -> Result { } Ok(format!("{}({})", fun.name, names.join(","))) } + Expr::GroupingSet(grouping_set) => match grouping_set { + GroupingSet::Rollup(exprs) => Ok(format!( + "ROLLUP ({})", + exprs + .iter() + .map(|e| create_physical_name(e, false)) + .collect::>>()? + .join(", ") + )), + GroupingSet::Cube(exprs) => Ok(format!( + "CUBE ({})", + exprs + .iter() + .map(|e| create_physical_name(e, false)) + .collect::>>()? + .join(", ") + )), + GroupingSet::GroupingSets(lists_of_exprs) => { + let mut strings = vec![]; + for exprs in lists_of_exprs { + let exprs_str = exprs + .iter() + .map(|e| create_physical_name(e, false)) + .collect::>>()? + .join(", "); + strings.push(format!("({})", exprs_str)); + } + Ok(format!("GROUPING SETS ({})", strings.join(", "))) + } + }, + Expr::InList { expr, list, diff --git a/datafusion/core/src/sql/planner.rs b/datafusion/core/src/sql/planner.rs index d4002997b79b..b223c91ce0a8 100644 --- a/datafusion/core/src/sql/planner.rs +++ b/datafusion/core/src/sql/planner.rs @@ -37,7 +37,7 @@ use crate::logical_plan::{ use crate::optimizer::utils::exprlist_to_columns; use crate::prelude::JoinType; use crate::scalar::ScalarValue; -use crate::sql::utils::{make_decimal_type, normalize_ident}; +use crate::sql::utils::{make_decimal_type, normalize_ident, resolve_columns}; use crate::{ error::{DataFusionError, Result}, physical_plan::aggregates, @@ -50,6 +50,7 @@ use datafusion_expr::{window_function::WindowFunction, BuiltinScalarFunction}; use hashbrown::HashMap; use datafusion_common::field_not_found; +use datafusion_expr::expr::GroupingSet; use datafusion_expr::logical_plan::{Filter, Subquery}; use sqlparser::ast::{ BinaryOperator, DataType as SQLDataType, DateTimeField, Expr as SQLExpr, FunctionArg, @@ -1144,30 +1145,55 @@ impl<'a, S: ContextProvider> SqlToRel<'a, S> { group_by_exprs: Vec, aggr_exprs: Vec, ) -> Result<(LogicalPlan, Vec, Option)> { - let aggr_projection_exprs = group_by_exprs - .iter() - .chain(aggr_exprs.iter()) - .cloned() - .collect::>(); - + // create the aggregate plan let plan = LogicalPlanBuilder::from(input.clone()) - .aggregate(group_by_exprs, aggr_exprs)? + .aggregate(group_by_exprs.clone(), aggr_exprs.clone())? .build()?; - // After aggregation, these are all of the columns that will be - // available to next phases of planning. + // in this next section of code we are re-writing the projection to refer to columns + // output by the aggregate plan. For example, if the projection contains the expression + // `SUM(a)` then we replace that with a reference to a column `#SUM(a)` produced by + // the aggregate plan. + + // combine the original grouping and aggregate expressions into one list (note that + // we do not add the "having" expression since that is not part of the projection) + let mut aggr_projection_exprs = vec![]; + for expr in &group_by_exprs { + // TODO this needs to be recursive to handle GROUP BY function(arg, ..) + match expr { + Expr::GroupingSet(GroupingSet::Rollup(exprs)) => { + aggr_projection_exprs.extend_from_slice(&exprs) + } + Expr::GroupingSet(GroupingSet::Cube(exprs)) => { + aggr_projection_exprs.extend_from_slice(&exprs) + } + Expr::GroupingSet(GroupingSet::GroupingSets(_)) => todo!(), + _ => aggr_projection_exprs.push(expr.clone()), + } + } + aggr_projection_exprs.extend_from_slice(&aggr_exprs); + + // now attempt to resolve columns and replace with fully-qualified columns + let aggr_projection_exprs = aggr_projection_exprs + .iter() + .map(|expr| resolve_columns(expr, &input)) + .collect::>>()?; + + // next we replace any expressions that are not a column with a column referencing + // an output column from the aggregate schema let column_exprs_post_aggr = aggr_projection_exprs .iter() .map(|expr| expr_as_column_expr(expr, &input)) .collect::>>()?; - // Rewrite the SELECT expression to use the columns produced by the - // aggregation. + // next we re-write the projection let select_exprs_post_aggr = select_exprs .iter() .map(|expr| rebase_expr(expr, &aggr_projection_exprs, &input)) .collect::>>()?; + // finally, we have some validation that the re-written projection can be resolved + // from the aggregate output columns check_columns_satisfy_exprs( &column_exprs_post_aggr, &select_exprs_post_aggr, @@ -1862,7 +1888,18 @@ impl<'a, S: ContextProvider> SqlToRel<'a, S> { normalize_ident(&function.name.0[0]) }; - // first, scalar built-in + // first, check SQL reserved words + if name == "rollup" { + let args = self.function_args_to_expr(function.args, schema)?; + return Ok(Expr::GroupingSet(GroupingSet::Rollup(args))); + } else if name == "cube" { + let args = self.function_args_to_expr(function.args, schema)?; + return Ok(Expr::GroupingSet(GroupingSet::Cube(args))); + } else if name == "grouping sets" { // TODO is not possible to hit this case yet + todo!() + } + + // next, scalar built-in if let Ok(fun) = BuiltinScalarFunction::from_str(&name) { let args = self.function_args_to_expr(function.args, schema)?; @@ -2560,7 +2597,7 @@ mod tests { #[test] fn select_no_relation() { - quick_test( + assert_expected_plan( "SELECT 1", "Projection: Int64(1)\ \n EmptyRelation", @@ -2569,7 +2606,7 @@ mod tests { #[test] fn test_real_f32() { - quick_test( + assert_expected_plan( "SELECT CAST(1.1 AS REAL)", "Projection: CAST(Float64(1.1) AS Float32)\ \n EmptyRelation", @@ -2605,7 +2642,7 @@ mod tests { #[test] fn select_wildcard_with_repeated_column_but_is_aliased() { - quick_test( + assert_expected_plan( "SELECT *, first_name AS fn from person", "Projection: #person.id, #person.first_name, #person.last_name, #person.age, #person.state, #person.salary, #person.birth_date, #person.😀, #person.first_name AS fn\ \n TableScan: person projection=None", @@ -2614,7 +2651,7 @@ mod tests { #[test] fn select_scalar_func_with_literal_no_relation() { - quick_test( + assert_expected_plan( "SELECT sqrt(9)", "Projection: sqrt(Int64(9))\ \n EmptyRelation", @@ -2628,7 +2665,7 @@ mod tests { let expected = "Projection: #person.id, #person.first_name, #person.last_name\ \n Filter: #person.state = Utf8(\"CO\")\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -2652,7 +2689,7 @@ mod tests { let expected = "Projection: #person.id, #person.first_name, #person.last_name\ \n Filter: NOT #person.state\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -2662,7 +2699,7 @@ mod tests { let expected = "Projection: #person.id, #person.first_name, #person.last_name\ \n Filter: #person.state = Utf8(\"CO\") AND #person.age >= Int64(21) AND #person.age <= Int64(65)\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -2674,7 +2711,7 @@ mod tests { \n Filter: #person.birth_date < CAST(Int64(158412331400600000) AS Timestamp(Nanosecond, None))\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -2686,7 +2723,7 @@ mod tests { \n Filter: #person.birth_date < CAST(Utf8(\"2020-01-01\") AS Date32)\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -2707,7 +2744,7 @@ mod tests { AND #person.age < Int64(65) \ AND #person.age <= Int64(65)\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -2717,7 +2754,7 @@ mod tests { \n Filter: #person.age BETWEEN Int64(21) AND Int64(65)\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -2727,7 +2764,7 @@ mod tests { \n Filter: #person.age NOT BETWEEN Int64(21) AND Int64(65)\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -2746,7 +2783,7 @@ mod tests { \n Projection: #a.fn1, #a.last_name, #a.birth_date, #a.age, alias=a\ \n Projection: #person.first_name AS fn1, #person.last_name, #person.birth_date, #person.age, alias=a\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -2766,7 +2803,7 @@ mod tests { \n Filter: #person.age > Int64(20)\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -2777,7 +2814,7 @@ mod tests { \n Projection: #l.l_item_id AS a, #l.l_description AS b, #l.price AS c, alias=l\ \n SubqueryAlias: l\ \n TableScan: lineitem projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -2799,7 +2836,7 @@ mod tests { let expected = "Projection: #person.id, #person.age\ \n Filter: #person.age > Int64(100) AND #person.age < Int64(200)\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -2849,7 +2886,7 @@ mod tests { \n Filter: #MAX(person.age) < Int64(30)\ \n Aggregate: groupBy=[[]], aggr=[[MAX(#person.age)]]\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -2861,7 +2898,7 @@ mod tests { \n Filter: #MAX(person.first_name) > Utf8(\"M\")\ \n Aggregate: groupBy=[[]], aggr=[[MAX(#person.age), MAX(#person.first_name)]]\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -2888,7 +2925,7 @@ mod tests { \n Filter: #MAX(person.age) < Int64(30)\ \n Aggregate: groupBy=[[]], aggr=[[MAX(#person.age)]]\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -2900,7 +2937,7 @@ mod tests { \n Filter: #MAX(person.age) < Int64(30)\ \n Aggregate: groupBy=[[]], aggr=[[MAX(#person.age)]]\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -2913,7 +2950,7 @@ mod tests { \n Filter: #person.first_name = Utf8(\"M\")\ \n Aggregate: groupBy=[[#person.first_name]], aggr=[[MAX(#person.age)]]\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -2928,7 +2965,7 @@ mod tests { \n Aggregate: groupBy=[[#person.first_name]], aggr=[[MAX(#person.age)]]\ \n Filter: #person.id > Int64(5)\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -2944,7 +2981,7 @@ mod tests { \n Aggregate: groupBy=[[#person.first_name]], aggr=[[MAX(#person.age)]]\ \n Filter: #person.id > Int64(5) AND #person.age > Int64(18)\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -2957,7 +2994,7 @@ mod tests { \n Filter: #MAX(person.age) > Int64(2) AND #person.first_name = Utf8(\"M\")\ \n Aggregate: groupBy=[[#person.first_name]], aggr=[[MAX(#person.age)]]\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -2971,7 +3008,7 @@ mod tests { \n Filter: #MAX(person.age) > Int64(2) AND #MAX(person.age) < Int64(5) AND #person.first_name = Utf8(\"M\") AND #person.first_name = Utf8(\"N\")\ \n Aggregate: groupBy=[[#person.first_name]], aggr=[[MAX(#person.age)]]\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -2984,7 +3021,7 @@ mod tests { \n Filter: #MAX(person.age) > Int64(100)\ \n Aggregate: groupBy=[[#person.first_name]], aggr=[[MAX(#person.age)]]\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3012,7 +3049,7 @@ mod tests { \n Filter: #MAX(person.age) > Int64(100) AND #MAX(person.age) < Int64(200)\ \n Aggregate: groupBy=[[#person.first_name]], aggr=[[MAX(#person.age)]]\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3025,7 +3062,7 @@ mod tests { \n Filter: #MAX(person.age) > Int64(100) AND #MIN(person.id) < Int64(50)\ \n Aggregate: groupBy=[[#person.first_name]], aggr=[[MAX(#person.age), MIN(#person.id)]]\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3039,7 +3076,7 @@ mod tests { \n Filter: #MAX(person.age) > Int64(100)\ \n Aggregate: groupBy=[[#person.first_name]], aggr=[[MAX(#person.age)]]\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3054,7 +3091,7 @@ mod tests { \n Filter: #MAX(person.age) + Int64(1) > Int64(100)\ \n Aggregate: groupBy=[[#person.first_name]], aggr=[[MAX(#person.age)]]\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3068,7 +3105,7 @@ mod tests { \n Filter: #MAX(person.age) > Int64(100) AND #MIN(person.id - Int64(2)) < Int64(50)\ \n Aggregate: groupBy=[[#person.first_name]], aggr=[[MAX(#person.age), MIN(#person.id - Int64(2))]]\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3081,7 +3118,7 @@ mod tests { \n Filter: #MAX(person.age) > Int64(100) AND #COUNT(UInt8(1)) < Int64(50)\ \n Aggregate: groupBy=[[#person.first_name]], aggr=[[MAX(#person.age), COUNT(UInt8(1))]]\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3089,7 +3126,7 @@ mod tests { let sql = "SELECT age + salary from person"; let expected = "Projection: #person.age + #person.salary\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3097,18 +3134,18 @@ mod tests { let sql = "SELECT (age + salary)/2 from person"; let expected = "Projection: #person.age + #person.salary / Int64(2)\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] fn select_wildcard_with_groupby() { - quick_test( + assert_expected_plan( r#"SELECT * FROM person GROUP BY id, first_name, last_name, age, state, salary, birth_date, "😀""#, "Projection: #person.id, #person.first_name, #person.last_name, #person.age, #person.state, #person.salary, #person.birth_date, #person.😀\ \n Aggregate: groupBy=[[#person.id, #person.first_name, #person.last_name, #person.age, #person.state, #person.salary, #person.birth_date, #person.😀]], aggr=[[]]\ \n TableScan: person projection=None", ); - quick_test( + assert_expected_plan( "SELECT * FROM (SELECT first_name, last_name FROM person) AS a GROUP BY first_name, last_name", "Projection: #a.first_name, #a.last_name\ \n Aggregate: groupBy=[[#a.first_name, #a.last_name]], aggr=[[]]\ @@ -3120,7 +3157,7 @@ mod tests { #[test] fn select_simple_aggregate() { - quick_test( + assert_expected_plan( "SELECT MIN(age) FROM person", "Projection: #MIN(person.age)\ \n Aggregate: groupBy=[[]], aggr=[[MIN(#person.age)]]\ @@ -3130,7 +3167,7 @@ mod tests { #[test] fn test_sum_aggregate() { - quick_test( + assert_expected_plan( "SELECT SUM(age) from person", "Projection: #SUM(person.age)\ \n Aggregate: groupBy=[[]], aggr=[[SUM(#person.age)]]\ @@ -3157,7 +3194,7 @@ mod tests { #[test] fn select_simple_aggregate_repeated_aggregate_with_single_alias() { - quick_test( + assert_expected_plan( "SELECT MIN(age), MIN(age) AS a FROM person", "Projection: #MIN(person.age), #MIN(person.age) AS a\ \n Aggregate: groupBy=[[]], aggr=[[MIN(#person.age)]]\ @@ -3167,7 +3204,7 @@ mod tests { #[test] fn select_simple_aggregate_repeated_aggregate_with_unique_aliases() { - quick_test( + assert_expected_plan( "SELECT MIN(age) AS a, MIN(age) AS b FROM person", "Projection: #MIN(person.age) AS a, #MIN(person.age) AS b\ \n Aggregate: groupBy=[[]], aggr=[[MIN(#person.age)]]\ @@ -3187,7 +3224,7 @@ mod tests { #[test] fn select_simple_aggregate_with_groupby() { - quick_test( + assert_expected_plan( "SELECT state, MIN(age), MAX(age) FROM person GROUP BY state", "Projection: #person.state, #MIN(person.age), #MAX(person.age)\ \n Aggregate: groupBy=[[#person.state]], aggr=[[MIN(#person.age), MAX(#person.age)]]\ @@ -3197,7 +3234,7 @@ mod tests { #[test] fn select_simple_aggregate_with_groupby_with_aliases() { - quick_test( + assert_expected_plan( "SELECT state AS a, MIN(age) AS b FROM person GROUP BY state", "Projection: #person.state AS a, #MIN(person.age) AS b\ \n Aggregate: groupBy=[[#person.state]], aggr=[[MIN(#person.age)]]\ @@ -3217,7 +3254,7 @@ mod tests { #[test] fn select_simple_aggregate_with_groupby_column_unselected() { - quick_test( + assert_expected_plan( "SELECT MIN(age), MAX(age) FROM person GROUP BY state", "Projection: #MIN(person.age), #MAX(person.age)\ \n Aggregate: groupBy=[[#person.state]], aggr=[[MIN(#person.age), MAX(#person.age)]]\ @@ -3275,7 +3312,7 @@ mod tests { #[test] fn select_simple_aggregate_with_groupby_and_column_is_in_aggregate_and_groupby() { - quick_test( + assert_expected_plan( "SELECT MAX(first_name) FROM person GROUP BY first_name", "Projection: #MAX(person.first_name)\ \n Aggregate: groupBy=[[#person.first_name]], aggr=[[MAX(#person.first_name)]]\ @@ -3285,13 +3322,13 @@ mod tests { #[test] fn select_simple_aggregate_with_groupby_can_use_positions() { - quick_test( + assert_expected_plan( "SELECT state, age AS b, COUNT(1) FROM person GROUP BY 1, 2", "Projection: #person.state, #person.age AS b, #COUNT(UInt8(1))\ \n Aggregate: groupBy=[[#person.state, #person.age]], aggr=[[COUNT(UInt8(1))]]\ \n TableScan: person projection=None", ); - quick_test( + assert_expected_plan( "SELECT state, age AS b, COUNT(1) FROM person GROUP BY 2, 1", "Projection: #person.state, #person.age AS b, #COUNT(UInt8(1))\ \n Aggregate: groupBy=[[#person.age, #person.state]], aggr=[[COUNT(UInt8(1))]]\ @@ -3318,7 +3355,7 @@ mod tests { #[test] fn select_simple_aggregate_with_groupby_can_use_alias() { - quick_test( + assert_expected_plan( "SELECT state AS a, MIN(age) AS b FROM person GROUP BY a", "Projection: #person.state AS a, #MIN(person.age) AS b\ \n Aggregate: groupBy=[[#person.state]], aggr=[[MIN(#person.age)]]\ @@ -3338,7 +3375,7 @@ mod tests { #[test] fn select_simple_aggregate_with_groupby_aggregate_repeated_and_one_has_alias() { - quick_test( + assert_expected_plan( "SELECT state, MIN(age), MIN(age) AS ma FROM person GROUP BY state", "Projection: #person.state, #MIN(person.age), #MIN(person.age) AS ma\ \n Aggregate: groupBy=[[#person.state]], aggr=[[MIN(#person.age)]]\ @@ -3348,7 +3385,7 @@ mod tests { #[test] fn select_simple_aggregate_with_groupby_non_column_expression_unselected() { - quick_test( + assert_expected_plan( "SELECT MIN(first_name) FROM person GROUP BY age + 1", "Projection: #MIN(person.first_name)\ \n Aggregate: groupBy=[[#person.age + Int64(1)]], aggr=[[MIN(#person.first_name)]]\ @@ -3359,13 +3396,13 @@ mod tests { #[test] fn select_simple_aggregate_with_groupby_non_column_expression_selected_and_resolvable( ) { - quick_test( + assert_expected_plan( "SELECT age + 1, MIN(first_name) FROM person GROUP BY age + 1", "Projection: #person.age + Int64(1), #MIN(person.first_name)\ \n Aggregate: groupBy=[[#person.age + Int64(1)]], aggr=[[MIN(#person.first_name)]]\ \n TableScan: person projection=None", ); - quick_test( + assert_expected_plan( "SELECT MIN(first_name), age + 1 FROM person GROUP BY age + 1", "Projection: #MIN(person.first_name), #person.age + Int64(1)\ \n Aggregate: groupBy=[[#person.age + Int64(1)]], aggr=[[MIN(#person.first_name)]]\ @@ -3376,7 +3413,7 @@ mod tests { #[test] fn select_simple_aggregate_with_groupby_non_column_expression_nested_and_resolvable() { - quick_test( + assert_expected_plan( "SELECT ((age + 1) / 2) * (age + 1), MIN(first_name) FROM person GROUP BY age + 1", "Projection: #person.age + Int64(1) / Int64(2) * #person.age + Int64(1), #MIN(person.first_name)\ \n Aggregate: groupBy=[[#person.age + Int64(1)]], aggr=[[MIN(#person.first_name)]]\ @@ -3409,7 +3446,7 @@ mod tests { #[test] fn select_simple_aggregate_nested_in_binary_expr_with_groupby() { - quick_test( + assert_expected_plan( "SELECT state, MIN(age) < 10 FROM person GROUP BY state", "Projection: #person.state, #MIN(person.age) < Int64(10)\ \n Aggregate: groupBy=[[#person.state]], aggr=[[MIN(#person.age)]]\ @@ -3419,7 +3456,7 @@ mod tests { #[test] fn select_simple_aggregate_and_nested_groupby_column() { - quick_test( + assert_expected_plan( "SELECT age + 1, MAX(first_name) FROM person GROUP BY age", "Projection: #person.age + Int64(1), #MAX(person.first_name)\ \n Aggregate: groupBy=[[#person.age]], aggr=[[MAX(#person.first_name)]]\ @@ -3429,7 +3466,7 @@ mod tests { #[test] fn select_aggregate_compounded_with_groupby_column() { - quick_test( + assert_expected_plan( "SELECT age + MIN(salary) FROM person GROUP BY age", "Projection: #person.age + #MIN(person.salary)\ \n Aggregate: groupBy=[[#person.age]], aggr=[[MIN(#person.salary)]]\ @@ -3439,7 +3476,7 @@ mod tests { #[test] fn select_aggregate_with_non_column_inner_expression_with_groupby() { - quick_test( + assert_expected_plan( "SELECT state, MIN(age + 1) FROM person GROUP BY state", "Projection: #person.state, #MIN(person.age + Int64(1))\ \n Aggregate: groupBy=[[#person.state]], aggr=[[MIN(#person.age + Int64(1))]]\ @@ -3449,7 +3486,7 @@ mod tests { #[test] fn test_wildcard() { - quick_test( + assert_expected_plan( "SELECT * from person", "Projection: #person.id, #person.first_name, #person.last_name, #person.age, #person.state, #person.salary, #person.birth_date, #person.😀\ \n TableScan: person projection=None", @@ -3462,7 +3499,7 @@ mod tests { let expected = "Projection: #COUNT(UInt8(1))\ \n Aggregate: groupBy=[[]], aggr=[[COUNT(UInt8(1))]]\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3471,7 +3508,7 @@ mod tests { let expected = "Projection: #COUNT(person.id)\ \n Aggregate: groupBy=[[]], aggr=[[COUNT(#person.id)]]\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3480,7 +3517,7 @@ mod tests { let expected = "Projection: #APPROXPERCENTILECONT(person.age,Float64(0.5))\ \n Aggregate: groupBy=[[]], aggr=[[APPROXPERCENTILECONT(#person.age, Float64(0.5))]]\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3488,7 +3525,7 @@ mod tests { let sql = "SELECT sqrt(age) FROM person"; let expected = "Projection: sqrt(#person.age)\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3496,7 +3533,7 @@ mod tests { let sql = "SELECT sqrt(person.age) AS square_people FROM person"; let expected = "Projection: sqrt(#person.age) AS square_people\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3506,7 +3543,7 @@ mod tests { let expected = "Projection: #aggregate_test_100.c3 / #aggregate_test_100.c4 + #aggregate_test_100.c5\ \n Filter: #aggregate_test_100.c3 / nullif(#aggregate_test_100.c4 + #aggregate_test_100.c5, Int64(0)) > Float64(0.1)\ \n TableScan: aggregate_test_100 projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3515,7 +3552,7 @@ mod tests { let expected = "Projection: #aggregate_test_100.c3\ \n Filter: #aggregate_test_100.c3 > Float64(-0.1) AND (- #aggregate_test_100.c4) > Int64(0)\ \n TableScan: aggregate_test_100 projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3524,7 +3561,7 @@ mod tests { let expected = "Projection: #aggregate_test_100.c3\ \n Filter: #aggregate_test_100.c3 > Float64(0.1) AND #aggregate_test_100.c4 > Int64(0)\ \n TableScan: aggregate_test_100 projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3534,7 +3571,7 @@ mod tests { \n Projection: #person.id\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3544,7 +3581,7 @@ mod tests { \n Projection: #person.id, #person.state, #person.age\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3573,7 +3610,7 @@ mod tests { let expected = "Sort: #person.id ASC NULLS LAST\ \n Projection: #person.id\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3582,19 +3619,19 @@ mod tests { let expected = "Sort: #person.id DESC NULLS FIRST\ \n Projection: #person.id\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] fn select_order_by_nulls_last() { - quick_test( + assert_expected_plan( "SELECT id FROM person ORDER BY id DESC NULLS LAST", "Sort: #person.id DESC NULLS LAST\ \n Projection: #person.id\ \n TableScan: person projection=None", ); - quick_test( + assert_expected_plan( "SELECT id FROM person ORDER BY id NULLS LAST", "Sort: #person.id ASC NULLS LAST\ \n Projection: #person.id\ @@ -3609,7 +3646,7 @@ mod tests { \n Aggregate: groupBy=[[#person.state]], aggr=[[]]\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3619,7 +3656,7 @@ mod tests { \n Aggregate: groupBy=[[#person.state]], aggr=[[MAX(#person.age)]]\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3629,7 +3666,7 @@ mod tests { \n Aggregate: groupBy=[[#person.state]], aggr=[[COUNT(UInt8(1))]]\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3640,7 +3677,7 @@ mod tests { \n Aggregate: groupBy=[[#person.state]], aggr=[[COUNT(#person.state)]]\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3649,7 +3686,7 @@ mod tests { let expected = "Projection: #aggregate_test_100.c1, #MIN(aggregate_test_100.c12)\ \n Aggregate: groupBy=[[#aggregate_test_100.c1, #aggregate_test_100.c13]], aggr=[[MIN(#aggregate_test_100.c12)]]\ \n TableScan: aggregate_test_100 projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3668,14 +3705,14 @@ mod tests { fn create_external_table_csv() { let sql = "CREATE EXTERNAL TABLE t(c1 int) STORED AS CSV LOCATION 'foo.csv'"; let expected = "CreateExternalTable: \"t\""; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] fn create_external_table_csv_no_schema() { let sql = "CREATE EXTERNAL TABLE t STORED AS CSV LOCATION 'foo.csv'"; let expected = "CreateExternalTable: \"t\""; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3693,7 +3730,7 @@ mod tests { fn create_external_table_parquet_no_schema() { let sql = "CREATE EXTERNAL TABLE t STORED AS PARQUET LOCATION 'foo.parquet'"; let expected = "CreateExternalTable: \"t\""; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3706,7 +3743,7 @@ mod tests { \n Inner Join: #person.id = #orders.customer_id\ \n TableScan: person projection=None\ \n TableScan: orders projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3720,7 +3757,7 @@ mod tests { \n Inner Join: #person.id = #orders.customer_id\ \n TableScan: person projection=None\ \n TableScan: orders projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3734,7 +3771,7 @@ mod tests { \n TableScan: person projection=None\ \n Filter: #orders.order_id > Int64(1)\ \n TableScan: orders projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3748,7 +3785,7 @@ mod tests { \n Filter: #person.id > Int64(1)\ \n TableScan: person projection=None\ \n TableScan: orders projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3761,7 +3798,7 @@ mod tests { \n Inner Join: #person.id = #orders.customer_id\ \n TableScan: person projection=None\ \n TableScan: orders projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3775,7 +3812,7 @@ mod tests { \n TableScan: person projection=None\ \n SubqueryAlias: person2\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3789,7 +3826,7 @@ mod tests { \n TableScan: lineitem projection=None\ \n SubqueryAlias: lineitem2\ \n TableScan: lineitem projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3805,7 +3842,7 @@ mod tests { \n TableScan: person projection=None\ \n TableScan: orders projection=None\ \n TableScan: lineitem projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3816,7 +3853,7 @@ mod tests { let expected = "Projection: #orders.order_id\ \n Filter: #orders.delivered = Boolean(false) OR #orders.delivered = Boolean(true)\ \n TableScan: orders projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3827,7 +3864,7 @@ mod tests { \n TableScan: orders projection=None\ \n Projection: #orders.order_id\ \n TableScan: orders projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3845,7 +3882,7 @@ mod tests { \n TableScan: orders projection=None\ \n Projection: #orders.order_id\ \n TableScan: orders projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3856,7 +3893,7 @@ mod tests { \n TableScan: orders projection=None\ \n Projection: #orders.customer_id\ \n TableScan: orders projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3867,7 +3904,7 @@ mod tests { \n EmptyRelation\ \n Projection: Int64(3) AS column0, Int64(4) AS column1\ \n EmptyRelation"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3890,7 +3927,7 @@ mod tests { Projection: #orders.order_id, #MAX(orders.order_id)\ \n WindowAggr: windowExpr=[[MAX(#orders.order_id)]]\ \n TableScan: orders projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3900,7 +3937,7 @@ mod tests { Projection: #orders.order_id AS oid, #MAX(orders.order_id) AS max_oid\ \n WindowAggr: windowExpr=[[MAX(#orders.order_id)]]\ \n TableScan: orders projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3910,7 +3947,7 @@ mod tests { Projection: #orders.order_id AS oid, #MAX(orders.order_id) AS max_oid, #MAX(orders.order_id) AS max_oid_dup\ \n WindowAggr: windowExpr=[[MAX(#orders.order_id)]]\ \n TableScan: orders projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3921,7 +3958,7 @@ mod tests { \n WindowAggr: windowExpr=[[MAX(#orders.order_id)]]\ \n WindowAggr: windowExpr=[[MAX(#orders.order_id) ORDER BY [#orders.order_id ASC NULLS LAST]]]\ \n TableScan: orders projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3931,7 +3968,7 @@ mod tests { Projection: #orders.order_id, #MAX(orders.qty * Float64(1.1))\ \n WindowAggr: windowExpr=[[MAX(#orders.qty * Float64(1.1))]]\ \n TableScan: orders projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3942,7 +3979,7 @@ mod tests { Projection: #orders.order_id, #MAX(orders.qty), #MIN(orders.qty), #AVG(orders.qty)\ \n WindowAggr: windowExpr=[[MAX(#orders.qty), MIN(#orders.qty), AVG(#orders.qty)]]\ \n TableScan: orders projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } /// psql result @@ -3961,7 +3998,7 @@ mod tests { Projection: #orders.order_id, #MAX(orders.qty) PARTITION BY [#orders.order_id]\ \n WindowAggr: windowExpr=[[MAX(#orders.qty) PARTITION BY [#orders.order_id]]]\ \n TableScan: orders projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } /// psql result @@ -3984,7 +4021,7 @@ mod tests { \n WindowAggr: windowExpr=[[MAX(#orders.qty) ORDER BY [#orders.order_id ASC NULLS LAST]]]\ \n WindowAggr: windowExpr=[[MIN(#orders.qty) ORDER BY [#orders.order_id DESC NULLS FIRST]]]\ \n TableScan: orders projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -3995,7 +4032,7 @@ mod tests { \n WindowAggr: windowExpr=[[MAX(#orders.qty) ORDER BY [#orders.order_id ASC NULLS LAST] ROWS BETWEEN 3 PRECEDING AND 3 FOLLOWING]]\ \n WindowAggr: windowExpr=[[MIN(#orders.qty) ORDER BY [#orders.order_id DESC NULLS FIRST]]]\ \n TableScan: orders projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -4006,7 +4043,7 @@ mod tests { \n WindowAggr: windowExpr=[[MAX(#orders.qty) ORDER BY [#orders.order_id ASC NULLS LAST] ROWS BETWEEN 3 PRECEDING AND CURRENT ROW]]\ \n WindowAggr: windowExpr=[[MIN(#orders.qty) ORDER BY [#orders.order_id DESC NULLS FIRST]]]\ \n TableScan: orders projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -4049,7 +4086,7 @@ mod tests { \n WindowAggr: windowExpr=[[MAX(#orders.qty) ORDER BY [#orders.order_id ASC NULLS LAST] GROUPS BETWEEN 3 PRECEDING AND CURRENT ROW]]\ \n WindowAggr: windowExpr=[[MIN(#orders.qty) ORDER BY [#orders.order_id DESC NULLS FIRST]]]\ \n TableScan: orders projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } /// psql result @@ -4072,7 +4109,7 @@ mod tests { \n WindowAggr: windowExpr=[[MAX(#orders.qty) ORDER BY [#orders.order_id ASC NULLS LAST]]]\ \n WindowAggr: windowExpr=[[MIN(#orders.qty) ORDER BY [#orders.order_id + Int64(1) ASC NULLS LAST]]]\ \n TableScan: orders projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } /// psql result @@ -4097,7 +4134,7 @@ mod tests { \n WindowAggr: windowExpr=[[MAX(#orders.qty) ORDER BY [#orders.qty ASC NULLS LAST, #orders.order_id ASC NULLS LAST]]]\ \n WindowAggr: windowExpr=[[MIN(#orders.qty) ORDER BY [#orders.order_id ASC NULLS LAST, #orders.qty ASC NULLS LAST]]]\ \n TableScan: orders projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } /// psql result @@ -4122,7 +4159,7 @@ mod tests { \n WindowAggr: windowExpr=[[MAX(#orders.qty) ORDER BY [#orders.order_id ASC NULLS LAST]]]\ \n WindowAggr: windowExpr=[[MIN(#orders.qty) ORDER BY [#orders.order_id ASC NULLS LAST, #orders.qty ASC NULLS LAST]]]\ \n TableScan: orders projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } /// psql result @@ -4151,7 +4188,7 @@ mod tests { \n WindowAggr: windowExpr=[[MAX(#orders.qty) ORDER BY [#orders.qty ASC NULLS LAST, #orders.order_id ASC NULLS LAST]]]\ \n WindowAggr: windowExpr=[[MIN(#orders.qty) ORDER BY [#orders.order_id ASC NULLS LAST, #orders.qty ASC NULLS LAST]]]\ \n TableScan: orders projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } /// psql result @@ -4171,7 +4208,7 @@ mod tests { Projection: #orders.order_id, #MAX(orders.qty) PARTITION BY [#orders.order_id] ORDER BY [#orders.qty ASC NULLS LAST]\ \n WindowAggr: windowExpr=[[MAX(#orders.qty) PARTITION BY [#orders.order_id] ORDER BY [#orders.qty ASC NULLS LAST]]]\ \n TableScan: orders projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } /// psql result @@ -4191,7 +4228,7 @@ mod tests { Projection: #orders.order_id, #MAX(orders.qty) PARTITION BY [#orders.order_id, #orders.qty] ORDER BY [#orders.qty ASC NULLS LAST]\ \n WindowAggr: windowExpr=[[MAX(#orders.qty) PARTITION BY [#orders.order_id, #orders.qty] ORDER BY [#orders.qty ASC NULLS LAST]]]\ \n TableScan: orders projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } /// psql result @@ -4215,7 +4252,7 @@ mod tests { \n WindowAggr: windowExpr=[[MIN(#orders.qty) PARTITION BY [#orders.qty] ORDER BY [#orders.order_id ASC NULLS LAST]]]\ \n WindowAggr: windowExpr=[[MAX(#orders.qty) PARTITION BY [#orders.order_id, #orders.qty] ORDER BY [#orders.qty ASC NULLS LAST]]]\ \n TableScan: orders projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } /// psql result @@ -4238,7 +4275,7 @@ mod tests { \n WindowAggr: windowExpr=[[MAX(#orders.qty) PARTITION BY [#orders.order_id] ORDER BY [#orders.qty ASC NULLS LAST]]]\ \n WindowAggr: windowExpr=[[MIN(#orders.qty) PARTITION BY [#orders.order_id, #orders.qty] ORDER BY [#orders.price ASC NULLS LAST]]]\ \n TableScan: orders projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -4249,7 +4286,7 @@ mod tests { Projection: #orders.order_id, #APPROXPERCENTILECONT(orders.qty,Float64(0.5)) PARTITION BY [#orders.order_id]\ \n WindowAggr: windowExpr=[[APPROXPERCENTILECONT(#orders.qty, Float64(0.5)) PARTITION BY [#orders.order_id]]]\ \n TableScan: orders projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -4257,7 +4294,7 @@ mod tests { let sql = "SELECT date '2020-12-10' AS date FROM person"; let expected = "Projection: CAST(Utf8(\"2020-12-10\") AS Date32) AS date\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -4265,7 +4302,7 @@ mod tests { let sql = r#"SELECT "😀" FROM person"#; let expected = "Projection: #person.😀\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } fn logical_plan(sql: &str) -> Result { @@ -4276,7 +4313,7 @@ mod tests { } /// Create logical plan, write with formatter, compare to expected output - fn quick_test(sql: &str, expected: &str) { + fn assert_expected_plan(sql: &str, expected: &str) { let plan = logical_plan(sql).unwrap(); assert_eq!(format!("{:?}", plan), expected); } @@ -4388,7 +4425,7 @@ mod tests { let sql = r#"SELECT person.first_name FROM public.person"#; let expected = "Projection: #public.person.first_name\ \n TableScan: public.person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -4400,7 +4437,7 @@ mod tests { \n TableScan: person projection=None\ \n TableScan: lineitem projection=None\ \n TableScan: orders projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -4413,7 +4450,7 @@ mod tests { \n TableScan: person projection=None\ \n TableScan: orders projection=None\ \n TableScan: lineitem projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -4425,7 +4462,7 @@ mod tests { \n TableScan: person projection=None\ \n SubqueryAlias: folks\ \n TableScan: person projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -4441,7 +4478,7 @@ mod tests { let sql = "select t_date32 + interval '5 days' FROM test"; let expected = "Projection: #test.t_date32 + IntervalDayTime(\"21474836480\")\ \n TableScan: test projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -4454,7 +4491,7 @@ mod tests { "Projection: #test.t_date64\ \n Filter: #test.t_date64 BETWEEN CAST(Utf8(\"1999-12-31\") AS Date32) AND CAST(Utf8(\"1999-12-31\") AS Date32) + IntervalDayTime(\"128849018880\")\ \n TableScan: test projection=None"; - quick_test(sql, expected); + assert_expected_plan(sql, expected); } #[test] @@ -4475,7 +4512,7 @@ mod tests { \n TableScan: person projection=None", subquery_expected ); - quick_test(sql, &expected); + assert_expected_plan(sql, &expected); } #[test] @@ -4504,7 +4541,7 @@ mod tests { \n TableScan: person projection=None", subquery_expected ); - quick_test(sql, &expected); + assert_expected_plan(sql, &expected); } #[test] @@ -4526,7 +4563,7 @@ mod tests { \n TableScan: person projection=None", subquery_expected ); - quick_test(sql, &expected); + assert_expected_plan(sql, &expected); } #[test] @@ -4544,7 +4581,7 @@ mod tests { \n TableScan: person projection=None", subquery_expected ); - quick_test(sql, &expected); + assert_expected_plan(sql, &expected); } #[test] @@ -4563,7 +4600,7 @@ mod tests { \n TableScan: person projection=None", subquery_expected ); - quick_test(sql, &expected); + assert_expected_plan(sql, &expected); } #[test] @@ -4581,7 +4618,7 @@ mod tests { \n TableScan: person projection=None", subquery_expected ); - quick_test(sql, &expected); + assert_expected_plan(sql, &expected); } #[test] @@ -4610,7 +4647,7 @@ mod tests { subquery ); - quick_test(sql, &expected); + assert_expected_plan(sql, &expected); } #[tokio::test] @@ -4628,7 +4665,34 @@ mod tests { \n Filter: EXISTS ({})\ \n TableScan: person projection=None", subquery); - quick_test(sql, &expected) + assert_expected_plan(sql, &expected) + } + + #[tokio::test] + async fn aggregate_with_rollup() { + let sql = "SELECT id, state, age, COUNT(*) FROM person GROUP BY id, ROLLUP (state, age)"; + let expected = "Projection: #person.id, #person.state, #person.age, #COUNT(UInt8(1))\ + \n Aggregate: groupBy=[[#person.id, ROLLUP (#person.state, #person.age)]], aggr=[[COUNT(UInt8(1))]]\ + \n TableScan: person projection=None"; + assert_expected_plan(sql, &expected); + } + + #[tokio::test] + async fn aggregate_with_cube() { + let sql = + "SELECT id, state, age, COUNT(*) FROM person GROUP BY id, CUBE (state, age)"; + let expected = "Projection: #person.id, #person.state, #person.age, #COUNT(UInt8(1))\ + \n Aggregate: groupBy=[[#person.id, CUBE (#person.state, #person.age)]], aggr=[[COUNT(UInt8(1))]]\ + \n TableScan: person projection=None"; + assert_expected_plan(sql, &expected); + } + + #[ignore] // see https://github.com/apache/arrow-datafusion/issues/2469 + #[tokio::test] + async fn aggregate_with_grouping_sets() { + let sql = "SELECT id, state, age, COUNT(*) FROM person GROUP BY id, GROUPING SETS ((state), (state, age), (id, state))"; + let expected = "TBD"; + assert_expected_plan(sql, &expected); } fn assert_field_not_found(err: DataFusionError, name: &str) { diff --git a/datafusion/core/src/sql/utils.rs b/datafusion/core/src/sql/utils.rs index cd1fb316b76d..ab1d6be36406 100644 --- a/datafusion/core/src/sql/utils.rs +++ b/datafusion/core/src/sql/utils.rs @@ -28,6 +28,7 @@ use crate::{ logical_plan::{Column, ExpressionVisitor, Recursion}, }; use datafusion_expr::expr::find_columns_referenced_by_expr; +use datafusion_expr::expr::GroupingSet; use std::collections::HashMap; /// Collect all deeply nested `Expr::AggregateFunction` and @@ -155,6 +156,22 @@ pub(crate) fn expr_as_column_expr(expr: &Expr, plan: &LogicalPlan) -> Result Result { + clone_with_replacement(expr, &|nested_expr| { + match nested_expr { + Expr::Column(col) => { + let field = plan.schema().field_from_column(col)?; + Ok(Some(Expr::Column(field.qualified_column()))) + } + _ => { + // keep recursing + Ok(None) + } + } + }) +} + /// Rebuilds an `Expr` as a projection on top of a collection of `Expr`'s. /// /// For example, the expression `a + b < 1` would require, as input, the 2 @@ -196,22 +213,49 @@ pub(crate) fn check_columns_satisfy_exprs( "Expr::Column are required".to_string(), )), })?; - - for e in &find_column_exprs(exprs) { - if !columns.contains(e) { - return Err(DataFusionError::Plan(format!( - "{}: Expression {:?} could not be resolved from available columns: {}", - message_prefix, - e, - columns - .iter() - .map(|e| format!("{}", e)) - .collect::>() - .join(", ") - ))); + let column_exprs = find_column_exprs(exprs); + for e in &column_exprs { + match e { + Expr::GroupingSet(GroupingSet::Rollup(exprs)) => { + for e in exprs { + check_column_satisfies_expr(columns, e, message_prefix)?; + } + } + Expr::GroupingSet(GroupingSet::Cube(exprs)) => { + for e in exprs { + check_column_satisfies_expr(columns, e, message_prefix)?; + } + } + Expr::GroupingSet(GroupingSet::GroupingSets(lists_of_exprs)) => { + for exprs in lists_of_exprs { + for e in exprs { + check_column_satisfies_expr(columns, e, message_prefix)?; + } + } + } + _ => check_column_satisfies_expr(columns, e, message_prefix)?, } } + Ok(()) +} +fn check_column_satisfies_expr( + columns: &[Expr], + expr: &Expr, + message_prefix: &str, +) -> Result<()> { + if !columns.contains(expr) { + return Err(DataFusionError::Plan(format!( + "{}: Expression {:?} could not be resolved from available columns: {}", + message_prefix, + expr, + columns + .iter() + .map(|e| format!("{}", e)) + .collect::>() + .join(", ") + ))); + } Ok(()) } @@ -417,6 +461,10 @@ where expr: Box::new(clone_with_replacement(expr.as_ref(), replacement_fn)?), key: key.clone(), }), + Expr::GroupingSet(_) => { + //TODO ??? + Ok(expr.clone()) + } }, } } diff --git a/datafusion/core/tests/sql/group_by.rs b/datafusion/core/tests/sql/group_by.rs index 41f2471f6c9e..e3da1b02195a 100644 --- a/datafusion/core/tests/sql/group_by.rs +++ b/datafusion/core/tests/sql/group_by.rs @@ -211,6 +211,32 @@ async fn csv_query_having_without_group_by() -> Result<()> { Ok(()) } +#[tokio::test] +async fn csv_query_group_by_substr() -> Result<()> { + let ctx = SessionContext::new(); + register_aggregate_csv(&ctx).await?; + // there is an input column "c1" as well a projection expression aliased as "c1" + let sql = "SELECT substr(c1, 1, 1) c1 \ + FROM aggregate_test_100 \ + GROUP BY substr(c1, 1, 1) \ + "; + let actual = execute_to_batches(&ctx, sql).await; + #[rustfmt::skip] + let expected = vec![ + "+----+", + "| c1 |", + "+----+", + "| a |", + "| b |", + "| c |", + "| d |", + "| e |", + "+----+", + ]; + assert_batches_sorted_eq!(expected, &actual); + Ok(()) +} + #[tokio::test] async fn csv_query_group_by_avg() -> Result<()> { let ctx = SessionContext::new(); diff --git a/datafusion/expr/src/expr.rs b/datafusion/expr/src/expr.rs index 7e1adac430b0..6cf9b54e1303 100644 --- a/datafusion/expr/src/expr.rs +++ b/datafusion/expr/src/expr.rs @@ -249,6 +249,24 @@ pub enum Expr { Wildcard, /// Represents a reference to all fields in a specific schema. QualifiedWildcard { qualifier: String }, + /// List of grouping set expressions. Only valid in the context of an aggregate + /// GROUP BY expression list + GroupingSet(GroupingSet), +} + +/// Grouping sets +/// See https://www.postgresql.org/docs/current/queries-table-expressions.html#QUERIES-GROUPING-SETS +/// for Postgres definition. +/// See https://spark.apache.org/docs/latest/sql-ref-syntax-qry-select-groupby.html +/// for Apache Spark definition. +#[derive(Clone, PartialEq, Hash)] +pub enum GroupingSet { + /// Rollup grouping sets + Rollup(Vec), + /// Cube grouping sets + Cube(Vec), + /// User-defined grouping sets + GroupingSets(Vec>), } /// Recursively find all columns referenced by an expression @@ -319,6 +337,21 @@ pub fn find_columns_referenced_by_expr(e: &Expr) -> Vec { .iter() .flat_map(find_columns_referenced_by_expr) .collect(), + Expr::GroupingSet(set) => match set { + GroupingSet::Rollup(exprs) | GroupingSet::Cube(exprs) => exprs + .iter() + .flat_map(find_columns_referenced_by_expr) + .collect(), + GroupingSet::GroupingSets(lists_of_exprs) => { + let mut cols = vec![]; + for exprs in lists_of_exprs { + for expr in exprs { + cols.extend(find_columns_referenced_by_expr(expr)); + } + } + cols + } + }, } } @@ -627,6 +660,51 @@ impl fmt::Debug for Expr { Expr::GetIndexedField { ref expr, key } => { write!(f, "({:?})[{}]", expr, key) } + Expr::GroupingSet(grouping_sets) => match grouping_sets { + GroupingSet::Rollup(exprs) => { + // ROLLUP (c0, c1, c2) + write!( + f, + "ROLLUP ({})", + exprs + .iter() + .map(|e| format!("{}", e)) + .collect::>() + .join(", ") + ) + } + GroupingSet::Cube(exprs) => { + // CUBE (c0, c1, c2) + write!( + f, + "CUBE ({})", + exprs + .iter() + .map(|e| format!("{}", e)) + .collect::>() + .join(", ") + ) + } + GroupingSet::GroupingSets(lists_of_exprs) => { + // GROUPING SETS ((c0), (c1, c2), (c3, c4)) + write!( + f, + "GROUPING SETS ({})", + lists_of_exprs + .iter() + .map(|exprs| format!( + "({})", + exprs + .iter() + .map(|e| format!("{}", e)) + .collect::>() + .join(", ") + )) + .collect::>() + .join(", ") + ) + } + }, } } } @@ -781,6 +859,23 @@ fn create_name(e: &Expr, input_schema: &DFSchema) -> Result { } Ok(format!("{}({})", fun.name, names.join(","))) } + Expr::GroupingSet(grouping_set) => match grouping_set { + GroupingSet::Rollup(exprs) => Ok(format!( + "ROLLUP ({})", + create_names(exprs.as_slice(), input_schema)? + )), + GroupingSet::Cube(exprs) => Ok(format!( + "CUBE ({})", + create_names(exprs.as_slice(), input_schema)? + )), + GroupingSet::GroupingSets(_list_of_exprs) => { + todo!() + // Ok(format!( + // "GROUPING SETS ({})", + // list_of_exprs.iter().map(|exprs| format!("({})", create_names(exprs.as_slice(), input_schema))).collect::>>()?.join(", ") + // )) + } + }, Expr::InList { expr, list, @@ -821,6 +916,15 @@ fn create_name(e: &Expr, input_schema: &DFSchema) -> Result { } } +/// Create a comma separated list of names from a list of expressions +fn create_names(exprs: &[Expr], input_schema: &DFSchema) -> Result { + Ok(exprs + .iter() + .map(|e| create_name(e, input_schema)) + .collect::>>()? + .join(", ")) +} + #[cfg(test)] mod test { use crate::expr_fn::col; diff --git a/datafusion/expr/src/expr_schema.rs b/datafusion/expr/src/expr_schema.rs index b932eefa0b96..9de68f07caa4 100644 --- a/datafusion/expr/src/expr_schema.rs +++ b/datafusion/expr/src/expr_schema.rs @@ -124,6 +124,10 @@ impl ExprSchemable for Expr { "QualifiedWildcard expressions are not valid in a logical query plan" .to_owned(), )), + Expr::GroupingSet(_) => { + // TODO grouping sets do not really have a type + Ok(DataType::Null) + } Expr::GetIndexedField { ref expr, key } => { let data_type = expr.get_type(schema)?; @@ -198,6 +202,10 @@ impl ExprSchemable for Expr { let data_type = expr.get_type(input_schema)?; get_indexed_field(&data_type, key).map(|x| x.is_nullable()) } + Expr::GroupingSet(_) => { + // TODO grouping sets do not really have the concept of nullable + Ok(true) + } } } diff --git a/datafusion/expr/src/logical_plan/plan.rs b/datafusion/expr/src/logical_plan/plan.rs index 579898dbe207..3201e5f0437c 100644 --- a/datafusion/expr/src/logical_plan/plan.rs +++ b/datafusion/expr/src/logical_plan/plan.rs @@ -15,11 +15,12 @@ // specific language governing permissions and limitations // under the License. +use crate::expr::find_columns_referenced_by_expr; use crate::logical_plan::display::{GraphvizVisitor, IndentVisitor}; use crate::logical_plan::extension::UserDefinedLogicalNode; use crate::{Expr, TableProviderFilterPushDown, TableSource}; use arrow::datatypes::{DataType, Field, Schema, SchemaRef}; -use datafusion_common::{Column, DFSchemaRef, DataFusionError}; +use datafusion_common::{Column, DFSchemaRef, DataFusionError, Result}; use std::collections::HashSet; ///! Logical plan types use std::fmt::{self, Debug, Display, Formatter}; @@ -281,7 +282,7 @@ impl LogicalPlan { } /// returns all `Using` join columns in a logical plan - pub fn using_columns(&self) -> Result>, DataFusionError> { + pub fn using_columns(&self) -> Result>> { struct UsingJoinColumnVisitor { using_columns: Vec>, } @@ -289,7 +290,10 @@ impl LogicalPlan { impl PlanVisitor for UsingJoinColumnVisitor { type Error = DataFusionError; - fn pre_visit(&mut self, plan: &LogicalPlan) -> Result { + fn pre_visit( + &mut self, + plan: &LogicalPlan, + ) -> std::result::Result { if let LogicalPlan::Join(Join { join_constraint: JoinConstraint::Using, on, @@ -1126,6 +1130,19 @@ pub struct Aggregate { pub schema: DFSchemaRef, } +impl Aggregate { + /// Return all columns referenced in the grouping expressions + pub fn columns_in_group_expr(&self) -> Result> { + let mut cols = vec![]; + for e in &self.group_expr { + for col in find_columns_referenced_by_expr(e) { + cols.push(col) + } + } + Ok(cols) + } +} + /// Sorts its input according to a list of sort expressions. #[derive(Clone)] pub struct Sort { From 969910753540701d8a8e473d7821d28725d9d050 Mon Sep 17 00:00:00 2001 From: Andy Grove Date: Fri, 6 May 2022 10:35:59 -0600 Subject: [PATCH 2/8] prep for review --- datafusion/core/src/sql/planner.rs | 274 ++++++++++++++--------------- 1 file changed, 137 insertions(+), 137 deletions(-) diff --git a/datafusion/core/src/sql/planner.rs b/datafusion/core/src/sql/planner.rs index b223c91ce0a8..06a783c89fae 100644 --- a/datafusion/core/src/sql/planner.rs +++ b/datafusion/core/src/sql/planner.rs @@ -1159,7 +1159,6 @@ impl<'a, S: ContextProvider> SqlToRel<'a, S> { // we do not add the "having" expression since that is not part of the projection) let mut aggr_projection_exprs = vec![]; for expr in &group_by_exprs { - // TODO this needs to be recursive to handle GROUP BY function(arg, ..) match expr { Expr::GroupingSet(GroupingSet::Rollup(exprs)) => { aggr_projection_exprs.extend_from_slice(&exprs) @@ -1167,7 +1166,11 @@ impl<'a, S: ContextProvider> SqlToRel<'a, S> { Expr::GroupingSet(GroupingSet::Cube(exprs)) => { aggr_projection_exprs.extend_from_slice(&exprs) } - Expr::GroupingSet(GroupingSet::GroupingSets(_)) => todo!(), + Expr::GroupingSet(GroupingSet::GroupingSets(lists_of_exprs)) => { + for exprs in lists_of_exprs { + aggr_projection_exprs.extend_from_slice(exprs) + } + } _ => aggr_projection_exprs.push(expr.clone()), } } @@ -1895,14 +1898,11 @@ impl<'a, S: ContextProvider> SqlToRel<'a, S> { } else if name == "cube" { let args = self.function_args_to_expr(function.args, schema)?; return Ok(Expr::GroupingSet(GroupingSet::Cube(args))); - } else if name == "grouping sets" { // TODO is not possible to hit this case yet - todo!() } // next, scalar built-in if let Ok(fun) = BuiltinScalarFunction::from_str(&name) { let args = self.function_args_to_expr(function.args, schema)?; - return Ok(Expr::ScalarFunction { fun, args }); }; @@ -2597,7 +2597,7 @@ mod tests { #[test] fn select_no_relation() { - assert_expected_plan( + quick_test( "SELECT 1", "Projection: Int64(1)\ \n EmptyRelation", @@ -2606,7 +2606,7 @@ mod tests { #[test] fn test_real_f32() { - assert_expected_plan( + quick_test( "SELECT CAST(1.1 AS REAL)", "Projection: CAST(Float64(1.1) AS Float32)\ \n EmptyRelation", @@ -2642,7 +2642,7 @@ mod tests { #[test] fn select_wildcard_with_repeated_column_but_is_aliased() { - assert_expected_plan( + quick_test( "SELECT *, first_name AS fn from person", "Projection: #person.id, #person.first_name, #person.last_name, #person.age, #person.state, #person.salary, #person.birth_date, #person.😀, #person.first_name AS fn\ \n TableScan: person projection=None", @@ -2651,7 +2651,7 @@ mod tests { #[test] fn select_scalar_func_with_literal_no_relation() { - assert_expected_plan( + quick_test( "SELECT sqrt(9)", "Projection: sqrt(Int64(9))\ \n EmptyRelation", @@ -2665,7 +2665,7 @@ mod tests { let expected = "Projection: #person.id, #person.first_name, #person.last_name\ \n Filter: #person.state = Utf8(\"CO\")\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -2689,7 +2689,7 @@ mod tests { let expected = "Projection: #person.id, #person.first_name, #person.last_name\ \n Filter: NOT #person.state\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -2699,7 +2699,7 @@ mod tests { let expected = "Projection: #person.id, #person.first_name, #person.last_name\ \n Filter: #person.state = Utf8(\"CO\") AND #person.age >= Int64(21) AND #person.age <= Int64(65)\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -2711,7 +2711,7 @@ mod tests { \n Filter: #person.birth_date < CAST(Int64(158412331400600000) AS Timestamp(Nanosecond, None))\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -2723,7 +2723,7 @@ mod tests { \n Filter: #person.birth_date < CAST(Utf8(\"2020-01-01\") AS Date32)\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -2744,7 +2744,7 @@ mod tests { AND #person.age < Int64(65) \ AND #person.age <= Int64(65)\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -2754,7 +2754,7 @@ mod tests { \n Filter: #person.age BETWEEN Int64(21) AND Int64(65)\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -2764,7 +2764,7 @@ mod tests { \n Filter: #person.age NOT BETWEEN Int64(21) AND Int64(65)\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -2783,7 +2783,7 @@ mod tests { \n Projection: #a.fn1, #a.last_name, #a.birth_date, #a.age, alias=a\ \n Projection: #person.first_name AS fn1, #person.last_name, #person.birth_date, #person.age, alias=a\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -2803,7 +2803,7 @@ mod tests { \n Filter: #person.age > Int64(20)\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -2814,7 +2814,7 @@ mod tests { \n Projection: #l.l_item_id AS a, #l.l_description AS b, #l.price AS c, alias=l\ \n SubqueryAlias: l\ \n TableScan: lineitem projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -2836,7 +2836,7 @@ mod tests { let expected = "Projection: #person.id, #person.age\ \n Filter: #person.age > Int64(100) AND #person.age < Int64(200)\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -2886,7 +2886,7 @@ mod tests { \n Filter: #MAX(person.age) < Int64(30)\ \n Aggregate: groupBy=[[]], aggr=[[MAX(#person.age)]]\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -2898,7 +2898,7 @@ mod tests { \n Filter: #MAX(person.first_name) > Utf8(\"M\")\ \n Aggregate: groupBy=[[]], aggr=[[MAX(#person.age), MAX(#person.first_name)]]\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -2925,7 +2925,7 @@ mod tests { \n Filter: #MAX(person.age) < Int64(30)\ \n Aggregate: groupBy=[[]], aggr=[[MAX(#person.age)]]\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -2937,7 +2937,7 @@ mod tests { \n Filter: #MAX(person.age) < Int64(30)\ \n Aggregate: groupBy=[[]], aggr=[[MAX(#person.age)]]\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -2950,7 +2950,7 @@ mod tests { \n Filter: #person.first_name = Utf8(\"M\")\ \n Aggregate: groupBy=[[#person.first_name]], aggr=[[MAX(#person.age)]]\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -2965,7 +2965,7 @@ mod tests { \n Aggregate: groupBy=[[#person.first_name]], aggr=[[MAX(#person.age)]]\ \n Filter: #person.id > Int64(5)\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -2981,7 +2981,7 @@ mod tests { \n Aggregate: groupBy=[[#person.first_name]], aggr=[[MAX(#person.age)]]\ \n Filter: #person.id > Int64(5) AND #person.age > Int64(18)\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -2994,7 +2994,7 @@ mod tests { \n Filter: #MAX(person.age) > Int64(2) AND #person.first_name = Utf8(\"M\")\ \n Aggregate: groupBy=[[#person.first_name]], aggr=[[MAX(#person.age)]]\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3008,7 +3008,7 @@ mod tests { \n Filter: #MAX(person.age) > Int64(2) AND #MAX(person.age) < Int64(5) AND #person.first_name = Utf8(\"M\") AND #person.first_name = Utf8(\"N\")\ \n Aggregate: groupBy=[[#person.first_name]], aggr=[[MAX(#person.age)]]\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3021,7 +3021,7 @@ mod tests { \n Filter: #MAX(person.age) > Int64(100)\ \n Aggregate: groupBy=[[#person.first_name]], aggr=[[MAX(#person.age)]]\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3049,7 +3049,7 @@ mod tests { \n Filter: #MAX(person.age) > Int64(100) AND #MAX(person.age) < Int64(200)\ \n Aggregate: groupBy=[[#person.first_name]], aggr=[[MAX(#person.age)]]\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3062,7 +3062,7 @@ mod tests { \n Filter: #MAX(person.age) > Int64(100) AND #MIN(person.id) < Int64(50)\ \n Aggregate: groupBy=[[#person.first_name]], aggr=[[MAX(#person.age), MIN(#person.id)]]\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3076,7 +3076,7 @@ mod tests { \n Filter: #MAX(person.age) > Int64(100)\ \n Aggregate: groupBy=[[#person.first_name]], aggr=[[MAX(#person.age)]]\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3091,7 +3091,7 @@ mod tests { \n Filter: #MAX(person.age) + Int64(1) > Int64(100)\ \n Aggregate: groupBy=[[#person.first_name]], aggr=[[MAX(#person.age)]]\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3105,7 +3105,7 @@ mod tests { \n Filter: #MAX(person.age) > Int64(100) AND #MIN(person.id - Int64(2)) < Int64(50)\ \n Aggregate: groupBy=[[#person.first_name]], aggr=[[MAX(#person.age), MIN(#person.id - Int64(2))]]\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3118,7 +3118,7 @@ mod tests { \n Filter: #MAX(person.age) > Int64(100) AND #COUNT(UInt8(1)) < Int64(50)\ \n Aggregate: groupBy=[[#person.first_name]], aggr=[[MAX(#person.age), COUNT(UInt8(1))]]\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3126,7 +3126,7 @@ mod tests { let sql = "SELECT age + salary from person"; let expected = "Projection: #person.age + #person.salary\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3134,18 +3134,18 @@ mod tests { let sql = "SELECT (age + salary)/2 from person"; let expected = "Projection: #person.age + #person.salary / Int64(2)\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] fn select_wildcard_with_groupby() { - assert_expected_plan( + quick_test( r#"SELECT * FROM person GROUP BY id, first_name, last_name, age, state, salary, birth_date, "😀""#, "Projection: #person.id, #person.first_name, #person.last_name, #person.age, #person.state, #person.salary, #person.birth_date, #person.😀\ \n Aggregate: groupBy=[[#person.id, #person.first_name, #person.last_name, #person.age, #person.state, #person.salary, #person.birth_date, #person.😀]], aggr=[[]]\ \n TableScan: person projection=None", ); - assert_expected_plan( + quick_test( "SELECT * FROM (SELECT first_name, last_name FROM person) AS a GROUP BY first_name, last_name", "Projection: #a.first_name, #a.last_name\ \n Aggregate: groupBy=[[#a.first_name, #a.last_name]], aggr=[[]]\ @@ -3157,7 +3157,7 @@ mod tests { #[test] fn select_simple_aggregate() { - assert_expected_plan( + quick_test( "SELECT MIN(age) FROM person", "Projection: #MIN(person.age)\ \n Aggregate: groupBy=[[]], aggr=[[MIN(#person.age)]]\ @@ -3167,7 +3167,7 @@ mod tests { #[test] fn test_sum_aggregate() { - assert_expected_plan( + quick_test( "SELECT SUM(age) from person", "Projection: #SUM(person.age)\ \n Aggregate: groupBy=[[]], aggr=[[SUM(#person.age)]]\ @@ -3194,7 +3194,7 @@ mod tests { #[test] fn select_simple_aggregate_repeated_aggregate_with_single_alias() { - assert_expected_plan( + quick_test( "SELECT MIN(age), MIN(age) AS a FROM person", "Projection: #MIN(person.age), #MIN(person.age) AS a\ \n Aggregate: groupBy=[[]], aggr=[[MIN(#person.age)]]\ @@ -3204,7 +3204,7 @@ mod tests { #[test] fn select_simple_aggregate_repeated_aggregate_with_unique_aliases() { - assert_expected_plan( + quick_test( "SELECT MIN(age) AS a, MIN(age) AS b FROM person", "Projection: #MIN(person.age) AS a, #MIN(person.age) AS b\ \n Aggregate: groupBy=[[]], aggr=[[MIN(#person.age)]]\ @@ -3224,7 +3224,7 @@ mod tests { #[test] fn select_simple_aggregate_with_groupby() { - assert_expected_plan( + quick_test( "SELECT state, MIN(age), MAX(age) FROM person GROUP BY state", "Projection: #person.state, #MIN(person.age), #MAX(person.age)\ \n Aggregate: groupBy=[[#person.state]], aggr=[[MIN(#person.age), MAX(#person.age)]]\ @@ -3234,7 +3234,7 @@ mod tests { #[test] fn select_simple_aggregate_with_groupby_with_aliases() { - assert_expected_plan( + quick_test( "SELECT state AS a, MIN(age) AS b FROM person GROUP BY state", "Projection: #person.state AS a, #MIN(person.age) AS b\ \n Aggregate: groupBy=[[#person.state]], aggr=[[MIN(#person.age)]]\ @@ -3254,7 +3254,7 @@ mod tests { #[test] fn select_simple_aggregate_with_groupby_column_unselected() { - assert_expected_plan( + quick_test( "SELECT MIN(age), MAX(age) FROM person GROUP BY state", "Projection: #MIN(person.age), #MAX(person.age)\ \n Aggregate: groupBy=[[#person.state]], aggr=[[MIN(#person.age), MAX(#person.age)]]\ @@ -3312,7 +3312,7 @@ mod tests { #[test] fn select_simple_aggregate_with_groupby_and_column_is_in_aggregate_and_groupby() { - assert_expected_plan( + quick_test( "SELECT MAX(first_name) FROM person GROUP BY first_name", "Projection: #MAX(person.first_name)\ \n Aggregate: groupBy=[[#person.first_name]], aggr=[[MAX(#person.first_name)]]\ @@ -3322,13 +3322,13 @@ mod tests { #[test] fn select_simple_aggregate_with_groupby_can_use_positions() { - assert_expected_plan( + quick_test( "SELECT state, age AS b, COUNT(1) FROM person GROUP BY 1, 2", "Projection: #person.state, #person.age AS b, #COUNT(UInt8(1))\ \n Aggregate: groupBy=[[#person.state, #person.age]], aggr=[[COUNT(UInt8(1))]]\ \n TableScan: person projection=None", ); - assert_expected_plan( + quick_test( "SELECT state, age AS b, COUNT(1) FROM person GROUP BY 2, 1", "Projection: #person.state, #person.age AS b, #COUNT(UInt8(1))\ \n Aggregate: groupBy=[[#person.age, #person.state]], aggr=[[COUNT(UInt8(1))]]\ @@ -3355,7 +3355,7 @@ mod tests { #[test] fn select_simple_aggregate_with_groupby_can_use_alias() { - assert_expected_plan( + quick_test( "SELECT state AS a, MIN(age) AS b FROM person GROUP BY a", "Projection: #person.state AS a, #MIN(person.age) AS b\ \n Aggregate: groupBy=[[#person.state]], aggr=[[MIN(#person.age)]]\ @@ -3375,7 +3375,7 @@ mod tests { #[test] fn select_simple_aggregate_with_groupby_aggregate_repeated_and_one_has_alias() { - assert_expected_plan( + quick_test( "SELECT state, MIN(age), MIN(age) AS ma FROM person GROUP BY state", "Projection: #person.state, #MIN(person.age), #MIN(person.age) AS ma\ \n Aggregate: groupBy=[[#person.state]], aggr=[[MIN(#person.age)]]\ @@ -3385,7 +3385,7 @@ mod tests { #[test] fn select_simple_aggregate_with_groupby_non_column_expression_unselected() { - assert_expected_plan( + quick_test( "SELECT MIN(first_name) FROM person GROUP BY age + 1", "Projection: #MIN(person.first_name)\ \n Aggregate: groupBy=[[#person.age + Int64(1)]], aggr=[[MIN(#person.first_name)]]\ @@ -3396,13 +3396,13 @@ mod tests { #[test] fn select_simple_aggregate_with_groupby_non_column_expression_selected_and_resolvable( ) { - assert_expected_plan( + quick_test( "SELECT age + 1, MIN(first_name) FROM person GROUP BY age + 1", "Projection: #person.age + Int64(1), #MIN(person.first_name)\ \n Aggregate: groupBy=[[#person.age + Int64(1)]], aggr=[[MIN(#person.first_name)]]\ \n TableScan: person projection=None", ); - assert_expected_plan( + quick_test( "SELECT MIN(first_name), age + 1 FROM person GROUP BY age + 1", "Projection: #MIN(person.first_name), #person.age + Int64(1)\ \n Aggregate: groupBy=[[#person.age + Int64(1)]], aggr=[[MIN(#person.first_name)]]\ @@ -3413,7 +3413,7 @@ mod tests { #[test] fn select_simple_aggregate_with_groupby_non_column_expression_nested_and_resolvable() { - assert_expected_plan( + quick_test( "SELECT ((age + 1) / 2) * (age + 1), MIN(first_name) FROM person GROUP BY age + 1", "Projection: #person.age + Int64(1) / Int64(2) * #person.age + Int64(1), #MIN(person.first_name)\ \n Aggregate: groupBy=[[#person.age + Int64(1)]], aggr=[[MIN(#person.first_name)]]\ @@ -3446,7 +3446,7 @@ mod tests { #[test] fn select_simple_aggregate_nested_in_binary_expr_with_groupby() { - assert_expected_plan( + quick_test( "SELECT state, MIN(age) < 10 FROM person GROUP BY state", "Projection: #person.state, #MIN(person.age) < Int64(10)\ \n Aggregate: groupBy=[[#person.state]], aggr=[[MIN(#person.age)]]\ @@ -3456,7 +3456,7 @@ mod tests { #[test] fn select_simple_aggregate_and_nested_groupby_column() { - assert_expected_plan( + quick_test( "SELECT age + 1, MAX(first_name) FROM person GROUP BY age", "Projection: #person.age + Int64(1), #MAX(person.first_name)\ \n Aggregate: groupBy=[[#person.age]], aggr=[[MAX(#person.first_name)]]\ @@ -3466,7 +3466,7 @@ mod tests { #[test] fn select_aggregate_compounded_with_groupby_column() { - assert_expected_plan( + quick_test( "SELECT age + MIN(salary) FROM person GROUP BY age", "Projection: #person.age + #MIN(person.salary)\ \n Aggregate: groupBy=[[#person.age]], aggr=[[MIN(#person.salary)]]\ @@ -3476,7 +3476,7 @@ mod tests { #[test] fn select_aggregate_with_non_column_inner_expression_with_groupby() { - assert_expected_plan( + quick_test( "SELECT state, MIN(age + 1) FROM person GROUP BY state", "Projection: #person.state, #MIN(person.age + Int64(1))\ \n Aggregate: groupBy=[[#person.state]], aggr=[[MIN(#person.age + Int64(1))]]\ @@ -3486,7 +3486,7 @@ mod tests { #[test] fn test_wildcard() { - assert_expected_plan( + quick_test( "SELECT * from person", "Projection: #person.id, #person.first_name, #person.last_name, #person.age, #person.state, #person.salary, #person.birth_date, #person.😀\ \n TableScan: person projection=None", @@ -3499,7 +3499,7 @@ mod tests { let expected = "Projection: #COUNT(UInt8(1))\ \n Aggregate: groupBy=[[]], aggr=[[COUNT(UInt8(1))]]\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3508,7 +3508,7 @@ mod tests { let expected = "Projection: #COUNT(person.id)\ \n Aggregate: groupBy=[[]], aggr=[[COUNT(#person.id)]]\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3517,7 +3517,7 @@ mod tests { let expected = "Projection: #APPROXPERCENTILECONT(person.age,Float64(0.5))\ \n Aggregate: groupBy=[[]], aggr=[[APPROXPERCENTILECONT(#person.age, Float64(0.5))]]\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3525,7 +3525,7 @@ mod tests { let sql = "SELECT sqrt(age) FROM person"; let expected = "Projection: sqrt(#person.age)\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3533,7 +3533,7 @@ mod tests { let sql = "SELECT sqrt(person.age) AS square_people FROM person"; let expected = "Projection: sqrt(#person.age) AS square_people\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3543,7 +3543,7 @@ mod tests { let expected = "Projection: #aggregate_test_100.c3 / #aggregate_test_100.c4 + #aggregate_test_100.c5\ \n Filter: #aggregate_test_100.c3 / nullif(#aggregate_test_100.c4 + #aggregate_test_100.c5, Int64(0)) > Float64(0.1)\ \n TableScan: aggregate_test_100 projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3552,7 +3552,7 @@ mod tests { let expected = "Projection: #aggregate_test_100.c3\ \n Filter: #aggregate_test_100.c3 > Float64(-0.1) AND (- #aggregate_test_100.c4) > Int64(0)\ \n TableScan: aggregate_test_100 projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3561,7 +3561,7 @@ mod tests { let expected = "Projection: #aggregate_test_100.c3\ \n Filter: #aggregate_test_100.c3 > Float64(0.1) AND #aggregate_test_100.c4 > Int64(0)\ \n TableScan: aggregate_test_100 projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3571,7 +3571,7 @@ mod tests { \n Projection: #person.id\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3581,7 +3581,7 @@ mod tests { \n Projection: #person.id, #person.state, #person.age\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3610,7 +3610,7 @@ mod tests { let expected = "Sort: #person.id ASC NULLS LAST\ \n Projection: #person.id\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3619,19 +3619,19 @@ mod tests { let expected = "Sort: #person.id DESC NULLS FIRST\ \n Projection: #person.id\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] fn select_order_by_nulls_last() { - assert_expected_plan( + quick_test( "SELECT id FROM person ORDER BY id DESC NULLS LAST", "Sort: #person.id DESC NULLS LAST\ \n Projection: #person.id\ \n TableScan: person projection=None", ); - assert_expected_plan( + quick_test( "SELECT id FROM person ORDER BY id NULLS LAST", "Sort: #person.id ASC NULLS LAST\ \n Projection: #person.id\ @@ -3646,7 +3646,7 @@ mod tests { \n Aggregate: groupBy=[[#person.state]], aggr=[[]]\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3656,7 +3656,7 @@ mod tests { \n Aggregate: groupBy=[[#person.state]], aggr=[[MAX(#person.age)]]\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3666,7 +3666,7 @@ mod tests { \n Aggregate: groupBy=[[#person.state]], aggr=[[COUNT(UInt8(1))]]\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3677,7 +3677,7 @@ mod tests { \n Aggregate: groupBy=[[#person.state]], aggr=[[COUNT(#person.state)]]\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3686,7 +3686,7 @@ mod tests { let expected = "Projection: #aggregate_test_100.c1, #MIN(aggregate_test_100.c12)\ \n Aggregate: groupBy=[[#aggregate_test_100.c1, #aggregate_test_100.c13]], aggr=[[MIN(#aggregate_test_100.c12)]]\ \n TableScan: aggregate_test_100 projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3705,14 +3705,14 @@ mod tests { fn create_external_table_csv() { let sql = "CREATE EXTERNAL TABLE t(c1 int) STORED AS CSV LOCATION 'foo.csv'"; let expected = "CreateExternalTable: \"t\""; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] fn create_external_table_csv_no_schema() { let sql = "CREATE EXTERNAL TABLE t STORED AS CSV LOCATION 'foo.csv'"; let expected = "CreateExternalTable: \"t\""; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3730,7 +3730,7 @@ mod tests { fn create_external_table_parquet_no_schema() { let sql = "CREATE EXTERNAL TABLE t STORED AS PARQUET LOCATION 'foo.parquet'"; let expected = "CreateExternalTable: \"t\""; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3743,7 +3743,7 @@ mod tests { \n Inner Join: #person.id = #orders.customer_id\ \n TableScan: person projection=None\ \n TableScan: orders projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3757,7 +3757,7 @@ mod tests { \n Inner Join: #person.id = #orders.customer_id\ \n TableScan: person projection=None\ \n TableScan: orders projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3771,7 +3771,7 @@ mod tests { \n TableScan: person projection=None\ \n Filter: #orders.order_id > Int64(1)\ \n TableScan: orders projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3785,7 +3785,7 @@ mod tests { \n Filter: #person.id > Int64(1)\ \n TableScan: person projection=None\ \n TableScan: orders projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3798,7 +3798,7 @@ mod tests { \n Inner Join: #person.id = #orders.customer_id\ \n TableScan: person projection=None\ \n TableScan: orders projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3812,7 +3812,7 @@ mod tests { \n TableScan: person projection=None\ \n SubqueryAlias: person2\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3826,7 +3826,7 @@ mod tests { \n TableScan: lineitem projection=None\ \n SubqueryAlias: lineitem2\ \n TableScan: lineitem projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3842,7 +3842,7 @@ mod tests { \n TableScan: person projection=None\ \n TableScan: orders projection=None\ \n TableScan: lineitem projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3853,7 +3853,7 @@ mod tests { let expected = "Projection: #orders.order_id\ \n Filter: #orders.delivered = Boolean(false) OR #orders.delivered = Boolean(true)\ \n TableScan: orders projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3864,7 +3864,7 @@ mod tests { \n TableScan: orders projection=None\ \n Projection: #orders.order_id\ \n TableScan: orders projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3882,7 +3882,7 @@ mod tests { \n TableScan: orders projection=None\ \n Projection: #orders.order_id\ \n TableScan: orders projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3893,7 +3893,7 @@ mod tests { \n TableScan: orders projection=None\ \n Projection: #orders.customer_id\ \n TableScan: orders projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3904,7 +3904,7 @@ mod tests { \n EmptyRelation\ \n Projection: Int64(3) AS column0, Int64(4) AS column1\ \n EmptyRelation"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3927,7 +3927,7 @@ mod tests { Projection: #orders.order_id, #MAX(orders.order_id)\ \n WindowAggr: windowExpr=[[MAX(#orders.order_id)]]\ \n TableScan: orders projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3937,7 +3937,7 @@ mod tests { Projection: #orders.order_id AS oid, #MAX(orders.order_id) AS max_oid\ \n WindowAggr: windowExpr=[[MAX(#orders.order_id)]]\ \n TableScan: orders projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3947,7 +3947,7 @@ mod tests { Projection: #orders.order_id AS oid, #MAX(orders.order_id) AS max_oid, #MAX(orders.order_id) AS max_oid_dup\ \n WindowAggr: windowExpr=[[MAX(#orders.order_id)]]\ \n TableScan: orders projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3958,7 +3958,7 @@ mod tests { \n WindowAggr: windowExpr=[[MAX(#orders.order_id)]]\ \n WindowAggr: windowExpr=[[MAX(#orders.order_id) ORDER BY [#orders.order_id ASC NULLS LAST]]]\ \n TableScan: orders projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3968,7 +3968,7 @@ mod tests { Projection: #orders.order_id, #MAX(orders.qty * Float64(1.1))\ \n WindowAggr: windowExpr=[[MAX(#orders.qty * Float64(1.1))]]\ \n TableScan: orders projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -3979,7 +3979,7 @@ mod tests { Projection: #orders.order_id, #MAX(orders.qty), #MIN(orders.qty), #AVG(orders.qty)\ \n WindowAggr: windowExpr=[[MAX(#orders.qty), MIN(#orders.qty), AVG(#orders.qty)]]\ \n TableScan: orders projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } /// psql result @@ -3998,7 +3998,7 @@ mod tests { Projection: #orders.order_id, #MAX(orders.qty) PARTITION BY [#orders.order_id]\ \n WindowAggr: windowExpr=[[MAX(#orders.qty) PARTITION BY [#orders.order_id]]]\ \n TableScan: orders projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } /// psql result @@ -4021,7 +4021,7 @@ mod tests { \n WindowAggr: windowExpr=[[MAX(#orders.qty) ORDER BY [#orders.order_id ASC NULLS LAST]]]\ \n WindowAggr: windowExpr=[[MIN(#orders.qty) ORDER BY [#orders.order_id DESC NULLS FIRST]]]\ \n TableScan: orders projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -4032,7 +4032,7 @@ mod tests { \n WindowAggr: windowExpr=[[MAX(#orders.qty) ORDER BY [#orders.order_id ASC NULLS LAST] ROWS BETWEEN 3 PRECEDING AND 3 FOLLOWING]]\ \n WindowAggr: windowExpr=[[MIN(#orders.qty) ORDER BY [#orders.order_id DESC NULLS FIRST]]]\ \n TableScan: orders projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -4043,7 +4043,7 @@ mod tests { \n WindowAggr: windowExpr=[[MAX(#orders.qty) ORDER BY [#orders.order_id ASC NULLS LAST] ROWS BETWEEN 3 PRECEDING AND CURRENT ROW]]\ \n WindowAggr: windowExpr=[[MIN(#orders.qty) ORDER BY [#orders.order_id DESC NULLS FIRST]]]\ \n TableScan: orders projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -4086,7 +4086,7 @@ mod tests { \n WindowAggr: windowExpr=[[MAX(#orders.qty) ORDER BY [#orders.order_id ASC NULLS LAST] GROUPS BETWEEN 3 PRECEDING AND CURRENT ROW]]\ \n WindowAggr: windowExpr=[[MIN(#orders.qty) ORDER BY [#orders.order_id DESC NULLS FIRST]]]\ \n TableScan: orders projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } /// psql result @@ -4109,7 +4109,7 @@ mod tests { \n WindowAggr: windowExpr=[[MAX(#orders.qty) ORDER BY [#orders.order_id ASC NULLS LAST]]]\ \n WindowAggr: windowExpr=[[MIN(#orders.qty) ORDER BY [#orders.order_id + Int64(1) ASC NULLS LAST]]]\ \n TableScan: orders projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } /// psql result @@ -4134,7 +4134,7 @@ mod tests { \n WindowAggr: windowExpr=[[MAX(#orders.qty) ORDER BY [#orders.qty ASC NULLS LAST, #orders.order_id ASC NULLS LAST]]]\ \n WindowAggr: windowExpr=[[MIN(#orders.qty) ORDER BY [#orders.order_id ASC NULLS LAST, #orders.qty ASC NULLS LAST]]]\ \n TableScan: orders projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } /// psql result @@ -4159,7 +4159,7 @@ mod tests { \n WindowAggr: windowExpr=[[MAX(#orders.qty) ORDER BY [#orders.order_id ASC NULLS LAST]]]\ \n WindowAggr: windowExpr=[[MIN(#orders.qty) ORDER BY [#orders.order_id ASC NULLS LAST, #orders.qty ASC NULLS LAST]]]\ \n TableScan: orders projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } /// psql result @@ -4188,7 +4188,7 @@ mod tests { \n WindowAggr: windowExpr=[[MAX(#orders.qty) ORDER BY [#orders.qty ASC NULLS LAST, #orders.order_id ASC NULLS LAST]]]\ \n WindowAggr: windowExpr=[[MIN(#orders.qty) ORDER BY [#orders.order_id ASC NULLS LAST, #orders.qty ASC NULLS LAST]]]\ \n TableScan: orders projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } /// psql result @@ -4208,7 +4208,7 @@ mod tests { Projection: #orders.order_id, #MAX(orders.qty) PARTITION BY [#orders.order_id] ORDER BY [#orders.qty ASC NULLS LAST]\ \n WindowAggr: windowExpr=[[MAX(#orders.qty) PARTITION BY [#orders.order_id] ORDER BY [#orders.qty ASC NULLS LAST]]]\ \n TableScan: orders projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } /// psql result @@ -4228,7 +4228,7 @@ mod tests { Projection: #orders.order_id, #MAX(orders.qty) PARTITION BY [#orders.order_id, #orders.qty] ORDER BY [#orders.qty ASC NULLS LAST]\ \n WindowAggr: windowExpr=[[MAX(#orders.qty) PARTITION BY [#orders.order_id, #orders.qty] ORDER BY [#orders.qty ASC NULLS LAST]]]\ \n TableScan: orders projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } /// psql result @@ -4252,7 +4252,7 @@ mod tests { \n WindowAggr: windowExpr=[[MIN(#orders.qty) PARTITION BY [#orders.qty] ORDER BY [#orders.order_id ASC NULLS LAST]]]\ \n WindowAggr: windowExpr=[[MAX(#orders.qty) PARTITION BY [#orders.order_id, #orders.qty] ORDER BY [#orders.qty ASC NULLS LAST]]]\ \n TableScan: orders projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } /// psql result @@ -4275,7 +4275,7 @@ mod tests { \n WindowAggr: windowExpr=[[MAX(#orders.qty) PARTITION BY [#orders.order_id] ORDER BY [#orders.qty ASC NULLS LAST]]]\ \n WindowAggr: windowExpr=[[MIN(#orders.qty) PARTITION BY [#orders.order_id, #orders.qty] ORDER BY [#orders.price ASC NULLS LAST]]]\ \n TableScan: orders projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -4286,7 +4286,7 @@ mod tests { Projection: #orders.order_id, #APPROXPERCENTILECONT(orders.qty,Float64(0.5)) PARTITION BY [#orders.order_id]\ \n WindowAggr: windowExpr=[[APPROXPERCENTILECONT(#orders.qty, Float64(0.5)) PARTITION BY [#orders.order_id]]]\ \n TableScan: orders projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -4294,7 +4294,7 @@ mod tests { let sql = "SELECT date '2020-12-10' AS date FROM person"; let expected = "Projection: CAST(Utf8(\"2020-12-10\") AS Date32) AS date\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -4302,7 +4302,7 @@ mod tests { let sql = r#"SELECT "😀" FROM person"#; let expected = "Projection: #person.😀\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } fn logical_plan(sql: &str) -> Result { @@ -4313,7 +4313,7 @@ mod tests { } /// Create logical plan, write with formatter, compare to expected output - fn assert_expected_plan(sql: &str, expected: &str) { + fn quick_test(sql: &str, expected: &str) { let plan = logical_plan(sql).unwrap(); assert_eq!(format!("{:?}", plan), expected); } @@ -4425,7 +4425,7 @@ mod tests { let sql = r#"SELECT person.first_name FROM public.person"#; let expected = "Projection: #public.person.first_name\ \n TableScan: public.person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -4437,7 +4437,7 @@ mod tests { \n TableScan: person projection=None\ \n TableScan: lineitem projection=None\ \n TableScan: orders projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -4450,7 +4450,7 @@ mod tests { \n TableScan: person projection=None\ \n TableScan: orders projection=None\ \n TableScan: lineitem projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -4462,7 +4462,7 @@ mod tests { \n TableScan: person projection=None\ \n SubqueryAlias: folks\ \n TableScan: person projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -4478,7 +4478,7 @@ mod tests { let sql = "select t_date32 + interval '5 days' FROM test"; let expected = "Projection: #test.t_date32 + IntervalDayTime(\"21474836480\")\ \n TableScan: test projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -4491,7 +4491,7 @@ mod tests { "Projection: #test.t_date64\ \n Filter: #test.t_date64 BETWEEN CAST(Utf8(\"1999-12-31\") AS Date32) AND CAST(Utf8(\"1999-12-31\") AS Date32) + IntervalDayTime(\"128849018880\")\ \n TableScan: test projection=None"; - assert_expected_plan(sql, expected); + quick_test(sql, expected); } #[test] @@ -4512,7 +4512,7 @@ mod tests { \n TableScan: person projection=None", subquery_expected ); - assert_expected_plan(sql, &expected); + quick_test(sql, &expected); } #[test] @@ -4541,7 +4541,7 @@ mod tests { \n TableScan: person projection=None", subquery_expected ); - assert_expected_plan(sql, &expected); + quick_test(sql, &expected); } #[test] @@ -4563,7 +4563,7 @@ mod tests { \n TableScan: person projection=None", subquery_expected ); - assert_expected_plan(sql, &expected); + quick_test(sql, &expected); } #[test] @@ -4581,7 +4581,7 @@ mod tests { \n TableScan: person projection=None", subquery_expected ); - assert_expected_plan(sql, &expected); + quick_test(sql, &expected); } #[test] @@ -4600,7 +4600,7 @@ mod tests { \n TableScan: person projection=None", subquery_expected ); - assert_expected_plan(sql, &expected); + quick_test(sql, &expected); } #[test] @@ -4618,7 +4618,7 @@ mod tests { \n TableScan: person projection=None", subquery_expected ); - assert_expected_plan(sql, &expected); + quick_test(sql, &expected); } #[test] @@ -4647,7 +4647,7 @@ mod tests { subquery ); - assert_expected_plan(sql, &expected); + quick_test(sql, &expected); } #[tokio::test] @@ -4665,7 +4665,7 @@ mod tests { \n Filter: EXISTS ({})\ \n TableScan: person projection=None", subquery); - assert_expected_plan(sql, &expected) + quick_test(sql, &expected) } #[tokio::test] @@ -4674,7 +4674,7 @@ mod tests { let expected = "Projection: #person.id, #person.state, #person.age, #COUNT(UInt8(1))\ \n Aggregate: groupBy=[[#person.id, ROLLUP (#person.state, #person.age)]], aggr=[[COUNT(UInt8(1))]]\ \n TableScan: person projection=None"; - assert_expected_plan(sql, &expected); + quick_test(sql, &expected); } #[tokio::test] @@ -4684,7 +4684,7 @@ mod tests { let expected = "Projection: #person.id, #person.state, #person.age, #COUNT(UInt8(1))\ \n Aggregate: groupBy=[[#person.id, CUBE (#person.state, #person.age)]], aggr=[[COUNT(UInt8(1))]]\ \n TableScan: person projection=None"; - assert_expected_plan(sql, &expected); + quick_test(sql, &expected); } #[ignore] // see https://github.com/apache/arrow-datafusion/issues/2469 @@ -4692,7 +4692,7 @@ mod tests { async fn aggregate_with_grouping_sets() { let sql = "SELECT id, state, age, COUNT(*) FROM person GROUP BY id, GROUPING SETS ((state), (state, age), (id, state))"; let expected = "TBD"; - assert_expected_plan(sql, &expected); + quick_test(sql, &expected); } fn assert_field_not_found(err: DataFusionError, name: &str) { From 01e5bfc4c177f0c75d1b1a31d0a7678dca908186 Mon Sep 17 00:00:00 2001 From: Andy Grove Date: Fri, 6 May 2022 10:45:05 -0600 Subject: [PATCH 3/8] fix more todo comments --- datafusion/core/src/sql/utils.rs | 32 ++++++++++++++++++++++++++---- datafusion/expr/src/expr.rs | 15 ++++++++------ datafusion/expr/src/expr_schema.rs | 5 +++-- 3 files changed, 40 insertions(+), 12 deletions(-) diff --git a/datafusion/core/src/sql/utils.rs b/datafusion/core/src/sql/utils.rs index ab1d6be36406..f18832fb1b42 100644 --- a/datafusion/core/src/sql/utils.rs +++ b/datafusion/core/src/sql/utils.rs @@ -461,10 +461,34 @@ where expr: Box::new(clone_with_replacement(expr.as_ref(), replacement_fn)?), key: key.clone(), }), - Expr::GroupingSet(_) => { - //TODO ??? - Ok(expr.clone()) - } + Expr::GroupingSet(set) => match set { + GroupingSet::Rollup(exprs) => Ok(Expr::GroupingSet(GroupingSet::Rollup( + exprs + .iter() + .map(|e| clone_with_replacement(e, replacement_fn)) + .collect::>>()?, + ))), + GroupingSet::Cube(exprs) => Ok(Expr::GroupingSet(GroupingSet::Cube( + exprs + .iter() + .map(|e| clone_with_replacement(e, replacement_fn)) + .collect::>>()?, + ))), + GroupingSet::GroupingSets(lists_of_exprs) => { + let mut new_lists_of_exprs = vec![]; + for exprs in lists_of_exprs { + new_lists_of_exprs.push( + exprs + .iter() + .map(|e| clone_with_replacement(e, replacement_fn)) + .collect::>>()?, + ); + } + Ok(Expr::GroupingSet(GroupingSet::GroupingSets( + new_lists_of_exprs, + ))) + } + }, }, } } diff --git a/datafusion/expr/src/expr.rs b/datafusion/expr/src/expr.rs index 6cf9b54e1303..2deb55872766 100644 --- a/datafusion/expr/src/expr.rs +++ b/datafusion/expr/src/expr.rs @@ -868,12 +868,15 @@ fn create_name(e: &Expr, input_schema: &DFSchema) -> Result { "CUBE ({})", create_names(exprs.as_slice(), input_schema)? )), - GroupingSet::GroupingSets(_list_of_exprs) => { - todo!() - // Ok(format!( - // "GROUPING SETS ({})", - // list_of_exprs.iter().map(|exprs| format!("({})", create_names(exprs.as_slice(), input_schema))).collect::>>()?.join(", ") - // )) + GroupingSet::GroupingSets(lists_of_exprs) => { + let mut list_of_names = vec![]; + for exprs in lists_of_exprs { + list_of_names.push(format!( + "({})", + create_names(exprs.as_slice(), input_schema)? + )); + } + Ok(format!("GROUPING SETS ({})", list_of_names.join(", "))) } }, Expr::InList { diff --git a/datafusion/expr/src/expr_schema.rs b/datafusion/expr/src/expr_schema.rs index 9de68f07caa4..2433024e38a4 100644 --- a/datafusion/expr/src/expr_schema.rs +++ b/datafusion/expr/src/expr_schema.rs @@ -125,7 +125,7 @@ impl ExprSchemable for Expr { .to_owned(), )), Expr::GroupingSet(_) => { - // TODO grouping sets do not really have a type + // grouping sets do not really have a type and do not appear in projections Ok(DataType::Null) } Expr::GetIndexedField { ref expr, key } => { @@ -203,7 +203,8 @@ impl ExprSchemable for Expr { get_indexed_field(&data_type, key).map(|x| x.is_nullable()) } Expr::GroupingSet(_) => { - // TODO grouping sets do not really have the concept of nullable + // grouping sets do not really have the concept of nullable and do not appear + // in projections Ok(true) } } From 8d03784983ff833c1cdfb1f99cbb6ebd2d492cf5 Mon Sep 17 00:00:00 2001 From: Andy Grove Date: Fri, 6 May 2022 11:26:17 -0600 Subject: [PATCH 4/8] code cleanup --- datafusion/core/src/logical_plan/expr.rs | 48 +++++++------------ datafusion/core/src/logical_plan/mod.rs | 2 +- .../src/optimizer/projection_push_down.rs | 7 +-- 3 files changed, 19 insertions(+), 38 deletions(-) diff --git a/datafusion/core/src/logical_plan/expr.rs b/datafusion/core/src/logical_plan/expr.rs index 1ebbbafd9831..3f78cb986837 100644 --- a/datafusion/core/src/logical_plan/expr.rs +++ b/datafusion/core/src/logical_plan/expr.rs @@ -140,43 +140,27 @@ pub fn exprlist_to_fields<'a>( expr: impl IntoIterator, plan: &LogicalPlan, ) -> Result> { - let exprs: Vec = expr.into_iter().cloned().collect(); - let mut fields = vec![]; - for expr in &exprs { - match expr { - Expr::Column(c) => { - match plan { - LogicalPlan::Aggregate(agg) => { - let group_expr = agg.columns_in_group_expr()?; - if let Some(_) = group_expr.into_iter().find(|x| x == c) { - // fall back to legacy behavior, which has known issues, but at least use valid expressions and schemas - fields.push(expr.to_field(&agg.input.schema())?); - } else { - // fall back to legacy behavior, which has known issues - fields.push(expr.to_field(plan.schema())?); - } - } - _ => { - // fall back to legacy behavior, which has known issues - fields.push(expr.to_field(plan.schema())?); + match plan { + LogicalPlan::Aggregate(agg) => { + let group_expr = agg.columns_in_group_expr()?; + let exprs: Vec = expr.into_iter().cloned().collect(); + let mut fields = vec![]; + for expr in &exprs { + match expr { + Expr::Column(c) if group_expr.iter().any(|x| x == c) => { + // resolve against schema of input to aggregate + fields.push(expr.to_field(&agg.input.schema())?); } + _ => fields.push(expr.to_field(plan.schema())?) } } - _ => { - // fall back to legacy behavior, which has known issues - fields.push(expr.to_field(&plan.schema())?); - } + Ok(fields) + } + _ => { + let input_schema = &plan.schema(); + expr.into_iter().map(|e| e.to_field(input_schema)).collect() } } - Ok(fields) -} - -/// Create field meta-data from an expression, for use in a result set schema -pub fn exprlist_to_fields_from_schema<'a>( - expr: impl IntoIterator, - input_schema: &DFSchema, -) -> Result> { - expr.into_iter().map(|e| e.to_field(input_schema)).collect() } /// Calls a named built in function diff --git a/datafusion/core/src/logical_plan/mod.rs b/datafusion/core/src/logical_plan/mod.rs index e0968f99eb3d..12c34d857e08 100644 --- a/datafusion/core/src/logical_plan/mod.rs +++ b/datafusion/core/src/logical_plan/mod.rs @@ -41,7 +41,7 @@ pub use expr::{ avg, bit_length, btrim, call_fn, case, ceil, character_length, chr, coalesce, col, columnize_expr, combine_filters, concat, concat_expr, concat_ws, concat_ws_expr, cos, count, count_distinct, create_udaf, create_udf, date_part, date_trunc, digest, - exists, exp, exprlist_to_fields_from_schema, floor, in_list, in_subquery, initcap, + exists, exp, exprlist_to_fields, floor, in_list, in_subquery, initcap, left, length, lit, lit_timestamp_nano, ln, log10, log2, lower, lpad, ltrim, max, md5, min, not_exists, not_in_subquery, now, now_expr, nullif, octet_length, or, power, random, regexp_match, regexp_replace, repeat, replace, reverse, right, round, rpad, diff --git a/datafusion/core/src/optimizer/projection_push_down.rs b/datafusion/core/src/optimizer/projection_push_down.rs index 595045cd4298..6f3ea39ac317 100644 --- a/datafusion/core/src/optimizer/projection_push_down.rs +++ b/datafusion/core/src/optimizer/projection_push_down.rs @@ -506,10 +506,7 @@ mod tests { use std::collections::HashMap; use super::*; - use crate::logical_plan::{ - col, exprlist_to_fields_from_schema, lit, max, min, Expr, JoinType, - LogicalPlanBuilder, - }; + use crate::logical_plan::{col, lit, max, min, Expr, JoinType, LogicalPlanBuilder, exprlist_to_fields}; use crate::test::*; use arrow::datatypes::DataType; @@ -812,7 +809,7 @@ mod tests { // relation is `None`). PlanBuilder resolves the expressions let expr = vec![col("a"), col("b")]; let projected_fields = - exprlist_to_fields_from_schema(&expr, input_schema).unwrap(); + exprlist_to_fields(&expr, &table_scan).unwrap(); let projected_schema = DFSchema::new_with_metadata( projected_fields, input_schema.metadata().clone(), From 5211ee290a8f973ecb5d30dd68cc70438d5b9594 Mon Sep 17 00:00:00 2001 From: Andy Grove Date: Fri, 6 May 2022 11:44:11 -0600 Subject: [PATCH 5/8] clippy --- datafusion/core/src/logical_plan/expr.rs | 4 ++-- datafusion/core/src/logical_plan/mod.rs | 12 ++++++------ .../core/src/optimizer/common_subexpr_eliminate.rs | 10 +++++----- .../core/src/optimizer/projection_push_down.rs | 7 ++++--- datafusion/core/src/sql/planner.rs | 4 ++-- 5 files changed, 19 insertions(+), 18 deletions(-) diff --git a/datafusion/core/src/logical_plan/expr.rs b/datafusion/core/src/logical_plan/expr.rs index 3f78cb986837..22cd379547e3 100644 --- a/datafusion/core/src/logical_plan/expr.rs +++ b/datafusion/core/src/logical_plan/expr.rs @@ -149,9 +149,9 @@ pub fn exprlist_to_fields<'a>( match expr { Expr::Column(c) if group_expr.iter().any(|x| x == c) => { // resolve against schema of input to aggregate - fields.push(expr.to_field(&agg.input.schema())?); + fields.push(expr.to_field(agg.input.schema())?); } - _ => fields.push(expr.to_field(plan.schema())?) + _ => fields.push(expr.to_field(plan.schema())?), } } Ok(fields) diff --git a/datafusion/core/src/logical_plan/mod.rs b/datafusion/core/src/logical_plan/mod.rs index 12c34d857e08..55295e22e956 100644 --- a/datafusion/core/src/logical_plan/mod.rs +++ b/datafusion/core/src/logical_plan/mod.rs @@ -41,12 +41,12 @@ pub use expr::{ avg, bit_length, btrim, call_fn, case, ceil, character_length, chr, coalesce, col, columnize_expr, combine_filters, concat, concat_expr, concat_ws, concat_ws_expr, cos, count, count_distinct, create_udaf, create_udf, date_part, date_trunc, digest, - exists, exp, exprlist_to_fields, floor, in_list, in_subquery, initcap, - left, length, lit, lit_timestamp_nano, ln, log10, log2, lower, lpad, ltrim, max, md5, - min, not_exists, not_in_subquery, now, now_expr, nullif, octet_length, or, power, - random, regexp_match, regexp_replace, repeat, replace, reverse, right, round, rpad, - rtrim, scalar_subquery, sha224, sha256, sha384, sha512, signum, sin, split_part, - sqrt, starts_with, strpos, substr, sum, tan, to_hex, to_timestamp_micros, + exists, exp, exprlist_to_fields, floor, in_list, in_subquery, initcap, left, length, + lit, lit_timestamp_nano, ln, log10, log2, lower, lpad, ltrim, max, md5, min, + not_exists, not_in_subquery, now, now_expr, nullif, octet_length, or, power, random, + regexp_match, regexp_replace, repeat, replace, reverse, right, round, rpad, rtrim, + scalar_subquery, sha224, sha256, sha384, sha512, signum, sin, split_part, sqrt, + starts_with, strpos, substr, sum, tan, to_hex, to_timestamp_micros, to_timestamp_millis, to_timestamp_seconds, translate, trim, trunc, unalias, upper, when, Column, Expr, ExprSchema, Literal, }; diff --git a/datafusion/core/src/optimizer/common_subexpr_eliminate.rs b/datafusion/core/src/optimizer/common_subexpr_eliminate.rs index b4010542ea80..967ef58b39c4 100644 --- a/datafusion/core/src/optimizer/common_subexpr_eliminate.rs +++ b/datafusion/core/src/optimizer/common_subexpr_eliminate.rs @@ -487,26 +487,26 @@ impl ExprIdentifierVisitor<'_> { GroupingSet::Rollup(exprs) => { desc.push_str("Rollup"); for expr in exprs { - desc.push_str("-"); + desc.push('-'); desc.push_str(&Self::desc_expr(expr)); } } GroupingSet::Cube(exprs) => { desc.push_str("Cube"); for expr in exprs { - desc.push_str("-"); + desc.push('-'); desc.push_str(&Self::desc_expr(expr)); } } GroupingSet::GroupingSets(lists_of_exprs) => { desc.push_str("GroupingSets"); for exprs in lists_of_exprs { - desc.push_str("("); + desc.push('('); for expr in exprs { - desc.push_str("-"); + desc.push('-'); desc.push_str(&Self::desc_expr(expr)); } - desc.push_str(")"); + desc.push(')'); } } }, diff --git a/datafusion/core/src/optimizer/projection_push_down.rs b/datafusion/core/src/optimizer/projection_push_down.rs index 6f3ea39ac317..0979d8f5b218 100644 --- a/datafusion/core/src/optimizer/projection_push_down.rs +++ b/datafusion/core/src/optimizer/projection_push_down.rs @@ -506,7 +506,9 @@ mod tests { use std::collections::HashMap; use super::*; - use crate::logical_plan::{col, lit, max, min, Expr, JoinType, LogicalPlanBuilder, exprlist_to_fields}; + use crate::logical_plan::{ + col, exprlist_to_fields, lit, max, min, Expr, JoinType, LogicalPlanBuilder, + }; use crate::test::*; use arrow::datatypes::DataType; @@ -808,8 +810,7 @@ mod tests { // that the Column references are unqualified (e.g. their // relation is `None`). PlanBuilder resolves the expressions let expr = vec![col("a"), col("b")]; - let projected_fields = - exprlist_to_fields(&expr, &table_scan).unwrap(); + let projected_fields = exprlist_to_fields(&expr, &table_scan).unwrap(); let projected_schema = DFSchema::new_with_metadata( projected_fields, input_schema.metadata().clone(), diff --git a/datafusion/core/src/sql/planner.rs b/datafusion/core/src/sql/planner.rs index 06a783c89fae..f97a18380b22 100644 --- a/datafusion/core/src/sql/planner.rs +++ b/datafusion/core/src/sql/planner.rs @@ -1161,10 +1161,10 @@ impl<'a, S: ContextProvider> SqlToRel<'a, S> { for expr in &group_by_exprs { match expr { Expr::GroupingSet(GroupingSet::Rollup(exprs)) => { - aggr_projection_exprs.extend_from_slice(&exprs) + aggr_projection_exprs.extend_from_slice(exprs) } Expr::GroupingSet(GroupingSet::Cube(exprs)) => { - aggr_projection_exprs.extend_from_slice(&exprs) + aggr_projection_exprs.extend_from_slice(exprs) } Expr::GroupingSet(GroupingSet::GroupingSets(lists_of_exprs)) => { for exprs in lists_of_exprs { From 81c6677855de0317b0673347abd0df6b54cf07a7 Mon Sep 17 00:00:00 2001 From: Andy Grove Date: Fri, 6 May 2022 13:30:21 -0600 Subject: [PATCH 6/8] fmt and clippy --- datafusion/core/src/logical_plan/expr.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/datafusion/core/src/logical_plan/expr.rs b/datafusion/core/src/logical_plan/expr.rs index ef32dd3099b8..3ffc1894e554 100644 --- a/datafusion/core/src/logical_plan/expr.rs +++ b/datafusion/core/src/logical_plan/expr.rs @@ -22,6 +22,7 @@ pub use super::Operator; use crate::error::Result; use crate::logical_plan::ExprSchemable; use crate::logical_plan::{DFField, DFSchema}; +use crate::sql::utils::find_columns_referenced_by_expr; use arrow::datatypes::DataType; pub use datafusion_common::{Column, ExprSchema}; pub use datafusion_expr::expr_fn::*; @@ -35,7 +36,6 @@ use datafusion_expr::{ ReturnTypeFunction, ScalarFunctionImplementation, Signature, Volatility, }; use std::sync::Arc; -use crate::sql::utils::find_columns_referenced_by_expr; /// Combines an array of filter expressions into a single filter expression /// consisting of the input filter expressions joined with logical AND. @@ -143,7 +143,11 @@ pub fn exprlist_to_fields<'a>( ) -> Result> { match plan { LogicalPlan::Aggregate(agg) => { - let group_expr: Vec = agg.group_expr.iter().flat_map(|expr| find_columns_referenced_by_expr(expr)).collect(); + let group_expr: Vec = agg + .group_expr + .iter() + .flat_map(find_columns_referenced_by_expr) + .collect(); let exprs: Vec = expr.into_iter().cloned().collect(); let mut fields = vec![]; for expr in &exprs { From 5520e66c53d4c9c5505123168a0b107412d4d7ea Mon Sep 17 00:00:00 2001 From: Andy Grove Date: Fri, 6 May 2022 13:37:11 -0600 Subject: [PATCH 7/8] revert change --- datafusion/expr/src/logical_plan/plan.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/datafusion/expr/src/logical_plan/plan.rs b/datafusion/expr/src/logical_plan/plan.rs index d3237691fa0b..579898dbe207 100644 --- a/datafusion/expr/src/logical_plan/plan.rs +++ b/datafusion/expr/src/logical_plan/plan.rs @@ -19,7 +19,7 @@ use crate::logical_plan::display::{GraphvizVisitor, IndentVisitor}; use crate::logical_plan::extension::UserDefinedLogicalNode; use crate::{Expr, TableProviderFilterPushDown, TableSource}; use arrow::datatypes::{DataType, Field, Schema, SchemaRef}; -use datafusion_common::{Column, DFSchemaRef, DataFusionError, Result}; +use datafusion_common::{Column, DFSchemaRef, DataFusionError}; use std::collections::HashSet; ///! Logical plan types use std::fmt::{self, Debug, Display, Formatter}; @@ -281,7 +281,7 @@ impl LogicalPlan { } /// returns all `Using` join columns in a logical plan - pub fn using_columns(&self) -> Result>> { + pub fn using_columns(&self) -> Result>, DataFusionError> { struct UsingJoinColumnVisitor { using_columns: Vec>, } @@ -289,10 +289,7 @@ impl LogicalPlan { impl PlanVisitor for UsingJoinColumnVisitor { type Error = DataFusionError; - fn pre_visit( - &mut self, - plan: &LogicalPlan, - ) -> std::result::Result { + fn pre_visit(&mut self, plan: &LogicalPlan) -> Result { if let LogicalPlan::Join(Join { join_constraint: JoinConstraint::Using, on, From 469e39b862328455fa39eeb8acd7821ec7240178 Mon Sep 17 00:00:00 2001 From: Andy Grove Date: Fri, 6 May 2022 15:24:58 -0600 Subject: [PATCH 8/8] clippy --- datafusion/core/src/sql/planner.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/datafusion/core/src/sql/planner.rs b/datafusion/core/src/sql/planner.rs index 136e8783f96b..af8329018f67 100644 --- a/datafusion/core/src/sql/planner.rs +++ b/datafusion/core/src/sql/planner.rs @@ -4682,7 +4682,7 @@ mod tests { let expected = "Projection: #person.id, #person.state, #person.age, #COUNT(UInt8(1))\ \n Aggregate: groupBy=[[#person.id, ROLLUP (#person.state, #person.age)]], aggr=[[COUNT(UInt8(1))]]\ \n TableScan: person projection=None"; - quick_test(sql, &expected); + quick_test(sql, expected); } #[tokio::test] @@ -4692,7 +4692,7 @@ mod tests { let expected = "Projection: #person.id, #person.state, #person.age, #COUNT(UInt8(1))\ \n Aggregate: groupBy=[[#person.id, CUBE (#person.state, #person.age)]], aggr=[[COUNT(UInt8(1))]]\ \n TableScan: person projection=None"; - quick_test(sql, &expected); + quick_test(sql, expected); } #[ignore] // see https://github.com/apache/arrow-datafusion/issues/2469 @@ -4700,7 +4700,7 @@ mod tests { async fn aggregate_with_grouping_sets() { let sql = "SELECT id, state, age, COUNT(*) FROM person GROUP BY id, GROUPING SETS ((state), (state, age), (id, state))"; let expected = "TBD"; - quick_test(sql, &expected); + quick_test(sql, expected); } fn assert_field_not_found(err: DataFusionError, name: &str) {