startup_cost = ofpinfo->rel_startup_cost;
            startup_cost += aggcosts.transCost.startup;
            startup_cost += aggcosts.transCost.per_tuple * input_rows;
+           startup_cost += aggcosts.finalCost.startup;
            startup_cost += (cpu_operator_cost * numGroupCols) * input_rows;
 
            /*-----
             *-----
             */
            run_cost = ofpinfo->rel_total_cost - ofpinfo->rel_startup_cost;
-           run_cost += aggcosts.finalCost * numGroups;
+           run_cost += aggcosts.finalCost.per_tuple * numGroups;
            run_cost += cpu_tuple_cost * numGroups;
 
            /* Account for the eval cost of HAVING quals, if any */
 
     simplify.  Ensure rigorous equivalence between the simplified
     expression and an actual execution of the target function.
    </para>
+
+   <para>
+    For target functions that return boolean, it is often useful to estimate
+    the fraction of rows that will be selected by a WHERE clause using that
+    function.  This can be done by a support function that implements
+    the <literal>SupportRequestSelectivity</literal> request type.
+   </para>
+
+   <para>
+    If the target function's runtime is highly dependent on its inputs,
+    it may be useful to provide a non-constant cost estimate for it.
+    This can be done by a support function that implements
+    the <literal>SupportRequestCost</literal> request type.
+   </para>
+
+   <para>
+    For target functions that return sets, it is often useful to provide
+    a non-constant estimate for the number of rows that will be returned.
+    This can be done by a support function that implements
+    the <literal>SupportRequestRows</literal> request type.
+   </para>
   </sect1>
 
        if (IsA(clause, DistinctExpr))
            s1 = 1.0 - s1;
    }
+   else if (is_funcclause(clause))
+   {
+       FuncExpr   *funcclause = (FuncExpr *) clause;
+
+       /* Try to get an estimate from the support function, if any */
+       s1 = function_selectivity(root,
+                                 funcclause->funcid,
+                                 funcclause->args,
+                                 funcclause->inputcollid,
+                                 treat_as_join_clause(clause, rinfo,
+                                                      varRelid, sjinfo),
+                                 varRelid,
+                                 jointype,
+                                 sjinfo);
+   }
    else if (IsA(clause, ScalarArrayOpExpr))
    {
        /* Use node specific selectivity calculation function */
 
    /*
     * The transCost.per_tuple component of aggcosts should be charged once
     * per input tuple, corresponding to the costs of evaluating the aggregate
-    * transfns and their input expressions (with any startup cost of course
-    * charged but once).  The finalCost component is charged once per output
-    * tuple, corresponding to the costs of evaluating the finalfns.
+    * transfns and their input expressions. The finalCost.per_tuple component
+    * is charged once per output tuple, corresponding to the costs of
+    * evaluating the finalfns.  Startup costs are of course charged but once.
     *
     * If we are grouping, we charge an additional cpu_operator_cost per
     * grouping column per input tuple for grouping comparisons.
        startup_cost = input_total_cost;
        startup_cost += aggcosts->transCost.startup;
        startup_cost += aggcosts->transCost.per_tuple * input_tuples;
-       startup_cost += aggcosts->finalCost;
+       startup_cost += aggcosts->finalCost.startup;
+       startup_cost += aggcosts->finalCost.per_tuple;
        /* we aren't grouping */
        total_cost = startup_cost + cpu_tuple_cost;
        output_tuples = 1;
        total_cost += aggcosts->transCost.startup;
        total_cost += aggcosts->transCost.per_tuple * input_tuples;
        total_cost += (cpu_operator_cost * numGroupCols) * input_tuples;
-       total_cost += aggcosts->finalCost * numGroups;
+       total_cost += aggcosts->finalCost.startup;
+       total_cost += aggcosts->finalCost.per_tuple * numGroups;
        total_cost += cpu_tuple_cost * numGroups;
        output_tuples = numGroups;
    }
        startup_cost += aggcosts->transCost.startup;
        startup_cost += aggcosts->transCost.per_tuple * input_tuples;
        startup_cost += (cpu_operator_cost * numGroupCols) * input_tuples;
+       startup_cost += aggcosts->finalCost.startup;
        total_cost = startup_cost;
-       total_cost += aggcosts->finalCost * numGroups;
+       total_cost += aggcosts->finalCost.per_tuple * numGroups;
        total_cost += cpu_tuple_cost * numGroups;
        output_tuples = numGroups;
    }
        Cost        wfunccost;
        QualCost    argcosts;
 
-       wfunccost = get_func_cost(wfunc->winfnoid) * cpu_operator_cost;
+       argcosts.startup = argcosts.per_tuple = 0;
+       add_function_cost(root, wfunc->winfnoid, (Node *) wfunc,
+                         &argcosts);
+       startup_cost += argcosts.startup;
+       wfunccost = argcosts.per_tuple;
 
        /* also add the input expressions' cost to per-input-row costs */
        cost_qual_eval_node(&argcosts, (Node *) wfunc->args, root);
     */
    if (IsA(node, FuncExpr))
    {
-       context->total.per_tuple +=
-           get_func_cost(((FuncExpr *) node)->funcid) * cpu_operator_cost;
+       add_function_cost(context->root, ((FuncExpr *) node)->funcid, node,
+                         &context->total);
    }
    else if (IsA(node, OpExpr) ||
             IsA(node, DistinctExpr) ||
    {
        /* rely on struct equivalence to treat these all alike */
        set_opfuncid((OpExpr *) node);
-       context->total.per_tuple +=
-           get_func_cost(((OpExpr *) node)->opfuncid) * cpu_operator_cost;
+       add_function_cost(context->root, ((OpExpr *) node)->opfuncid, node,
+                         &context->total);
    }
    else if (IsA(node, ScalarArrayOpExpr))
    {
         */
        ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) node;
        Node       *arraynode = (Node *) lsecond(saop->args);
+       QualCost    sacosts;
 
        set_sa_opfuncid(saop);
-       context->total.per_tuple += get_func_cost(saop->opfuncid) *
-           cpu_operator_cost * estimate_array_length(arraynode) * 0.5;
+       sacosts.startup = sacosts.per_tuple = 0;
+       add_function_cost(context->root, saop->opfuncid, NULL,
+                         &sacosts);
+       context->total.startup += sacosts.startup;
+       context->total.per_tuple += sacosts.per_tuple *
+           estimate_array_length(arraynode) * 0.5;
    }
    else if (IsA(node, Aggref) ||
             IsA(node, WindowFunc))
        /* check the result type's input function */
        getTypeInputInfo(iocoerce->resulttype,
                         &iofunc, &typioparam);
