Re: Parameterized-path cost comparisons need some work - Mailing list pgsql-hackers

From Tom Lane
Subject Re: Parameterized-path cost comparisons need some work
Date
Msg-id 10175.1334679281@sss.pgh.pa.us
Whole thread Raw
In response to Re: Parameterized-path cost comparisons need some work  (Robert Haas <robertmhaas@gmail.com>)
Responses Re: Parameterized-path cost comparisons need some work
Re: Parameterized-path cost comparisons need some work
List pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
> On Thu, Apr 12, 2012 at 3:27 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> 3. Rearrange plan generation so that a parameterized path always uses
>> all join clauses available from the specified outer rels.  (Any that
>> don't work as indexquals would have to be applied as filter conditions.)
>> If we did that, then we would be back to a situation where all paths
>> with the same parameterization should yield the same rowcount, thus
>> justifying letting add_path_precheck work as it does now.
>>
>> #3 would amount to pushing quals that would otherwise be checked at the
>> nestloop join node down to the lowest inner-relation level where they
>> could be checked.  This is something I'd suspected would be a good idea
>> to start with, but hadn't gotten around to implementing for non-index
>> quals.  It had not occurred to me that it might simplify cost estimation
>> to always do that.

> This seems like it could be quite a significant win.

I've been hacking away on a patch to do this, and attached is something
that I think is pretty close to committable.  It needs another going-over
and some new regression test cases, but it seems to work, and it fixes a
number of things besides the above-mentioned issue.  In particular, this
has a much more principled approach than HEAD does to the problem of where
to place parameterizable join clauses in the plan tree; that can be seen
in the one change in the existing regression tests, where we no longer
generate a redundant upper-level copy of an OR join clause that the old
code wasn't bright enough to get rid of.

The patch is a bit large because I chose to revise the data representation.
Instead of each Path having its own required_outer, rows, and
param_clauses fields, now a parameterized Path has a pointer to a
ParamPathInfo struct that it shares with other Paths for the same rel and
the same parameterization.  This guarantees that such paths will have the
same estimated rowcount, because we only compute that once per
parameterization, which should save some work as well as making the world
safe for add_path_precheck.

The only place where this approach proved a bit tricky was in handling
AppendPaths and MergeAppendPaths, which didn't surprise me because that
was a rough spot for the old way too (and indeed they aren't handled
completely correctly in HEAD).  A parameterized path is now *required*
to enforce all clauses that the join clause movement rules assign to it;
but Append and MergeAppend don't do qual checking, and I didn't feel like
changing that.  The method that I have settled on is to require all child
paths of a parameterized append to have the exact same parameterization,
IOW we push the qual checks down below the append.  Now the interesting
point about that is that we want to support Appends wherein some children
are seqscans and some are indexscans (consider a partitioned table where
the parent is a dummy empty table with no indexes).  The "raw" situation
there is that we'll have a plain seqscan path for the parent and then a
collection of similarly-parameterized indexscan paths for the live
partition children.  To make it possible to convert that case into a
parameterized append path, I added parameterization support to seqscans
and then wrote "reparameterize_path", which changes a Path to increase
its parameterization level (and thereby assign it more pushed-down join
clauses to check at runtime).  That allows us to reconfigure the seqscan
to match the other children.  I've also added such support to
SubqueryScan, on the grounds that the other common use of append paths is
UNION ALL across subqueries.  We might later want to add parameterization
support to other path types, but this seemed like enough for the moment.

BTW, after writing the code for it I decided to remove creation of
parameterized MergeAppendPaths from allpaths.c, though there is still some
support for them elsewhere.  On reflection it seemed to me that the code
was willing to create far too many of these, much more than their
potential usefulness could justify (remember that parameterized paths must
be on the inside of a nestloop, so their sort ordering is typically of
marginal use).  We can put that back if we can think of a more restrictive
heuristic for when to create them.

The core of the patch is in the new functions get_baserel_parampathinfo
and get_joinrel_parampathinfo, which look up or construct ParamPathInfos,
and join_clause_is_parameterizable_for and
join_clause_is_parameterizable_within, which control
movement of parameterizable join clauses.  (I'm not that thrilled with the
names of the latter two functions, anybody got a better idea?)  The rest
of it is pretty much boilerplate changes and replacing ad-hoc logic with
uses of this stuff.

I have a couple of other ideas in mind in the way of mop-up, but they are
not in this patch to keep it from bloating even more.  First, I'm thinking
we should get rid of RelOptInfo.baserestrictcost, thus forcing all scan
cost estimators to invoke cost_qual_eval explicitly.  That field has been
vestigial from a planning-speed standpoint for a long time, ever since we
started caching eval costs in RestrictInfos.  The most commonly used cost
estimators don't use it anymore as of this patch, and it'd likely be best
to have a uniform coding pattern there.  Second, I've gotten dissatisfied
with the terminology "required_outer" that was used in the original param
plans patch.  I'm considering a global search and replace with
"param_relids" or some variant of that.

Comments?

            regards, tom lane

diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index 2d36a72e9f299aa45f0f108e4431c83fe77faf26..9ada736a48c179bf6672609f620f0086d95d57bd 100644
*** a/contrib/file_fdw/file_fdw.c
--- b/contrib/file_fdw/file_fdw.c
*************** fileGetForeignPaths(PlannerInfo *root,
*** 468,474 ****
                                       total_cost,
                                       NIL, /* no pathkeys */
                                       NULL, /* no outer rel either */
-                                      NIL,
                                       NIL)); /* no fdw_private data */

      /*
--- 468,473 ----
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 594b3fdea85c571bd04e2c07a8da20fffce1af7a..757cec7e0e2053ea7768edd8f6bb6f9a532b09bd 100644
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outPathInfo(StringInfo str, const Path
*** 1468,1479 ****
      WRITE_ENUM_FIELD(pathtype, NodeTag);
      appendStringInfo(str, " :parent_relids ");
      _outBitmapset(str, node->parent->relids);
      WRITE_FLOAT_FIELD(rows, "%.0f");
      WRITE_FLOAT_FIELD(startup_cost, "%.2f");
      WRITE_FLOAT_FIELD(total_cost, "%.2f");
      WRITE_NODE_FIELD(pathkeys);
-     WRITE_BITMAPSET_FIELD(required_outer);
-     WRITE_NODE_FIELD(param_clauses);
  }

  /*
--- 1468,1478 ----
      WRITE_ENUM_FIELD(pathtype, NodeTag);
      appendStringInfo(str, " :parent_relids ");
      _outBitmapset(str, node->parent->relids);
+     WRITE_NODE_FIELD(param_info);
      WRITE_FLOAT_FIELD(rows, "%.0f");
      WRITE_FLOAT_FIELD(startup_cost, "%.2f");
      WRITE_FLOAT_FIELD(total_cost, "%.2f");
      WRITE_NODE_FIELD(pathkeys);
  }

  /*
*************** _outRelOptInfo(StringInfo str, const Rel
*** 1727,1732 ****
--- 1726,1732 ----
      WRITE_INT_FIELD(width);
      WRITE_NODE_FIELD(reltargetlist);
      WRITE_NODE_FIELD(pathlist);
+     /* don't print ppilist, as pathlist dump will show the values */
      WRITE_NODE_FIELD(cheapest_startup_path);
      WRITE_NODE_FIELD(cheapest_total_path);
      WRITE_NODE_FIELD(cheapest_unique_path);
*************** _outPathKey(StringInfo str, const PathKe
*** 1818,1823 ****
--- 1818,1833 ----
  }

  static void
+ _outParamPathInfo(StringInfo str, const ParamPathInfo *node)
+ {
+     WRITE_NODE_TYPE("PARAMPATHINFO");
+
+     WRITE_BITMAPSET_FIELD(ppi_outer);
+     WRITE_FLOAT_FIELD(ppi_rows, "%.0f");
+     WRITE_NODE_FIELD(ppi_clauses);
+ }
+
+ static void
  _outRestrictInfo(StringInfo str, const RestrictInfo *node)
  {
      WRITE_NODE_TYPE("RESTRICTINFO");
*************** _outNode(StringInfo str, const void *obj
*** 2992,2997 ****
--- 3002,3010 ----
              case T_PathKey:
                  _outPathKey(str, obj);
                  break;
+             case T_ParamPathInfo:
+                 _outParamPathInfo(str, obj);
+                 break;
              case T_RestrictInfo:
                  _outRestrictInfo(str, obj);
                  break;
diff --git a/src/backend/optimizer/README b/src/backend/optimizer/README
index 9ab1fdf8154b964be91eb54ff2a3f38e9705af74..6b4d88bb8c5f93dd492f2ef95ab2f8c68d852fda 100644
*** a/src/backend/optimizer/README
--- b/src/backend/optimizer/README
*************** possible join types.  So we will form a
*** 751,756 ****
--- 751,783 ----
  plan shown above, and it will compete in the usual way with paths built
  from non-parameterized scans.

+ While all ordinary paths for a particular relation generate the same set
+ of rows (since they must all apply the same set of restriction clauses),
+ parameterized paths typically generate fewer rows than less-parameterized
+ paths, since they have additional clauses to work with.  This means we
+ must consider the number of rows generated as an additional figure of
+ merit.  A path that costs more than another, but generates fewer rows,
+ must be kept since the smaller number of rows might save work at some
+ intermediate join level.  (It would not save anything if joined
+ immediately to the source of the parameters.)
+
+ To keep cost estimation rules relatively simple, we make an implementation
+ restriction that all paths for a given relation of the same parameterization
+ (i.e., the same set of outer relations supplying parameters) must have the
+ same rowcount estimate.  This is justified by insisting that each such path
+ apply *all* join clauses that are available with the named outer relations.
+ Different paths might, for instance, choose different join clauses to use
+ as index clauses; but they must then apply any other join clauses available
+ from the same outer relations as filter conditions, so that the set of rows
+ returned is held constant.  This restriction doesn't degrade the quality of
+ the finished plan: it amounts to saying that we should always push down
+ parameterizable join clauses to the lowest possible evaluation level, which
+ is a good thing anyway.  The restriction is useful in particular to support
+ pre-filtering of join paths in add_path_precheck.  Without this rule we
+ could never reject a parameterized path in advance of computing its
+ rowcount estimate, which would greatly reduce the value of the pre-filter
+ mechanism.
+
  To limit planning time, we have to avoid generating an unreasonably large
  number of parameterized paths.  We do this by only generating parameterized
  relation scan paths for index scans, and then only for indexes for which
*************** a nestloop that provides parameters to t
*** 763,776 ****
  do not ignore merge joins entirely, joinpath.c does not fully explore the
  space of potential merge joins with parameterized inputs.  Also, add_path
  treats parameterized paths as having no pathkeys, so that they compete
! only on cost and don't get preference for producing a special sort order.
! This creates additional bias against merge joins, since we might discard
! a path that could have been useful for performing a merge without an
! explicit sort step.  Since a parameterized path must ultimately be used
! on the inside of a nestloop, where its sort order is uninteresting, these
! choices do not affect any requirement for the final output order of a
! query --- they only make it harder to use a merge join at a lower level.
! The savings in planning work justifies that.


  -- bjm & tgl
--- 790,803 ----
  do not ignore merge joins entirely, joinpath.c does not fully explore the
  space of potential merge joins with parameterized inputs.  Also, add_path
  treats parameterized paths as having no pathkeys, so that they compete
! only on cost and rowcount; they don't get preference for producing a
! special sort order.  This creates additional bias against merge joins,
! since we might discard a path that could have been useful for performing
! a merge without an explicit sort step.  Since a parameterized path must
! ultimately be used on the inside of a nestloop, where its sort order is
! uninteresting, these choices do not affect any requirement for the final
! output order of a query --- they only make it harder to use a merge join
! at a lower level.  The savings in planning work justifies that.


  -- bjm & tgl
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 03c604a03d6f37d9d6d975fd0809429c56d61b38..073ef9263ad15ed7b7ad30d62341f6bef62fd4cc 100644
*** a/src/backend/optimizer/path/allpaths.c
--- b/src/backend/optimizer/path/allpaths.c
*************** static void set_append_rel_pathlist(Plan
*** 67,74 ****
                          Index rti, RangeTblEntry *rte);
  static void generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel,
                             List *live_childrels,
!                            List *all_child_pathkeys,
!                            Relids required_outer);
  static List *accumulate_append_subpath(List *subpaths, Path *path);
  static void set_dummy_rel_pathlist(RelOptInfo *rel);
  static void set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
--- 67,73 ----
                          Index rti, RangeTblEntry *rte);
  static void generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel,
                             List *live_childrels,
!                            List *all_child_pathkeys);
  static List *accumulate_append_subpath(List *subpaths, Path *path);
  static void set_dummy_rel_pathlist(RelOptInfo *rel);
  static void set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
