Bogus EPQ plan construction in postgres_fdw

Поиск
Список
Период
Сортировка
От Tom Lane
Тема Bogus EPQ plan construction in postgres_fdw
Дата
Msg-id 8946.1544644803@sss.pgh.pa.us
обсуждение исходный текст
Ответы Re: Bogus EPQ plan construction in postgres_fdw  (Robert Haas <robertmhaas@gmail.com>)
Список pgsql-hackers
By chance I noticed that postgres_fdw's postgresGetForeignPlan() assumes
--- without any checking --- that the outer_plan it's given for a join
relation must have a NestLoop, MergeJoin, or HashJoin node at the top.
That's been wrong at least since commit 4bbf6edfb (which could cause
insertion of a Sort node on top) and it seems like a pretty unsafe
thing to Just Assume even without that.

There are two ways in which this faulty assumption is materialized.
One is that a new targetlist is jammed into the plan node without
any check as to whether it's really safe to do that, ie whether the
node is projection-capable or not.  Of course, a Sort node is *not*
projection-capable, meaning that we get a broken plan tree that will
not deliver the set of columns that postgres_fdw wanted.

This would be a security problem, similar to other recent problems where
the output datatypes of a plan node are misidentified, except that through
blind good fortune it seems impossible to reach the problem at execution.
The jammed-in targetlist is always identical to the subplan's original
targetlist (i.e., it's the Vars-only targetlist the planner constructed
for the joinrel) except at the topmost join level --- and if this is
happening at the topmost level, then the foreign join must encompass all
relations in the query.  That means that an EPQ recheck will never happen
(since EPQ could fire only if there's at least one non-postgres_fdw
relation involved), so the broken plan tree will never be executed.

There remains a cosmetic problem, which is that since the Sort node's
tlist doesn't match reality, EXPLAIN sometimes gets confused and prints
garbage info about the Sort's sortkeys.  This is in fact visible in
current regression test results:

               ->  Sort
                     Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
                     Sort Key: t1.c3 USING <, t1.c1

The "USING <" bit should only appear if the sort key is using a nondefault
sort operator, which surely isn't the case here, so why's that there?
The reason is that EXPLAIN is looking at the bogus tlist and drawing the
wrong conclusion about the sort key's datatype, which naturally leads it
to draw the wrong conclusion about whether the operator is the default.

The second way in which postgresGetForeignPlan() is broken is that it
does this without any check on the plan node's actual type:

                if (join_plan->jointype == JOIN_INNER)
                    join_plan->joinqual = list_delete(join_plan->joinqual,
                                                      qual);