-       context->total.per_tuple += get_func_cost(iofunc) * cpu_operator_cost;
+       add_function_cost(context->root, iofunc, NULL,
+                         &context->total);
        /* check the input type's output function */
        getTypeOutputInfo(exprType((Node *) iocoerce->arg),
                          &iofunc, &typisvarlena);
-       context->total.per_tuple += get_func_cost(iofunc) * cpu_operator_cost;
+       add_function_cost(context->root, iofunc, NULL,
+                         &context->total);
    }
    else if (IsA(node, ArrayCoerceExpr))
    {
        {
            Oid         opid = lfirst_oid(lc);
 
-           context->total.per_tuple += get_func_cost(get_opcode(opid)) *
-               cpu_operator_cost;
+           add_function_cost(context->root, get_opcode(opid), NULL,
+                             &context->total);
        }
    }
    else if (IsA(node, MinMaxExpr) ||
    foreach(lc, rte->functions)
    {
        RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
-       double      ntup = expression_returns_set_rows(rtfunc->funcexpr);
+       double      ntup = expression_returns_set_rows(root, rtfunc->funcexpr);
 
        if (ntup > rel->tuples)
            rel->tuples = ntup;
 
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/optimizer.h"
+#include "optimizer/plancat.h"
 #include "optimizer/planmain.h"
-#include "optimizer/prep.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
 #include "parser/parse_coerce.h"
        if (DO_AGGSPLIT_COMBINE(context->aggsplit))
        {
            /* charge for combining previously aggregated states */
-           costs->transCost.per_tuple += get_func_cost(aggcombinefn) * cpu_operator_cost;
+           add_function_cost(context->root, aggcombinefn, NULL,
+                             &costs->transCost);
        }
        else
-           costs->transCost.per_tuple += get_func_cost(aggtransfn) * cpu_operator_cost;
+           add_function_cost(context->root, aggtransfn, NULL,
+                             &costs->transCost);
        if (DO_AGGSPLIT_DESERIALIZE(context->aggsplit) &&
            OidIsValid(aggdeserialfn))
-           costs->transCost.per_tuple += get_func_cost(aggdeserialfn) * cpu_operator_cost;
+           add_function_cost(context->root, aggdeserialfn, NULL,
+                             &costs->transCost);
        if (DO_AGGSPLIT_SERIALIZE(context->aggsplit) &&
            OidIsValid(aggserialfn))
-           costs->finalCost += get_func_cost(aggserialfn) * cpu_operator_cost;
+           add_function_cost(context->root, aggserialfn, NULL,
+                             &costs->finalCost);
        if (!DO_AGGSPLIT_SKIPFINAL(context->aggsplit) &&
            OidIsValid(aggfinalfn))
-           costs->finalCost += get_func_cost(aggfinalfn) * cpu_operator_cost;
+           add_function_cost(context->root, aggfinalfn, NULL,
+                             &costs->finalCost);
 
        /*
         * These costs are incurred only by the initial aggregate node, so we
        {
            cost_qual_eval_node(&argcosts, (Node *) aggref->aggdirectargs,
                                context->root);
-           costs->transCost.startup += argcosts.startup;
-           costs->finalCost += argcosts.per_tuple;
+           costs->finalCost.startup += argcosts.startup;
+           costs->finalCost.per_tuple += argcosts.per_tuple;
        }
 
        /*
  * Note: keep this in sync with expression_returns_set() in nodes/nodeFuncs.c.
  */
 double
-expression_returns_set_rows(Node *clause)
+expression_returns_set_rows(PlannerInfo *root, Node *clause)
 {
    if (clause == NULL)
        return 1.0;
        FuncExpr   *expr = (FuncExpr *) clause;
 
        if (expr->funcretset)
-           return clamp_row_est(get_func_rows(expr->funcid));
+           return clamp_row_est(get_function_rows(root, expr->funcid, clause));
    }
    if (IsA(clause, OpExpr))
    {
        if (expr->opretset)
        {
            set_opfuncid(expr);
-           return clamp_row_est(get_func_rows(expr->opfuncid));
+           return clamp_row_est(get_function_rows(root, expr->opfuncid, clause));
        }
    }
    return 1.0;
 
        Node       *node = (Node *) lfirst(lc);
        double      itemrows;
 
-       itemrows = expression_returns_set_rows(node);
+       itemrows = expression_returns_set_rows(root, node);
        if (tlist_rows < itemrows)
            tlist_rows = itemrows;
    }
 
 #include "catalog/heap.h"
 #include "catalog/partition.h"
 #include "catalog/pg_am.h"
+#include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "nodes/supportnodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/optimizer.h"
  * Returns the selectivity of a specified join operator clause.
  * This code executes registered procedures stored in the
  * operator relation, by calling the function manager.
+ *
+ * See clause_selectivity() for the meaning of the additional parameters.
  */
 Selectivity
 join_selectivity(PlannerInfo *root,
    return (Selectivity) result;
 }
 
+/*
+ * function_selectivity
+ *
+ * Returns the selectivity of a specified boolean function clause.
+ * This code executes registered procedures stored in the
+ * pg_proc relation, by calling the function manager.
+ *
+ * See clause_selectivity() for the meaning of the additional parameters.
+ */
+Selectivity
+function_selectivity(PlannerInfo *root,
+                    Oid funcid,
+                    List *args,
+                    Oid inputcollid,
+                    bool is_join,
+                    int varRelid,
+                    JoinType jointype,
+                    SpecialJoinInfo *sjinfo)
+{
+   RegProcedure prosupport = get_func_support(funcid);
+   SupportRequestSelectivity req;
+   SupportRequestSelectivity *sresult;
+
+   /*
+    * If no support function is provided, use our historical default
+    * estimate, 0.3333333.  This seems a pretty unprincipled choice, but
+    * Postgres has been using that estimate for function calls since 1992.
+    * The hoariness of this behavior suggests that we should not be in too
+    * much hurry to use another value.
+    */
+   if (!prosupport)
+       return (Selectivity) 0.3333333;
+
+   req.type = T_SupportRequestSelectivity;
+   req.root = root;
+   req.funcid = funcid;
+   req.args = args;
+   req.inputcollid = inputcollid;
+   req.is_join = is_join;
+   req.varRelid = varRelid;
+   req.jointype = jointype;
+   req.sjinfo = sjinfo;
+   req.selectivity = -1;       /* to catch failure to set the value */
+
+   sresult = (SupportRequestSelectivity *)
+       DatumGetPointer(OidFunctionCall1(prosupport,
+                                        PointerGetDatum(&req)));
+
+   /* If support function fails, use default */
+   if (sresult != &req)
+       return (Selectivity) 0.3333333;
+
+   if (req.selectivity < 0.0 || req.selectivity > 1.0)
+       elog(ERROR, "invalid function selectivity: %f", req.selectivity);
+
+   return (Selectivity) req.selectivity;
+}
+
+/*
+ * add_function_cost
+ *
+ * Get an estimate of the execution cost of a function, and *add* it to
+ * the contents of *cost.  The estimate may include both one-time and
+ * per-tuple components, since QualCost does.
+ *
+ * The funcid must always be supplied.  If it is being called as the
+ * implementation of a specific parsetree node (FuncExpr, OpExpr,
+ * WindowFunc, etc), pass that as "node", else pass NULL.
+ *
+ * In some usages root might be NULL, too.
+ */
+void
+add_function_cost(PlannerInfo *root, Oid funcid, Node *node,
+                 QualCost *cost)
+{
+   HeapTuple   proctup;
+   Form_pg_proc procform;
+
+   proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
+   if (!HeapTupleIsValid(proctup))
+       elog(ERROR, "cache lookup failed for function %u", funcid);
+   procform = (Form_pg_proc) GETSTRUCT(proctup);
+
+   if (OidIsValid(procform->prosupport))
+   {
+       SupportRequestCost req;
+       SupportRequestCost *sresult;
+
+       req.type = T_SupportRequestCost;
+       req.root = root;
+       req.funcid = funcid;
+       req.node = node;
+
+       /* Initialize cost fields so that support function doesn't have to */
+       req.startup = 0;
+       req.per_tuple = 0;
+
+       sresult = (SupportRequestCost *)
+           DatumGetPointer(OidFunctionCall1(procform->prosupport,
+                                            PointerGetDatum(&req)));
+
+       if (sresult == &req)
+       {
+           /* Success, so accumulate support function's estimate into *cost */
+           cost->startup += req.startup;
+           cost->per_tuple += req.per_tuple;
+           ReleaseSysCache(proctup);
+           return;
+       }
+   }
+
+   /* No support function, or it failed, so rely on procost */
+   cost->per_tuple += procform->procost * cpu_operator_cost;
+
+   ReleaseSysCache(proctup);
+}
+
+/*
+ * get_function_rows
+ *
+ * Get an estimate of the number of rows returned by a set-returning function.
+ *
+ * The funcid must always be supplied.  In current usage, the calling node
+ * will always be supplied, and will be either a FuncExpr or OpExpr.
+ * But it's a good idea to not fail if it's NULL.
+ *
+ * In some usages root might be NULL, too.
+ *
+ * Note: this returns the unfiltered result of the support function, if any.
+ * It's usually a good idea to apply clamp_row_est() to the result, but we
+ * leave it to the caller to do so.
+ */
+double
+get_function_rows(PlannerInfo *root, Oid funcid, Node *node)
+{
+   HeapTuple   proctup;
+   Form_pg_proc procform;
+   double      result;
+
+   proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
+   if (!HeapTupleIsValid(proctup))
+       elog(ERROR, "cache lookup failed for function %u", funcid);
+   procform = (Form_pg_proc) GETSTRUCT(proctup);
+
+   Assert(procform->proretset);    /* else caller error */
+
+   if (OidIsValid(procform->prosupport))
+   {
+       SupportRequestRows req;
+       SupportRequestRows *sresult;
+
+       req.type = T_SupportRequestRows;
+       req.root = root;
+       req.funcid = funcid;
+       req.node = node;
+
+       req.rows = 0;           /* just for sanity */
+
+       sresult = (SupportRequestRows *)
+           DatumGetPointer(OidFunctionCall1(procform->prosupport,
+                                            PointerGetDatum(&req)));
+
+       if (sresult == &req)
+       {
+           /* Success */
+           ReleaseSysCache(proctup);
+           return req.rows;
+       }
+   }
+
+   /* No support function, or it failed, so rely on prorows */
+   result = procform->prorows;
+
+   ReleaseSysCache(proctup);
+
+   return result;
+}
+
 /*
  * has_unique_index
  *
 
 #include "catalog/pg_type.h"
 #include "funcapi.h"
 #include "libpq/pqformat.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/supportnodes.h"
+#include "optimizer/optimizer.h"
 #include "utils/array.h"
 #include "utils/arrayaccess.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
+#include "utils/selfuncs.h"
 #include "utils/typcache.h"
 
 
    }
 }
 
+/*
+ * Planner support function for array_unnest(anyarray)
+ */
+Datum
+array_unnest_support(PG_FUNCTION_ARGS)
+{
+   Node       *rawreq = (Node *) PG_GETARG_POINTER(0);
+   Node       *ret = NULL;
+
+   if (IsA(rawreq, SupportRequestRows))
+   {
+       /* Try to estimate the number of rows returned */
+       SupportRequestRows *req = (SupportRequestRows *) rawreq;
+
+       if (is_funcclause(req->node))   /* be paranoid */
+       {
+           List       *args = ((FuncExpr *) req->node)->args;
+           Node       *arg1;
+
+           /* We can use estimated argument values here */
+           arg1 = estimate_expression_value(req->root, linitial(args));
+
+           req->rows = estimate_array_length(arg1);
+           ret = (Node *) req;
+       }
+   }
+
+   PG_RETURN_POINTER(ret);
+}
+
 
 /*
  * array_replace/array_remove support
 
 
 #include <ctype.h>
 #include <limits.h>
+#include <math.h>
 
 #include "catalog/pg_type.h"
 #include "common/int.h"
 #include "funcapi.h"
 #include "libpq/pqformat.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/supportnodes.h"
+#include "optimizer/optimizer.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
 
        /* do when there is no more left */
        SRF_RETURN_DONE(funcctx);
 }
