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 d2f6faf567 Omit NULLS FIRST/LAST when unparsing ORDER BY clauses for 
MySQL (#10625)
d2f6faf567 is described below

commit d2f6faf56784867beafaa2ee88df37ffc3d20720
Author: Phillip LeBlanc <[email protected]>
AuthorDate: Thu May 23 20:40:05 2024 +0900

    Omit NULLS FIRST/LAST when unparsing ORDER BY clauses for MySQL (#10625)
    
    * Omit `NULLS FIRST/LAST` when deparsing ORDER BY clauses for MySQL
    
    * Add test and fix sort_to_sql
    
    * Fix link for cargo doc
    
    * Remove reference to sqlparser-rs PR
---
 datafusion/sql/src/unparser/dialect.rs  | 14 ++++++++-
 datafusion/sql/src/unparser/expr.rs     |  9 +++++-
 datafusion/sql/src/unparser/plan.rs     |  9 +++++-
 datafusion/sql/tests/sql_integration.rs | 52 ++++++++++++++++++++++++++++++++-
 4 files changed, 80 insertions(+), 4 deletions(-)

diff --git a/datafusion/sql/src/unparser/dialect.rs 
b/datafusion/sql/src/unparser/dialect.rs
index 6a0df61ac1..e8cbde0585 100644
--- a/datafusion/sql/src/unparser/dialect.rs
+++ b/datafusion/sql/src/unparser/dialect.rs
@@ -22,11 +22,19 @@ use sqlparser::keywords::ALL_KEYWORDS;
 ///
 /// The default dialect tries to avoid quoting identifiers unless necessary 
(e.g. `a` instead of `"a"`)
 /// but this behavior can be overridden as needed
-/// Note: this trait will eventually be replaced by the Dialect in the 
SQLparser package
+///
+/// **Note**: This trait will eventually be replaced by the Dialect in the 
SQLparser package
 ///
 /// See <https://github.com/sqlparser-rs/sqlparser-rs/pull/1170>
+/// See also the discussion in 
<https://github.com/apache/datafusion/pull/10625>
 pub trait Dialect {
+    /// Return the character used to quote identifiers.
     fn identifier_quote_style(&self, _identifier: &str) -> Option<char>;
+
+    /// Does the dialect support specifying `NULLS FIRST/LAST` in `ORDER BY` 
clauses?
+    fn supports_nulls_first_in_sort(&self) -> bool {
+        true
+    }
 }
 pub struct DefaultDialect {}
 
@@ -57,6 +65,10 @@ impl Dialect for MySqlDialect {
     fn identifier_quote_style(&self, _: &str) -> Option<char> {
         Some('`')
     }
+
+    fn supports_nulls_first_in_sort(&self) -> bool {
+        false
+    }
 }
 
 pub struct SqliteDialect {}
diff --git a/datafusion/sql/src/unparser/expr.rs 
b/datafusion/sql/src/unparser/expr.rs
index 5fe744e359..ea991102df 100644
--- a/datafusion/sql/src/unparser/expr.rs
+++ b/datafusion/sql/src/unparser/expr.rs
@@ -474,10 +474,17 @@ impl Unparser<'_> {
                 nulls_first,
             }) => {
                 let sql_parser_expr = self.expr_to_sql(expr)?;
+
+                let nulls_first = if 
self.dialect.supports_nulls_first_in_sort() {
+                    Some(*nulls_first)
+                } else {
+                    None
+                };
+
                 Ok(Unparsed::OrderByExpr(ast::OrderByExpr {
                     expr: sql_parser_expr,
                     asc: Some(*asc),
-                    nulls_first: Some(*nulls_first),
+                    nulls_first,
                 }))
             }
             _ => {
diff --git a/datafusion/sql/src/unparser/plan.rs 
b/datafusion/sql/src/unparser/plan.rs
index 07a8d78171..833ac5cdbe 100644
--- a/datafusion/sql/src/unparser/plan.rs
+++ b/datafusion/sql/src/unparser/plan.rs
@@ -439,10 +439,17 @@ impl Unparser<'_> {
             .map(|expr: &Expr| match expr {
                 Expr::Sort(sort_expr) => {
                     let col = self.expr_to_sql(&sort_expr.expr)?;
+
+                    let nulls_first = if 
self.dialect.supports_nulls_first_in_sort() {
+                        Some(sort_expr.nulls_first)
+                    } else {
+                        None
+                    };
+
                     Ok(ast::OrderByExpr {
                         asc: Some(sort_expr.asc),
                         expr: col,
-                        nulls_first: Some(sort_expr.nulls_first),
+                        nulls_first,
                     })
                 }
                 _ => plan_err!("Expecting Sort expr"),
diff --git a/datafusion/sql/tests/sql_integration.rs 
b/datafusion/sql/tests/sql_integration.rs
index e7da113e60..685911ee81 100644
--- a/datafusion/sql/tests/sql_integration.rs
+++ b/datafusion/sql/tests/sql_integration.rs
@@ -33,7 +33,11 @@ use datafusion_expr::{
     Volatility, WindowUDF,
 };
 use datafusion_functions::{string, unicode};
-use datafusion_sql::unparser::{expr_to_sql, plan_to_sql};
+use datafusion_sql::unparser::dialect::{
+    DefaultDialect as UnparserDefaultDialect, Dialect as UnparserDialect,
+    MySqlDialect as UnparserMySqlDialect,
+};
+use datafusion_sql::unparser::{expr_to_sql, plan_to_sql, Unparser};
 use datafusion_sql::{
     parser::DFParser,
     planner::{ContextProvider, ParserOptions, PlannerContext, SqlToRel},
@@ -4726,6 +4730,52 @@ fn roundtrip_crossjoin() -> Result<()> {
     Ok(())
 }
 
+#[test]
+fn roundtrip_statement_with_dialect() -> Result<()> {
+    struct TestStatementWithDialect {
+        sql: &'static str,
+        expected: &'static str,
+        parser_dialect: Box<dyn Dialect>,
+        unparser_dialect: Box<dyn UnparserDialect>,
+    }
+    let tests: Vec<TestStatementWithDialect> = vec![
+        TestStatementWithDialect {
+            sql: "select ta.j1_id from j1 ta order by j1_id limit 10;",
+            expected:
+                "SELECT `ta`.`j1_id` FROM `j1` AS `ta` ORDER BY `ta`.`j1_id` 
ASC LIMIT 10",
+            parser_dialect: Box::new(MySqlDialect {}),
+            unparser_dialect: Box::new(UnparserMySqlDialect {}),
+        },
+        TestStatementWithDialect {
+            sql: "select ta.j1_id from j1 ta order by j1_id limit 10;",
+            expected: r#"SELECT ta.j1_id FROM j1 AS ta ORDER BY ta.j1_id ASC 
NULLS LAST LIMIT 10"#,
+            parser_dialect: Box::new(GenericDialect {}),
+            unparser_dialect: Box::new(UnparserDefaultDialect {}),
+        },
+    ];
+
+    for query in tests {
+        let statement = Parser::new(&*query.parser_dialect)
+            .try_with_sql(query.sql)?
+            .parse_statement()?;
+
+        let context = MockContextProvider::default();
+        let sql_to_rel = SqlToRel::new(&context);
+        let plan = sql_to_rel.sql_statement_to_plan(statement).unwrap();
+
+        let unparser = Unparser::new(&*query.unparser_dialect);
+        let roundtrip_statement = unparser.plan_to_sql(&plan)?;
+
+        let actual = format!("{}", &roundtrip_statement);
+        println!("roundtrip sql: {actual}");
+        println!("plan {}", plan.display_indent());
+
+        assert_eq!(query.expected, actual);
+    }
+
+    Ok(())
+}
+
 #[test]
 fn test_unnest_logical_plan() -> Result<()> {
     let query = "select unnest(struct_col), unnest(array_col), struct_col, 
array_col from unnest_table";


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to