fix-bogus-epq-plan-construction-1.patch
text/x-diff
Filename: fix-bogus-epq-plan-construction-1.patch
Type: text/x-diff
Part: 0
Patch
Format: unified
| File | + | − |
|---|---|---|
| contrib/postgres_fdw/expected/postgres_fdw.out | 0 | 0 |
| contrib/postgres_fdw/postgres_fdw.c | 0 | 0 |
| src/backend/optimizer/plan/createplan.c | 0 | 0 |
| src/include/optimizer/planmain.h | 0 | 0 |
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 OF t1;
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" 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"
! (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 OF t1;
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" 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
! (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" 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"
! (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" 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
! (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);