You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datafusion.apache.org by al...@apache.org on 2025/03/20 14:54:43 UTC

(datafusion) branch main updated: Simplify display format of `AggregateFunctionExpr`, add `Expr::sql_name` (#15253)

This is an automated email from the ASF dual-hosted git repository.

alamb pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion.git


The following commit(s) were added to refs/heads/main by this push:
     new 11838be5ef Simplify display format of `AggregateFunctionExpr`, add `Expr::sql_name` (#15253)
11838be5ef is described below

commit 11838be5efa8607f762504c198615c54575b0593
Author: irenjj <re...@gmail.com>
AuthorDate: Thu Mar 20 22:54:37 2025 +0800

    Simplify display format of `AggregateFunctionExpr`, add `Expr::sql_name` (#15253)
    
    * Simplify display format of `AggregateFunctionExpr`
    
    * add more info for SqlDisplay
    
    * simplify aggr expr
    
    * add doc
    
    * add table name
    
    * fix issues
    
    * Update datafusion/physical-expr/src/aggregate.rs
    
    Co-authored-by: Andrew Lamb <an...@nerdnetworks.org>
    
    * Update datafusion/physical-expr/src/aggregate.rs
    
    Co-authored-by: Andrew Lamb <an...@nerdnetworks.org>
    
    * Update datafusion/physical-plan/src/aggregates/mod.rs
    
    Co-authored-by: Andrew Lamb <an...@nerdnetworks.org>
    
    * Update datafusion/physical-plan/src/aggregates/mod.rs
    
    Co-authored-by: Andrew Lamb <an...@nerdnetworks.org>
    
    * fmt
    
    * fix build
    
    * improve docs
    
    * Add documentation about sql_format
    
    * Rename to `human_display`
    
    ---------
    
    Co-authored-by: Andrew Lamb <an...@nerdnetworks.org>
---
 datafusion/core/src/physical_planner.rs            |  17 +-
 datafusion/expr/src/expr.rs                        | 298 ++++++++++++++++++++-
 datafusion/expr/src/udaf.rs                        |  48 +++-
 datafusion/physical-expr/src/aggregate.rs          |  18 ++
 datafusion/physical-plan/src/aggregates/mod.rs     |  37 ++-
 .../sqllogictest/test_files/explain_tree.slt       | 216 +++++++--------
 6 files changed, 500 insertions(+), 134 deletions(-)

diff --git a/datafusion/core/src/physical_planner.rs b/datafusion/core/src/physical_planner.rs
index 135b32a0a8..bc3c1d0ac9 100644
--- a/datafusion/core/src/physical_planner.rs
+++ b/datafusion/core/src/physical_planner.rs
@@ -1588,6 +1588,7 @@ type AggregateExprWithOptionalArgs = (
 pub fn create_aggregate_expr_with_name_and_maybe_filter(
     e: &Expr,
     name: Option<String>,
+    human_displan: String,
     logical_input_schema: &DFSchema,
     physical_input_schema: &Schema,
     execution_props: &ExecutionProps,
@@ -1642,6 +1643,7 @@ pub fn create_aggregate_expr_with_name_and_maybe_filter(
                         .order_by(ordering_reqs)
                         .schema(Arc::new(physical_input_schema.to_owned()))
                         .alias(name)
+                        .human_display(human_displan)
                         .with_ignore_nulls(ignore_nulls)
                         .with_distinct(*distinct)
                         .build()
@@ -1664,15 +1666,22 @@ pub fn create_aggregate_expr_and_maybe_filter(
     execution_props: &ExecutionProps,
 ) -> Result<AggregateExprWithOptionalArgs> {
     // unpack (nested) aliased logical expressions, e.g. "sum(col) as total"
-    let (name, e) = match e {
-        Expr::Alias(Alias { expr, name, .. }) => (Some(name.clone()), expr.as_ref()),
-        Expr::AggregateFunction(_) => (Some(e.schema_name().to_string()), e),
-        _ => (None, e),
+    let (name, human_display, e) = match e {
+        Expr::Alias(Alias { expr, name, .. }) => {
+            (Some(name.clone()), String::default(), expr.as_ref())
+        }
+        Expr::AggregateFunction(_) => (
+            Some(e.schema_name().to_string()),
+            e.human_display().to_string(),
+            e,
+        ),
+        _ => (None, String::default(), e),
     };
 
     create_aggregate_expr_with_name_and_maybe_filter(
         e,
         name,
+        human_display,
         logical_input_schema,
         physical_input_schema,
         execution_props,
diff --git a/datafusion/expr/src/expr.rs b/datafusion/expr/src/expr.rs
index 6f110895a4..9f6855b698 100644
--- a/datafusion/expr/src/expr.rs
+++ b/datafusion/expr/src/expr.rs
@@ -64,6 +64,15 @@ use sqlparser::ast::{
 ///
 /// [`ExprFunctionExt`]: crate::expr_fn::ExprFunctionExt
 ///
+/// # Printing Expressions
+///
+/// You can print `Expr`s using the the `Debug` trait, `Display` trait, or
+/// [`Self::human_display`]. See the [examples](#examples-displaying-exprs) below.
+///
+/// If you need  SQL to pass to other systems, consider using [`Unparser`].
+///
+/// [`Unparser`]: https://docs.rs/datafusion/latest/datafusion/sql/unparser/struct.Unparser.html
+///
 /// # Schema Access
 ///
 /// See [`ExprSchemable::get_type`] to access the [`DataType`] and nullability
@@ -76,9 +85,9 @@ use sqlparser::ast::{
 /// `Expr` and [`TreeNode::transform`] can be used to rewrite an expression. See
 /// the examples below and [`TreeNode`] for more information.
 ///
-/// # Examples
+/// # Examples: Creating and Using `Expr`s
 ///
-/// ## Column references and literals
+/// ## Column References and Literals
 ///
 /// [`Expr::Column`] refer to the values of columns and are often created with
 /// the [`col`] function. For example to create an expression `c1` referring to
@@ -104,6 +113,7 @@ use sqlparser::ast::{
 /// // All literals are strongly typed in DataFusion. To make an `i64` 42:
 /// let expr = lit(42i64);
 /// assert_eq!(expr, Expr::Literal(ScalarValue::Int64(Some(42))));
+/// assert_eq!(expr, Expr::Literal(ScalarValue::Int64(Some(42))));
 /// // To make a (typed) NULL:
 /// let expr = Expr::Literal(ScalarValue::Int64(None));
 /// // to make an (untyped) NULL (the optimizer will coerce this to the correct type):
@@ -171,7 +181,51 @@ use sqlparser::ast::{
 /// ]);
 /// ```
 ///
-/// # Visiting and Rewriting `Expr`s
+/// # Examples: Displaying `Exprs`
+///
+/// There are three ways to print an `Expr` depending on the usecase.
+///
+/// ## Use `Debug` trait
+///
+/// Following Rust conventions, the `Debug` implementation prints out the
+/// internal structure of the expression, which is useful for debugging.
+///
+/// ```
+/// # use datafusion_expr::{lit, col};
+/// let expr = col("c1") + lit(42);
+/// assert_eq!(format!("{expr:?}"), "BinaryExpr(BinaryExpr { left: Column(Column { relation: None, name: \"c1\" }), op: Plus, right: Literal(Int32(42)) })");
+/// ```
+///
+/// ## Use the `Display` trait  (detailed expression)
+///
+/// The `Display` implementation prints out the expression in a SQL-like form,
+/// but has additional details such as the data type of literals. This is useful
+/// for understanding the expression in more detail and is used for the low level
+/// [`ExplainFormat::Indent`] explain plan format.
+///
+/// [`ExplainFormat::Indent`]: crate::logical_plan::ExplainFormat::Indent
+///
+/// ```
+/// # use datafusion_expr::{lit, col};
+/// let expr = col("c1") + lit(42);
+/// assert_eq!(format!("{expr}"), "c1 + Int32(42)");
+/// ```
+///
+/// ## Use [`Self::human_display`] (human readable)
+///
+/// [`Self::human_display`]  prints out the expression in a SQL-like form, optimized
+/// for human consumption by end users. It is used for the
+/// [`ExplainFormat::Tree`] explain plan format.
+///
+/// [`ExplainFormat::Tree`]: crate::logical_plan::ExplainFormat::Tree
+///
+///```
+/// # use datafusion_expr::{lit, col};
+/// let expr = col("c1") + lit(42);
+/// assert_eq!(format!("{}", expr.human_display()), "c1 + 42");
+/// ```
+///
+/// # Examples: Visiting and Rewriting `Expr`s
 ///
 /// Here is an example that finds all literals in an `Expr` tree:
 /// ```
@@ -1147,6 +1201,31 @@ impl Expr {
         SchemaDisplay(self)
     }
 
+    /// Human readable display formatting for this expression.
+    ///
+    /// This function is primarily used in printing the explain tree output,
+    /// (e.g. `EXPLAIN FORMAT TREE <query>`), providing a readable format to
+    /// show how expressions are used in physical and logical plans. See the
+    /// [`Expr`] for other ways to format expressions
+    ///
+    /// Note this format is intended for human consumption rather than SQL for
+    /// other systems. If you need  SQL to pass to other systems, consider using
+    /// [`Unparser`].
+    ///
+    /// [`Unparser`]: https://docs.rs/datafusion/latest/datafusion/sql/unparser/struct.Unparser.html
+    ///
+    /// # Example
+    /// ```
+    /// # use datafusion_expr::{col, lit};
+    /// let expr = col("foo") + lit(42);
+    /// // For EXPLAIN output:
+    /// // "foo + 42"
+    /// println!("{}", expr.human_display());
+    /// ```
+    pub fn human_display(&self) -> impl Display + '_ {
+        SqlDisplay(self)
+    }
+
     /// Returns the qualifier and the schema name of this expression.
     ///
     /// Used when the expression forms the output field of a certain plan.
@@ -2596,6 +2675,187 @@ impl Display for SchemaDisplay<'_> {
     }
 }
 
+/// A helper struct for displaying an `Expr` as an SQL-like string.
+struct SqlDisplay<'a>(&'a Expr);
+
+impl Display for SqlDisplay<'_> {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        match self.0 {
+            Expr::Literal(scalar) => scalar.fmt(f),
+            Expr::Alias(Alias { name, .. }) => write!(f, "{name}"),
+            Expr::Between(Between {
+                expr,
+                negated,
+                low,
+                high,
+            }) => {
+                if *negated {
+                    write!(
+                        f,
+                        "{} NOT BETWEEN {} AND {}",
+                        SqlDisplay(expr),
+                        SqlDisplay(low),
+                        SqlDisplay(high),
+                    )
+                } else {
+                    write!(
+                        f,
+                        "{} BETWEEN {} AND {}",
+                        SqlDisplay(expr),
+                        SqlDisplay(low),
+                        SqlDisplay(high),
+                    )
+                }
+            }
+            Expr::BinaryExpr(BinaryExpr { left, op, right }) => {
+                write!(f, "{} {op} {}", SqlDisplay(left), SqlDisplay(right),)
+            }
+            Expr::Case(Case {
+                expr,
+                when_then_expr,
+                else_expr,
+            }) => {
+                write!(f, "CASE ")?;
+
+                if let Some(e) = expr {
+                    write!(f, "{} ", SqlDisplay(e))?;
+                }
+
+                for (when, then) in when_then_expr {
+                    write!(f, "WHEN {} THEN {} ", SqlDisplay(when), SqlDisplay(then),)?;
+                }
+
+                if let Some(e) = else_expr {
+                    write!(f, "ELSE {} ", SqlDisplay(e))?;
+                }
+
+                write!(f, "END")
+            }
+            Expr::Cast(Cast { expr, .. }) | Expr::TryCast(TryCast { expr, .. }) => {
+                write!(f, "{}", SqlDisplay(expr))
+            }
+            Expr::InList(InList {
+                expr,
+                list,
+                negated,
+            }) => {
+                write!(
+                    f,
+                    "{}{} IN {}",
+                    SqlDisplay(expr),
+                    if *negated { " NOT" } else { "" },
+                    ExprListDisplay::comma_separated(list.as_slice())
+                )
+            }
+            Expr::GroupingSet(GroupingSet::Cube(exprs)) => {
+                write!(
+                    f,
+                    "ROLLUP ({})",
+                    ExprListDisplay::comma_separated(exprs.as_slice())
+                )
+            }
+            Expr::GroupingSet(GroupingSet::GroupingSets(lists_of_exprs)) => {
+                write!(f, "GROUPING SETS (")?;
+                for exprs in lists_of_exprs.iter() {
+                    write!(
+                        f,
+                        "({})",
+                        ExprListDisplay::comma_separated(exprs.as_slice())
+                    )?;
+                }
+                write!(f, ")")
+            }
+            Expr::GroupingSet(GroupingSet::Rollup(exprs)) => {
+                write!(
+                    f,
+                    "ROLLUP ({})",
+                    ExprListDisplay::comma_separated(exprs.as_slice())
+                )
+            }
+            Expr::IsNull(expr) => write!(f, "{} IS NULL", SqlDisplay(expr)),
+            Expr::IsNotNull(expr) => {
+                write!(f, "{} IS NOT NULL", SqlDisplay(expr))
+            }
+            Expr::IsUnknown(expr) => {
+                write!(f, "{} IS UNKNOWN", SqlDisplay(expr))
+            }
+            Expr::IsNotUnknown(expr) => {
+                write!(f, "{} IS NOT UNKNOWN", SqlDisplay(expr))
+            }
+            Expr::IsTrue(expr) => write!(f, "{} IS TRUE", SqlDisplay(expr)),
+            Expr::IsFalse(expr) => write!(f, "{} IS FALSE", SqlDisplay(expr)),
+            Expr::IsNotTrue(expr) => {
+                write!(f, "{} IS NOT TRUE", SqlDisplay(expr))
+            }
+            Expr::IsNotFalse(expr) => {
+                write!(f, "{} IS NOT FALSE", SqlDisplay(expr))
+            }
+            Expr::Like(Like {
+                negated,
+                expr,
+                pattern,
+                escape_char,
+                case_insensitive,
+            }) => {
+                write!(
+                    f,
+                    "{} {}{} {}",
+                    SqlDisplay(expr),
+                    if *negated { "NOT " } else { "" },
+                    if *case_insensitive { "ILIKE" } else { "LIKE" },
+                    SqlDisplay(pattern),
+                )?;
+
+                if let Some(char) = escape_char {
+                    write!(f, " CHAR '{char}'")?;
+                }
+
+                Ok(())
+            }
+            Expr::Negative(expr) => write!(f, "(- {})", SqlDisplay(expr)),
+            Expr::Not(expr) => write!(f, "NOT {}", SqlDisplay(expr)),
+            Expr::Unnest(Unnest { expr }) => {
+                write!(f, "UNNEST({})", SqlDisplay(expr))
+            }
+            Expr::SimilarTo(Like {
+                negated,
+                expr,
+                pattern,
+                escape_char,
+                ..
+            }) => {
+                write!(
+                    f,
+                    "{} {} {}",
+                    SqlDisplay(expr),
+                    if *negated {
+                        "NOT SIMILAR TO"
+                    } else {
+                        "SIMILAR TO"
+                    },
+                    SqlDisplay(pattern),
+                )?;
+                if let Some(char) = escape_char {
+                    write!(f, " CHAR '{char}'")?;
+                }
+
+                Ok(())
+            }
+            Expr::AggregateFunction(AggregateFunction { func, params }) => {
+                match func.human_display(params) {
+                    Ok(name) => {
+                        write!(f, "{name}")
+                    }
+                    Err(e) => {
+                        write!(f, "got error from schema_name {}", e)
+                    }
+                }
+            }
+            _ => write!(f, "{}", self.0),
+        }
+    }
+}
+
 /// Get schema_name for Vector of expressions
 ///
 /// Internal usage. Please call `schema_name_from_exprs` instead
@@ -2607,6 +2867,38 @@ pub(crate) fn schema_name_from_exprs_comma_separated_without_space(
     schema_name_from_exprs_inner(exprs, ",")
 }
 
+/// Formats a list of `&Expr` with a custom separator using SQL display format
+pub struct ExprListDisplay<'a> {
+    exprs: &'a [Expr],
+    sep: &'a str,
+}
+
+impl<'a> ExprListDisplay<'a> {
+    /// Create a new display struct with the given expressions and separator
+    pub fn new(exprs: &'a [Expr], sep: &'a str) -> Self {
+        Self { exprs, sep }
+    }
+
+    /// Create a new display struct with comma-space separator
+    pub fn comma_separated(exprs: &'a [Expr]) -> Self {
+        Self::new(exprs, ", ")
+    }
+}
+
+impl Display for ExprListDisplay<'_> {
+    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+        let mut first = true;
+        for expr in self.exprs {
+            if !first {
+                write!(f, "{}", self.sep)?;
+            }
+            write!(f, "{}", SqlDisplay(expr))?;
+            first = false;
+        }
+        Ok(())
+    }
+}
+
 /// Get schema_name for Vector of expressions
 pub fn schema_name_from_exprs(exprs: &[Expr]) -> Result<String, fmt::Error> {
     schema_name_from_exprs_inner(exprs, ", ")
diff --git a/datafusion/expr/src/udaf.rs b/datafusion/expr/src/udaf.rs
index f9039cea2e..b75e8fd3cd 100644
--- a/datafusion/expr/src/udaf.rs
+++ b/datafusion/expr/src/udaf.rs
@@ -31,7 +31,7 @@ use datafusion_physical_expr_common::physical_expr::PhysicalExpr;
 
 use crate::expr::{
     schema_name_from_exprs, schema_name_from_exprs_comma_separated_without_space,
-    schema_name_from_sorts, AggregateFunction, AggregateFunctionParams,
+    schema_name_from_sorts, AggregateFunction, AggregateFunctionParams, ExprListDisplay,
     WindowFunctionParams,
 };
 use crate::function::{
@@ -175,6 +175,13 @@ impl AggregateUDF {
         self.inner.schema_name(params)
     }
 
+    /// Returns a human readable expression.
+    ///
+    /// See [`Expr::human_display`] for details.
+    pub fn human_display(&self, params: &AggregateFunctionParams) -> Result<String> {
+        self.inner.human_display(params)
+    }
+
     pub fn window_function_schema_name(
         &self,
         params: &WindowFunctionParams,
@@ -452,6 +459,45 @@ pub trait AggregateUDFImpl: Debug + Send + Sync {
         Ok(schema_name)
     }
 
+    /// Returns a human readable expression.
+    ///
+    /// See [`Expr::human_display`] for details.
+    fn human_display(&self, params: &AggregateFunctionParams) -> Result<String> {
+        let AggregateFunctionParams {
+            args,
+            distinct,
+            filter,
+            order_by,
+            null_treatment,
+        } = params;
+
+        let mut schema_name = String::new();
+
+        schema_name.write_fmt(format_args!(
+            "{}({}{})",
+            self.name(),
+            if *distinct { "DISTINCT " } else { "" },
+            ExprListDisplay::comma_separated(args.as_slice())
+        ))?;
+
+        if let Some(null_treatment) = null_treatment {
+            schema_name.write_fmt(format_args!(" {}", null_treatment))?;
+        }
+
+        if let Some(filter) = filter {
+            schema_name.write_fmt(format_args!(" FILTER (WHERE {filter})"))?;
+        };
+
+        if let Some(order_by) = order_by {
+            schema_name.write_fmt(format_args!(
+                " ORDER BY [{}]",
+                schema_name_from_sorts(order_by)?
+            ))?;
+        };
+
+        Ok(schema_name)
+    }
+
     /// Returns the name of the column this expression would create
     ///
     /// See [`Expr::schema_name`] for details
diff --git a/datafusion/physical-expr/src/aggregate.rs b/datafusion/physical-expr/src/aggregate.rs
index 34c4e52d51..ae3d9050fa 100644
--- a/datafusion/physical-expr/src/aggregate.rs
+++ b/datafusion/physical-expr/src/aggregate.rs
@@ -65,6 +65,8 @@ pub struct AggregateExprBuilder {
     /// Physical expressions of the aggregate function
     args: Vec<Arc<dyn PhysicalExpr>>,
     alias: Option<String>,
+    /// A human readable name
+    human_display: String,
     /// Arrow Schema for the aggregate function
     schema: SchemaRef,
     /// The physical order by expressions
@@ -83,6 +85,7 @@ impl AggregateExprBuilder {
             fun,
             args,
             alias: None,
+            human_display: String::default(),
             schema: Arc::new(Schema::empty()),
             ordering_req: LexOrdering::default(),
             ignore_nulls: false,
@@ -99,6 +102,7 @@ impl AggregateExprBuilder {
             fun,
             args,
             alias,
+            human_display,
             schema,
             ordering_req,
             ignore_nulls,
@@ -148,6 +152,7 @@ impl AggregateExprBuilder {
             args,
             data_type,
             name,
+            human_display,
             schema: Arc::unwrap_or_clone(schema),
             ordering_req,
             ignore_nulls,
@@ -164,6 +169,11 @@ impl AggregateExprBuilder {
         self
     }
 
+    pub fn human_display(mut self, name: String) -> Self {
+        self.human_display = name;
+        self
+    }
+
     pub fn schema(mut self, schema: SchemaRef) -> Self {
         self.schema = schema;
         self
@@ -214,7 +224,10 @@ pub struct AggregateFunctionExpr {
     args: Vec<Arc<dyn PhysicalExpr>>,
     /// Output / return type of this aggregate
     data_type: DataType,
+    /// Output column name that this expression creates
     name: String,
+    /// Simplified name for `tree` explain.
+    human_display: String,
     schema: Schema,
     // The physical order by expressions
     ordering_req: LexOrdering,
@@ -245,6 +258,11 @@ impl AggregateFunctionExpr {
         &self.name
     }
 
+    /// Simplified name for `tree` explain.
+    pub fn human_display(&self) -> &str {
+        &self.human_display
+    }
+
     /// Return if the aggregation is distinct
     pub fn is_distinct(&self) -> bool {
         self.is_distinct
diff --git a/datafusion/physical-plan/src/aggregates/mod.rs b/datafusion/physical-plan/src/aggregates/mod.rs
index 4230eeeed0..84ba0fe3b6 100644
--- a/datafusion/physical-plan/src/aggregates/mod.rs
+++ b/datafusion/physical-plan/src/aggregates/mod.rs
@@ -48,6 +48,7 @@ use datafusion_physical_expr::{
     PhysicalSortRequirement,
 };
 
+use datafusion_physical_expr_common::physical_expr::fmt_sql;
 use itertools::Itertools;
 
 pub(crate) mod group_values;
@@ -742,17 +743,18 @@ impl DisplayAs for AggregateExec {
         t: DisplayFormatType,
         f: &mut std::fmt::Formatter,
     ) -> std::fmt::Result {
-        let format_expr_with_alias =
-            |(e, alias): &(Arc<dyn PhysicalExpr>, String)| -> String {
-                let e = e.to_string();
-                if &e != alias {
-                    format!("{e} as {alias}")
-                } else {
-                    e
-                }
-            };
         match t {
             DisplayFormatType::Default | DisplayFormatType::Verbose => {
+                let format_expr_with_alias =
+                    |(e, alias): &(Arc<dyn PhysicalExpr>, String)| -> String {
+                        let e = e.to_string();
+                        if &e != alias {
+                            format!("{e} as {alias}")
+                        } else {
+                            e
+                        }
+                    };
+
                 write!(f, "AggregateExec: mode={:?}", self.mode)?;
                 let g: Vec<String> = if self.group_by.is_single() {
                     self.group_by
@@ -801,6 +803,16 @@ impl DisplayAs for AggregateExec {
                 }
             }
             DisplayFormatType::TreeRender => {
+                let format_expr_with_alias =
+                    |(e, alias): &(Arc<dyn PhysicalExpr>, String)| -> String {
+                        let expr_sql = fmt_sql(e.as_ref()).to_string();
+                        if &expr_sql != alias {
+                            format!("{expr_sql} as {alias}")
+                        } else {
+                            expr_sql
+                        }
+                    };
+
                 let g: Vec<String> = if self.group_by.is_single() {
                     self.group_by
                         .expr
@@ -830,17 +842,18 @@ impl DisplayAs for AggregateExec {
                         })
                         .collect()
                 };
-                // TODO: Implement `fmt_sql` for `AggregateFunctionExpr`.
                 let a: Vec<String> = self
                     .aggr_expr
                     .iter()
-                    .map(|agg| agg.name().to_string())
+                    .map(|agg| agg.human_display().to_string())
                     .collect();
                 writeln!(f, "mode={:?}", self.mode)?;
                 if !g.is_empty() {
                     writeln!(f, "group_by={}", g.join(", "))?;
                 }
-                writeln!(f, "aggr={}", a.join(", "))?;
+                if !a.is_empty() {
+                    writeln!(f, "aggr={}", a.join(", "))?;
+                }
             }
         }
         Ok(())
diff --git a/datafusion/sqllogictest/test_files/explain_tree.slt b/datafusion/sqllogictest/test_files/explain_tree.slt
index 22b1b46019..aaea05be76 100644
--- a/datafusion/sqllogictest/test_files/explain_tree.slt
+++ b/datafusion/sqllogictest/test_files/explain_tree.slt
@@ -204,53 +204,50 @@ physical_plan
 04)│           aggr:           │
 05)│   sum(table1.bigint_col)  │
 06)│                           │
-07)│         group_by:         │
-08)│ string_col@0 as string_col│
-09)│                           │
-10)│           mode:           │
-11)│      FinalPartitioned     │
-12)└─────────────┬─────────────┘
-13)┌─────────────┴─────────────┐
-14)│    CoalesceBatchesExec    │
-15)│    --------------------   │
-16)│     target_batch_size:    │
-17)│            8192           │
-18)└─────────────┬─────────────┘
-19)┌─────────────┴─────────────┐
-20)│      RepartitionExec      │
-21)│    --------------------   │
-22)│  output_partition_count:  │
-23)│             4             │
-24)│                           │
-25)│    partitioning_scheme:   │
-26)│  Hash([string_col@0], 4)  │
-27)└─────────────┬─────────────┘
-28)┌─────────────┴─────────────┐
-29)│       AggregateExec       │
-30)│    --------------------   │
-31)│           aggr:           │
-32)│   sum(table1.bigint_col)  │
-33)│                           │
-34)│         group_by:         │
-35)│ string_col@0 as string_col│
-36)│                           │
-37)│       mode: Partial       │
-38)└─────────────┬─────────────┘
-39)┌─────────────┴─────────────┐
-40)│      RepartitionExec      │
-41)│    --------------------   │
-42)│  output_partition_count:  │
-43)│             1             │
-44)│                           │
-45)│    partitioning_scheme:   │
-46)│     RoundRobinBatch(4)    │
-47)└─────────────┬─────────────┘
-48)┌─────────────┴─────────────┐
-49)│       DataSourceExec      │
-50)│    --------------------   │
-51)│          files: 1         │
-52)│        format: csv        │
-53)└───────────────────────────┘
+07)│    group_by: string_col   │
+08)│                           │
+09)│           mode:           │
+10)│      FinalPartitioned     │
+11)└─────────────┬─────────────┘
+12)┌─────────────┴─────────────┐
+13)│    CoalesceBatchesExec    │
+14)│    --------------------   │
+15)│     target_batch_size:    │
+16)│            8192           │
+17)└─────────────┬─────────────┘
+18)┌─────────────┴─────────────┐
+19)│      RepartitionExec      │
+20)│    --------------------   │
+21)│  output_partition_count:  │
+22)│             4             │
+23)│                           │
+24)│    partitioning_scheme:   │
+25)│  Hash([string_col@0], 4)  │
+26)└─────────────┬─────────────┘
+27)┌─────────────┴─────────────┐
+28)│       AggregateExec       │
+29)│    --------------------   │
+30)│           aggr:           │
+31)│   sum(table1.bigint_col)  │
+32)│                           │
+33)│    group_by: string_col   │
+34)│       mode: Partial       │
+35)└─────────────┬─────────────┘
+36)┌─────────────┴─────────────┐
+37)│      RepartitionExec      │
+38)│    --------------------   │
+39)│  output_partition_count:  │
+40)│             1             │
+41)│                           │
+42)│    partitioning_scheme:   │
+43)│     RoundRobinBatch(4)    │
+44)└─────────────┬─────────────┘
+45)┌─────────────┴─────────────┐
+46)│       DataSourceExec      │
+47)│    --------------------   │
+48)│          files: 1         │
+49)│        format: csv        │
+50)└───────────────────────────┘
 
 
 # Limit
