Skip to content

Commit

Permalink
Implement prettier SQL unparsing (more human readable) (apache#11186)
Browse files Browse the repository at this point in the history
* initial prettier unparse

* bug fix

* handling minus and divide

* cleaning references and comments

* moved tests

* Update precedence of BETWEEN

* rerun CI

* Change precedence to match PGSQLs

* more pretty unparser tests

* Update operator precedence to match latest PGSQL

* directly prettify expr_to_sql

* handle IS operator

* correct IS precedence

* update unparser tests

* update unparser example

* update more unparser examples

* add with_pretty builder to unparser
  • Loading branch information
MohamedAbdeen21 authored and xinlifoobar committed Jul 17, 2024
1 parent 7fd73f0 commit f52724c
Show file tree
Hide file tree
Showing 6 changed files with 319 additions and 76 deletions.
9 changes: 9 additions & 0 deletions datafusion-examples/examples/parse_sql_expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,5 +153,14 @@ async fn round_trip_parse_sql_expr_demo() -> Result<()> {

assert_eq!(sql, round_trip_sql);

// enable pretty-unparsing. This make the output more human-readable
// but can be problematic when passed to other SQL engines due to
// difference in precedence rules between DataFusion and target engines.
let unparser = Unparser::default().with_pretty(true);

let pretty = "int_col < 5 OR double_col = 8";
let pretty_round_trip_sql = unparser.expr_to_sql(&parsed_expr)?.to_string();
assert_eq!(pretty, pretty_round_trip_sql);

Ok(())
}
18 changes: 15 additions & 3 deletions datafusion-examples/examples/plan_to_sql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ use datafusion_sql::unparser::{plan_to_sql, Unparser};
/// 1. [`simple_expr_to_sql_demo`]: Create a simple expression [`Exprs`] with
/// fluent API and convert to sql suitable for passing to another database
///
/// 2. [`simple_expr_to_sql_demo_no_escape`] Create a simple expression
/// [`Exprs`] with fluent API and convert to sql without escaping column names
/// more suitable for displaying to humans.
/// 2. [`simple_expr_to_pretty_sql_demo`] Create a simple expression
/// [`Exprs`] with fluent API and convert to sql without extra parentheses,
/// suitable for displaying to humans
///
/// 3. [`simple_expr_to_sql_demo_escape_mysql_style`]" Create a simple
/// expression [`Exprs`] with fluent API and convert to sql escaping column
Expand All @@ -49,6 +49,7 @@ use datafusion_sql::unparser::{plan_to_sql, Unparser};
async fn main() -> Result<()> {
// See how to evaluate expressions
simple_expr_to_sql_demo()?;
simple_expr_to_pretty_sql_demo()?;
simple_expr_to_sql_demo_escape_mysql_style()?;
simple_plan_to_sql_demo().await?;
round_trip_plan_to_sql_demo().await?;
Expand All @@ -64,6 +65,17 @@ fn simple_expr_to_sql_demo() -> Result<()> {
Ok(())
}

/// DataFusioon can remove parentheses when converting an expression to SQL.
/// Note that output is intended for humans, not for other SQL engines,
/// as difference in precedence rules can cause expressions to be parsed differently.
fn simple_expr_to_pretty_sql_demo() -> Result<()> {
let expr = col("a").lt(lit(5)).or(col("a").eq(lit(8)));
let unparser = Unparser::default().with_pretty(true);
let sql = unparser.expr_to_sql(&expr)?.to_string();
assert_eq!(sql, r#"a < 5 OR a = 8"#);
Ok(())
}

/// DataFusion can convert expressions to SQL without escaping column names using
/// using a custom dialect and an explicit unparser
fn simple_expr_to_sql_demo_escape_mysql_style() -> Result<()> {
Expand Down
24 changes: 10 additions & 14 deletions datafusion/expr/src/operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,37 +218,33 @@ impl Operator {
}

/// Get the operator precedence
/// use <https://www.postgresql.org/docs/7.0/operators.htm#AEN2026> as a reference
/// use <https://www.postgresql.org/docs/7.2/sql-precedence.html> as a reference
pub fn precedence(&self) -> u8 {
match self {
Operator::Or => 5,
Operator::And => 10,
Operator::NotEq
| Operator::Eq
| Operator::Lt
| Operator::LtEq
| Operator::Gt
| Operator::GtEq => 20,
Operator::Plus | Operator::Minus => 30,
Operator::Multiply | Operator::Divide | Operator::Modulo => 40,
Operator::Eq | Operator::NotEq | Operator::LtEq | Operator::GtEq => 15,
Operator::Lt | Operator::Gt => 20,
Operator::LikeMatch
| Operator::NotLikeMatch
| Operator::ILikeMatch
| Operator::NotILikeMatch => 25,
Operator::IsDistinctFrom
| Operator::IsNotDistinctFrom
| Operator::RegexMatch
| Operator::RegexNotMatch
| Operator::RegexIMatch
| Operator::RegexNotIMatch
| Operator::LikeMatch
| Operator::ILikeMatch
| Operator::NotLikeMatch
| Operator::NotILikeMatch
| Operator::BitwiseAnd
| Operator::BitwiseOr
| Operator::BitwiseShiftLeft
| Operator::BitwiseShiftRight
| Operator::BitwiseXor
| Operator::StringConcat
| Operator::AtArrow
| Operator::ArrowAt => 0,
| Operator::ArrowAt => 30,
Operator::Plus | Operator::Minus => 40,
Operator::Multiply | Operator::Divide | Operator::Modulo => 45,
}
}
}
Expand Down
Loading

0 comments on commit f52724c

Please sign in to comment.