Support for deparsing of ArrayCoerceExpr node in contrib/postgres_fdw
authorAlexander Korotkov <akorotkov@postgresql.org>
Fri, 18 Jul 2025 07:52:05 +0000 (10:52 +0300)
committerAlexander Korotkov <akorotkov@postgresql.org>
Fri, 18 Jul 2025 07:52:05 +0000 (10:52 +0300)
When using a prepared statement to select data from a PostgreSQL foreign
table (postgres_fdw) with the "field = ANY($1)" expression, the operation
is not pushed down when an implicit type case is applied, and a generic plan
is used.  This commit resolves the issue by supporting the push-down of
ArrayCoerceExpr, which is used in this case.  The support is quite
straightforward and similar to other nods, such as RelabelType.

Discussion: https://postgr.es/m/4f0cea802476d23c6e799512ffd17aff%40postgrespro.ru
Author: Alexander Pyhalov <a.pyhalov@postgrespro.ru>
Reviewed-by: Maxim Orlov <orlovmg@gmail.com>
Reviewed-by: Alexander Korotkov <aekorotkov@gmail.com>
contrib/postgres_fdw/deparse.c
contrib/postgres_fdw/expected/postgres_fdw.out
contrib/postgres_fdw/sql/postgres_fdw.sql

index 9351835b5e4f8038a5d8d8a4def6de4f89ab76b2..d761d076dc8befb161fcbb6ac65c1873afd7e4bb 100644 (file)
@@ -161,6 +161,7 @@ static void deparseDistinctExpr(DistinctExpr *node, deparse_expr_cxt *context);
 static void deparseScalarArrayOpExpr(ScalarArrayOpExpr *node,
                                     deparse_expr_cxt *context);
 static void deparseRelabelType(RelabelType *node, deparse_expr_cxt *context);
+static void deparseArrayCoerceExpr(ArrayCoerceExpr *node, deparse_expr_cxt *context);
 static void deparseBoolExpr(BoolExpr *node, deparse_expr_cxt *context);
 static void deparseNullTest(NullTest *node, deparse_expr_cxt *context);
 static void deparseCaseExpr(CaseExpr *node, deparse_expr_cxt *context);
@@ -702,6 +703,34 @@ foreign_expr_walker(Node *node,
                    state = FDW_COLLATE_UNSAFE;
            }
            break;
+       case T_ArrayCoerceExpr:
+           {
+               ArrayCoerceExpr *e = (ArrayCoerceExpr *) node;
+
+               /*
+                * Recurse to input subexpression.
+                */
+               if (!foreign_expr_walker((Node *) e->arg,
+                                        glob_cxt, &inner_cxt, case_arg_cxt))
+                   return false;
+
+               /*
+                * T_ArrayCoerceExpr must not introduce a collation not
+                * derived from an input foreign Var (same logic as for a
+                * function).
+                */
+               collation = e->resultcollid;
+               if (collation == InvalidOid)
+                   state = FDW_COLLATE_NONE;
+               else if (inner_cxt.state == FDW_COLLATE_SAFE &&
+                        collation == inner_cxt.collation)
+                   state = FDW_COLLATE_SAFE;
+               else if (collation == DEFAULT_COLLATION_OID)
+                   state = FDW_COLLATE_NONE;
+               else
+                   state = FDW_COLLATE_UNSAFE;
+           }
+           break;
        case T_BoolExpr:
            {
                BoolExpr   *b = (BoolExpr *) node;
@@ -2919,6 +2948,9 @@ deparseExpr(Expr *node, deparse_expr_cxt *context)
        case T_RelabelType:
            deparseRelabelType((RelabelType *) node, context);
            break;
+       case T_ArrayCoerceExpr:
+           deparseArrayCoerceExpr((ArrayCoerceExpr *) node, context);
+           break;
        case T_BoolExpr:
            deparseBoolExpr((BoolExpr *) node, context);
            break;
@@ -3507,6 +3539,24 @@ deparseRelabelType(RelabelType *node, deparse_expr_cxt *context)
                                           node->resulttypmod));
 }
 
+/*
+ * Deparse a ArrayCoerceExpr (array-type conversion) node.
+ */
+static void
+deparseArrayCoerceExpr(ArrayCoerceExpr *node, deparse_expr_cxt *context)
+{
+   deparseExpr(node->arg, context);
+
+   /*
+    * No difference how to deparse explicit cast, but if we omit implicit
+    * cast in the query, it'll be more user-friendly
+    */
+   if (node->coerceformat != COERCE_IMPLICIT_CAST)
+       appendStringInfo(context->buf, "::%s",
+                        deparse_type_name(node->resulttype,
+                                          node->resulttypmod));
+}
+
 /*
  * Deparse a BoolExpr node.
  */
index 2185b42bb4f79f976dc6d8ed4936dae3ce468c70..ff2b30cc91221edd2d6f102a8c712640e0d68f02 100644 (file)
@@ -1180,6 +1180,27 @@ SELECT * FROM ft1 WHERE CASE c3 COLLATE "C" WHEN c6 THEN true ELSE c3 < 'bar' EN
    Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
 (4 rows)
 
+-- Test array type conversion pushdown
+SET plan_cache_mode = force_generic_plan;
+PREPARE s(varchar[]) AS SELECT count(*) FROM ft2 WHERE c6 = ANY ($1);
+EXPLAIN (VERBOSE, COSTS OFF)
+EXECUTE s(ARRAY['1','2']);
+                                         QUERY PLAN                                          
+---------------------------------------------------------------------------------------------
+ Foreign Scan
+   Output: (count(*))
+   Relations: Aggregate on (public.ft2)
+   Remote SQL: SELECT count(*) FROM "S 1"."T 1" WHERE ((c6 = ANY ($1::character varying[])))
+(4 rows)
+
+EXECUTE s(ARRAY['1','2']);
+ count 
+-------
+   200
+(1 row)
+
+DEALLOCATE s;
+RESET plan_cache_mode;
 -- a regconfig constant referring to this text search configuration
 -- is initially unshippable
 CREATE TEXT SEARCH CONFIGURATION public.custom_search
index e534b40de3c76e133eefbbc0b898d885310a5c22..7267732f569e5e9b7665bcc5c4f91389e9409b8a 100644 (file)
@@ -458,6 +458,15 @@ SELECT * FROM ft1 WHERE CASE c3 WHEN c6 THEN true ELSE c3 < 'bar' END;
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT * FROM ft1 WHERE CASE c3 COLLATE "C" WHEN c6 THEN true ELSE c3 < 'bar' END;
 
+-- Test array type conversion pushdown
+SET plan_cache_mode = force_generic_plan;
+PREPARE s(varchar[]) AS SELECT count(*) FROM ft2 WHERE c6 = ANY ($1);
+EXPLAIN (VERBOSE, COSTS OFF)
+EXECUTE s(ARRAY['1','2']);
+EXECUTE s(ARRAY['1','2']);
+DEALLOCATE s;
+RESET plan_cache_mode;
+
 -- a regconfig constant referring to this text search configuration
 -- is initially unshippable
 CREATE TEXT SEARCH CONFIGURATION public.custom_search