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 5d3bbaa323 Unparser: Support `ORDER BY` in window function definition
(#10370)
5d3bbaa323 is described below
commit 5d3bbaa3233523819d823e69139cd522db715cae
Author: Junhao Liu <[email protected]>
AuthorDate: Sat May 4 05:15:40 2024 -0600
Unparser: Support `ORDER BY` in window function definition (#10370)
* support OrderBy in Unparser
* resolve comment
---
datafusion/sql/src/unparser/expr.rs | 108 +++++++++++++++++++++++++++++++++---
1 file changed, 99 insertions(+), 9 deletions(-)
diff --git a/datafusion/sql/src/unparser/expr.rs
b/datafusion/sql/src/unparser/expr.rs
index 7194b0a7d8..c619c62668 100644
--- a/datafusion/sql/src/unparser/expr.rs
+++ b/datafusion/sql/src/unparser/expr.rs
@@ -15,10 +15,14 @@
// specific language governing permissions and limitations
// under the License.
+use core::fmt;
+use std::fmt::Display;
+
use arrow_array::{Date32Array, Date64Array};
use arrow_schema::DataType;
use datafusion_common::{
- internal_datafusion_err, not_impl_err, plan_err, Column, Result,
ScalarValue,
+ internal_datafusion_err, internal_err, not_impl_err, plan_err, Column,
Result,
+ ScalarValue,
};
use datafusion_expr::{
expr::{Alias, Exists, InList, ScalarFunction, Sort, WindowFunction},
@@ -30,10 +34,38 @@ use sqlparser::ast::{
use super::Unparser;
+/// DataFusion's Exprs can represent either an `Expr` or an `OrderByExpr`
+pub enum Unparsed {
+ // SQL Expression
+ Expr(ast::Expr),
+ // SQL ORDER BY expression (e.g. `col ASC NULLS FIRST`)
+ OrderByExpr(ast::OrderByExpr),
+}
+
+impl Unparsed {
+ pub fn into_order_by_expr(self) -> Result<ast::OrderByExpr> {
+ if let Unparsed::OrderByExpr(order_by_expr) = self {
+ Ok(order_by_expr)
+ } else {
+ internal_err!("Expected Sort expression to be converted an
OrderByExpr")
+ }
+ }
+}
+
+impl Display for Unparsed {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Unparsed::Expr(expr) => write!(f, "{}", expr),
+ Unparsed::OrderByExpr(order_by_expr) => write!(f, "{}",
order_by_expr),
+ }
+ }
+}
+
/// Convert a DataFusion [`Expr`] to `sqlparser::ast::Expr`
///
/// This function is the opposite of `SqlToRel::sql_to_expr` and can
-/// be used to, among other things, convert `Expr`s to strings.
+/// be used to, among other things, convert [`Expr`]s to strings.
+/// Throws an error if [`Expr`] can not be represented by an
`sqlparser::ast::Expr`
///
/// # Example
/// ```
@@ -49,6 +81,15 @@ pub fn expr_to_sql(expr: &Expr) -> Result<ast::Expr> {
unparser.expr_to_sql(expr)
}
+/// Convert a DataFusion [`Expr`] to [`Unparsed`]
+///
+/// This function is similar to expr_to_sql, but it supports converting more
[`Expr`] types like
+/// `Sort` expressions to `OrderByExpr` expressions.
+pub fn expr_to_unparsed(expr: &Expr) -> Result<Unparsed> {
+ let unparser = Unparser::default();
+ unparser.expr_to_unparsed(expr)
+}
+
impl Unparser<'_> {
pub fn expr_to_sql(&self, expr: &Expr) -> Result<ast::Expr> {
match expr {
@@ -170,7 +211,7 @@ impl Unparser<'_> {
fun,
args,
partition_by,
- order_by: _,
+ order_by,
window_frame,
null_treatment: _,
}) => {
@@ -189,6 +230,11 @@ impl Unparser<'_> {
ast::WindowFrameUnits::Groups
}
};
+ let order_by: Vec<ast::OrderByExpr> = order_by
+ .iter()
+ .map(|expr| expr_to_unparsed(expr)?.into_order_by_expr())
+ .collect::<Result<Vec<_>>>()?;
+
let start_bound =
self.convert_bound(&window_frame.start_bound);
let end_bound = self.convert_bound(&window_frame.end_bound);
let over = Some(ast::WindowType::WindowSpec(ast::WindowSpec {
@@ -197,13 +243,14 @@ impl Unparser<'_> {
.iter()
.map(|e| self.expr_to_sql(e))
.collect::<Result<Vec<_>>>()?,
- order_by: vec![],
+ order_by,
window_frame: Some(ast::WindowFrame {
units,
start_bound,
end_bound: Option::from(end_bound),
}),
}));
+
Ok(ast::Expr::Function(Function {
name: ast::ObjectName(vec![Ident {
value: func_name.to_string(),
@@ -305,10 +352,10 @@ impl Unparser<'_> {
})
}
Expr::Sort(Sort {
- expr,
+ expr: _,
asc: _,
nulls_first: _,
- }) => self.expr_to_sql(expr),
+ }) => plan_err!("Sort expression should be handled by
expr_to_unparsed"),
Expr::IsNotNull(expr) => {
Ok(ast::Expr::IsNotNull(Box::new(self.expr_to_sql(expr)?)))
}
@@ -366,6 +413,29 @@ impl Unparser<'_> {
}
}
+ /// This function can convert more [`Expr`] types than `expr_to_sql`,
returning an [`Unparsed`]
+ /// like `Sort` expressions to `OrderByExpr` expressions.
+ pub fn expr_to_unparsed(&self, expr: &Expr) -> Result<Unparsed> {
+ match expr {
+ Expr::Sort(Sort {
+ expr,
+ asc,
+ nulls_first,
+ }) => {
+ let sql_parser_expr = self.expr_to_sql(expr)?;
+ Ok(Unparsed::OrderByExpr(ast::OrderByExpr {
+ expr: sql_parser_expr,
+ asc: Some(*asc),
+ nulls_first: Some(*nulls_first),
+ }))
+ }
+ _ => {
+ let sql_parser_expr = self.expr_to_sql(expr)?;
+ Ok(Unparsed::Expr(sql_parser_expr))
+ }
+ }
+ }
+
fn col_to_sql(&self, col: &Column) -> Result<ast::Expr> {
if let Some(table_ref) = &col.relation {
let mut id = table_ref.to_vec();
@@ -992,7 +1062,11 @@ mod tests {
),
args: vec![wildcard()],
partition_by: vec![],
- order_by: vec![],
+ order_by: vec![Expr::Sort(Sort::new(
+ Box::new(col("a")),
+ false,
+ true,
+ ))],
window_frame: WindowFrame::new_bounds(
datafusion_expr::WindowFrameUnits::Range,
datafusion_expr::WindowFrameBound::Preceding(
@@ -1004,7 +1078,7 @@ mod tests {
),
null_treatment: None,
}),
- r#"COUNT(*) OVER (RANGE BETWEEN 6 PRECEDING AND 2 FOLLOWING)"#,
+ r#"COUNT(*) OVER (ORDER BY "a" DESC NULLS FIRST RANGE BETWEEN
6 PRECEDING AND 2 FOLLOWING)"#,
),
(col("a").is_not_null(), r#""a" IS NOT NULL"#),
(
@@ -1041,7 +1115,6 @@ mod tests {
not_exists(Arc::new(dummy_logical_plan.clone())),
r#"NOT EXISTS (SELECT "t"."a" FROM "t" WHERE ("t"."a" = 1))"#,
),
- (col("a").sort(true, true), r#""a""#),
];
for (expr, expected) in tests {
@@ -1055,6 +1128,23 @@ mod tests {
Ok(())
}
+ #[test]
+ fn expr_to_unparsed_ok() -> Result<()> {
+ let tests: Vec<(Expr, &str)> = vec![
+ ((col("a") + col("b")).gt(lit(4)), r#"(("a" + "b") > 4)"#),
+ (col("a").sort(true, true), r#""a" ASC NULLS FIRST"#),
+ ];
+
+ for (expr, expected) in tests {
+ let ast = expr_to_unparsed(&expr)?;
+
+ let actual = format!("{}", ast);
+
+ assert_eq!(actual, expected);
+ }
+
+ Ok(())
+ }
#[test]
fn custom_dialect() -> Result<()> {
let dialect = CustomDialect::new(Some('\''));
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]