+
+/*
+ * Planner support function for generate_series(int4, int4 [, int4])
+ */
+Datum
+generate_series_int4_support(PG_FUNCTION_ARGS)
+{
+   Node       *rawreq = (Node *) PG_GETARG_POINTER(0);
+   Node       *ret = NULL;
+
+   if (IsA(rawreq, SupportRequestRows))
+   {
+       /* Try to estimate the number of rows returned */
+       SupportRequestRows *req = (SupportRequestRows *) rawreq;
+
+       if (is_funcclause(req->node))   /* be paranoid */
+       {
+           List       *args = ((FuncExpr *) req->node)->args;
+           Node       *arg1,
+                      *arg2,
+                      *arg3;
+
+           /* We can use estimated argument values here */
+           arg1 = estimate_expression_value(req->root, linitial(args));
+           arg2 = estimate_expression_value(req->root, lsecond(args));
+           if (list_length(args) >= 3)
+               arg3 = estimate_expression_value(req->root, lthird(args));
+           else
+               arg3 = NULL;
+
+           /*
+            * If any argument is constant NULL, we can safely assume that
+            * zero rows are returned.  Otherwise, if they're all non-NULL
+            * constants, we can calculate the number of rows that will be
+            * returned.  Use double arithmetic to avoid overflow hazards.
+            */
+           if ((IsA(arg1, Const) &&
+                ((Const *) arg1)->constisnull) ||
+               (IsA(arg2, Const) &&
+                ((Const *) arg2)->constisnull) ||
+               (arg3 != NULL && IsA(arg3, Const) &&
+                ((Const *) arg3)->constisnull))
+           {
+               req->rows = 0;
+               ret = (Node *) req;
+           }
+           else if (IsA(arg1, Const) &&
+                    IsA(arg2, Const) &&
+                    (arg3 == NULL || IsA(arg3, Const)))
+           {
+               double      start,
+                           finish,
+                           step;
+
+               start = DatumGetInt32(((Const *) arg1)->constvalue);
+               finish = DatumGetInt32(((Const *) arg2)->constvalue);
+               step = arg3 ? DatumGetInt32(((Const *) arg3)->constvalue) : 1;
+
+               /* This equation works for either sign of step */
+               if (step != 0)
+               {
+                   req->rows = floor((finish - start + step) / step);
+                   ret = (Node *) req;
+               }
+           }
+       }
+   }
+
+   PG_RETURN_POINTER(ret);
+}
 
 #include "common/int.h"
 #include "funcapi.h"
 #include "libpq/pqformat.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/supportnodes.h"
