Disallow replacing joins with scans in problematic cases.
authorEtsuro Fujita <[email protected]>
Fri, 28 Jul 2023 06:45:09 +0000 (15:45 +0900)
committerEtsuro Fujita <[email protected]>
Fri, 28 Jul 2023 06:45:09 +0000 (15:45 +0900)
Commit e7cb7ee14, which introduced the infrastructure for FDWs and
custom scan providers to replace joins with scans, failed to add support
handling of pseudoconstant quals assigned to replaced joins in
createplan.c, leading to an incorrect plan without a gating Result node
when postgres_fdw replaced a join with such a qual.

To fix, we could add the support by 1) modifying the ForeignPath and
CustomPath structs to store the list of RestrictInfo nodes to apply to
the join, as in JoinPaths, if they represent foreign and custom scans
replacing a join with a scan, and by 2) modifying create_scan_plan() in
createplan.c to use that list in that case, instead of the
baserestrictinfo list, to get pseudoconstant quals assigned to the join;
but #1 would cause an ABI break.  So fix by modifying the infrastructure
to just disallow replacing joins with such quals.

Back-patch to all supported branches.

Reported by Nishant Sharma.  Patch by me, reviewed by Nishant Sharma and
Richard Guo.

Discussion: https://postgr.es/m/CADrsxdbcN1vejBaf8a%2BQhrZY5PXL-04mCd4GDu6qm6FigDZd6Q%40mail.gmail.com

contrib/postgres_fdw/expected/postgres_fdw.out
contrib/postgres_fdw/sql/postgres_fdw.sql
src/backend/optimizer/path/joinpath.c
src/backend/optimizer/util/restrictinfo.c
src/include/optimizer/restrictinfo.h

index ec650c66244e41d96f8732309a8194aa739c9943..435c828886e77d628d35fb6a1ad0491f0476d89f 100644 (file)
@@ -2283,6 +2283,32 @@ SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM
    1
 (10 rows)
 
+-- join with pseudoconstant quals, not pushed down.
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1 AND CURRENT_USER = SESSION_USER) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
+                                  QUERY PLAN                                   
+-------------------------------------------------------------------------------
+ Limit
+   Output: t1.c1, t2.c1, t1.c3
+   ->  Sort
+         Output: t1.c1, t2.c1, t1.c3
+         Sort Key: t1.c3, t1.c1
+         ->  Result
+               Output: t1.c1, t2.c1, t1.c3
+               One-Time Filter: (CURRENT_USER = SESSION_USER)
+               ->  Hash Join
+                     Output: t1.c1, t1.c3, t2.c1
+                     Hash Cond: (t2.c1 = t1.c1)
+                     ->  Foreign Scan on public.ft2 t2
+                           Output: t2.c1
+                           Remote SQL: SELECT "C 1" FROM "S 1"."T 1"
+                     ->  Hash
+                           Output: t1.c1, t1.c3
+                           ->  Foreign Scan on public.ft1 t1
+                                 Output: t1.c1, t1.c3
+                                 Remote SQL: SELECT "C 1", c3 FROM "S 1"."T 1"
+(19 rows)
+
 -- non-Var items in targetlist of the nullable rel of a join preventing
 -- push-down in some cases
 -- unable to push {ft1, ft2}
index 2b46b2d0315e8732baa99f2859baa3c5ef21917f..486c6d1e9403cc94981b68c1db041b9b309238fa 100644 (file)
@@ -555,6 +555,9 @@ SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10;
 SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10;
+-- join with pseudoconstant quals, not pushed down.
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1 AND CURRENT_USER = SESSION_USER) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
 
 -- non-Var items in targetlist of the nullable rel of a join preventing
 -- push-down in some cases
index 7844a5b2a0ee2c0610cdc28437974c8e425f8f4d..af240bf27762d0408e4b58b61494a582434148f2 100644 (file)
@@ -22,6 +22,7 @@
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/planmain.h"
+#include "optimizer/restrictinfo.h"
 
 /* Hook for plugins to get control in add_paths_to_joinrel() */
 set_join_pathlist_hook_type set_join_pathlist_hook = NULL;
