From 4b575218b956b668424575c5248a126df34356e3 Mon Sep 17 00:00:00 2001 From: Vinay Mehta <14790730+vimeh@users.noreply.github.com> Date: Mon, 2 Mar 2026 10:10:58 -0800 Subject: [PATCH] sql: unparse array_has as ANY for Postgres MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PostgreSQL does not have an array_has() function — it uses the `= ANY(array)` operator for array containment checks. Without this dialect override, SQL unparsed for Postgres federation produces invalid syntax. This converts array_has(haystack, needle) to needle = ANY(haystack) in the PostgreSqlDialect. --- datafusion/sql/src/unparser/dialect.rs | 21 +++++++++++++++++++++ datafusion/sql/src/unparser/expr.rs | 20 +++++++++++++++++++- datafusion/sql/tests/cases/plan_to_sql.rs | 11 +++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/datafusion/sql/src/unparser/dialect.rs b/datafusion/sql/src/unparser/dialect.rs index 31d2662cc4cc0..869c6314470cf 100644 --- a/datafusion/sql/src/unparser/dialect.rs +++ b/datafusion/sql/src/unparser/dialect.rs @@ -340,6 +340,10 @@ impl Dialect for PostgreSqlDialect { func_name: &str, args: &[Expr], ) -> Result> { + if func_name == "array_has" { + return self.array_has_to_sql_any(unparser, args); + } + if func_name == "round" { return Ok(Some( self.round_to_sql_enforce_numeric(unparser, func_name, args)?, @@ -351,6 +355,23 @@ impl Dialect for PostgreSqlDialect { } impl PostgreSqlDialect { + fn array_has_to_sql_any( + &self, + unparser: &Unparser, + args: &[Expr], + ) -> Result> { + let [haystack, needle] = args else { + return Ok(None); + }; + + Ok(Some(ast::Expr::AnyOp { + left: Box::new(unparser.expr_to_sql(needle)?), + compare_op: BinaryOperator::Eq, + right: Box::new(unparser.expr_to_sql(haystack)?), + is_some: false, + })) + } + fn round_to_sql_enforce_numeric( &self, unparser: &Unparser, diff --git a/datafusion/sql/src/unparser/expr.rs b/datafusion/sql/src/unparser/expr.rs index 59a9207b51ef0..f5862f0d6b660 100644 --- a/datafusion/sql/src/unparser/expr.rs +++ b/datafusion/sql/src/unparser/expr.rs @@ -1849,7 +1849,7 @@ mod tests { use datafusion_functions::expr_fn::{get_field, named_struct}; use datafusion_functions_aggregate::count::count_udaf; use datafusion_functions_aggregate::expr_fn::sum; - use datafusion_functions_nested::expr_fn::{array_element, make_array}; + use datafusion_functions_nested::expr_fn::{array_element, array_has, make_array}; use datafusion_functions_nested::map::map; use datafusion_functions_window::rank::rank_udwf; use datafusion_functions_window::row_number::row_number_udwf; @@ -3046,6 +3046,24 @@ mod tests { Ok(()) } + #[test] + fn test_postgres_array_has_to_any() -> Result<()> { + let default_dialect: Arc = Arc::new(DefaultDialect {}); + let postgres_dialect: Arc = Arc::new(PostgreSqlDialect {}); + let expr = array_has(col("items"), lit(1)); + + for (dialect, expected) in [ + (default_dialect, "array_has(\"items\", 1)"), + (postgres_dialect, "1 = ANY(\"items\")"), + ] { + let unparser = Unparser::new(dialect.as_ref()); + let actual = format!("{}", unparser.expr_to_sql(&expr)?); + assert_eq!(actual, expected); + } + + Ok(()) + } + #[test] fn test_window_func_support_window_frame() -> Result<()> { let default_dialect: Arc = diff --git a/datafusion/sql/tests/cases/plan_to_sql.rs b/datafusion/sql/tests/cases/plan_to_sql.rs index 4717b843abb53..263afcb6c9d60 100644 --- a/datafusion/sql/tests/cases/plan_to_sql.rs +++ b/datafusion/sql/tests/cases/plan_to_sql.rs @@ -361,6 +361,17 @@ fn roundtrip_statement_with_dialect_3() -> Result<(), DataFusionError> { Ok(()) } +#[test] +fn roundtrip_statement_postgres_any_array_expr() -> Result<(), DataFusionError> { + roundtrip_statement_with_dialect_helper!( + sql: "select left from array where 1 = any(left);", + parser_dialect: GenericDialect {}, + unparser_dialect: UnparserPostgreSqlDialect {}, + expected: @r#"SELECT "array"."left" FROM "array" WHERE 1 = ANY("array"."left")"#, + ); + Ok(()) +} + #[test] fn roundtrip_statement_with_dialect_4() -> Result<(), DataFusionError> { roundtrip_statement_with_dialect_helper!(