+#include "optimizer/optimizer.h"
 #include "utils/int8.h"
 #include "utils/builtins.h"
 
        /* do when there is no more left */
        SRF_RETURN_DONE(funcctx);
 }
+
+/*
+ * Planner support function for generate_series(int8, int8 [, int8])
+ */
+Datum
+generate_series_int8_support(PG_FUNCTION_ARGS)
+{
+   Node       *rawreq = (Node *) PG_GETARG_POINTER(0);
+   Node       *ret = NULL;
+
+   if (IsA(rawreq, SupportRequestRows))
+   {
+       /* Try to estimate the number of rows returned */
+       SupportRequestRows *req = (SupportRequestRows *) rawreq;
+
+       if (is_funcclause(req->node))   /* be paranoid */
+       {
+           List       *args = ((FuncExpr *) req->node)->args;
+           Node       *arg1,
+                      *arg2,
+                      *arg3;
+
+           /* We can use estimated argument values here */
+           arg1 = estimate_expression_value(req->root, linitial(args));
+           arg2 = estimate_expression_value(req->root, lsecond(args));
+           if (list_length(args) >= 3)
+               arg3 = estimate_expression_value(req->root, lthird(args));
+           else
+               arg3 = NULL;
+
+           /*
+            * If any argument is constant NULL, we can safely assume that
+            * zero rows are returned.  Otherwise, if they're all non-NULL
+            * constants, we can calculate the number of rows that will be
+            * returned.  Use double arithmetic to avoid overflow hazards.
+            */
+           if ((IsA(arg1, Const) &&
+                ((Const *) arg1)->constisnull) ||
+               (IsA(arg2, Const) &&
+                ((Const *) arg2)->constisnull) ||
+               (arg3 != NULL && IsA(arg3, Const) &&
+                ((Const *) arg3)->constisnull))
+           {
+               req->rows = 0;
+               ret = (Node *) req;
+           }
+           else if (IsA(arg1, Const) &&
+                    IsA(arg2, Const) &&
+                    (arg3 == NULL || IsA(arg3, Const)))
+           {
+               double      start,
+                           finish,
+                           step;
+
+               start = DatumGetInt64(((Const *) arg1)->constvalue);
+               finish = DatumGetInt64(((Const *) arg2)->constvalue);
+               step = arg3 ? DatumGetInt64(((Const *) arg3)->constvalue) : 1;
+
+               /* This equation works for either sign of step */
+               if (step != 0)
+               {
+                   req->rows = floor((finish - start + step) / step);
+                   ret = (Node *) req;
+               }
+           }
+       }
+   }
+
+   PG_RETURN_POINTER(ret);
+}
 
        selec = var_eq_const(&vardata, BooleanEqualOperator,
                             BoolGetDatum(true), false, true, false);
    }