@@ -1339,7 +1336,7 @@ physical_plan
 12)-----------------------------┌─────────────┴─────────────┐
 13)-----------------------------│       AggregateExec       │
 14)-----------------------------│    --------------------   │
-15)-----------------------------│   aggr: count(Int64(1))   │
+15)-----------------------------│       aggr: count(1)      │
 16)-----------------------------│        mode: Final        │
 17)-----------------------------└─────────────┬─────────────┘
 18)-----------------------------┌─────────────┴─────────────┐
@@ -1348,7 +1345,7 @@ physical_plan
 21)-----------------------------┌─────────────┴─────────────┐
 22)-----------------------------│       AggregateExec       │
 23)-----------------------------│    --------------------   │
-24)-----------------------------│   aggr: count(Int64(1))   │
+24)-----------------------------│       aggr: count(1)      │
 25)-----------------------------│       mode: Partial       │
 26)-----------------------------└─────────────┬─────────────┘
 27)-----------------------------┌─────────────┴─────────────┐
@@ -1477,69 +1474,60 @@ physical_plan
 07)┌─────────────┴─────────────┐
 08)│       AggregateExec       │
 09)│    --------------------   │
-10)│   aggr: count(Int64(1))   │
-11)│                           │
-12)│         group_by:         │
-13)│       name@0 as name      │
-14)│                           │
-15)│           mode:           │
-16)│     SinglePartitioned     │
-17)└─────────────┬─────────────┘
-18)┌─────────────┴─────────────┐
-19)│       InterleaveExec      ├──────────────┐
-20)└─────────────┬─────────────┘              │
-21)┌─────────────┴─────────────┐┌─────────────┴─────────────┐
-22)│       AggregateExec       ││       AggregateExec       │
-23)│    --------------------   ││    --------------------   │
-24)│            aggr           ││            aggr           │
-25)│                           ││                           │
-26)│         group_by:         ││         group_by:         │
-27)│       name@0 as name      ││       name@0 as name      │
-28)│                           ││                           │
-29)│           mode:           ││           mode:           │
-30)│      FinalPartitioned     ││      FinalPartitioned     │
-31)└─────────────┬─────────────┘└─────────────┬─────────────┘
-32)┌─────────────┴─────────────┐┌─────────────┴─────────────┐
-33)│    CoalesceBatchesExec    ││    CoalesceBatchesExec    │
-34)│    --------------------   ││    --------------------   │
-35)│     target_batch_size:    ││     target_batch_size:    │
-36)│            8192           ││            8192           │
-37)└─────────────┬─────────────┘└─────────────┬─────────────┘
-38)┌─────────────┴─────────────┐┌─────────────┴─────────────┐
-39)│      RepartitionExec      ││      RepartitionExec      │
-40)│    --------------------   ││    --------------------   │
-41)│  output_partition_count:  ││  output_partition_count:  │
-42)│             4             ││             4             │
-43)│                           ││                           │
-44)│    partitioning_scheme:   ││    partitioning_scheme:   │
-45)│     Hash([name@0], 4)     ││     Hash([name@0], 4)     │
-46)└─────────────┬─────────────┘└─────────────┬─────────────┘
-47)┌─────────────┴─────────────┐┌─────────────┴─────────────┐
-48)│      RepartitionExec      ││      RepartitionExec      │
-49)│    --------------------   ││    --------------------   │
-50)│  output_partition_count:  ││  output_partition_count:  │
-51)│             1             ││             1             │
-52)│                           ││                           │
-53)│    partitioning_scheme:   ││    partitioning_scheme:   │
-54)│     RoundRobinBatch(4)    ││     RoundRobinBatch(4)    │
-55)└─────────────┬─────────────┘└─────────────┬─────────────┘
-56)┌─────────────┴─────────────┐┌─────────────┴─────────────┐
-57)│       AggregateExec       ││       AggregateExec       │
-58)│    --------------------   ││    --------------------   │
-59)│            aggr           ││            aggr           │
-60)│                           ││                           │
-61)│         group_by:         ││         group_by:         │
-62)│       name@0 as name      ││       name@0 as name      │
-63)│                           ││                           │
-64)│       mode: Partial       ││       mode: Partial       │
-65)└─────────────┬─────────────┘└─────────────┬─────────────┘
-66)┌─────────────┴─────────────┐┌─────────────┴─────────────┐
-67)│       DataSourceExec      ││       DataSourceExec      │
-68)│    --------------------   ││    --------------------   │
-69)│        bytes: 1320        ││        bytes: 1312        │
-70)│       format: memory      ││       format: memory      │
-71)│          rows: 1          ││          rows: 1          │
-72)└───────────────────────────┘└───────────────────────────┘
+10)│       aggr: count(1)      │
+11)│       group_by: name      │
+12)│                           │
+13)│           mode:           │
+14)│     SinglePartitioned     │
+15)└─────────────┬─────────────┘
+16)┌─────────────┴─────────────┐
+17)│       InterleaveExec      ├──────────────┐
+18)└─────────────┬─────────────┘              │
+19)┌─────────────┴─────────────┐┌─────────────┴─────────────┐
+20)│       AggregateExec       ││       AggregateExec       │
+21)│    --------------------   ││    --------------------   │
+22)│       group_by: name      ││       group_by: name      │
+23)│                           ││                           │
+24)│           mode:           ││           mode:           │
+25)│      FinalPartitioned     ││      FinalPartitioned     │
+26)└─────────────┬─────────────┘└─────────────┬─────────────┘
+27)┌─────────────┴─────────────┐┌─────────────┴─────────────┐
+28)│    CoalesceBatchesExec    ││    CoalesceBatchesExec    │
+29)│    --------------------   ││    --------------------   │
+30)│     target_batch_size:    ││     target_batch_size:    │
+31)│            8192           ││            8192           │
+32)└─────────────┬─────────────┘└─────────────┬─────────────┘
+33)┌─────────────┴─────────────┐┌─────────────┴─────────────┐
+34)│      RepartitionExec      ││      RepartitionExec      │
+35)│    --------------------   ││    --------------------   │
+36)│  output_partition_count:  ││  output_partition_count:  │
+37)│             4             ││             4             │
+38)│                           ││                           │
+39)│    partitioning_scheme:   ││    partitioning_scheme:   │
+40)│     Hash([name@0], 4)     ││     Hash([name@0], 4)     │
+41)└─────────────┬─────────────┘└─────────────┬─────────────┘
+42)┌─────────────┴─────────────┐┌─────────────┴─────────────┐
+43)│      RepartitionExec      ││      RepartitionExec      │
+44)│    --------------------   ││    --------------------   │
+45)│  output_partition_count:  ││  output_partition_count:  │
+46)│             1             ││             1             │
+47)│                           ││                           │
+48)│    partitioning_scheme:   ││    partitioning_scheme:   │
+49)│     RoundRobinBatch(4)    ││     RoundRobinBatch(4)    │
+50)└─────────────┬─────────────┘└─────────────┬─────────────┘
+51)┌─────────────┴─────────────┐┌─────────────┴─────────────┐
+52)│       AggregateExec       ││       AggregateExec       │
+53)│    --------------------   ││    --------------------   │
+54)│       group_by: name      ││       group_by: name      │
+55)│       mode: Partial       ││       mode: Partial       │
+56)└─────────────┬─────────────┘└─────────────┬─────────────┘
+57)┌─────────────┴─────────────┐┌─────────────┴─────────────┐
+58)│       DataSourceExec      ││       DataSourceExec      │
+59)│    --------------------   ││    --------------------   │
+60)│        bytes: 1320        ││        bytes: 1312        │
+61)│       format: memory      ││       format: memory      │
+62)│          rows: 1          ││          rows: 1          │
+63)└───────────────────────────┘└───────────────────────────┘
 
 # Test explain tree for UnionExec
 query TT
@@ -1975,7 +1963,7 @@ physical_plan
 07)┌─────────────┴─────────────┐
 08)│       AggregateExec       │
 09)│    --------------------   │
-10)│   aggr: count(Int64(1))   │
+10)│       aggr: count(1)      │
 11)│        mode: Final        │
 12)└─────────────┬─────────────┘
 13)┌─────────────┴─────────────┐
@@ -1984,7 +1972,7 @@ physical_plan
 16)┌─────────────┴─────────────┐
 17)│       AggregateExec       │
 18)│    --------------------   │
-19)│   aggr: count(Int64(1))   │
+19)│       aggr: count(1)      │
 20)│       mode: Partial       │
 21)└─────────────┬─────────────┘
 22)┌─────────────┴─────────────┐


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datafusion.apache.org
For additional commands, e-mail: commits-help@datafusion.apache.org