This would certainly lead to a crash if list_delete were invoked on
some bits that weren't a pointer-to-List.  In the case of a Sort node,
we're again escaping bad consequences through blind good fortune:
Join.jointype is at the same struct offset as Sort.numCols, which will
never be zero.  Hence, if JoinType is the same size as int, or if it's
smaller but the machine is little-endian, jointype will not read as
JOIN_INNER (zero) so nothing bad happens.  On a big-endian machine where
enum types can be smaller than int, this'd likely crash in list_delete.
The fact that none of our big-endian buildfarm critters have failed on
the regression tests suggests that that combination may not exist in
reality.  (We'd also have a problem if the plan node was neither a join
nor a Sort, but I'm unsure whether such a case is actually reachable.)

Hence, I'm planning to apply the attached, which removes both of the
unwarranted assumptions.  In the qual-deletion step, we can just not
try to delete quals if it's not recognizably a join node; that's only
a minor optimization that we can skip.  The other problem can be fixed
by checking whether the node is projection-capable and inserting a
Result node if it isn't.  createplan.c has logic for exactly that,
but it wasn't exposed in a usable form, so I modified createplan.c
to provide a function for this.

            regards, tom lane

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index dfa6201..666548b 100644
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
*************** SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2
*** 1701,1725 ****
                 Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
                 Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
                 Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1",
r1.c2,r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2,
r2.c3,r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C
1"))))ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST FOR UPDATE OF r1 
!                ->  Sort
!                      Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
!                      Sort Key: t1.c3 USING <, t1.c1
!                      ->  Merge Join
                             Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
!                            Merge Cond: (t1.c1 = t2.c1)
!                            ->  Sort
!                                  Output: t1.c1, t1.c3, t1.*
!                                  Sort Key: t1.c1
!                                  ->  Foreign Scan on public.ft1 t1
                                         Output: t1.c1, t1.c3, t1.*
!                                        Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR
UPDATE
!                            ->  Sort
!                                  Output: t2.c1, t2.*
!                                  Sort Key: t2.c1
!                                  ->  Foreign Scan on public.ft2 t2
                                         Output: t2.c1, t2.*
!                                        Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
! (26 rows)

  SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE
OFt1; 
   c1  | c1
--- 1701,1727 ----
                 Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
                 Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
                 Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1",
r1.c2,r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2,
r2.c3,r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C
1"))))ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST FOR UPDATE OF r1 
!                ->  Result
!                      Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
!                      ->  Sort
                             Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
!                            Sort Key: t1.c3, t1.c1
!                            ->  Merge Join
!                                  Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
!                                  Merge Cond: (t1.c1 = t2.c1)
!                                  ->  Sort
                                         Output: t1.c1, t1.c3, t1.*
!                                        Sort Key: t1.c1
!                                        ->  Foreign Scan on public.ft1 t1
!                                              Output: t1.c1, t1.c3, t1.*
!                                              Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
FORUPDATE 
!                                  ->  Sort
                                         Output: t2.c1, t2.*
!                                        Sort Key: t2.c1
!                                        ->  Foreign Scan on public.ft2 t2
!                                              Output: t2.c1, t2.*
!                                              Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
! (28 rows)

  SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE
OFt1; 
   c1  | c1
*************** SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2
*** 1748,1772 ****
                 Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
                 Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
                 Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1",
r1.c2,r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2,
r2.c3,r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C
1"))))ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST FOR UPDATE OF r1 FOR UPDATE OF r2 
!                ->  Sort
!                      Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
!                      Sort Key: t1.c3 USING <, t1.c1
!                      ->  Merge Join
                             Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
!                            Merge Cond: (t1.c1 = t2.c1)
!                            ->  Sort
!                                  Output: t1.c1, t1.c3, t1.*
!                                  Sort Key: t1.c1
!                                  ->  Foreign Scan on public.ft1 t1
                                         Output: t1.c1, t1.c3, t1.*
!                                        Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR
UPDATE
!                            ->  Sort
!                                  Output: t2.c1, t2.*
!                                  Sort Key: t2.c1
!                                  ->  Foreign Scan on public.ft2 t2
                                         Output: t2.c1, t2.*
!                                        Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR
UPDATE
! (26 rows)

  SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE;
   c1  | c1
--- 1750,1776 ----
                 Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
                 Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
                 Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1",
r1.c2,r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2,
r2.c3,r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C
1"))))ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST FOR UPDATE OF r1 FOR UPDATE OF r2 
!                ->  Result
!                      Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
!                      ->  Sort
                             Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
!                            Sort Key: t1.c3, t1.c1
!                            ->  Merge Join
!                                  Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
!                                  Merge Cond: (t1.c1 = t2.c1)
!                                  ->  Sort
                                         Output: t1.c1, t1.c3, t1.*
!                                        Sort Key: t1.c1
!                                        ->  Foreign Scan on public.ft1 t1
!                                              Output: t1.c1, t1.c3, t1.*
!                                              Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
FORUPDATE 
!                                  ->  Sort
                                         Output: t2.c1, t2.*
!                                        Sort Key: t2.c1
!                                        ->  Foreign Scan on public.ft2 t2
!                                              Output: t2.c1, t2.*
!                                              Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
FORUPDATE 
! (28 rows)

  SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE;
   c1  | c1
*************** SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2
*** 1796,1820 ****
                 Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
                 Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
                 Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1",
r1.c2,r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2,
r2.c3,r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C
1"))))ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST FOR SHARE OF r1 
!                ->  Sort
!                      Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
!                      Sort Key: t1.c3 USING <, t1.c1
!                      ->  Merge Join
                             Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
!                            Merge Cond: (t1.c1 = t2.c1)
!                            ->  Sort
!                                  Output: t1.c1, t1.c3, t1.*
!                                  Sort Key: t1.c1
!                                  ->  Foreign Scan on public.ft1 t1
                                         Output: t1.c1, t1.c3, t1.*
!                                        Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR
SHARE
!                            ->  Sort
!                                  Output: t2.c1, t2.*
!                                  Sort Key: t2.c1
!                                  ->  Foreign Scan on public.ft2 t2
                                         Output: t2.c1, t2.*
!                                        Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
! (26 rows)

  SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE OF
t1;
   c1  | c1
--- 1800,1826 ----
                 Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
                 Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
                 Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1",
r1.c2,r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2,
r2.c3,r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C
1"))))ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST FOR SHARE OF r1 
!                ->  Result
!                      Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
!                      ->  Sort
                             Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
!                            Sort Key: t1.c3, t1.c1
!                            ->  Merge Join
!                                  Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
!                                  Merge Cond: (t1.c1 = t2.c1)
!                                  ->  Sort
                                         Output: t1.c1, t1.c3, t1.*
!                                        Sort Key: t1.c1
!                                        ->  Foreign Scan on public.ft1 t1
!                                              Output: t1.c1, t1.c3, t1.*
!                                              Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
FORSHARE 
!                                  ->  Sort
                                         Output: t2.c1, t2.*
!                                        Sort Key: t2.c1
!                                        ->  Foreign Scan on public.ft2 t2
!                                              Output: t2.c1, t2.*
!                                              Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
! (28 rows)

  SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE OF
t1;
   c1  | c1
*************** SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2
*** 1843,1867 ****
                 Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
                 Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
                 Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1",
r1.c2,r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2,
r2.c3,r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C
1"))))ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST FOR SHARE OF r1 FOR SHARE OF r2 
!                ->  Sort
!                      Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
!                      Sort Key: t1.c3 USING <, t1.c1
!                      ->  Merge Join
                             Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
!                            Merge Cond: (t1.c1 = t2.c1)
!                            ->  Sort
!                                  Output: t1.c1, t1.c3, t1.*
!                                  Sort Key: t1.c1
!                                  ->  Foreign Scan on public.ft1 t1
                                         Output: t1.c1, t1.c3, t1.*
!                                        Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR
SHARE
!                            ->  Sort
!                                  Output: t2.c1, t2.*
!                                  Sort Key: t2.c1
!                                  ->  Foreign Scan on public.ft2 t2
                                         Output: t2.c1, t2.*
!                                        Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR
SHARE
! (26 rows)

  SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE;
   c1  | c1
--- 1849,1875 ----
                 Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
                 Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
                 Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1",
r1.c2,r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2,
r2.c3,r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C
1"))))ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST FOR SHARE OF r1 FOR SHARE OF r2 
!                ->  Result
!                      Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
!                      ->  Sort
                             Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
!                            Sort Key: t1.c3, t1.c1
!                            ->  Merge Join
!                                  Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
!                                  Merge Cond: (t1.c1 = t2.c1)
!                                  ->  Sort
                                         Output: t1.c1, t1.c3, t1.*
!                                        Sort Key: t1.c1
!                                        ->  Foreign Scan on public.ft1 t1
!                                              Output: t1.c1, t1.c3, t1.*
!                                              Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
FORSHARE 
!                                  ->  Sort
                                         Output: t2.c1, t2.*
!                                        Sort Key: t2.c1
!                                        ->  Foreign Scan on public.ft2 t2
!                                              Output: t2.c1, t2.*
!                                              Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
FORSHARE 
! (28 rows)

  SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE;
   c1  | c1
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 674eb98..2ac9d7b 100644
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
*************** postgresGetForeignPlan(PlannerInfo *root
*** 1229,1239 ****

          /*
           * Ensure that the outer plan produces a tuple whose descriptor
!          * matches our scan tuple slot. This is safe because all scans and
!          * joins support projection, so we never need to insert a Result node.
!          * Also, remove the local conditions from outer plan's quals, lest
!          * they will be evaluated twice, once by the local plan and once by
!          * the scan.
           */
          if (outer_plan)
          {
--- 1229,1237 ----

          /*
           * Ensure that the outer plan produces a tuple whose descriptor
!          * matches our scan tuple slot.  Also, remove the local conditions
!          * from outer plan's quals, lest they be evaluated twice, once by the
!          * local plan and once by the scan.
           */
          if (outer_plan)
          {
*************** postgresGetForeignPlan(PlannerInfo *root
*** 1246,1268 ****
               */
              Assert(!IS_UPPER_REL(foreignrel));

!             outer_plan->targetlist = fdw_scan_tlist;
!
              foreach(lc, local_exprs)
              {
-                 Join       *join_plan = (Join *) outer_plan;
                  Node       *qual = lfirst(lc);

                  outer_plan->qual = list_delete(outer_plan->qual, qual);

                  /*
                   * For an inner join the local conditions of foreign scan plan
!                  * can be part of the joinquals as well.
                   */
!                 if (join_plan->jointype == JOIN_INNER)
!                     join_plan->joinqual = list_delete(join_plan->joinqual,
!                                                       qual);
              }
          }
      }

--- 1244,1285 ----
               */
              Assert(!IS_UPPER_REL(foreignrel));

!             /*
!              * First, update the plan's qual list if possible.  In some cases
!              * the quals might be enforced below the topmost plan level, in
!              * which case we'll fail to remove them; it's not worth working
!              * harder than this.
!              */
              foreach(lc, local_exprs)
              {
                  Node       *qual = lfirst(lc);

                  outer_plan->qual = list_delete(outer_plan->qual, qual);

                  /*
                   * For an inner join the local conditions of foreign scan plan
!                  * can be part of the joinquals as well.  (They might also be
!                  * in the mergequals or hashquals, but we can't touch those
!                  * without breaking the plan.)
                   */
!                 if (IsA(outer_plan, NestLoop) ||
!                     IsA(outer_plan, MergeJoin) ||
!                     IsA(outer_plan, HashJoin))
!                 {
!                     Join       *join_plan = (Join *) outer_plan;
!
!                     if (join_plan->jointype == JOIN_INNER)
!                         join_plan->joinqual = list_delete(join_plan->joinqual,
!                                                           qual);
!                 }
              }
+
+             /*
+              * Now fix the subplan's tlist --- this might result in inserting
+              * a Result node atop the plan tree.
+              */
+             outer_plan = change_plan_targetlist(outer_plan, fdw_scan_tlist,
+                                                 best_path->path.parallel_safe);
          }
      }

diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index da7a920..91cf782 100644
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
*************** create_unique_plan(PlannerInfo *root, Un
*** 1407,1426 ****
          }
      }

      if (newitems || best_path->umethod == UNIQUE_PATH_SORT)
