1616// under the License.
1717
1818use std:: any:: Any ;
19+ use std:: fmt;
1920use std:: fmt:: { Debug , Display , Formatter } ;
2021use std:: hash:: { Hash , Hasher } ;
2122use std:: sync:: Arc ;
@@ -53,6 +54,12 @@ pub type PhysicalExprRef = Arc<dyn PhysicalExpr>;
5354/// * [`SessionContext::create_physical_expr`]: A high level API
5455/// * [`create_physical_expr`]: A low level API
5556///
57+ /// # Formatting `PhysicalExpr` as strings
58+ /// There are three ways to format `PhysicalExpr` as a string:
59+ /// * [`Debug`]: Standard Rust debugging format (e.g. `Constant { value: ... }`)
60+ /// * [`Display`]: Detailed SQL-like format that shows expression structure (e.g. (`Utf8 ("foobar")`). This is often used for debugging and tests
61+ /// * [`Self::fmt_sql`]: SQL-like human readable format (e.g. ('foobar')`), See also [`sql_fmt`]
62+ ///
5663/// [`SessionContext::create_physical_expr`]: https://docs.rs/datafusion/latest/datafusion/execution/context/struct.SessionContext.html#method.create_physical_expr
5764/// [`PhysicalPlanner`]: https://docs.rs/datafusion/latest/datafusion/physical_planner/trait.PhysicalPlanner.html
5865/// [`Expr`]: https://docs.rs/datafusion/latest/datafusion/logical_expr/enum.Expr.html
@@ -266,6 +273,16 @@ pub trait PhysicalExpr: Send + Sync + Display + Debug + DynEq + DynHash {
266273 fn get_properties ( & self , _children : & [ ExprProperties ] ) -> Result < ExprProperties > {
267274 Ok ( ExprProperties :: new_unknown ( ) )
268275 }
276+
277+ /// Format this `PhysicalExpr` in nice human readable "SQL" format
278+ ///
279+ /// Specifically, this format is designed to be readable by humans, at the
280+ /// expense of details. Use `Display` or `Debug` for more detailed
281+ /// representation.
282+ ///
283+ /// See the [`fmt_sql`] function for an example of printing `PhysicalExpr`s as SQL.
284+ ///
285+ fn fmt_sql ( & self , f : & mut Formatter < ' _ > ) -> fmt:: Result ;
269286}
270287
271288/// [`PhysicalExpr`] can't be constrained by [`Eq`] directly because it must remain object
@@ -363,7 +380,7 @@ where
363380 I : Iterator + Clone ,
364381 I :: Item : Display ,
365382 {
366- fn fmt ( & self , f : & mut Formatter < ' _ > ) -> std :: fmt:: Result {
383+ fn fmt ( & self , f : & mut Formatter < ' _ > ) -> fmt:: Result {
367384 let mut iter = self . 0 . clone ( ) ;
368385 write ! ( f, "[" ) ?;
369386 if let Some ( expr) = iter. next ( ) {
@@ -379,3 +396,53 @@ where
379396
380397 DisplayWrapper ( exprs. into_iter ( ) )
381398}
399+
400+ /// Prints a [`PhysicalExpr`] in a SQL-like format
401+ ///
402+ /// # Example
403+ /// ```
404+ /// # // The boiler plate needed to create a `PhysicalExpr` for the example
405+ /// # use std::any::Any;
406+ /// # use std::fmt::Formatter;
407+ /// # use std::sync::Arc;
408+ /// # use arrow::array::RecordBatch;
409+ /// # use arrow::datatypes::{DataType, Schema};
410+ /// # use datafusion_common::Result;
411+ /// # use datafusion_expr_common::columnar_value::ColumnarValue;
412+ /// # use datafusion_physical_expr_common::physical_expr::{fmt_sql, DynEq, PhysicalExpr};
413+ /// # #[derive(Debug, Hash, PartialOrd, PartialEq)]
414+ /// # struct MyExpr {};
415+ /// # impl PhysicalExpr for MyExpr {fn as_any(&self) -> &dyn Any { unimplemented!() }
416+ /// # fn data_type(&self, input_schema: &Schema) -> Result<DataType> { unimplemented!() }
417+ /// # fn nullable(&self, input_schema: &Schema) -> Result<bool> { unimplemented!() }
418+ /// # fn evaluate(&self, batch: &RecordBatch) -> Result<ColumnarValue> { unimplemented!() }
419+ /// # fn children(&self) -> Vec<&Arc<dyn PhysicalExpr>>{ unimplemented!() }
420+ /// # fn with_new_children(self: Arc<Self>, children: Vec<Arc<dyn PhysicalExpr>>) -> Result<Arc<dyn PhysicalExpr>> { unimplemented!() }
421+ /// # fn fmt_sql(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "CASE a > b THEN 1 ELSE 0 END") }
422+ /// # }
423+ /// # impl std::fmt::Display for MyExpr {fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { unimplemented!() } }
424+ /// # impl DynEq for MyExpr {fn dyn_eq(&self, other: &dyn Any) -> bool { unimplemented!() } }
425+ /// # fn make_physical_expr() -> Arc<dyn PhysicalExpr> { Arc::new(MyExpr{}) }
426+ /// let expr: Arc<dyn PhysicalExpr> = make_physical_expr();
427+ /// // wrap the expression in `sql_fmt` which can be used with
428+ /// // `format!`, `to_string()`, etc
429+ /// let expr_as_sql = fmt_sql(expr.as_ref());
430+ /// assert_eq!(
431+ /// "The SQL: CASE a > b THEN 1 ELSE 0 END",
432+ /// format!("The SQL: {expr_as_sql}")
433+ /// );
434+ /// ```
435+ pub fn fmt_sql ( expr : & dyn PhysicalExpr ) -> impl Display + ' _ {
436+ struct Wrapper < ' a > {
437+ expr : & ' a dyn PhysicalExpr ,
438+ }
439+
440+ impl Display for Wrapper < ' _ > {
441+ fn fmt ( & self , f : & mut Formatter ) -> fmt:: Result {
442+ self . expr . fmt_sql ( f) ?;
443+ Ok ( ( ) )
444+ }
445+ }
446+
447+ Wrapper { expr }
448+ }
0 commit comments