-   else if (is_funcclause(arg))
-   {
-       /*
-        * If we have no stats and it's a function call, estimate 0.3333333.
-        * This seems a pretty unprincipled choice, but Postgres has been
-        * using that estimate for function calls since 1992.  The hoariness
-        * of this behavior suggests that we should not be in too much hurry
-        * to use another value.
-        */
-       selec = 0.3333333;
-   }
    else
    {
        /* Otherwise, the default estimate is 0.5 */
         * pointless to worry too much about this without much better
         * estimates for SRF output rowcounts than we have today.)
         */
-       this_srf_multiplier = expression_returns_set_rows(groupexpr);
+       this_srf_multiplier = expression_returns_set_rows(root, groupexpr);
        if (srf_multiplier < this_srf_multiplier)
            srf_multiplier = this_srf_multiplier;
 
 
 }
 
 /*
- * get_func_cost
- *     Given procedure id, return the function's procost field.
- */
-float4
-get_func_cost(Oid funcid)
-{
-   HeapTuple   tp;
-   float4      result;
-
-   tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
-   if (!HeapTupleIsValid(tp))
-       elog(ERROR, "cache lookup failed for function %u", funcid);
-
-   result = ((Form_pg_proc) GETSTRUCT(tp))->procost;
-   ReleaseSysCache(tp);
-   return result;
-}
-
-/*
- * get_func_rows
- *     Given procedure id, return the function's prorows field.
+ * get_func_support
+ *
+ *     Returns the support function OID associated with a given function,
+ *     or InvalidOid if there is none.
  */
-float4
-get_func_rows(Oid funcid)
+RegProcedure
+get_func_support(Oid funcid)
 {
    HeapTuple   tp;
-   float4      result;
 
    tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
-   if (!HeapTupleIsValid(tp))
-       elog(ERROR, "cache lookup failed for function %u", funcid);
+   if (HeapTupleIsValid(tp))
+   {
+       Form_pg_proc functup = (Form_pg_proc) GETSTRUCT(tp);
+       RegProcedure result;
 
-   result = ((Form_pg_proc) GETSTRUCT(tp))->prorows;
-   ReleaseSysCache(tp);
-   return result;
+       result = functup->prosupport;
+       ReleaseSysCache(tp);
+       return result;
+   }
+   else
+       return (RegProcedure) InvalidOid;
 }
 
 /*             ---------- RELATION CACHE ----------                     */
 
  */
 
 /*                         yyyymmddN */
-#define CATALOG_VERSION_NO 201902091
+#define CATALOG_VERSION_NO 201902092
 
 #endif
 
   proargtypes => 'anyelement _int4 _int4',
   prosrc => 'array_fill_with_lower_bounds' },
 { oid => '2331', descr => 'expand array to set of rows',
-  proname => 'unnest', prorows => '100', proretset => 't',
-  prorettype => 'anyelement', proargtypes => 'anyarray',
+  proname => 'unnest', prorows => '100', prosupport => 'array_unnest_support',
+  proretset => 't', prorettype => 'anyelement', proargtypes => 'anyarray',
   prosrc => 'array_unnest' },
