-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Simplify display format of AggregateFunctionExpr
, add Expr::sql_name
#15253
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f8dd65a
03e33a4
3610bf5
38d34e2
3e8f1d0
5929ef4
f13a0ca
2720261
316ab9b
202a1d2
f0e0798
b7e985d
acc0dc5
af13820
e1bdb5f
76eb7f9
4b9c89e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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<'_> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We already have code to convert from However, datafusion-expr doesn't currently depend on the datafusion-sql crate which is probably a good thing So I think we should add a note in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the main difference between There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's true, it's redundant for most cases. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we should take nested expr into consideration, like |
||
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, ", ") | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added this section and the one below to try and explain the uses of the different explain variants.