Skip to content

Commit 4b57521

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 3a970c5 commit 4b57521

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
@@ -340,6 +340,10 @@ impl Dialect for PostgreSqlDialect {
340340
func_name: &str,
341341
args: &[Expr],
342342
) -> Result<Option<ast::Expr>> {
343+
if func_name == "array_has" {
344+
return self.array_has_to_sql_any(unparser, args);
345+
}
346+
343347
if func_name == "round" {
344348
return Ok(Some(
345349
self.round_to_sql_enforce_numeric(unparser, func_name, args)?,
@@ -351,6 +355,23 @@ impl Dialect for PostgreSqlDialect {
351355
}
352356

353357
impl PostgreSqlDialect {
358+
fn array_has_to_sql_any(
359+
&self,
360+
unparser: &Unparser,
361+
args: &[Expr],
362+
) -> Result<Option<ast::Expr>> {
363+
let [haystack, needle] = args else {
364+
return Ok(None);
365+
};
366+
367+
Ok(Some(ast::Expr::AnyOp {
368+
left: Box::new(unparser.expr_to_sql(needle)?),
369+
compare_op: BinaryOperator::Eq,
370+
right: Box::new(unparser.expr_to_sql(haystack)?),
371+
is_some: false,
372+
}))
373+
}
374+
354375
fn round_to_sql_enforce_numeric(
355376
&self,
356377
unparser: &Unparser,

datafusion/sql/src/unparser/expr.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1849,7 +1849,7 @@ mod tests {
18491849
use datafusion_functions::expr_fn::{get_field, named_struct};
18501850
use datafusion_functions_aggregate::count::count_udaf;
18511851
use datafusion_functions_aggregate::expr_fn::sum;
1852-
use datafusion_functions_nested::expr_fn::{array_element, make_array};
1852+
use datafusion_functions_nested::expr_fn::{array_element, array_has, make_array};
18531853
use datafusion_functions_nested::map::map;
18541854
use datafusion_functions_window::rank::rank_udwf;
18551855
use datafusion_functions_window::row_number::row_number_udwf;
@@ -3046,6 +3046,24 @@ mod tests {
30463046
Ok(())
30473047
}
30483048

3049+
#[test]
3050+
fn test_postgres_array_has_to_any() -> Result<()> {
3051+
let default_dialect: Arc<dyn Dialect> = Arc::new(DefaultDialect {});
3052+
let postgres_dialect: Arc<dyn Dialect> = Arc::new(PostgreSqlDialect {});
3053+
let expr = array_has(col("items"), lit(1));
3054+
3055+
for (dialect, expected) in [
3056+
(default_dialect, "array_has(\"items\", 1)"),
3057+
(postgres_dialect, "1 = ANY(\"items\")"),
3058+
] {
3059+
let unparser = Unparser::new(dialect.as_ref());
3060+
let actual = format!("{}", unparser.expr_to_sql(&expr)?);
3061+
assert_eq!(actual, expected);
3062+
}
3063+
3064+
Ok(())
3065+
}
3066+
30493067
#[test]
30503068
fn test_window_func_support_window_frame() -> Result<()> {
30513069
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)