!     {
!         /*
!          * If the top plan node can't do projections and its existing target
!          * list isn't already what we need, we need to add a Result node to
!          * help it along.
!          */
!         if (!is_projection_capable_plan(subplan) &&
!             !tlist_same_exprs(newtlist, subplan->targetlist))
!             subplan = inject_projection_plan(subplan, newtlist,
!                                              best_path->path.parallel_safe);
!         else
!             subplan->targetlist = newtlist;
!     }

      /*
       * Build control information showing which subplan output columns are to
--- 1407,1416 ----
          }
      }

+     /* Use change_plan_targetlist in case we need to insert a Result node */
      if (newitems || best_path->umethod == UNIQUE_PATH_SORT)
!         subplan = change_plan_targetlist(subplan, newtlist,
!                                          best_path->path.parallel_safe);

      /*
       * Build control information showing which subplan output columns are to
*************** inject_projection_plan(Plan *subplan, Li
*** 1763,1768 ****
--- 1753,1792 ----
  }

  /*
+  * change_plan_targetlist
+  *      Externally available wrapper for inject_projection_plan.
+  *
+  * This is meant for use by FDW plan-generation functions, which might
+  * want to adjust the tlist computed by some subplan tree.  In general,
+  * a Result node is needed to compute the new tlist, but we can optimize
+  * some cases.
+  *
+  * In most cases, tlist_parallel_safe can just be passed as the parallel_safe
+  * flag of the FDW's own Path node.
+  */
+ Plan *
+ change_plan_targetlist(Plan *subplan, List *tlist, bool tlist_parallel_safe)
+ {
+     /*
+      * If the top plan node can't do projections and its existing target list
+      * isn't already what we need, we need to add a Result node to help it
+      * along.
+      */
+     if (!is_projection_capable_plan(subplan) &&
+         !tlist_same_exprs(tlist, subplan->targetlist))
+         subplan = inject_projection_plan(subplan, tlist,
+                                          subplan->parallel_safe &&
+                                          tlist_parallel_safe);
+     else
+     {
+         /* Else we can just replace the plan node's tlist */
+         subplan->targetlist = tlist;
+         subplan->parallel_safe &= tlist_parallel_safe;
+     }
+     return subplan;
+ }
+
+ /*
   * create_sort_plan
   *
   *      Create a Sort plan for 'best_path' and (recursively) plans
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index c8ab028..a081ca6 100644
*** a/src/include/optimizer/planmain.h
--- b/src/include/optimizer/planmain.h
*************** extern ForeignScan *make_foreignscan(Lis
*** 53,58 ****
--- 53,60 ----
                   Index scanrelid, List *fdw_exprs, List *fdw_private,
                   List *fdw_scan_tlist, List *fdw_recheck_quals,
                   Plan *outer_plan);
+ extern Plan *change_plan_targetlist(Plan *subplan, List *tlist,
+                        bool tlist_parallel_safe);
  extern Plan *materialize_finished_plan(Plan *subplan);
  extern bool is_projection_capable_path(Path *path);
  extern bool is_projection_capable_plan(Plan *plan);

В списке pgsql-hackers по дате отправления:

Предыдущее
От: Tomas Vondra
Дата:
Сообщение: Re: COPY FROM WHEN condition
Следующее
От: Andres Freund
Дата:
Сообщение: Minimal logical decoding on standbys