*************** static void
*** 375,381 ****
  set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
  {
      /* Consider sequential scan */
!     add_path(rel, create_seqscan_path(root, rel));

      /* Consider index scans */
      create_index_paths(root, rel);
--- 374,380 ----
  set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
  {
      /* Consider sequential scan */
!     add_path(rel, create_seqscan_path(root, rel, NULL));

      /* Consider index scans */
      create_index_paths(root, rel);
*************** set_append_rel_pathlist(PlannerInfo *roo
*** 717,723 ****
          {
              Path       *childpath = (Path *) lfirst(lcp);
              List       *childkeys = childpath->pathkeys;
!             Relids        childouter = childpath->required_outer;

              /* Unsorted paths don't contribute to pathkey list */
              if (childkeys != NIL)
--- 716,722 ----
          {
              Path       *childpath = (Path *) lfirst(lcp);
              List       *childkeys = childpath->pathkeys;
!             Relids        childouter = PATH_PARAMRELS(childpath);

              /* Unsorted paths don't contribute to pathkey list */
              if (childkeys != NIL)
*************** set_append_rel_pathlist(PlannerInfo *roo
*** 777,801 ****
       * (Note: this is correct even if we have zero or one live subpath due to
       * constraint exclusion.)
       */
!     add_path(rel, (Path *) create_append_path(rel, subpaths));

      /*
       * Build unparameterized MergeAppend paths based on the collected list of
       * child pathkeys.
       */
!     generate_mergeappend_paths(root, rel, live_childrels,
!                                all_child_pathkeys, NULL);

      /*
!      * Build Append and MergeAppend paths for each parameterization seen
!      * among the child rels.  (This may look pretty expensive, but in most
!      * cases of practical interest, the child relations will tend to expose
!      * the same parameterizations and pathkeys, so that not that many cases
!      * actually get considered here.)
       */
      foreach(l, all_child_outers)
      {
          Relids    required_outer = (Relids) lfirst(l);
          ListCell   *lcr;

          /* Select the child paths for an Append with this parameterization */
--- 776,799 ----
       * (Note: this is correct even if we have zero or one live subpath due to
       * constraint exclusion.)
       */
!     add_path(rel, (Path *) create_append_path(rel, subpaths, NULL));

      /*
       * Build unparameterized MergeAppend paths based on the collected list of
       * child pathkeys.
       */
!     generate_mergeappend_paths(root, rel, live_childrels, all_child_pathkeys);

      /*
!      * Build Append paths for each parameterization seen among the child rels.
!      * (This may look pretty expensive, but in most cases of practical
!      * interest, the child rels will expose mostly the same parameterizations,
!      * so that not that many cases actually get considered here.)
       */
      foreach(l, all_child_outers)
      {
          Relids    required_outer = (Relids) lfirst(l);
+         bool        ok = true;
          ListCell   *lcr;

          /* Select the child paths for an Append with this parameterization */
*************** set_append_rel_pathlist(PlannerInfo *roo
*** 812,824 ****
                                                 TOTAL_COST);
              Assert(cheapest_total != NULL);

              subpaths = accumulate_append_subpath(subpaths, cheapest_total);
          }
-         add_path(rel, (Path *) create_append_path(rel, subpaths));

!         /* And build parameterized MergeAppend paths */
!         generate_mergeappend_paths(root, rel, live_childrels,
!                                    all_child_pathkeys, required_outer);
      }

      /* Select cheapest paths */
--- 810,833 ----
                                                 TOTAL_COST);
              Assert(cheapest_total != NULL);

+             /* Children must have exactly the desired parameterization */
+             if (!bms_equal(PATH_PARAMRELS(cheapest_total), required_outer))
+             {
+                 cheapest_total = reparameterize_path(root, cheapest_total,
+                                                      required_outer, 1.0);
+                 if (cheapest_total == NULL)
+                 {
+                     ok = false;
+                     break;
+                 }
+             }
+
              subpaths = accumulate_append_subpath(subpaths, cheapest_total);
          }

!         if (ok)
!             add_path(rel, (Path *)
!                      create_append_path(rel, subpaths, required_outer));
      }

      /* Select cheapest paths */
*************** set_append_rel_pathlist(PlannerInfo *roo
*** 830,847 ****
   *        Generate MergeAppend paths for an append relation
   *
   * Generate a path for each ordering (pathkey list) appearing in
!  * all_child_pathkeys.  If required_outer isn't NULL, accept paths having
!  * those relations as required outer relations.
   *
   * We consider both cheapest-startup and cheapest-total cases, ie, for each
   * interesting ordering, collect all the cheapest startup subpaths and all the
   * cheapest total paths, and build a MergeAppend path for each case.
   */
  static void
  generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel,
                             List *live_childrels,
!                            List *all_child_pathkeys,
!                            Relids required_outer)
  {
      ListCell   *lcp;

--- 839,861 ----
   *        Generate MergeAppend paths for an append relation
   *
   * Generate a path for each ordering (pathkey list) appearing in
!  * all_child_pathkeys.
   *
   * We consider both cheapest-startup and cheapest-total cases, ie, for each
   * interesting ordering, collect all the cheapest startup subpaths and all the
   * cheapest total paths, and build a MergeAppend path for each case.
+  *
+  * We don't currently generate any parameterized MergeAppend paths.  While
+  * it would not take much more code here to do so, it's very unclear that it
+  * is worth the planning cycles to investigate such paths: there's little
+  * use for an ordered path on the inside of a nestloop.  If we ever try
+  * harder to support parameterized mergejoin cases, it might be worth
+  * adding support for parameterized MergeAppends to feed such joins.
   */
  static void
  generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel,
                             List *live_childrels,
!                            List *all_child_pathkeys)
  {
      ListCell   *lcp;

*************** generate_mergeappend_paths(PlannerInfo *
*** 864,893 ****
              cheapest_startup =
                  get_cheapest_path_for_pathkeys(childrel->pathlist,
                                                 pathkeys,
!                                                required_outer,
                                                 STARTUP_COST);
              cheapest_total =
                  get_cheapest_path_for_pathkeys(childrel->pathlist,
                                                 pathkeys,
!                                                required_outer,
                                                 TOTAL_COST);

              /*
               * If we can't find any paths with the right order just use the
!              * cheapest-total path; we'll have to sort it later.  We can
!              * use the cheapest path for the parameterization, though.
               */
              if (cheapest_startup == NULL || cheapest_total == NULL)
              {
!                 if (required_outer)
!                     cheapest_startup = cheapest_total =
!                         get_cheapest_path_for_pathkeys(childrel->pathlist,
!                                                        NIL,
!                                                        required_outer,
!                                                        TOTAL_COST);
!                 else
!                     cheapest_startup = cheapest_total =
!                         childrel->cheapest_total_path;
                  Assert(cheapest_total != NULL);
              }

--- 878,899 ----
              cheapest_startup =
                  get_cheapest_path_for_pathkeys(childrel->pathlist,
                                                 pathkeys,
!                                                NULL,
                                                 STARTUP_COST);
              cheapest_total =
                  get_cheapest_path_for_pathkeys(childrel->pathlist,
                                                 pathkeys,
!                                                NULL,
                                                 TOTAL_COST);

              /*
               * If we can't find any paths with the right order just use the
!              * cheapest-total path; we'll have to sort it later.
               */
              if (cheapest_startup == NULL || cheapest_total == NULL)
              {
!                 cheapest_startup = cheapest_total =
!                     childrel->cheapest_total_path;
                  Assert(cheapest_total != NULL);
              }

*************** generate_mergeappend_paths(PlannerInfo *
*** 909,920 ****
          add_path(rel, (Path *) create_merge_append_path(root,
                                                          rel,
                                                          startup_subpaths,
!                                                         pathkeys));
          if (startup_neq_total)
              add_path(rel, (Path *) create_merge_append_path(root,
                                                              rel,
                                                              total_subpaths,
!                                                             pathkeys));
      }
  }

--- 915,928 ----
          add_path(rel, (Path *) create_merge_append_path(root,
                                                          rel,
                                                          startup_subpaths,
!                                                         pathkeys,
!                                                         NULL));
          if (startup_neq_total)
              add_path(rel, (Path *) create_merge_append_path(root,
                                                              rel,
                                                              total_subpaths,
!                                                             pathkeys,
!                                                             NULL));
      }
  }

*************** set_dummy_rel_pathlist(RelOptInfo *rel)
*** 958,964 ****
      /* Discard any pre-existing paths; no further need for them */
      rel->pathlist = NIL;

!     add_path(rel, (Path *) create_append_path(rel, NIL));

      /* Select cheapest path (pretty easy in this case...) */
      set_cheapest(rel);
--- 966,972 ----
      /* Discard any pre-existing paths; no further need for them */
      rel->pathlist = NIL;

!     add_path(rel, (Path *) create_append_path(rel, NIL, NULL));

      /* Select cheapest path (pretty easy in this case...) */
      set_cheapest(rel);
*************** set_subquery_pathlist(PlannerInfo *root,
*** 1112,1118 ****
      pathkeys = convert_subquery_pathkeys(root, rel, subroot->query_pathkeys);

      /* Generate appropriate path */
!     add_path(rel, create_subqueryscan_path(rel, pathkeys));

      /* Select cheapest path (pretty easy in this case...) */
      set_cheapest(rel);
--- 1120,1126 ----
      pathkeys = convert_subquery_pathkeys(root, rel, subroot->query_pathkeys);

      /* Generate appropriate path */
!     add_path(rel, create_subqueryscan_path(root, rel, pathkeys, NULL));

      /* Select cheapest path (pretty easy in this case...) */
      set_cheapest(rel);
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 0330f01531e5bc1ae2a98c807b60fa212b50ed17..db16f9abfd1b75974e7ecad8263edf25e44fbfd5 100644
*** a/src/backend/optimizer/path/costsize.c
--- b/src/backend/optimizer/path/costsize.c
*************** static MergeScanSelCache *cached_scansel
*** 125,136 ****
  static void cost_rescan(PlannerInfo *root, Path *path,
              Cost *rescan_startup_cost, Cost *rescan_total_cost);
  static bool cost_qual_eval_walker(Node *node, cost_qual_eval_context *context);
! static bool has_indexed_join_quals(NestPath *path, List *joinclauses);
  static double approx_tuple_count(PlannerInfo *root, JoinPath *path,
                     List *quals);
- static void set_joinpath_size_estimate(PlannerInfo *root, JoinPath *path,
-                            SpecialJoinInfo *sjinfo,
-                            List *restrictlist);
  static double calc_joinrel_size_estimate(PlannerInfo *root,
                             double outer_rows,
                             double inner_rows,
--- 125,133 ----
  static void cost_rescan(PlannerInfo *root, Path *path,
              Cost *rescan_startup_cost, Cost *rescan_total_cost);
  static bool cost_qual_eval_walker(Node *node, cost_qual_eval_context *context);
! static bool has_indexed_join_quals(NestPath *joinpath);
  static double approx_tuple_count(PlannerInfo *root, JoinPath *path,
                     List *quals);
  static double calc_joinrel_size_estimate(PlannerInfo *root,
                             double outer_rows,
                             double inner_rows,
*************** clamp_row_est(double nrows)
*** 165,186 ****
  /*
   * cost_seqscan
   *      Determines and returns the cost of scanning a relation sequentially.
   */
  void
  cost_seqscan(Path *path, PlannerInfo *root,
!              RelOptInfo *baserel)
  {
-     double        spc_seq_page_cost;
      Cost        startup_cost = 0;
      Cost        run_cost = 0;
      Cost        cpu_per_tuple;

      /* Should only be applied to base relations */
      Assert(baserel->relid > 0);
      Assert(baserel->rtekind == RTE_RELATION);

!     /* For now, at least, seqscans are never parameterized */
!     path->rows = baserel->rows;

      if (!enable_seqscan)
          startup_cost += disable_cost;
--- 162,200 ----
  /*
   * cost_seqscan
   *      Determines and returns the cost of scanning a relation sequentially.
+  *
+  * 'baserel' is the relation to be scanned
+  * 'param_info' is the ParamPathInfo if this is a parameterized path, else NULL
   */
  void
  cost_seqscan(Path *path, PlannerInfo *root,
!              RelOptInfo *baserel, ParamPathInfo *param_info)
  {
      Cost        startup_cost = 0;
      Cost        run_cost = 0;
+     List       *allclauses;
+     double        spc_seq_page_cost;
+     QualCost    qpqual_cost;
      Cost        cpu_per_tuple;

      /* Should only be applied to base relations */
      Assert(baserel->relid > 0);
      Assert(baserel->rtekind == RTE_RELATION);

!     /* Mark the path with the correct row estimate */
!     if (param_info)
!     {
!         path->rows = param_info->ppi_rows;
!         /* also get the set of clauses that should be enforced by the scan */
!         allclauses = list_concat(list_copy(param_info->ppi_clauses),
!                                  baserel->baserestrictinfo);
!     }
!     else
!     {
!         path->rows = baserel->rows;
!         /* allclauses should just be the rel's restriction clauses */
!         allclauses = baserel->baserestrictinfo;
!     }

      if (!enable_seqscan)
          startup_cost += disable_cost;
*************** cost_seqscan(Path *path, PlannerInfo *ro
*** 196,203 ****
      run_cost += spc_seq_page_cost * baserel->pages;

      /* CPU costs */
!     startup_cost += baserel->baserestrictcost.startup;
!     cpu_per_tuple = cpu_tuple_cost + baserel->baserestrictcost.per_tuple;
      run_cost += cpu_per_tuple * baserel->tuples;

      path->startup_cost = startup_cost;
--- 210,219 ----
      run_cost += spc_seq_page_cost * baserel->pages;

      /* CPU costs */
!     cost_qual_eval(&qpqual_cost, allclauses, root);
!
!     startup_cost += qpqual_cost.startup;
!     cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple;
      run_cost += cpu_per_tuple * baserel->tuples;

      path->startup_cost = startup_cost;
*************** cost_index(IndexPath *path, PlannerInfo
*** 251,296 ****
      Assert(baserel->relid > 0);
      Assert(baserel->rtekind == RTE_RELATION);

!     /* Estimate the number of rows returned by the indexscan */
!     if (path->path.required_outer)
      {
!         /*
!          * The estimate should be less than baserel->rows because of the
!          * additional selectivity of the join clauses.  Since indexclauses may
!          * contain both restriction and join clauses, we have to do a set
!          * union to get the full set of clauses that must be considered to
!          * compute the correct selectivity.  (Without the union operation, we
!          * might have some restriction clauses appearing twice, which'd
!          * mislead clauselist_selectivity into double-counting their
!          * selectivity.  However, since RestrictInfo nodes aren't copied when
!          * linking them into different lists, it should be sufficient to use
!          * pointer comparison to remove duplicates.)
!          *
!          * Note that we force the clauses to be treated as non-join clauses
!          * during selectivity estimation.
!          */
!         allclauses = list_union_ptr(baserel->baserestrictinfo,
!                                     path->indexclauses);
!         path->path.rows = baserel->tuples *
!             clauselist_selectivity(root,
!                                    allclauses,
!                                    baserel->relid,    /* do not use 0! */
!                                    JOIN_INNER,
!                                    NULL);
!         if (path->path.rows > baserel->rows)
!             path->path.rows = baserel->rows;
!         path->path.rows = clamp_row_est(path->path.rows);
      }
      else
      {
          /* allclauses should just be the rel's restriction clauses */
          allclauses = baserel->baserestrictinfo;
-
-         /*
-          * The number of rows is the same as the parent rel's estimate, since
-          * this isn't a parameterized path.
-          */
-         path->path.rows = baserel->rows;
      }

      if (!enable_indexscan)
--- 267,285 ----
      Assert(baserel->relid > 0);
      Assert(baserel->rtekind == RTE_RELATION);

!     /* Mark the path with the correct row estimate */
!     if (path->path.param_info)
      {
!         path->path.rows = path->path.param_info->ppi_rows;
!         /* also get the set of clauses that should be enforced by the scan */
!         allclauses = list_concat(list_copy(path->path.param_info->ppi_clauses),
!                                  baserel->baserestrictinfo);
      }
      else
      {
+         path->path.rows = baserel->rows;
          /* allclauses should just be the rel's restriction clauses */
          allclauses = baserel->baserestrictinfo;
      }

      if (!enable_indexscan)
*************** cost_index(IndexPath *path, PlannerInfo
*** 447,455 ****
       *
       * What we want here is cpu_tuple_cost plus the evaluation costs of any
       * qual clauses that we have to evaluate as qpquals.  We approximate that
!      * list as allclauses minus any clauses appearing in indexquals (as
!      * before, assuming that pointer equality is enough to recognize duplicate
!      * RestrictInfos).  This method neglects some considerations such as
       * clauses that needn't be checked because they are implied by a partial
       * index's predicate.  It does not seem worth the cycles to try to factor
       * those things in at this stage, even though createplan.c will take pains
--- 436,444 ----
       *
       * What we want here is cpu_tuple_cost plus the evaluation costs of any
       * qual clauses that we have to evaluate as qpquals.  We approximate that
!      * list as allclauses minus any clauses appearing in indexquals.  (We
!      * assume that pointer equality is enough to recognize duplicate
!      * RestrictInfos.)  This method neglects some considerations such as
       * clauses that needn't be checked because they are implied by a partial
       * index's predicate.  It does not seem worth the cycles to try to factor
       * those things in at this stage, even though createplan.c will take pains
*************** get_indexpath_pages(Path *bitmapqual)
*** 614,619 ****
--- 603,609 ----
   *      index-then-heap plan.
   *
   * 'baserel' is the relation to be scanned
+  * 'param_info' is the ParamPathInfo if this is a parameterized path, else NULL
   * 'bitmapqual' is a tree of IndexPaths, BitmapAndPaths, and BitmapOrPaths
   * 'loop_count' is the number of repetitions of the indexscan to factor into
   *        estimates of caching behavior
*************** get_indexpath_pages(Path *bitmapqual)
*** 623,634 ****
--- 613,627 ----
   */
  void
  cost_bitmap_heap_scan(Path *path, PlannerInfo *root, RelOptInfo *baserel,
+                       ParamPathInfo *param_info,
                        Path *bitmapqual, double loop_count)
  {
      Cost        startup_cost = 0;
      Cost        run_cost = 0;
+     List       *allclauses;
      Cost        indexTotalCost;
      Selectivity indexSelectivity;
+     QualCost    qpqual_cost;
      Cost        cpu_per_tuple;
      Cost        cost_per_page;
      double        tuples_fetched;
*************** cost_bitmap_heap_scan(Path *path, Planne
*** 642,673 ****
      Assert(baserel->relid > 0);
      Assert(baserel->rtekind == RTE_RELATION);

!     /* Estimate the number of rows returned by the bitmap scan */
!     if (path->required_outer)
      {
!         /*
!          * The estimate should be less than baserel->rows because of the
!          * additional selectivity of the join clauses.  We make use of the
!          * selectivity estimated for the bitmap to do this; this isn't really
!          * quite right since there may be restriction conditions not included
!          * in the bitmap ...
!          */
!         Cost        indexTotalCost;
!         Selectivity indexSelectivity;
!
!         cost_bitmap_tree_node(bitmapqual, &indexTotalCost, &indexSelectivity);
!         path->rows = baserel->tuples * indexSelectivity;
!         if (path->rows > baserel->rows)
!             path->rows = baserel->rows;
!         path->rows = clamp_row_est(path->rows);
      }
      else
      {
-         /*
-          * The number of rows is the same as the parent rel's estimate, since
-          * this isn't a parameterized path.
-          */
          path->rows = baserel->rows;
      }

      if (!enable_bitmapscan)
--- 635,653 ----
      Assert(baserel->relid > 0);
      Assert(baserel->rtekind == RTE_RELATION);

!     /* Mark the path with the correct row estimate */
!     if (param_info)
      {
!         path->rows = param_info->ppi_rows;
!         /* also get the set of clauses that should be enforced by the scan */
!         allclauses = list_concat(list_copy(param_info->ppi_clauses),
!                                  baserel->baserestrictinfo);
      }
      else
      {
          path->rows = baserel->rows;
+         /* allclauses should just be the rel's restriction clauses */
+         allclauses = baserel->baserestrictinfo;
      }

      if (!enable_bitmapscan)
*************** cost_bitmap_heap_scan(Path *path, Planne
*** 743,752 ****
       * Often the indexquals don't need to be rechecked at each tuple ... but
       * not always, especially not if there are enough tuples involved that the
       * bitmaps become lossy.  For the moment, just assume they will be
!      * rechecked always.
       */
!     startup_cost += baserel->baserestrictcost.startup;
!     cpu_per_tuple = cpu_tuple_cost + baserel->baserestrictcost.per_tuple;

      run_cost += cpu_per_tuple * tuples_fetched;

--- 723,735 ----
       * Often the indexquals don't need to be rechecked at each tuple ... but
       * not always, especially not if there are enough tuples involved that the
       * bitmaps become lossy.  For the moment, just assume they will be
!      * rechecked always.  This means we charge the full freight for all the
!      * scan clauses.
       */
!     cost_qual_eval(&qpqual_cost, allclauses, root);
!
!     startup_cost += qpqual_cost.startup;
!     cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple;

      run_cost += cpu_per_tuple * tuples_fetched;

*************** cost_tidscan(Path *path, PlannerInfo *ro
*** 978,997 ****
  /*
   * cost_subqueryscan
   *      Determines and returns the cost of scanning a subquery RTE.
   */
  void
! cost_subqueryscan(Path *path, RelOptInfo *baserel)
  {
      Cost        startup_cost;
      Cost        run_cost;
      Cost        cpu_per_tuple;

      /* Should only be applied to base relations that are subqueries */
      Assert(baserel->relid > 0);
      Assert(baserel->rtekind == RTE_SUBQUERY);

!     /* subqueryscans are never parameterized */
!     path->rows = baserel->rows;

      /*
       * Cost of path is cost of evaluating the subplan, plus cost of evaluating
--- 961,998 ----
  /*
   * cost_subqueryscan
   *      Determines and returns the cost of scanning a subquery RTE.
+  *
+  * 'baserel' is the relation to be scanned
+  * 'param_info' is the ParamPathInfo if this is a parameterized path, else NULL
   */
  void
! cost_subqueryscan(Path *path, PlannerInfo *root,
!                   RelOptInfo *baserel, ParamPathInfo *param_info)
  {
      Cost        startup_cost;
      Cost        run_cost;
+     List       *allclauses;
+     QualCost    qpqual_cost;
      Cost        cpu_per_tuple;

      /* Should only be applied to base relations that are subqueries */
      Assert(baserel->relid > 0);
      Assert(baserel->rtekind == RTE_SUBQUERY);

!     /* Mark the path with the correct row estimate */
!     if (param_info)
!     {
!         path->rows = param_info->ppi_rows;
!         /* also get the set of clauses that should be enforced by the scan */
!         allclauses = list_concat(list_copy(param_info->ppi_clauses),
!                                  baserel->baserestrictinfo);
!     }
!     else
!     {
!         path->rows = baserel->rows;
!         /* allclauses should just be the rel's restriction clauses */
!         allclauses = baserel->baserestrictinfo;
!     }

      /*
       * Cost of path is cost of evaluating the subplan, plus cost of evaluating
*************** cost_subqueryscan(Path *path, RelOptInfo
*** 1001,1008 ****
      path->startup_cost = baserel->subplan->startup_cost;
      path->total_cost = baserel->subplan->total_cost;

!     startup_cost = baserel->baserestrictcost.startup;
!     cpu_per_tuple = cpu_tuple_cost + baserel->baserestrictcost.per_tuple;
      run_cost = cpu_per_tuple * baserel->tuples;

      path->startup_cost += startup_cost;
--- 1002,1011 ----
      path->startup_cost = baserel->subplan->startup_cost;
      path->total_cost = baserel->subplan->total_cost;

!     cost_qual_eval(&qpqual_cost, allclauses, root);
!
!     startup_cost = qpqual_cost.startup;
!     cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple;
      run_cost = cpu_per_tuple * baserel->tuples;

      path->startup_cost += startup_cost;
*************** final_cost_nestloop(PlannerInfo *root, N
*** 1764,1796 ****
      Cost        run_cost = workspace->run_cost;
      Cost        inner_rescan_run_cost = workspace->inner_rescan_run_cost;
      Cost        cpu_per_tuple;
-     List       *joinclauses;
      QualCost    restrict_qual_cost;
      double        ntuples;

!     /* Estimate the number of rows returned by the join */
!     if (path->path.required_outer)
!     {
!         /*
!          * The nestloop is (still) parameterized because of upper-level join
!          * clauses used by the input paths.  So the rowcount estimate should
!          * be less than the joinrel's row count because of the additional
!          * selectivity of those join clauses.  To estimate the size we need
!          * to know which of the joinrestrictinfo clauses nominally associated
!          * with the join have been applied in the inner input path.
!          *
!          * We should also assume that such clauses won't be evaluated at the
!          * join node at runtime, so exclude them from restrict_qual_cost.
!          */
!         joinclauses = select_nonredundant_join_clauses(path->joinrestrictinfo,
!                                          path->innerjoinpath->param_clauses);
!         set_joinpath_size_estimate(root, path, sjinfo, joinclauses);
!     }
      else
-     {
-         joinclauses = path->joinrestrictinfo;
          path->path.rows = path->path.parent->rows;
-     }

      /*
       * We could include disable_cost in the preliminary estimate, but that
--- 1767,1780 ----
      Cost        run_cost = workspace->run_cost;
      Cost        inner_rescan_run_cost = workspace->inner_rescan_run_cost;
      Cost        cpu_per_tuple;
      QualCost    restrict_qual_cost;
      double        ntuples;

!     /* Mark the path with the correct row estimate */
!     if (path->path.param_info)
!         path->path.rows = path->path.param_info->ppi_rows;
      else
          path->path.rows = path->path.parent->rows;

      /*
       * We could include disable_cost in the preliminary estimate, but that
*************** final_cost_nestloop(PlannerInfo *root, N
*** 1822,1828 ****
           * return the first tuple of a nonempty scan.  Otherwise, the executor
           * will have to scan the whole inner rel; not so cheap.
           */
!         if (has_indexed_join_quals(path, joinclauses))
          {
              run_cost += (outer_path_rows - outer_matched_rows) *
                  inner_rescan_run_cost / inner_path_rows;
--- 1806,1812 ----
           * return the first tuple of a nonempty scan.  Otherwise, the executor
           * will have to scan the whole inner rel; not so cheap.
           */
!         if (has_indexed_join_quals(path))
          {
              run_cost += (outer_path_rows - outer_matched_rows) *
                  inner_rescan_run_cost / inner_path_rows;
*************** final_cost_nestloop(PlannerInfo *root, N
*** 1849,1855 ****
      }

      /* CPU costs */
!     cost_qual_eval(&restrict_qual_cost, joinclauses, root);
      startup_cost += restrict_qual_cost.startup;
      cpu_per_tuple = cpu_tuple_cost + restrict_qual_cost.per_tuple;
      run_cost += cpu_per_tuple * ntuples;
--- 1833,1839 ----
      }

      /* CPU costs */
!     cost_qual_eval(&restrict_qual_cost, path->joinrestrictinfo, root);
      startup_cost += restrict_qual_cost.startup;
      cpu_per_tuple = cpu_tuple_cost + restrict_qual_cost.per_tuple;
      run_cost += cpu_per_tuple * ntuples;
*************** final_cost_mergejoin(PlannerInfo *root,
*** 2141,2149 ****
      if (inner_path_rows <= 0 || isnan(inner_path_rows))
          inner_path_rows = 1;

!     /* Estimate the number of rows returned by the join */
!     set_joinpath_size_estimate(root, &path->jpath, sjinfo,
!                                path->jpath.joinrestrictinfo);

      /*
       * We could include disable_cost in the preliminary estimate, but that
--- 2125,2135 ----
      if (inner_path_rows <= 0 || isnan(inner_path_rows))
          inner_path_rows = 1;

!     /* Mark the path with the correct row estimate */
!     if (path->jpath.path.param_info)
!         path->jpath.path.rows = path->jpath.path.param_info->ppi_rows;
!     else
!         path->jpath.path.rows = path->jpath.path.parent->rows;

      /*
       * We could include disable_cost in the preliminary estimate, but that
*************** final_cost_hashjoin(PlannerInfo *root, H
*** 2513,2521 ****
      Selectivity innerbucketsize;
      ListCell   *hcl;

!     /* Estimate the number of rows returned by the join */
!     set_joinpath_size_estimate(root, &path->jpath, sjinfo,
!                                path->jpath.joinrestrictinfo);

      /*
       * We could include disable_cost in the preliminary estimate, but that
--- 2499,2509 ----
      Selectivity innerbucketsize;
      ListCell   *hcl;

!     /* Mark the path with the correct row estimate */
!     if (path->jpath.path.param_info)
!         path->jpath.path.rows = path->jpath.path.param_info->ppi_rows;
!     else
!         path->jpath.path.rows = path->jpath.path.parent->rows;

      /*
       * We could include disable_cost in the preliminary estimate, but that
*************** compute_semi_anti_join_factors(PlannerIn
*** 3257,3290 ****
   * expensive.
   */
  static bool
! has_indexed_join_quals(NestPath *path, List *joinclauses)
  {
!     NodeTag        pathtype = path->innerjoinpath->pathtype;

!     if (pathtype == T_IndexScan ||
!         pathtype == T_IndexOnlyScan ||
!         pathtype == T_BitmapHeapScan)
      {
!         if (path->joinrestrictinfo != NIL)
          {
!             /* OK if all those clauses were found to be redundant */
!             return (joinclauses == NIL);
          }
!         else
!         {
!             /* a clauseless join does NOT qualify */
              return false;
-         }
      }
!     else
      {
!         /*
!          * If it's not a simple indexscan, it probably doesn't run quickly for
!          * zero rows out, even if it's a parameterized path using all the
!          * joinquals.
!          */
!         return false;
      }
  }


--- 3245,3313 ----
   * expensive.
   */
  static bool
! has_indexed_join_quals(NestPath *joinpath)
  {
!     Relids        joinrelids = joinpath->path.parent->relids;
!     Path       *outerpath = joinpath->outerjoinpath;
!     Path       *innerpath = joinpath->innerjoinpath;
!     List       *indexclauses;
!     bool        found_one;
!     ListCell   *lc;

!     /* If join still has quals to evaluate, it's not fast */
!     if (joinpath->joinrestrictinfo != NIL)
!         return false;
!     /* Nor if the inner path isn't parameterized at all */
!     if (innerpath->param_info == NULL)
!         return false;
!
!     /* Find the indexclauses list for the inner scan */
!     switch (innerpath->pathtype)
      {
!         case T_IndexScan:
!         case T_IndexOnlyScan:
!             indexclauses = ((IndexPath *) innerpath)->indexclauses;
!             break;
!         case T_BitmapHeapScan:
          {
!             /* Accept only a simple bitmap scan, not AND/OR cases */
!             Path   *bmqual = ((BitmapHeapPath *) innerpath)->bitmapqual;
!
!             if (IsA(bmqual, IndexPath))
!                 indexclauses = ((IndexPath *) bmqual)->indexclauses;
!             else
!                 return false;
!             break;
          }
!         default:
!             /*
!              * If it's not a simple indexscan, it probably doesn't run quickly
!              * for zero rows out, even if it's a parameterized path using all
!              * the joinquals.
!              */
              return false;
      }
!
!     /*
!      * Examine the inner path's param clauses.  Any that are from the outer
!      * path must be found in the indexclauses list.  Also, we must find at
!      * least one such clause, else it's a clauseless join which isn't fast.
!      */
!     found_one = false;
!     foreach(lc, innerpath->param_info->ppi_clauses)
      {
!         RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
!
!         if (join_clause_is_parameterizable_within(rinfo,
!                                                   outerpath->parent->relids,
!                                                   joinrelids))
!         {
!             if (!list_member_ptr(indexclauses, rinfo))
!                 return false;
!             found_one = true;
!         }
      }
+     return found_one;
  }


*************** set_baserel_size_estimates(PlannerInfo *
*** 3388,3393 ****
--- 3411,3452 ----
  }

  /*
+  * get_parameterized_baserel_size
+  *        Make a size estimate for a parameterized scan of a base relation.
+  *
+  * 'param_clauses' lists the additional join clauses to be used.
+  *
+  * set_baserel_size_estimates must have been applied already.
+  */
+ double
+ get_parameterized_baserel_size(PlannerInfo *root, RelOptInfo *rel,
+                                List *param_clauses)
+ {
+     List       *allclauses;
+     double        nrows;
+
+     /*
+      * Estimate the number of rows returned by the parameterized scan, knowing
+      * that it will apply all the extra join clauses as well as the rel's own
+      * restriction clauses.  Note that we force the clauses to be treated as
+      * non-join clauses during selectivity estimation.
+      */
+     allclauses = list_concat(list_copy(param_clauses),
+                              rel->baserestrictinfo);
+     nrows = rel->tuples *
+         clauselist_selectivity(root,
+                                allclauses,
+                                rel->relid,        /* do not use 0! */
+                                JOIN_INNER,
+                                NULL);
+     nrows = clamp_row_est(nrows);
+     /* For safety, make sure result is not more than the base estimate */
+     if (nrows > rel->rows)
+         nrows = rel->rows;
+     return nrows;
+ }
+
+ /*
   * set_joinrel_size_estimates
   *        Set the size estimates for the given join relation.
   *
*************** set_baserel_size_estimates(PlannerInfo *
*** 3402,3408 ****
   * routines don't handle all cases equally well, we might not.  But there's
   * not much to be done about it.  (Would it make sense to repeat the
   * calculations for each pair of input rels that's encountered, and somehow
!  * average the results?  Probably way more trouble than it's worth.)
   *
   * We set only the rows field here.  The width field was already set by
   * build_joinrel_tlist, and baserestrictcost is not used for join rels.
--- 3461,3469 ----
   * routines don't handle all cases equally well, we might not.  But there's
   * not much to be done about it.  (Would it make sense to repeat the
   * calculations for each pair of input rels that's encountered, and somehow
!  * average the results?  Probably way more trouble than it's worth, and
!  * anyway we must keep the rowcount estimate the same for all paths for the
!  * joinrel.)
   *
   * We set only the rows field here.  The width field was already set by
   * build_joinrel_tlist, and baserestrictcost is not used for join rels.
*************** set_joinrel_size_estimates(PlannerInfo *
*** 3422,3460 ****
  }

  /*
!  * set_joinpath_size_estimate
!  *        Set the rows estimate for the given join path.
   *
!  * If the join is not parameterized by any joinclauses from higher joins, the
!  * estimate is the same as previously computed by set_joinrel_size_estimates.
!  * Otherwise, we estimate afresh using the identical logic, but with the rows
!  * estimates from the input paths (which are typically less than their rels'
!  * regular row estimates) and the restriction clauses actually being applied
!  * at the join.
   */
! static void
! set_joinpath_size_estimate(PlannerInfo *root, JoinPath *path,
!                            SpecialJoinInfo *sjinfo,
!                            List *restrictlist)
  {
!     if (path->path.required_outer)
!     {
!         path->path.rows = calc_joinrel_size_estimate(root,
!                                                      path->outerjoinpath->rows,
!                                                      path->innerjoinpath->rows,
!                                                      sjinfo,
!                                                      restrictlist);
!         /* For safety, make sure result is not more than the base estimate */
!         if (path->path.rows > path->path.parent->rows)
!             path->path.rows = path->path.parent->rows;
!     }
!     else
!         path->path.rows = path->path.parent->rows;
  }

  /*
   * calc_joinrel_size_estimate
!  *        Workhorse for set_joinrel_size_estimates and set_joinpath_size_estimate
   */
  static double
  calc_joinrel_size_estimate(PlannerInfo *root,
--- 3483,3539 ----
  }

  /*
!  * get_parameterized_joinrel_size
!  *        Make a size estimate for a parameterized scan of a join relation.
   *
!  * 'rel' is the joinrel under consideration.
!  * 'outer_rows', 'inner_rows' are the sizes of the (probably also
!  *        parameterized) join inputs under consideration.
!  * 'sjinfo' is any SpecialJoinInfo relevant to this join.
!  * 'restrict_clauses' lists the ordinary join clauses that need to be applied
!  * at the join (including clauses derived from EquivalenceClasses, but not
!  * including anything that is to be pushed down into the child rels).
!  * 'param_clauses' lists the additional parameterized join clauses to be used.
!  *
!  * set_joinrel_size_estimates must have been applied already.
   */
! double
! get_parameterized_joinrel_size(PlannerInfo *root, RelOptInfo *rel,
!                                double outer_rows,
!                                double inner_rows,
!                                SpecialJoinInfo *sjinfo,
!                                List *restrict_clauses,
!                                List *param_clauses)
  {
!     List       *allclauses;
!     double        nrows;
!
!     /*
!      * Estimate the number of rows returned by the parameterized join, knowing
!      * that it will apply all the parameterized join clauses as well as
!      * the ordinary join clauses needed for this pair of input rels.
!      *
!      * As with set_joinrel_size_estimates, the rowcount estimate could depend
!      * on the pair of input paths provided, though ideally we'd get the same
!      * estimate for any pair.
!      */
!     allclauses = list_concat(list_copy(param_clauses),
!                              restrict_clauses);
!     nrows = calc_joinrel_size_estimate(root,
!                                        outer_rows,
!                                        inner_rows,
!                                        sjinfo,
!                                        allclauses);
!     /* For safety, make sure result is not more than the base estimate */
!     if (nrows > rel->rows)
!         nrows = rel->rows;
!     return nrows;
  }

  /*
   * calc_joinrel_size_estimate
!  *        Workhorse for set_joinrel_size_estimates and
!  *        get_parameterized_joinrel_size.
   */
  static double
  calc_joinrel_size_estimate(PlannerInfo *root,
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 5cdfec542cde91ded3271a62b9da53b63cabc20c..05247594bcf676737e0240533c73c81744f80053 100644
*** a/src/backend/optimizer/path/equivclass.c
--- b/src/backend/optimizer/path/equivclass.c
***************
*** 21,26 ****
--- 21,27 ----
  #include "nodes/makefuncs.h"
  #include "nodes/nodeFuncs.h"
  #include "optimizer/clauses.h"
+ #include "optimizer/pathnode.h"
  #include "optimizer/paths.h"
  #include "optimizer/planmain.h"
  #include "optimizer/prep.h"
*************** static void generate_base_implied_equali
*** 39,52 ****
                                          EquivalenceClass *ec);
  static List *generate_join_implied_equalities_normal(PlannerInfo *root,
                                          EquivalenceClass *ec,
!                                         RelOptInfo *joinrel,
!                                         RelOptInfo *outer_rel,
!                                         RelOptInfo *inner_rel);
  static List *generate_join_implied_equalities_broken(PlannerInfo *root,
                                          EquivalenceClass *ec,
!                                         RelOptInfo *joinrel,
!                                         RelOptInfo *outer_rel,
!                                         RelOptInfo *inner_rel);
  static Oid select_equality_operator(EquivalenceClass *ec,
                           Oid lefttype, Oid righttype);
  static RestrictInfo *create_join_clause(PlannerInfo *root,
--- 40,54 ----
                                          EquivalenceClass *ec);
  static List *generate_join_implied_equalities_normal(PlannerInfo *root,
                                          EquivalenceClass *ec,
!                                         Relids join_relids,
!                                         Relids outer_relids,
!                                         Relids inner_relids);
  static List *generate_join_implied_equalities_broken(PlannerInfo *root,
                                          EquivalenceClass *ec,
!                                         Relids nominal_join_relids,
!                                         Relids outer_relids,
!                                         Relids nominal_inner_relids,
!                                         AppendRelInfo *inner_appinfo);
  static Oid select_equality_operator(EquivalenceClass *ec,
                           Oid lefttype, Oid righttype);
  static RestrictInfo *create_join_clause(PlannerInfo *root,
*************** static bool reconsider_outer_join_clause
*** 59,65 ****
                               bool outer_on_left);
  static bool reconsider_full_join_clause(PlannerInfo *root,
                              RestrictInfo *rinfo);
- static Index get_parent_relid(PlannerInfo *root, RelOptInfo *rel);


  /*
--- 61,66 ----
*************** generate_base_implied_equalities_broken(
*** 905,918 ****
   * that all equivalence-class members computable at that node are equal.
   * Since the set of clauses to enforce can vary depending on which subset
   * relations are the inputs, we have to compute this afresh for each join
!  * path pair.  Hence a fresh List of RestrictInfo nodes is built and passed
!  * back on each call.
   *
   * The results are sufficient for use in merge, hash, and plain nestloop join
   * methods.  We do not worry here about selecting clauses that are optimal
!  * for use in a nestloop-with-parameterized-inner-scan.  indxpath.c makes
!  * its own selections of clauses to use, and if the ones we pick here are
!  * redundant with those, the extras will be eliminated in createplan.c.
   *
   * Because the same join clauses are likely to be needed multiple times as
   * we consider different join paths, we avoid generating multiple copies:
--- 906,931 ----
   * that all equivalence-class members computable at that node are equal.
   * Since the set of clauses to enforce can vary depending on which subset
   * relations are the inputs, we have to compute this afresh for each join
!  * relation pair.  Hence a fresh List of RestrictInfo nodes is built and
!  * passed back on each call.
!  *
!  * In addition to its use at join nodes, this can be applied to generate
!  * eclass-based join clauses for use in a parameterized scan of a base rel.
!  * The reason for the asymmetry of specifying the inner rel as a RelOptInfo
!  * and the outer rel by Relids is that this usage occurs before we have
!  * built any join RelOptInfos.
!  *
!  * An annoying special case for parameterized scans is that the inner rel can
!  * be an appendrel child (an "other rel").  In this case we must generate
!  * appropriate clauses using child EC members.  add_child_rel_equivalences
!  * must already have been done for the child rel.
   *
   * The results are sufficient for use in merge, hash, and plain nestloop join
   * methods.  We do not worry here about selecting clauses that are optimal
!  * for use in a parameterized indexscan.  indxpath.c makes its own selections
!  * of clauses to use, and if the ones we pick here are redundant with those,
!  * the extras will be eliminated at createplan time (using the parent_ec
!  * markers that we provide).
   *
   * Because the same join clauses are likely to be needed multiple times as
   * we consider different join paths, we avoid generating multiple copies:
*************** generate_base_implied_equalities_broken(
*** 920,942 ****
   * we check to see if the pair matches any original clause (in ec_sources)
   * or previously-built clause (in ec_derives).    This saves memory and allows
   * re-use of information cached in RestrictInfos.
   */
  List *
  generate_join_implied_equalities(PlannerInfo *root,
!                                  RelOptInfo *joinrel,
!                                  RelOptInfo *outer_rel,
                                   RelOptInfo *inner_rel)
  {
      List       *result = NIL;
      ListCell   *lc;

      foreach(lc, root->eq_classes)
      {
          EquivalenceClass *ec = (EquivalenceClass *) lfirst(lc);
          List       *sublist = NIL;

!         /* ECs containing consts do not need any further enforcement */
!         if (ec->ec_has_const)
              continue;

          /* Single-member ECs won't generate any deductions */
--- 933,983 ----
   * we check to see if the pair matches any original clause (in ec_sources)
   * or previously-built clause (in ec_derives).    This saves memory and allows
   * re-use of information cached in RestrictInfos.
+  *
+  * join_relids should always equal bms_union(outer_relids, inner_rel->relids).
+  * We could simplify this function's API by computing it internally, but in
+  * all current uses, the caller has the value at hand anyway.
   */
  List *
  generate_join_implied_equalities(PlannerInfo *root,
!                                  Relids join_relids,
!                                  Relids outer_relids,
                                   RelOptInfo *inner_rel)
  {
      List       *result = NIL;
+     Relids        inner_relids = inner_rel->relids;
+     Relids        nominal_inner_relids;
+     Relids        nominal_join_relids;
+     AppendRelInfo *inner_appinfo;
      ListCell   *lc;

+     /* If inner rel is a child, extra setup work is needed */
+     if (inner_rel->reloptkind == RELOPT_OTHER_MEMBER_REL)
+     {
+         /* Lookup parent->child translation data */
+         inner_appinfo = find_childrel_appendrelinfo(root, inner_rel);
+         /* Construct relids for the parent rel */
+         nominal_inner_relids = bms_make_singleton(inner_appinfo->parent_relid);
+         /* ECs will be marked with the parent's relid, not the child's */
+         nominal_join_relids = bms_union(outer_relids, nominal_inner_relids);
+     }
+     else
+     {
+         inner_appinfo = NULL;
+         nominal_inner_relids = inner_relids;
+         nominal_join_relids = join_relids;
+     }
+
      foreach(lc, root->eq_classes)
      {
          EquivalenceClass *ec = (EquivalenceClass *) lfirst(lc);
          List       *sublist = NIL;

!         /*
!          * ECs containing consts do not need any further enforcement, unless
!          * they're broken
!          */
!         if (ec->ec_has_const && !ec->ec_broken)
              continue;

          /* Single-member ECs won't generate any deductions */
*************** generate_join_implied_equalities(Planner
*** 944,966 ****
              continue;

          /* We can quickly ignore any that don't overlap the join, too */
!         if (!bms_overlap(ec->ec_relids, joinrel->relids))
              continue;

          if (!ec->ec_broken)
              sublist = generate_join_implied_equalities_normal(root,
                                                                ec,
!                                                               joinrel,
!                                                               outer_rel,
!                                                               inner_rel);

          /* Recover if we failed to generate required derived clauses */
          if (ec->ec_broken)
              sublist = generate_join_implied_equalities_broken(root,
                                                                ec,
!                                                               joinrel,
!                                                               outer_rel,
!                                                               inner_rel);

          result = list_concat(result, sublist);
      }
--- 985,1008 ----
              continue;

          /* We can quickly ignore any that don't overlap the join, too */
!         if (!bms_overlap(ec->ec_relids, nominal_join_relids))
              continue;

          if (!ec->ec_broken)
              sublist = generate_join_implied_equalities_normal(root,
                                                                ec,
!                                                               join_relids,
!                                                               outer_relids,
!                                                               inner_relids);

          /* Recover if we failed to generate required derived clauses */
          if (ec->ec_broken)
              sublist = generate_join_implied_equalities_broken(root,
                                                                ec,
!                                                               nominal_join_relids,
!                                                               outer_relids,
!                                                               nominal_inner_relids,
!                                                               inner_appinfo);

          result = list_concat(result, sublist);
      }
*************** generate_join_implied_equalities(Planner
*** 974,982 ****
  static List *
  generate_join_implied_equalities_normal(PlannerInfo *root,
                                          EquivalenceClass *ec,
!                                         RelOptInfo *joinrel,
!                                         RelOptInfo *outer_rel,
!                                         RelOptInfo *inner_rel)
  {
      List       *result = NIL;
      List       *new_members = NIL;
--- 1016,1024 ----
  static List *
  generate_join_implied_equalities_normal(PlannerInfo *root,
                                          EquivalenceClass *ec,
!                                         Relids join_relids,
!                                         Relids outer_relids,
!                                         Relids inner_relids)
  {
      List       *result = NIL;
      List       *new_members = NIL;
*************** generate_join_implied_equalities_normal(
*** 997,1010 ****
      {
          EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc1);

!         if (cur_em->em_is_child)
!             continue;            /* ignore children here */
!         if (!bms_is_subset(cur_em->em_relids, joinrel->relids))
!             continue;            /* ignore --- not computable yet */

!         if (bms_is_subset(cur_em->em_relids, outer_rel->relids))
              outer_members = lappend(outer_members, cur_em);
!         else if (bms_is_subset(cur_em->em_relids, inner_rel->relids))
              inner_members = lappend(inner_members, cur_em);
          else
              new_members = lappend(new_members, cur_em);
--- 1039,1055 ----
      {
          EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc1);

!         /*
!          * We don't need to check explicitly for child EC members.  This test
!          * against join_relids will cause them to be ignored except when
!          * considering a child inner rel, which is what we want.
!          */
!         if (!bms_is_subset(cur_em->em_relids, join_relids))
!             continue;            /* not computable yet, or wrong child */

!         if (bms_is_subset(cur_em->em_relids, outer_relids))
              outer_members = lappend(outer_members, cur_em);
!         else if (bms_is_subset(cur_em->em_relids, inner_relids))
              inner_members = lappend(inner_members, cur_em);
          else
              new_members = lappend(new_members, cur_em);
*************** generate_join_implied_equalities_normal(
*** 1140,1152 ****
   * generate_join_implied_equalities cleanup after failure
   *
   * Return any original RestrictInfos that are enforceable at this join.
   */
  static List *
  generate_join_implied_equalities_broken(PlannerInfo *root,
                                          EquivalenceClass *ec,
!                                         RelOptInfo *joinrel,
!                                         RelOptInfo *outer_rel,
!                                         RelOptInfo *inner_rel)
  {
      List       *result = NIL;
      ListCell   *lc;
--- 1185,1201 ----
   * generate_join_implied_equalities cleanup after failure
   *
   * Return any original RestrictInfos that are enforceable at this join.
+  *
+  * In the case of a child inner relation, we have to translate the
+  * original RestrictInfos from parent to child Vars.
   */
  static List *
  generate_join_implied_equalities_broken(PlannerInfo *root,
                                          EquivalenceClass *ec,
!                                         Relids nominal_join_relids,
!                                         Relids outer_relids,
!                                         Relids nominal_inner_relids,
!                                         AppendRelInfo *inner_appinfo)
  {
      List       *result = NIL;
      ListCell   *lc;
*************** generate_join_implied_equalities_broken(
*** 1154,1166 ****
      foreach(lc, ec->ec_sources)
      {
          RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(lc);

!         if (bms_is_subset(restrictinfo->required_relids, joinrel->relids) &&
!           !bms_is_subset(restrictinfo->required_relids, outer_rel->relids) &&
!             !bms_is_subset(restrictinfo->required_relids, inner_rel->relids))
              result = lappend(result, restrictinfo);
      }

      return result;
  }

--- 1203,1227 ----
      foreach(lc, ec->ec_sources)
      {
          RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(lc);
+         Relids        clause_relids = restrictinfo->required_relids;

!         if (bms_is_subset(clause_relids, nominal_join_relids) &&
!             !bms_is_subset(clause_relids, outer_relids) &&
!             !bms_is_subset(clause_relids, nominal_inner_relids))
              result = lappend(result, restrictinfo);
      }

+     /*
+      * If we have to translate, just brute-force apply adjust_appendrel_attrs
+      * to all the RestrictInfos at once.  This will result in returning
+      * RestrictInfos that are not listed in ec_derives, but there shouldn't
+      * be any duplication, and it's a sufficiently narrow corner case that
+      * we shouldn't sweat too much over it anyway.
+      */
+     if (inner_appinfo)
+         result = (List *) adjust_appendrel_attrs(root, (Node *) result,
+                                                  inner_appinfo);
+
      return result;
  }

*************** exprs_known_equal(PlannerInfo *root, Nod
*** 1783,1789 ****

  /*
   * add_child_rel_equivalences
!  *      Search for EC members that reference (only) the parent_rel, and
   *      add transformed members referencing the child_rel.
   *
   * Note that this function won't be called at all unless we have at least some
--- 1844,1850 ----

  /*
   * add_child_rel_equivalences
!  *      Search for EC members that reference the parent_rel, and
   *      add transformed members referencing the child_rel.
   *
   * Note that this function won't be called at all unless we have at least some
*************** add_child_rel_equivalences(PlannerInfo *
*** 1821,1840 ****
          {
              EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2);

!             if (cur_em->em_is_child)
!                 continue;        /* ignore children here */

!             /* Does it reference (only) parent_rel? */
!             if (bms_equal(cur_em->em_relids, parent_rel->relids))
              {
                  /* Yes, generate transformed child version */
                  Expr       *child_expr;

                  child_expr = (Expr *)
                      adjust_appendrel_attrs(root,
                                             (Node *) cur_em->em_expr,
                                             appinfo);
!                 (void) add_eq_member(cur_ec, child_expr, child_rel->relids,
                                       true, cur_em->em_datatype);
              }
          }
--- 1882,1913 ----
          {
              EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2);

!             if (cur_em->em_is_const || cur_em->em_is_child)
!                 continue;        /* ignore consts and children here */

!             /* Does it reference parent_rel? */
!             if (bms_overlap(cur_em->em_relids, parent_rel->relids))
              {
                  /* Yes, generate transformed child version */
                  Expr       *child_expr;
+                 Relids        new_relids;

                  child_expr = (Expr *)
                      adjust_appendrel_attrs(root,
                                             (Node *) cur_em->em_expr,
                                             appinfo);
!
!                 /*
!                  * Transform em_relids to match.  Note we do *not* do
!                  * pull_varnos(child_expr) here, as for example the
!                  * transformation might have substituted a constant, but we
!                  * don't want the child member to be marked as constant.
!                  */
!                 new_relids = bms_difference(cur_em->em_relids,
!                                             parent_rel->relids);
!                 new_relids = bms_add_members(new_relids, child_rel->relids);
!
!                 (void) add_eq_member(cur_ec, child_expr, new_relids,
                                       true, cur_em->em_datatype);
              }
          }
*************** generate_implied_equalities_for_indexcol
*** 1907,1913 ****

      /* If it's a child rel, we'll need to know what its parent is */
      if (is_child_rel)
!         parent_relid = get_parent_relid(root, rel);
      else
          parent_relid = 0;        /* not used, but keep compiler quiet */

--- 1980,1986 ----

      /* If it's a child rel, we'll need to know what its parent is */
      if (is_child_rel)
!         parent_relid = find_childrel_appendrelinfo(root, rel)->parent_relid;
      else
          parent_relid = 0;        /* not used, but keep compiler quiet */

*************** generate_implied_equalities_for_indexcol
*** 2009,2038 ****
  }

  /*
-  * get_parent_relid
-  *        Get the relid of a child rel's parent appendrel
-  *
-  * Possibly this should be somewhere else, but right now the logic is only
-  * needed here.
-  */
- static Index
- get_parent_relid(PlannerInfo *root, RelOptInfo *rel)
- {
-     ListCell   *lc;
-
-     foreach(lc, root->append_rel_list)
-     {
-         AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc);
-
-         if (appinfo->child_relid == rel->relid)
-             return appinfo->parent_relid;
-     }
-     /* should have found the entry ... */
-     elog(ERROR, "child rel not found in append_rel_list");
-     return 0;
- }
-
- /*
   * have_relevant_eclass_joinclause
   *        Detect whether there is an EquivalenceClass that could produce
   *        a joinclause involving the two given relations.
--- 2082,2087 ----
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index e23cc5eb7b2b38d5eabc34a30ad9e8d9f024d548..ecc594b34ec57bac873515450496804e9261b175 100644
*** a/src/backend/optimizer/path/indxpath.c
--- b/src/backend/optimizer/path/indxpath.c
*************** static Cost bitmap_and_cost_est(PlannerI
*** 111,116 ****
--- 111,117 ----
                                  List *paths);
  static PathClauseUsage *classify_index_clause_usage(Path *path,
                              List **clauselist);
+ static Relids get_bitmap_tree_paramrels(Path *bitmapqual);
  static void find_indexpath_quals(Path *bitmapqual, List **quals, List **preds);
  static int    find_list_position(Node *node, List **nodelist);
  static bool check_index_only(RelOptInfo *rel, IndexOptInfo *index);
*************** create_index_paths(PlannerInfo *root, Re
*** 303,309 ****
          BitmapHeapPath *bpath;

          bitmapqual = choose_bitmap_and(root, rel, bitindexpaths);
!         bpath = create_bitmap_heap_path(root, rel, bitmapqual, 1.0);
          add_path(rel, (Path *) bpath);
      }

--- 304,310 ----
          BitmapHeapPath *bpath;

          bitmapqual = choose_bitmap_and(root, rel, bitindexpaths);
!         bpath = create_bitmap_heap_path(root, rel, bitmapqual, NULL, 1.0);
          add_path(rel, (Path *) bpath);
      }

*************** create_index_paths(PlannerInfo *root, Re
*** 318,329 ****
      if (bitjoinpaths != NIL)
      {
          Path       *bitmapqual;
          double        loop_count;
          BitmapHeapPath *bpath;

          bitmapqual = choose_bitmap_and(root, rel, bitjoinpaths);
!         loop_count = get_loop_count(root, bitmapqual->required_outer);
!         bpath = create_bitmap_heap_path(root, rel, bitmapqual, loop_count);
          add_path(rel, (Path *) bpath);
      }
  }
--- 319,333 ----
      if (bitjoinpaths != NIL)
      {
          Path       *bitmapqual;
+         Relids    required_outer;
          double        loop_count;
          BitmapHeapPath *bpath;

          bitmapqual = choose_bitmap_and(root, rel, bitjoinpaths);
!         required_outer = get_bitmap_tree_paramrels(bitmapqual);
!         loop_count = get_loop_count(root, required_outer);
!         bpath = create_bitmap_heap_path(root, rel, bitmapqual,
!                                         required_outer, loop_count);
          add_path(rel, (Path *) bpath);
      }
  }
*************** bitmap_scan_cost_est(PlannerInfo *root,
*** 1320,1336 ****
  {
      BitmapHeapPath bpath;

      /* Set up a dummy BitmapHeapPath */
      bpath.path.type = T_BitmapHeapPath;
      bpath.path.pathtype = T_BitmapHeapScan;
      bpath.path.parent = rel;
      bpath.path.pathkeys = NIL;
-     bpath.path.required_outer = ipath->required_outer;
-     bpath.path.param_clauses = ipath->param_clauses;
      bpath.bitmapqual = ipath;

!     cost_bitmap_heap_scan((Path *) &bpath, root, rel, ipath,
!                           get_loop_count(root, bpath.path.required_outer));

      return bpath.path.total_cost;
  }
--- 1324,1344 ----
  {
      BitmapHeapPath bpath;

+     /* Must be a simple IndexPath so that we can just copy its param_info */
+     Assert(IsA(ipath, IndexPath));
+
      /* Set up a dummy BitmapHeapPath */
      bpath.path.type = T_BitmapHeapPath;
      bpath.path.pathtype = T_BitmapHeapScan;
      bpath.path.parent = rel;
+     bpath.path.param_info = ipath->param_info;
      bpath.path.pathkeys = NIL;
      bpath.bitmapqual = ipath;

!     cost_bitmap_heap_scan(&bpath.path, root, rel,
!                           bpath.path.param_info,
!                           ipath,
!                           get_loop_count(root, PATH_PARAMRELS(ipath)));

      return bpath.path.total_cost;
  }
*************** bitmap_and_cost_est(PlannerInfo *root, R
*** 1344,1369 ****
  {
      BitmapAndPath *apath;
      BitmapHeapPath bpath;

      /*
!      * Create a temporary BitmapAndPath.  (Because it needs realistic
!      * required_outer and param_clauses values, making a dummy one would
       * take more code than it's worth.)
       */
      apath = create_bitmap_and_path(root, rel, paths);

      /* Set up a dummy BitmapHeapPath */
      bpath.path.type = T_BitmapHeapPath;
      bpath.path.pathtype = T_BitmapHeapScan;
      bpath.path.parent = rel;
      bpath.path.pathkeys = NIL;
-     bpath.path.required_outer = apath->path.required_outer;
-     bpath.path.param_clauses = apath->path.param_clauses;
      bpath.bitmapqual = (Path *) apath;

      /* Now we can do cost_bitmap_heap_scan */
!     cost_bitmap_heap_scan((Path *) &bpath, root, rel, (Path *) apath,
!                           get_loop_count(root, bpath.path.required_outer));

      return bpath.path.total_cost;
  }
--- 1352,1382 ----
  {
      BitmapAndPath *apath;
      BitmapHeapPath bpath;
+     Relids    required_outer;

      /*
!      * Create a temporary BitmapAndPath.  (Making a dummy one would
       * take more code than it's worth.)
       */
      apath = create_bitmap_and_path(root, rel, paths);

+     /* Identify required outer rels, in case it's a parameterized scan */
+     required_outer = get_bitmap_tree_paramrels((Path *) apath);
+
      /* Set up a dummy BitmapHeapPath */
      bpath.path.type = T_BitmapHeapPath;
      bpath.path.pathtype = T_BitmapHeapScan;
      bpath.path.parent = rel;
+     bpath.path.param_info = get_baserel_parampathinfo(root, rel,
+                                                       required_outer);
      bpath.path.pathkeys = NIL;
      bpath.bitmapqual = (Path *) apath;

      /* Now we can do cost_bitmap_heap_scan */
!     cost_bitmap_heap_scan(&bpath.path, root, rel,
!                           bpath.path.param_info,
!                           (Path *) apath,
!                           get_loop_count(root, required_outer));

      return bpath.path.total_cost;
  }
*************** classify_index_clause_usage(Path *path,
*** 1421,1426 ****
--- 1434,1482 ----


  /*
+  * get_bitmap_tree_paramrels
+  *        Find the required outer rels for a bitmap tree (index/and/or)
+  *
+  * We don't associate any particular parameterization with a BitmapAnd or
+  * BitmapOr node; however, the IndexPaths have parameterization info, in
+  * their capacity as standalone access paths.  The parameterization required
+  * for the bitmap heap scan node is the union of rels referenced in the
+  * child IndexPaths.
+  */
+ static Relids
+ get_bitmap_tree_paramrels(Path *bitmapqual)
+ {
+     Relids        result = NULL;
+     ListCell   *lc;
+
+     if (IsA(bitmapqual, IndexPath))
+     {
+         return bms_copy(PATH_PARAMRELS(bitmapqual));
+     }
+     else if (IsA(bitmapqual, BitmapAndPath))
+     {
+         foreach(lc, ((BitmapAndPath *) bitmapqual)->bitmapquals)
+         {
+             result = bms_join(result,
+                               get_bitmap_tree_paramrels((Path *) lfirst(lc)));
+         }
+     }
+     else if (IsA(bitmapqual, BitmapOrPath))
+     {
+         foreach(lc, ((BitmapOrPath *) bitmapqual)->bitmapquals)
+         {
+             result = bms_join(result,
+                               get_bitmap_tree_paramrels((Path *) lfirst(lc)));
+         }
+     }
+     else
+         elog(ERROR, "unrecognized node type: %d", nodeTag(bitmapqual));
+
+     return result;
+ }
+
+
+ /*
   * find_indexpath_quals
   *
   * Given the Path structure for a plain or bitmap indexscan, extract lists
*************** match_join_clauses_to_index(PlannerInfo
*** 1690,1718 ****
      {
          RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);

!         /* Ignore if it mentions anything from wrong side of an outer join */
!         if (bms_overlap(rinfo->clause_relids, inner_baserels))
              continue;

!         /*
!          * Note that we ignore required_relids; that's okay because we are
!          * intentionally ignoring the normal rules for placing evaluation of
!          * join clauses.  The whole point here is to evaluate join clauses
!          * below their join, even if they would normally be delayed by
!          * outer join rules.
!          *
!          * Instead of considering required_relids, we ignore clauses for which
!          * the indexed rel is in nullable_relids; that means there's an outer
!          * join below the clause and so it can't be checked at the relation
!          * scan level.
!          *
!          * Note: unlike create_or_index_quals(), we can accept clauses that
!          * are marked !is_pushed_down (ie they are themselves outer-join
!          * clauses).  This is OK because any path generated with these clauses
!          * could only be used in the inside of a nestloop join, which will be
!          * the nullable side.
!          */
!         if (bms_overlap(rel->relids, rinfo->nullable_relids))
              continue;

          /* Potentially usable, so see if it matches the index or is an OR */
--- 1746,1757 ----
      {
          RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);

!         /* Check safety of clause for parameterization */
!         if (!join_clause_is_parameterizable_for(rinfo, rel->relids))
              continue;

!         /* Ignore if it mentions anything from wrong side of an outer join */
!         if (bms_overlap(rinfo->clause_relids, inner_baserels))
              continue;

          /* Potentially usable, so see if it matches the index or is an OR */
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 1d537d84ee6a37f89e5bcfc2df93001ab8426fc6..f75743cb42b9164947ad7beed7c36563dbf68654 100644
*** a/src/backend/optimizer/path/joinpath.c
--- b/src/backend/optimizer/path/joinpath.c
*************** match_unsorted_outer(PlannerInfo *root,
*** 728,734 ****
          /*
           * We cannot use an outer path that is parameterized by the inner rel.
           */
!         if (bms_overlap(outerpath->required_outer, innerrel->relids))
              continue;

          /*
--- 728,734 ----
          /*
           * We cannot use an outer path that is parameterized by the inner rel.
           */
!         if (bms_overlap(PATH_PARAMRELS(outerpath), innerrel->relids))
              continue;

          /*
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1172,1178 ****
                   * We cannot use an outer path that is parameterized by the
                   * inner rel.
                   */
!                 if (bms_overlap(outerpath->required_outer, innerrel->relids))
                      continue;

                  foreach(lc2, innerrel->cheapest_parameterized_paths)
--- 1172,1178 ----
                   * We cannot use an outer path that is parameterized by the
                   * inner rel.
                   */
!                 if (bms_overlap(PATH_PARAMRELS(outerpath), innerrel->relids))
                      continue;

                  foreach(lc2, innerrel->cheapest_parameterized_paths)
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1183,1189 ****
                       * We cannot use an inner path that is parameterized by
                       * the outer rel, either.
                       */
!                     if (bms_overlap(innerpath->required_outer,
                                      outerrel->relids))
                          continue;

--- 1183,1189 ----
                       * We cannot use an inner path that is parameterized by
                       * the outer rel, either.
                       */
!                     if (bms_overlap(PATH_PARAMRELS(innerpath),
                                      outerrel->relids))
                          continue;

diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index ea297db907de3354c2a665fb395d0bc77fb59070..82252753fb0d331875c1607a7f7169601b6183d6 100644
*** a/src/backend/optimizer/path/joinrels.c
--- b/src/backend/optimizer/path/joinrels.c
*************** mark_dummy_rel(RelOptInfo *rel)
*** 930,936 ****
      rel->pathlist = NIL;

      /* Set up the dummy path */
!     add_path(rel, (Path *) create_append_path(rel, NIL));

      /* Set or update cheapest_total_path and related fields */
      set_cheapest(rel);
--- 930,936 ----
      rel->pathlist = NIL;

      /* Set up the dummy path */
!     add_path(rel, (Path *) create_append_path(rel, NIL, NULL));

      /* Set or update cheapest_total_path and related fields */
      set_cheapest(rel);
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index 4ff8016666d7f2785c0d64ae9012d686e1ab7382..7a5228aac1a032daff3404063de34f2474f2f840 100644
*** a/src/backend/optimizer/path/pathkeys.c
--- b/src/backend/optimizer/path/pathkeys.c
*************** get_cheapest_path_for_pathkeys(List *pat
*** 439,445 ****
              continue;

          if (pathkeys_contained_in(pathkeys, path->pathkeys) &&
!             bms_is_subset(path->required_outer, required_outer))
              matched_path = path;
      }
      return matched_path;
--- 439,445 ----
              continue;

          if (pathkeys_contained_in(pathkeys, path->pathkeys) &&
!             bms_is_subset(PATH_PARAMRELS(path), required_outer))
              matched_path = path;
      }
      return matched_path;
*************** get_cheapest_fractional_path_for_pathkey
*** 481,487 ****
              continue;

          if (pathkeys_contained_in(pathkeys, path->pathkeys) &&
!             bms_is_subset(path->required_outer, required_outer))
              matched_path = path;
      }
      return matched_path;
--- 481,487 ----
              continue;

          if (pathkeys_contained_in(pathkeys, path->pathkeys) &&
!             bms_is_subset(PATH_PARAMRELS(path), required_outer))
              matched_path = path;
      }
      return matched_path;
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 38b040ffff3b9ee787fed078cc6b14be033fba24..7ba640f12b6e97e109364b6eb3ad332a4abd77a7 100644
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
*************** static BitmapHeapScan *create_bitmap_sca
*** 60,66 ****
                          BitmapHeapPath *best_path,
                          List *tlist, List *scan_clauses);
  static Plan *create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
!                       List **qual, List **indexqual);
  static TidScan *create_tidscan_plan(PlannerInfo *root, TidPath *best_path,
                      List *tlist, List *scan_clauses);
  static SubqueryScan *create_subqueryscan_plan(PlannerInfo *root, Path *best_path,
--- 60,66 ----
                          BitmapHeapPath *best_path,
                          List *tlist, List *scan_clauses);
  static Plan *create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
!                       List **qual, List **indexqual, List **indexECs);
  static TidScan *create_tidscan_plan(PlannerInfo *root, TidPath *best_path,
                      List *tlist, List *scan_clauses);
  static SubqueryScan *create_subqueryscan_plan(PlannerInfo *root, Path *best_path,
*************** static Node *replace_nestloop_params_mut
*** 86,91 ****
--- 86,92 ----
  static List *fix_indexqual_references(PlannerInfo *root, IndexPath *index_path);
  static List *fix_indexorderby_references(PlannerInfo *root, IndexPath *index_path);
  static Node *fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol);
+ static bool is_redundant_derived_clause(RestrictInfo *rinfo, List *indexquals);
  static List *get_switched_clauses(List *clauses, Relids outerrelids);
  static List *order_qual_clauses(PlannerInfo *root, List *clauses);
  static void copy_path_costsize(Plan *dest, Path *src);
*************** create_scan_plan(PlannerInfo *root, Path
*** 305,310 ****
--- 306,321 ----
       */
      scan_clauses = rel->baserestrictinfo;

+     /*
+      * If this is a parameterized scan, we also need to enforce all the join
+      * clauses available from the outer relation(s).
+      *
+      * For paranoia's sake, don't modify the stored baserestrictinfo list.
+      */
+     if (best_path->param_info)
+         scan_clauses = list_concat(list_copy(scan_clauses),
+                                    best_path->param_info->ppi_clauses);
+
      switch (best_path->pathtype)
      {
          case T_SeqScan:
*************** create_seqscan_plan(PlannerInfo *root, P
*** 1060,1065 ****
--- 1071,1083 ----
      /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
      scan_clauses = extract_actual_clauses(scan_clauses, false);

+     /* Replace any outer-relation variables with nestloop params */
+     if (best_path->param_info)
+     {
+         scan_clauses = (List *)
+             replace_nestloop_params(root, (Node *) scan_clauses);
+     }
+
      scan_plan = make_seqscan(tlist,
                               scan_clauses,
                               scan_relid);
*************** create_indexscan_plan(PlannerInfo *root,
*** 1119,1152 ****
      fixed_indexorderbys = fix_indexorderby_references(root, best_path);

      /*
-      * If this is a parameterized scan, the indexclauses will contain join
-      * clauses that are not present in scan_clauses (since the passed-in value
-      * is just the rel's baserestrictinfo list).  We must add these clauses to
-      * scan_clauses to ensure they get checked.  In most cases we will remove
-      * the join clauses again below, but if a join clause contains a special
-      * operator, we need to make sure it gets into the scan_clauses.
-      *
-      * Note: pointer comparison should be enough to determine RestrictInfo
-      * matches.
-      */
-     if (best_path->path.required_outer)
-         scan_clauses = list_union_ptr(scan_clauses, best_path->indexclauses);
-
-     /*
       * The qpqual list must contain all restrictions not automatically handled
!      * by the index.  All the predicates in the indexquals will be checked
!      * (either by the index itself, or by nodeIndexscan.c), but if there are
!      * any "special" operators involved then they must be included in qpqual.
!      * The upshot is that qpqual must contain scan_clauses minus whatever
!      * appears in indexquals.
       *
       * In normal cases simple pointer equality checks will be enough to spot
!      * duplicate RestrictInfos, so we try that first.  In some situations
!      * (particularly with OR'd index conditions) we may have scan_clauses that
!      * are not equal to, but are logically implied by, the index quals; so we
!      * also try a predicate_implied_by() check to see if we can discard quals
!      * that way.  (predicate_implied_by assumes its first input contains only
!      * immutable functions, so we have to check that.)
       *
       * We can also discard quals that are implied by a partial index's
       * predicate, but only in a plain SELECT; when scanning a target relation
--- 1137,1165 ----
      fixed_indexorderbys = fix_indexorderby_references(root, best_path);

      /*
       * The qpqual list must contain all restrictions not automatically handled
!      * by the index, other than pseudoconstant clauses which will be handled
!      * by a separate gating plan node.  All the predicates in the indexquals
!      * will be checked (either by the index itself, or by nodeIndexscan.c),
!      * but if there are any "special" operators involved then they must be
!      * included in qpqual.  The upshot is that qpqual must contain
!      * scan_clauses minus whatever appears in indexquals.
       *
       * In normal cases simple pointer equality checks will be enough to spot
!      * duplicate RestrictInfos, so we try that first.
!      *
!      * Another common case is that a scan_clauses entry is generated from the
!      * same EquivalenceClass as some indexqual, and is therefore redundant
!      * with it, though not equal.  (This happens when indxpath.c prefers a
!      * different derived equality than what generate_join_implied_equalities
!      * picked for a parameterized scan's ppi_clauses.)
!      *
!      * In some situations (particularly with OR'd index conditions) we may
!      * have scan_clauses that are not equal to, but are logically implied by,
!      * the index quals; so we also try a predicate_implied_by() check to see
!      * if we can discard quals that way.  (predicate_implied_by assumes its
!      * first input contains only immutable functions, so we have to check
!      * that.)
       *
       * We can also discard quals that are implied by a partial index's
       * predicate, but only in a plain SELECT; when scanning a target relation
*************** create_indexscan_plan(PlannerInfo *root,
*** 1162,1181 ****
          if (rinfo->pseudoconstant)
              continue;            /* we may drop pseudoconstants here */
          if (list_member_ptr(indexquals, rinfo))
!             continue;
          if (!contain_mutable_functions((Node *) rinfo->clause))
          {
              List       *clausel = list_make1(rinfo->clause);

              if (predicate_implied_by(clausel, indexquals))
!                 continue;
              if (best_path->indexinfo->indpred)
              {
                  if (baserelid != root->parse->resultRelation &&
                      get_parse_rowmark(root->parse, baserelid) == NULL)
                      if (predicate_implied_by(clausel,
                                               best_path->indexinfo->indpred))
!                         continue;
              }
          }
          qpqual = lappend(qpqual, rinfo);
--- 1175,1196 ----
          if (rinfo->pseudoconstant)
              continue;            /* we may drop pseudoconstants here */
          if (list_member_ptr(indexquals, rinfo))
!             continue;            /* simple duplicate */
!         if (is_redundant_derived_clause(rinfo, indexquals))
!             continue;            /* derived from same EquivalenceClass */
          if (!contain_mutable_functions((Node *) rinfo->clause))
          {
              List       *clausel = list_make1(rinfo->clause);

              if (predicate_implied_by(clausel, indexquals))
!                 continue;        /* provably implied by indexquals */
              if (best_path->indexinfo->indpred)
              {
                  if (baserelid != root->parse->resultRelation &&
                      get_parse_rowmark(root->parse, baserelid) == NULL)
                      if (predicate_implied_by(clausel,
                                               best_path->indexinfo->indpred))
!                         continue; /* implied by index predicate */
              }
          }
          qpqual = lappend(qpqual, rinfo);
*************** create_indexscan_plan(PlannerInfo *root,
*** 1196,1202 ****
       * it'd break the comparisons to predicates above ... (or would it?  Those
       * wouldn't have outer refs)
       */
!     if (best_path->path.required_outer)
      {
          stripped_indexquals = (List *)
              replace_nestloop_params(root, (Node *) stripped_indexquals);
--- 1211,1217 ----
       * it'd break the comparisons to predicates above ... (or would it?  Those
       * wouldn't have outer refs)
       */
!     if (best_path->path.param_info)
      {
          stripped_indexquals = (List *)
              replace_nestloop_params(root, (Node *) stripped_indexquals);
*************** create_bitmap_scan_plan(PlannerInfo *roo
*** 1247,1252 ****
--- 1262,1268 ----
      Plan       *bitmapqualplan;
      List       *bitmapqualorig;
      List       *indexquals;
+     List       *indexECs;
      List       *qpqual;
      ListCell   *l;
      BitmapHeapScan *scan_plan;
*************** create_bitmap_scan_plan(PlannerInfo *roo
*** 1257,1322 ****

      /* Process the bitmapqual tree into a Plan tree and qual lists */
      bitmapqualplan = create_bitmap_subplan(root, best_path->bitmapqual,
!                                            &bitmapqualorig, &indexquals);
!
!     /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
!     scan_clauses = extract_actual_clauses(scan_clauses, false);
!
!     /*
!      * If this is a parameterized scan, the indexclauses will contain join clauses
!      * that are not present in scan_clauses (since the passed-in value is just
!      * the rel's baserestrictinfo list).  We must add these clauses to
!      * scan_clauses to ensure they get checked.  In most cases we will remove
!      * the join clauses again below, but if a join clause contains a special
!      * operator, we need to make sure it gets into the scan_clauses.
!      */
!     if (best_path->path.required_outer)
!     {
!         scan_clauses = list_concat_unique(scan_clauses, bitmapqualorig);
!     }

      /*
       * The qpqual list must contain all restrictions not automatically handled
!      * by the index.  All the predicates in the indexquals will be checked
!      * (either by the index itself, or by nodeBitmapHeapscan.c), but if there
!      * are any "special" operators involved then they must be added to qpqual.
!      * The upshot is that qpqual must contain scan_clauses minus whatever
!      * appears in indexquals.
       *
       * In normal cases simple equal() checks will be enough to spot duplicate
!      * clauses, so we try that first.  In some situations (particularly with
!      * OR'd index conditions) we may have scan_clauses that are not equal to,
!      * but are logically implied by, the index quals; so we also try a
!      * predicate_implied_by() check to see if we can discard quals that way.
!      * (predicate_implied_by assumes its first input contains only immutable
!      * functions, so we have to check that.)
       *
       * Unlike create_indexscan_plan(), we need take no special thought here
       * for partial index predicates; this is because the predicate conditions
       * are already listed in bitmapqualorig and indexquals.  Bitmap scans have
       * to do it that way because predicate conditions need to be rechecked if
!      * the scan becomes lossy.
       */
      qpqual = NIL;
      foreach(l, scan_clauses)
      {
!         Node       *clause = (Node *) lfirst(l);

          if (list_member(indexquals, clause))
!             continue;
          if (!contain_mutable_functions(clause))
          {
              List       *clausel = list_make1(clause);

              if (predicate_implied_by(clausel, indexquals))
!                 continue;
          }
!         qpqual = lappend(qpqual, clause);
      }

      /* Sort clauses into best execution order */
      qpqual = order_qual_clauses(root, qpqual);

      /*
       * When dealing with special operators, we will at this point have
       * duplicate clauses in qpqual and bitmapqualorig.    We may as well drop
--- 1273,1335 ----

      /* Process the bitmapqual tree into a Plan tree and qual lists */
      bitmapqualplan = create_bitmap_subplan(root, best_path->bitmapqual,
!                                            &bitmapqualorig, &indexquals,
!                                            &indexECs);

      /*
       * The qpqual list must contain all restrictions not automatically handled
!      * by the index, other than pseudoconstant clauses which will be handled
!      * by a separate gating plan node.  All the predicates in the indexquals
!      * will be checked (either by the index itself, or by
!      * nodeBitmapHeapscan.c), but if there are any "special" operators
!      * involved then they must be added to qpqual.  The upshot is that qpqual
!      * must contain scan_clauses minus whatever appears in indexquals.
!      *
!      * This loop is similar to the comparable code in create_indexscan_plan(),
!      * but with some differences because it has to compare the scan clauses to
!      * stripped (no RestrictInfos) indexquals.  See comments there for more
!      * info.
       *
       * In normal cases simple equal() checks will be enough to spot duplicate
!      * clauses, so we try that first.  We next see if the scan clause is
!      * redundant with any top-level indexqual by virtue of being generated
!      * from the same EC.  After that, try predicate_implied_by().
       *
       * Unlike create_indexscan_plan(), we need take no special thought here
       * for partial index predicates; this is because the predicate conditions
       * are already listed in bitmapqualorig and indexquals.  Bitmap scans have
       * to do it that way because predicate conditions need to be rechecked if
!      * the scan becomes lossy, so they have to be included in bitmapqualorig.
       */
      qpqual = NIL;
      foreach(l, scan_clauses)
      {
!         RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
!         Node       *clause = (Node *) rinfo->clause;

+         Assert(IsA(rinfo, RestrictInfo));
+         if (rinfo->pseudoconstant)
+             continue;            /* we may drop pseudoconstants here */
          if (list_member(indexquals, clause))
!             continue;            /* simple duplicate */
!         if (rinfo->parent_ec && list_member_ptr(indexECs, rinfo->parent_ec))
!             continue;            /* derived from same EquivalenceClass */
          if (!contain_mutable_functions(clause))
          {
              List       *clausel = list_make1(clause);

              if (predicate_implied_by(clausel, indexquals))
!                 continue;        /* provably implied by indexquals */
          }
!         qpqual = lappend(qpqual, rinfo);
      }

      /* Sort clauses into best execution order */
      qpqual = order_qual_clauses(root, qpqual);

+     /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
+     qpqual = extract_actual_clauses(qpqual, false);
+
      /*
       * When dealing with special operators, we will at this point have
       * duplicate clauses in qpqual and bitmapqualorig.    We may as well drop
*************** create_bitmap_scan_plan(PlannerInfo *roo
*** 1325,1330 ****
--- 1338,1356 ----
       */
      bitmapqualorig = list_difference_ptr(bitmapqualorig, qpqual);

+     /*
+      * We have to replace any outer-relation variables with nestloop params in
+      * the qpqual and bitmapqualorig expressions.  (This was already done for
+      * expressions attached to plan nodes in the bitmapqualplan tree.)
+      */
+     if (best_path->path.param_info)
+     {
+         qpqual = (List *)
+             replace_nestloop_params(root, (Node *) qpqual);
+         bitmapqualorig = (List *)
+             replace_nestloop_params(root, (Node *) bitmapqualorig);
+     }
+
      /* Finally ready to build the plan node */
      scan_plan = make_bitmap_heapscan(tlist,
                                       qpqual,
*************** create_bitmap_scan_plan(PlannerInfo *roo
*** 1349,1360 ****
   * predicates, because we have to recheck predicates as well as index
   * conditions if the bitmap scan becomes lossy.
   *
   * Note: if you find yourself changing this, you probably need to change
   * make_restrictinfo_from_bitmapqual too.
   */
  static Plan *
  create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
!                       List **qual, List **indexqual)
  {
      Plan       *plan;

--- 1375,1394 ----
   * predicates, because we have to recheck predicates as well as index
   * conditions if the bitmap scan becomes lossy.
   *
+  * In addition, we return a list of EquivalenceClass pointers for all the
+  * top-level indexquals that were possibly-redundantly derived from ECs.
+  * This allows removal of scan_clauses that are redundant with such quals.
+  * (We do not attempt to detect such redundancies for quals that are within
+  * OR subtrees.  This could be done in a less hacky way if we returned the
+  * indexquals in RestrictInfo form, but that would be slower and still pretty
+  * messy, since we'd have to build new RestrictInfos in many cases.)
+  *
   * Note: if you find yourself changing this, you probably need to change
   * make_restrictinfo_from_bitmapqual too.
   */
  static Plan *
  create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
!                       List **qual, List **indexqual, List **indexECs)
  {
      Plan       *plan;

*************** create_bitmap_subplan(PlannerInfo *root,
*** 1364,1369 ****
--- 1398,1404 ----
          List       *subplans = NIL;
          List       *subquals = NIL;
          List       *subindexquals = NIL;
+         List       *subindexECs = NIL;
          ListCell   *l;

          /*
*************** create_bitmap_subplan(PlannerInfo *root,
*** 1378,1389 ****
              Plan       *subplan;
              List       *subqual;
              List       *subindexqual;

              subplan = create_bitmap_subplan(root, (Path *) lfirst(l),
!                                             &subqual, &subindexqual);
              subplans = lappend(subplans, subplan);
              subquals = list_concat_unique(subquals, subqual);
              subindexquals = list_concat_unique(subindexquals, subindexqual);
          }
          plan = (Plan *) make_bitmap_and(subplans);
          plan->startup_cost = apath->path.startup_cost;
--- 1413,1428 ----
              Plan       *subplan;
              List       *subqual;
              List       *subindexqual;
+             List       *subindexEC;

              subplan = create_bitmap_subplan(root, (Path *) lfirst(l),
!                                             &subqual, &subindexqual,
!                                             &subindexEC);
              subplans = lappend(subplans, subplan);
              subquals = list_concat_unique(subquals, subqual);
              subindexquals = list_concat_unique(subindexquals, subindexqual);
+             /* Duplicates in indexECs aren't worth getting rid of */
+             subindexECs = list_concat(subindexECs, subindexEC);
          }
          plan = (Plan *) make_bitmap_and(subplans);
          plan->startup_cost = apath->path.startup_cost;
*************** create_bitmap_subplan(PlannerInfo *root,
*** 1393,1398 ****
--- 1432,1438 ----
          plan->plan_width = 0;    /* meaningless */
          *qual = subquals;
          *indexqual = subindexquals;
+         *indexECs = subindexECs;
      }
      else if (IsA(bitmapqual, BitmapOrPath))
      {
*************** create_bitmap_subplan(PlannerInfo *root,
*** 1418,1426 ****
              Plan       *subplan;
              List       *subqual;
              List       *subindexqual;

              subplan = create_bitmap_subplan(root, (Path *) lfirst(l),
!                                             &subqual, &subindexqual);
              subplans = lappend(subplans, subplan);
              if (subqual == NIL)
                  const_true_subqual = true;
--- 1458,1468 ----
              Plan       *subplan;
              List       *subqual;
              List       *subindexqual;
+             List       *subindexEC;

              subplan = create_bitmap_subplan(root, (Path *) lfirst(l),
!                                             &subqual, &subindexqual,
!                                             &subindexEC);
              subplans = lappend(subplans, subplan);
              if (subqual == NIL)
                  const_true_subqual = true;
*************** create_bitmap_subplan(PlannerInfo *root,
*** 1469,1479 ****
--- 1511,1523 ----
              *indexqual = subindexquals;
          else
              *indexqual = list_make1(make_orclause(subindexquals));
+         *indexECs = NIL;
      }
      else if (IsA(bitmapqual, IndexPath))
      {
          IndexPath  *ipath = (IndexPath *) bitmapqual;
          IndexScan  *iscan;
+         List       *subindexECs;
          ListCell   *l;

          /* Use the regular indexscan plan build machinery... */
*************** create_bitmap_subplan(PlannerInfo *root,
*** 1508,1525 ****
                  *indexqual = lappend(*indexqual, pred);
              }
          }
!
!         /*
!          * Replace outer-relation variables with nestloop params, but only
!          * after doing the above comparisons to index predicates.
!          */
!         if (ipath->path.required_outer)
          {
!             *qual = (List *)
!                 replace_nestloop_params(root, (Node *) *qual);
!             *indexqual = (List *)
!                 replace_nestloop_params(root, (Node *) *indexqual);
          }
      }
      else
      {
--- 1552,1566 ----
                  *indexqual = lappend(*indexqual, pred);
              }
          }
!         subindexECs = NIL;
!         foreach(l, ipath->indexquals)
          {
!             RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
!
!             if (rinfo->parent_ec)
!                 subindexECs = lappend(subindexECs, rinfo->parent_ec);
          }
+         *indexECs = subindexECs;
      }
      else
      {
*************** create_subqueryscan_plan(PlannerInfo *ro
*** 1594,1599 ****
--- 1635,1647 ----
      /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
      scan_clauses = extract_actual_clauses(scan_clauses, false);

+     /* Replace any outer-relation variables with nestloop params */
+     if (best_path->param_info)
+     {
+         scan_clauses = (List *)
+             replace_nestloop_params(root, (Node *) scan_clauses);
+     }
+
      scan_plan = make_subqueryscan(tlist,
                                    scan_clauses,
                                    scan_relid,
*************** create_foreignscan_plan(PlannerInfo *roo
*** 1859,1865 ****
       * from join clauses, so doing this beforehand on the scan_clauses
       * wouldn't work.)
       */
!     if (best_path->path.required_outer)
      {
          scan_plan->scan.plan.qual = (List *)
              replace_nestloop_params(root, (Node *) scan_plan->scan.plan.qual);
--- 1907,1913 ----
       * from join clauses, so doing this beforehand on the scan_clauses
       * wouldn't work.)
       */
!     if (best_path->path.param_info)
      {
          scan_plan->scan.plan.qual = (List *)
              replace_nestloop_params(root, (Node *) scan_plan->scan.plan.qual);
*************** create_nestloop_plan(PlannerInfo *root,
*** 1909,1923 ****
      ListCell   *prev;
      ListCell   *next;

-     /*
-      * If the inner path is parameterized, it might have already used some of
-      * the join quals, in which case we don't have to check them again at the
-      * join node.  Remove any join quals that are redundant.
-      */
-     joinrestrictclauses =
-         select_nonredundant_join_clauses(joinrestrictclauses,
-                                          best_path->innerjoinpath->param_clauses);
-
      /* Sort join qual clauses into best execution order */
      joinrestrictclauses = order_qual_clauses(root, joinrestrictclauses);

--- 1957,1962 ----
*************** create_nestloop_plan(PlannerInfo *root,
*** 1935,1940 ****
--- 1974,1988 ----
          otherclauses = NIL;
      }

+     /* Replace any outer-relation variables with nestloop params */
+     if (best_path->path.param_info)
+     {
+         joinclauses = (List *)
+             replace_nestloop_params(root, (Node *) joinclauses);
+         otherclauses = (List *)
+             replace_nestloop_params(root, (Node *) otherclauses);
+     }
+
      /*
       * Identify any nestloop parameters that should be supplied by this join
       * node, and move them from root->curOuterParams to the nestParams list.
*************** create_mergejoin_plan(PlannerInfo *root,
*** 2032,2037 ****
--- 2080,2097 ----
      joinclauses = list_difference(joinclauses, mergeclauses);

      /*
+      * Replace any outer-relation variables with nestloop params.  There
+      * should not be any in the mergeclauses.
+      */
+     if (best_path->jpath.path.param_info)
+     {
+         joinclauses = (List *)
+             replace_nestloop_params(root, (Node *) joinclauses);
+         otherclauses = (List *)
+             replace_nestloop_params(root, (Node *) otherclauses);
+     }
+
+     /*
       * Rearrange mergeclauses, if needed, so that the outer variable is always
       * on the left; mark the mergeclause restrictinfos with correct
       * outer_is_left status.
*************** create_hashjoin_plan(PlannerInfo *root,
*** 2310,2315 ****
--- 2370,2387 ----
      joinclauses = list_difference(joinclauses, hashclauses);

      /*
+      * Replace any outer-relation variables with nestloop params.  There
+      * should not be any in the hashclauses.
+      */
+     if (best_path->jpath.path.param_info)
+     {
+         joinclauses = (List *)
+             replace_nestloop_params(root, (Node *) joinclauses);
+         otherclauses = (List *)
+             replace_nestloop_params(root, (Node *) otherclauses);
+     }
+
+     /*
       * Rearrange hashclauses, if needed, so that the outer variable is always
       * on the left.
       */
*************** fix_indexqual_operand(Node *node, IndexO
*** 2763,2768 ****
--- 2835,2865 ----
  }

  /*
+  * is_redundant_derived_clause
+  *        Test whether rinfo is derived from same EC as any clause in indexquals
+  */
+ static bool
+ is_redundant_derived_clause(RestrictInfo *rinfo, List *indexquals)
+ {
+     EquivalenceClass *parent_ec = rinfo->parent_ec;
+     ListCell   *lc;
+
+     /* Fail if it's not a potentially-redundant clause from some EC */
+     if (parent_ec == NULL)
+         return false;
+
+     foreach(lc, indexquals)
+     {
+         RestrictInfo *otherrinfo = (RestrictInfo *) lfirst(lc);
+
+         if (otherrinfo->parent_ec == parent_ec)
+             return true;
+     }
+
+     return false;
+ }
+
+ /*
   * get_switched_clauses
   *      Given a list of merge or hash joinclauses (as RestrictInfo nodes),
   *      extract the bare clauses, and rearrange the elements within the
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index dcf32c0744456b5680b525a6310f29d4ac555314..14b251037c0d49f44e8d27f96859e8fc325c5260 100644
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
*************** plan_cluster_use_sort(Oid tableOid, Oid
*** 3288,3294 ****
      comparisonCost = 2.0 * (indexExprCost.startup + indexExprCost.per_tuple);

      /* Estimate the cost of seq scan + sort */
!     seqScanPath = create_seqscan_path(root, rel);
      cost_sort(&seqScanAndSortPath, root, NIL,
                seqScanPath->total_cost, rel->tuples, rel->width,
                comparisonCost, maintenance_work_mem, -1.0);
--- 3288,3294 ----
      comparisonCost = 2.0 * (indexExprCost.startup + indexExprCost.per_tuple);

      /* Estimate the cost of seq scan + sort */
!     seqScanPath = create_seqscan_path(root, rel, NULL);
      cost_sort(&seqScanAndSortPath, root, NIL,
                seqScanPath->total_cost, rel->tuples, rel->width,
                comparisonCost, maintenance_work_mem, -1.0);
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index a2fc75a659e50ca7d5726ed9d024622af9e7f191..55918c98fa7ef616311c8ff91140e17fbddae5b4 100644
*** a/src/backend/optimizer/util/pathnode.c
--- b/src/backend/optimizer/util/pathnode.c
***************
*** 22,27 ****
--- 22,28 ----
  #include "optimizer/cost.h"
  #include "optimizer/pathnode.h"
  #include "optimizer/paths.h"
+ #include "optimizer/restrictinfo.h"
  #include "optimizer/tlist.h"
  #include "parser/parsetree.h"
  #include "utils/lsyscache.h"
*************** set_cheapest(RelOptInfo *parent_rel)
*** 213,219 ****
          int            cmp;

          /* We only consider unparameterized paths in this step */
!         if (path->required_outer)
          {
              have_parameterized_paths = true;
              continue;
--- 214,220 ----
          int            cmp;

          /* We only consider unparameterized paths in this step */
!         if (path->param_info)
          {
              have_parameterized_paths = true;
              continue;
*************** set_cheapest(RelOptInfo *parent_rel)
*** 263,269 ****
          {
              Path       *path = (Path *) lfirst(p);

!             if (path->required_outer)
                  add_parameterized_path(parent_rel, path);
          }
      }
--- 264,270 ----
          {
              Path       *path = (Path *) lfirst(p);

!             if (path->param_info)
                  add_parameterized_path(parent_rel, path);
          }
      }
*************** set_cheapest(RelOptInfo *parent_rel)
*** 273,285 ****
   * add_path
   *      Consider a potential implementation path for the specified parent rel,
   *      and add it to the rel's pathlist if it is worthy of consideration.
!  *      A path is worthy if it has either a better sort order (better pathkeys)
!  *      or cheaper cost (on either dimension) than any of the existing old paths
!  *      that have the same or superset required_outer rels.
   *
   *      We also remove from the rel's pathlist any old paths that are dominated
   *      by new_path --- that is, new_path is cheaper, at least as well ordered,
!  *      and requires no outer rels not required by old path.
   *
   *      There is one policy decision embedded in this function, along with its
   *      sibling add_path_precheck: we treat all parameterized paths as having
--- 274,293 ----
   * add_path
   *      Consider a potential implementation path for the specified parent rel,
   *      and add it to the rel's pathlist if it is worthy of consideration.
!  *      A path is worthy if it has a better sort order (better pathkeys) or
!  *      cheaper cost (on either dimension), or generates fewer rows, than any
!  *      existing path that has the same or superset parameterization rels.
   *
   *      We also remove from the rel's pathlist any old paths that are dominated
   *      by new_path --- that is, new_path is cheaper, at least as well ordered,
!  *      generates no more rows, and requires no outer rels not required by the
!  *      old path.
!  *
!  *      In most cases, a path with a superset parameterization will generate
!  *      fewer rows (since it has more join clauses to apply), so that those two
!  *      figures of merit move in opposite directions; this means that a path of
!  *      one parameterization can seldom dominate a path of another.  But such
!  *      cases do arise, so we make the full set of checks anyway.
   *
   *      There is one policy decision embedded in this function, along with its
   *      sibling add_path_precheck: we treat all parameterized paths as having
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 325,331 ****
      CHECK_FOR_INTERRUPTS();

      /* Pretend parameterized paths have no pathkeys, per comment above */
!     new_path_pathkeys = new_path->required_outer ? NIL : new_path->pathkeys;

      /*
       * Loop to check proposed new path against old paths.  Note it is possible
--- 333,339 ----
      CHECK_FOR_INTERRUPTS();

      /* Pretend parameterized paths have no pathkeys, per comment above */
!     new_path_pathkeys = new_path->param_info ? NIL : new_path->pathkeys;

      /*
       * Loop to check proposed new path against old paths.  Note it is possible
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 351,367 ****
          /*
           * If the two paths compare differently for startup and total cost,
           * then we want to keep both, and we can skip comparing pathkeys and
!          * required_outer rels.  If they compare the same, proceed with the
!          * other comparisons.  (We make the tests in this order because the
!          * cost comparison is most likely to turn out "different", and the
!          * pathkeys comparison next most likely.)
           */
          if (costcmp != COSTS_DIFFERENT)
          {
              /* Similarly check to see if either dominates on pathkeys */
              List       *old_path_pathkeys;

!             old_path_pathkeys = old_path->required_outer ? NIL : old_path->pathkeys;
              keyscmp = compare_pathkeys(new_path_pathkeys,
                                         old_path_pathkeys);
              if (keyscmp != PATHKEYS_DIFFERENT)
--- 359,378 ----
          /*
           * If the two paths compare differently for startup and total cost,
           * then we want to keep both, and we can skip comparing pathkeys and
!          * parameterizations.  If they compare the same, proceed with the
!          * other comparisons.  Row count is checked last.  (We make the tests
!          * in this order because the cost comparison is most likely to turn
!          * out "different", and the pathkeys comparison next most likely.  As
!          * explained above, row count very seldom makes a difference, so even
!          * though it's cheap to compare there's not much point in checking it
!          * earlier.)
           */
          if (costcmp != COSTS_DIFFERENT)
          {
              /* Similarly check to see if either dominates on pathkeys */
              List       *old_path_pathkeys;

!             old_path_pathkeys = old_path->param_info ? NIL : old_path->pathkeys;
              keyscmp = compare_pathkeys(new_path_pathkeys,
                                         old_path_pathkeys);
              if (keyscmp != PATHKEYS_DIFFERENT)
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 369,386 ****
                  switch (costcmp)
                  {
                      case COSTS_EQUAL:
!                         outercmp = bms_subset_compare(new_path->required_outer,
!                                                       old_path->required_outer);
                          if (keyscmp == PATHKEYS_BETTER1)
                          {
!                             if (outercmp == BMS_EQUAL ||
!                                 outercmp == BMS_SUBSET1)
                                  remove_old = true;    /* new dominates old */
                          }
                          else if (keyscmp == PATHKEYS_BETTER2)
                          {
!                             if (outercmp == BMS_EQUAL ||
!                                 outercmp == BMS_SUBSET2)
                                  accept_new = false;    /* old dominates new */
                          }
                          else    /* keyscmp == PATHKEYS_EQUAL */
--- 380,399 ----
                  switch (costcmp)
                  {
                      case COSTS_EQUAL:
!                         outercmp = bms_subset_compare(PATH_PARAMRELS(new_path),
!                                                       PATH_PARAMRELS(old_path));
                          if (keyscmp == PATHKEYS_BETTER1)
                          {
!                             if ((outercmp == BMS_EQUAL ||
!                                  outercmp == BMS_SUBSET1) &&
!                                 new_path->rows <= old_path->rows)
                                  remove_old = true;    /* new dominates old */
                          }
                          else if (keyscmp == PATHKEYS_BETTER2)
                          {
!                             if ((outercmp == BMS_EQUAL ||
!                                  outercmp == BMS_SUBSET2) &&
!                                 new_path->rows >= old_path->rows)
                                  accept_new = false;    /* old dominates new */
                          }
                          else    /* keyscmp == PATHKEYS_EQUAL */
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 389,407 ****
                              {
                                  /*
                                   * Same pathkeys and outer rels, and fuzzily
!                                  * the same cost, so keep just one --- but
!                                  * we'll do an exact cost comparison to decide
!                                  * which.
                                   */
!                                 if (compare_path_costs(new_path, old_path,
                                                         TOTAL_COST) < 0)
                                      remove_old = true;    /* new dominates old */
                                  else
                                      accept_new = false; /* old equals or dominates new */
                              }
!                             else if (outercmp == BMS_SUBSET1)
                                  remove_old = true;    /* new dominates old */
!                             else if (outercmp == BMS_SUBSET2)
                                  accept_new = false;    /* old dominates new */
                              /* else different parameterizations, keep both */
                          }
--- 402,426 ----
                              {
                                  /*
                                   * Same pathkeys and outer rels, and fuzzily
!                                  * the same cost, so keep just one; to decide
!                                  * which, first check rows and then do an
!                                  * exact cost comparison.
                                   */
!                                 if (new_path->rows < old_path->rows)
!                                     remove_old = true;    /* new dominates old */
!                                 else if (new_path->rows > old_path->rows)
!                                     accept_new = false;    /* old dominates new */
!                                 else if (compare_path_costs(new_path, old_path,
                                                         TOTAL_COST) < 0)
                                      remove_old = true;    /* new dominates old */
                                  else
                                      accept_new = false; /* old equals or dominates new */
                              }
!                             else if (outercmp == BMS_SUBSET1 &&
!                                      new_path->rows <= old_path->rows)
                                  remove_old = true;    /* new dominates old */
!                             else if (outercmp == BMS_SUBSET2 &&
!                                      new_path->rows >= old_path->rows)
                                  accept_new = false;    /* old dominates new */
                              /* else different parameterizations, keep both */
                          }
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 409,428 ****
                      case COSTS_BETTER1:
                          if (keyscmp != PATHKEYS_BETTER2)
                          {
!                             outercmp = bms_subset_compare(new_path->required_outer,
!                                                           old_path->required_outer);
!                             if (outercmp == BMS_EQUAL ||
!                                 outercmp == BMS_SUBSET1)
                                  remove_old = true;    /* new dominates old */
                          }
                          break;
                      case COSTS_BETTER2:
                          if (keyscmp != PATHKEYS_BETTER1)
                          {
!                             outercmp = bms_subset_compare(new_path->required_outer,
!                                                           old_path->required_outer);
!                             if (outercmp == BMS_EQUAL ||
!                                 outercmp == BMS_SUBSET2)
                                  accept_new = false;    /* old dominates new */
                          }
                          break;
--- 428,449 ----
                      case COSTS_BETTER1:
                          if (keyscmp != PATHKEYS_BETTER2)
                          {
!                             outercmp = bms_subset_compare(PATH_PARAMRELS(new_path),
!                                                           PATH_PARAMRELS(old_path));
!                             if ((outercmp == BMS_EQUAL ||
!                                  outercmp == BMS_SUBSET1) &&
!                                 new_path->rows <= old_path->rows)
                                  remove_old = true;    /* new dominates old */
                          }
                          break;
                      case COSTS_BETTER2:
                          if (keyscmp != PATHKEYS_BETTER1)
                          {
!                             outercmp = bms_subset_compare(PATH_PARAMRELS(new_path),
!                                                           PATH_PARAMRELS(old_path));
!                             if ((outercmp == BMS_EQUAL ||
!                                  outercmp == BMS_SUBSET2) &&
!                                 new_path->rows >= old_path->rows)
                                  accept_new = false;    /* old dominates new */
                          }
                          break;
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 491,496 ****
--- 512,525 ----
   *      We assume we know the path's pathkeys and parameterization accurately,
   *      and have lower bounds for its costs.
   *
+  * Note that we do not know the path's rowcount, since getting an estimate for
+  * that is too expensive to do before prechecking.  We assume here that paths
+  * of a superset parameterization will generate fewer rows; if that holds,
+  * then paths with different parameterizations cannot dominate each other
+  * and so we can simply ignore existing paths of another parameterization.
+  * (In the infrequent cases where that rule of thumb fails, add_path will
+  * get rid of the inferior path.)
+  *
   * At the time this is called, we haven't actually built a Path structure,
   * so the required information has to be passed piecemeal.
   */
*************** add_path_precheck(RelOptInfo *parent_rel
*** 502,519 ****
      List       *new_path_pathkeys;
      ListCell   *p1;

!     /* Pretend parameterized paths have no pathkeys, per comment above */
      new_path_pathkeys = required_outer ? NIL : pathkeys;

      foreach(p1, parent_rel->pathlist)
      {
          Path       *old_path = (Path *) lfirst(p1);
          PathKeysComparison keyscmp;
-         BMS_Comparison outercmp;

          /*
!          * We are looking for an old_path that dominates the new path across
!          * all four metrics.  If we find one, we can reject the new path.
           *
           * For speed, we make exact rather than fuzzy cost comparisons.
           * If an old path dominates the new path exactly on both costs, it
--- 531,549 ----
      List       *new_path_pathkeys;
      ListCell   *p1;

!     /* Pretend parameterized paths have no pathkeys, per add_path comment */
      new_path_pathkeys = required_outer ? NIL : pathkeys;

      foreach(p1, parent_rel->pathlist)
      {
          Path       *old_path = (Path *) lfirst(p1);
          PathKeysComparison keyscmp;

          /*
!          * We are looking for an old_path with the same parameterization (and
!          * by assumption the same rowcount) that dominates the new path on
!          * pathkeys as well as both cost metrics.  If we find one, we can
!          * reject the new path.
           *
           * For speed, we make exact rather than fuzzy cost comparisons.
           * If an old path dominates the new path exactly on both costs, it
*************** add_path_precheck(RelOptInfo *parent_rel
*** 525,541 ****
              {
                  List       *old_path_pathkeys;

!                 old_path_pathkeys = old_path->required_outer ? NIL : old_path->pathkeys;
                  keyscmp = compare_pathkeys(new_path_pathkeys,
                                             old_path_pathkeys);
                  if (keyscmp == PATHKEYS_EQUAL ||
                      keyscmp == PATHKEYS_BETTER2)
                  {
!                     outercmp = bms_subset_compare(required_outer,
!                                                   old_path->required_outer);
!                     if (outercmp == BMS_EQUAL ||
!                         outercmp == BMS_SUBSET2)
                          return false;
                  }
              }
          }
--- 555,571 ----
              {
                  List       *old_path_pathkeys;

!                 old_path_pathkeys = old_path->param_info ? NIL : old_path->pathkeys;
                  keyscmp = compare_pathkeys(new_path_pathkeys,
                                             old_path_pathkeys);
                  if (keyscmp == PATHKEYS_EQUAL ||
                      keyscmp == PATHKEYS_BETTER2)
                  {
!                     if (bms_equal(required_outer, PATH_PARAMRELS(old_path)))
!                     {
!                         /* Found an old path that dominates the new one */
                          return false;
+                     }
                  }
              }
          }
*************** add_path_precheck(RelOptInfo *parent_rel
*** 559,568 ****
   *      and add it to the rel's cheapest_parameterized_paths list if it
   *      belongs there, removing any old entries that it dominates.
   *
!  *      This is essentially a cut-down form of add_path(): we do not care about
!  *      startup cost or sort ordering, only total cost and parameterization.
!  *      Also, we should not recycle rejected paths, since they will still be
!  *      present in the rel's pathlist.
   *
   * 'parent_rel' is the relation entry to which the path corresponds.
   * 'new_path' is a parameterized path for parent_rel.
--- 589,598 ----
   *      and add it to the rel's cheapest_parameterized_paths list if it
   *      belongs there, removing any old entries that it dominates.
   *
!  *      This is essentially a cut-down form of add_path(): we do not care
!  *      about startup cost or sort ordering, only total cost, rowcount, and
!  *      parameterization.  Also, we must not recycle rejected paths, since
!  *      they will still be present in the rel's pathlist.
   *
   * 'parent_rel' is the relation entry to which the path corresponds.
   * 'new_path' is a parameterized path for parent_rel.
*************** add_parameterized_path(RelOptInfo *paren
*** 598,624 ****
          p1_next = lnext(p1);

          costcmp = compare_path_costs(new_path, old_path, TOTAL_COST);
!         outercmp = bms_subset_compare(new_path->required_outer,
!                                       old_path->required_outer);
          if (outercmp != BMS_DIFFERENT)
          {
              if (costcmp < 0)
              {
!                 if (outercmp != BMS_SUBSET2)
                      remove_old = true; /* new dominates old */
              }
              else if (costcmp > 0)
              {
!                 if (outercmp != BMS_SUBSET1)
                      accept_new = false;    /* old dominates new */
              }
!             else if (outercmp == BMS_SUBSET1)
                  remove_old = true; /* new dominates old */
!             else if (outercmp == BMS_SUBSET2)
                  accept_new = false;    /* old dominates new */
              else
              {
!                 /* Same cost and outer rels, arbitrarily keep the old */
                  accept_new = false; /* old equals or dominates new */
              }
          }
--- 628,660 ----
          p1_next = lnext(p1);

          costcmp = compare_path_costs(new_path, old_path, TOTAL_COST);
!         outercmp = bms_subset_compare(PATH_PARAMRELS(new_path),
!                                       PATH_PARAMRELS(old_path));
          if (outercmp != BMS_DIFFERENT)
          {
              if (costcmp < 0)
              {
!                 if (outercmp != BMS_SUBSET2 &&
!                     new_path->rows <= old_path->rows)
                      remove_old = true; /* new dominates old */
              }
              else if (costcmp > 0)
              {
!                 if (outercmp != BMS_SUBSET1 &&
!                     new_path->rows >= old_path->rows)
                      accept_new = false;    /* old dominates new */
              }
!             else if (outercmp == BMS_SUBSET1 &&
!                      new_path->rows <= old_path->rows)
                  remove_old = true; /* new dominates old */
!             else if (outercmp == BMS_SUBSET2 &&
!                      new_path->rows >= old_path->rows)
                  accept_new = false;    /* old dominates new */
+             else if (new_path->rows < old_path->rows)
+                 remove_old = true; /* new dominates old */
              else
              {
!                 /* Same cost, rows, and param rels; arbitrarily keep old */
                  accept_new = false; /* old equals or dominates new */
              }
          }
*************** add_parameterized_path(RelOptInfo *paren
*** 675,691 ****
   *      pathnode.
   */
  Path *
! create_seqscan_path(PlannerInfo *root, RelOptInfo *rel)
  {
      Path       *pathnode = makeNode(Path);

      pathnode->pathtype = T_SeqScan;
      pathnode->parent = rel;
      pathnode->pathkeys = NIL;    /* seqscan has unordered result */
-     pathnode->required_outer = NULL;
-     pathnode->param_clauses = NIL;

!     cost_seqscan(pathnode, root, rel);

      return pathnode;
  }
--- 711,727 ----
   *      pathnode.
   */
  Path *
! create_seqscan_path(PlannerInfo *root, RelOptInfo *rel, Relids required_outer)
  {
      Path       *pathnode = makeNode(Path);

      pathnode->pathtype = T_SeqScan;
      pathnode->parent = rel;
+     pathnode->param_info = get_baserel_parampathinfo(root, rel,
+                                                      required_outer);
      pathnode->pathkeys = NIL;    /* seqscan has unordered result */

!     cost_seqscan(pathnode, root, rel, pathnode->param_info);

      return pathnode;
  }
*************** create_seqscan_path(PlannerInfo *root, R
*** 708,714 ****
   *            for an ordered index, or NoMovementScanDirection for
   *            an unordered index.
   * 'indexonly' is true if an index-only scan is wanted.
!  * 'required_outer' is the set of outer relids referenced in indexclauses.
   * 'loop_count' is the number of repetitions of the indexscan to factor into
   *        estimates of caching behavior.
   *
--- 744,750 ----
   *            for an ordered index, or NoMovementScanDirection for
   *            an unordered index.
   * 'indexonly' is true if an index-only scan is wanted.
!  * 'required_outer' is the set of outer relids for a parameterized path.
   * 'loop_count' is the number of repetitions of the indexscan to factor into
   *        estimates of caching behavior.
   *
*************** create_index_path(PlannerInfo *root,
*** 734,758 ****

      pathnode->path.pathtype = indexonly ? T_IndexOnlyScan : T_IndexScan;
      pathnode->path.parent = rel;
      pathnode->path.pathkeys = pathkeys;
-     pathnode->path.required_outer = required_outer;
-     if (required_outer)
-     {
-         /* Identify index clauses that are join clauses */
-         List       *jclauses = NIL;
-         ListCell   *lc;
-
-         foreach(lc, indexclauses)
-         {
-             RestrictInfo   *rinfo = (RestrictInfo *) lfirst(lc);
-
-             if (!bms_is_subset(rinfo->clause_relids, rel->relids))
-                 jclauses = lappend(jclauses, rinfo);
-         }
-         pathnode->path.param_clauses = jclauses;
-     }
-     else
-         pathnode->path.param_clauses = NIL;

      /* Convert clauses to indexquals the executor can handle */
      expand_indexqual_conditions(index, indexclauses, indexclausecols,
--- 770,778 ----

      pathnode->path.pathtype = indexonly ? T_IndexOnlyScan : T_IndexScan;
      pathnode->path.parent = rel;
+     pathnode->path.param_info = get_baserel_parampathinfo(root, rel,
+                                                           required_outer);
      pathnode->path.pathkeys = pathkeys;

      /* Convert clauses to indexquals the executor can handle */
      expand_indexqual_conditions(index, indexclauses, indexclausecols,
*************** create_index_path(PlannerInfo *root,
*** 777,782 ****
--- 797,803 ----
   *      Creates a path node for a bitmap scan.
   *
   * 'bitmapqual' is a tree of IndexPath, BitmapAndPath, and BitmapOrPath nodes.
+  * 'required_outer' is the set of outer relids for a parameterized path.
   * 'loop_count' is the number of repetitions of the indexscan to factor into
   *        estimates of caching behavior.
   *
*************** BitmapHeapPath *
*** 787,805 ****
  create_bitmap_heap_path(PlannerInfo *root,
                          RelOptInfo *rel,
                          Path *bitmapqual,
                          double loop_count)
  {
      BitmapHeapPath *pathnode = makeNode(BitmapHeapPath);

      pathnode->path.pathtype = T_BitmapHeapScan;
      pathnode->path.parent = rel;
      pathnode->path.pathkeys = NIL;        /* always unordered */
-     pathnode->path.required_outer = bitmapqual->required_outer;
-     pathnode->path.param_clauses = bitmapqual->param_clauses;

      pathnode->bitmapqual = bitmapqual;

!     cost_bitmap_heap_scan(&pathnode->path, root, rel, bitmapqual, loop_count);

      return pathnode;
  }
--- 808,829 ----
  create_bitmap_heap_path(PlannerInfo *root,
                          RelOptInfo *rel,
                          Path *bitmapqual,
+                         Relids required_outer,
                          double loop_count)
  {
      BitmapHeapPath *pathnode = makeNode(BitmapHeapPath);

      pathnode->path.pathtype = T_BitmapHeapScan;
      pathnode->path.parent = rel;
+     pathnode->path.param_info = get_baserel_parampathinfo(root, rel,
+                                                           required_outer);
      pathnode->path.pathkeys = NIL;        /* always unordered */

      pathnode->bitmapqual = bitmapqual;

!     cost_bitmap_heap_scan(&pathnode->path, root, rel,
!                           pathnode->path.param_info,
!                           bitmapqual, loop_count);

      return pathnode;
  }
*************** create_bitmap_and_path(PlannerInfo *root
*** 814,842 ****
                         List *bitmapquals)
  {
      BitmapAndPath *pathnode = makeNode(BitmapAndPath);
-     ListCell   *lc;

      pathnode->path.pathtype = T_BitmapAnd;
      pathnode->path.parent = rel;
      pathnode->path.pathkeys = NIL;        /* always unordered */
-     pathnode->path.required_outer = NULL;
-     pathnode->path.param_clauses = NIL;

      pathnode->bitmapquals = bitmapquals;

-     /* required_outer and param_clauses are the union of the inputs' values */
-     foreach(lc, bitmapquals)
-     {
-         Path   *bpath = (Path *) lfirst(lc);
-
-         pathnode->path.required_outer =
-             bms_add_members(pathnode->path.required_outer,
-                             bpath->required_outer);
-         pathnode->path.param_clauses =
-             list_concat(pathnode->path.param_clauses,
-                         list_copy(bpath->param_clauses));
-     }
-
      /* this sets bitmapselectivity as well as the regular cost fields: */
      cost_bitmap_and_node(pathnode, root);

--- 838,851 ----
                         List *bitmapquals)
  {
      BitmapAndPath *pathnode = makeNode(BitmapAndPath);

      pathnode->path.pathtype = T_BitmapAnd;
      pathnode->path.parent = rel;
+     pathnode->path.param_info = NULL;    /* not used in bitmap trees */
      pathnode->path.pathkeys = NIL;        /* always unordered */

      pathnode->bitmapquals = bitmapquals;

      /* this sets bitmapselectivity as well as the regular cost fields: */
      cost_bitmap_and_node(pathnode, root);

*************** create_bitmap_or_path(PlannerInfo *root,
*** 853,881 ****
                        List *bitmapquals)
  {
      BitmapOrPath *pathnode = makeNode(BitmapOrPath);
-     ListCell   *lc;

      pathnode->path.pathtype = T_BitmapOr;
      pathnode->path.parent = rel;
      pathnode->path.pathkeys = NIL;        /* always unordered */
-     pathnode->path.required_outer = NULL;
-     pathnode->path.param_clauses = NIL;

      pathnode->bitmapquals = bitmapquals;

-     /* required_outer and param_clauses are the union of the inputs' values */
-     foreach(lc, bitmapquals)
-     {
-         Path   *bpath = (Path *) lfirst(lc);
-
-         pathnode->path.required_outer =
-             bms_add_members(pathnode->path.required_outer,
-                             bpath->required_outer);
-         pathnode->path.param_clauses =
-             list_concat(pathnode->path.param_clauses,
-                         list_copy(bpath->param_clauses));
-     }
-
      /* this sets bitmapselectivity as well as the regular cost fields: */
      cost_bitmap_or_node(pathnode, root);

--- 862,875 ----
                        List *bitmapquals)
  {
      BitmapOrPath *pathnode = makeNode(BitmapOrPath);

      pathnode->path.pathtype = T_BitmapOr;
      pathnode->path.parent = rel;
+     pathnode->path.param_info = NULL;    /* not used in bitmap trees */
      pathnode->path.pathkeys = NIL;        /* always unordered */

      pathnode->bitmapquals = bitmapquals;

      /* this sets bitmapselectivity as well as the regular cost fields: */
      cost_bitmap_or_node(pathnode, root);

*************** create_tidscan_path(PlannerInfo *root, R
*** 893,901 ****

      pathnode->path.pathtype = T_TidScan;
      pathnode->path.parent = rel;
!     pathnode->path.pathkeys = NIL;
!     pathnode->path.required_outer = NULL;
!     pathnode->path.param_clauses = NIL;

      pathnode->tidquals = tidquals;

--- 887,894 ----

      pathnode->path.pathtype = T_TidScan;
      pathnode->path.parent = rel;
!     pathnode->path.param_info = NULL;    /* never parameterized at present */
!     pathnode->path.pathkeys = NIL;        /* always unordered */

      pathnode->tidquals = tidquals;

*************** create_tidscan_path(PlannerInfo *root, R
*** 912,928 ****
   * Note that we must handle subpaths = NIL, representing a dummy access path.
   */
  AppendPath *
! create_append_path(RelOptInfo *rel, List *subpaths)
  {
      AppendPath *pathnode = makeNode(AppendPath);
      ListCell   *l;

      pathnode->path.pathtype = T_Append;
      pathnode->path.parent = rel;
      pathnode->path.pathkeys = NIL;        /* result is always considered
                                           * unsorted */
-     pathnode->path.required_outer = NULL; /* updated below */
-     pathnode->path.param_clauses = NIL;    /* XXX see below */
      pathnode->subpaths = subpaths;

      /*
--- 905,921 ----
   * Note that we must handle subpaths = NIL, representing a dummy access path.
   */
  AppendPath *
! create_append_path(RelOptInfo *rel, List *subpaths, Relids required_outer)
  {
      AppendPath *pathnode = makeNode(AppendPath);
      ListCell   *l;

      pathnode->path.pathtype = T_Append;
      pathnode->path.parent = rel;
+     pathnode->path.param_info = get_appendrel_parampathinfo(rel,
+                                                             required_outer);
      pathnode->path.pathkeys = NIL;        /* result is always considered
                                           * unsorted */
      pathnode->subpaths = subpaths;

      /*
*************** create_append_path(RelOptInfo *rel, List
*** 932,949 ****
       * nothing extra for the Append itself, which perhaps is too optimistic,
       * but since it doesn't do any selection or projection, it is a pretty
       * cheap node.  If you change this, see also make_append().
-      *
-      * We also compute the correct required_outer set, namely the union of
-      * the input paths' requirements.
-      *
-      * XXX We should also compute a proper param_clauses list, but that
-      * will require identifying which joinclauses are enforced by all the
-      * subplans, as well as locating the original parent RestrictInfo from
-      * which they were generated.  For the moment we punt and leave the list
-      * as NIL.  This will result in uselessly rechecking such joinclauses
-      * at the parameter-supplying nestloop join, which is slightly annoying,
-      * as well as overestimating the sizes of any intermediate joins, which
-      * is significantly more annoying.
       */
      pathnode->path.rows = 0;
      pathnode->path.startup_cost = 0;
--- 925,930 ----
*************** create_append_path(RelOptInfo *rel, List
*** 958,966 ****
              pathnode->path.startup_cost = subpath->startup_cost;
          pathnode->path.total_cost += subpath->total_cost;

!         pathnode->path.required_outer =
!             bms_add_members(pathnode->path.required_outer,
!                             subpath->required_outer);
      }

      return pathnode;
--- 939,946 ----
              pathnode->path.startup_cost = subpath->startup_cost;
          pathnode->path.total_cost += subpath->total_cost;

!         /* All child paths must have same parameterization */
!         Assert(bms_equal(PATH_PARAMRELS(subpath), required_outer));
      }

      return pathnode;
*************** MergeAppendPath *
*** 975,981 ****
  create_merge_append_path(PlannerInfo *root,
                           RelOptInfo *rel,
                           List *subpaths,
!                          List *pathkeys)
  {
      MergeAppendPath *pathnode = makeNode(MergeAppendPath);
      Cost        input_startup_cost;
--- 955,962 ----
  create_merge_append_path(PlannerInfo *root,
                           RelOptInfo *rel,
                           List *subpaths,
!                          List *pathkeys,
!                          Relids required_outer)
  {
      MergeAppendPath *pathnode = makeNode(MergeAppendPath);
      Cost        input_startup_cost;
*************** create_merge_append_path(PlannerInfo *ro
*** 984,1029 ****

      pathnode->path.pathtype = T_MergeAppend;
      pathnode->path.parent = rel;
      pathnode->path.pathkeys = pathkeys;
-     pathnode->path.required_outer = NULL; /* updated below */
-     pathnode->path.param_clauses = NIL;    /* XXX see below */
      pathnode->subpaths = subpaths;

      /*
       * Apply query-wide LIMIT if known and path is for sole base relation.
!      * Finding out the latter at this low level is a bit klugy.
       */
!     pathnode->limit_tuples = root->limit_tuples;
!     if (pathnode->limit_tuples >= 0)
!     {
!         Index        rti;
!
!         for (rti = 1; rti < root->simple_rel_array_size; rti++)
!         {
!             RelOptInfo *brel = root->simple_rel_array[rti];
!
!             if (brel == NULL)
!                 continue;
!
!             /* ignore RTEs that are "other rels" */
!             if (brel->reloptkind != RELOPT_BASEREL)
!                 continue;
!
!             if (brel != rel)
!             {
!                 /* Oops, it's a join query */
!                 pathnode->limit_tuples = -1.0;
!                 break;
!             }
!         }
!     }

      /*
!      * Add up the sizes and costs of the input paths, and also compute the
!      * real required_outer value.
!      *
!      * XXX as in create_append_path(), we should compute param_clauses but
!      * it will require more work.
       */
      pathnode->path.rows = 0;
      input_startup_cost = 0;
--- 965,986 ----

      pathnode->path.pathtype = T_MergeAppend;
      pathnode->path.parent = rel;
+     pathnode->path.param_info = get_appendrel_parampathinfo(rel,
+                                                             required_outer);
      pathnode->path.pathkeys = pathkeys;
      pathnode->subpaths = subpaths;

      /*
       * Apply query-wide LIMIT if known and path is for sole base relation.
!      * (Handling this at this low level is a bit klugy.)
       */
!     if (bms_equal(rel->relids, root->all_baserels))
!         pathnode->limit_tuples = root->limit_tuples;
!     else
!         pathnode->limit_tuples = -1.0;

      /*
!      * Add up the sizes and costs of the input paths.
       */
      pathnode->path.rows = 0;
      input_startup_cost = 0;
*************** create_merge_append_path(PlannerInfo *ro
*** 1058,1066 ****
              input_total_cost += sort_path.total_cost;
          }

!         pathnode->path.required_outer =
!             bms_add_members(pathnode->path.required_outer,
!                             subpath->required_outer);
      }

      /* Now we can compute total costs of the MergeAppend */
--- 1015,1022 ----
              input_total_cost += sort_path.total_cost;
          }

!         /* All child paths must have same parameterization */
!         Assert(bms_equal(PATH_PARAMRELS(subpath), required_outer));
      }

      /* Now we can compute total costs of the MergeAppend */
*************** create_result_path(List *quals)
*** 1084,1092 ****

      pathnode->path.pathtype = T_Result;
      pathnode->path.parent = NULL;
      pathnode->path.pathkeys = NIL;
-     pathnode->path.required_outer = NULL;
-     pathnode->path.param_clauses = NIL;
      pathnode->quals = quals;

      /* Hardly worth defining a cost_result() function ... just do it */
--- 1040,1047 ----

      pathnode->path.pathtype = T_Result;
      pathnode->path.parent = NULL;
+     pathnode->path.param_info = NULL;
      pathnode->path.pathkeys = NIL;
      pathnode->quals = quals;

      /* Hardly worth defining a cost_result() function ... just do it */
*************** create_material_path(RelOptInfo *rel, Pa
*** 1114,1124 ****
  {
      MaterialPath *pathnode = makeNode(MaterialPath);

      pathnode->path.pathtype = T_Material;
      pathnode->path.parent = rel;
      pathnode->path.pathkeys = subpath->pathkeys;
-     pathnode->path.required_outer = subpath->required_outer;
-     pathnode->path.param_clauses = subpath->param_clauses;

      pathnode->subpath = subpath;

--- 1069,1080 ----
  {
      MaterialPath *pathnode = makeNode(MaterialPath);

+     Assert(subpath->parent == rel);
+
      pathnode->path.pathtype = T_Material;
      pathnode->path.parent = rel;
+     pathnode->path.param_info = subpath->param_info;
      pathnode->path.pathkeys = subpath->pathkeys;

      pathnode->subpath = subpath;

*************** create_unique_path(PlannerInfo *root, Re
*** 1159,1164 ****
--- 1115,1121 ----

      /* Caller made a mistake if subpath isn't cheapest_total ... */
      Assert(subpath == rel->cheapest_total_path);
+     Assert(subpath->parent == rel);
      /* ... or if SpecialJoinInfo is the wrong one */
      Assert(sjinfo->jointype == JOIN_SEMI);
      Assert(bms_equal(rel->relids, sjinfo->syn_righthand));
*************** create_unique_path(PlannerInfo *root, Re
*** 1325,1338 ****

      pathnode->path.pathtype = T_Unique;
      pathnode->path.parent = rel;

      /*
       * Assume the output is unsorted, since we don't necessarily have pathkeys
       * to represent it.  (This might get overridden below.)
       */
      pathnode->path.pathkeys = NIL;
-     pathnode->path.required_outer = subpath->required_outer;
-     pathnode->path.param_clauses = subpath->param_clauses;

      pathnode->subpath = subpath;
      pathnode->in_operators = in_operators;
--- 1282,1294 ----

      pathnode->path.pathtype = T_Unique;
      pathnode->path.parent = rel;
+     pathnode->path.param_info = subpath->param_info;

      /*
       * Assume the output is unsorted, since we don't necessarily have pathkeys
       * to represent it.  (This might get overridden below.)
       */
      pathnode->path.pathkeys = NIL;

      pathnode->subpath = subpath;
      pathnode->in_operators = in_operators;
*************** distinct_col_search(int colno, List *col
*** 1661,1677 ****
   *      returning the pathnode.
   */
  Path *
! create_subqueryscan_path(RelOptInfo *rel, List *pathkeys)
  {
      Path       *pathnode = makeNode(Path);

      pathnode->pathtype = T_SubqueryScan;
      pathnode->parent = rel;
      pathnode->pathkeys = pathkeys;
-     pathnode->required_outer = NULL;
-     pathnode->param_clauses = NIL;

!     cost_subqueryscan(pathnode, rel);

      return pathnode;
  }
--- 1617,1634 ----
   *      returning the pathnode.
   */
  Path *
! create_subqueryscan_path(PlannerInfo *root, RelOptInfo *rel,
!                          List *pathkeys, Relids required_outer)
  {
      Path       *pathnode = makeNode(Path);

      pathnode->pathtype = T_SubqueryScan;
      pathnode->parent = rel;
+     pathnode->param_info = get_baserel_parampathinfo(root, rel,
+                                                      required_outer);
      pathnode->pathkeys = pathkeys;

!     cost_subqueryscan(pathnode, root, rel, pathnode->param_info);

      return pathnode;
  }
*************** create_functionscan_path(PlannerInfo *ro
*** 1688,1696 ****

      pathnode->pathtype = T_FunctionScan;
      pathnode->parent = rel;
      pathnode->pathkeys = NIL;    /* for now, assume unordered result */
-     pathnode->required_outer = NULL;
-     pathnode->param_clauses = NIL;

      cost_functionscan(pathnode, root, rel);

--- 1645,1652 ----

      pathnode->pathtype = T_FunctionScan;
      pathnode->parent = rel;
+     pathnode->param_info = NULL;    /* never parameterized at present */
      pathnode->pathkeys = NIL;    /* for now, assume unordered result */

      cost_functionscan(pathnode, root, rel);

*************** create_valuesscan_path(PlannerInfo *root
*** 1709,1717 ****

      pathnode->pathtype = T_ValuesScan;
      pathnode->parent = rel;
      pathnode->pathkeys = NIL;    /* result is always unordered */
-     pathnode->required_outer = NULL;
-     pathnode->param_clauses = NIL;

      cost_valuesscan(pathnode, root, rel);

--- 1665,1672 ----

      pathnode->pathtype = T_ValuesScan;
      pathnode->parent = rel;
+     pathnode->param_info = NULL;    /* never parameterized at present */
      pathnode->pathkeys = NIL;    /* result is always unordered */

      cost_valuesscan(pathnode, root, rel);

*************** create_ctescan_path(PlannerInfo *root, R
*** 1730,1738 ****

      pathnode->pathtype = T_CteScan;
      pathnode->parent = rel;
      pathnode->pathkeys = NIL;    /* XXX for now, result is always unordered */
-     pathnode->required_outer = NULL;
-     pathnode->param_clauses = NIL;

      cost_ctescan(pathnode, root, rel);

--- 1685,1692 ----

      pathnode->pathtype = T_CteScan;
      pathnode->parent = rel;
+     pathnode->param_info = NULL;    /* never parameterized at present */
      pathnode->pathkeys = NIL;    /* XXX for now, result is always unordered */

      cost_ctescan(pathnode, root, rel);

*************** create_worktablescan_path(PlannerInfo *r
*** 1751,1759 ****

      pathnode->pathtype = T_WorkTableScan;
      pathnode->parent = rel;
      pathnode->pathkeys = NIL;    /* result is always unordered */
-     pathnode->required_outer = NULL;
-     pathnode->param_clauses = NIL;

      /* Cost is the same as for a regular CTE scan */
      cost_ctescan(pathnode, root, rel);
--- 1705,1712 ----

      pathnode->pathtype = T_WorkTableScan;
      pathnode->parent = rel;
+     pathnode->param_info = NULL;    /* never parameterized at present */
      pathnode->pathkeys = NIL;    /* result is always unordered */

      /* Cost is the same as for a regular CTE scan */
      cost_ctescan(pathnode, root, rel);
*************** ForeignPath *
*** 1775,1793 ****
  create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel,
                          double rows, Cost startup_cost, Cost total_cost,
                          List *pathkeys,
!                         Relids required_outer, List *param_clauses,
                          List *fdw_private)
  {
      ForeignPath *pathnode = makeNode(ForeignPath);

      pathnode->path.pathtype = T_ForeignScan;
      pathnode->path.parent = rel;
      pathnode->path.rows = rows;
      pathnode->path.startup_cost = startup_cost;
      pathnode->path.total_cost = total_cost;
      pathnode->path.pathkeys = pathkeys;
-     pathnode->path.required_outer = required_outer;
-     pathnode->path.param_clauses = param_clauses;

      pathnode->fdw_private = fdw_private;

--- 1728,1746 ----
  create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel,
                          double rows, Cost startup_cost, Cost total_cost,
                          List *pathkeys,
!                         Relids required_outer,
                          List *fdw_private)
  {
      ForeignPath *pathnode = makeNode(ForeignPath);

      pathnode->path.pathtype = T_ForeignScan;
      pathnode->path.parent = rel;
+     pathnode->path.param_info = get_baserel_parampathinfo(root, rel,
+                                                           required_outer);
      pathnode->path.rows = rows;
      pathnode->path.startup_cost = startup_cost;
      pathnode->path.total_cost = total_cost;
      pathnode->path.pathkeys = pathkeys;

      pathnode->fdw_private = fdw_private;

*************** create_foreignscan_path(PlannerInfo *roo
*** 1803,1819 ****
  Relids
  calc_nestloop_required_outer(Path *outer_path, Path *inner_path)
  {
      Relids    required_outer;

      /* inner_path can require rels from outer path, but not vice versa */
!     Assert(!bms_overlap(outer_path->required_outer,
!                         inner_path->parent->relids));
      /* easy case if inner path is not parameterized */
!     if (!inner_path->required_outer)
!         return bms_copy(outer_path->required_outer);
      /* else, form the union ... */
!     required_outer = bms_union(outer_path->required_outer,
!                                inner_path->required_outer);
      /* ... and remove any mention of now-satisfied outer rels */
      required_outer = bms_del_members(required_outer,
                                       outer_path->parent->relids);
--- 1756,1772 ----
  Relids
  calc_nestloop_required_outer(Path *outer_path, Path *inner_path)
  {
+     Relids    outer_paramrels = PATH_PARAMRELS(outer_path);
+     Relids    inner_paramrels = PATH_PARAMRELS(inner_path);
      Relids    required_outer;

      /* inner_path can require rels from outer path, but not vice versa */
!     Assert(!bms_overlap(outer_paramrels, inner_path->parent->relids));
      /* easy case if inner path is not parameterized */
!     if (!inner_paramrels)
!         return bms_copy(outer_paramrels);
      /* else, form the union ... */
!     required_outer = bms_union(outer_paramrels, inner_paramrels);
      /* ... and remove any mention of now-satisfied outer rels */
      required_outer = bms_del_members(required_outer,
                                       outer_path->parent->relids);
*************** calc_nestloop_required_outer(Path *outer
*** 1835,1850 ****
  Relids
  calc_non_nestloop_required_outer(Path *outer_path, Path *inner_path)
  {
      Relids    required_outer;

      /* neither path can require rels from the other */
!     Assert(!bms_overlap(outer_path->required_outer,
!                         inner_path->parent->relids));
!     Assert(!bms_overlap(inner_path->required_outer,
!                         outer_path->parent->relids));
      /* form the union ... */
!     required_outer = bms_union(outer_path->required_outer,
!                                inner_path->required_outer);
      /* we do not need an explicit test for empty; bms_union gets it right */
      return required_outer;
  }
--- 1788,1802 ----
  Relids
  calc_non_nestloop_required_outer(Path *outer_path, Path *inner_path)
  {
+     Relids    outer_paramrels = PATH_PARAMRELS(outer_path);
+     Relids    inner_paramrels = PATH_PARAMRELS(inner_path);
      Relids    required_outer;

      /* neither path can require rels from the other */
!     Assert(!bms_overlap(outer_paramrels, inner_path->parent->relids));
!     Assert(!bms_overlap(inner_paramrels, outer_path->parent->relids));
      /* form the union ... */
!     required_outer = bms_union(outer_paramrels, inner_paramrels);
      /* we do not need an explicit test for empty; bms_union gets it right */
      return required_outer;
  }
*************** create_nestloop_path(PlannerInfo *root,
*** 1881,1914 ****
                       Relids required_outer)
  {
      NestPath   *pathnode = makeNode(NestPath);

!     pathnode->path.pathtype = T_NestLoop;
!     pathnode->path.parent = joinrel;
!     pathnode->path.pathkeys = pathkeys;
!     pathnode->path.required_outer = required_outer;
!     if (pathnode->path.required_outer)
      {
!         /* Identify parameter clauses not yet applied here */
!         List       *jclauses;
          ListCell   *lc;

!         /* LHS clauses could not be satisfied here */
!         jclauses = list_copy(outer_path->param_clauses);
!         foreach(lc, inner_path->param_clauses)
          {
!             RestrictInfo   *rinfo = (RestrictInfo *) lfirst(lc);

!             if (!bms_is_subset(rinfo->clause_relids, joinrel->relids))
                  jclauses = lappend(jclauses, rinfo);
          }
!         pathnode->path.param_clauses = jclauses;
      }
!     else
!         pathnode->path.param_clauses = NIL;
      pathnode->jointype = jointype;
      pathnode->outerjoinpath = outer_path;
      pathnode->innerjoinpath = inner_path;
!     pathnode->joinrestrictinfo = restrict_clauses;

      final_cost_nestloop(root, pathnode, workspace, sjinfo, semifactors);

--- 1833,1884 ----
                       Relids required_outer)
  {
      NestPath   *pathnode = makeNode(NestPath);
+     Relids        innerparamrelids = PATH_PARAMRELS(inner_path);
+     List       *param_clauses;

!     /*
!      * If the inner path is parameterized by the outer, we must drop any
!      * restrict_clauses that are due to be pushed into the inner path.  We
!      * have to do this now, rather than postpone the work till createplan
!      * time, because the restrict_clauses list can affect the size and cost
!      * estimates for this path.
!      */
!     if (bms_overlap(innerparamrelids, outer_path->parent->relids))
      {
!         Relids        paramcontext = bms_union(inner_path->parent->relids,
!                                              innerparamrelids);
!         List       *jclauses = NIL;
          ListCell   *lc;

!         foreach(lc, restrict_clauses)
          {
!             RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);

!             if (!join_clause_is_parameterizable_within(rinfo,
!                                                        innerparamrelids,
!                                                        paramcontext))
                  jclauses = lappend(jclauses, rinfo);
          }
!         restrict_clauses = jclauses;
      }
!
!     pathnode->path.pathtype = T_NestLoop;
!     pathnode->path.parent = joinrel;
!     pathnode->path.param_info =
!         get_joinrel_parampathinfo(root,
!                                   joinrel,
!                                   outer_path,
!                                   inner_path,
!                                   sjinfo,
!                                   restrict_clauses,
!                                   required_outer,
!                                   ¶m_clauses);
!     pathnode->path.pathkeys = pathkeys;
      pathnode->jointype = jointype;
      pathnode->outerjoinpath = outer_path;
      pathnode->innerjoinpath = inner_path;
!     pathnode->joinrestrictinfo = list_concat(param_clauses,
!                                              restrict_clauses);

      final_cost_nestloop(root, pathnode, workspace, sjinfo, semifactors);

*************** create_mergejoin_path(PlannerInfo *root,
*** 1950,1967 ****
                        List *innersortkeys)
  {
      MergePath  *pathnode = makeNode(MergePath);

      pathnode->jpath.path.pathtype = T_MergeJoin;
      pathnode->jpath.path.parent = joinrel;
      pathnode->jpath.path.pathkeys = pathkeys;
-     pathnode->jpath.path.required_outer = required_outer;
-     pathnode->jpath.path.param_clauses =
-         list_concat(list_copy(outer_path->param_clauses),
-                     inner_path->param_clauses);
      pathnode->jpath.jointype = jointype;
      pathnode->jpath.outerjoinpath = outer_path;
      pathnode->jpath.innerjoinpath = inner_path;
!     pathnode->jpath.joinrestrictinfo = restrict_clauses;
      pathnode->path_mergeclauses = mergeclauses;
      pathnode->outersortkeys = outersortkeys;
      pathnode->innersortkeys = innersortkeys;
--- 1920,1944 ----
                        List *innersortkeys)
  {
      MergePath  *pathnode = makeNode(MergePath);
+     List       *param_clauses;

      pathnode->jpath.path.pathtype = T_MergeJoin;
      pathnode->jpath.path.parent = joinrel;
+     pathnode->jpath.path.param_info =
+         get_joinrel_parampathinfo(root,
+                                   joinrel,
+                                   outer_path,
+                                   inner_path,
+                                   sjinfo,
+                                   restrict_clauses,
+                                   required_outer,
+                                   ¶m_clauses);
      pathnode->jpath.path.pathkeys = pathkeys;
      pathnode->jpath.jointype = jointype;
      pathnode->jpath.outerjoinpath = outer_path;
      pathnode->jpath.innerjoinpath = inner_path;
!     pathnode->jpath.joinrestrictinfo = list_concat(param_clauses,
!                                                    restrict_clauses);
      pathnode->path_mergeclauses = mergeclauses;
      pathnode->outersortkeys = outersortkeys;
      pathnode->innersortkeys = innersortkeys;
*************** create_hashjoin_path(PlannerInfo *root,
*** 2002,2010 ****
--- 1979,1997 ----
                       List *hashclauses)
  {
      HashPath   *pathnode = makeNode(HashPath);
+     List       *param_clauses;

      pathnode->jpath.path.pathtype = T_HashJoin;
      pathnode->jpath.path.parent = joinrel;
+     pathnode->jpath.path.param_info =
+         get_joinrel_parampathinfo(root,
+                                   joinrel,
+                                   outer_path,
+                                   inner_path,
+                                   sjinfo,
+                                   restrict_clauses,
+                                   required_outer,
+                                   ¶m_clauses);

      /*
       * A hashjoin never has pathkeys, since its output ordering is
*************** create_hashjoin_path(PlannerInfo *root,
*** 2018,2031 ****
       * outer rel than it does now.)
       */
      pathnode->jpath.path.pathkeys = NIL;
-     pathnode->jpath.path.required_outer = required_outer;
-     pathnode->jpath.path.param_clauses =
-         list_concat(list_copy(outer_path->param_clauses),
-                     inner_path->param_clauses);
      pathnode->jpath.jointype = jointype;
      pathnode->jpath.outerjoinpath = outer_path;
      pathnode->jpath.innerjoinpath = inner_path;
!     pathnode->jpath.joinrestrictinfo = restrict_clauses;
      pathnode->path_hashclauses = hashclauses;
      /* final_cost_hashjoin will fill in pathnode->num_batches */

--- 2005,2015 ----
       * outer rel than it does now.)
       */
      pathnode->jpath.path.pathkeys = NIL;
      pathnode->jpath.jointype = jointype;
      pathnode->jpath.outerjoinpath = outer_path;
      pathnode->jpath.innerjoinpath = inner_path;
!     pathnode->jpath.joinrestrictinfo = list_concat(param_clauses,
!                                                    restrict_clauses);
      pathnode->path_hashclauses = hashclauses;
      /* final_cost_hashjoin will fill in pathnode->num_batches */

*************** create_hashjoin_path(PlannerInfo *root,
*** 2033,2035 ****
--- 2017,2087 ----

      return pathnode;
  }
+
+ /*
+  * reparameterize_path
+  *        Attempt to modify a Path to have greater parameterization
+  *
+  * We use this to attempt to bring all child paths of an appendrel to the
+  * same parameterization level, ensuring that they all enforce the same set
+  * of join quals (and thus that that parameterization can be attributed to
+  * an append path built from such paths).  Currently, only a few path types
+  * are supported here, though more could be added at need.  We return NULL
+  * if we can't reparameterize the given path.
+  *
+  * Note: we intentionally do not pass created paths to add_path(); it would
+  * possibly try to delete them on the grounds of being cost-inferior to the
+  * paths they were made from, and we don't want that.  Paths made here are
+  * not necessarily of general-purpose usefulness, but they can be useful
+  * as members of an append path.
+  */
+ Path *
+ reparameterize_path(PlannerInfo *root, Path *path,
+                     Relids required_outer,
+                     double loop_count)
+ {
+     RelOptInfo *rel = path->parent;
+
+     /* Can only increase, not decrease, path's parameterization */
+     if (!bms_is_subset(PATH_PARAMRELS(path), required_outer))
+         return NULL;
+     switch (path->pathtype)
+     {
+         case T_SeqScan:
+             return create_seqscan_path(root, rel, required_outer);
+         case T_IndexScan:
+         case T_IndexOnlyScan:
+         {
+             IndexPath  *ipath = (IndexPath *) path;
+             IndexPath  *newpath = makeNode(IndexPath);
+
+             /*
+              * We can't use create_index_path directly, and would not want to
+              * because it would re-compute the indexqual conditions which is
+              * wasted effort.  Instead we hack things a bit: flat-copy the
+              * path node, revise its param_info, and redo the cost estimate.
+              */
+             memcpy(newpath, ipath, sizeof(IndexPath));
+             newpath->path.param_info =
+                 get_baserel_parampathinfo(root, rel, required_outer);
+             cost_index(newpath, root, loop_count);
+             return (Path *) newpath;
+         }
+         case T_BitmapHeapScan:
+         {
+             BitmapHeapPath *bpath = (BitmapHeapPath *) path;
+
+             return (Path *) create_bitmap_heap_path(root,
+                                                     rel,
+                                                     bpath->bitmapqual,
+                                                     required_outer,
+                                                     loop_count);
+         }
+         case T_SubqueryScan:
+             return create_subqueryscan_path(root, rel, path->pathkeys,
+                                             required_outer);
+         default:
+             break;
+     }
+     return NULL;
+ }
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index cee092a8810102fdf48023e180739cd8dbc3d81c..f8d6df8a9390c36705d30c4998075efc9f0fdab6 100644
*** a/src/backend/optimizer/util/relnode.c
--- b/src/backend/optimizer/util/relnode.c
***************
*** 19,24 ****
--- 19,25 ----
  #include "optimizer/paths.h"
  #include "optimizer/placeholder.h"
  #include "optimizer/plancat.h"
+ #include "optimizer/restrictinfo.h"
  #include "utils/hsearch.h"


*************** build_simple_rel(PlannerInfo *root, int
*** 100,105 ****
--- 101,107 ----
      rel->width = 0;
      rel->reltargetlist = NIL;
      rel->pathlist = NIL;
+     rel->ppilist = NIL;
      rel->cheapest_startup_path = NULL;
      rel->cheapest_total_path = NULL;
      rel->cheapest_unique_path = NULL;
*************** build_join_rel(PlannerInfo *root,
*** 352,357 ****
--- 354,360 ----
      joinrel->width = 0;
      joinrel->reltargetlist = NIL;
      joinrel->pathlist = NIL;
+     joinrel->ppilist = NIL;
      joinrel->cheapest_startup_path = NULL;
      joinrel->cheapest_total_path = NULL;
      joinrel->cheapest_unique_path = NULL;
*************** build_joinrel_restrictlist(PlannerInfo *
*** 574,581 ****
       */
      result = list_concat(result,
                           generate_join_implied_equalities(root,
!                                                           joinrel,
!                                                           outer_rel,
                                                            inner_rel));

      return result;
--- 577,584 ----
       */
      result = list_concat(result,
                           generate_join_implied_equalities(root,
!                                                           joinrel->relids,
!                                                           outer_rel->relids,
                                                            inner_rel));

      return result;
*************** subbuild_joinrel_joinlist(RelOptInfo *jo
*** 667,669 ****
--- 670,956 ----

      return new_joininfo;
  }
+
+
+ /*
+  * find_childrel_appendrelinfo
+  *        Get the AppendRelInfo associated with an appendrel child rel.
+  *
+  * This search could be eliminated by storing a link in child RelOptInfos,
+  * but for now it doesn't seem performance-critical.
+  */
+ AppendRelInfo *
+ find_childrel_appendrelinfo(PlannerInfo *root, RelOptInfo *rel)
+ {
+     Index        relid = rel->relid;
+     ListCell   *lc;
+
+     /* Should only be called on child rels */
+     Assert(rel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+
+     foreach(lc, root->append_rel_list)
+     {
+         AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc);
+
+         if (appinfo->child_relid == relid)
+             return appinfo;
+     }
+     /* should have found the entry ... */
+     elog(ERROR, "child rel %d not found in append_rel_list", relid);
+     return NULL;                /* not reached */
+ }
+
+
+ /*
+  * get_baserel_parampathinfo
+  *        Get the ParamPathInfo for a parameterized path for a base relation,
+  *        constructing one if we don't have one already.
+  *
+  * This centralizes estimating the rowcounts for parameterized paths.
+  * We need to cache those to be sure we use the same rowcount for all paths
+  * of the same parameterization for a given rel.  This is also a convenient
+  * place to determine which join clauses the parameterized path will be
+  * responsible for evaluating.
+  */
+ ParamPathInfo *
+ get_baserel_parampathinfo(PlannerInfo *root, RelOptInfo *baserel,
+                           Relids required_outer)
+ {
+     ParamPathInfo *ppi;
+     Relids        joinrelids;
+     List       *pclauses;
+     double        rows;
+     ListCell   *lc;
+
+     /* Unparameterized paths have no ParamPathInfo */
+     if (bms_is_empty(required_outer))
+         return NULL;
+
+     Assert(!bms_overlap(baserel->relids, required_outer));
+
+     /* If we already have a PPI for this parameterization, just return it */
+     foreach(lc, baserel->ppilist)
+     {
+         ppi = (ParamPathInfo *) lfirst(lc);
+         if (bms_equal(ppi->ppi_outer, required_outer))
+             return ppi;
+     }
+
+     /*
+      * Identify all relevant joinclauses for this parameterization.
+      */
+     joinrelids = bms_union(baserel->relids, required_outer);
+     pclauses = NIL;
+     foreach(lc, baserel->joininfo)
+     {
+         RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+         if (join_clause_is_parameterizable_within(rinfo,
+                                                   required_outer,
+                                                   joinrelids))
+             pclauses = lappend(pclauses, rinfo);
+     }
+
+     /*
+      * Add in joinclauses generated by EquivalenceClasses, too.  (These
+      * necessarily satisfy join_clause_is_parameterizable_within.)
+      */
+     pclauses = list_concat(pclauses,
+                            generate_join_implied_equalities(root,
+                                                             joinrelids,
+                                                             required_outer,
+                                                             baserel));
+
+     /* Estimate the number of rows returned by the parameterized scan */
+     rows = get_parameterized_baserel_size(root, baserel, pclauses);
+
+     /* And now we can build the ParamPathInfo */
+     ppi = makeNode(ParamPathInfo);
+     ppi->ppi_outer = required_outer;
+     ppi->ppi_rows = rows;
+     ppi->ppi_clauses = pclauses;
+     baserel->ppilist = lappend(baserel->ppilist, ppi);
+
+     return ppi;
+ }
+
+ /*
+  * get_joinrel_parampathinfo
+  *        Get the ParamPathInfo for a parameterized path for a join relation,
+  *        constructing one if we don't have one already.
+  *
+  * This centralizes estimating the rowcounts for parameterized paths.
+  * We need to cache those to be sure we use the same rowcount for all paths
+  * of the same parameterization for a given rel.  This is also a convenient
+  * place to determine which join clauses the parameterized path will be
+  * responsible for evaluating.
+  *
+  * outer_path and inner_path are a pair of input paths that can be used to
+  * construct the join, and restrict_clauses is the list of regular join
+  * clauses (including clauses derived from EquivalenceClasses) that must be
+  * applied at the join node when using these inputs.
+  *
+  * Unlike the situation for base rels, the set of extra parameterized join
+  * clauses to be enforced at a join varies with the selected pair of input
+  * rels, so we must calculate that and pass it back, even if we already have
+  * a matching ParamPathInfo.
+  *
+  * Note: when considering a nestloop join, the caller must have removed from
+  * restrict_clauses any parameterizable clauses that are themselves scheduled
+  * to be pushed into the right-hand path.  We do not do this here since it's
+  * unnecessary for other join types.
+  */
+ ParamPathInfo *
+ get_joinrel_parampathinfo(PlannerInfo *root, RelOptInfo *joinrel,
+                           Path *outer_path,
+                           Path *inner_path,
+                           SpecialJoinInfo *sjinfo,
+                           List *restrict_clauses,
+                           Relids required_outer,
+                           List **param_clauses)
+ {
+     ParamPathInfo *ppi;
+     Relids        joinrelids;
+     Relids        outerpcontext;
+     Relids        innerpcontext;
+     List       *pclauses;
+     List       *eclauses;
+     double        rows;
+     ListCell   *lc;
+
+     /* Unparameterized paths have no ParamPathInfo or extra join clauses */
+     if (bms_is_empty(required_outer))
+     {
+         *param_clauses = NIL;
+         return NULL;
+     }
+
+     Assert(!bms_overlap(joinrel->relids, required_outer));
+
+     /*
+      * Identify relevant joinclauses for this parameterization, but ignore any
+      * that should have been applied in one of the input paths.
+      */
+     joinrelids = bms_union(joinrel->relids, required_outer);
+     if (outer_path->param_info)
+         outerpcontext = bms_union(outer_path->parent->relids,
+                                   PATH_PARAMRELS(outer_path));
+     else
+         outerpcontext = NULL;        /* outer path does not accept parameters */
+     if (inner_path->param_info)
+         innerpcontext = bms_union(inner_path->parent->relids,
+                                   PATH_PARAMRELS(inner_path));
+     else
+         innerpcontext = NULL;        /* inner path does not accept parameters */
+
+     pclauses = NIL;
+     foreach(lc, joinrel->joininfo)
+     {
+         RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+         if (join_clause_is_parameterizable_within(rinfo,
+                                                   required_outer,
+                                                   joinrelids) &&
+             !join_clause_is_parameterizable_within(rinfo,
+                                                    PATH_PARAMRELS(outer_path),
+                                                    outerpcontext) &&
+             !join_clause_is_parameterizable_within(rinfo,
+                                                    PATH_PARAMRELS(inner_path),
+                                                    innerpcontext))
+             pclauses = lappend(pclauses, rinfo);
+     }
+
+     /* Consider joinclauses generated by EquivalenceClasses, too */
+     eclauses = generate_join_implied_equalities(root,
+                                                 joinrelids,
+                                                 required_outer,
+                                                 joinrel);
+     /* We only want ones that weren't enforced at lower levels */
+     foreach(lc, eclauses)
+     {
+         RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+         Assert(join_clause_is_parameterizable_within(rinfo,
+                                                      required_outer,
+                                                      joinrelids));
+         if (!join_clause_is_parameterizable_within(rinfo,
+                                                    PATH_PARAMRELS(outer_path),
+                                                    outerpcontext) &&
+             !join_clause_is_parameterizable_within(rinfo,
+                                                    PATH_PARAMRELS(inner_path),
+                                                    innerpcontext))
+             pclauses = lappend(pclauses, rinfo);
+     }
+
+     *param_clauses = pclauses;
+
+     /* If we already have a PPI for this parameterization, just return it */
+     foreach(lc, joinrel->ppilist)
+     {
+         ppi = (ParamPathInfo *) lfirst(lc);
+         if (bms_equal(ppi->ppi_outer, required_outer))
+             return ppi;
+     }
+
+     /* Estimate the number of rows returned by the parameterized join */
+     rows = get_parameterized_joinrel_size(root, joinrel,
+                                           outer_path->rows,
+                                           inner_path->rows,
+                                           sjinfo,
+                                           restrict_clauses,
+                                           pclauses);
+
+     /*
+      * And now we can build the ParamPathInfo.  No point in saving the
+      * input-pair-dependent clause list, though.
+      *
+      * Note: in GEQO mode, we'll be called in a temporary memory context, but
+      * the joinrel structure is there too, so no problem.
+      */
+     ppi = makeNode(ParamPathInfo);
+     ppi->ppi_outer = required_outer;
+     ppi->ppi_rows = rows;
+     ppi->ppi_clauses = NIL;
+     joinrel->ppilist = lappend(joinrel->ppilist, ppi);
+
+     return ppi;
+ }
+
+ /*
+  * get_appendrel_parampathinfo
+  *        Get the ParamPathInfo for a parameterized path for an append relation.
+  *
+  * For an append relation, the rowcount estimate will just be the sum of
+  * the estimates for its children.  However, we still need a ParamPathInfo
+  * to flag the fact that the path requires parameters.  So this just creates
+  * a suitable struct with zero ppi_rows (and no ppi_clauses either).
+  */
+ ParamPathInfo *
+ get_appendrel_parampathinfo(RelOptInfo *appendrel, Relids required_outer)
+ {
+     ParamPathInfo *ppi;
+     ListCell   *lc;
+
+     /* Unparameterized paths have no ParamPathInfo */
+     if (bms_is_empty(required_outer))
+         return NULL;
+
+     Assert(!bms_overlap(appendrel->relids, required_outer));
+
+     /* If we already have a PPI for this parameterization, just return it */
+     foreach(lc, appendrel->ppilist)
+     {
+         ppi = (ParamPathInfo *) lfirst(lc);
+         if (bms_equal(ppi->ppi_outer, required_outer))
+             return ppi;
+     }
+
+     /* Else build the ParamPathInfo */
+     ppi = makeNode(ParamPathInfo);
+     ppi->ppi_outer = required_outer;
+     ppi->ppi_rows = 0;
+     ppi->ppi_clauses = NIL;
+     appendrel->ppilist = lappend(appendrel->ppilist, ppi);
+
+     return ppi;
+ }
diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c
index 7d03d91f5d3b6f90de576021542fea9b21ca4122..b419dc91ae322b407d06f2387e9dcc374c5abf65 100644
*** a/src/backend/optimizer/util/restrictinfo.c
--- b/src/backend/optimizer/util/restrictinfo.c
*************** static Expr *make_sub_restrictinfos(Expr
*** 33,40 ****
                         bool pseudoconstant,
                         Relids required_relids,
                         Relids nullable_relids);
- static bool join_clause_is_redundant(RestrictInfo *rinfo,
-                          List *reference_list);


  /*
--- 33,38 ----
*************** extract_actual_join_clauses(List *restri
*** 620,691 ****


  /*
!  * select_nonredundant_join_clauses
!  *        Select the members of restrictinfo_list that are not redundant with
!  *        any member of reference_list.
   *
!  * Given a list of RestrictInfo clauses that are to be applied in a join,
!  * select the ones that are not redundant with any clause that's listed in
!  * reference_list.  This is used, for example, to avoid checking joinclauses
!  * again at a nestloop join when they've already been enforced by a
!  * parameterized inner path.
   *
!  * "Redundant" means either equal() or derived from the same EquivalenceClass.
!  * We have to check the latter because indxpath.c may select different derived
!  * clauses than were selected by generate_join_implied_equalities().
   *
!  * Note that we are *not* checking for local redundancies within the given
!  * restrictinfo_list; that should have been handled elsewhere.
   */
! List *
! select_nonredundant_join_clauses(List *restrictinfo_list,
!                                  List *reference_list)
  {
!     List       *result = NIL;
!     ListCell   *item;
!
!     /* Quick out if nothing could be removed */
!     if (reference_list == NIL)
!         return restrictinfo_list;
!
!     foreach(item, restrictinfo_list)
!     {
!         RestrictInfo *rinfo = (RestrictInfo *) lfirst(item);
!
!         /* drop it if redundant with any reference clause */
!         if (join_clause_is_redundant(rinfo, reference_list))
!             continue;

!         /* otherwise, add it to result list */
!         result = lappend(result, rinfo);
!     }

!     return result;
  }

  /*
!  * join_clause_is_redundant
!  *        Test whether rinfo is redundant with any clause in reference_list.
   */
! static bool
! join_clause_is_redundant(RestrictInfo *rinfo,
!                          List *reference_list)
  {
!     ListCell   *refitem;

!     foreach(refitem, reference_list)
!     {
!         RestrictInfo *refrinfo = (RestrictInfo *) lfirst(refitem);

!         /* always consider exact duplicates redundant */
!         if (equal(rinfo, refrinfo))
!             return true;

!         /* check if derived from same EquivalenceClass */
!         if (rinfo->parent_ec != NULL &&
!             rinfo->parent_ec == refrinfo->parent_ec)
!             return true;
!     }

!     return false;
  }
--- 618,714 ----


  /*
!  * join_clause_is_parameterizable_for
!  *        Test whether a join clause is a safe candidate for parameterization
!  *        of a scan on the target relation (currently always a base rel).
   *
!  * A parameterizable clause is one that can safely be evaluated at a rel below
!  * its normal semantic level (ie, its required_relids), if the values of
!  * variables that it would need from other rels are provided.
   *
!  * Currently we insist that the clause not be outerjoin_delayed and have
!  * empty nullable_relids.  That could probably be relaxed, but it will
!  * require study.  An important constraint here is that we must not change
!  * our minds about the parameterizability of a given clause as we move up
!  * the join tree (so for example it would not do to compare nullable_relids
!  * with currentcontext in join_clause_is_parameterizable_within).
   *
!  * We also insist that the clause actually refer to both the target base
!  * relation (that we intend to push it down to) and some other relation(s)
!  * (which will become the paramrels for the parameterized path for the
!  * target rel).  Requiring these sets to be nonempty is important to avoid
!  * unstable behavior with degenerate join clauses (compare the tests in
!  * join_clause_is_parameterizable_within).
!  *
!  * Note that clauses belonging to an outer join can only be pushed down into
!  * its nullable side, but that is not enforced here.  It will happen because
!  * indxpath.c doesn't try to select any parameterization clause that would
!  * be coming from an outer join that the target rel is on the outer side of.
   */
! bool
! join_clause_is_parameterizable_for(RestrictInfo *rinfo, Relids targetrelids)
  {
!     /* Clause must be safe to push around */
!     if (rinfo->outerjoin_delayed)
!         return false;
!     if (!bms_is_empty(rinfo->nullable_relids))
!         return false;

!     /* Must reference target rel and some other rel too */
!     if (!bms_overlap(rinfo->clause_relids, targetrelids))
!         return false;
!     if (bms_is_subset(rinfo->clause_relids, targetrelids))
!         return false;

!     return true;
  }

  /*
!  * join_clause_is_parameterizable_within
!  *        Test whether a join clause is a safe candidate for parameterization
!  *        and can be evaluated within the current context.
!  *
!  * paramrels: the relids of the parameterization's outer relations
!  * currentcontext: the union of paramrels and the relids of the
!  *        proposed evaluation location
!  *
!  * (The API would be a bit clearer if we passed the param relids and the
!  * location relids separately and did bms_union internally; but since most
!  * callers need to apply this function to multiple clauses, we make the caller
!  * perform the union.)
!  *
!  * Note: as a convenience for callers, this function is guaranteed to return
!  * "false" if either paramrels or currentcontext is NULL.
   */
! bool
! join_clause_is_parameterizable_within(RestrictInfo *rinfo,
!                                       Relids paramrels,
!                                       Relids currentcontext)
  {
!     /* Clause must be safe to push around */
!     if (rinfo->outerjoin_delayed)
!         return false;
!     if (!bms_is_empty(rinfo->nullable_relids))
!         return false;

!     /* Must be evaluatable in current context */
!     if (!bms_is_subset(rinfo->clause_relids, currentcontext))
!         return false;

!     /*
!      * These additional tests reject degenerate cases that would make it hard
!      * to identify a unique place in the parameterized join tree at which to
!      * evaluate the clause.  Instead, we treat degenerate clauses as not
!      * parameterizable, so they stay at their normal location in the tree.  We
!      * do this last since usually these will succeed if the earlier tests did.
!      */

!     /* Must be identifiably associated with the param rels ... */
!     if (!bms_overlap(rinfo->clause_relids, paramrels))
!         return false;
!     /* ... but be a join to something else too */
!     if (bms_is_subset(rinfo->clause_relids, paramrels))
!         return false;

!     return true;
  }
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index fdcc80edbb567f981b6e558b6f14052f06f28e95..1e16088b7ef738438f14b6118f3fd94b05461e40 100644
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
*************** typedef enum NodeTag
*** 212,217 ****
--- 212,218 ----
      T_PlannerGlobal,
      T_RelOptInfo,
      T_IndexOptInfo,
+     T_ParamPathInfo,
      T_Path,
      T_IndexPath,
      T_BitmapHeapPath,
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 6606e67055b160750241bf57f18929442b76fdca..8177b058e67bbbea392af07d41254831a804db1a 100644
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
*************** typedef struct PlannerInfo
*** 304,309 ****
--- 304,310 ----
   *                        ConvertRowtypeExpr representing a whole-row Var.
   *        pathlist - List of Path nodes, one for each potentially useful
   *                   method of generating the relation
+  *        ppilist - ParamPathInfo nodes for parameterized Paths, if any
   *        cheapest_startup_path - the pathlist member with lowest startup cost
   *                                (regardless of its ordering; but must be
   *                                 unparameterized)
*************** typedef struct RelOptInfo
*** 400,405 ****
--- 401,407 ----
      /* materialization information */
      List       *reltargetlist;    /* Vars to be output by scan of relation */
      List       *pathlist;        /* Path structures */
+     List       *ppilist;        /* ParamPathInfos used in pathlist */
      struct Path *cheapest_startup_path;
      struct Path *cheapest_total_path;
      struct Path *cheapest_unique_path;
*************** typedef struct PathKey
*** 628,633 ****
--- 630,660 ----
      bool        pk_nulls_first; /* do NULLs come before normal values? */
  } PathKey;

+
+ /*
+  * ParamPathInfo
+  *
+  * All parameterized paths for a given relation with given outer relids
+  * link to a single ParamPathInfo, which stores common information such as
+  * the estimated rowcount for this parameterization.  We do this partly to
+  * avoid recalculations, but mostly to ensure that the estimated rowcount
+  * is in fact the same for every such path.
+  *
+  * Note: ppi_clauses is only used in ParamPathInfos for base relation paths;
+  * in join cases it's NIL because the set of relevant clauses varies depending
+  * on how the join is formed.  The relevant clauses will appear in each
+  * parameterized join path's joinrestrictinfo list, instead.
+  */
+ typedef struct ParamPathInfo
+ {
+     NodeTag        type;
+
+     Relids        ppi_outer;        /* rels supplying parameters used by path */
+     double        ppi_rows;        /* estimated number of result tuples */
+     List       *ppi_clauses;    /* join clauses available from outer rels */
+ } ParamPathInfo;
+
+
  /*
   * Type "Path" is used as-is for sequential-scan paths, as well as some other
   * simple plan types that we don't need any extra information in the path for.
*************** typedef struct PathKey
*** 638,662 ****
   * the same Path type for multiple Plan types when there is no need to
   * distinguish the Plan type during path processing.
   *
   * "rows" is the same as parent->rows in simple paths, but in parameterized
   * paths and UniquePaths it can be less than parent->rows, reflecting the
   * fact that we've filtered by extra join conditions or removed duplicates.
   *
   * "pathkeys" is a List of PathKey nodes (see above), describing the sort
   * ordering of the path's output rows.
-  *
-  * "required_outer", if not NULL, contains the relids of one or more relations
-  * that must provide parameter values to each scan of this path, because the
-  * path relies on join clauses using those rels. That means this path can only
-  * be joined to those rels by means of nestloop joins with this path on the
-  * inside.  Note: for a normal unparameterized path, required_outer must be
-  * NULL, not an empty-but-not-null Bitmapset.
-  *
-  * "param_clauses" is a List of RestrictInfo nodes, containing the join
-  * clauses used by a parameterized path.  Ideally param_clauses should be NIL
-  * if and only if required_outer is NULL.  XXX for the moment, however, we do
-  * not compute param_clauses for Append and MergeAppend paths, so the list
-  * is inaccurate in those paths and possibly paths above them.
   */
  typedef struct Path
  {
--- 665,682 ----
   * the same Path type for multiple Plan types when there is no need to
   * distinguish the Plan type during path processing.
   *
+  * "param_info", if not NULL, links to a ParamPathInfo that identifies outer
+  * relation(s) that provide parameter values to each scan of this path.
+  * That means this path can only be joined to those rels by means of nestloop
+  * joins with this path on the inside, and this path is responsible for
+  * testing all joinclauses involving this rel and the specified outer rels.
+  *
   * "rows" is the same as parent->rows in simple paths, but in parameterized
   * paths and UniquePaths it can be less than parent->rows, reflecting the
   * fact that we've filtered by extra join conditions or removed duplicates.
   *
   * "pathkeys" is a List of PathKey nodes (see above), describing the sort
   * ordering of the path's output rows.
   */
  typedef struct Path
  {
*************** typedef struct Path
*** 665,670 ****
--- 685,691 ----
      NodeTag        pathtype;        /* tag identifying scan/join method */

      RelOptInfo *parent;            /* the relation this path can build */
+     ParamPathInfo *param_info;    /* parameterization info, or NULL if none */

      /* estimated size/costs for path (see costsize.c for more info) */
      double        rows;            /* estimated number of result tuples */
*************** typedef struct Path
*** 672,682 ****
      Cost        total_cost;        /* total cost (assuming all tuples fetched) */

      List       *pathkeys;        /* sort ordering of path's output */
!
!     Relids        required_outer;    /* rels supplying parameters used by path */
!     List       *param_clauses;    /* join clauses that use such parameters */
  } Path;

  /*----------
   * IndexPath represents an index scan over a single index.
   *
--- 693,705 ----
      Cost        total_cost;        /* total cost (assuming all tuples fetched) */

      List       *pathkeys;        /* sort ordering of path's output */
!     /* pathkeys is a List of PathKey nodes; see above */
  } Path;

+ /* Macro for extracting a path's parameterization relids; beware double eval */
+ #define PATH_PARAMRELS(path)  \
+     ((path)->param_info ? (path)->param_info->ppi_outer : (Relids) NULL)
+
  /*----------
   * IndexPath represents an index scan over a single index.
   *
*************** typedef struct JoinPath
*** 924,931 ****
      List       *joinrestrictinfo;        /* RestrictInfos to apply to join */

      /*
!      * See the notes for RelOptInfo to understand why joinrestrictinfo is
!      * needed in JoinPath, and can't be merged into the parent RelOptInfo.
       */
  } JoinPath;

--- 947,955 ----
      List       *joinrestrictinfo;        /* RestrictInfos to apply to join */

      /*
!      * See the notes for RelOptInfo and ParamPathInfo to understand why
!      * joinrestrictinfo is needed in JoinPath, and can't be merged into the
!      * parent RelOptInfo.
       */
  } JoinPath;

diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index b1e664265b25d2c3118fc82c2147e4d1c7b7e3ef..01357ea680b7a0627054dba5c1e708035ae03d23 100644
*** a/src/include/optimizer/cost.h
--- b/src/include/optimizer/cost.h
*************** extern int    constraint_exclusion;
*** 66,82 ****
  extern double clamp_row_est(double nrows);
  extern double index_pages_fetched(double tuples_fetched, BlockNumber pages,
                      double index_pages, PlannerInfo *root);
! extern void cost_seqscan(Path *path, PlannerInfo *root, RelOptInfo *baserel);
  extern void cost_index(IndexPath *path, PlannerInfo *root,
                         double loop_count);
  extern void cost_bitmap_heap_scan(Path *path, PlannerInfo *root, RelOptInfo *baserel,
                        Path *bitmapqual, double loop_count);
  extern void cost_bitmap_and_node(BitmapAndPath *path, PlannerInfo *root);
  extern void cost_bitmap_or_node(BitmapOrPath *path, PlannerInfo *root);
  extern void cost_bitmap_tree_node(Path *path, Cost *cost, Selectivity *selec);
  extern void cost_tidscan(Path *path, PlannerInfo *root,
               RelOptInfo *baserel, List *tidquals);
! extern void cost_subqueryscan(Path *path, RelOptInfo *baserel);
  extern void cost_functionscan(Path *path, PlannerInfo *root,
                    RelOptInfo *baserel);
  extern void cost_valuesscan(Path *path, PlannerInfo *root,
--- 66,85 ----
  extern double clamp_row_est(double nrows);
  extern double index_pages_fetched(double tuples_fetched, BlockNumber pages,
                      double index_pages, PlannerInfo *root);
! extern void cost_seqscan(Path *path, PlannerInfo *root, RelOptInfo *baserel,
!                          ParamPathInfo *param_info);
  extern void cost_index(IndexPath *path, PlannerInfo *root,
                         double loop_count);
  extern void cost_bitmap_heap_scan(Path *path, PlannerInfo *root, RelOptInfo *baserel,
+                       ParamPathInfo *param_info,
                        Path *bitmapqual, double loop_count);
  extern void cost_bitmap_and_node(BitmapAndPath *path, PlannerInfo *root);
  extern void cost_bitmap_or_node(BitmapOrPath *path, PlannerInfo *root);
  extern void cost_bitmap_tree_node(Path *path, Cost *cost, Selectivity *selec);
  extern void cost_tidscan(Path *path, PlannerInfo *root,
               RelOptInfo *baserel, List *tidquals);
! extern void cost_subqueryscan(Path *path, PlannerInfo *root,
!                               RelOptInfo *baserel, ParamPathInfo *param_info);
  extern void cost_functionscan(Path *path, PlannerInfo *root,
                    RelOptInfo *baserel);
  extern void cost_valuesscan(Path *path, PlannerInfo *root,
*************** extern void compute_semi_anti_join_facto
*** 149,154 ****
--- 152,167 ----
                                 List *restrictlist,
                                 SemiAntiJoinFactors *semifactors);
  extern void set_baserel_size_estimates(PlannerInfo *root, RelOptInfo *rel);
+ extern double get_parameterized_baserel_size(PlannerInfo *root,
+                                              RelOptInfo *rel,
+                                              List *param_clauses);
+ extern double get_parameterized_joinrel_size(PlannerInfo *root,
+                                              RelOptInfo *rel,
+                                              double outer_rows,
+                                              double inner_rows,
+                                              SpecialJoinInfo *sjinfo,
+                                              List *restrict_clauses,
+                                              List *param_clauses);
  extern void set_joinrel_size_estimates(PlannerInfo *root, RelOptInfo *rel,
                             RelOptInfo *outer_rel,
                             RelOptInfo *inner_rel,
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 3f80ca3fe9f01dbcddd2ffcc66b14f804e0e884c..7b636c07bf502a4b1d206a9e53acb942786258ba 100644
*** a/src/include/optimizer/pathnode.h
--- b/src/include/optimizer/pathnode.h
*************** extern bool add_path_precheck(RelOptInfo
*** 30,36 ****
                    Cost startup_cost, Cost total_cost,
                    List *pathkeys, Relids required_outer);

! extern Path *create_seqscan_path(PlannerInfo *root, RelOptInfo *rel);
  extern IndexPath *create_index_path(PlannerInfo *root,
                    IndexOptInfo *index,
                    List *indexclauses,
--- 30,37 ----
                    Cost startup_cost, Cost total_cost,
                    List *pathkeys, Relids required_outer);

! extern Path *create_seqscan_path(PlannerInfo *root, RelOptInfo *rel,
!                                  Relids required_outer);
  extern IndexPath *create_index_path(PlannerInfo *root,
                    IndexOptInfo *index,
                    List *indexclauses,
*************** extern IndexPath *create_index_path(Plan
*** 45,50 ****
--- 46,52 ----
  extern BitmapHeapPath *create_bitmap_heap_path(PlannerInfo *root,
                          RelOptInfo *rel,
                          Path *bitmapqual,
+                         Relids required_outer,
                          double loop_count);
  extern BitmapAndPath *create_bitmap_and_path(PlannerInfo *root,
                         RelOptInfo *rel,
*************** extern BitmapOrPath *create_bitmap_or_pa
*** 54,69 ****
                        List *bitmapquals);
  extern TidPath *create_tidscan_path(PlannerInfo *root, RelOptInfo *rel,
                      List *tidquals);
! extern AppendPath *create_append_path(RelOptInfo *rel, List *subpaths);
  extern MergeAppendPath *create_merge_append_path(PlannerInfo *root,
                           RelOptInfo *rel,
                           List *subpaths,
!                          List *pathkeys);
  extern ResultPath *create_result_path(List *quals);
  extern MaterialPath *create_material_path(RelOptInfo *rel, Path *subpath);
  extern UniquePath *create_unique_path(PlannerInfo *root, RelOptInfo *rel,
                     Path *subpath, SpecialJoinInfo *sjinfo);
! extern Path *create_subqueryscan_path(RelOptInfo *rel, List *pathkeys);
  extern Path *create_functionscan_path(PlannerInfo *root, RelOptInfo *rel);
  extern Path *create_valuesscan_path(PlannerInfo *root, RelOptInfo *rel);
  extern Path *create_ctescan_path(PlannerInfo *root, RelOptInfo *rel);
--- 56,74 ----
                        List *bitmapquals);
  extern TidPath *create_tidscan_path(PlannerInfo *root, RelOptInfo *rel,
                      List *tidquals);
! extern AppendPath *create_append_path(RelOptInfo *rel, List *subpaths,
!                                       Relids required_outer);
  extern MergeAppendPath *create_merge_append_path(PlannerInfo *root,
                           RelOptInfo *rel,
                           List *subpaths,
!                          List *pathkeys,
!                          Relids required_outer);
  extern ResultPath *create_result_path(List *quals);
  extern MaterialPath *create_material_path(RelOptInfo *rel, Path *subpath);
  extern UniquePath *create_unique_path(PlannerInfo *root, RelOptInfo *rel,
                     Path *subpath, SpecialJoinInfo *sjinfo);
! extern Path *create_subqueryscan_path(PlannerInfo *root, RelOptInfo *rel,
!                                       List *pathkeys, Relids required_outer);
  extern Path *create_functionscan_path(PlannerInfo *root, RelOptInfo *rel);
  extern Path *create_valuesscan_path(PlannerInfo *root, RelOptInfo *rel);
  extern Path *create_ctescan_path(PlannerInfo *root, RelOptInfo *rel);
*************** extern Path *create_worktablescan_path(P
*** 71,77 ****
  extern ForeignPath *create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel,
                          double rows, Cost startup_cost, Cost total_cost,
                          List *pathkeys,
!                         Relids required_outer, List *param_clauses,
                          List *fdw_private);

  extern Relids calc_nestloop_required_outer(Path *outer_path, Path *inner_path);
--- 76,82 ----
  extern ForeignPath *create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel,
                          double rows, Cost startup_cost, Cost total_cost,
                          List *pathkeys,
!                         Relids required_outer,
                          List *fdw_private);

  extern Relids calc_nestloop_required_outer(Path *outer_path, Path *inner_path);
*************** extern HashPath *create_hashjoin_path(Pl
*** 115,120 ****
--- 120,129 ----
                       Relids required_outer,
                       List *hashclauses);

+ extern Path *reparameterize_path(PlannerInfo *root, Path *path,
+                     Relids required_outer,
+                     double loop_count);
+
  /*
   * prototypes for relnode.c
   */
*************** extern RelOptInfo *build_join_rel(Planne
*** 129,133 ****
--- 138,157 ----
                 RelOptInfo *inner_rel,
                 SpecialJoinInfo *sjinfo,
                 List **restrictlist_ptr);
+ extern AppendRelInfo *find_childrel_appendrelinfo(PlannerInfo *root,
+                                                   RelOptInfo *rel);
+ extern ParamPathInfo *get_baserel_parampathinfo(PlannerInfo *root,
+                                                 RelOptInfo *baserel,
+                                                 Relids required_outer);
+ extern ParamPathInfo *get_joinrel_parampathinfo(PlannerInfo *root,
+                                                 RelOptInfo *joinrel,
+                                                 Path *outer_path,
+                                                 Path *inner_path,
+                                                 SpecialJoinInfo *sjinfo,
+                                                 List *restrict_clauses,
+                                                 Relids required_outer,
+                                                 List **param_clauses);
+ extern ParamPathInfo *get_appendrel_parampathinfo(RelOptInfo *appendrel,
+                                                   Relids required_outer);

  #endif   /* PATHNODE_H */
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index caaa7c24a1cf21230e44a5db83c34c8d9d7608e3..e482dbbece2a7e8ca682743ecba7bf331048d111 100644
*** a/src/include/optimizer/paths.h
--- b/src/include/optimizer/paths.h
*************** extern EquivalenceClass *get_eclass_for_
*** 114,121 ****
                           bool create_it);
  extern void generate_base_implied_equalities(PlannerInfo *root);
  extern List *generate_join_implied_equalities(PlannerInfo *root,
!                                  RelOptInfo *joinrel,
!                                  RelOptInfo *outer_rel,
                                   RelOptInfo *inner_rel);
  extern bool exprs_known_equal(PlannerInfo *root, Node *item1, Node *item2);
  extern void add_child_rel_equivalences(PlannerInfo *root,
--- 114,121 ----
                           bool create_it);
  extern void generate_base_implied_equalities(PlannerInfo *root);
  extern List *generate_join_implied_equalities(PlannerInfo *root,
!                                  Relids join_relids,
!                                  Relids outer_relids,
                                   RelOptInfo *inner_rel);
  extern bool exprs_known_equal(PlannerInfo *root, Node *item1, Node *item2);
  extern void add_child_rel_equivalences(PlannerInfo *root,
diff --git a/src/include/optimizer/restrictinfo.h b/src/include/optimizer/restrictinfo.h
index 34977f9b95cbf5b96502100b5c1b695e74b3a58a..9bc03cb180059dd328f9bdfdfef2e2099de25f2e 100644
*** a/src/include/optimizer/restrictinfo.h
--- b/src/include/optimizer/restrictinfo.h
*************** extern List *extract_actual_clauses(List
*** 40,46 ****
  extern void extract_actual_join_clauses(List *restrictinfo_list,
                              List **joinquals,
                              List **otherquals);
! extern List *select_nonredundant_join_clauses(List *restrictinfo_list,
!                                  List *reference_list);

  #endif   /* RESTRICTINFO_H */
--- 40,49 ----
  extern void extract_actual_join_clauses(List *restrictinfo_list,
                              List **joinquals,
                              List **otherquals);
! extern bool join_clause_is_parameterizable_for(RestrictInfo *rinfo,
!                                                Relids targetrelids);
! extern bool join_clause_is_parameterizable_within(RestrictInfo *rinfo,
!                                                   Relids paramrels,
!                                                   Relids currentcontext);

  #endif   /* RESTRICTINFO_H */
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index e5dceff76586503b818e89bfa0b20466e68f5ae4..817267aa0fb09071766732a41caf39ec6eb21593 100644
*** a/src/test/regress/expected/join.out
--- b/src/test/regress/expected/join.out
*************** select * from
*** 2675,2686 ****
    int4(sin(1)) q1,
    int4(sin(0)) q2
  where q1 = thousand or q2 = thousand;
!                                  QUERY PLAN
! -----------------------------------------------------------------------------
   Hash Join
     Hash Cond: (tenk1.twothousand = int4_tbl.f1)
     ->  Nested Loop
-          Join Filter: ((q1.q1 = tenk1.thousand) OR (q2.q2 = tenk1.thousand))
           ->  Nested Loop
                 ->  Function Scan on q1
                 ->  Function Scan on q2
--- 2675,2685 ----
    int4(sin(1)) q1,
    int4(sin(0)) q2
  where q1 = thousand or q2 = thousand;
!                                QUERY PLAN
! ------------------------------------------------------------------------
   Hash Join
     Hash Cond: (tenk1.twothousand = int4_tbl.f1)
     ->  Nested Loop
           ->  Nested Loop
                 ->  Function Scan on q1
                 ->  Function Scan on q2
*************** where q1 = thousand or q2 = thousand;
*** 2693,2699 ****
                             Index Cond: (q2.q2 = thousand)
     ->  Hash
           ->  Seq Scan on int4_tbl
! (16 rows)

  explain (costs off)
  select * from
--- 2692,2698 ----
                             Index Cond: (q2.q2 = thousand)
     ->  Hash
           ->  Seq Scan on int4_tbl
! (15 rows)

  explain (costs off)
  select * from

pgsql-hackers by date:

Previous
From: Stephen Frost
Date:
Subject: Re: Gsoc2012 idea, tablesample
Next
From: Jameison Martin
Date:
Subject: patch submission: truncate trailing nulls from heap rows to reduce the size of the null bitmap