Skip to content

Commit 5300629

Browse files
committed
sql: unparse array_has as ANY for Postgres
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.
1 parent 11c2fbc commit 5300629

3 files changed

Lines changed: 51 additions & 1 deletion

File tree

datafusion/sql/src/unparser/dialect.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,10 @@ impl Dialect for PostgreSqlDialect {
351351
func_name: &str,
352352
args: &[Expr],
353353
) -> Result<Option<ast::Expr>> {
354+
if func_name == "array_has" {
355+
return self.array_has_to_sql_any(unparser, args);
356+
}
357+
354358
if func_name == "round" {
355359
return Ok(Some(
356360
self.round_to_sql_enforce_numeric(unparser, func_name, args)?,
@@ -362,6 +366,23 @@ impl Dialect for PostgreSqlDialect {
362366
}
363367

364368
impl PostgreSqlDialect {
369+
fn array_has_to_sql_any(
370+
&self,
371+
unparser: &Unparser,
372+
args: &[Expr],
373+
) -> Result<Option<ast::Expr>> {
374+
let [haystack, needle] = args else {
375+
return Ok(None);
376+
};
377+
378+
Ok(Some(ast::Expr::AnyOp {
379+
left: Box::new(unparser.expr_to_sql(needle)?),
380+
compare_op: BinaryOperator::Eq,
381+
right: Box::new(unparser.expr_to_sql(haystack)?),
382+
is_some: false,
383+
}))
384+
}
385+
365386
fn round_to_sql_enforce_numeric(
366387
&self,
367388
unparser: &Unparser,

datafusion/sql/src/unparser/expr.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1853,7 +1853,7 @@ mod tests {
18531853
use datafusion_functions::expr_fn::{get_field, named_struct};
18541854
use datafusion_functions_aggregate::count::count_udaf;
18551855
use datafusion_functions_aggregate::expr_fn::sum;
1856-
use datafusion_functions_nested::expr_fn::{array_element, make_array};
1856+
use datafusion_functions_nested::expr_fn::{array_element, array_has, make_array};
18571857
use datafusion_functions_nested::map::map;
18581858
use datafusion_functions_window::rank::rank_udwf;
18591859
use datafusion_functions_window::row_number::row_number_udwf;
@@ -3074,6 +3074,24 @@ mod tests {
30743074
Ok(())
30753075
}
30763076

3077+
#[test]
3078+
fn test_postgres_array_has_to_any() -> Result<()> {
3079+
let default_dialect: Arc<dyn Dialect> = Arc::new(DefaultDialect {});
3080+
let postgres_dialect: Arc<dyn Dialect> = Arc::new(PostgreSqlDialect {});
3081+
let expr = array_has(col("items"), lit(1));
3082+
3083+
for (dialect, expected) in [
3084+
(default_dialect, "array_has(\"items\", 1)"),
3085+
(postgres_dialect, "1 = ANY(\"items\")"),
3086+
] {
3087+
let unparser = Unparser::new(dialect.as_ref());
3088+
let actual = format!("{}", unparser.expr_to_sql(&expr)?);
3089+
assert_eq!(actual, expected);
3090+
}
3091+
3092+
Ok(())
3093+
}
3094+
30773095
#[test]
30783096
fn test_window_func_support_window_frame() -> Result<()> {
30793097
let default_dialect: Arc<dyn Dialect> =

datafusion/sql/tests/cases/plan_to_sql.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,17 @@ fn roundtrip_statement_with_dialect_3() -> Result<(), DataFusionError> {
361361
Ok(())
362362
}
363363

364+
#[test]
365+
fn roundtrip_statement_postgres_any_array_expr() -> Result<(), DataFusionError> {
366+
roundtrip_statement_with_dialect_helper!(
367+
sql: "select left from array where 1 = any(left);",
368+
parser_dialect: GenericDialect {},
369+
unparser_dialect: UnparserPostgreSqlDialect {},
370+
expected: @r#"SELECT "array"."left" FROM "array" WHERE 1 = ANY("array"."left")"#,
371+
);
372+
Ok(())
373+
}
374+
364375
#[test]
365376
fn roundtrip_statement_with_dialect_4() -> Result<(), DataFusionError> {
366377
roundtrip_statement_with_dialect_helper!(

0 commit comments

Comments
 (0)