@@ -124,6 +125,7 @@ add_paths_to_joinrel(PlannerInfo *root,
 {
    JoinPathExtraData extra;
    bool        mergejoin_allowed = true;
+   bool        consider_join_pushdown = false;
    ListCell   *lc;
    Relids      joinrelids;
 
@@ -307,13 +309,25 @@ add_paths_to_joinrel(PlannerInfo *root,
        hash_inner_and_outer(root, joinrel, outerrel, innerrel,
                             jointype, &extra);
 
+   /*
+    * createplan.c does not currently support handling of pseudoconstant
+    * clauses assigned to joins pushed down by extensions; check if the
+    * restrictlist has such clauses, and if so, disallow pushing down joins.
+    */
+   if ((joinrel->fdwroutine &&
+        joinrel->fdwroutine->GetForeignJoinPaths) ||
+       set_join_pathlist_hook)
+       consider_join_pushdown = !has_pseudoconstant_clauses(root,
+                                                            restrictlist);
+
    /*
     * 5. If inner and outer relations are foreign tables (or joins) belonging
     * to the same server and assigned to the same user to check access
     * permissions as, give the FDW a chance to push down joins.
     */
    if (joinrel->fdwroutine &&
-       joinrel->fdwroutine->GetForeignJoinPaths)
+       joinrel->fdwroutine->GetForeignJoinPaths &&
+       consider_join_pushdown)
        joinrel->fdwroutine->GetForeignJoinPaths(root, joinrel,
                                                 outerrel, innerrel,
                                                 jointype, &extra);
@@ -321,7 +335,8 @@ add_paths_to_joinrel(PlannerInfo *root,
    /*
     * 6. Finally, give extensions a chance to manipulate the path list.
     */
-   if (set_join_pathlist_hook)
+   if (set_join_pathlist_hook &&
+       consider_join_pushdown)
        set_join_pathlist_hook(root, joinrel, outerrel, innerrel,
                               jointype, &extra);
 }
index edf5a4807fd4c5db6664726d4e0faf96fe4eca98..e047ff155b4a2a4cbc0397aa0218e5c97fd7653a 100644 (file)
@@ -408,6 +408,35 @@ extract_actual_join_clauses(List *restrictinfo_list,
    }
 }
 
+/*
+ * has_pseudoconstant_clauses
+ *
+ * Returns true if 'restrictinfo_list' includes pseudoconstant clauses.
+ *
+ * This is used when we determine whether to allow extensions to consider
+ * pushing down joins in add_paths_to_joinrel().
+ */
+bool
+has_pseudoconstant_clauses(PlannerInfo *root,
+                          List *restrictinfo_list)
+{
+   ListCell   *l;
+
+   /* No need to look if we know there are no pseudoconstants */
+   if (!root->hasPseudoConstantQuals)
+       return false;
+
+   /* See if there are pseudoconstants in the RestrictInfo list */
+   foreach(l, restrictinfo_list)
+   {
+       RestrictInfo *rinfo = lfirst_node(RestrictInfo, l);
+
+       if (rinfo->pseudoconstant)
+           return true;
+   }
+   return false;
+}
+
 
 /*
  * join_clause_is_movable_to
index a734d798c1ed80b96979f690cdda46abc7ca30e3..9f657023f1f0a7ef3f7a719d1cf0c3012e01e7e1 100644 (file)
@@ -39,6 +39,8 @@ extern void extract_actual_join_clauses(List *restrictinfo_list,
                            Relids joinrelids,
                            List **joinquals,
                            List **otherquals);
+extern bool has_pseudoconstant_clauses(PlannerInfo *root,
+                          List *restrictinfo_list);
 extern bool join_clause_is_movable_to(RestrictInfo *rinfo, RelOptInfo *baserel);
 extern bool join_clause_is_movable_into(RestrictInfo *rinfo,
                            Relids currentrelids,