+{ oid => '3996', descr => 'planner support for array_unnest',
+  proname => 'array_unnest_support', prorettype => 'internal',
+  proargtypes => 'internal', prosrc => 'array_unnest_support' },
 { oid => '3167',
   descr => 'remove any occurrences of an element from an array',
   proname => 'array_remove', proisstrict => 'f', prorettype => 'anyarray',
 
 # non-persistent series generator
 { oid => '1066', descr => 'non-persistent series generator',
-  proname => 'generate_series', prorows => '1000', proretset => 't',
+  proname => 'generate_series', prorows => '1000',
+  prosupport => 'generate_series_int4_support', proretset => 't',
   prorettype => 'int4', proargtypes => 'int4 int4 int4',
   prosrc => 'generate_series_step_int4' },
 { oid => '1067', descr => 'non-persistent series generator',
-  proname => 'generate_series', prorows => '1000', proretset => 't',
+  proname => 'generate_series', prorows => '1000',
+  prosupport => 'generate_series_int4_support', proretset => 't',
   prorettype => 'int4', proargtypes => 'int4 int4',
   prosrc => 'generate_series_int4' },
+{ oid => '3994', descr => 'planner support for generate_series',
+  proname => 'generate_series_int4_support', prorettype => 'internal',
+  proargtypes => 'internal', prosrc => 'generate_series_int4_support' },
 { oid => '1068', descr => 'non-persistent series generator',
-  proname => 'generate_series', prorows => '1000', proretset => 't',
+  proname => 'generate_series', prorows => '1000',
+  prosupport => 'generate_series_int8_support', proretset => 't',
   prorettype => 'int8', proargtypes => 'int8 int8 int8',
   prosrc => 'generate_series_step_int8' },
 { oid => '1069', descr => 'non-persistent series generator',
-  proname => 'generate_series', prorows => '1000', proretset => 't',
+  proname => 'generate_series', prorows => '1000',
+  prosupport => 'generate_series_int8_support', proretset => 't',
   prorettype => 'int8', proargtypes => 'int8 int8',
   prosrc => 'generate_series_int8' },
+{ oid => '3995', descr => 'planner support for generate_series',
+  proname => 'generate_series_int8_support', prorettype => 'internal',
+  proargtypes => 'internal', prosrc => 'generate_series_int8_support' },
 { oid => '3259', descr => 'non-persistent series generator',
   proname => 'generate_series', prorows => '1000', proretset => 't',
   prorettype => 'numeric', proargtypes => 'numeric numeric numeric',
 
    T_TsmRoutine,               /* in access/tsmapi.h */
    T_ForeignKeyCacheInfo,      /* in utils/rel.h */
    T_CallContext,              /* in nodes/parsenodes.h */
-   T_SupportRequestSimplify    /* in nodes/supportnodes.h */
+   T_SupportRequestSimplify,   /* in nodes/supportnodes.h */
+   T_SupportRequestSelectivity,    /* in nodes/supportnodes.h */
+   T_SupportRequestCost,       /* in nodes/supportnodes.h */
+   T_SupportRequestRows        /* in nodes/supportnodes.h */
 } NodeTag;
 
 /*
 
    bool        hasNonPartial;  /* does any agg not support partial mode? */
    bool        hasNonSerial;   /* is any partial agg non-serializable? */
    QualCost    transCost;      /* total per-input-row execution costs */
-   Cost        finalCost;      /* total per-aggregated-row costs */
+   QualCost    finalCost;      /* total per-aggregated-row costs */
    Size        transitionSpace;    /* space for pass-by-ref transition data */
 } AggClauseCosts;
 
 
 #include "nodes/primnodes.h"
 
 struct PlannerInfo;                /* avoid including relation.h here */
+struct SpecialJoinInfo;
 
 
 /*
    FuncExpr   *fcall;          /* Function call to be simplified */
 } SupportRequestSimplify;
 
+/*
+ * The Selectivity request allows the support function to provide a
+ * selectivity estimate for a function appearing at top level of a WHERE
+ * clause (so it applies only to functions returning boolean).
+ *
+ * The input arguments are the same as are supplied to operator restriction
+ * and join estimators, except that we unify those two APIs into just one
+ * request type.  See clause_selectivity() for the details.
+ *
+ * If an estimate can be made, store it into the "selectivity" field and
+ * return the address of the SupportRequestSelectivity node; the estimate
+ * must be between 0 and 1 inclusive.  Return NULL if no estimate can be
+ * made (in which case the planner will fall back to a default estimate,
+ * traditionally 1/3).
+ *
+ * If the target function is being used as the implementation of an operator,
+ * the support function will not be used for this purpose; the operator's
+ * restriction or join estimator is consulted instead.
+ */
+typedef struct SupportRequestSelectivity
+{
+   NodeTag     type;
+
+   /* Input fields: */
+   struct PlannerInfo *root;   /* Planner's infrastructure */
+   Oid         funcid;         /* function we are inquiring about */
+   List       *args;           /* pre-simplified arguments to function */
+   Oid         inputcollid;    /* function's input collation */
+   bool        is_join;        /* is this a join or restriction case? */
+   int         varRelid;       /* if restriction, RTI of target relation */
+   JoinType    jointype;       /* if join, outer join type */
+   struct SpecialJoinInfo *sjinfo; /* if outer join, info about join */
+
+   /* Output fields: */
+   Selectivity selectivity;    /* returned selectivity estimate */
+} SupportRequestSelectivity;
+
+/*
+ * The Cost request allows the support function to provide an execution
+ * cost estimate for its target function.  The cost estimate can include
+ * both a one-time (query startup) component and a per-execution component.
+ * The estimate should *not* include the costs of evaluating the target
+ * function's arguments, only the target function itself.
+ *
+ * The "node" argument is normally the parse node that is invoking the
+ * target function.  This is a FuncExpr in the simplest case, but it could
+ * also be an OpExpr, DistinctExpr, NullIfExpr, or WindowFunc, or possibly
+ * other cases in future.  NULL is passed if the function cannot presume
+ * its arguments to be equivalent to what the calling node presents as
+ * arguments; that happens for, e.g., aggregate support functions and
+ * per-column comparison operators used by RowExprs.
+ *
+ * If an estimate can be made, store it into the cost fields and return the
+ * address of the SupportRequestCost node.  Return NULL if no estimate can be
+ * made, in which case the planner will rely on the target function's procost
+ * field.  (Note: while procost is automatically scaled by cpu_operator_cost,
+ * this is not the case for the outputs of the Cost request; the support
+ * function must scale its results appropriately on its own.)
+ */
+typedef struct SupportRequestCost
+{
+   NodeTag     type;
+
+   /* Input fields: */
+   struct PlannerInfo *root;   /* Planner's infrastructure (could be NULL) */
+   Oid         funcid;         /* function we are inquiring about */
+   Node       *node;           /* parse node invoking function, or NULL */
+
+   /* Output fields: */
+   Cost        startup;        /* one-time cost */
+   Cost        per_tuple;      /* per-evaluation cost */
+} SupportRequestCost;
+
+/*
+ * The Rows request allows the support function to provide an output rowcount
+ * estimate for its target function (so it applies only to set-returning
+ * functions).
+ *
+ * The "node" argument is the parse node that is invoking the target function;
+ * currently this will always be a FuncExpr or OpExpr.
+ *
+ * If an estimate can be made, store it into the rows field and return the
+ * address of the SupportRequestRows node.  Return NULL if no estimate can be
+ * made, in which case the planner will rely on the target function's prorows
+ * field.
+ */
+typedef struct SupportRequestRows
+{
+   NodeTag     type;
+
+   /* Input fields: */
+   struct PlannerInfo *root;   /* Planner's infrastructure (could be NULL) */
+   Oid         funcid;         /* function we are inquiring about */
+   Node       *node;           /* parse node invoking function */
+
+   /* Output fields: */
+   double      rows;           /* number of rows expected to be returned */
+} SupportRequestRows;
+
 #endif                         /* SUPPORTNODES_H */
 
 extern bool contain_window_function(Node *clause);
 extern WindowFuncLists *find_window_functions(Node *clause, Index maxWinRef);
 
