Skip to content

Commit 98d280f

Browse files
authored
sql: render PostgreSQL array literals as ARRAY[...] in unparser (#21513)
## Which issue does this PR close? - Closes #. ## Rationale for this change PostgreSQL array literals should be rendered using `ARRAY[...]` syntax when unparsing SQL. Without this, roundtripped PostgreSQL SQL can lose the dialect-specific array form and emit a plain bracketed array literal instead. ## What changes are included in this PR? - Added a dialect hook to indicate whether array literals should render as `ARRAY[...]`. - Enabled that behavior for `PostgreSqlDialect`. - Updated the SQL unparser to honor the dialect setting when rendering array expressions. - Adjusted the PostgreSQL roundtrip test to expect `ARRAY[1, 2, 3, 4, 5]`. ## Are these changes tested? Yes. The PostgreSQL roundtrip test in plan_to_sql.rs covers the array literal case and verifies the unparsed SQL now uses `ARRAY[...]`. ## Are there any user-facing changes? Yes. PostgreSQL SQL generated by DataFusion will now emit array literals in `ARRAY[...]` form instead of plain bracketed form.
1 parent 29c5dd5 commit 98d280f

File tree

3 files changed

+80
-2
lines changed

3 files changed

+80
-2
lines changed

datafusion/sql/src/unparser/dialect.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ pub trait Dialect: Send + Sync {
5151
/// Return the character used to quote identifiers.
5252
fn identifier_quote_style(&self, _identifier: &str) -> Option<char>;
5353

54+
/// Whether array literals should be rendered with the `ARRAY[...]` keyword.
55+
fn use_array_keyword_for_array_literals(&self) -> bool {
56+
false
57+
}
58+
5459
/// Does the dialect support specifying `NULLS FIRST/LAST` in `ORDER BY` clauses?
5560
fn supports_nulls_first_in_sort(&self) -> bool {
5661
true
@@ -327,6 +332,10 @@ impl Dialect for DefaultDialect {
327332
pub struct PostgreSqlDialect {}
328333

329334
impl Dialect for PostgreSqlDialect {
335+
fn use_array_keyword_for_array_literals(&self) -> bool {
336+
true
337+
}
338+
330339
fn supports_qualify(&self) -> bool {
331340
false
332341
}

datafusion/sql/src/unparser/expr.rs

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,7 @@ impl Unparser<'_> {
604604
.collect::<Result<Vec<_>>>()?;
605605
Ok(ast::Expr::Array(Array {
606606
elem: args,
607-
named: false,
607+
named: self.dialect.use_array_keyword_for_array_literals(),
608608
}))
609609
}
610610

@@ -615,7 +615,10 @@ impl Unparser<'_> {
615615
elem.push(self.scalar_to_sql(&value)?);
616616
}
617617

618-
Ok(ast::Expr::Array(Array { elem, named: false }))
618+
Ok(ast::Expr::Array(Array {
619+
elem,
620+
named: self.dialect.use_array_keyword_for_array_literals(),
621+
}))
619622
}
620623

621624
fn array_element_to_sql(&self, args: &[Expr]) -> Result<ast::Expr> {
@@ -3042,6 +3045,61 @@ mod tests {
30423045
}
30433046
}
30443047

3048+
#[test]
3049+
fn test_array_literal_scalar_value_to_sql_postgres() -> Result<()> {
3050+
let dialect: Arc<dyn Dialect> = Arc::new(PostgreSqlDialect {});
3051+
let unparser = Unparser::new(dialect.as_ref());
3052+
3053+
let expr = Expr::Literal(
3054+
ScalarValue::List(ScalarValue::new_list_nullable(
3055+
&[
3056+
ScalarValue::Int32(Some(1)),
3057+
ScalarValue::Int32(Some(2)),
3058+
ScalarValue::Int32(Some(3)),
3059+
],
3060+
&DataType::Int32,
3061+
)),
3062+
None,
3063+
);
3064+
3065+
let ast = unparser.expr_to_sql(&expr)?;
3066+
assert_eq!(ast.to_string(), "ARRAY[1, 2, 3]");
3067+
3068+
Ok(())
3069+
}
3070+
3071+
#[test]
3072+
fn test_nested_array_literal_scalar_value_to_sql_postgres() -> Result<()> {
3073+
let dialect: Arc<dyn Dialect> = Arc::new(PostgreSqlDialect {});
3074+
let unparser = Unparser::new(dialect.as_ref());
3075+
3076+
let inner_type = DataType::Int32;
3077+
let nested_type =
3078+
DataType::List(Arc::new(Field::new_list_field(inner_type.clone(), true)));
3079+
3080+
let expr = Expr::Literal(
3081+
ScalarValue::List(ScalarValue::new_list_nullable(
3082+
&[
3083+
ScalarValue::List(ScalarValue::new_list_nullable(
3084+
&[ScalarValue::Int32(Some(1)), ScalarValue::Int32(Some(2))],
3085+
&inner_type,
3086+
)),
3087+
ScalarValue::List(ScalarValue::new_list_nullable(
3088+
&[ScalarValue::Int32(Some(3)), ScalarValue::Int32(Some(4))],
3089+
&inner_type,
3090+
)),
3091+
],
3092+
&nested_type,
3093+
)),
3094+
None,
3095+
);
3096+
3097+
let ast = unparser.expr_to_sql(&expr)?;
3098+
assert_eq!(ast.to_string(), "ARRAY[ARRAY[1, 2], ARRAY[3, 4]]");
3099+
3100+
Ok(())
3101+
}
3102+
30453103
#[test]
30463104
fn test_round_scalar_fn_to_expr() -> Result<()> {
30473105
let default_dialect: Arc<dyn Dialect> = Arc::new(

datafusion/sql/tests/cases/plan_to_sql.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2750,6 +2750,17 @@ fn test_unparse_window() -> Result<()> {
27502750
Ok(())
27512751
}
27522752

2753+
#[test]
2754+
fn test_array_to_sql_postgres() -> Result<(), DataFusionError> {
2755+
roundtrip_statement_with_dialect_helper!(
2756+
sql: "SELECT [1, 2, 3, 4, 5]",
2757+
parser_dialect: GenericDialect {},
2758+
unparser_dialect: UnparserPostgreSqlDialect {},
2759+
expected: @"SELECT ARRAY[1, 2, 3, 4, 5]",
2760+
);
2761+
Ok(())
2762+
}
2763+
27532764
#[test]
27542765
fn test_like_filter() {
27552766
let statement = generate_round_trip_statement(

0 commit comments

Comments
 (0)