From 61ef7f43dd8c154c79820d06eb6250794d97cdc8 Mon Sep 17 00:00:00 2001 From: xiedeyantu Date: Thu, 9 Apr 2026 23:11:27 +0800 Subject: [PATCH 1/3] sql: render PostgreSQL array literals as ARRAY[...] in unparser --- datafusion/sql/src/unparser/dialect.rs | 9 +++++++++ datafusion/sql/src/unparser/expr.rs | 7 +++++-- datafusion/sql/tests/cases/plan_to_sql.rs | 11 +++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/datafusion/sql/src/unparser/dialect.rs b/datafusion/sql/src/unparser/dialect.rs index 9e0683bdd7b20..4efd8c0c3bb28 100644 --- a/datafusion/sql/src/unparser/dialect.rs +++ b/datafusion/sql/src/unparser/dialect.rs @@ -51,6 +51,11 @@ pub trait Dialect: Send + Sync { /// Return the character used to quote identifiers. fn identifier_quote_style(&self, _identifier: &str) -> Option; + /// Whether array literals should be rendered with the `ARRAY[...]` keyword. + fn array_keyword(&self) -> bool { + false + } + /// Does the dialect support specifying `NULLS FIRST/LAST` in `ORDER BY` clauses? fn supports_nulls_first_in_sort(&self) -> bool { true @@ -321,6 +326,10 @@ impl Dialect for DefaultDialect { pub struct PostgreSqlDialect {} impl Dialect for PostgreSqlDialect { + fn array_keyword(&self) -> bool { + true + } + fn supports_qualify(&self) -> bool { false } diff --git a/datafusion/sql/src/unparser/expr.rs b/datafusion/sql/src/unparser/expr.rs index 3601febe744c9..eb400c8490231 100644 --- a/datafusion/sql/src/unparser/expr.rs +++ b/datafusion/sql/src/unparser/expr.rs @@ -604,7 +604,7 @@ impl Unparser<'_> { .collect::>>()?; Ok(ast::Expr::Array(Array { elem: args, - named: false, + named: self.dialect.array_keyword(), })) } @@ -615,7 +615,10 @@ impl Unparser<'_> { elem.push(self.scalar_to_sql(&value)?); } - Ok(ast::Expr::Array(Array { elem, named: false })) + Ok(ast::Expr::Array(Array { + elem, + named: self.dialect.array_keyword(), + })) } fn array_element_to_sql(&self, args: &[Expr]) -> Result { diff --git a/datafusion/sql/tests/cases/plan_to_sql.rs b/datafusion/sql/tests/cases/plan_to_sql.rs index 0dad48b168976..fa4161168bac1 100644 --- a/datafusion/sql/tests/cases/plan_to_sql.rs +++ b/datafusion/sql/tests/cases/plan_to_sql.rs @@ -2728,6 +2728,17 @@ fn test_unparse_window() -> Result<()> { Ok(()) } +#[test] +fn test_array_to_sql_postgres() -> Result<(), DataFusionError> { + roundtrip_statement_with_dialect_helper!( + sql: "SELECT [1, 2, 3, 4, 5]", + parser_dialect: GenericDialect {}, + unparser_dialect: UnparserPostgreSqlDialect {}, + expected: @"SELECT ARRAY[1, 2, 3, 4, 5]", + ); + Ok(()) +} + #[test] fn test_like_filter() { let statement = generate_round_trip_statement( From 6ee19a4e5c221333178fe1a259331287c5eaebcf Mon Sep 17 00:00:00 2001 From: xiedeyantu Date: Fri, 10 Apr 2026 22:40:58 +0800 Subject: [PATCH 2/3] add tests --- datafusion/sql/src/unparser/dialect.rs | 4 +- datafusion/sql/src/unparser/expr.rs | 67 +++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/datafusion/sql/src/unparser/dialect.rs b/datafusion/sql/src/unparser/dialect.rs index 4efd8c0c3bb28..c2b595cd2697f 100644 --- a/datafusion/sql/src/unparser/dialect.rs +++ b/datafusion/sql/src/unparser/dialect.rs @@ -52,7 +52,7 @@ pub trait Dialect: Send + Sync { fn identifier_quote_style(&self, _identifier: &str) -> Option; /// Whether array literals should be rendered with the `ARRAY[...]` keyword. - fn array_keyword(&self) -> bool { + fn use_array_keyword_for_array_literals(&self) -> bool { false } @@ -326,7 +326,7 @@ impl Dialect for DefaultDialect { pub struct PostgreSqlDialect {} impl Dialect for PostgreSqlDialect { - fn array_keyword(&self) -> bool { + fn use_array_keyword_for_array_literals(&self) -> bool { true } diff --git a/datafusion/sql/src/unparser/expr.rs b/datafusion/sql/src/unparser/expr.rs index eb400c8490231..e5a6355a798a9 100644 --- a/datafusion/sql/src/unparser/expr.rs +++ b/datafusion/sql/src/unparser/expr.rs @@ -604,7 +604,7 @@ impl Unparser<'_> { .collect::>>()?; Ok(ast::Expr::Array(Array { elem: args, - named: self.dialect.array_keyword(), + named: self.dialect.use_array_keyword_for_array_literals(), })) } @@ -617,7 +617,7 @@ impl Unparser<'_> { Ok(ast::Expr::Array(Array { elem, - named: self.dialect.array_keyword(), + named: self.dialect.use_array_keyword_for_array_literals(), })) } @@ -3045,6 +3045,69 @@ mod tests { } } + #[test] + fn test_array_literal_scalar_value_to_sql_postgres() -> Result<()> { + let dialect: Arc = Arc::new(PostgreSqlDialect {}); + let unparser = Unparser::new(dialect.as_ref()); + + let expr = Expr::Literal( + ScalarValue::List(ScalarValue::new_list_nullable( + &[ + ScalarValue::Int32(Some(1)), + ScalarValue::Int32(Some(2)), + ScalarValue::Int32(Some(3)), + ], + &DataType::Int32, + )), + None, + ); + + let ast = unparser.expr_to_sql(&expr)?; + assert_eq!(ast.to_string(), "ARRAY[1, 2, 3]"); + + Ok(()) + } + + #[test] + fn test_nested_array_literal_scalar_value_to_sql_postgres() -> Result<()> { + let dialect: Arc = Arc::new(PostgreSqlDialect {}); + let unparser = Unparser::new(dialect.as_ref()); + + let inner_type = DataType::Int32; + let nested_type = DataType::List(Arc::new(Field::new_list_field( + inner_type.clone(), + true, + ))); + + let expr = Expr::Literal( + ScalarValue::List(ScalarValue::new_list_nullable( + &[ + ScalarValue::List(ScalarValue::new_list_nullable( + &[ + ScalarValue::Int32(Some(1)), + ScalarValue::Int32(Some(2)), + ], + &inner_type, + )), + ScalarValue::List(ScalarValue::new_list_nullable( + &[ + ScalarValue::Int32(Some(3)), + ScalarValue::Int32(Some(4)), + ], + &inner_type, + )), + ], + &nested_type, + )), + None, + ); + + let ast = unparser.expr_to_sql(&expr)?; + assert_eq!(ast.to_string(), "ARRAY[ARRAY[1, 2], ARRAY[3, 4]]"); + + Ok(()) + } + #[test] fn test_round_scalar_fn_to_expr() -> Result<()> { let default_dialect: Arc = Arc::new( From f34be002632152cde4f6e71af773599ddb714f68 Mon Sep 17 00:00:00 2001 From: xiedeyantu Date: Fri, 10 Apr 2026 22:45:48 +0800 Subject: [PATCH 3/3] fix format --- datafusion/sql/src/unparser/expr.rs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/datafusion/sql/src/unparser/expr.rs b/datafusion/sql/src/unparser/expr.rs index e5a6355a798a9..e7a7d04e58b75 100644 --- a/datafusion/sql/src/unparser/expr.rs +++ b/datafusion/sql/src/unparser/expr.rs @@ -3074,26 +3074,18 @@ mod tests { let unparser = Unparser::new(dialect.as_ref()); let inner_type = DataType::Int32; - let nested_type = DataType::List(Arc::new(Field::new_list_field( - inner_type.clone(), - true, - ))); + let nested_type = + DataType::List(Arc::new(Field::new_list_field(inner_type.clone(), true))); let expr = Expr::Literal( ScalarValue::List(ScalarValue::new_list_nullable( &[ ScalarValue::List(ScalarValue::new_list_nullable( - &[ - ScalarValue::Int32(Some(1)), - ScalarValue::Int32(Some(2)), - ], + &[ScalarValue::Int32(Some(1)), ScalarValue::Int32(Some(2))], &inner_type, )), ScalarValue::List(ScalarValue::new_list_nullable( - &[ - ScalarValue::Int32(Some(3)), - ScalarValue::Int32(Some(4)), - ], + &[ScalarValue::Int32(Some(3)), ScalarValue::Int32(Some(4))], &inner_type, )), ],