-extern double expression_returns_set_rows(Node *clause);
+extern double expression_returns_set_rows(PlannerInfo *root, Node *clause);
 
 extern bool contain_subplans(Node *clause);
 
 
                 JoinType jointype,
                 SpecialJoinInfo *sjinfo);
 
+extern Selectivity function_selectivity(PlannerInfo *root,
+                    Oid funcid,
+                    List *args,
+                    Oid inputcollid,
+                    bool is_join,
+                    int varRelid,
+                    JoinType jointype,
+                    SpecialJoinInfo *sjinfo);
+
+extern void add_function_cost(PlannerInfo *root, Oid funcid, Node *node,
+                 QualCost *cost);
+
+extern double get_function_rows(PlannerInfo *root, Oid funcid, Node *node);
+
 extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
 
 #endif                         /* PLANCAT_H */
 
 extern char func_parallel(Oid funcid);
 extern char get_func_prokind(Oid funcid);
 extern bool get_func_leakproof(Oid funcid);
-extern float4 get_func_cost(Oid funcid);
-extern float4 get_func_rows(Oid funcid);
+extern RegProcedure get_func_support(Oid funcid);
 extern Oid get_relname_relid(const char *relname, Oid relnamespace);
 extern char *get_rel_name(Oid relid);
 extern Oid get_rel_namespace(Oid relid);
 
 LINE 1: SELECT num_nulls();
                ^
 HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+--
+-- Test adding a support function to a subject function
+--
+CREATE FUNCTION my_int_eq(int, int) RETURNS bool
+  LANGUAGE internal STRICT IMMUTABLE PARALLEL SAFE
+  AS $$int4eq$$;
+-- By default, planner does not think that's selective
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk1 a JOIN tenk1 b ON a.unique1 = b.unique1
+WHERE my_int_eq(a.unique2, 42);
+                  QUERY PLAN                  
+----------------------------------------------
+ Hash Join
+   Hash Cond: (b.unique1 = a.unique1)
+   ->  Seq Scan on tenk1 b
+   ->  Hash
+         ->  Seq Scan on tenk1 a
+               Filter: my_int_eq(unique2, 42)
+(6 rows)
+
+-- With support function that knows it's int4eq, we get a different plan
+ALTER FUNCTION my_int_eq(int, int) SUPPORT test_support_func;
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk1 a JOIN tenk1 b ON a.unique1 = b.unique1
+WHERE my_int_eq(a.unique2, 42);
+                   QUERY PLAN                    
+-------------------------------------------------
+ Nested Loop
+   ->  Seq Scan on tenk1 a
+         Filter: my_int_eq(unique2, 42)
+   ->  Index Scan using tenk1_unique1 on tenk1 b
+         Index Cond: (unique1 = a.unique1)
+(5 rows)
+
+-- Also test non-default rowcount estimate
+CREATE FUNCTION my_gen_series(int, int) RETURNS SETOF integer
+  LANGUAGE internal STRICT IMMUTABLE PARALLEL SAFE
+  AS $$generate_series_int4$$
+  SUPPORT test_support_func;
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk1 a JOIN my_gen_series(1,1000) g ON a.unique1 = g;
+               QUERY PLAN               
+----------------------------------------
+ Hash Join
+   Hash Cond: (g.g = a.unique1)
+   ->  Function Scan on my_gen_series g
+   ->  Hash
+         ->  Seq Scan on tenk1 a
+(5 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk1 a JOIN my_gen_series(1,10) g ON a.unique1 = g;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Nested Loop
+   ->  Function Scan on my_gen_series g
+   ->  Index Scan using tenk1_unique1 on tenk1 a
+         Index Cond: (unique1 = g.g)
+(4 rows)
+
 
 --
 explain (verbose, costs off)
 select * from int4_tbl o where (f1, f1) in
-  (select f1, generate_series(1,2) / 10 g from int4_tbl i group by f1);
+  (select f1, generate_series(1,50) / 10 g from int4_tbl i group by f1);
                             QUERY PLAN                             
 -------------------------------------------------------------------
  Nested Loop Semi Join
                Output: "ANY_subquery".f1, "ANY_subquery".g
                Filter: ("ANY_subquery".f1 = "ANY_subquery".g)
                ->  Result
-                     Output: i.f1, ((generate_series(1, 2)) / 10)
+                     Output: i.f1, ((generate_series(1, 50)) / 10)
                      ->  ProjectSet
-                           Output: generate_series(1, 2), i.f1
+                           Output: generate_series(1, 50), i.f1
                            ->  HashAggregate
                                  Output: i.f1
                                  Group Key: i.f1
 (19 rows)
 
 select * from int4_tbl o where (f1, f1) in
-  (select f1, generate_series(1,2) / 10 g from int4_tbl i group by f1);
+  (select f1, generate_series(1,50) / 10 g from int4_tbl i group by f1);
  f1 
 ----
   0
 
     AS '@libdir@/regress@DLSUFFIX@', 'test_fdw_handler'
     LANGUAGE C;
 
+CREATE FUNCTION test_support_func(internal)
+    RETURNS internal
+    AS '@libdir@/regress@DLSUFFIX@', 'test_support_func'
+    LANGUAGE C STRICT;
+
 -- Things that shouldn't work:
 
 CREATE FUNCTION test1 (int) RETURNS int LANGUAGE SQL
 
     RETURNS fdw_handler
     AS '@libdir@/regress@DLSUFFIX@', 'test_fdw_handler'
     LANGUAGE C;
+CREATE FUNCTION test_support_func(internal)
+    RETURNS internal
+    AS '@libdir@/regress@DLSUFFIX@', 'test_support_func'
+    LANGUAGE C STRICT;
 -- Things that shouldn't work:
 CREATE FUNCTION test1 (int) RETURNS int LANGUAGE SQL
     AS 'SELECT ''not an integer'';';
 
 #include "access/transam.h"
 #include "access/tuptoaster.h"
 #include "access/xact.h"
+#include "catalog/pg_operator.h"
 #include "catalog/pg_type.h"
 #include "commands/sequence.h"
 #include "commands/trigger.h"
 #include "executor/executor.h"
 #include "executor/spi.h"
 #include "miscadmin.h"
+#include "nodes/supportnodes.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/plancat.h"
 #include "port/atomics.h"
 #include "utils/builtins.h"
 #include "utils/geo_decls.h"
    elog(ERROR, "test_fdw_handler is not implemented");
    PG_RETURN_NULL();
 }
