@@ -65,8 +65,8 @@ use arrow::datatypes::{Schema, SchemaRef};
6565use datafusion_common:: display:: ToStringifiedPlan ;
6666use datafusion_common:: tree_node:: { TreeNode , TreeNodeRecursion , TreeNodeVisitor } ;
6767use datafusion_common:: {
68- exec_err, internal_datafusion_err, internal_err, not_impl_err, plan_err, DFSchema , DFSchemaRef ,
69- ScalarValue , Column , TableReference ,
68+ exec_err, internal_datafusion_err, internal_err, not_impl_err, plan_err, DFSchema ,
69+ ScalarValue , Column ,
7070} ;
7171use datafusion_datasource:: memory:: MemorySourceConfig ;
7272use datafusion_expr:: dml:: { CopyTo , InsertOp , DmlStatement , WriteOp } ;
@@ -76,12 +76,7 @@ use datafusion_expr::expr::{
7676} ;
7777use datafusion_expr:: expr_rewriter:: unnormalize_cols;
7878use datafusion_expr:: logical_plan:: builder:: wrap_projection_for_join_if_necessary;
79- use datafusion_expr:: {
80- Analyze , DescribeTable , Explain , ExplainFormat , Extension , FetchType ,
81- Filter , JoinType , RecursiveQuery , SkipType , SortExpr , StringifiedPlan , WindowFrame ,
82- WindowFrameBound , SubqueryAlias ,
83- } ;
84- use datafusion_execution:: FunctionRegistry ;
79+ use datafusion_expr:: { Analyze , DescribeTable , Explain , ExplainFormat , Extension , FetchType , Filter , JoinType , RecursiveQuery , SkipType , SortExpr , StringifiedPlan , WindowFrame , WindowFrameBound , LogicalPlanBuilder , BinaryExpr } ;
8580use datafusion_physical_expr:: aggregate:: { AggregateExprBuilder , AggregateFunctionExpr } ;
8681use datafusion_physical_expr:: expressions:: Literal ;
8782use datafusion_physical_expr:: LexOrdering ;
@@ -97,8 +92,7 @@ use itertools::{multiunzip, Itertools};
9792use log:: { debug, trace} ;
9893use sqlparser:: ast:: NullTreatment ;
9994use tokio:: sync:: Mutex ;
100-
101- use datafusion_sql:: transform_pivot_to_aggregate;
95+ use datafusion_expr_common:: operator:: Operator ;
10296
10397use datafusion_physical_plan:: collect;
10498
@@ -309,10 +303,8 @@ impl DefaultPhysicalPlanner {
309303 }
310304 1 => NodeState :: ZeroOrOneChild ,
311305 _ => {
312-
313306 let ready_children = Vec :: with_capacity ( node. inputs ( ) . len ( ) ) ;
314307 let ready_children = Mutex :: new ( ready_children) ;
315-
316308 NodeState :: TwoOrMoreChildren ( ready_children)
317309 }
318310 } ;
@@ -924,21 +916,18 @@ impl DefaultPhysicalPlanner {
924916 } else {
925917 pivot. pivot_values . clone ( )
926918 } ;
927-
928- if !pivot_values. is_empty ( ) {
929- // Transform Pivot into Aggregate plan with the resolved pivot values
919+
920+ return if !pivot_values. is_empty ( ) {
930921 let agg_plan = transform_pivot_to_aggregate (
931922 Arc :: new ( pivot. input . as_ref ( ) . clone ( ) ) ,
932923 & pivot. aggregate_expr ,
933924 & pivot. pivot_column ,
934- Some ( pivot_values) ,
935- None ,
925+ pivot_values,
936926 ) ?;
937-
938- // The schema information is already preserved in the agg_plan
939- return self . create_physical_plan ( & agg_plan, session_state) . await ;
927+
928+ self . create_physical_plan ( & agg_plan, session_state) . await
940929 } else {
941- return plan_err ! ( "PIVOT operation requires at least one value to pivot on" ) ;
930+ plan_err ! ( "PIVOT operation requires at least one value to pivot on" )
942931 }
943932 }
944933 // 2 Children
@@ -1764,6 +1753,76 @@ pub fn create_physical_sort_exprs(
17641753 . collect :: < Result < LexOrdering > > ( )
17651754}
17661755
1756+ /// Transform a PIVOT operation into a more standard Aggregate + Projection plan
1757+ /// For known pivot values, we create a projection that includes "IS NOT DISTINCT FROM" conditions
1758+ ///
1759+ /// For example, for SUM(amount) PIVOT(quarter FOR quarter in ('2023_Q1', '2023_Q2')), we create:
1760+ /// - SUM(amount) FILTER (WHERE quarter IS NOT DISTINCT FROM '2023_Q1') AS "2023_Q1"
1761+ /// - SUM(amount) FILTER (WHERE quarter IS NOT DISTINCT FROM '2023_Q2') AS "2023_Q2"
1762+ ///
1763+ pub fn transform_pivot_to_aggregate (
1764+ input : Arc < LogicalPlan > ,
1765+ aggregate_expr : & Expr ,
1766+ pivot_column : & Column ,
1767+ pivot_values : Vec < ScalarValue > ,
1768+ ) -> Result < LogicalPlan > {
1769+ let df_schema = input. schema ( ) ;
1770+
1771+ let all_columns: Vec < Column > = df_schema. columns ( ) ;
1772+
1773+ // Filter to include only columns we want for GROUP BY
1774+ // (exclude pivot column and aggregate expression columns)
1775+ let group_by_columns: Vec < Expr > = all_columns
1776+ . into_iter ( )
1777+ . filter ( |col| {
1778+ col. name != pivot_column. name
1779+ && !aggregate_expr. column_refs ( ) . iter ( ) . any ( |agg_col| agg_col. name == col. name )
1780+ } )
1781+ . map ( |col| Expr :: Column ( col) )
1782+ . collect ( ) ;
1783+
1784+ let builder = LogicalPlanBuilder :: from ( Arc :: unwrap_or_clone ( input. clone ( ) ) ) ;
1785+
1786+ let mut aggregate_exprs = Vec :: new ( ) ;
1787+
1788+ for value in & pivot_values {
1789+ let filter_condition = Expr :: BinaryExpr ( BinaryExpr :: new (
1790+ Box :: new ( Expr :: Column ( pivot_column. clone ( ) ) ) ,
1791+ Operator :: IsNotDistinctFrom ,
1792+ Box :: new ( Expr :: Literal ( value. clone ( ) ) )
1793+ ) ) ;
1794+
1795+ let filtered_agg = match aggregate_expr {
1796+ Expr :: AggregateFunction ( agg) => {
1797+ let mut new_params = agg. params . clone ( ) ;
1798+ new_params. filter = Some ( Box :: new ( filter_condition) ) ;
1799+ Expr :: AggregateFunction ( AggregateFunction {
1800+ func : agg. func . clone ( ) ,
1801+ params : new_params,
1802+ } )
1803+ } ,
1804+ _ => {
1805+ return plan_err ! ( "Unsupported aggregate expression should always be AggregateFunction" ) ;
1806+ }
1807+ } ;
1808+
1809+ // Use the pivot value as the column name
1810+ let field_name = value. to_string ( ) . trim_matches ( '\'' ) . to_string ( ) ;
1811+ let aliased_agg = Expr :: Alias ( Alias {
1812+ expr : Box :: new ( filtered_agg) ,
1813+ relation : None ,
1814+ name : field_name,
1815+ metadata : None ,
1816+ } ) ;
1817+
1818+ aggregate_exprs. push ( aliased_agg) ;
1819+ }
1820+
1821+ let aggregate_plan = builder. aggregate ( group_by_columns, aggregate_exprs) ?. build ( ) ?;
1822+
1823+ Ok ( aggregate_plan)
1824+ }
1825+
17671826impl DefaultPhysicalPlanner {
17681827 /// Handles capturing the various plans for EXPLAIN queries
17691828 ///
0 commit comments