+
+PG_FUNCTION_INFO_V1(test_support_func);
+Datum
+test_support_func(PG_FUNCTION_ARGS)
+{
+   Node       *rawreq = (Node *) PG_GETARG_POINTER(0);
+   Node       *ret = NULL;
+
+   if (IsA(rawreq, SupportRequestSelectivity))
+   {
+       /*
+        * Assume that the target is int4eq; that's safe as long as we don't
+        * attach this to any other boolean-returning function.
+        */
+       SupportRequestSelectivity *req = (SupportRequestSelectivity *) rawreq;
+       Selectivity s1;
+
+       if (req->is_join)
+           s1 = join_selectivity(req->root, Int4EqualOperator,
+                                 req->args,
+                                 req->inputcollid,
+                                 req->jointype,
+                                 req->sjinfo);
+       else
+           s1 = restriction_selectivity(req->root, Int4EqualOperator,
+                                        req->args,
+                                        req->inputcollid,
+                                        req->varRelid);
+
+       req->selectivity = s1;
+       ret = (Node *) req;
+   }
+
+   if (IsA(rawreq, SupportRequestCost))
+   {
+       /* Provide some generic estimate */
+       SupportRequestCost *req = (SupportRequestCost *) rawreq;
+
+       req->startup = 0;
+       req->per_tuple = 2 * cpu_operator_cost;
+       ret = (Node *) req;
+   }
+
+   if (IsA(rawreq, SupportRequestRows))
+   {
+       /*
+        * Assume that the target is generate_series_int4; that's safe as long
+        * as we don't attach this to any other set-returning function.
+        */
+       SupportRequestRows *req = (SupportRequestRows *) rawreq;
+
+       if (req->node && IsA(req->node, FuncExpr))  /* be paranoid */
+       {
+           List       *args = ((FuncExpr *) req->node)->args;
+           Node       *arg1 = linitial(args);
+           Node       *arg2 = lsecond(args);
+
+           if (IsA(arg1, Const) &&
+               !((Const *) arg1)->constisnull &&
+               IsA(arg2, Const) &&
+               !((Const *) arg2)->constisnull)
+           {
+               int32       val1 = DatumGetInt32(((Const *) arg1)->constvalue);
+               int32       val2 = DatumGetInt32(((Const *) arg2)->constvalue);
+
+               req->rows = val2 - val1 + 1;
+               ret = (Node *) req;
+           }
+       }
+   }
+
+   PG_RETURN_POINTER(ret);
+}
 
 -- should fail, one or more arguments is required
 SELECT num_nonnulls();
 SELECT num_nulls();
+
+--
+-- Test adding a support function to a subject function
+--
+
+CREATE FUNCTION my_int_eq(int, int) RETURNS bool
+  LANGUAGE internal STRICT IMMUTABLE PARALLEL SAFE
+  AS $$int4eq$$;
+
+-- By default, planner does not think that's selective
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk1 a JOIN tenk1 b ON a.unique1 = b.unique1
+WHERE my_int_eq(a.unique2, 42);
+
+-- With support function that knows it's int4eq, we get a different plan
+ALTER FUNCTION my_int_eq(int, int) SUPPORT test_support_func;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk1 a JOIN tenk1 b ON a.unique1 = b.unique1
+WHERE my_int_eq(a.unique2, 42);
+
+-- Also test non-default rowcount estimate
+CREATE FUNCTION my_gen_series(int, int) RETURNS SETOF integer
+  LANGUAGE internal STRICT IMMUTABLE PARALLEL SAFE
+  AS $$generate_series_int4$$
+  SUPPORT test_support_func;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk1 a JOIN my_gen_series(1,1000) g ON a.unique1 = g;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk1 a JOIN my_gen_series(1,10) g ON a.unique1 = g;
 
 --
 explain (verbose, costs off)
 select * from int4_tbl o where (f1, f1) in
-  (select f1, generate_series(1,2) / 10 g from int4_tbl i group by f1);
+  (select f1, generate_series(1,50) / 10 g from int4_tbl i group by f1);
 select * from int4_tbl o where (f1, f1) in
-  (select f1, generate_series(1,2) / 10 g from int4_tbl i group by f1);
+  (select f1, generate_series(1,50) / 10 g from int4_tbl i group by f1);
 
 --
 -- check for over-optimization of whole-row Var referencing an Append plan